01f8974314
CI / Architecture Guardrails (pull_request) Successful in 2m59s
CI / Typecheck (pull_request) Successful in 6m41s
CI / Lint (pull_request) Successful in 4m18s
CI / Assistant Split Regression (pull_request) Successful in 5m6s
CI / Unit Tests (pull_request) Successful in 7m21s
CI / Build (pull_request) Successful in 5m21s
CI / Fresh-Linux Docker Deploy (pull_request) Failing after 38s
CI / E2E Tests (pull_request) Successful in 3m28s
CI / Release Images (pull_request) Has been skipped
- docker-compose.yml / .prod.yml / .ci.yml: project names, POSTGRES_DB/USER, pg_isready, DATABASE_URL, volume names (nexus_pgdata, nexus_prod_*) - .github/workflows/ci.yml: POSTGRES_PASSWORD, pg_isready, psql credentials, GRANT statements, POSTGRES_PASSWORD=nexus_dev for Docker Deploy job - scripts/db-target-guard.mjs: expectedDatabase default, NEXUS_EXPECTED_DB_NAME - scripts/prisma-with-env.mjs, e2e/test-server.mjs: env-var rename - packages/db/src/safe-destructive-env.ts + reset-dispo-import.ts: DB name set - packages/db/src/destructive-db-guard.ts: PROTECTED_DATABASE_NAMES → "nexus" - packages/db/src/destructive-db-guard.test.ts: all fixture DB names + comments - .env.example, tooling/deploy/deploy.env.example: DATABASE_URL, image refs - packages/api: Redis channel/key prefixes (rbac-invalidate, sse, ratelimit), logger service name, app-base-url log prefix - E2E: DB container names, localStorage/sessionStorage keys, email domains - scripts: architecture-guardrails filter, export/import-dev-seed defaults, harden-postgres defaults, start.sh pg_isready, worktree-hygiene fixture - tooling/migrate/rename-to-nexus.sh: new maintenance-window cutover script Only intentional capakraken survivor: anonymization.ts DEFAULT_ANONYMIZATION_SEED (functional cryptographic constant — changing it would invalidate stored aliases). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
168 lines
8.4 KiB
Bash
Executable File
168 lines
8.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Phase 3 cutover: migrate the running stack from `capakraken` (DB, role,
|
|
# volumes, compose project) to `nexus`. Intended to be run inside a brief
|
|
# maintenance window — the app is stopped for the duration.
|
|
#
|
|
# Idempotency: each step checks the current state. Re-running after a
|
|
# partial run only does the missing pieces. The script never DROPS the
|
|
# legacy `capakraken` DB or role — that's a manual decision after a
|
|
# stability window.
|
|
#
|
|
# Usage:
|
|
# ./tooling/migrate/rename-to-nexus.sh dev # against docker-compose.yml
|
|
# ./tooling/migrate/rename-to-nexus.sh prod # against docker-compose.prod.yml
|
|
#
|
|
# Requires:
|
|
# - POSTGRES_PASSWORD set in env (or .env file picked up by docker compose)
|
|
# - docker compose CLI v2+
|
|
# - the `nexus` branch of code already checked out (compose files reference nexus_*)
|
|
|
|
set -euo pipefail
|
|
|
|
MODE="${1:-dev}"
|
|
case "$MODE" in
|
|
dev)
|
|
COMPOSE_FILE=docker-compose.yml
|
|
OLD_PROJECT=capakraken
|
|
NEW_PROJECT=nexus
|
|
VOLUMES=(pgdata node_modules next)
|
|
;;
|
|
prod)
|
|
COMPOSE_FILE=docker-compose.prod.yml
|
|
OLD_PROJECT=capakraken-prod
|
|
NEW_PROJECT=nexus-prod
|
|
VOLUMES=(prod_pgdata prod_redis)
|
|
;;
|
|
*)
|
|
echo "Usage: $0 [dev|prod]" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
|
|
if [ -z "${POSTGRES_PASSWORD:-}" ]; then
|
|
echo "POSTGRES_PASSWORD must be set in the environment." >&2
|
|
exit 2
|
|
fi
|
|
|
|
DUMP_FILE="/tmp/capakraken-pre-rename-$(date +%Y%m%d-%H%M%S).sql"
|
|
|
|
echo "=== Phase 3 cutover ($MODE) ==="
|
|
echo "compose: $COMPOSE_FILE project: $OLD_PROJECT → $NEW_PROJECT dump: $DUMP_FILE"
|
|
echo
|
|
|
|
#───────────────────────────────────────────────────────────────────────────────
|
|
# 1. Stop the app (DB stays up so we can dump it).
|
|
#───────────────────────────────────────────────────────────────────────────────
|
|
echo "[1/7] Stopping app container under old project name..."
|
|
docker compose -p "$OLD_PROJECT" -f "$COMPOSE_FILE" stop app 2>/dev/null || true
|
|
|
|
#───────────────────────────────────────────────────────────────────────────────
|
|
# 2. Capture row counts for verification.
|
|
#───────────────────────────────────────────────────────────────────────────────
|
|
echo "[2/7] Capturing pre-rename row counts..."
|
|
PRE_COUNTS=$(docker compose -p "$OLD_PROJECT" -f "$COMPOSE_FILE" exec -T postgres \
|
|
psql -U capakraken -d capakraken -t -c \
|
|
"SELECT table_name, n_live_tup FROM pg_stat_user_tables ORDER BY table_name;")
|
|
echo "$PRE_COUNTS" | head -20
|
|
echo "..."
|
|
|
|
#───────────────────────────────────────────────────────────────────────────────
|
|
# 3. Dump existing DB.
|
|
#───────────────────────────────────────────────────────────────────────────────
|
|
echo "[3/7] pg_dump capakraken → $DUMP_FILE..."
|
|
docker compose -p "$OLD_PROJECT" -f "$COMPOSE_FILE" exec -T postgres \
|
|
pg_dump -U capakraken -d capakraken --clean --if-exists > "$DUMP_FILE"
|
|
echo "Dump size: $(du -h "$DUMP_FILE" | cut -f1)"
|
|
|
|
#───────────────────────────────────────────────────────────────────────────────
|
|
# 4. Create new role + DB inside the running postgres container.
|
|
#───────────────────────────────────────────────────────────────────────────────
|
|
echo "[4/7] Creating nexus role + database..."
|
|
docker compose -p "$OLD_PROJECT" -f "$COMPOSE_FILE" exec -T postgres \
|
|
psql -U capakraken -d postgres <<SQL
|
|
DO \$\$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='nexus') THEN
|
|
CREATE ROLE nexus LOGIN PASSWORD '${POSTGRES_PASSWORD}';
|
|
END IF;
|
|
END
|
|
\$\$;
|
|
|
|
SELECT 'CREATE DATABASE nexus OWNER nexus'
|
|
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname='nexus')
|
|
\gexec
|
|
SQL
|
|
|
|
#───────────────────────────────────────────────────────────────────────────────
|
|
# 5. Restore into new DB.
|
|
#───────────────────────────────────────────────────────────────────────────────
|
|
echo "[5/7] Restoring dump into nexus DB..."
|
|
# Replace OWNER directives so restored objects belong to `nexus`.
|
|
sed 's/OWNER TO capakraken/OWNER TO nexus/g' "$DUMP_FILE" | \
|
|
docker compose -p "$OLD_PROJECT" -f "$COMPOSE_FILE" exec -T postgres \
|
|
psql -U nexus -d nexus
|
|
|
|
#───────────────────────────────────────────────────────────────────────────────
|
|
# 6. Volume rename (offline copy).
|
|
#───────────────────────────────────────────────────────────────────────────────
|
|
echo "[6/7] Stopping old project and copying volumes..."
|
|
docker compose -p "$OLD_PROJECT" -f "$COMPOSE_FILE" down --remove-orphans
|
|
|
|
for v in "${VOLUMES[@]}"; do
|
|
src="${OLD_PROJECT//-/_}_${v}"
|
|
dst="${NEW_PROJECT//-/_}_${v}"
|
|
# Skip dev-mode app overlays (node_modules / next) — they regenerate on startup.
|
|
if [ "$MODE" = "dev" ] && { [ "$v" = "node_modules" ] || [ "$v" = "next" ]; }; then
|
|
echo " Skipping $v (regenerated on next boot)"
|
|
continue
|
|
fi
|
|
if ! docker volume inspect "$src" >/dev/null 2>&1; then
|
|
echo " Source volume $src missing — skip"
|
|
continue
|
|
fi
|
|
if docker volume inspect "$dst" >/dev/null 2>&1; then
|
|
echo " Destination volume $dst already exists — skip"
|
|
continue
|
|
fi
|
|
echo " $src → $dst"
|
|
docker volume create "$dst" >/dev/null
|
|
docker run --rm \
|
|
-v "${src}:/from:ro" \
|
|
-v "${dst}:/to" \
|
|
alpine sh -c "cd /from && cp -a . /to/"
|
|
done
|
|
|
|
#───────────────────────────────────────────────────────────────────────────────
|
|
# 7. Bring up under new compose project name.
|
|
#───────────────────────────────────────────────────────────────────────────────
|
|
echo "[7/7] Starting stack under new project name '$NEW_PROJECT'..."
|
|
PROFILE=""
|
|
[ "$MODE" = "dev" ] && PROFILE="--profile full"
|
|
# shellcheck disable=SC2086
|
|
docker compose -p "$NEW_PROJECT" -f "$COMPOSE_FILE" $PROFILE up -d
|
|
|
|
echo
|
|
echo "Waiting 15s for postgres to be ready..."
|
|
sleep 15
|
|
|
|
echo "=== Verification ==="
|
|
POST_COUNTS=$(docker compose -p "$NEW_PROJECT" -f "$COMPOSE_FILE" exec -T postgres \
|
|
psql -U nexus -d nexus -t -c \
|
|
"SELECT table_name, n_live_tup FROM pg_stat_user_tables ORDER BY table_name;")
|
|
echo "Post-rename row counts (sample):"
|
|
echo "$POST_COUNTS" | head -20
|
|
|
|
if diff <(echo "$PRE_COUNTS") <(echo "$POST_COUNTS") >/dev/null; then
|
|
echo "✓ Row counts match — migration verified."
|
|
else
|
|
echo "⚠ Row counts differ — review diff:"
|
|
diff <(echo "$PRE_COUNTS") <(echo "$POST_COUNTS") | head
|
|
fi
|
|
|
|
echo
|
|
echo "Done. Old DB+role retained for rollback. Dump kept at $DUMP_FILE."
|
|
echo "After a stability window, drop with:"
|
|
echo " docker compose -p $NEW_PROJECT -f $COMPOSE_FILE exec postgres psql -U nexus -d postgres \\"
|
|
echo " -c 'DROP DATABASE capakraken; DROP ROLE capakraken;'"
|