feat(ops): W4-B single-container packaging, DATA_DIR, backup/restore
- DATA_DIR (/data: agent-sessions, backups, exports, tmp); entrypoint chowns it to PUID/PGID then drops privileges via gosu; /app binary stays root read-only - compile with --allow-read --allow-write so mounted library roots are writable - preflight: DATA_DIR/session dir must be writable (fatal); enabled library roots and downloader path mappings checked read/write (warn + audit) - --role export-mappings (mapping manifest JSON, --compare row counts) and --role read-only-on (automation.readOnlyReconcile=true + audit) - scripts/backup.sh (pg_dump custom + mappings + agent sessions, SHA256SUMS, BACKUP_KEEP rotation) and scripts/restore.sh (checksum, refuses with live connections, psql single-transaction replay compatible with PG16, read-only reconcile on, row-count verification) - compose: app + postgres only, data volume, env example with key generation - docs: runbook deploy.md, backup-restore.md with verification record Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
8c6a922cfb
commit
34e8751b45
18 files changed
+918
-43
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { join, resolve } from "node:path";
|
||||
import { z } from "zod";
|
||||
|
||||
const LogLevel = z.enum(["fatal", "error", "warn", "info", "debug", "trace", "silent"]);
|
||||
@@ -16,6 +17,13 @@ export const ConfigSchema = z.object({
|
||||
WORKER_HEARTBEAT_MS: z.coerce.number().int().min(1000).default(30_000),
|
||||
/** web 静态产物目录(单容器模式):设置后同端口托管 SPA,非 /api 路径回退 index.html */
|
||||
WEB_ROOT: z.string().min(1).optional(),
|
||||
/**
|
||||
* 应用数据目录(容器内 /data,由卷挂载):agent-sessions/、backups/、exports/、tmp/。
|
||||
* 编译后的二进制与 /app 保持只读,所有运行期写入都落在这里。相对路径按进程 cwd 解析。
|
||||
*/
|
||||
DATA_DIR: z.string().min(1).default("./data"),
|
||||
/** Agent JSONL 会话目录;缺省 DATA_DIR/agent-sessions(api 与 worker 必须共享同一目录) */
|
||||
AGENT_SESSION_DIR: z.string().min(1).optional(),
|
||||
});
|
||||
export type Config = z.infer<typeof ConfigSchema>;
|
||||
|
||||
@@ -29,4 +37,19 @@ export function loadConfig(env: Record<string, string | undefined> = Deno.env.to
|
||||
return parsed.data;
|
||||
}
|
||||
|
||||
/** DATA_DIR 下的固定子目录 */
|
||||
export const DATA_SUBDIRS = ["agent-sessions", "backups", "exports", "tmp"] as const;
|
||||
|
||||
/** 解析数据目录与会话目录为绝对路径 */
|
||||
export function dataPaths(c: Pick<Config, "DATA_DIR" | "AGENT_SESSION_DIR">, cwd: string = Deno.cwd()) {
|
||||
const dataDir = resolve(cwd, c.DATA_DIR);
|
||||
return {
|
||||
dataDir,
|
||||
sessionDir: resolve(cwd, c.AGENT_SESSION_DIR ?? join(dataDir, "agent-sessions")),
|
||||
backupsDir: join(dataDir, "backups"),
|
||||
exportsDir: join(dataDir, "exports"),
|
||||
tmpDir: join(dataDir, "tmp"),
|
||||
};
|
||||
}
|
||||
|
||||
export const isDev = (c: Pick<Config, "NODE_ENV">) => c.NODE_ENV !== "production";
|
||||
Reference in new issue
Block a user