#!/usr/bin/env node /** * Copy Deck Hearth data from Neon → homelab Postgres (CT 102). * * Run AFTER: * 1. `deckhearth` database exists on CT 102 (see docs/HOMELAB_DATABASE.md) * 2. `npm run migrate up` on the empty homelab database * * Env: * NEON_DATABASE_URL — Neon direct connection (source) * POSTGRES_URL_DIRECT — homelab deckhearth URL (target), e.g. * postgresql://deckhearth:…@192.168.68.102:5432/deckhearth * SKIP_CONFIRM=1 — skip interactive prompt * * Requires `pg_dump`, `pg_restore`, and `psql` on PATH. */ import { spawnSync } from 'node:child_process'; import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import readline from 'node:readline/promises'; function requireEnv(name) { const value = process.env[name]; if (!value?.trim()) { console.error(`❌ ${name} is required`); process.exit(1); } return value.trim(); } function run(cmd, args) { console.log(`\n→ ${cmd} ${args.join(' ')}`); const result = spawnSync(cmd, args, { stdio: 'inherit' }); if (result.status !== 0) { throw new Error(`${cmd} exited with code ${result.status}`); } } async function confirm(message) { if (process.env.SKIP_CONFIRM === '1') return; const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const answer = await rl.question(`${message} Type "yes" to continue: `); rl.close(); if (answer.trim().toLowerCase() !== 'yes') { console.log('Aborted.'); process.exit(0); } } async function main() { const sourceUrl = requireEnv('NEON_DATABASE_URL'); const targetUrl = requireEnv('POSTGRES_URL_DIRECT'); console.log('Neon → homelab (CT 102) data migration'); console.log('Target must already have schema from npm run migrate up'); await confirm('\nThis copies DATA ONLY into homelab Postgres.\n'); const tempDir = mkdtempSync(join(tmpdir(), 'deckhearth-pg-')); const dumpPath = join(tempDir, 'neon-data.dump'); try { run('pg_dump', [ sourceUrl, '--format=custom', '--data-only', '--no-owner', '--no-acl', '--verbose', '--file', dumpPath, ]); run('pg_restore', [ '--dbname', targetUrl, '--data-only', '--no-owner', '--no-acl', '--verbose', '--disable-triggers', dumpPath, ]); 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).'); } finally { rmSync(tempDir, { recursive: true, force: true }); } } main().catch((error) => { console.error(error.message || error); process.exit(1); });