100 lines
2.8 KiB
JavaScript
100 lines
2.8 KiB
JavaScript
|
|
#!/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);
|
||
|
|
});
|