deploy: prod-only pipeline with Drizzle migrations; dev refresh + preview
- push to main builds, backs up prod (pg_dump 17), runs db-migrate + seeds, preflights and deploys; rollback = workflow_dispatch with rollback_tag - staging workflow, promote step and migrate-*.ts / old SQL removed - DB_URL = app role, DB_MIGRATE_URL = owner role - scripts/db-refresh-dev.sh (prod dump -> local dev DB), scripts/preview.sh <branch>
This commit is contained in:
@@ -1,26 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Production is the only deployed environment (local dev uses scripts/db-refresh-dev.sh).
|
||||
# deploy-remote.sh <sha> deploy back up, migrate, seed, preflight, switch, health-check
|
||||
# deploy-remote.sh <sha> rollback back up, preflight, switch to an earlier image (no migrations;
|
||||
# the database is not rolled back, restore a backup for that)
|
||||
sha=${1:?commit SHA required}
|
||||
directory=${2:?deployment directory required}
|
||||
origin=${3:?public origin required}
|
||||
mode=${2:-deploy}
|
||||
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
|
||||
case "$mode" in deploy|rollback) ;; *) echo 'Mode must be deploy or rollback' >&2; exit 2 ;; esac
|
||||
directory=/opt/personal-website
|
||||
origin=https://stanrunge.dev
|
||||
environment=/etc/personal-website/prod.env
|
||||
environment_name=production
|
||||
|
||||
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
|
||||
if ! sudo docker image inspect "$image" >/dev/null 2>&1; then
|
||||
echo "Image $image is not on this host" >&2; exit 1
|
||||
fi
|
||||
previous_tag=$(sed -n 's/^IMAGE_TAG=//p' "$directory/.env" | head -n 1)
|
||||
switched=0
|
||||
@@ -33,17 +32,21 @@ rollback() {
|
||||
}
|
||||
trap rollback ERR
|
||||
|
||||
# The deploy account cannot read the env file directly; check inside a container.
|
||||
if ! sudo docker run --rm --env-file "$environment" --entrypoint sh "$image" \
|
||||
-c 'test -n "${DB_MIGRATE_URL:-}"'; then
|
||||
echo "DB_MIGRATE_URL (schema owner) is missing from $environment" >&2; exit 1
|
||||
fi
|
||||
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))"
|
||||
db_major=$((db_version / 10000))
|
||||
if [ "$db_major" -gt 17 ]; then
|
||||
echo "Server is PostgreSQL $db_major; update the pinned postgres:17-alpine backup image" >&2; exit 1
|
||||
fi
|
||||
backup_image="postgres:${db_major}-alpine"
|
||||
backup_image=postgres:17-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"
|
||||
@@ -53,27 +56,29 @@ sudo docker pull "$backup_image"
|
||||
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"'
|
||||
sh -eu -c 'chmod 0700 /backups; umask 077; pg_dump --format=custom --no-owner --no-acl --file="/backups/$BACKUP_FILE" "$DB_MIGRATE_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"
|
||||
|
||||
# Migrations and seeds (ref.* is read-only for the app) need the owner role.
|
||||
# DB_MIGRATE_URL (role personal) must be set in the env file; DB_URL is the app
|
||||
# role (personal_app), which has no DDL rights.
|
||||
run_database_script() {
|
||||
echo "Running $1"
|
||||
sudo docker run --rm --network backend --env-file "$environment" "$image" bun "$1"
|
||||
sudo docker run --rm --network backend --env-file "$environment" \
|
||||
--entrypoint sh "$image" -eu -c 'DB_URL="$DB_MIGRATE_URL" exec bun "$0"' "$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
|
||||
if [ "$mode" = deploy ]; then
|
||||
# Schema changes: Drizzle migrations from ./drizzle, as the schema owner.
|
||||
run_database_script scripts/db-migrate.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
|
||||
fi
|
||||
|
||||
sudo docker run --rm --network backend --env-file "$environment" "$image" \
|
||||
bun scripts/deploy-preflight.ts "$origin"
|
||||
@@ -96,4 +101,4 @@ curl --retry 6 --retry-delay 2 --retry-all-errors --fail --silent --show-error \
|
||||
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"
|
||||
echo "Deployed $sha to $origin ($mode)"
|
||||
|
||||
Reference in New Issue
Block a user