ken_nogi/dbbackup/lib/configBackup.js
Kenichiro NOGI 67cc3da6c8 up
2026-07-13 19:02:04 +09:00

62 lines
2.5 KiB
JavaScript

"use strict";
/*
* DB以外の環境設定ファイル一式(.envのCONFIG_BACKUP_PATHSで指定、カンマ区切り)を
* tar.gzにまとめて共有ドライブへアップロードするための補助モジュール。
* サーバーが完全に失われても同じ環境を再構築できるようにすることが目的。
*/
const fs = require("fs");
const path = require("path");
const { spawn } = require("child_process");
function parseConfigPaths() {
const raw = process.env.CONFIG_BACKUP_PATHS || "";
return raw
.split(",")
.map((p) => p.trim())
.filter(Boolean);
}
function runTar(args) {
return new Promise((resolve, reject) => {
const child = spawn("tar", args, { stdio: ["ignore", "pipe", "pipe"] });
let stderr = "";
child.stderr.on("data", (d) => (stderr += d.toString("utf8")));
child.on("error", (err) => reject(new Error(`tar起動失敗: ${err.message}`)));
child.on("close", (code) => resolve({ code, stderr: stderr.trim() }));
});
}
// paths のうち実在するものだけを対象にし、無いものは logger.warn するだけでスキップする
// (サーバーによって存在しないパスがあっても全体を失敗させないため)。
async function createConfigArchive({ paths, outputDir, timestamp, hostname, logger }) {
const existingPaths = [];
for (const p of paths) {
if (fs.existsSync(p)) {
existingPaths.push(p);
} else {
logger.warn(`設定バックアップ対象が見つかりません(スキップ): ${p}`);
}
}
if (existingPaths.length === 0) {
throw new Error("設定バックアップ対象のパスが1件も見つかりませんでした");
}
fs.mkdirSync(outputDir, { recursive: true });
const archiveName = `[${hostname}]config-${timestamp}.tar.gz`;
const archivePath = path.join(outputDir, archiveName);
// tarは絶対パスを渡すと「先頭の/を除去します」という警告(非ゼロ終了)を出すことがあるため、
// 成否はcodeではなく実際にアーカイブが作られたかで判定する。
const { stderr } = await runTar(["-czf", archivePath, "--", ...existingPaths]);
if (!fs.existsSync(archivePath) || fs.statSync(archivePath).size === 0) {
throw new Error(`設定アーカイブの作成に失敗しました: ${stderr}`);
}
return { archivePath, includedPaths: existingPaths };
}
module.exports = { parseConfigPaths, createConfigArchive };