#!/usr/bin/env bash set -euo pipefail sha=${1:?commit SHA required} directory=${2:?deployment directory required} origin=${3:?public origin required} case "$sha" in *[!0-9a-f]*|'') echo 'Invalid image tag' >&2; exit 2 ;; esac case "$directory:$origin" in /opt/personal-website:https://stanrunge.dev) environment=/etc/personal-website/prod.env ;; /opt/personal-website-staging:https://staging.stanrunge.dev) environment=/etc/personal-website-staging/staging.env ;; *) echo 'Unrecognized deployment target' >&2; exit 2 ;; esac image="git.stanrunge.dev/stan/personal-website:$sha" compose="$directory/compose.prod.yml" if [ "$directory" = /opt/personal-website-staging ]; then environment_name=staging else environment_name=production fi previous_tag=$(sed -n 's/^IMAGE_TAG=//p' "$directory/.env" | head -n 1) switched=0 rollback() { if [ "$switched" -eq 1 ] && [ -n "$previous_tag" ]; then echo "Health check failed; restoring prior image tag $previous_tag" >&2 printf 'IMAGE_TAG=%s\n' "$previous_tag" | sudo tee "$directory/.env" >/dev/null sudo docker compose --env-file "$directory/.env" -f "$compose" up -d fi } trap rollback ERR db_version=$(sudo docker run --rm --network backend --env-file "$environment" "$image" \ bun scripts/print-db-server-version.ts) case "$db_version" in *[!0-9]*|'') echo 'Could not determine the PostgreSQL server version' >&2; exit 1 ;; esac if [ "$db_version" -ge 100000 ]; then db_major=$((db_version / 10000)) else db_major="$((db_version / 10000)).$(((db_version / 100) % 100))" fi backup_image="postgres:${db_major}-alpine" backup_dir=/var/backups/personal-website backup_stamp=$(date -u +%Y%m%dT%H%M%SZ) backup_name="${environment_name}-${backup_stamp}-${sha}.dump" sudo docker pull "$backup_image" # The deploy account can run Docker, but has no general host-file sudo access. # A bind volume creates the root-owned directory; restrict it before dumping. sudo docker run --rm --network backend --env-file "$environment" \ --volume "$backup_dir:/backups" \ --env "BACKUP_FILE=$backup_name" "$backup_image" \ sh -eu -c 'chmod 0700 /backups; umask 077; pg_dump --format=custom --no-owner --no-acl --file="/backups/$BACKUP_FILE" "$DB_URL"' sudo docker run --rm --mount "type=bind,source=$backup_dir,target=/backups" \ "$backup_image" pg_restore --list "/backups/$backup_name" >/dev/null echo "Verified $environment_name database backup at $backup_dir/$backup_name" run_database_script() { echo "Running $1" sudo docker run --rm --network backend --env-file "$environment" "$image" bun "$1" } run_database_script scripts/migrate-auth-catalog.ts run_database_script scripts/migrate-foundation.ts run_database_script scripts/migrate-money-tracker.ts run_database_script scripts/migrate-game-tracker.ts run_database_script scripts/migrate-tracker-journal.ts run_database_script scripts/migrate-house-tracker.ts run_database_script scripts/seed-catalog.ts run_database_script scripts/seed-cod-catalog.ts run_database_script scripts/seed-pokemon-research.ts run_database_script scripts/seed-cod-challenges.ts run_database_script scripts/seed-cod-art.ts sudo docker run --rm --network backend --env-file "$environment" "$image" \ bun scripts/deploy-preflight.ts "$origin" printf 'IMAGE_TAG=%s\n' "$sha" | sudo tee "$directory/.env" >/dev/null switched=1 sudo docker compose --env-file "$directory/.env" -f "$compose" up -d container=$(sudo docker compose --env-file "$directory/.env" -f "$compose" ps -q app) expected_image=$(sudo docker image inspect -f '{{.Id}}' "$image") actual_image=$(sudo docker inspect -f '{{.Image}}' "$container") if [ "$actual_image" != "$expected_image" ]; then echo 'Compose did not start the intended image' >&2 false fi curl --retry 12 --retry-delay 2 --retry-all-errors --fail --silent --show-error \ "$origin/login" >/dev/null curl --retry 6 --retry-delay 2 --retry-all-errors --fail --silent --show-error \ "$origin/projects/game-tracker/pokemon" >/dev/null curl --retry 6 --retry-delay 2 --retry-all-errors --fail --silent --show-error \ "$origin/projects/game-tracker/call-of-duty/black-ops-2" >/dev/null trap - ERR echo "Deployed $sha to $origin"