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

92 lines
3.9 KiB
JavaScript

"use strict";
/*
* 設定ファイルアーカイブ(tar.gz)を展開し、CONFIG_BACKUP_PATHS の各パスへ配置する。
* 復元先に既存のファイル/ディレクトリがある場合は、必ず "<path>.pre-restore-<timestamp>" に
* リネームして保持してから配置する(復元前の状態を失わないため)。
*/
const fs = require("fs");
const path = require("path");
const { spawn } = require("child_process");
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) => {
if (code !== 0) {
reject(new Error(`tar展開失敗 (code=${code}): ${stderr.trim()}`));
return;
}
resolve({ stderr: stderr.trim() });
});
});
}
async function extractArchive(archivePath, extractDir) {
fs.mkdirSync(extractDir, { recursive: true });
await runTar(["-xzf", archivePath, "-C", extractDir]);
return extractDir;
}
// tar展開時、絶対パスの先頭"/"は除去されて格納される(例: /etc/nginx → etc/nginx)。
function extractedPathFor(extractDir, absolutePath) {
return path.join(extractDir, absolutePath.replace(/^\/+/, ""));
}
// シンボリックリンク(例: letsencryptの live/*.pem → ../../archive/*.pem という相対リンク)を
// 配置する際、fs.cpSync({recursive:true}) は相対リンクを「コピー元ディレクトリを基準とした絶対パス」に
// 書き換えてしまう(実機検証で確認済みの既知の挙動)。そのためNodeのfs APIではなく、
// シンボリックリンクをそのまま複製する `cp -a` を使う。
function copyPreservingSymlinks(source, dest) {
return new Promise((resolve, reject) => {
const child = spawn("cp", ["-a", "--", source, dest], { stdio: ["ignore", "pipe", "pipe"] });
let stderr = "";
child.stderr.on("data", (d) => (stderr += d.toString("utf8")));
child.on("error", (err) => reject(new Error(`cp起動失敗: ${err.message}`)));
child.on("close", (code) => {
if (code !== 0) {
reject(new Error(`cp -a 失敗 (code=${code}): ${stderr.trim()}`));
return;
}
resolve();
});
});
}
// targetPaths(CONFIG_BACKUP_PATHSと同じ絶対パスの配列)それぞれについて、
// 展開済みディレクトリに対応する中身があれば「既存を退避 → 配置」を行う。
// 展開ファイル側に無いパスはスキップ(そのアーカイブに含まれていなかった=バックアップ時に存在しなかった扱い)。
async function restoreConfigPaths({ extractDir, targetPaths, timestamp, logger }) {
const results = [];
for (const targetPath of targetPaths) {
const source = extractedPathFor(extractDir, targetPath);
if (!fs.existsSync(source)) {
logger.warn(`アーカイブ内に対象が見つからないためスキップ: ${targetPath}`);
results.push({ targetPath, status: "skipped" });
continue;
}
let renamedTo = null;
if (fs.existsSync(targetPath)) {
renamedTo = `${targetPath}.pre-restore-${timestamp}`;
fs.renameSync(targetPath, renamedTo);
logger.info(`既存を退避: ${targetPath}${renamedTo}`);
}
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
await copyPreservingSymlinks(source, targetPath);
logger.info(`配置完了: ${targetPath}`);
results.push({ targetPath, status: "restored", renamedTo });
}
return results;
}
module.exports = { extractArchive, restoreConfigPaths };