outline of what should be a working recipe

This commit is contained in:
brooke 2025-02-12 18:21:53 -05:00
parent 090874acca
commit 28b67afc2b
8 changed files with 793 additions and 27 deletions

View File

@ -2,10 +2,22 @@ TYPE=zulip
DOMAIN=zulip.example.com
## Domain aliases
#EXTRA_DOMAINS=', `www.zulip.example.com`'
LETS_ENCRYPT_ENV=production
SECRET_DB_PASSWORD_VERSION=v1
SECRET_RABBITMQ_PASSWORD_VERSION=v1
SECRET_REDIS_PASSWORD_VERSION=v1
SECRET_SMTP_PASSWORD_VERSION=v1
SECRET_ZULIP_SECRET_VERSION=v1
SECRET_MEMCACHED_PASSWORD_VERSION=v1
#######################
##### SMTP CONFIG #####
#######################
SETTING_EMAIL_HOST: "smtp.example.com"
SETTING_EMAIL_HOST_USER: "user"
SETTING_EMAIL_PORT: "587" # STARTTLS = 587 SSL/TLS = 465
#SETTING_EMAIL_USE_SSL: "False" # implicit SSL/TLS
SETTING_EMAIL_USE_TLS: "True" # STARTTLS

View File

@ -5,9 +5,9 @@
<!-- metadata -->
* **Category**: Apps
* **Status**: 0
* **Status**: wip
* **Image**: [`zulip`](https://hub.docker.com/r/zulip), 4, upstream
* **Healthcheck**: No
* **Healthcheck**: Yes
* **Backups**: No
* **Email**: No
* **Tests**: No
@ -17,8 +17,12 @@
## Quick start
* `abra app new zulip --secrets`
* `abra app new zulip`
* `abra app secret insert <app-domain> smtp_password v1 <smtp-password>` (A working SMTP server is required for setup)
* `abra app config <app-name>`
* Populate SMTP settings by editing env variables that start with `SETTING_EMAIL`
* `abra app deploy <app-name>`
For more, see [`docs.coopcloud.tech`](https://docs.coopcloud.tech).
For more, reed [`zulip's documentation`](https://zulip.readthedocs.io/en/latest/index.html).

5
abra.sh Normal file
View File

@ -0,0 +1,5 @@
export ENTRYPOINT_VERSION=v1
export PG_BACKUP_VERSION=v1
export MEM_ENTRYPOINT_VERSION=v1
export REDIS_ENTRYPOINT_VERSION=v1
export RABBIT_HEALTHCHECK_VERSION=v1

View File

@ -5,8 +5,11 @@ services:
app:
image: "zulip/docker-zulip:9.4-0"
ports:
- "80:80"
configs:
- source: entrypoint
target: /custom-entrypoint.sh
mode: 555
entrypoint: /custom-entrypoint.sh
environment:
DB_HOST: "database"
DB_HOST_PORT: "5432"
@ -15,19 +18,8 @@ services:
SETTING_MEMCACHED_LOCATION: "memcached:11211"
SETTING_RABBITMQ_HOST: "rabbitmq"
SETTING_REDIS_HOST: "redis"
SECRETS_email_password: "123456789"
SECRETS_rabbitmq_password: "REPLACE_WITH_SECURE_RABBITMQ_PASSWORD"
SECRETS_postgres_password: "REPLACE_WITH_SECURE_POSTGRES_PASSWORD"
SECRETS_memcached_password: "REPLACE_WITH_SECURE_MEMCACHED_PASSWORD"
SECRETS_redis_password: "REPLACE_WITH_SECURE_REDIS_PASSWORD"
SECRETS_secret_key: "REPLACE_WITH_SECURE_SECRET_KEY"
SETTING_EXTERNAL_HOST: "localhost.localdomain"
SETTING_EXTERNAL_HOST: ${DOMAIN}
SETTING_ZULIP_ADMINISTRATOR: "admin@example.com"
SETTING_EMAIL_HOST: ""
SETTING_EMAIL_HOST_USER: "noreply@example.com"
SETTING_EMAIL_PORT: "587"
SETTING_EMAIL_USE_SSL: "False"
SETTING_EMAIL_USE_TLS: "True"
ZULIP_AUTH_BACKENDS: "EmailAuthBackend"
volumes:
- "zulip:/data:rw"
@ -56,13 +48,13 @@ services:
configs:
- source: pg_backup
target: /pg_backup.sh
mode: 0555
mode: 555
healthcheck:
test: [ "CMD-SHELL", "pg_isready" ]
interval: 10s
timeout: 5s
retries: 5
memcached:
image: "memcached:alpine"
command:
@ -73,35 +65,53 @@ services:
echo "zulip@$$HOSTNAME:$$MEMCACHED_PASSWORD" > "$$MEMCACHED_SASL_PWDB"
echo "zulip@localhost:$$MEMCACHED_PASSWORD" >> "$$MEMCACHED_SASL_PWDB"
exec memcached -S
configs:
- source: memcached_entrypoint
target: /custom-entrypoint.sh
mode: 555
entrypoint: /custom-entrypoint.sh
secrets:
- memcached_password
environment:
SASL_CONF_PATH: "/home/memcache/memcached.conf"
MEMCACHED_SASL_PWDB: "/home/memcache/memcached-sasl-db"
MEMCACHED_PASSWORD: "REPLACE_WITH_SECURE_MEMCACHED_PASSWORD"
rabbitmq:
image: "rabbitmq:3.12.14"
environment:
RABBITMQ_DEFAULT_USER: "zulip"
RABBITMQ_DEFAULT_PASS_FILE: "/run/secrets/rabbitmq_password"
configs:
- source: rabbitmq_healthcheck
target: /healthcheck.sh
mode: 555
secrets:
- rabbitmq_password
volumes:
- "rabbitmq:/var/lib/rabbitmq:rw"
healthcheck:
test: [ "CMD-SHELL", "/healthcheck.sh" ]
interval: 10s
timeout: 5s
retries: 3
redis:
image: "redis:alpine"
configs:
- source: redis_entrypoint
target: /custom-entrypoint.sh
mode: 555
entrypoint: /custom-entrypoint.sh
command:
- "sh"
- "-euc"
- |
echo "requirepass '$$REDIS_PASSWORD'" > /etc/redis.conf
exec redis-server /etc/redis.conf
environment:
REDIS_PASSWORD: "REPLACE_WITH_SECURE_REDIS_PASSWORD"
environment: REDIS_PASSWORD
volumes:
- "redis:/data:rw"
secrets:
db_password:
name: ${STACK_NAME}_db_password_${SECRET_DB_PASSWORD_VERSION}
@ -109,11 +119,39 @@ secrets:
rabbitmq_password:
name: ${STACK_NAME}_rabbitmq_password_${SECRET_RABBITMQ_PASSWORD_VERSION}
external: true
redis_password:
name: ${STACK_NAME}_redis_password_${SECRET_REDIS_PASSWORD_VERSION}
external: true
memcached_password:
name: ${STACK_NAME}_memcached_password_${SECRET_MEMCACHED_PASSWORD_VERSION}
external: true
smtp_password:
name: ${STACK_NAME}_smtp_password_${SECRET_SMTP_PASSWORD_VERSION}
external: true
zulip_secret:
name: ${STACK_NAME}_zulip_secret_${SECRET_ZULIP_SECRET_VERSION}
external: true
configs:
pg_backup:
name: ${STACK_NAME}_pg_backup_${PG_BACKUP_VERSION}
file: pg_backup.sh
entrypoint:
name: ${STACK_NAME}_entrypoint_${ENTRYPOINT_VERSION}
file: entrypoint.sh.tmpl
template_driver: golang
memcached_entrypoint:
name: ${STACK_NAME}_memcached_entrypoint_${MEM_ENTRYPOINT_VERSION}
file: entrypoint.memcached.sh.tmpl
template_driver: golang
redis_entrypoint:
name: ${STACK_NAME}_redis_entrypoint_${REDIS_ENTRYPOINT_VERSION}
file: entrypoint.redis.sh.tmpl
template_driver: golang
rabbitmq_healthcheck:
name: ${STACK_NAME}_rabbitmq_healthcheck_${RABBIT_HEALTHCHECK_VERSION}
file: rabbitmq_healthcheck.sh
volumes:
zulip:
@ -121,6 +159,7 @@ volumes:
rabbitmq:
redis:
networks:
internal:
proxy:

View File

@ -0,0 +1,17 @@
#!/bin/sh
if [ -f /run/secrets/memcached_password ]; then
export MEMCACHED_PASSWORD=$(cat /run/secrets/memcached_password)
else
echo "Membcached password secret not found, skipping."
fi
set -e
# first arg is `-f` or `--some-option`
if [ "${1#-}" != "$1" ]; then
set -- memcached "$@"
fi
exec "$@"

45
entrypoint.redis.sh.tmpl Normal file
View File

@ -0,0 +1,45 @@
#!/bin/bash
if [ -f /run/secrets/redis_password ]; then
export REDIS_PASSWORD=$(cat /run/secrets/redis_password)
else
echo "REDIS secret not found, skipping."
fi
### docker entrypoint script, for starting redis stack
BASEDIR=/opt/redis-stack
cd ${BASEDIR}
CMD=${BASEDIR}/bin/redis-server
if [ -f /redis-stack.conf ]; then
CONFFILE=/redis-stack.conf
fi
if [ -z "${REDIS_DATA_DIR}" ]; then
REDIS_DATA_DIR=/data
fi
# when running in redis-stack (as opposed to redis-stack-server)
if [ -f ${BASEDIR}/nodejs/bin/node ]; then
${BASEDIR}/nodejs/bin/node -r ${BASEDIR}/share/redisinsight/api/node_modules/dotenv/config ${BASEDIR}/share/redisinsight/api/dist/src/main.js dotenv_config_path=${BASEDIR}/share/redisinsight/.env &
fi
if [ -z "${REDISEARCH_ARGS}" ]; then
REDISEARCH_ARGS="MAXSEARCHRESULTS 10000 MAXAGGREGATERESULTS 10000"
fi
${CMD} \
${CONFFILE} \
--dir ${REDIS_DATA_DIR} \
--protected-mode no \
--daemonize no \
--loadmodule /opt/redis-stack/lib/rediscompat.so \
--loadmodule /opt/redis-stack/lib/redisearch.so ${REDISEARCH_ARGS} \
--loadmodule /opt/redis-stack/lib/redistimeseries.so ${REDISTIMESERIES_ARGS} \
--loadmodule /opt/redis-stack/lib/rejson.so ${REDISJSON_ARGS} \
--loadmodule /opt/redis-stack/lib/redisbloom.so ${REDISBLOOM_ARGS} \
--loadmodule /opt/redis-stack/lib/redisgears.so v8-plugin-path /opt/redis-stack/lib/libredisgears_v8_plugin.so ${REDISGEARS_ARGS} \
${REDIS_ARGS}

630
entrypoint.sh.tmpl Normal file
View File

@ -0,0 +1,630 @@
#!/bin/bash
if [ -f /run/secrets/db_password ]; then
export SECRETS_postgres_password=$(cat /run/secrets/db_password)
else
echo "Postgres password secret not found, skipping."
fi
if [ -f /run/secrets/memcached_password ]; then
export SECRETS_memcached_password=$(cat /run/secrets/memcached_password)
else
echo "Membcached password secret not found, skipping."
fi
if [ -f /run/secrets/redis_password ]; then
export SECRETS_redis_password=$(cat /run/secrets/redis_password)
else
echo "DB password secret not found, skipping."
fi
if [ -f /run/secrets/rabbitmq_password ]; then
export SECRETS_rabbitmq_password=$(cat /run/secrets/rabbitmq_password)
else
echo "DB password secret not found, skipping."
fi
if [ -f /run/secrets/smtp_password ]; then
export SECRETS_email_password=$(cat /run/secrets/smtp_password)
else
echo "DB password secret not found, skipping."
fi
if [ -f /run/secrets/zulip_secret ]; then
export SECRETS_secret_key=$(cat /run/secrets/zulip_secret)
else
echo "Zulip secret not found, skipping."
fi
if [ "$DEBUG" = "true" ] || [ "$DEBUG" = "True" ]; then
set -x
set -o functrace
fi
set -e
shopt -s extglob
# 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
SETTING_RABBITMQ_HOST="${SETTING_RABBITMQ_HOST:-127.0.0.1}"
SETTING_RABBITMQ_USER="${SETTING_RABBITMQ_USER:-zulip}"
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:-80m}"
# 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() {
mkdir -p "$DATA_DIR" "$DATA_DIR/backups" "$DATA_DIR/certs" "$DATA_DIR/letsencrypt" "$DATA_DIR/uploads"
[ -e /etc/letsencrypt ] || ln -ns "$DATA_DIR/letsencrypt" /etc/letsencrypt
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 ..."
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."
}
puppetConfiguration() {
echo "Executing puppet 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
fi
if [ "$QUEUE_WORKERS_MULTIPROCESS" == "True" ] || [ "$QUEUE_WORKERS_MULTIPROCESS" == "true" ]; then
echo "Setting queue workers to run in multiprocess mode ..."
crudini --set /etc/zulip/zulip.conf application_server queue_workers_multiprocess true
elif [ "$QUEUE_WORKERS_MULTIPROCESS" == "False" ] || [ "$QUEUE_WORKERS_MULTIPROCESS" == "false" ]; then
echo "Setting queue workers to run in multithreaded mode ..."
crudini --set /etc/zulip/zulip.conf application_server queue_workers_multiprocess false
fi
if [ -n "$LOADBALANCER_IPS" ]; then
echo "Setting IPs for load balancer"
crudini --set /etc/zulip/zulip.conf loadbalancer ips "${LOADBALANCER_IPS}"
fi
/home/zulip/deployments/current/scripts/zulip-puppet-apply -f
}
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 SSL_CERTIFICATE_GENERATION 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 SSL_CERTIFICATE_GENERATION 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"
ln -ns "$DATA_DIR/zulip-secrets.conf" "/etc/zulip/zulip-secrets.conf"
else
ln -nsf "$DATA_DIR/zulip-secrets.conf" "/etc/zulip/zulip-secrets.conf"
echo "Generating Zulip secrets ..."
/root/zulip/scripts/setup/generate_secrets.py --production
fi
echo "Secrets generation succeeded."
local key
for key in "${!SECRETS_@}"; do
[[ "$key" == SECRETS_*([0-9A-Z_a-z-]) ]] || continue
local SECRET_KEY="${key#SECRETS_}"
local SECRET_VAR="${!key}"
if [ -z "$SECRET_VAR" ]; then
echo "Empty secret for key \"$SECRET_KEY\"."
fi
crudini --set "$DATA_DIR/zulip-secrets.conf" "secrets" "${SECRET_KEY}" "${SECRET_VAR}"
done
echo "Zulip secrets configuration succeeded."
}
databaseConfiguration() {
echo "Setting database configuration ..."
setConfigurationValue "REMOTE_POSTGRES_HOST" "$DB_HOST" "$SETTINGS_PY" "string"
setConfigurationValue "REMOTE_POSTGRES_PORT" "$DB_HOST_PORT" "$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
local auth_backends
IFS=, read -r -a auth_backends <<< "$ZULIP_AUTH_BACKENDS"
for AUTH_BACKEND in "${auth_backends[@]}"; 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 [ -n "$ZULIP_CUSTOM_SETTINGS" ]; then
echo -e "\n$ZULIP_CUSTOM_SETTINGS" >> "$SETTINGS_PY"
fi
local key
for key in "${!SETTING_@}"; do
[[ "$key" == SETTING_*([0-9A-Za-z_]) ]] || continue
local setting_key="${key#SETTING_}"
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_GLOBAL_OPTIONS" ] || \
[ "$setting_key" = "AUTH_LDAP_USER_SEARCH" ] || \
[ "$setting_key" = "AUTH_LDAP_GROUP_SEARCH" ] || \
[ "$setting_key" = "AUTH_LDAP_REVERSE_EMAIL_SEARCH" ] || \
[ "$setting_key" = "AUTH_LDAP_USER_ATTR_MAP" ] || \
[ "$setting_key" = "AUTH_LDAP_USER_FLAGS_BY_GROUP" ] || \
[ "$setting_key" = "AUTH_LDAP_GROUP_TYPE" ] || \
[ "$setting_key" = "AUTH_LDAP_ADVANCED_REALM_ACCESS_CONTROL" ] || \
[ "$setting_key" = "LDAP_SYNCHRONIZED_GROUPS_BY_REALM" ] || \
[ "$setting_key" = "SOCIAL_AUTH_OIDC_ENABLED_IDPS" ] || \
[ "$setting_key" = "SOCIAL_AUTH_SAML_ENABLED_IDPS" ] || \
[ "$setting_key" = "SOCIAL_AUTH_SAML_ORG_INFO" ] || \
{ [ "$setting_key" = "LDAP_APPEND_DOMAIN" ] && [ "$setting_var" = "None" ]; } || \
[ "$setting_key" = "SCIM_CONFIG" ] || \
[ "$setting_key" = "SECURE_PROXY_SSL_HEADER" ] || \
[[ "$setting_key" = "CSRF_"* ]] || \
[ "$setting_key" = "REALM_HOSTS" ] || \
[ "$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
if ! su zulip -c "/home/zulip/deployments/current/manage.py checkconfig"; 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
printf 'MAILTO=""\n%s cd /;/sbin/entrypoint.sh app:backup\n' "$AUTO_BACKUP_INTERVAL" > /etc/cron.d/autobackup
echo "Auto backup enabled."
}
initialConfiguration() {
echo "=== Begin Initial Configuration Phase ==="
prepareDirectories
puppetConfiguration
nginxConfiguration
configureCerts
if [ "$MANUAL_CONFIGURATION" = "False" ] || [ "$MANUAL_CONFIGURATION" = "false" ]; then
# Start with the settings template file.
cp -a /home/zulip/deployments/current/zproject/prod_settings_template.py "$SETTINGS_PY"
databaseConfiguration
secretsConfiguration
authenticationBackends
zulipConfiguration
fi
autoBackupConfiguration
echo "=== End Initial Configuration Phase ==="
}
# === bootstrappingEnvironment ===
waitingForDatabase() {
local TIMEOUT=60
echo "Waiting for database server to allow connections ..."
while ! PGPASSWORD="${SECRETS_postgres_password?}" /usr/bin/pg_isready -h "$DB_HOST" -p "$DB_HOST_PORT" -U "$DB_USER" -t 1 >/dev/null 2>&1
do
if ! ((TIMEOUT--)); then
echo "Could not connect to database server. Exiting."
exit 1
fi
echo -n "."
sleep 1
done
}
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
ln -sf /sbin/certbot-deploy-hook /etc/letsencrypt/renewal-hooks/deploy/docker-deploy-hook
# Accept the terms of service automatically.
/home/zulip/deployments/current/scripts/setup/setup-certbot \
--agree-tos \
--email="$SETTING_ZULIP_ADMINISTRATOR" \
--skip-symlink \
-- \
"$SETTING_EXTERNAL_HOST"
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"
}
appInit() {
echo "=== Running initial setup ==="
initialConfiguration
bootstrappingEnvironment
}
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 $(printf '%q ' "$COMMAND" "$@")"
}
appBackup() {
echo "Starting backup process ..."
local TIMESTAMP
TIMESTAMP=$(date -u -Iseconds | tr ':' '_')
if [ -d "/tmp/backup-$TIMESTAMP" ]; then
echo "Temporary backup folder for \"$TIMESTAMP\" already exists. Aborting."
echo "Backup process failed. Exiting."
exit 1
fi
local BACKUP_FOLDER
BACKUP_FOLDER="/tmp/backup-$TIMESTAMP)"
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-$TIMESTAMP.tar.gz" "$BACKUP_FOLDER/"
rm -r "${BACKUP_FOLDER:?}/"
echo "Backup process succeeded."
exit 0
}
appRestore() {
echo "Starting restore process ..."
if [ -z "$(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
local backups=("$DATA_DIR"/backups/*)
printf '|-> %s\n' "${backups[@]#"$DATA_DIR"/backups/}"
echo "Please enter backup filename (full filename with extension): "
read -r 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
for TIMEOUT in {10..1}; do
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 "$BACKUP_FILE" | 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 "> app:init - Run inital setup of 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:init)
appInit
;;
app:managepy)
shift 1
appManagePy "$@"
;;
app:backup)
appBackup
;;
app:restore)
appRestore
;;
app:certs)
appCerts
;;
app:help)
appHelp
;;
app:version)
appVersion
;;
*)
exec "$@" || appHelp
;;
esac

14
rabbitmq_healthcheck.sh Normal file
View File

@ -0,0 +1,14 @@
#!/bin/bash
set -eo pipefail
# A RabbitMQ node is considered healthy if all the below are true:
# * the rabbit app finished booting & it's running
# * there are no alarms
# * there is at least 1 active listener
rabbitmqctl eval '
{ true, rabbit_app_booted_and_running } = { rabbit:is_booted(node()), rabbit_app_booted_and_running },
{ [], no_alarms } = { rabbit:alarms(), no_alarms },
[] /= rabbit_networking:active_listeners(),
rabbitmq_node_is_healthy.
' || exit 1