157 lines
5.0 KiB
Bash
Executable File
157 lines
5.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
export LC_ALL=C
|
|
|
|
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
compose_args=(-f "$script_dir/docker-compose.prod.yml")
|
|
env_file=
|
|
allow_local_images=false
|
|
while (($#)); do
|
|
case $1 in
|
|
--allow-local-images)
|
|
allow_local_images=true
|
|
shift
|
|
;;
|
|
--)
|
|
shift
|
|
if (($# != 1)) || [[ -n $env_file ]]; then
|
|
echo "usage: $0 [--allow-local-images] [env-file]" >&2
|
|
exit 2
|
|
fi
|
|
env_file=$1
|
|
shift
|
|
;;
|
|
-*)
|
|
echo "unknown option: $1" >&2
|
|
exit 2
|
|
;;
|
|
*)
|
|
if [[ -n $env_file ]]; then
|
|
echo "usage: $0 [--allow-local-images] [env-file]" >&2
|
|
exit 2
|
|
fi
|
|
env_file=$1
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
if [[ -n $env_file ]]; then
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
source "$env_file"
|
|
set +a
|
|
compose_args=(--env-file "$env_file" "${compose_args[@]}")
|
|
fi
|
|
|
|
required=(GOCHAT_IMAGE_REF SHANGWUTONG_IMAGE_REF GOCHAT_DATABASE_DSN GOCHAT_REDIS_DSN MEILI_MASTER_KEY GOCHAT_JWT_SECRET GOCHAT_BACKUP_OFFSITE_DIR GOCHAT_BACKUP_OFFSITE_SOURCE GOCHAT_BACKUP_OFFSITE_FSTYPE)
|
|
for name in "${required[@]}"; do
|
|
value=${!name:-}
|
|
if [[ -z $value || ${value^^} == *CHANGE_ME* ]]; then
|
|
echo "$name is required and must not contain CHANGE_ME" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
if [[ $GOCHAT_BACKUP_OFFSITE_DIR != /* ]] || [[ ! -d $GOCHAT_BACKUP_OFFSITE_DIR ]]; then
|
|
echo "GOCHAT_BACKUP_OFFSITE_DIR must be an existing external mount point" >&2
|
|
exit 1
|
|
fi
|
|
offsite_dir=$(realpath -e -- "$GOCHAT_BACKUP_OFFSITE_DIR")
|
|
if [[ $offsite_dir == / ]]; then
|
|
echo "GOCHAT_BACKUP_OFFSITE_DIR must not be /" >&2
|
|
exit 1
|
|
fi
|
|
if ! offsite_info=$(findmnt -M "$offsite_dir" -n -o SOURCE,FSTYPE,MAJ:MIN); then
|
|
echo "GOCHAT_BACKUP_OFFSITE_DIR must be an existing external mount point" >&2
|
|
exit 1
|
|
fi
|
|
read -r offsite_source offsite_type offsite_device <<<"$offsite_info"
|
|
if [[ $offsite_source != "$GOCHAT_BACKUP_OFFSITE_SOURCE" || $offsite_type != "$GOCHAT_BACKUP_OFFSITE_FSTYPE" ]]; then
|
|
echo "GOCHAT_BACKUP_OFFSITE_DIR mount source/type does not match the approved values" >&2
|
|
exit 1
|
|
fi
|
|
case $offsite_type in
|
|
tmpfs | devtmpfs | ramfs)
|
|
echo "GOCHAT_BACKUP_OFFSITE_DIR must not use an in-memory filesystem" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
local_probe=${GOCHAT_BACKUP_DIR:-./backups/local}
|
|
if [[ $local_probe != /* ]]; then
|
|
local_probe=$script_dir/$local_probe
|
|
fi
|
|
while [[ ! -e $local_probe && $local_probe != / ]]; do
|
|
local_probe=$(dirname -- "$local_probe")
|
|
done
|
|
read -r local_source local_device < <(findmnt -T "$local_probe" -n -o SOURCE,MAJ:MIN)
|
|
if [[ $offsite_source == "$local_source" || $offsite_device == "$local_device" ]]; then
|
|
echo "GOCHAT_BACKUP_OFFSITE_DIR must use a different source/device than GOCHAT_BACKUP_DIR" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ((${#GOCHAT_JWT_SECRET} < 32)); then
|
|
echo "GOCHAT_JWT_SECRET must be at least 32 characters" >&2
|
|
exit 1
|
|
fi
|
|
if ((${#MEILI_MASTER_KEY} < 16)); then
|
|
echo "MEILI_MASTER_KEY must be at least 16 bytes" >&2
|
|
exit 1
|
|
fi
|
|
alertmanager_webhook_file=${ALERTMANAGER_WEBHOOK_URL_FILE:-../../.secrets/alertmanager-webhook-url}
|
|
if [[ $alertmanager_webhook_file != /* ]]; then
|
|
alertmanager_webhook_file=$script_dir/$alertmanager_webhook_file
|
|
fi
|
|
if [[ ! -r $alertmanager_webhook_file ]] || ! IFS= read -r alertmanager_webhook_url <"$alertmanager_webhook_file" || [[ ! $alertmanager_webhook_url =~ ^https://[^[:space:]]+$ ]]; then
|
|
echo "ALERTMANAGER_WEBHOOK_URL_FILE must contain one HTTPS URL" >&2
|
|
exit 1
|
|
fi
|
|
if [[ -n ${GOCHAT_DATABASE_DSN:-} ]]; then
|
|
"$script_dir/database_client_entrypoint.sh" --preflight
|
|
if [[ $GOCHAT_DATABASE_DSN == *sslrootcert=* ]]; then
|
|
tls_gid=${GOCHAT_DATABASE_TLS_GID:-}
|
|
if [[ ! $tls_gid =~ ^[1-9][0-9]*$ ]]; then
|
|
echo "GOCHAT_DATABASE_TLS_GID must be set to a positive numeric group ID" >&2
|
|
exit 1
|
|
fi
|
|
for name in GOCHAT_DATABASE_TLS_CA_FILE GOCHAT_DATABASE_TLS_CLIENT_CERT_FILE GOCHAT_DATABASE_TLS_CLIENT_KEY_FILE; do
|
|
host_file=${!name:-}
|
|
if [[ -z $host_file ]]; then
|
|
echo "$name must be set when the database DSN uses certificate files" >&2
|
|
exit 1
|
|
fi
|
|
if [[ $host_file != /* ]]; then
|
|
host_file=$script_dir/$host_file
|
|
fi
|
|
if [[ ! -f $host_file ]]; then
|
|
echo "$name must point to an existing regular file" >&2
|
|
exit 1
|
|
fi
|
|
file_gid=$(stat -c %g -- "$host_file")
|
|
file_mode=$(stat -c %a -- "$host_file")
|
|
group_digit=${file_mode: -2:1}
|
|
if [[ $file_gid != "$tls_gid" ]] || (((8#$group_digit & 4) == 0)); then
|
|
echo "$name must be readable by GOCHAT_DATABASE_TLS_GID" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
fi
|
|
fi
|
|
|
|
images=$(docker compose "${compose_args[@]}" config --images)
|
|
while IFS= read -r image; do
|
|
if [[ $image =~ @sha256:[0-9a-fA-F]{64}$ ]]; then
|
|
continue
|
|
fi
|
|
if [[ $allow_local_images == true ]]; then
|
|
if ! docker image inspect "$image" >/dev/null 2>&1; then
|
|
echo "local production image is not loaded: $image" >&2
|
|
exit 1
|
|
fi
|
|
continue
|
|
fi
|
|
echo "production image must be pinned to a sha256 digest: $image" >&2
|
|
exit 1
|
|
done <<<"$images"
|
|
echo "production preflight passed"
|