openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
60 lines (59 loc) • 2.39 kB
JavaScript
import path from "node:path";
//#region src/config/backup-rotation.ts
const CONFIG_BACKUP_COUNT = 5;
/**
* Advances the config `.bak` ring before a new primary backup is copied in.
*
* Missing slots are ignored so interrupted writes or first-run configs do not
* block the next config write.
*/
async function rotateConfigBackups(configPath, ioFs) {
const backupBase = `${configPath}.bak`;
await ioFs.unlink(`${backupBase}.4`).catch(() => {});
for (let index = 3; index >= 1; index -= 1) await ioFs.rename(`${backupBase}.${index}`, `${backupBase}.${index + 1}`).catch(() => {});
await ioFs.rename(backupBase, `${backupBase}.1`).catch(() => {});
}
/**
* Sets owner-only permissions on every backup slot when chmod exists.
*
* Backups are copied on mixed filesystems, so copy mode preservation is not a
* portable security guarantee.
*/
async function hardenBackupPermissions(configPath, ioFs) {
if (!ioFs.chmod) return;
const backupBase = `${configPath}.bak`;
await ioFs.chmod(backupBase, 384).catch(() => {});
for (let i = 1; i < CONFIG_BACKUP_COUNT; i++) await ioFs.chmod(`${backupBase}.${i}`, 384).catch(() => {});
}
const preUpdateConfigSnapshotsWritten = /* @__PURE__ */ new Set();
/**
* Captures the first on-disk config state for an update attempt.
*
* The snapshot is outside the rotating `.bak` ring so repeated writes during
* one process keep an operator-visible rollback point for the original file.
*/
async function createPreUpdateConfigSnapshot(params) {
if (!params.fs.existsSync(params.configPath)) return;
const snapshotKey = path.resolve(params.configPath);
if (preUpdateConfigSnapshotsWritten.has(snapshotKey)) return;
preUpdateConfigSnapshotsWritten.add(snapshotKey);
const snapshotPath = `${params.configPath}.pre-update`;
try {
const content = await params.fs.readFile(params.configPath, "utf-8");
await params.fs.writeFile(snapshotPath, content, {
encoding: "utf-8",
mode: 384,
flag: "w"
});
} catch {
preUpdateConfigSnapshotsWritten.delete(snapshotKey);
}
}
/** Runs rotation, primary copy, and permission hardening. */
async function maintainConfigBackups(configPath, ioFs) {
await rotateConfigBackups(configPath, ioFs);
await ioFs.copyFile(configPath, `${configPath}.bak`).catch(() => {});
await hardenBackupPermissions(configPath, ioFs);
}
//#endregion
export { maintainConfigBackups as n, createPreUpdateConfigSnapshot as t };