| # EPK Migration Runbook & Rollback Plan |
|
|
| > **Source platform:** Base44 (proprietary, locked-in) |
| > **Target platform:** Self-hosted on Hetzner Cloud (PostgreSQL, Redis, MinIO, kubeadm K8s + Hetzner LB + DNS) |
| > **Estimated timeline:** 10β11 weeks (per architecture audit) |
| > **Critical blockers:** 7 requiring full reimplementation, 3 partial, 5 platform constraints |
|
|
| --- |
|
|
| ## Phase 0 β Pre-Migration Setup |
|
|
| ### Prerequisites |
| - [ ] Hetzner Cloud account with API token configured |
| - [ ] Domain DNS managed in Hetzner (or delegated NS records) |
| - [ ] Terraform β₯ 1.6 installed and validated (`terraform validate` passes) |
| - [ ] kubectl, helm, psql, redis-cli available locally |
| - [ ] Base44 admin access for data export |
| - [ ] Staging environment deployed and smoke-tested |
| - [ ] Runbook reviewed and signed off by team |
|
|
| ### Environment Variables (secrets) |
| | Variable | Source | Target | |
| |---|---|---| |
| | `HETZNER_TOKEN` | 1Password / Vault | TF_VAR_hcloud_token | |
| | `DOMAIN` | DNS zone | `epk.example.com` | |
| | `DB_PASSWORD` | Generated | PostgreSQL superuser | |
| | `REDIS_PASSWORD` | Generated | Redis ACL | |
| | `MINIO_ACCESS_KEY` / `MINIO_SECRET_KEY` | Generated | MinIO root | |
| | `JWT_SECRET` | Generated (256-bit) | Auth service | |
| | `OAUTH_*_CLIENT_ID` / `OAUTH_*_SECRET` | Provider dashboards | 6 OAuth connectors | |
|
|
| --- |
|
|
| ## Phase 1 β Data Export from Base44 |
|
|
| ### 1.1 Export Strategy |
| - **Frontend-only export:** Base44 GitHub export contains only React frontend code. Backend (DB, auth, functions, realtime) is proprietary and **not exported**. |
| - **Data export:** Use Base44 admin dashboard β "Export Data" (JSON/CSV per entity). For large datasets, request Base44 support for full DB dump. |
| - **Entities to export:** All inferred EPK entities from audit (users, projects, tasks, comments, attachments, subscriptions, webhooks, audit_logs, etc.). |
| |
| ### 1.2 Export Checklist |
| - [ ] Inventory all entities from `base44-audit.md` data models section |
| - [ ] Request full DB dump from Base44 support (if dataset > 100k rows) |
| - [ ] Export each entity to dated directory: `exports/YYYYMMDD/<entity>.json` |
| - [ ] Verify row counts match admin dashboard counters |
| - [ ] Compute SHA256 checksums for each file; store in `exports/YYYYMMDD/CHECKSUMS.txt` |
| - [ ] Archive to cold storage (S3/MinIO) with retention tag |
| |
| ### 1.3 Known Limitations |
| - No access to Base44 internal IDs for auth users (map by email) |
| - Realtime subscription state not exportable β rebuild from scratch |
| - File attachments stored in Base44 CDN β download URLs expire in 24h |
| |
| --- |
| |
| ## Phase 2 β Schema Transformation Scripts |
| |
| ### 2.1 Mapping Rules (MongoDB-compat β PostgreSQL + RLS) |
| | Base44 Type | Postgres Type | Notes | |
| |---|---|---| |
| | `ObjectId` | `uuid` (gen_random_uuid()) | PK on all tables | |
| | `Date` | `timestamptz` | UTC normalized | |
| | `DBRef` | `uuid` FK + RLS policy | Enforce ownership | |
| | `Array<subdoc>` | JSONB column / child table | Normalize if queried | |
| | `Binary` | `bytea` or MinIO object ref | > 1MB β MinIO | |
| |
| ### 2.2 Transformation Pipeline |
| ``` |
| exports/YYYYMMDD/*.json |
| β |
| βΌ |
| scripts/transform/ |
| βββ 01_normalize_ids.py # ObjectId β UUID, build ID map |
| βββ 02_flatten_arrays.py # Subdoc arrays β child tables |
| βββ 03_enforce_rls.py # Add owner_id, gen policies |
| βββ 04_attachments_to_minio.py # Download + upload, rewrite refs |
| βββ 05_validate_schema.py # pg_dump --schema-only diff |
| β |
| βΌ |
| staging_import/YYYYMMDD/*.sql |
| ``` |
| |
| ### 2.3 Script Checklist |
| - [ ] `01_normalize_ids.py` β deterministic UUID v5 (namespace = Base44 export ID) |
| - [ ] `02_flatten_arrays.py` β configurable depth (default 1), foreign keys indexed |
| - [ ] `03_enforce_rls.py` β generates `CREATE POLICY` per table per role (anon, user, admin) |
| - [ ] `04_attachments_to_minio.py` β parallel downloads (8 workers), resumable, Verifies checksums |
| - [ ] `05_validate_schema.py` β compares generated DDL against `schema/target.sql` (committed) |
| - [ ] All scripts: idempotent, structured JSON logs, exit code 0 on success |
| - [ ] Unit tests for each transform (`tests/transform/test_*.py`) |
|
|
| --- |
|
|
| ## Phase 3 β Import Validation |
|
|
| ### 3.1 Staging Import Procedure |
| ```bash |
| # 1. Fresh staging DB (Terraform destroys/recreates) |
| terraform -chdir=infra/terraform apply -target=hcloud_server.db -auto-approve |
| |
| # 2. Run schema |
| psql "$STAGING_DB_URL" -f schema/target.sql |
| |
| # 3. Load data |
| psql "$STAGING_DB_URL" -f staging_import/YYYYMMDD/01_core.sql |
| psql "$STAGING_DB_URL" -f staging_import/YYYYMMDD/02_relations.sql |
| psql "$STAGING_DB_URL" -f staging_import/YYYYMMDD/03_attachments.sql |
| |
| # 4. Validate |
| python scripts/validate_import.py --env staging |
| ``` |
|
|
| ### 3.2 Validation Checks |
| | Check | Tool | Threshold | |
| |---|---|---| |
| | Row count per table | `validate_import.py` | Β±0.1% vs export | |
| | Referential integrity | `pg_dump --data-only --inserts` | 0 FK violations | |
| | RLS policy coverage | `SELECT * FROM pg_policies` | 100% tables with owner_id | |
| | Attachment checksums | MinIO `mc stat` + DB `file_sha256` | 100% match | |
| | Auth user mapping | `validate_import.py --auth` | 100% emails mapped | |
| | Index usage | `pg_stat_user_indexes` | No unused indexes > 100MB | |
|
|
| ### 3.3 Import Checklist |
| - [ ] Staging DB provisioned fresh (no residual data) |
| - [ ] Schema applied without errors |
| - [ ] All 3 data batches loaded in order |
| - [ ] Validation script exits 0 |
| - [ ] Spot-check 10 random entities end-to-end (UI + API) |
| - [ ] Performance baseline: `EXPLAIN ANALYZE` on top 5 queries < 50ms p95 |
|
|
| --- |
|
|
| ## Phase 4 β DNS Cutover |
|
|
| ### 4.1 Pre-Cutover (T-24h) |
| - [ ] TTL on all `epk.example.com` records reduced to 60s |
| - [ ] Hetzner Load Balancer health checks passing (green for 1h) |
| - [ ] Staging promoted to "candidate" β same infra, separate DNS label `candidate.epk.example.com` |
| - [ ] Team smoke test on candidate (Phase 5) passes |
| - [ ] Rollback DNS records pre-staged in Hetzner DNS console |
|
|
| ### 4.2 Cutover Procedure (T=0) |
| ```bash |
| # 1. Verify candidate one last time |
| curl -sf https://candidate.epk.example.com/healthz |
| |
| # 2. Switch apex + www CNAME in single API call (atomic) |
| hcloud dns record update --zone epk.example.com --id <apex_id> --value <lb_ip> |
| hcloud dns record update --zone epk.example.com --id <www_id> --value <lb_ip> |
| |
| # 3. Confirm propagation (check from 3 regions) |
| for r in us eu asia; do dig @$r.epk.example.com +short; done |
| |
| # 4. Update TTL back to 3600 |
| hcloud dns record update --zone epk.example.com --id <apex_id> --ttl 3600 |
| ``` |
|
|
| ### 4.3 Post-Cutover (T+0 to T+1h) |
| - [ ] Monitor LB request rate, error rate, latency (Grafana dashboard) |
| - [ ] Verify realtime WebSocket connections establishing |
| - [ ] Confirm OAuth redirects resolve to new domain |
| - [ ] Run automated smoke suite (Phase 5) every 5 min for 30 min |
|
|
| --- |
|
|
| ## Phase 5 β Smoke Tests |
|
|
| ### 5.1 Automated Suite (`scripts/smoke_test.py`) |
| | Test | Description | Timeout | |
| |---|---|---| |
| | `healthz` | GET `/healthz` returns 200 + `{"status":"ok"}` | 5s | |
| | `auth_login` | POST `/auth/login` β JWT + refresh cookie | 10s | |
| | `auth_refresh` | POST `/auth/refresh` rotates tokens | 5s | |
| | `oauth_google` | Full OAuth flow (headless) | 30s | |
| | `crud_project` | Create β Read β Update β Delete project | 15s | |
| | `realtime_sub` | WS connect β subscribe β receive event | 20s | |
| | `file_upload` | POST `/files` β 201 + MinIO object exists | 30s | |
| | `webhook_delivery` | Trigger webhook β verify signature + retry | 30s | |
|
|
| ### 5.2 Manual Spot-Checks |
| - [ ] Login as admin, create project, invite team member |
| - [ ] Team member accepts invite, sees project |
| - [ ] Realtime: open two browsers, edit same task β sync < 500ms |
| - [ ] File upload > 10MB β progress bar, thumbnail generates |
| - [ ] Export project data (CSV) β downloads correctly |
|
|
| ### 5.3 Smoke Test Checklist |
| - [ ] Automated suite: 8/8 pass |
| - [ ] Manual spot-checks: 5/5 pass |
| - [ ] No console errors in browser devtools |
| - [ ] All OAuth providers redirect to correct callback URL |
| - [ ] Webhook endpoints receive and verify signatures |
|
|
| --- |
|
|
| ## Phase 6 β Rollback Triggers & Procedure |
|
|
| ### 6.1 Automatic Rollback Triggers |
| | Metric | Threshold | Window | Action | |
| |---|---|---|---| |
| | HTTP 5xx rate | > 5% | 5 min | Auto-rollback DNS | |
| | p95 latency | > 2s | 5 min | Alert + manual decision | |
| | WebSocket connect failure | > 10% | 5 min | Alert + manual decision | |
| | DB replication lag | > 30s | 2 min | Alert + manual decision | |
| | Smoke test failure | Any critical test fails | 1 run | Auto-rollback DNS | |
|
|
| ### 6.2 Manual Rollback Decision Matrix |
| | Scenario | Decision | Owner | |
| |---|---|---| |
| | Single non-critical test flaky | Investigate, re-run | On-call | |
| | Multiple critical tests fail | **Rollback immediately** | On-call | |
| | Data inconsistency reported | **Rollback immediately** | TL + On-call | |
| | Performance degrade, recovering | Monitor 15 min, then decide | TL | |
|
|
| ### 6.3 Rollback Procedure (DNS-based, < 2 min) |
| ```bash |
| # 1. Revert DNS to Base44 (pre-staged records) |
| hcloud dns record update --zone epk.example.com --id <apex_id> --value <base44_ip> |
| hcloud dns record update --zone epk.example.com --id <www_id> --value <base44_ip> |
| |
| # 2. Verify Base44 responding |
| curl -sf https://epk.example.com/healthz |
| |
| # 3. Notify team + stakeholders |
| ./scripts/notify.py --channel "#epk-migration" --text "ROLLBACK executed at $(date -u +%H:%M UTC). Base44 live." |
| |
| # 4. Preserve staging for forensics |
| # (Terraform does NOT destroy β keep candidate env) |
| ``` |
|
|
| ### 6.4 Post-Rollback |
| - [ ] Document incident in `incidents/YYYYMMDD-rollback.md` |
| - [ ] Root cause analysis within 48h |
| - [ ] Fix β re-run Phase 3 validation β schedule new cutover |
|
|
| --- |
|
|
| ## Phase 7 β Post-Migration (T+24h to T+7d) |
|
|
| ### 7.1 Stabilization Checklist |
| - [ ] Monitoring dashboards green for 24h |
| - [ ] No P1/P2 incidents |
| - [ ] Backup/restore drill: restore staging from prod snapshot |
| - [ ] Cost review: Hetzner bill vs projection |
| - [ ] Documentation updated: `DEPLOYMENT.md`, `ARCHITECTURE.md` |
| - [ ] Team retrospective scheduled |
|
|
| ### 7.2 Decommission Base44 (T+7d) |
| - [ ] Confirm zero traffic to Base44 (Cloudflare analytics) |
| - [ ] Cancel Base44 subscription |
| - [ ] Revoke Base44 API keys / OAuth credentials |
| - [ ] Archive final Base44 export to cold storage (7-year retention) |
|
|
| --- |
|
|
| ## Appendix A β Command Reference |
|
|
| ```bash |
| # Deploy staging |
| terraform -chdir=infra/terraform apply -var environment=staging |
| |
| # Deploy prod (after cutover approval) |
| terraform -chdir=infra/terraform apply -var environment=prod |
| |
| # Run smoke tests |
| python scripts/smoke_test.py --env prod --junit output/smoke.xml |
| |
| # Validate import |
| python scripts/validate_import.py --env staging --strict |
| |
| # Generate checksums |
| sha256sum exports/YYYYMMDD/*.json > exports/YYYYMMDD/CHECKSUMS.txt |
| |
| # Notify team |
| python scripts/notify.py --channel "#epk-migration" --text "Message" |
| ``` |
|
|
| --- |
|
|
| ## Appendix B β Contacts |
|
|
| | Role | Name | Primary | Escalation | |
| |---|---|---|---| |
| | Migration Lead | | | | |
| | Backend TL | | | | |
| | DevOps | | | | |
| | Base44 Support | support@base44.com | Ticket | Phone: +1-xxx-xxx-xxxx | |
| | Hetzner Support | support@hetzner.com | Ticket | | |
|
|
| --- |
|
|
| ## Sign-Off |
|
|
| | Phase | Reviewer | Date | Signature | |
| |---|---|---|---| |
| | Pre-Migration | | | | |
| | Data Export | | | | |
| | Schema Transform | | | | |
| | Import Validation | | | | |
| | DNS Cutover | | | | |
| | Smoke Tests | | | | |
| | Post-Migration | | | | |
|
|
| --- |
|
|
| *Generated from Base44 audit (t_014f13b3) and Terraform infra (t_a8af80b0). Review with team before execution.* |