diff --git a/AUDIT-2026-09-22.md b/AUDIT-2026-09-22.md index e48a70d..8a1fa22 100644 --- a/AUDIT-2026-09-22.md +++ b/AUDIT-2026-09-22.md @@ -253,6 +253,18 @@ IPv6-only 接入(`ssh -6 -i ~/.ssh/id_ed25519 root@fdb2:2c26:f4e4:0:21c:42ff:f 5. **清理**:2 行 submission + 1 行 build 删除、回滚演练 blob 删除、S3 构建产物从 whitelist 摘除(204)、MinIO 容器 + 两个镜像移除;`/fleet` 200。 - **遗留观察(非缺陷)**:registry 里保留本次推送的 `user-uploads/sub-12c953e12b0cd144:latest` 层数据(与早前 direct-probe 同类,filesystem registry 无删除接口);`felis setup` 的「S3 存储」向导屏本身未演练(列入 TUI 逐屏待办)。 +### 本轮新增真机证据(第二十五批:breakGlass 控制台逐屏全量 + 备份/恢复门禁 —— 缺陷 #39–#43) + +- **背景**:breakGlass 此前只验过 Owner 首装与 Add Operator happy path。本批把菜单四操作(Owner reset / Add Operator / Halt / Sync)+ 首装(bootstrap)分支全部逐屏真机走完,并打穿 Sync/恢复背后的 API 门禁;共抓 5 个缺陷、全部修复复验。驱动方式:VM tmux(`remain-on-exit on` 才能读回 alt-screen 撕掉后的 durable summary)。 +- **先落的正向证据(无缺陷)**:Halt——`test-one` Running → 选中 → 卡「is stopping」→ CRD `desiredState=Stopped`、pod 收敛消失、审计 `break_glass.halt`;面板 `wake`/`stop` 两个恢复杠杆均 202(复验后还原)。Sync 正路径——test-one(Stopped)→ 卡 backup started → Job 6s Complete → 归档落 `felis-backups` PVC(167MB)→ `world_backups` 行 `present` → `/api/v1/backups` 可见。Sync 负路径——对 Running 选 → 友好 409 卡(不误烧冷却)。 +- **缺陷 #39(owner 席位可被静默复制,且不可清理)**:恢复模式下用非在位席位名做「reset」→ `UpsertOwner` insert 臂**铸出第二个 owner 行**、原席位继续存活;面板对任何 owner 行都删/降/禁 403 → 永久无法收敛回单席。修复 `55d515d`:`provisionOwner` 先查在位席位(新 `PGRepo.OwnerUsername`),非席位名 → `ownerSeatTakenError`(`Is api.ErrConflict` → TUI 路由回表单并**指名**应输入的用户名);bootstrap(无席位)与同席位名复位原样。真机双验:新名被拒(表单原位显示 `an Owner already exists as "08595879-…" — enter that username to reset the Owner`);改席位名复位成功(owner 恒 1 行、id/邮箱不变);面板删除保护同步实证(对新 owner 行 DELETE → 403)。 +- **缺陷 #40(operator 撞名 = 裸 SQLSTATE,「换名重试」分支在真机从未生效)**:`InsertOperator` 冲突返回原始驱动错误(23505),TUI 却按 `api.ErrConflict` 判定「可恢复、换名重试」——fake 与 PG 漂移。真机复现:输入已存在用户名 → 控制台 exit 1 + 裸错误。修复 `55d515d`:`isUniqueViolation → ErrConflict` + pgint 契约断言。复验:撞名**回到表单**提示换名 → `drill-op-2` 成功(审计 `break_glass.operator_create` 落账,演练行已清)。 +- **缺陷 #41(Sync picker 死选项)**:picker 列出 login/lobby,而备份 API 对它们**永远失败**(保留名 + 无 servers 行);真机选 lobby → 裸内部错误 + exit 1。修复 `ac3a557`:`backupPickable` 过滤系统服(halt picker 保留它们,断玻璃完整权力)。 +- **缺陷 #42(缺失世界盘 → 202 后静默卡死 30 分钟)**:对「从未启动/已被回收」的服备份或恢复:202 → Job → Pod `persistentvolumeclaim "world--0" not found` **Pending 至 deadline**,全程零失败记录。真机用已回收的 `resolvecheck` 复现(留证后删除)。修复 `508a1c0`:`Cluster.WorldVolumeExists`(直接 Get 与 Job 挂载**同名**的 PVC)+ 两 handler 409 `no_world_volume`("start it once to create it, then retry")。**修复首跑翻出配套 RBAC 洞**:felis-api SA 无 `persistentvolumeclaims:get`(403 被吞成 500「internal error」)→ `APIMinecraftRole` 补 get-only 规则 + rbac 测试锚点。复验(auditfix38):resolvecheck 两面 409 + 友好文案;test-one 照常 202 → Job 10s Complete → 新行落库。 +- **缺陷 #43(409 一刀切文案)**:TUI 把所有 409 当停服门 → 世界盘拒绝会展示错误原因。修复 `ac3a557`:按 body 的 `error.code` 分流(无 code 的旧体仍按停服门)。复验:无盘 pick 显示 API 原文;停服门文案不变。 +- **同步完成**:① bootstrap 分支 scratch 库演练(`migrate up` 20 迁移 → 无菜单/无认证直接铸 owner;`local_auth_enabled=true`;审计 `break_glass.bootstrap`;exit 0;库/hba 规则/临时配置即测即清);② 台面收敛:reaper CronJob `suspend=true/auditfix36/无 pin` → `suspend=false / felis:auditfix38 / nodeSelector=localhost.localdomain`;③ 镜像升级 `auditfix37→38`(api/operator + `FELIS_IMAGE`)。 +- **流程修正(教训)**:pgint 一度误用**本机 Docker Desktop**(启动 daemon + 临时 PG 容器)——已完全清理(容器/镜像删除、daemon 退出),并改为**经 ssh 隧道用 VM 的 postgres** 运行(`ssh -L 15433:127.0.0.1:5432` → `postgres://felis:***@localhost:15433/felis_pgint?sslmode=disable`)。勿再在本机跑容器。 + ## 结论:离"生产可用"还差什么(按优先级) 1. ~~构建链路的上下文通道~~ ✅ **已修**(`f79e5eb`/`02fd2de`,真机全链路含拉回校验;Trivy DB 需按 §8e 镜像一次)。 @@ -265,6 +277,7 @@ IPv6-only 接入(`ssh -6 -i ~/.ssh/id_ed25519 root@fdb2:2c26:f4e4:0:21c:42ff:f 8. ~~面板文件编辑器入口~~ ✅ **已补**(`0a36b3f`,缺口补齐,真机 CDP 全链)。 9. ~~fleet 系统服务死操作~~ ✅ **已修**(`2f90851`,缺陷 #37)。 10. ~~S3 上传通道演练~~ ✅ **已演练**(第二十四批:MinIO 真机全链 + 回滚复核,0 缺陷)。 +11. ~~breakGlass 控制台逐屏~~ ✅ **已演练**(第二十五批:菜单 4 操作 + bootstrap 分支,缺陷 #39–#43 全修全验)。仍待一屏:`felis setup` 全屏向导逐屏(postgres/owner/connect/edge/storage/smtp/mc-bind/migration/preflight/bootstrap/summary)。 ## 系统性观察 @@ -276,6 +289,8 @@ IPv6-only 接入(`ssh -6 -i ~/.ssh/id_ed25519 root@fdb2:2c26:f4e4:0:21c:42ff:f - 面板会话 cookie:`/tmp/felis-cookies.json`;API 助手:`/tmp/fcurl.sh` - 测试服:`test-one`(minecraft ns,stopped);合法备份 `bk-47ee2e7e96e5a4ca9d0e51b805518bac` - CDP 调试口:Mac `127.0.0.1:9333`(独立 Chrome,profile `/tmp/felis-chrome2`);WebAuthn 虚拟认证器需在**同一 CDP 会话**内完成仪式,且先 `Page.bringToFront`(否则 NotAllowedError: page does not have focus) -- 已部署到 VM:`felis-api`/`felis-operator`/`felis-reaper`(CronJob) 镜像 = `felis:auditfix32`(含 #20–#35 全部修复;job 侧执行器镜像 `FELIS_IMAGE` 已同步;**world 执行器以 root+DAC_OVERRIDE 运行**,reaper CronJob 模板已热补丁为新形状;迁移 0020 已应用,`schema_migrations` max=20;CronJob 仍 `suspend=true`,SMTP 密码 env 待 `felis setup`「configure email」刷新时落地;operator Role 已手动补 `minecraftservers:patch`,`felis install/setup` 重渲染时收敛) +- 已部署到 VM:`felis-api`/`felis-operator` 镜像 = `felis:auditfix38`(含 #20–#43 全部修复;`FELIS_IMAGE` 已同步);**world 执行器以 root+DAC_OVERRIDE 运行**;reaper CronJob(minecraft ns)已收敛为 `suspend=false / felis:auditfix38 / nodeSelector=localhost.localdomain`;迁移 `schema_migrations` max=20;SMTP 密码 env 待 `felis setup`「configure email」刷新时落地;felis-api Role 已手动补 `persistentvolumeclaims:get`、operator Role 已手动补 `minecraftservers:patch`(`felis install/setup` 重渲染时收敛) +- pgint 正确跑法(走 VM 的 PG,勿在本机起容器):Mac 侧短隧道 `ssh -6 -i ~/.ssh/id_ed25519 -N -L 15433:127.0.0.1:5432 root@…` → `FELIS_TEST_PG_URL='postgres://felis:@localhost:15433/felis_pgint?sslmode=disable' go test -tags pgint ./internal/pgint/ -count=1`(pg_hba 的 `felis`/`felis_pgint` 规则已具备;隧道用完即 kill) +- breakGlass TUI 驱动法:VM tmux `new-session -d -s bg -x 160 -y 45 "/root/felis-auditfixNN breakGlass"` + `set-window-option -t bg remain-on-exit on`(否则退出摘要读不到);send-keys/capture-pane 驱动;退出码 1 = 操作失败(卡上会显示原因) - 构建链路 drill 现成条件:`felis-build` 里有 `felis-service-token`(Secret 复制);`felis-config` 里 `kaniko_image/trivy_image` 指向 k3s containerd 已导入的 pin tag、`trivy_db_repository = registry.felis.svc:5000/mirror/trivy-db:2`(镜像配方 §8e;VM 上 docker daemon 的 `insecure-registries` 已含 registry ClusterIP) -- VM 内部面:`k3s kubectl -n felis port-forward svc/felis-api-internal 18081:8081`(Pod 重建后转发会悬死,需重启);reaper CronJob(minecraft ns)已应用但 `suspend=true` +- VM 内部面:`k3s kubectl -n felis port-forward svc/felis-api-internal 18081:8081`(Pod 重建后转发会悬死,需重启);reaper CronJob(minecraft ns)已应用且已收敛(`suspend=false`)