deckhearth/scripts/migrate-neon-to-homelab.js

100 lines
2.8 KiB
JavaScript
Raw Normal View History

#!/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);
});