92 lines
3.9 KiB
JavaScript
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 };
|