74 lines
2.1 KiB
TypeScript
Executable File
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 });
|
|
});
|
|
}
|