30 KiB
MotorSignal OPS-4 Production Deployment & Database Migration Runbook
(Hardened Final Execution Guide)
NOTICE: DOCUMENTATION & PLANNING ONLY
This runbook is a procedural guide for human execution during an authorized maintenance window on the MotorSignal production VPS.
DO NOT EXECUTE AUTOMATICALLY. Every destructive or state-changing step requires human review, validation, and confirmation.
Document Metadata & Verified Baseline
- Target Checkpoint Commit:
8681e6f25972cea768106e534b11418b58374d28 - Target Branch:
fix/restore-clean-build - Production Host:
api.motorsignal.app(89.31.239.10) - Operating System: Ubuntu 22.04.5 LTS (Jammy Jellyfish)
- Database Engine: PostgreSQL 14.24 (
postgresql@14-main.serviceon127.0.0.1:5432) - Database Name / User:
database=motorsignal,user=motorsignal_app(Size: ~9.2 MB) - Pre-Migration Alembic Revision:
e0f5a7123b46(link_firebase_identity) - Target Alembic Revision:
f1a2b3c4d5e6(add_ops_schema_and_incidents) - Authoritative Migration Path:
e0f5a7123b46\tof1a6b8234c57\tof1b7c8345d68\tof1a2b3c4d5e6 - Deployment User / Service User:
motorsignal-admin(deployer) /motorsignal:motorsignal(service account) - Service Ingress & Ports:
- API:
motorsignal-api.service(127.0.0.1:8080,WorkingDirectory=/opt/motorsignal/current/app) - Website:
motorsignal-website.service(127.0.0.1:3001, Next.js standaloneserver.js) - Ingress:
nginx.service(proxiesapi.motorsignal.app\to8080,motorsignal.app\to3001)
- API:
- Authoritative Backup Directory:
/var/backups/motorsignal(DO NOT use/opt/motorsignal/backups) - Host Security Baseline: Fail2ban active; UFW inactive (DO NOT alter firewall during deployment).
Core Operating Principles
- Investigate First & Stop on Error: If any step produces an unexpected result, error, or revision mismatch, STOP IMMEDIATELY. Never "continue anyway" or retry failing commands blindly.
- Zero-Drift & Safe Secret Handling: Follow the exact commands specified. Never expand secrets into command-line arguments or echo passwords. Pass environment variables in-memory using dedicated Python runners.
- Controlled Quiescence: Application writers (
motorsignal-api.serviceandmotorsignal-website.service) must be stopped before database migration to eliminate version-skew writes. - Immutable Releases & Truly Atomic Cutover: Deploy via timestamped release directories (
/opt/motorsignal/releases/<release-id>) and atomic filesystem renames (current.next\tocurrent). - Rollback Discipline: Never run blind database downgrades on production after writes have occurred. Emergency disaster recovery uses the verified pre-migration backup.
PHASE 0 — Scope & Safety
0.1 Deployment Scope Summary
- Backend API: FastAPI application update supporting OPS-4 operational incidents workflow, CSRF origin verification on mutations, audit-event rollback safety, and rate-limiting enhancements.
- Operations Frontend: Next.js Operations console (
/admin,/admin/incidents,/admin/incidents/[id],/admin/incidents/create), session management, and CSRF-protected proxy routes. - Database Schema: PostgreSQL
opsschema creation and evolution:ops.operators: Operations staff accounts withauthority_versionand immutable identity trigger.ops.sessions: Operator sessions tied to__Host-motorsignal_opscookies.ops.audit_events: Tamper-evident operational audit trail.ops.incidents: Incident management records with case references (OPS-YYYY-NNNNNN).ops.incident_notes: Internal operator notes.ops.incident_case_seq: Sequence for case reference generation.ops.guard_operator_identity(): Trigger function enforcing identity immutability.
PHASE 1 — Preflight & Host Baseline Checks
1.1 VPS Connection & Identity Verification
- Precondition: SSH access configured with deployment key
C:\Users\Bill\.ssh\motorsignal_prod. - Command:
ssh -i ~/.ssh/motorsignal_prod motorsignal-admin@89.31.239.10 hostname whoami id - Expected Result: Hostname is
api.motorsignal.app; user ismotorsignal-admin(uid=1000, member ofsudoandmotorsignalgroups). - Failure Action: STOP. Do not proceed on an unverified host.
1.2 Host Storage & Resources Inspection
- Command:
# Storage check (require >= 10 GB free on root /) df -h / # Memory and load check (require >= 1 GB available RAM) free -m uptime - Expected Result: Free disk
\ge10 GB (discovery showed ~272 GB free); available RAM\ge1 GB; system load normal. - Failure Action: STOP. Resolve resource constraints before proceeding.
1.3 Service Baseline Status
- Command:
systemctl status motorsignal-api.service --no-pager systemctl status motorsignal-website.service --no-pager systemctl status nginx.service --no-pager systemctl status postgresql@14-main.service --no-pager - Expected Result: All services reporting
active (running). - Failure Action: STOP. Investigate pre-existing service failures.
1.4 PostgreSQL 14.24 Engine & Exact Alembic Revision Gate
- Command:
# Check PostgreSQL version sudo -u postgres psql -d motorsignal -c "SELECT version();" # Query exact current Alembic revision sudo -u postgres psql -d motorsignal -t -A -c "SELECT version_num FROM alembic_version;" - Expected Result: Version string indicates PostgreSQL 14.24; revision is EXACTLY
e0f5a7123b46. - Failure Action: STOP IMMEDIATELY. If the revision is not
e0f5a7123b46, abort deployment.
1.5 Baseline Health Probes
- Command:
# Record currently active release PREV_RELEASE=$(readlink -f /opt/motorsignal/current) echo "CURRENT_ACTIVE_RELEASE=${PREV_RELEASE}" # Probe API and website baseline curl -fsS http://127.0.0.1:8080/api/v1/health curl -fsS http://127.0.0.1:8080/api/v1/ready curl -fsS -I http://127.0.0.1:3001/ - Expected Result: API returns
{"status":"ok","service":"motorsignal-api"}and{"status":"ready","database":"ok"}; website returns HTTP 200. - Failure Action: STOP. Do not deploy on a degraded baseline.
1.6 Security & Firewall Notice
- Command:
systemctl is-active fail2ban - Expected Result:
active. - Policy Notice: Discovery confirmed UFW is inactive on this host. DO NOT modify UFW or iptables during OPS-4 deployment.
PHASE 2 — Release Staging & Artifact Preflight
Execute this phase while production is STILL ONLINE and serving traffic from ${PREV_RELEASE}.
2.1 Release Directory Setup & Namespace Isolation
To satisfy motorsignal-api.service (WorkingDirectory=/opt/motorsignal/current/app) and motorsignal-website.service (ExecStart=/usr/bin/node /opt/motorsignal/current/website/server.js) without directory collision:
- Command:
RELEASE_ID="20260910-ops4" NEW_RELEASE="/opt/motorsignal/releases/${RELEASE_ID}" # Ensure release directory structure exists sudo install -d -o motorsignal -g motorsignal "${NEW_RELEASE}" sudo install -d -o motorsignal -g motorsignal "${NEW_RELEASE}/backend" sudo install -d -o motorsignal -g motorsignal "${NEW_RELEASE}/website"
2.2 Unpack Release Artifacts & Compatibility Symlinks
- Command:
# Extract uploaded artifacts sudo -u motorsignal unzip -q /tmp/motorsignal-backend-${RELEASE_ID}.zip -d "${NEW_RELEASE}/backend" sudo -u motorsignal unzip -q /tmp/motorsignal-website-${RELEASE_ID}.zip -d "${NEW_RELEASE}/website" # Establish release-internal compatibility symlinks for systemd and Alembic sudo -u motorsignal ln -sfn backend/app "${NEW_RELEASE}/app" sudo -u motorsignal ln -sfn backend/alembic "${NEW_RELEASE}/alembic" sudo -u motorsignal ln -sfn backend/alembic.ini "${NEW_RELEASE}/alembic.ini" sudo -u motorsignal ln -sfn backend/requirements.lock "${NEW_RELEASE}/requirements.lock" sudo chown -R motorsignal:motorsignal "${NEW_RELEASE}"
2.3 Virtual Environment Setup
- Command:
# Create dedicated virtual environment for this release sudo -u motorsignal python3 -m venv "${NEW_RELEASE}/venv" # Install locked dependencies strictly from requirements.lock sudo -u motorsignal "${NEW_RELEASE}/venv/bin/pip" install --no-cache-dir -r "${NEW_RELEASE}/requirements.lock" - Expected Result: Virtualenv created cleanly with all locked dependencies installed.
2.4 Artifact Preflight & Manifest Verification (MANDATORY GATE)
- Command:
# 1. Verify release manifest source commit grep "source_commit=8681e6f25972cea768106e534b11418b58374d28" "${NEW_RELEASE}/backend/release_manifest.json" || \ grep "8681e6f25972cea768106e534b11418b58374d28" /tmp/release_manifest_${RELEASE_ID}.txt # 2. Verify all runtime files and migration files exist and resolve test -f "${NEW_RELEASE}/app/main.py" && echo "BACKEND_MAIN_OK" test -f "${NEW_RELEASE}/alembic.ini" && echo "ALEMBIC_INI_OK" test -f "${NEW_RELEASE}/alembic/env.py" && echo "ALEMBIC_ENV_OK" test -f "${NEW_RELEASE}/alembic/versions/e0f5a7123b46_link_firebase_identity.py" && echo "MIGRATION_e0f5_OK" test -f "${NEW_RELEASE}/alembic/versions/f1a6b8234c57_add_ops_foundation.py" && echo "MIGRATION_f1a6_OK" test -f "${NEW_RELEASE}/alembic/versions/f1b7c8345d68_evolve_ops_runtime_schema.py" && echo "MIGRATION_f1b7_OK" test -f "${NEW_RELEASE}/alembic/versions/f1a2b3c4d5e6_add_ops_schema_and_incidents.py" && echo "MIGRATION_f1a2_OK" test -f "${NEW_RELEASE}/requirements.lock" && echo "LOCK_OK" test -x "${NEW_RELEASE}/venv/bin/python" && echo "VENV_PYTHON_OK" test -x "${NEW_RELEASE}/venv/bin/alembic" && echo "VENV_ALEMBIC_OK" test -f "${NEW_RELEASE}/website/server.js" && echo "WEBSITE_SERVER_OK" # 3. Check Python engine version "${NEW_RELEASE}/venv/bin/python" --version # 4. Verify Alembic heads in new release (Must be exactly f1a2b3c4d5e6, single head) "${NEW_RELEASE}/venv/bin/alembic" -c "${NEW_RELEASE}/alembic.ini" heads - Expected Result: All artifact checks print
OK; Python version is 3.10.x; Alembic heads showsf1a2b3c4d5e6 (head). - Failure Action: STOP BEFORE MAINTENANCE. If any artifact or migration file is missing, abort.
2.5 Pre-Maintenance Authorization Gate
NEW_RELEASEdirectory created at/opt/motorsignal/releases/20260910-ops4- Source commit confirmed as
8681e6f25972cea768106e534b11418b58374d28 - Backend runtime files verified (
app/main.py,alembic.ini,alembic/env.py) - Website runtime file verified (
website/server.js) - Dedicated
venvcreated and populated from locked requirements - All 4 migration files present in chain (
e0f5,f1a6,f1b7,f1a2) - Alembic head confirmed as
f1a2b3c4d5e6(single head) - Active release path recorded in
PREV_RELEASE - Production currently healthy (
/health== 200,/ready== 200)
GATE DECISION: If ALL items checked, proceed to Phase 3. Otherwise, DO NOT STOP SERVICES.
PHASE 3 — Pre-Maintenance Backup & Disposable Restore Drill
3.1 Safe Pre-Migration Logical Backup
- Command:
# Ensure backup directory exists sudo install -d -m 0750 -o postgres -g postgres /var/backups/motorsignal BACKUP_TS=$(date +%Y%m%d_%H%M%S) BACKUP_FILE="/var/backups/motorsignal/motorsignal_pre_ops4_${BACKUP_TS}.dump" export BACKUP_FILE # Execute pg_dump securely via Python runner (No passwords in argv or stdout) sudo -u motorsignal BACKUP_FILE="${BACKUP_FILE}" "${NEW_RELEASE}/venv/bin/python" - <<'PY' import os, subprocess, sys from dotenv import dotenv_values env_vars = dotenv_values("/etc/motorsignal/production.env") pg_env = os.environ.copy() pg_env["PGHOST"] = env_vars.get("DATABASE_HOST", "127.0.0.1") pg_env["PGPORT"] = str(env_vars.get("DATABASE_PORT", "5432")) pg_env["PGDATABASE"] = env_vars.get("DATABASE_NAME", "motorsignal") pg_env["PGUSER"] = env_vars.get("DATABASE_USER", "motorsignal_app") if "DATABASE_PASSWORD" in env_vars: pg_env["PGPASSWORD"] = env_vars["DATABASE_PASSWORD"] backup_file = os.environ["BACKUP_FILE"] res = subprocess.run(["pg_dump", "-Fc", "-v", "-f", backup_file, pg_env["PGDATABASE"]], env=pg_env) sys.exit(res.returncode) PY # Verify backup file size, checksum, and table-of-contents readability test -s "${BACKUP_FILE}" && echo "BACKUP_SIZE_OK" sha256sum "${BACKUP_FILE}" | sudo tee "${BACKUP_FILE}.sha256" sudo -u postgres pg_restore --list "${BACKUP_FILE}" | head -n 25 - Expected Result: Backup created with non-zero size (~9 MB); checksum recorded;
pg_restore --listdisplays valid TOC.
3.2 Disposable Restore Drill (Mandatory Gate)
- Command:
DRILL_DB="motorsignal_restore_drill_${BACKUP_TS}" # 1. Create temporary drill database sudo -u postgres psql -c "CREATE DATABASE ${DRILL_DB};" # 2. Restore backup into drill database sudo -u postgres pg_restore -v --no-owner --no-privileges -d "${DRILL_DB}" "${BACKUP_FILE}" # 3. Verify Alembic revision in drill database sudo -u postgres psql -d "${DRILL_DB}" -t -A -c "SELECT version_num FROM alembic_version;" # Expected: e0f5a7123b46 # 4. Verify core table counts match sudo -u postgres psql -d "${DRILL_DB}" -c "SELECT 'users' AS tbl, count(*) FROM users UNION ALL SELECT 'rides', count(*) FROM rides;" # 5. Clean up drill database sudo -u postgres psql -c "DROP DATABASE ${DRILL_DB};" - Expected Result: Drill DB restores cleanly;
alembic_versionise0f5a7123b46; counts match; drill DB dropped. - Failure Action: STOP DEPLOYMENT. If the drill fails, do not proceed with migration.
PHASE 4 — Maintenance Quiesce
4.1 Quiesce Application Writers
- Command:
# Record pre-quiesce service states sudo systemctl status motorsignal-api.service motorsignal-website.service --no-pager # Stop application services sudo systemctl stop motorsignal-api.service sudo systemctl stop motorsignal-website.service # Confirm services are inactive sudo systemctl is-active motorsignal-api.service || echo "API_STOPPED" sudo systemctl is-active motorsignal-website.service || echo "WEBSITE_STOPPED" # Confirm PostgreSQL remains active sudo systemctl is-active postgresql@14-main.service - Expected Result: Application services report
inactive; PostgreSQL remainsactive (running). - Note: Nginx remains running. Public traffic temporarily receives
502 Bad Gatewayduring the ~1–2 minute migration window.
PHASE 5 — Final Quiesced Database Backup
5.1 Final Quiesced Snapshot
To guarantee an absolute zero-skew recovery point after writers are confirmed stopped:
- Command:
FINAL_BACKUP_FILE="/var/backups/motorsignal/motorsignal_final_quiesced_${BACKUP_TS}.dump" export FINAL_BACKUP_FILE sudo -u motorsignal BACKUP_FILE="${FINAL_BACKUP_FILE}" "${NEW_RELEASE}/venv/bin/python" - <<'PY' import os, subprocess, sys from dotenv import dotenv_values env_vars = dotenv_values("/etc/motorsignal/production.env") pg_env = os.environ.copy() pg_env["PGHOST"] = env_vars.get("DATABASE_HOST", "127.0.0.1") pg_env["PGPORT"] = str(env_vars.get("DATABASE_PORT", "5432")) pg_env["PGDATABASE"] = env_vars.get("DATABASE_NAME", "motorsignal") pg_env["PGUSER"] = env_vars.get("DATABASE_USER", "motorsignal_app") if "DATABASE_PASSWORD" in env_vars: pg_env["PGPASSWORD"] = env_vars["DATABASE_PASSWORD"] backup_file = os.environ["BACKUP_FILE"] res = subprocess.run(["pg_dump", "-Fc", "-v", "-f", backup_file, pg_env["PGDATABASE"]], env=pg_env) sys.exit(res.returncode) PY test -s "${FINAL_BACKUP_FILE}" && sha256sum "${FINAL_BACKUP_FILE}" | sudo tee "${FINAL_BACKUP_FILE}.sha256" - Expected Result: Final quiesced backup created and checksum recorded.
PHASE 6 — Database Migration
CRITICAL EXECUTION GATE:
Executes migration against production database. Run once. DO NOT retry blindly on error.
6.1 Pre-Migration State Gate
- Command:
# Verify current database revision is exactly e0f5a7123b46 sudo -u postgres psql -d motorsignal -t -A -c "SELECT version_num FROM alembic_version;" # Must output: e0f5a7123b46
6.2 Execute Migration via Secure Python Runner
- Command:
sudo -u motorsignal NEW_RELEASE="${NEW_RELEASE}" "${NEW_RELEASE}/venv/bin/python" - <<'PY' import os, subprocess, sys from dotenv import dotenv_values release = os.environ["NEW_RELEASE"] env_vars = dotenv_values("/etc/motorsignal/production.env") run_env = os.environ.copy() for k, v in env_vars.items(): if v is not None: run_env[k] = v res = subprocess.run( [ f"{release}/venv/bin/alembic", "-c", f"{release}/alembic.ini", "upgrade", "f1a2b3c4d5e6", ], cwd=release, env=run_env, ) sys.exit(res.returncode) PY - Expected Result: Alembic logs execution through:
e0f5a7123b46 -> f1a6b8234c57(add_ops_foundation)f1a6b8234c57 -> f1b7c8345d68(evolve_ops_runtime_schema)f1b7c8345d68 -> f1a2b3c4d5e6(add_ops_schema_and_incidents)
Python process exits with code0.
- Failure Action: If non-zero exit code: STOP IMMEDIATELY. Do not retry. Proceed to PHASE 12 (Rollback - Case C).
PHASE 7 — Post-Migration Database Verification
Perform read-only PostgreSQL catalog inspection before cutover.
7.1 Verify Alembic Version
- Command:
sudo -u postgres psql -d motorsignal -t -A -c "SELECT version_num FROM alembic_version;" - Expected Result: EXACTLY
f1a2b3c4d5e6.
7.2 Verify ops Schema & Tables
- Command:
sudo -u postgres psql -d motorsignal -c " SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema = 'ops' ORDER BY table_name;" - Expected Result: 5 tables listed:
ops.audit_eventsops.incident_notesops.incidentsops.operatorsops.sessions
7.3 Verify Sequence, Functions & Triggers
- Command:
# Sequence check sudo -u postgres psql -d motorsignal -c " SELECT sequence_name FROM information_schema.sequences WHERE sequence_schema = 'ops';" # Function check sudo -u postgres psql -d motorsignal -c " SELECT routine_name FROM information_schema.routines WHERE routine_schema = 'ops';" # Trigger check sudo -u postgres psql -d motorsignal -c " SELECT trigger_name, event_manipulation, event_object_table FROM information_schema.triggers WHERE trigger_schema = 'ops';" - Expected Result:
ops.incident_case_seqexists;guard_operator_identityfunction and trigger onops.operatorsexist.
7.4 Verify Permissions & Constraints
- Command:
# Check PUBLIC schema usage revoked sudo -u postgres psql -d motorsignal -t -A -c "SELECT has_schema_privilege('public', 'ops', 'usage');" # Expected: f - Expected Result: Returns
f(false).
PHASE 8 — Atomic Symlink Cutover & Service Startup
8.1 Truly Atomic Symlink Cutover
- Command:
# 1. Verify temporary target does NOT already exist if [ -e /opt/motorsignal/current.next ] || [ -L /opt/motorsignal/current.next ]; then echo "STOP: /opt/motorsignal/current.next already exists! Investigate before proceeding." exit 1 fi # 2. Create temporary symlink on the same filesystem sudo -u motorsignal ln -s "${NEW_RELEASE}" /opt/motorsignal/current.next # 3. Verify temporary symlink resolves to NEW_RELEASE test "$(readlink -f /opt/motorsignal/current.next)" = "${NEW_RELEASE}" || { echo "STOP: current.next target mismatch!"; exit 1; } # 4. Atomically replace current symlink via Linux rename semantics sudo mv -Tf /opt/motorsignal/current.next /opt/motorsignal/current # 5. Verify active release test "$(readlink -f /opt/motorsignal/current)" = "${NEW_RELEASE}" && echo "CUTOVER_SUCCESSFUL" - Expected Result:
CUTOVER_SUCCESSFULprinted;/opt/motorsignal/currentresolves to${NEW_RELEASE}.
8.2 Start Services
- Command:
sudo systemctl start motorsignal-api.service sudo systemctl start motorsignal-website.service # Check service statuses sudo systemctl status motorsignal-api.service --no-pager sudo systemctl status motorsignal-website.service --no-pager - Expected Result: Both services report
active (running).
8.3 Inspect Startup Journals
- Command:
sudo journalctl -u motorsignal-api.service -n 50 --no-pager sudo journalctl -u motorsignal-website.service -n 50 --no-pager - Expected Result: Uvicorn listening on
127.0.0.1:8080, Next.js standalone listening on127.0.0.1:3001, zero startup errors.
PHASE 9 — Local Service Health Verification
9.1 Local Loopback Health Probes
- Command:
# Core API liveness probe curl -fsS -v http://127.0.0.1:8080/api/v1/health # Database readiness probe curl -fsS -v http://127.0.0.1:8080/api/v1/ready # Operations subsystem health probe curl -fsS -v http://127.0.0.1:8080/api/v1/ops/health # Next.js website loopback probe curl -fsS -I http://127.0.0.1:3001/ - Expected Results:
/api/v1/health\toHTTP 200{"status":"ok","service":"motorsignal-api"}/api/v1/ready\toHTTP 200{"status":"ready","database":"ok"}/api/v1/ops/health\toHTTP 200{"status":"available",...}http://127.0.0.1:3001/\toHTTP 200 OK
- Failure Action: STOP. Do not consider deployment successful if local probes fail.
PHASE 10 — Public HTTPS Health Verification
10.1 Public Ingress Probes
- Command:
# Public API health curl -fsS -i https://api.motorsignal.app/api/v1/health curl -fsS -i https://api.motorsignal.app/api/v1/ready # Public Website routes curl -fsS -I https://motorsignal.app/ curl -fsS -I https://motorsignal.app/features curl -fsS -I https://motorsignal.app/pricing - Expected Result: HTTP 200 OK across public endpoints.
10.2 Operations Security Probes (Safe Unauthenticated Checks)
- Command:
# Probe /ops/me unauthenticated (Must return 401, NOT 404) curl -s -o /dev/null -w "%{http_code}\n" https://api.motorsignal.app/api/v1/ops/me # Expected: 401 # Probe /ops/incidents unauthenticated (Must return 401, NOT 404) curl -s -o /dev/null -w "%{http_code}\n" https://api.motorsignal.app/api/v1/ops/incidents # Expected: 401 # Check Operations web route redirect / response curl -s -I https://motorsignal.app/admin | head -n 5 - Expected Result:
/ops/meand/ops/incidentsreturnHTTP 401 Unauthorized(confirming OPS-4 routes are active and secured); no 404s.
PHASE 11 — Observation Window
11.1 30-Minute Intensive Monitoring Checklist
- Service Restart Counts:
systemctl show motorsignal-api.service -p NRestarts systemctl show motorsignal-website.service -p NRestarts # Must remain 0 - Nginx Error Logs:
sudo tail -n 100 /var/log/nginx/error.log - PostgreSQL Connection & Activity:
sudo -u postgres psql -c "SELECT count(*), state FROM pg_stat_activity GROUP BY state;" - Live Journal Monitoring:
sudo journalctl -u motorsignal-api.service -f --since "10 minutes ago"
11.2 Rollback Criteria During Observation
- Sustained HTTP 5xx error rate > 1%.
- Database deadlocks, lock contention, or connection exhaustion.
- Uncaught exceptions or crash loops in API service.
- Auth or session corruption affecting consumer riders or operators.
PHASE 12 — Rollback & Recovery Procedures
12.1 Rollback Philosophy & Decision Matrix
| Scenario | Nature of Failure | Action |
|---|---|---|
| CASE A | Application fails immediately post-cutover; ZERO new production writes. | 1. Stop services. 2. Atomically repoint /opt/motorsignal/current to ${PREV_RELEASE}.3. Restart previous services. 4. Re-verify health. 5. Do NOT downgrade database unless old code is incompatible with expanded schema. |
| CASE B | Failure occurs after production traffic and NEW WRITES have occurred. | 1. DO NOT run blind alembic downgrade (risks data loss). 2. Deploy forward-fix patch, OR 3. If catastrophic, execute Disaster Recovery Restore from pre-migration backup. |
| CASE C | Alembic migration command fails during Phase 6. | 1. Keep application stopped. 2. Inspect PostgreSQL transaction state (Alembic runs in transaction). 3. Fix migration issue or restart old application against unchanged e0f5a7123b46 schema. |
12.2 Procedure: Application Rollback (Case A)
- Precondition: Stored
${PREV_RELEASE}path (e.g./opt/motorsignal/releases/20260908-15). - Command:
# 1. Stop application services sudo systemctl stop motorsignal-api.service sudo systemctl stop motorsignal-website.service # 2. Atomically repoint current symlink to previous release sudo -u motorsignal ln -s "${PREV_RELEASE}" /opt/motorsignal/current.prev sudo mv -Tf /opt/motorsignal/current.prev /opt/motorsignal/current # 3. Restart previous services sudo systemctl start motorsignal-api.service sudo systemctl start motorsignal-website.service # 4. Verify local health curl -fsS http://127.0.0.1:8080/api/v1/health curl -fsS http://127.0.0.1:8080/api/v1/ready curl -fsS -I http://127.0.0.1:3001/
12.3 Procedure: Disaster Recovery Database Restore (Emergency Only)
DANGER: RESTORATIVE DISASTER RECOVERY.
Requires explicit operator authorization. Replaces database contents with pre-migration backup.
- Command:
# 1. Stop application services sudo systemctl stop motorsignal-api.service sudo systemctl stop motorsignal-website.service # 2. Terminate active database connections sudo -u postgres psql -c " SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'motorsignal' AND pid <> pg_backend_pid();" # 3. Rename degraded database for forensic preservation (DO NOT DROP) TS=$(date +%Y%m%d_%H%M%S) sudo -u postgres psql -c "ALTER DATABASE motorsignal RENAME TO motorsignal_failed_${TS};" # 4. Create fresh database sudo -u postgres psql -c "CREATE DATABASE motorsignal OWNER motorsignal_app;" # 5. Restore from verified pre-migration backup sudo -u postgres pg_restore -v --no-owner --no-privileges -d motorsignal "${FINAL_BACKUP_FILE}" # 6. Verify restored database revision sudo -u postgres psql -d motorsignal -t -A -c "SELECT version_num FROM alembic_version;" # Expected: e0f5a7123b46 # 7. Start previous release sudo -u motorsignal ln -s "${PREV_RELEASE}" /opt/motorsignal/current.prev sudo mv -Tf /opt/motorsignal/current.prev /opt/motorsignal/current sudo systemctl start motorsignal-api.service sudo systemctl start motorsignal-website.service # 8. Re-verify health curl -fsS http://127.0.0.1:8080/api/v1/health curl -fsS http://127.0.0.1:8080/api/v1/ready
PHASE 13 — Post-Deployment Execution Record
Complete and archive this record upon deployment execution.
================================================================================
MOTORSIGNAL PRODUCTION DEPLOYMENT EXECUTION RECORD
================================================================================
Deployment Date/Time (UTC) : ________________________________________________
Operator Name / ID : ________________________________________________
Approved Checkpoint Commit : 8681e6f25972cea768106e534b11418b58374d28
Deployed Release Directory : /opt/motorsignal/releases/______________________
Previous Release Directory : /opt/motorsignal/releases/______________________
DATABASE STATE
--------------------------------------------------------------------------------
Pre-Migration Alembic Rev : e0f5a7123b46
Target Alembic Revision : f1a2b3c4d5e6
Post-Migration Alembic Rev : ________________________________________________
PostgreSQL Engine Version : PostgreSQL 14.24
Backup Filename : /var/backups/motorsignal/_______________________
Backup SHA-256 Checksum : ________________________________________________
Restore Drill Result : [ ] PASS (Verified e0f5a7123b46 in drill DB)
CHANGE WINDOW TIMINGS
--------------------------------------------------------------------------------
Application Quiesced : _____:_____:_____ UTC
Migration Start Time : _____:_____:_____ UTC
Migration End Time : _____:_____:_____ UTC
Migration Exit Code : ________________
Services Restarted : _____:_____:_____ UTC
Total Change Window Duration : _____ minutes
VERIFICATION & HEALTH
--------------------------------------------------------------------------------
Local API Health (200 OK) : [ ] PASS [ ] FAIL
Local API Ready (200 OK) : [ ] PASS [ ] FAIL
Local Ops Health (200 OK) : [ ] PASS [ ] FAIL
Local Website (200 OK) : [ ] PASS [ ] FAIL
Public HTTPS API Health : [ ] PASS [ ] FAIL
Public HTTPS Ready Check : [ ] PASS [ ] FAIL
Public Website Routes Check : [ ] PASS [ ] FAIL
Ops Unauth Probe /ops/me : [ ] PASS (401) [ ] FAIL
Ops Unauth Probe /ops/incidents: [ ] PASS (401) [ ] FAIL
Observation Window (30 min) : [ ] PASS [ ] FAIL
INCIDENTS & ROLLBACK
--------------------------------------------------------------------------------
Rollback Required? : [ ] YES [ ] NO
Rollback Scenario (if any) : [ ] CASE A [ ] CASE B [ ] CASE C
Incidents Encountered : ________________________________________________
Corrective Notes : ________________________________________________
Sign-off Operator Signature : ________________________________________________
================================================================================