Files
Qris-Soundbox/scripts/backup-production.mjs

86 lines
2.5 KiB
JavaScript

#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
import { spawnSync } from "node:child_process";
import "dotenv/config";
const args = process.argv.slice(2);
function getArg(name) {
const index = args.indexOf(name);
return index >= 0 ? args[index + 1] : undefined;
}
function hasFlag(name) {
return args.includes(name);
}
function usage() {
console.log(`Usage:
node scripts/backup-production.mjs [--out ./backups] [--include-mosquitto] [--dry-run]
Creates a timestamped PostgreSQL custom-format dump. When --include-mosquitto is set,
copies Mosquitto passwd/ACL files if readable.
`);
}
if (hasFlag("--help") || hasFlag("-h")) {
usage();
process.exit(0);
}
const outDir = path.resolve(process.cwd(), getArg("--out") || process.env.BACKUP_DIR || "./backups");
const includeMosquitto = hasFlag("--include-mosquitto");
const dryRun = hasFlag("--dry-run");
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const dbName = process.env.PGDATABASE || "qris_soundbox_platform";
const dbBackup = path.join(outDir, `${dbName}-${timestamp}.dump`);
const passwdFile = process.env.MOSQUITTO_PASSWD_FILE || "/etc/mosquitto/passwd";
const aclFile = process.env.MOSQUITTO_ACL_FILE || "/etc/mosquitto/acl";
fs.mkdirSync(outDir, { recursive: true });
const pgDumpArgs = ["-Fc", "-f", dbBackup];
if (process.env.DATABASE_URL) {
pgDumpArgs.push(process.env.DATABASE_URL);
} else {
pgDumpArgs.push("-h", process.env.PGHOST || "127.0.0.1");
pgDumpArgs.push("-p", String(process.env.PGPORT || 5432));
pgDumpArgs.push("-U", process.env.PGUSER || "postgres");
pgDumpArgs.push(dbName);
}
const plan = {
out_dir: outDir,
database_backup: dbBackup,
include_mosquitto: includeMosquitto,
commands: [`pg_dump ${pgDumpArgs.map((arg) => (arg.includes(" ") ? JSON.stringify(arg) : arg)).join(" ")}`]
};
if (dryRun) {
console.log(JSON.stringify({ dry_run: true, ...plan }, null, 2));
process.exit(0);
}
const result = spawnSync("pg_dump", pgDumpArgs, {
stdio: "inherit",
env: process.env
});
if (result.status !== 0) {
throw new Error(`pg_dump failed with status ${result.status}`);
}
const copied = [];
if (includeMosquitto) {
for (const source of [passwdFile, aclFile]) {
if (!fs.existsSync(source)) {
continue;
}
const target = path.join(outDir, `${path.basename(source)}-${timestamp}`);
fs.copyFileSync(source, target);
copied.push({ source, target });
}
}
console.log(JSON.stringify({ ok: true, ...plan, copied }, null, 2));