fix: write clawdbot config atomically
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import crypto from "node:crypto";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
@@ -295,13 +296,48 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function writeConfigFile(cfg: ClawdbotConfig) {
|
async function writeConfigFile(cfg: ClawdbotConfig) {
|
||||||
await deps.fs.promises.mkdir(path.dirname(configPath), {
|
const dir = path.dirname(configPath);
|
||||||
recursive: true,
|
await deps.fs.promises.mkdir(dir, { recursive: true, mode: 0o700 });
|
||||||
});
|
|
||||||
const json = JSON.stringify(applyModelDefaults(cfg), null, 2)
|
const json = JSON.stringify(applyModelDefaults(cfg), null, 2)
|
||||||
.trimEnd()
|
.trimEnd()
|
||||||
.concat("\n");
|
.concat("\n");
|
||||||
await deps.fs.promises.writeFile(configPath, json, "utf-8");
|
|
||||||
|
const tmp = path.join(
|
||||||
|
dir,
|
||||||
|
`${path.basename(configPath)}.${process.pid}.${crypto.randomUUID()}.tmp`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await deps.fs.promises.writeFile(tmp, json, {
|
||||||
|
encoding: "utf-8",
|
||||||
|
mode: 0o600,
|
||||||
|
});
|
||||||
|
|
||||||
|
await deps.fs.promises
|
||||||
|
.copyFile(configPath, `${configPath}.bak`)
|
||||||
|
.catch(() => {
|
||||||
|
// best-effort
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deps.fs.promises.rename(tmp, configPath);
|
||||||
|
} catch (err) {
|
||||||
|
const code = (err as { code?: string }).code;
|
||||||
|
// Windows doesn't reliably support atomic replace via rename when dest exists.
|
||||||
|
if (code === "EPERM" || code === "EEXIST") {
|
||||||
|
await deps.fs.promises.copyFile(tmp, configPath);
|
||||||
|
await deps.fs.promises.chmod(configPath, 0o600).catch(() => {
|
||||||
|
// best-effort
|
||||||
|
});
|
||||||
|
await deps.fs.promises.unlink(tmp).catch(() => {
|
||||||
|
// best-effort
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await deps.fs.promises.unlink(tmp).catch(() => {
|
||||||
|
// best-effort
|
||||||
|
});
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user