docs(homelab): pricing migration order fix and cutover checklist

Document homelab Neon teardown steps, add fix-pricing-pgmigration-order script, and refresh migrate-neon convoy status for Dokploy.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Randall Stillwell 2026-09-09 11:47:53 -05:00
parent d80c20c75e
commit 2984185e6b
5 changed files with 85 additions and 5 deletions

View file

@ -46,9 +46,9 @@ Vercel cannot connect to `192.168.68.102`. Production cutover requires
| 3 | `docs/HOMELAB_DATABASE.md` runbook | code | done |
| 4 | Operator: provision DB, `npm run migrate up` | operator | done |
| 5 | MinIO + Redis wiring, Dockerfile, Dokploy docs | code | done |
| 6 | `npm run migrate-neon-to-homelab` data copy | operator | pending |
| 7 | Dokploy app + Traefik route | operator | in_progress |
| 8 | Decommission Neon + Vercel | operator | pending |
| 6 | `npm run migrate-neon-to-homelab` data copy | operator | **done** (homelab has prod data; ~69k cards) |
| 7 | Dokploy app + Traefik route | operator | **done** (`deckhearth.stillwell.cloud`; `POSTGRES_URL` → CT 102) |
| 8 | Decommission Neon + Vercel | operator | **pending** — safe after Dokploy soak + local `.env.local` switched |
## Env contract

View file

@ -116,6 +116,22 @@ Copies **data only** (schema from Phase 2 migrations). Harmless restore errors:
After Dokploy prod is healthy for 24–48 h, pause/delete the Neon project and disable the Vercel deployment.
### Migration complete checklist (2026-09)
Use this when Neon sends storage alerts but production already runs on homelab:
| Step | Status (verify) | Action |
| --- | --- | --- |
| 1. Prod DB | Dokploy `POSTGRES_URL` → `192.168.68.102:5432/deckhearth` | CT 112 deckhearth container env |
| 2. Data on homelab | `SELECT count(*) FROM cards` ≈ 69k | CT 102 `psql` |
| 3. Pricing schema | `1783000000001_add-pricing-schema` in `pgmigrations` | Pricing convoy / PR #158 |
| 4. Migrate order fix | `npm run fix-pricing-pgmigration-order` then `npm run migrate up` | Mac on LAN with homelab `.env.local` |
| 5. Local dev | `.env.local` `POSTGRES_URL` + `POSTGRES_URL_DIRECT` → homelab (copy password from Dokploy) | Do not commit |
| 6. Dokploy cleanup | Remove env vars whose values contain `neon.tech` (`DATABASE_URL`, `NEON_*`, legacy `PGHOST*`) | Redeploy deckhearth |
| 7. Neon | Pause or delete project in Neon console | Frees storage; keep `NEON_DATABASE_URL` in `.env.local` until step 6 verified |
Optional: Neon had **6 users** vs homelab **5** at last check — run `npm run migrate-neon-to-homelab` only if you need that missing account (uses `postgres:17` Docker if local `pg_dump` is old).
---
## Code changes (this convoy)
@ -132,4 +148,6 @@ After Dokploy prod is healthy for 24–48 h, pause/delete the Neon project and d
| `connection refused` to 192.168.68.102 | Mac not on LAN / VPN; CT 102 down |
| `SSL required` against homelab | Fixed in `lib/sql.js` for private IPs |
| `type "vector" does not exist` | Run `CREATE EXTENSION vector` in `deckhearth` DB |
| Vercel preview still on Neon | Retired — CI smoke targets homelab URL |
| `npm run migrate up` fails "preceding already run migration" on homelab | Pricing branch backfill: `npm run fix-pricing-pgmigration-order` then `npm run migrate up`. Or one-off: `npx node-pg-migrate … --check-order false up` |
| Neon still in Dokploy env but app uses homelab | Remove `DATABASE_URL`, `NEON_*`, `PGHOST*` pointing at `*.neon.tech`; keep only `POSTGRES_URL` + `POSTGRES_URL_DIRECT` → `192.168.68.102` |
| Mac `.env.local` still Neon | Copy homelab `POSTGRES_URL` from Dokploy CT 112; set `POSTGRES_URL_DIRECT` to match; keep `NEON_DATABASE_URL` until Neon deleted |

View file

@ -18,6 +18,7 @@
"bulk-import-pokemon": "node --env-file=.env.local scripts/bulk-import-pokemon.js",
"backfill-embeddings": "node --env-file=.env.local scripts/backfill-card-embeddings.js",
"migrate-neon-to-homelab": "node --env-file=.env.local scripts/migrate-neon-to-homelab.js",
"fix-pricing-pgmigration-order": "node --env-file=.env.local scripts/fix-pricing-pgmigration-order.js",
"import-tags": "node --env-file=.env.local scripts/import-scryfall-tags.js",
"test": "vitest",
"test:run": "vitest run",

View file

@ -0,0 +1,61 @@
#!/usr/bin/env node
/**
* Backfill pgmigrations.run_on for 1783000000001 so `npm run migrate up`
* passes node-pg-migrate order check on homelab DBs that applied pricing
* DDL out of timestamp sequence (pricing convoy branch merged after frame migrations).
*
* Requires POSTGRES_URL_DIRECT (homelab deckhearth URL).
*
* Usage:
* node --env-file=.env.local scripts/fix-pricing-pgmigration-order.js
*/
import postgres from 'postgres';
function requireEnv(name) {
const value = process.env[name]?.trim();
if (!value) {
console.error(`❌ ${name} is required`);
process.exit(1);
}
return value;
}
const MIGRATION_NAME = '1783000000001_add-pricing-schema';
/** Between 1782000000001 (embeddings) and 1786757312884 (visibility index). */
const RUN_ON = '2026-06-15 12:00:00+00';
async function main() {
const url = requireEnv('POSTGRES_URL_DIRECT');
const isPrivateLan = /(?:^|@)(?:localhost|127\.0\.0\.1|192\.168\.|10\.|172\.(?:1[6-9]|2\d|3[01])\.)/.test(url);
const sql = postgres(url, { ssl: isPrivateLan ? false : 'require', max: 1 });
try {
const existing = await sql`
SELECT name, run_on FROM pgmigrations WHERE name = ${MIGRATION_NAME}
`;
if (existing.length === 0) {
console.error(`❌ No pgmigrations row for ${MIGRATION_NAME}. Run pricing migration SQL first.`);
process.exit(1);
}
const before = existing[0].run_on;
await sql`
UPDATE pgmigrations
SET run_on = ${RUN_ON}::timestamptz
WHERE name = ${MIGRATION_NAME}
`;
console.log(`✅ Updated ${MIGRATION_NAME}`);
console.log(` run_on: ${before} → ${RUN_ON}`);
console.log('Next: npm run migrate up');
} finally {
await sql.end({ timeout: 5 });
}
}
main().catch((err) => {
console.error(err.message || err);
process.exit(1);
});

View file

@ -87,7 +87,7 @@ async function main() {
run('psql', [targetUrl, '-c', 'SELECT pg_size_pretty(pg_database_size(current_database())) AS db_size;']);
console.log('\n✅ Data copy finished.');
console.log('Next: set POSTGRES_URL to the homelab URL and deploy (Coolify on CT 107).');
console.log('Next: set POSTGRES_URL to the homelab URL and deploy (Dokploy on CT 112).');
} finally {
rmSync(tempDir, { recursive: true, force: true });
}