Files
Wellspringmanagement01/apps/Backend/src/services/databaseBackupService.ts
T

74 lines
2.1 KiB
TypeScript
Executable File

import { spawn } from "child_process";
import fs from "fs";
import path from "path";
import archiver from "archiver";
interface BackupToPathParams {
destinationPath: string;
filename: string; // should end in .zip
}
export async function backupDatabaseToPath({
destinationPath,
filename,
}: BackupToPathParams): Promise<void> {
// Verify write access before spawning pg_dump
try {
fs.accessSync(destinationPath, fs.constants.W_OK);
} catch {
throw new Error(
`No write permission to "${destinationPath}". Try running: sudo chmod a+w "${destinationPath}"`
);
}
const zipFile = path.join(destinationPath, filename);
const sqlName = filename.replace(/\.zip$/, ".sql");
return new Promise((resolve, reject) => {
const output = fs.createWriteStream(zipFile);
const archive = archiver("zip", { zlib: { level: 6 } });
output.on("close", () => resolve());
archive.on("error", (err) => {
try { if (fs.existsSync(zipFile)) fs.unlinkSync(zipFile); } catch {}
reject(err);
});
archive.pipe(output);
const pgDump = spawn(
"pg_dump",
[
"--no-acl",
"--no-owner",
"-h", process.env.DB_HOST || "localhost",
"-U", process.env.DB_USER || "postgres",
process.env.DB_NAME || "dental_db",
],
{
env: { ...process.env, PGPASSWORD: process.env.DB_PASSWORD },
}
);
let pgError = "";
pgDump.stderr.on("data", (d) => (pgError += d.toString()));
pgDump.on("error", (err) => {
try { if (fs.existsSync(zipFile)) fs.unlinkSync(zipFile); } catch {}
reject(new Error(`Failed to start pg_dump: ${err.message}. Make sure postgresql-client is installed.`));
});
pgDump.on("close", (code) => {
if (code !== 0) {
try { if (fs.existsSync(zipFile)) fs.unlinkSync(zipFile); } catch {}
reject(new Error(pgError.trim() || `pg_dump exited with code ${code}`));
return;
}
archive.finalize();
});
// Stream pg_dump stdout directly into the zip as a .sql entry
archive.append(pgDump.stdout, { name: sqlName });
});
}