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:
parent
d80c20c75e
commit
2984185e6b
5 changed files with 85 additions and 5 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
61
scripts/fix-pricing-pgmigration-order.js
Normal file
61
scripts/fix-pricing-pgmigration-order.js
Normal 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);
|
||||
});
|
||||
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue