#!/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") if (($#)); then env_file=$1 set -a source "$env_file" set +a compose_args=(--env-file "$env_file" "${compose_args[@]}") fi required=(GOCHAT_IMAGE_REF SHANGWUTONG_IMAGE_REF GOCHAT_SERVER_CORS_ALLOWED_ORIGINS POSTGRES_PASSWORD REDIS_PASSWORD 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 if [[ -n ${GOCHAT_DATABASE_DSN:-} && ! $GOCHAT_DATABASE_DSN =~ (^|[?&])sslmode=(require|verify-ca|verify-full)(&|$) ]]; then echo "GOCHAT_DATABASE_DSN must explicitly require TLS for an external database" >&2 exit 1 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 echo "production image must be pinned to a sha256 digest: $image" >&2 exit 1 done <<< "$images" echo "production preflight passed"