Skip to content

Instantly share code, notes, and snippets.

@AvinashReddy3108
Last active March 27, 2025 17:31
Show Gist options
  • Save AvinashReddy3108/023070f1a67bd582053d59fa84bed280 to your computer and use it in GitHub Desktop.
Save AvinashReddy3108/023070f1a67bd582053d59fa84bed280 to your computer and use it in GitHub Desktop.
An over-engineered script to help me setup a Frappe (v15) development stack using Podman.
#!/usr/bin/env bash
cd "$(dirname "$(readlink -f "$0")")" || exit
[ "$EUID" = "0" ] && exit
# FAIL FAST!
set -Eeuo pipefail
# A simple(?) script to set up Frappe Dev stack using Podman
# Check prerequisites
declare -A DEPENDENCIES=(
['podman']='podman'
['whiptail']='whiptail/newt'
['xxd']='vim/tinyxxd'
)
for dep_bin in "${!DEPENDENCIES[@]}"; do
if ! command -v "$dep_bin" &> /dev/null; then
echo "error: command $dep_bin not found, make sure ${DEPENDENCIES[$dep_bin]} is installed!"
exit 1
fi
done
# Check SSH Agent Forwarding
# we need this to be able to interact with Git services via SSH
# References:
# - https://docs.github.com/en/authentication/connecting-to-github-with-ssh/checking-for-existing-ssh-keys
# - https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent
# - https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account
# - https://docs.github.com/en/authentication/connecting-to-github-with-ssh/using-ssh-agent-forwarding
[ -n "SSH_AUTH_SOCK" ] || { echo "the SSH agent forwarding seems to be inactive, exiting.."; exit 1; }
# ========== HELPER FUNCTIONS ==========
# whiptail needs this
export TERM=ansi
_radiolist()
{
whiptail \
--radiolist \
"${1}" 0 0 0 \
"${@:2}" \
3>&1 1>&2 2>&3
}
_yesno()
{
whiptail \
--yesno "${1}" 0 0 \
3>&1 1>&2 2>&3
echo $?
}
_inputbox()
{
whiptail \
--inputbox "${1}" 0 0 "${2}" \
3>&1 1>&2 2>&3
}
_progress()
{
echo "${2}" \
| whiptail \
--gauge "${1}" 0 0 0
}
_msgbox()
{
whiptail \
--msgbox "${1}" 0 0 \
3>&1 1>&2 2>&3
}
# ======================================
# ========== CONFIGURATION ==========
STACK_VERSION="version-15"; STACK_POD="v15"
# Define your custom VSCode binary path here (uses system 'code' if not set)
#VSCODE='${HOME}/Tools/VSCode/bin/code'
MODE="$(_radiolist "Select Stack:" 'frappe' "Frappe Barebones" ON 'erpnext' "Frappe + ERPNext" OFF)"
[ "$MODE" = 'erpnext' ] && ERPNEXT_HRMS="$(_yesno "Install FrappeHR?")" || ERPNEXT_HRMS="1" # Y: 0; N: 1
POD="$(_inputbox "What name would you like to use for the pod?" "${MODE}_dev-${STACK_POD}-xxxxxxxxxxx")"
NETWORK="${POD}.network"; SITE_NAME="$(echo $POD | cut -d- -f3)_${STACK_POD}"
FRONTEND_PORT="$(_inputbox "Select the Frontend port for ${SITE_NAME}" "8080")"
SOCKETIO_PORT="$(_inputbox "Select the SocketIO port for ${SITE_NAME}" "9090")"
LOG_FILE="${SITE_NAME}-$(date +%Y%m%d-%H%M%S).log"
# ===================================
# Logging
exec 4> "${LOG_FILE}"
export BASH_XTRACEFD=4
set -x
_log_output()
{
"$@" 2>&1 &>> "${LOG_FILE}"
}
# ========== CONTAINER VERSIONS ==========
declare -A VERSIONS=(
['frappe/bench']='latest'
['mariadb']='10.6'
['redis']='6.2-alpine'
)
# ========================================
# Network
_log_output podman --log-level=warn network create "${NETWORK}"
# Modified from: https://github.com/containers/podman/issues/21681#issuecomment-2093205155
host_gateway=$(podman network inspect -f "{{range .Subnets}}{{.Gateway}}{{end}}" "${NETWORK}" 2>/dev/null)
# Create Pod to contain the stack
_log_output podman --log-level=warn pod create \
--name "${POD}" \
--network "${NETWORK}" \
--userns=keep-id:uid=$(id -u),gid=$(id -g) \
-p ${FRONTEND_PORT}:8000 \
-p ${SOCKETIO_PORT}:${SOCKETIO_PORT} \
--add-host "${SITE_NAME}.localhost:${host_gateway}"
# BEGIN: Database (MariaDB)
_progress "Setting up MariaDB (${VERSIONS['mariadb']})" "0"
_log_output podman --log-level=warn run --detach \
--name="${POD}".database --pod "${POD}" \
--user=mysql:mysql \
-e MYSQL_ROOT_PASSWORD=admin \
-v "${POD}.volumes.mariadb.data":'/var/lib/mysql':Z,U \
--healthcheck-command '/bin/sh -c mysqladmin ping -h localhost --password=admin' \
--healthcheck-interval 1s --healthcheck-retries 15 \
--restart 'on-failure' \
--expose 3306 \
docker.io/mariadb:"${VERSIONS['mariadb']}" \
--character-set-server=utf8mb4 \
--collation-server=utf8mb4_unicode_ci \
--skip-character-set-client-handshake \
--skip-innodb-read-only-compressed
# END
# BEGIN: Redis (Cache, Queue)
_progress "Setting up Redis (${VERSIONS['redis']})" "15"
declare -A REDIS_CONTAINERS=(
['cache']=63791
['queue']=63792
)
for CONTAINER in "${!REDIS_CONTAINERS[@]}"; do
_log_output podman --log-level=warn run --detach \
--name="${POD}".redis-"${CONTAINER}" --pod "${POD}" \
--user redis:redis \
-v "${POD}.volumes.redis.${CONTAINER}.data":'/data':Z,U \
--restart 'on-failure' \
--expose "${REDIS_CONTAINERS[$CONTAINER]}" \
docker.io/redis:"${VERSIONS['redis']}" --port "${REDIS_CONTAINERS[$CONTAINER]}"
done
# END
# BEGIN: [ONESHOT] Create Bench
_progress "Creating Bench ($STACK_VERSION, name: frappe-bench)" "30"
_log_output podman --log-level=warn run --tty --rm \
--name="${POD}".mkbench --pod "${POD}" \
--user frappe:frappe \
-v "${POD}.volumes.home":'/home/frappe':Z,U \
-e FRAPPE_REPO='https://github.com/frappe/frappe' \
-e FRAPPE_BRANCH="$STACK_VERSION" \
--entrypoint 'bash' docker.io/frappe/bench:"${VERSIONS['frappe/bench']}" \
-c 'bench init --skip-redis-config-generation \
--frappe-path="$FRAPPE_REPO" \
--frappe-branch="$FRAPPE_BRANCH" \
frappe-bench'
# END
# BEGIN: [ONESHOT] Bench Configurator
_progress "Configuring Bench" "45"
_log_output podman --log-level=warn run --interactive --tty --rm \
--name="${POD}".benchcfg --pod "${POD}" \
-v "${POD}.volumes.home":'/home/frappe':Z,U \
--user frappe:frappe \
-e DB_HOST="${POD}".database \
-e DB_PORT=3306 \
-e REDIS_CACHE="${POD}".redis-cache:63791 \
-e REDIS_QUEUE="${POD}".redis-queue:63792 \
-e SOCKETIO_PORT=${SOCKETIO_PORT} \
-w '/home/frappe/frappe-bench' \
--entrypoint 'bash' docker.io/frappe/bench:"${VERSIONS['frappe/bench']}" \
-c 'bench set-config -g db_host $DB_HOST; \
bench set-config -gp db_port $DB_PORT; \
bench set-config -g redis_cache "redis://$REDIS_CACHE"; \
bench set-config -g redis_queue "redis://$REDIS_QUEUE"; \
bench set-config -g redis_socketio "redis://$REDIS_QUEUE"; \
bench set-config -gp socketio_port $SOCKETIO_PORT; \
bench set-config -gp developer_mode 1;'
# END
# BEGIN: [ONESHOT] Site Creation
_progress "Setting up a site in the Bench (name: ${SITE_NAME}.localhost)" "60"
[ "$MODE" = 'erpnext' ] && ERPNEXT_CMD="bench get-app erpnext --branch ${STACK_VERSION} && bench install-app erpnext" || ERPNEXT_CMD="true"
[ "$ERPNEXT_HRMS" = '0' ] && HRMS_CMD="bench get-app hrms --branch ${STACK_VERSION} && bench install-app hrms" || HRMS_CMD="true"
_log_output podman --log-level=warn run --tty --rm \
--name="${POD}".mksite --pod "${POD}" \
--user frappe:frappe \
-v "${POD}.volumes.home":'/home/frappe':Z,U \
-w '/home/frappe/frappe-bench' \
--entrypoint 'bash' docker.io/frappe/bench:"${VERSIONS['frappe/bench']}" \
-c "bench new-site \
--mariadb-user-host-login-scope='%' \
--admin-password=admin \
--mariadb-root-password=admin \
--set-default ${SITE_NAME}.localhost; ${ERPNEXT_CMD}; ${HRMS_CMD}"
# END
# BEGIN: Backend
_progress "Setting up Backend" "75"
_log_output podman --log-level=warn run --detach \
--name="${POD}".backend --pod "${POD}" \
--user frappe:frappe \
-v "${POD}.volumes.home":'/home/frappe':Z,U \
-v ${SSH_AUTH_SOCK}:${SSH_AUTH_SOCK}:Z \
-e SSH_AUTH_SOCK=$SSH_AUTH_SOCK \
-w '/home/frappe/frappe-bench' \
--restart 'unless-stopped' \
--expose 8000 --expose ${SOCKETIO_PORT} \
--entrypoint 'bash' docker.io/frappe/bench:"${VERSIONS['frappe/bench']}" \
-c 'tail -f /dev/null'
# END
# Stop Pod
_progress "We're almost done, shutting down the containers in the stack.." "90"
_log_output podman --log-level=warn pod stop "${POD}"
# Write handy launcher script
CONTAINER_HASH="$(printf "${POD}".backend | xxd -p -c 32)"
cat << EOF > "launch-${SITE_NAME}.sh"
#!/usr/bin/env bash
# ===== NOTES =====
# Site URL: http://${SITE_NAME}.localhost:${FRONTEND_PORT}
# SocketIO: ws://${SITE_NAME}.localhost:${SOCKETIO_PORT}
# =================
# Start the pod
podman pod start ${POD}
# Wait for a while
sleep 3
# Open a specialized VSCode profile
"${VSCODE:-code}" --profile ${SITE_NAME} --folder-uri 'vscode-remote://attached-container+${CONTAINER_HASH}/home/frappe/frappe-bench'
EOF
chmod +x "launch-${SITE_NAME}.sh"
# We're done!
_msgbox "Pod: ${POD}
Launch script written to: launch-${SITE_NAME}.sh
Site URL: http://${SITE_NAME}.localhost:${FRONTEND_PORT}
SocketIO: ws://${SITE_NAME}.localhost:${SOCKETIO_PORT}
Log file: ${LOG_FILE}"
# End logging
set +x
exec 4>&-
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment