fix: harden pi maintenance lifecycle

This commit is contained in:
2026-08-04 19:09:04 +02:00
parent 8fde1f81c7
commit 0b9ad7f53f
21 changed files with 432 additions and 171 deletions
+30
View File
@@ -0,0 +1,30 @@
/* Core-side, non-interactive installation-default writer used only through compose exec.
* It accepts no credentials and writes the same SETTINGS_FILE consumed by session creation. */
import { loadConfig } from "../config.js";
import { loadSettings, saveSettings, type Settings } from "./settings-store.js";
const choice = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$/;
function value(args: string[], flag: string): string {
const at = args.indexOf(flag);
if (at < 0 || at + 1 >= args.length || args.filter((part) => part === flag).length !== 1) {
throw new Error(`missing ${flag}`);
}
return args[at + 1];
}
try {
const args = process.argv.slice(2);
if (args.length !== 6) throw new Error("only provider, model, and thinking may be configured");
const provider = value(args, "--provider");
const model = value(args, "--model");
const thinking = value(args, "--thinking");
if (!choice.test(provider) || !choice.test(model)) throw new Error("invalid provider or model");
if (!["low", "medium", "high"].includes(thinking)) throw new Error("invalid thinking level");
const cfg = loadConfig(process.env);
const next: Settings = { ...loadSettings(cfg), provider, model, thinking };
saveSettings(cfg, next);
} catch (error) {
process.stderr.write(`settings-cli: ${error instanceof Error ? error.message : "invalid configuration"}\n`);
process.exitCode = 2;
}
+17 -2
View File
@@ -1,4 +1,4 @@
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { closeSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, writeFileSync } from "node:fs";
import { dirname } from "node:path";
import type { AppConfig } from "../config.js";
@@ -29,6 +29,21 @@ export function loadSettings(cfg: AppConfig): Settings {
/** Persist settings (pretty JSON). Creates the parent directory if needed. */
export function saveSettings(cfg: AppConfig, s: Settings): Settings {
mkdirSync(dirname(cfg.settingsFile), { recursive: true });
writeFileSync(cfg.settingsFile, JSON.stringify(s, null, 2) + "\n", "utf8");
const directory = dirname(cfg.settingsFile);
const temporary = `${cfg.settingsFile}.tmp-${process.pid}-${Date.now()}`;
const fd = openSync(temporary, "wx", 0o600);
try {
writeFileSync(fd, JSON.stringify(s, null, 2) + "\n", "utf8");
fsyncSync(fd);
} finally {
closeSync(fd);
}
renameSync(temporary, cfg.settingsFile);
// The core image runs Linux. Keep the directory acknowledgement explicit there; Windows
// filesystem replacement semantics are delegated to the host-side Go durable writer.
if (process.platform !== "win32") {
const dirFd = openSync(directory, "r");
try { fsyncSync(dirFd); } finally { closeSync(dirFd); }
}
return s;
}