Files
classroomio/packages/db/src/scripts/pg-dump-backup.ts
T
2026-05-19 04:32:32 +01:00

87 lines
2.9 KiB
TypeScript

/**
* Full logical backup of the database into `packages/db/backups/` using `pg_dump`.
*
* Requires PostgreSQL client tools (`pg_dump`) on PATH unless `PG_DUMP_BIN` is set.
*
* **Server/client major version:** `pg_dump` must match the server's major version or be newer (e.g.
* PostgreSQL **15** server needs `pg_dump` **15+**, not 14). Postgres.app 14 ships `pg_dump` 14 —
* upgrade Postgres.app, install `postgresql@15` / `libpq` from Homebrew, or set `PG_DUMP_BIN`.
*
* Env:
* - `DATABASE_URL` — connection string (default source).
* - `PGDUMP_URL` — overrides `DATABASE_URL` (use Supabase **direct** Postgres `5432` if pooler rejects dump).
* - `PG_DUMP_BIN` — path to `pg_dump` executable (defaults to `pg_dump` on `PATH`).
* - `PG_DUMP_FORMAT=plain` — SQL text `.sql`; default is custom `-Fc` `.dump` (`pg_restore` compatible).
*/
import 'dotenv/config';
import { spawnSync } from 'node:child_process';
import { mkdirSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const connectionUrl = process.env.PGDUMP_URL ?? process.env.DATABASE_URL;
if (!connectionUrl) {
console.error('Set DATABASE_URL (or PGDUMP_URL) to run a backup.');
process.exitCode = 1;
}
const usePlainSql = process.env.PG_DUMP_FORMAT === 'plain';
const verbose = process.env.PG_DUMP_VERBOSE !== '0';
const pgDumpExecutable = process.env.PG_DUMP_BIN ?? 'pg_dump';
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const backupDir = path.resolve(scriptDir, '../../backups');
function versionMismatchHint() {
return [
'',
'If the error mentions server/client version mismatch, use a newer pg_dump (major >= server), e.g.:',
' brew install postgresql@15',
` PG_DUMP_BIN=$(brew --prefix postgresql@15)/bin/pg_dump pnpm --filter @cio/db db:dump-backup`,
'(Or upgrade Postgres.app to a bundle that ships PostgreSQL 15+ client tools.)',
''
].join('\n');
}
function main() {
if (!connectionUrl) {
return;
}
mkdirSync(backupDir, { recursive: true });
const outfile = path.join(backupDir, usePlainSql ? `full-${stamp}.sql` : `full-${stamp}.dump`);
const args = usePlainSql
? [...(verbose ? ['-v'] : []), '--dbname', connectionUrl, '-F', 'p', '-f', outfile]
: [...(verbose ? ['-v'] : []), '--dbname', connectionUrl, '-F', 'c', '-Z', '9', '--file', outfile];
console.log('Using:', pgDumpExecutable);
console.log('pg_dump →', outfile);
const result = spawnSync(pgDumpExecutable, args, { stdio: 'inherit' });
if (result.error) {
console.error('pg_dump spawn failed:', result.error.message);
console.error(versionMismatchHint());
process.exitCode = 1;
return;
}
if (result.status !== 0) {
process.exitCode = result.status ?? 1;
console.error(versionMismatchHint());
return;
}
console.log('Backup finished:', outfile);
}
main();