rename(phase 3): compose/DB/infra names + stray code refs capakraken → nexus
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
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>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
APP_IMAGE=ghcr.io/example/capakraken-app:sha-abc123
|
||||
MIGRATOR_IMAGE=ghcr.io/example/capakraken-migrator:sha-abc123
|
||||
APP_IMAGE=ghcr.io/example/nexus-app:sha-abc123
|
||||
MIGRATOR_IMAGE=ghcr.io/example/nexus-migrator:sha-abc123
|
||||
APP_HOST_PORT=3000
|
||||
GHCR_USERNAME=
|
||||
GHCR_TOKEN=
|
||||
|
||||
Executable
+167
@@ -0,0 +1,167 @@
|
||||
#!/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;'"
|
||||
Reference in New Issue
Block a user