ken_nogi/PleasanterSystem/dbbackup_XXX/lib/pgDump.js
Kenichiro NOGI ed33892f08 chore: 作業中の変更を整理しコミット(複数プロジェクト分)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 11:09:50 +09:00

119 lines
3.5 KiB
JavaScript

"use strict";
/*
* pg_dump をspawnし、標準出力を splitSizeMb ごとにローカルファイルへ分割書き出しする。
* backup.sh の `pg_dump ... | split -d -a 3 -b 3072m - $SAVEPATH$FNAME` と同じ
* 3桁ゼロ埋め連番(000, 001, ...)をファイル名末尾に付与する。
*/
const fs = require("fs");
const path = require("path");
const { spawn } = require("child_process");
function pad2(n) {
return String(n).padStart(2, "0");
}
function formatTimestamp(date = new Date()) {
return (
`${date.getFullYear()}${pad2(date.getMonth() + 1)}${pad2(date.getDate())}-` +
`${pad2(date.getHours())}${pad2(date.getMinutes())}${pad2(date.getSeconds())}`
);
}
function runPgDump({
pgDumpBin,
host,
port,
user,
password,
database,
outputDir,
splitSizeMb,
prefix = "postgres-",
ext = ".dmp",
timestamp = formatTimestamp(),
}) {
return new Promise((resolve, reject) => {
fs.mkdirSync(outputDir, { recursive: true });
const splitSizeBytes = Number(splitSizeMb) * 1024 * 1024;
const baseName = `${prefix}${timestamp}${ext}`;
const args = ["-h", host, "-p", String(port), "-U", user, "-Fc", database];
const env = { ...process.env };
if (password) {
env.PGPASSWORD = password;
}
const child = spawn(pgDumpBin, args, { env });
const files = [];
let partIndex = 0;
let currentStream = null;
let currentBytes = 0;
let totalBytes = 0;
let stderrOutput = "";
let settled = false;
function openNextPart() {
const fileName = `${baseName}${String(partIndex).padStart(3, "0")}`;
const filePath = path.join(outputDir, fileName);
files.push(filePath);
currentStream = fs.createWriteStream(filePath);
currentBytes = 0;
partIndex += 1;
}
openNextPart();
child.stdout.on("data", (chunk) => {
let offset = 0;
while (offset < chunk.length) {
if (currentBytes >= splitSizeBytes) {
currentStream.end();
openNextPart();
}
const remaining = splitSizeBytes - currentBytes;
const writeLen = Math.min(remaining, chunk.length - offset);
currentStream.write(chunk.subarray(offset, offset + writeLen));
currentBytes += writeLen;
totalBytes += writeLen;
offset += writeLen;
}
});
child.stderr.on("data", (chunk) => {
stderrOutput += chunk.toString("utf8");
});
child.on("error", (err) => {
if (settled) return;
settled = true;
if (currentStream) currentStream.end();
reject(new Error(`pg_dump起動失敗: ${err.message}`));
});
child.on("close", (code) => {
if (settled) return;
const finalize = () => {
settled = true;
if (code !== 0) {
reject(new Error(`pg_dump異常終了 (code=${code}): ${stderrOutput.trim()}`));
return;
}
resolve({ files, totalBytes, stderrOutput: stderrOutput.trim() });
};
if (currentStream && !currentStream.destroyed) {
currentStream.end(finalize);
} else {
finalize();
}
});
});
}
module.exports = { runPgDump, formatTimestamp };