|
#!/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>&- |