86 lines
2.5 KiB
JavaScript
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));
|