#!/usr/bin/env bash # Sourced by both deployment entry points while holding .local/deploy.lock. record_deployment_config() { local temporary temporary=$(mktemp .env.XXXXXX) if [[ -f .env ]]; then if ! awk '!/^(CORE_IMAGE|ADAPTER_IMAGE|TELEGRAM_CHANNEL_ID)=/' .env >"$temporary"; then rm -f "$temporary" return 1 fi fi printf 'CORE_IMAGE=%s\nADAPTER_IMAGE=%s\nTELEGRAM_CHANNEL_ID=%s\n' "$CORE_IMAGE" "$ADAPTER_IMAGE" "$TELEGRAM_CHANNEL_ID" >>"$temporary" chmod 600 "$temporary" mv "$temporary" .env } wait_for_http() { local address=$1 path=$2 attempt address="${address/0.0.0.0/127.0.0.1}" address="${address/\[::\]/[::1]}" for ((attempt = 0; attempt < 90; attempt++)); do if curl -fsS --max-time 3 "http://$address$path" >/dev/null 2>&1; then return 0; fi sleep 2 done echo "Health check failed: $path. No automatic database downgrade was attempted." >&2 return 1 } deployment_failed() { local status=$? if [[ "${deployment_stopped:-false}" == true ]]; then docker compose --profile telegram stop telegram &2 return "$status" } deploy_revision() { local revision=$1 backup_dir=${2:-} telegram_exists=false telegram_running=false service phase='checking deployment configuration' docker compose config --quiet # Query directly in Postgres: never expose the bot token in output or arguments. export TELEGRAM_CHANNEL_ID TELEGRAM_CHANNEL_ID=$(docker compose exec -T postgres psql -X -U postgres -d monitor -At -v ON_ERROR_STOP=1 -c "SELECT value FROM config WHERE key='telegram_channel_id'" '')" &2 return 1 fi fi if [[ "$telegram_running" == true && ("$bot_enabled" != t || -z "$TELEGRAM_CHANNEL_ID") ]]; then echo 'Enabled Telegram requires a bot token and channel ID in database config.' >&2 return 1 fi if [[ -z "$backup_dir" ]]; then backup_dir=".local/backups/$(date -u +%Y%m%dT%H%M%SZ)-${revision:0:7}"; fi if [[ -e "$backup_dir/database.dump" || -e "$backup_dir/database.dump.partial" ]]; then echo 'Backup path already exists; choose a new directory.' >&2 return 1 fi mkdir -p "$backup_dir" printf '%s\n' "$revision" >"$backup_dir/revision" if [[ -f .env ]]; then cp .env "$backup_dir/deployment.env"; fi docker compose --profile telegram ps --all --format json >"$backup_dir/containers.json" phase='stopping services and backing up database' # Stop the old embedded bot and the standalone poller before changing the DB. printf '%s\n' "$telegram_running" >.local/deploy-telegram-state deployment_stopped=true docker compose --profile telegram stop telegram core "$backup_dir/database.dump.partial" mv "$backup_dir/database.dump.partial" "$backup_dir/database.dump" echo "Database backup: $backup_dir/database.dump" phase='migrating database' docker compose run --rm --no-deps --pull never migrate &2 return 1 fi wait_for_http "$admin_address" /healthz wait_for_http "$web_address" /app/ if [[ "$telegram_running" == true ]]; then phase='starting Telegram channel' docker compose --profile telegram up -d --no-deps --no-build --pull never telegram &2 return 1 fi fi printf 'CORE_IMAGE=%s\nADAPTER_IMAGE=%s\n' "$CORE_IMAGE" "$ADAPTER_IMAGE" >.local/deployed-images.env rm -f .local/deploy-telegram-state deployment_stopped=false printf 'Deployment complete: %s\nCore and web health: ok\nTelegram enabled: %s\n' "$revision" "$telegram_running" } set -E trap deployment_failed ERR