Files
docker-zulip/entrypoint.sh
jeaye 3b5d63b9ba setup: Add certbot support.
The task is to generate a self-signed cert so Zulip can be started, then
to wait until Zulip is up before using certbot to generate new certs.
Zulip needs to be up so it can meet certbot's challenge. Using a deploy
hook, certs are persisted in the data directory. The same applies to
renewal.

Tweaked by tabbott mostly to edit comments remove an unnecessary
setting before merging.

Fixes #120.
2018-07-23 22:29:10 -07:00

599 lines
21 KiB
Bash
Executable File

#!/bin/bash
if [ "$DEBUG" = "true" ] || [ "$DEBUG" = "True" ]; then
set -x
set -o functrace
fi
set -e
# DB aka Database
DB_HOST="${DB_HOST:-127.0.0.1}"
DB_HOST_PORT="${DB_HOST_PORT:-5432}"
DB_NAME="${DB_NAME:-zulip}"
DB_USER="${DB_USER:-zulip}"
REMOTE_POSTGRES_SSLMODE="${REMOTE_POSTGRES_SSLMODE:-prefer}"
# RabbitMQ
IGNORE_RABBITMQ_ERRORS="${IGNORE_RABBITMQ_ERRORS:-true}"
SETTING_RABBITMQ_HOST="${SETTING_RABBITMQ_HOST:-127.0.0.1}"
SETTING_RABBITMQ_USER="${SETTING_RABBITMQ_USER:-zulip}"
SETTING_RABBITMQ_PASSWORD="${SETTING_RABBITMQ_PASSWORD:-zulip}"
SECRETS_rabbitmq_password="${SECRETS_rabbitmq_password:-$(echo $SETTING_RABBITMQ_PASSWORD)}"
unset SETTING_RABBITMQ_PASSWORD
export RABBITMQ_NODE="$SETTING_RABBITMQ_HOST"
# Redis
SETTING_RATE_LIMITING="${SETTING_RATE_LIMITING:-True}"
SETTING_REDIS_HOST="${SETTING_REDIS_HOST:-127.0.0.1}"
SETTING_REDIS_PORT="${SETTING_REDIS_PORT:-6379}"
# Memcached
if [ -z "$SETTING_MEMCACHED_LOCATION" ]; then
SETTING_MEMCACHED_LOCATION="127.0.0.1:11211"
fi
# Nginx settings
DISABLE_HTTPS="${DISABLE_HTTPS:-false}"
NGINX_WORKERS="${NGINX_WORKERS:-2}"
NGINX_PROXY_BUFFERING="${NGINX_PROXY_BUFFERING:-off}"
NGINX_MAX_UPLOAD_SIZE="${NGINX_MAX_UPLOAD_SIZE:-24m}"
# Zulip certifcate parameters
SSL_CERTIFICATE_GENERATION="${SSL_CERTIFICATE_GENERATION:self-signed}"
# Zulip related settings
ZULIP_AUTH_BACKENDS="${ZULIP_AUTH_BACKENDS:-EmailAuthBackend}"
ZULIP_RUN_POST_SETUP_SCRIPTS="${ZULIP_RUN_POST_SETUP_SCRIPTS:-True}"
# Zulip user setup
FORCE_FIRST_START_INIT="${FORCE_FIRST_START_INIT:-False}"
# Auto backup settings
AUTO_BACKUP_ENABLED="${AUTO_BACKUP_ENABLED:-True}"
AUTO_BACKUP_INTERVAL="${AUTO_BACKUP_INTERVAL:-30 3 * * *}"
# Zulip configuration function specific variable(s)
SPECIAL_SETTING_DETECTION_MODE="${SPECIAL_SETTING_DETECTION_MODE:-}"
MANUAL_CONFIGURATION="${MANUAL_CONFIGURATION:-false}"
LINK_SETTINGS_TO_DATA="${LINK_SETTINGS_TO_DATA:-false}"
# entrypoint.sh specific variable(s)
SETTINGS_PY="/etc/zulip/settings.py"
# BEGIN appRun functions
# === initialConfiguration ===
prepareDirectories() {
if [ ! -d "$DATA_DIR" ]; then
mkdir -p "$DATA_DIR"
fi
if [ ! -d "$DATA_DIR/backups" ]; then
echo "Creating backups folder ..."
mkdir -p "$DATA_DIR/backups"
echo "Created backups folder."
fi
if [ ! -d "$DATA_DIR/certs" ]; then
echo "Creating certs folder ..."
mkdir -p "$DATA_DIR/certs"
echo "Created certs folder."
fi
if [ ! -d "$DATA_DIR/uploads" ]; then
echo "Creating uploads folder ..."
mkdir -p "$DATA_DIR/uploads"
echo "Created uploads folder."
fi
echo "Preparing and linking the uploads folder ..."
rm -rf /home/zulip/uploads
ln -sfT "$DATA_DIR/uploads" /home/zulip/uploads
chown zulip:zulip -R "$DATA_DIR/uploads"
# Link settings folder
if [ "$LINK_SETTINGS_TO_DATA" = "True" ] || [ "$LINK_SETTINGS_TO_DATA" = "true" ]; then
# Create settings directories
if [ ! -d "$DATA_DIR/settings" ]; then
mkdir -p "$DATA_DIR/settings"
fi
if [ ! -d "$DATA_DIR/settings/etc-zulip" ]; then
cp -rf /etc/zulip "$DATA_DIR/settings/etc-zulip"
fi
# Link /etc/zulip/ settings folder
rm -rf /etc/zulip
ln -sfT "$DATA_DIR/settings/etc-zulip" /etc/zulip
fi
echo "Prepared and linked the uploads directory."
}
setConfigurationValue() {
if [ -z "$1" ]; then
echo "No KEY given for setConfigurationValue."
return 1
fi
if [ -z "$3" ]; then
echo "No FILE given for setConfigurationValue."
return 1
fi
local KEY="$1"
local VALUE
local FILE="$3"
local TYPE="$4"
if [ -z "$TYPE" ]; then
case "$2" in
[Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]|[Nn]one)
TYPE="bool"
;;
[0-9]*)
TYPE="integer"
;;
[\[\(]*[\]\)])
TYPE="array"
;;
*)
TYPE="string"
;;
esac
fi
case "$TYPE" in
emptyreturn)
if [ -z "$2" ]; then
return 0
fi
;;
literal)
VALUE="$1"
;;
bool|boolean|int|integer|array)
VALUE="$KEY = $2"
;;
string|*)
VALUE="$KEY = '${2//\'/\'}'"
;;
esac
echo "$VALUE" >> "$FILE"
echo "Setting key \"$KEY\", type \"$TYPE\" in file \"$FILE\"."
}
nginxConfiguration() {
echo "Executing nginx configuration ..."
if [ "$DISABLE_HTTPS" == "True" ] || [ "$DISABLE_HTTPS" == "true" ]; then
echo "Disabling https in nginx."
crudini --set /etc/zulip/zulip.conf application_server http_only true
/home/zulip/deployments/current/scripts/zulip-puppet-apply -f
fi
sed -i "s/worker_processes .*/worker_processes $NGINX_WORKERS;/g" /etc/nginx/nginx.conf
sed -i "s/client_max_body_size .*/client_max_body_size $NGINX_MAX_UPLOAD_SIZE;/g" /etc/nginx/nginx.conf
sed -i "s/proxy_buffering .*/proxy_buffering $NGINX_PROXY_BUFFERING;/g" /etc/nginx/zulip-include/proxy_longpolling
echo "Nginx configuration succeeded."
}
configureCerts() {
case "$SSL_CERTIFICATE_GENERATION" in
self-signed)
GENERATE_SELF_SIGNED_CERT="True"
GENERATE_CERTBOT_CERT="False"
;;
certbot)
GENERATE_SELF_SIGNED_CERT="False"
GENERATE_CERTBOT_CERT="True"
;;
*)
echo "Not requesting auto-generated self-signed certs."
GENERATE_CERTBOT_CERT="False"
GENERATE_SELF_SIGNED_CERT="False"
;;
esac
if [ ! -e "$DATA_DIR/certs/zulip.key" ] && [ ! -e "$DATA_DIR/certs/zulip.combined-chain.crt" ]; then
if [ "$GENERATE_CERTBOT_CERT" = "True" ]; then
# Zulip isn't yet running, so the certbot's challenge can't be met.
# We'll schedule this for later.
echo "Scheduling LetsEncrypt cert generation ..."
GENERATE_CERTBOT_CERT_SCHEDULED=True
# Generate self-signed certs just to get Zulip going.
GENERATE_SELF_SIGNED_CERT=True
fi
if [ "$GENERATE_SELF_SIGNED_CERT" = "True" ]; then
echo "Generating self-signed certificates ..."
mkdir -p "$DATA_DIR/certs"
/home/zulip/deployments/current/scripts/setup/generate-self-signed-cert "$SETTING_EXTERNAL_HOST"
mv /etc/ssl/private/zulip.key "$DATA_DIR/certs/zulip.key"
mv /etc/ssl/certs/zulip.combined-chain.crt "$DATA_DIR/certs/zulip.combined-chain.crt"
echo "Self-signed certificate generation succeeded."
else
echo "Certificates already exist. No need to generate them. Continuing."
fi
fi
if [ ! -e "$DATA_DIR/certs/zulip.key" ]; then
echo "SSL private key zulip.key is not present in $DATA_DIR."
echo "Certificates configuration failed."
echo "Consider setting ZULIP_AUTO_GENERATE_CERTS=True in the environment to auto-generate"
exit 1
fi
if [ ! -e "$DATA_DIR/certs/zulip.combined-chain.crt" ]; then
echo "SSL public key zulip.combined-chain.crt is not present in $DATA_DIR."
echo "Certificates configuration failed."
echo "Consider setting ZULIP_AUTO_GENERATE_CERTS=True in the environment to auto-generate"
exit 1
fi
ln -sfT "$DATA_DIR/certs/zulip.key" /etc/ssl/private/zulip.key
ln -sfT "$DATA_DIR/certs/zulip.combined-chain.crt" /etc/ssl/certs/zulip.combined-chain.crt
echo "Certificates configuration succeeded."
}
secretsConfiguration() {
echo "Setting Zulip secrets ..."
if [ ! -e "$DATA_DIR/zulip-secrets.conf" ]; then
echo "Generating Zulip secrets ..."
/root/zulip/scripts/setup/generate_secrets.py --production
mv "/etc/zulip/zulip-secrets.conf" "$DATA_DIR/zulip-secrets.conf" || {
echo "Couldn't move the generate zulip secrets to the data dir."; exit 1;
}
echo "Secrets generation succeeded."
else
echo "Secrets already generated/existing."
fi
set +e
local SECRETS=($(env | sed -nr "s/SECRETS_([0-9A-Z_a-z-]*).*/\1/p"))
for SECRET_KEY in "${SECRETS[@]}"; do
local key="SECRETS_$SECRET_KEY"
local SECRET_VAR="${!key}"
if [ -z "$SECRET_VAR" ]; then
echo "Empty secret for key \"$SECRET_KEY\"."
fi
grep -q "$SECRET_KEY" "$DATA_DIR/zulip-secrets.conf"
if (($? > 0)); then
echo "$SECRET_KEY = $SECRET_VAR" >> "$DATA_DIR/zulip-secrets.conf"
echo "Secret added for \"$SECRET_KEY\"."
else
sed -i -r "s~#?$SECRET_KEY[ ]*=.*~$SECRET_KEY = $SECRET_VAR~g" "$DATA_DIR/zulip-secrets.conf"
echo "Secret found for \"$SECRET_KEY\"."
fi
done
set -e
unset SECRET_KEY SECRET_VAR key
if [ -e "/etc/zulip/zulip-secrets.conf" ]; then
rm "/etc/zulip/zulip-secrets.conf"
fi
echo "Linking secrets from data dir to etc zulip ..."
ln -s "$DATA_DIR/zulip-secrets.conf" "/etc/zulip/zulip-secrets.conf" || {
echo "Couldn't link existing zulip secrets to etc zulip.";
exit 1;
}
echo "Linked existing secrets from data dir to etc zulip."
echo "Zulip secrets configuration succeeded."
}
databaseConfiguration() {
echo "Setting database configuration ..."
setConfigurationValue "REMOTE_POSTGRES_HOST" "$DB_HOST" "$SETTINGS_PY" "string"
setConfigurationValue "REMOTE_POSTGRES_SSLMODE" "$REMOTE_POSTGRES_SSLMODE" "$SETTINGS_PY" "string"
# The password will be set in secretsConfiguration
echo "Database configuration succeeded."
}
authenticationBackends() {
echo "Activating authentication backends ..."
local FIRST=true
echo "$ZULIP_AUTH_BACKENDS" | sed -n 1'p' | tr ',' '\n' | while read AUTH_BACKEND; do
if [ "$FIRST" = true ]; then
setConfigurationValue "AUTHENTICATION_BACKENDS" "('zproject.backends.${AUTH_BACKEND//\'/\'}',)" "$SETTINGS_PY" "array"
FIRST=false
else
setConfigurationValue "AUTHENTICATION_BACKENDS += ('zproject.backends.${AUTH_BACKEND//\'/\'}',)" "" "$SETTINGS_PY" "literal"
fi
echo "Adding authentication backend \"$AUTH_BACKEND\"."
done
echo "Authentication backend activation succeeded."
}
zulipConfiguration() {
echo "Executing Zulip configuration ..."
if [ ! -z "$ZULIP_CUSTOM_SETTINGS" ]; then
echo -e "\n$ZULIP_CUSTOM_SETTINGS" >> "$SETTINGS_PY"
fi
local given_settings=($(env | sed -n -r "s/SETTING_([0-9A-Za-z_]*).*/\1/p"))
for setting_key in "${given_settings[@]}"; do
local key="SETTING_$setting_key"
local setting_var="${!key}"
local type="string"
if [ -z "$setting_var" ]; then
echo "Empty var for key \"$setting_key\"."
continue
fi
# Zulip settings.py / zproject specific overrides here
if [ "$setting_key" = "AUTH_LDAP_CONNECTION_OPTIONS" ] || \
[ "$setting_key" = "AUTH_LDAP_USER_SEARCH" ] || \
[ "$setting_key" = "AUTH_LDAP_USER_ATTR_MAP" ] || \
([ "$setting_key" = "LDAP_APPEND_DOMAIN" ] && [ "$setting_var" = "None" ]) || \
[ "$setting_key" = "SECURE_PROXY_SSL_HEADER" ] || \
[[ "$setting_key" = "CSRF_"* ]] || \
[ "$setting_key" = "ALLOWED_HOSTS" ]; then
type="array"
fi
if ([ "$SPECIAL_SETTING_DETECTION_MODE" = "True" ] || [ "$SPECIAL_SETTING_DETECTION_MODE" = "true" ]) || \
[ "$type" = "string" ]; then
type=""
fi
if [ "$setting_key" = "EMAIL_HOST_USER" ] || \
[ "$setting_key" = "EMAIL_HOST_PASSWORD" ] || \
[ "$setting_key" = "EXTERNAL_HOST" ]; then
type="string"
fi
setConfigurationValue "$setting_key" "$setting_var" "$SETTINGS_PY" "$type"
done
unset setting_key setting_var
su zulip -c "/home/zulip/deployments/current/manage.py checkconfig"
if [[ $? != 0 ]]; then
echo "Error in the Zulip configuration. Exiting."
exit 1
fi
echo "Zulip configuration succeeded."
}
autoBackupConfiguration() {
if ([ "$AUTO_BACKUP_ENABLED" != "True" ] && [ "$AUTO_BACKUP_ENABLED" != "true" ]); then
rm -f /etc/cron.d/autobackup
echo "Auto backup is disabled. Continuing."
return 0
fi
echo "MAILTO=""\n$AUTO_BACKUP_INTERVAL cd /;/entrypoint.sh app:backup" > /etc/cron.d/autobackup
echo "Auto backup enabled."
}
initialConfiguration() {
echo "=== Begin Initial Configuration Phase ==="
prepareDirectories
nginxConfiguration
configureCerts
if [ "$MANUAL_CONFIGURATION" = "False" ] || [ "$MANUAL_CONFIGURATION" = "false" ]; then
databaseConfiguration
secretsConfiguration
authenticationBackends
zulipConfiguration
fi
autoBackupConfiguration
echo "=== End Initial Configuration Phase ==="
}
# === bootstrappingEnvironment ===
waitingForDatabase() {
export PGPASSWORD="$SECRETS_postgres_password"
local TIMEOUT=60
echo "Waiting for database server to allow connections ..."
while ! /usr/bin/pg_isready -h "$DB_HOST" -p "$DB_HOST_PORT" -U "$DB_USER" -t 1 >/dev/null 2>&1
do
TIMEOUT=$(expr $TIMEOUT - 1)
if [[ $TIMEOUT -eq 0 ]]; then
echo "Could not connect to database server. Exiting."
unset PGPASSWORD
exit 1
fi
echo -n "."
sleep 1
done
unset PGPASSWORD
}
zulipFirstStartInit() {
echo "Executing Zulip first start init ..."
if [ -e "$DATA_DIR/.initiated" ] && ([ "$FORCE_FIRST_START_INIT" != "True" ] && [ "$FORCE_FIRST_START_INIT" != "true" ]); then
echo "First Start Init not needed. Continuing."
return 0
fi
local RETURN_CODE=0
set +e
su zulip -c /home/zulip/deployments/current/scripts/setup/initialize-database
RETURN_CODE=$?
if [[ $RETURN_CODE != 0 ]]; then
echo "Zulip first start database initi failed in \"initialize-database\" exit code $RETURN_CODE. Exiting."
exit $RETURN_CODE
fi
set -e
touch "$DATA_DIR/.initiated"
echo "Zulip first start init sucessful."
}
zulipMigration() {
echo "Migrating Zulip to new version ..."
set +e
su zulip -c "/home/zulip/deployments/current/manage.py migrate --noinput"
local RETURN_CODE=$?
if [[ $RETURN_CODE != 0 ]]; then
echo "Zulip migration failed with exit code $RETURN_CODE. Exiting."
exit $RETURN_CODE
fi
set -e
rm -rf "$DATA_DIR/.zulip-*"
touch "$DATA_DIR/.zulip-$ZULIP_VERSION"
echo "Zulip migration succeeded."
}
runPostSetupScripts() {
echo "Post setup scripts execution ..."
if ([ "$ZULIP_RUN_POST_SETUP_SCRIPTS" != "True" ] && [ "$ZULIP_RUN_POST_SETUP_SCRIPTS" != "true" ]); then
echo "Not running post setup scripts. ZULIP_RUN_POST_SETUP_SCRIPTS isn't true."
return 0
fi
if [ ! -d "$DATA_DIR/post-setup.d/" ]; then
echo "No post-setup.d folder found. Continuing."
return 0
fi
if [ ! "$(ls "$DATA_DIR/post-setup.d/")" ]; then
echo "No post setup scripts found in \"$DATA_DIR/post-setup.d/\"."
return 0
fi
set +e
for file in $DATA_DIR/post-setup.d/*; do
if [ -x "$file" ]; then
echo "Executing \"$file\" ..."
bash -c "$file"
echo "Executed \"$file\". Return code $?."
else
echo "Permissions denied for \"$file\". Please check the permissions. Exiting."
exit 1
fi
done
set -e
echo "Post setup scripts execution succeeded."
}
function runCertbotAsNeeded() {
if [ ! "$GENERATE_CERTBOT_CERT_SCHEDULED" = "True" ]; then
echo "Certbot is not scheduled to run."
return
fi
echo "Waiting for nginx to come online before generating certbot certificate ..."
while ! curl -sk "$SETTING_EXTERNAL_HOST" >/dev/null 2>&1; do
sleep 1;
done
echo "Generating LetsEncrypt/certbot certificate ..."
# Remove the self-signed certs which were only needed to get Zulip going.
rm -f "$DATA_DIR"/certs/zulip.key "$DATA_DIR"/certs/zulip.combined-chain.crt
ZULIP_CERTBOT_DEPLOY_HOOK="/sbin/certbot-deploy-hook"
# Accept the terms of service automatically.
/home/zulip/deployments/current/scripts/setup/setup-certbot \
--agree-tos \
--hostname="$SETTING_EXTERNAL_HOST" \
--email="$SETTING_ZULIP_ADMINISTRATOR" \
--deploy-hook "$ZULIP_CERTBOT_DEPLOY_HOOK"
echo "LetsEncrypt cert generated."
}
bootstrappingEnvironment() {
echo "=== Begin Bootstrap Phase ==="
waitingForDatabase
zulipFirstStartInit
zulipMigration
runPostSetupScripts
# Hack: We run this in the background, since we need nginx to be
# started before we can create the certificate. See #142 for
# details on how we can clean this up.
runCertbotAsNeeded &
echo "=== End Bootstrap Phase ==="
}
# END appRun functions
# BEGIN app functions
appRun() {
initialConfiguration
bootstrappingEnvironment
echo "=== Begin Run Phase ==="
echo "Starting Zulip using supervisor with \"/etc/supervisor/supervisord.conf\" config ..."
echo ""
exec supervisord -n -c "/etc/supervisor/supervisord.conf"
}
appManagePy() {
COMMAND="$1"
shift 1
if [ -z "$COMMAND" ]; then
echo "No command given for manage.py. Defaulting to \"shell\"."
COMMAND="shell"
fi
echo "Running manage.py ..."
set +e
exec su zulip -c "/home/zulip/deployments/current/manage.py $COMMAND $*"
}
appBackup() {
echo "Starting backup process ..."
if [ -d "/tmp/backup-$(date "%D-%H-%M-%S")" ]; then
echo "Temporary backup folder for \"$(date "%D-%H-%M-%S")\" already exists. Aborting."
echo "Backup process failed. Exiting."
exit 1
fi
local BACKUP_FOLDER
BACKUP_FOLDER="/tmp/backup-$(date "%D-%H-%M-%S")"
mkdir -p "$BACKUP_FOLDER"
waitingForDatabase
pg_dump -h "$DB_HOST" -p "$DB_HOST_PORT" -U "$DB_USER" "$DB_NAME" > "$BACKUP_FOLDER/database-postgres.sql"
tar -zcvf "$DATA_DIR/backups/backup-$(date "%D-%H-%M-%S").tar.gz" "$BACKUP_FOLDER/"
rm -r "${BACKUP_FOLDER:?}/"
echo "Backup process succeeded."
exit 0
}
appRestore() {
echo "Starting restore process ..."
if [ "$(ls -A "$DATA_DIR/backups/")" ]; then
echo "No backups to restore found in \"$DATA_DIR/backups/\"."
echo "Restore process failed. Exiting."
exit 1
fi
while true; do
ls "$DATA_DIR/backups/" | awk '{print "|-> " $1}'
echo "Please enter backup filename (full filename with extension): "
read BACKUP_FILE
if [ -z "$BACKUP_FILE" ]; then
echo "Empty filename given. Please try again."
echo ""
continue
fi
if [ ! -e "$DATA_DIR/backups/$BACKUP_FILE" ]; then
echo "File \"$BACKUP_FILE\" not found. Please try again."
echo ""
fi
break
done
echo "File \"$BACKUP_FILE\" found."
echo ""
echo "==============================================================="
echo "!! WARNING !! Your current data will be deleted!"
echo "!! WARNING !! YOU HAVE BEEN WARNED! You can abort with \"CTRL+C\"."
echo "!! WARNING !! Waiting 10 seconds before continuing ..."
echo "==============================================================="
echo ""
local TIMEOUT=11
while true; do
TIMEOUT=$(expr $TIMEOUT - 1)
if [[ $TIMEOUT -eq 0 ]]; then
break
fi
echo "$TIMEOUT"
sleep 1
done
echo "!! WARNING !! Starting restore process ... !! WARNING !!"
waitingForDatabase
tar -zxvf "$DATA_DIR/backups/$BACKUP_FILE" -C /tmp
psql -h "$DB_HOST" -p "$DB_HOST_PORT" -U "$DB_USER" "$DB_NAME" < "/tmp/$(basename "$BACKUP_FILE" | cut -d. -f1)/database-postgres.sql"
rm -r "/tmp/$(basename | cut -d. -f1)/"
echo "Restore process succeeded. Exiting."
exit 0
}
appCerts() {
configureCerts
}
appHelp() {
echo "Available commands:"
echo "> app:help - Show this help menu and exit"
echo "> app:version - Container Zulip server version"
echo "> app:managepy - Run Zulip's manage.py script (defaults to \"shell\")"
echo "> app:backup - Create backups of Zulip instances"
echo "> app:restore - Restore backups of Zulip instances"
echo "> app:certs - Create self-signed certificates"
echo "> app:run - Run the Zulip server"
echo "> [COMMAND] - Run given command with arguments in shell"
}
appVersion() {
echo "This container contains:"
echo "> Zulip server $ZULIP_VERSION"
echo "> Checksum: $ZULIP_CHECKSUM"
exit 0
}
# END app functions
case "$1" in
app:run)
appRun
;;
app:managepy)
shift 1
appManagePy "$@"
;;
app:backup)
appBackup
;;
app:restore)
appRestore
;;
app:certs)
appCerts
;;
app:help)
appHelp
;;
app:version)
appVersion
;;
*)
if [[ -x $1 ]]; then
$1
else
COMMAND="$1"
if [[ -n $(which $COMMAND) ]] ; then
shift 1
exec "$(which $COMMAND)" "$@"
else
appHelp
fi
fi
;;
esac