From 8d37fabe58a823a6bee0c31e29114202b0dc85c3 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Wed, 4 Nov 2026 16:34:17 +0800 Subject: [PATCH] Import approved PostgreSQL snapshot into dev --- .gitea/workflows/preview.yml | 3 + scripts/preview/import-production-snapshot.sh | 81 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 scripts/preview/import-production-snapshot.sh diff --git a/.gitea/workflows/preview.yml b/.gitea/workflows/preview.yml index bf4ad7b..bba83f0 100644 --- a/.gitea/workflows/preview.yml +++ b/.gitea/workflows/preview.yml @@ -30,3 +30,6 @@ jobs: bash scripts/preview/init.sh "$PWD" waterfall-wiki-tests:6654a1456bdcef77d83ca84373dc36188e15bd12 fi bash scripts/preview/sync.sh "$PWD" "$SOURCE_SHA" "$SOURCE_BRANCH" + - name: Import approved PostgreSQL snapshot into dev + if: gitea.ref == 'refs/heads/dev' && startsWith(gitea.event.head_commit.message, 'Import approved PostgreSQL snapshot into dev') + run: bash scripts/preview/import-production-snapshot.sh diff --git a/scripts/preview/import-production-snapshot.sh b/scripts/preview/import-production-snapshot.sh new file mode 100644 index 0000000..d6cbfea --- /dev/null +++ b/scripts/preview/import-production-snapshot.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -Eeuo pipefail +root=/opt/prompt-preview +test -f "$root/ready" +exec 8>/var/lock/prompt-preview-sync.lock +flock -w 30 8 + +container() { + local id + id=$(docker ps -q --filter "label=com.docker.compose.project=$1" --filter "label=com.docker.compose.service=$2") + [[ "$id" =~ ^[0-9a-f]{12}$ ]] + printf '%s\n' "$id" +} +production=$(container prompt postgres) +preview=$(container prompt-preview postgres) +web=$(container prompt-preview waterfall-wiki) +test "$production" != "$preview" + +suffix=$(date -u +%Y%m%d%H%M%S) +incoming="prompt_import_$suffix" +backup="prompt_before_$suffix" +port=$(docker port "$web" 3000/tcp | awk -F: 'NR==1 {print $NF}') +[[ "$port" =~ ^[0-9]+$ ]] +sql() { + docker exec -i "$preview" psql -X -q -v ON_ERROR_STOP=1 -U prompt "$@" +} +ready() { + for attempt in {1..24}; do + if curl --noproxy '*' -fsS --max-time 5 "http://127.0.0.1:$port/api/health" >/dev/null 2>&1; then return; fi + sleep 2 + done + return 1 +} + +# Keep the dump in a pipe on ces-control. Do not export active credentials. +docker exec "$preview" createdb -U prompt -T template0 "$incoming" +docker exec "$production" pg_dump -U prompt -d prompt -Fc \ + --exclude-table-data=public.account_tokens \ + --exclude-table-data=public.canvas_agent_links \ + --exclude-table-data=public.account_limits | + docker exec -i "$preview" pg_restore -U prompt -d "$incoming" --no-owner --no-privileges --exit-on-error + +existing_schema=$(sql -At -d prompt -c 'SELECT version FROM schema_migrations ORDER BY version') +incoming_schema=$(sql -At -d "$incoming" -c 'SELECT version FROM schema_migrations ORDER BY version') +test -n "$existing_schema" +if [[ "$existing_schema" != "$incoming_schema" ]]; then + echo 'Preview and production migration sets differ. Preview was not switched.' >&2 + exit 1 +fi +test "$(sql -At -d "$incoming" -c 'SELECT count(*) FROM account_tokens')" = 0 +test "$(sql -At -d "$incoming" -c 'SELECT count(*) FROM canvas_agent_links')" = 0 +verified=$(sql -At -d "$incoming" -c 'SELECT count(*) FROM account_users WHERE verified_at IS NOT NULL') +test "$verified" -gt 0 +echo "Snapshot validated: $verified verified accounts, matching migrations, no live account or canvas tokens." + +docker stop "$web" >/dev/null +if ! sql -d postgres </dev/null + exit 1 +fi +if docker start "$web" >/dev/null && ready; then + echo "Preview is healthy. Original preview retained as $backup." +else + docker stop "$web" >/dev/null + sql -d postgres </dev/null + ready + echo 'Snapshot failed the health check. Original preview restored.' >&2 + exit 1 +fi