fix(files): 配置文件改为同目录临时文件 fsync 后原子改名写入,读取返回内容哈希、保存带期望哈希冲突返回 409,磁盘满返回 507,面板提示载入最新或仍然覆盖

This commit is contained in:
Lemon-miaow committed 2026-09-25 03:56:30 +08:00
1 parent 074bd1783c
commit e1c325d594
17 files changed
+721 -131

No files matched your search

+19 -10
View File
@@ -404,23 +404,25 @@ export const api = {
`/servers/${name}/files?path=${encodeURIComponent(path)}`,
),
// readServerFile returns one file's bytes (base64). A file over the read
// ceiling is a 413, never a silent truncation, because a later save of a
// truncated body would destroy the rest of the file.
// readServerFile returns one file's bytes (base64) and the sha256 of the file
// as stored. A file over the read ceiling is a 413, never a silent truncation,
// because a later save of a truncated body would destroy the rest of the file.
readServerFile: (name: string, path: string) =>
request<{ path: string; content: string }>(
request<{ path: string; content: string; sha256: string }>(
"GET",
`/servers/${name}/file?path=${encodeURIComponent(path)}`,
),
// writeServerFile replaces a file's contents (creating it if absent). Sending
// an explicit "" is a deliberate truncate; the wire field is required, but that
// is enforced by the caller (this method always sends one).
writeServerFile: (name: string, path: string, content: string) =>
request<{ path: string; status: string }>(
// writeServerFile atomically replaces a file's contents (creating it if
// absent). Sending an explicit "" is a deliberate truncate; the wire field is
// required, but that is enforced by the caller (this method always sends one).
// With expectSha256 (the hash the read returned) a file someone changed since
// is refused with 409 file_changed; without it the write is unconditional.
writeServerFile: (name: string, path: string, content: string, expectSha256?: string) =>
request<{ path: string; status: string; sha256: string }>(
"PUT",
`/servers/${name}/file?path=${encodeURIComponent(path)}`,
{ content },
expectSha256 ? { content, expect_sha256: expectSha256 } : { content },
),
// Account linking (spec §10). Both are POST: start reports status from the
@@ -756,6 +758,13 @@ export function humanizeError(e: unknown): string {
return t("maintenance_in_progress");
case "no_world_volume":
return t("no_world_volume");
// File editor: the file changed after it was opened (another manager saved
// it, or the server rewrote it), so the save was refused rather than
// overwriting that edit.
case "file_changed":
return t("file_changed");
case "volume_full":
return t("volume_full");
// On-demand backup rationing (data-durability-9): one per server per
// cooldown, none while the shared backup store is at its cap.
case "backup_cooldown":