195 KiB
Felis 生产就绪审计 — 2026-09-22(真机 E2E + 混沌)
分支:全部修复逐 commit 直接推 main(不再用功能分支)。remote = [email protected]:FelisMC/Felis.git(2026-09-23 起;旧 MliroLirrorsIngenuity/Felis 仅靠 GitHub 301 兜住)。环境:CentOS Stream 9 / aarch64 / k3s v1.36.4,
IPv6-only 接入(ssh -6 -i ~/.ssh/id_ed25519 root@fdb2:2c26:f4e4:0:21c:42ff:fede:69ec),
面板经 socat TCP6:443 → 127.0.0.1:30443 中继(手动启动,重启 VM 后需重开)。
已修复并验证(分支内)
| # | 缺陷 | 证据 | 修复 |
|---|---|---|---|
| 1 | 失败恢复后重试被静默吞掉:restore Job 固定名 + ErrAlreadyExists 一律当"幂等成功";失败 Job 占名 10 分钟(TTL),期间重试返回 202 restoring 但什么都不跑 |
真机:坏 ref 制造失败 → 立刻合法重试 → Job 原地不动、无新 Pod | k8sjobs.go:撞名时检查已完成(成功/失败)→ 删除+等 finalizer 释放(有界 10s)+ 重建;进行中仍幂等吸收。单测 3 个。真机复验:重试 4s 完成恢复 |
| 2 | 备份 Job 全部 FailedMount:Job 在 minecraft 命名空间挂 felis-config,而 bootstrap 只在 felis 命名空间创建该 Secret |
事件:MountVolume.SetUp failed: secret "felis-config" not found |
felis setup 用既有 ensureSecretReplica 把 felis-config(felis.toml) 复制到 minecraft;VM 上手工复制后备份成功(167MB 归档) |
| 3 | RBAC 缺 jobs:get/delete(修复 #1 需要) |
Role 检查 | APIMinecraftRole jobs → create/get/delete,测试同步更新 |
| 4 | gofmt 9 文件漂移 + CI 无 gofmt 门禁;staticcheck 9 处 | 基线扫描 | 全部修复;CI 加 gofmt job |
| 5 | 依赖漏洞:pgx v5.7.1(GO-2026-5004,可达 pgrepo.go)、x/net、x/text | govulncheck | 升 pgx v5.9.2 / x/net v0.55.0 / x/text v0.39.0 |
| 16 | ConsumeLoginEmailOTP PG 实现与接口契约漂移:契约/fakeRepo 说「同 VerifyEmailOTP 生命周期(扣尝试/锁定/ErrOTPInvalid/ErrOTPLocked)」,PG 却是单条 UPDATE+ErrNotFound → 错码/重放/过期在 login 门、op-login finish、migration confirm 三个入口全部 500;且尝试次数永不累计、otpMaxAttempts 锁定失效 |
真机:login 门重放正确码 → HTTP 500;修复前错码不扣次。修复后复测:5 次错码 400 且 attempts=5(正确码因锁定也 400、码未消费)、新码可用、重放 400 | pgrepo.go 改置为 VerifyEmailOTP 同构事务(FOR UPDATE、先锁后比、mismatch 扣次、match 消费),无 users 写副作用。提交 52549f7 |
| 17 | ListPendingOpLogins PG 少列:接口注释承诺「joined to its staff username」,handler 输出 username/created_at,fake 正确填充;PG SQL 未 JOIN 也未取 created_at → 真机 pending 列表 username 为空、created_at 为 0001-01-01 |
真机 internal /op-login/pending 响应 |
pgrepo.go SQL 改为 JOIN users + 取 created_at。提交见分支 |
| 18 | 绑定码并发兑换 500:RedeemPlayerBindCode 无 FOR UPDATE(同文件 VerifyLinkCode 有),且裸 INSERT。6 路并发同码兑换 → 3×HTTP 500(users_username_key 唯一冲突)+ 1×400 + 2×200;跨码并发同 UUID 同样会撞 |
真机四组并发测试 + API 日志 unmapped error ... duplicate key |
同码:码行 FOR UPDATE(输家干净地 400 invalid_code);跨码:INSERT users ... ON CONFLICT (username) DO NOTHING+重读、account_links ON CONFLICT (mc_uuid) DO NOTHING(两路汇聚同一 user)。复测:同码×6=1×200+5×400、双码×2=2×200 同 ID、DB 干净、日志零 unmapped |
| 19 | reaper CronJob 渲染位置错误,永远无法调度:felis manifests 把 CronJob 渲染到控制 ns,却引用 minecraft ns 的备份 PVC(Pod 不能跨 ns 挂 PVC:真机 FailedScheduling: persistentvolumeclaim "felis-backups" not found);修正 ns 后又发现 ServerAccount 也不能跨 ns 使用(serviceaccount "felis-reaper" not found)。而 reaper 的 Role/RoleBinding 本就在 minecraft ns |
真机三层取证(PVC/SA/调度) | CronJob 与 SA、RoleBinding subject 全部移到 MinecraftNamespace(提交 e4f2cff+c839454)。修复后完整演练通过(见下) |
| 6 | 默认安装无备份能力 + 回收链路不可达/不可用:(a) 没有任何环节渲染归档 PVC,FELIS_BACKUP_PVC 永远为空 → backup/restore 恒 503;(b) bootstrap 从不下传 reaper 三旗标 → 官方安装路径根本无法启用回收;(c) 即便启用,stock k3s 的 <root>/<pvc> 布局不存在,且 k3s storage root 是 0700 root:root、reaper pod 以 uid 1000 运行 → 真机 lstat /worlds/…: permission denied(fail-closed 跳过,但纯空转);(d) README 口径(“定时备份”)不实 |
真机全链路:默认渲染 PVC+env → 建服→marker→备份 202→jobs 端点 running→succeeded→改 marker→restore→读回原值 ✅;再建 resolvecheck 世界(20d idle,marker)→ CronJob 手动 Job:evaluated=2 reaped=1,归档含 marker、PVC+宿主目录回收、world_backups 得 inactive_15d 行、servers 行/CR 保留 ✅ |
platform/workloads.go 渲染归档 PVC(minecraft ns、RWO 10Gi、默认 SC),felis manifests --backup-pvc 默认 felis-backups(= 空为显式关闭),bootstrap 统一下传 env/旗标并写 [archive] local_path;resolveWorldDir 新增精确 local-path 目录解析(读 PVC spec.volumeName,非 glob,杜绝陈旧 PV 目录顶替);ReaperRole 增 pvc:get;bootstrap 设 FELIS_WORLDS_HOST_PATH 时给 uid 1000 授 traverse(setfacl/o+x);README/故障手册改写。提交 fd33fd0+2b87a5a |
| 8 | 磁盘打满灾难链:DiskPressure → kubelet 驱逐控制面(无 PriorityClass 保护)→ 镜像被 GC(无外网、registry 空)→ 全部 ImagePullBackOff;释放后数分钟才恢复调度。恢复依赖人工 `docker save | k3s ctr images import -` | 三轮真机 drill(原缺陷复现 + 两轮修复验证):①自定义 1e6 类:游戏 pod 先走、控制面随后仍被驱逐(kubelet 日志逐条列出 ranked/evicted),随后镜像被 GC → ErrImagePull;②内建 system-cluster-critical(2e9):填盘至 1.7G free,kubelet 对 felis-api/operator/registry 全部报 “cannot evict a critical pod”,三者在整个 DiskPressure 期间保持 Running;游戏 pod(0)被驱逐;③释放磁盘后:DiskPressure 经 ~5 分钟(eviction-pressure-transition-period)转 False——即“恢复慢”的主因;被 GC 的游戏镜像按 runbook 重导入后 25s 恢复 ✅ |
| 9 | 升级策略 Recreate:单副本 + Recreate:任何控制面升级=停机;坏升级(错 tag)中断约 95s 且需人工 rollout undo(无自动回滚) |
复核部署模板(Recreate + 单副本 + 无 leader election 的注释理由成立) | 不改策略(Recreate 是对无 leader election 的正确取舍),改为固化 runbook:troubleshooting §15 = 升级即重跑安装器;停机窗口=rollout 时长;坏镜像在安装器 180s 等待内以 kubectl describe 诊断呈现;回滚 kubectl rollout undo(镜像被 GC 时先按 §13b 重导入)。提交见下 |
| 10 | 备份语义:归档包含整个 /data(jar、libraries、cache),167MB;是否符合 world backup 定位待评估 | 真机 tar 清单(cache/、config/、eula.txt、server.properties、jar)+ 恢复语义(overlay+prune) | 评估结论:整卷备份是正确语义(回滚=整服状态回滚,含配置/插件),保留行为;文档化:troubleshooting §10「backup contains」+ OpenAPI/README 口径改为“整服数据卷”。提交见下 |
| 20 | 验证邮箱唯一性只存在于注释里:errors.go/repo.go 都声称迁移 0010 建了 users_verified_email_unique 部分唯一索引、VerifyEmailOTP 会返回 ErrEmailTaken;实际上索引从未创建、ErrEmailTaken 全仓库从未被返回 → 两个账号可同时验证一个邮箱,而登录门正是按 verified email 解析账号 → 同一邮箱的登录码归谁由数据库任意决定 |
pgint 首跑即红:第二账号验证同邮箱返回 nil;grep users_verified_email_unique internal/store/migrations/*.sql 零命中。真机复验(auditfix17 邮箱 409 演练):同码二次 verify 仍 409(码未被消费) |
新增迁移 0020_verified_email_unique.sql(lower(email) WHERE email_verified);VerifyEmailOTP 在消费码前查「他人已验证」→ ErrEmailTaken(不消费码、不扣次数),并把并发唯一冲突映射为同一答案;handler 新增 409 email_taken。提交 b6ef27c;PG 契约测试基建 2a55a0d(-tags pgint,见 CONTRIBUTING) |
| 21 | admin 改邮箱不清 verified:UpdateUser 写入新地址但保留 email_verified=true → 面板改错一个字符就能让登录码寄到别人邮箱(该 flag 正是邮箱登录解析/寄码的依据);fake 同错 |
pgint 契约测试 | 改地址时同写 email_verified = email_verified AND email IS NOT DISTINCT FROM 新值(同值 no-op 保留证明;换值即清除);fake 同步。提交 d1ec40f。真机验证:PATCH→f、PATCH 回原值仍 f、玩家重验证→t |
| 22 | role='owner' 是死信:迁移 0011 加了 owner 角色并把全部用户管理路由压在 IsOwner() 上,但没有任何代码写过 owner——breakGlass(UpsertOwner)、setup MC 绑定(CompleteOwnerSetup)、重置路径一律写 admin → 全新安装的整个 owner 层(用户列表/创建/编辑/禁用/删除/配额/会话)不可达;且新角色没进各处 staff 判定(op-login 只认 admin、玩家邮箱门只拒 admin、reclaim 保护与 AdminExists 只认 admin);面板一旦有 owner 行还可被降级/删除/禁用 |
真机:owner 会话加载 /api/v1/users 403;promote 后 200。op-login:修复前 owner start 中立不发码,修复后铸请求+finish 得 role=owner 会话 |
UpsertOwner/CompleteOwnerSetup 改写 role='owner'(冲突臂重断言,即 0011 文档的 promote 路径);op-login 双端改 staffRole;玩家邮箱门改 role != 'user';IsProtectedAdminLink/AdminExists 计入 owner;面板新增守卫:owner 行不可降级/删除/禁用(用户名/邮箱编辑仍可)。提交 e0d2378 |
| 23 | 配额门非原子 + storage 缓存被清零:(a) audit #4:QuotaCheck 与 ClaimServer 两条语句,同一用户并发认领两台无主服可双双通过 max_servers(deferred-seams 曾把这条挂为“只能在真 PG 上关闭”);(b) 更隐蔽:PATCH 资源时 UpdateServerResources(..., 0) 把本不能改的 storage 缓存写 0,而缓存列是配额聚合的唯一输入 → 此后该服的 storage 维度在配额里凭空消失 |
(a) 新增 pgint 并发测试:修复前两台全赢;修复后恰 1 赢 + 1 ErrQuotaExceeded,DB 只 1 行 owned;(b) hermetic 测试 TestPatchServerPreservesStorageCache(修复前 resourceUpdates 里 storage=0) |
(a) 门槛进 ClaimServer:同一事务内 pg_advisory_xact_lock(hashtext(user_id)) + 四维重查(与 QuotaCheck 共用 quotaAllows 防漂移)+ 行 FOR UPDATE,两个 claim handler 把 ErrQuotaExceeded 映射为与串行一致的 403;(b) resize 前读取现值并透传 storage。提交 bb68fef;deferred-seams 对应条目核销 |
| 24 | reaper 警告信从不真正投递:felis reaper 从不装配任何 Warner(r.Warner 恒 nil),而 maybeWarn 对 nil warner / 投递失败一律照样 MarkWarned + warned++ → 每位有主的服都在无人收到提醒的情况下 15 天后被静默回收,跑批日志还谎报"已警告 N 台"。红线⑤的"best-effort 不阻塞回收"被误读成了"失败也要记成已通知" |
hermetic 单测 3 例(坏 notifier / nil warner → 不盖章、成功才盖章)。真机 drill(auditfix20):13d idle 实收 1 封(收件人=owner 的验证邮箱、subject 含 3d);重跑不重发;14.5d 补发 1d(elif 档位);SMTP 端口打坏 → 日志 warn delivery failed; will retry next run 且不盖章,恢复后补发;邮箱未验证 → has no verified email 不盖章;owner NULL → 静默跳过;边界 11d23h 不发 / 12d1m 发;全程 world_backups 保持 4 条不动(纯警告零备份副作用) |
mail.SendNotice + mailWarner:查 owner 的 verified email → SendNotice;nil/失败不盖章、下次重试;reaper pod 模板加 optional FELIS_SMTP_PASSWORD env;felis setup 复制 felis-smtp 镜像并在「configure email」刷新 minecraft ns 的 felis-smtp+felis-config 镜像;docs/troubleshooting §10 更新。提交 8e7c7bb |
| 25 | idle auto-stop 从未触发(三层复合缺陷):条件齐备的空载服永远不停。① CRD status schema 未声明 emptySince,apiserver pruning 掉计时戳(unknown field "status.emptySince"),计时器每次读回都是 nil;② 即使戳幸存,Running 空载稳态没有任何 watch 事件(玩家进出不碰 CRD、RCON 只在 Reconcile 内探),盖章一次后 reconcile 链停摆,无人叫醒;③ auto-stop 用整对象 Update 写 spec 且 OperatorRole 从未有 minecraftservers:patch/update → 403 cannot update resource。三层任一都让功能永久失效,而单测(fake client 不剪 schema、手动驱动、无 RBAC)全部覆盖不到 |
真机逐层实锤:schema 修复后 empty=2026-09-22T20:02:13Z 首次成功持久化;静置 88s+ 无动作、operator 日志 90s 零行(②实锤);修复前日志 5 条 403(③实锤);全修后场景 1:超时戳触发即 Stopped;场景 2:起服 → 20:11:43 盖章 → 全程无干预 → 20:12:17 自动 Stopped;翻转 Running→Stopped→Running 收敛 Running;idle=null 后不再自停 |
① schema 补 emptySince(c04a3f0);② reconcileRunning 尾部返回 RequeueAfter——空载=到点精确唤醒、有人=30s 探针周期,+3 个单测(1c89a5e);③ auto-stop 改 merge-patch(防 status clobber,与 reaper 的 Stop 同型)+ OperatorRole 补 patch + rbac 测试锚点(f650bf8)。真机 auditfix22 全通;troubleshooting §11 增自检三连 |
| 26 | RCON Secret 被删 → 服务器永久锁死:删掉 per-server RCON 密码 Secret 后,(a) operator 没有任何 watch 能看到删除(Running 稳态零事件),secret 一直不重建;(b) 一旦被任意事件带到 reconcile:新密码铸造成功,但运行中的 pod 仍持有旧密码、sts 模板无变化 → pod 永不重启 → 探针用新密码连旧密码 pod,永久 RconNotReachable 直至 300s ReadinessTimeout;恢复只有人工删 pod。ensureRconSecret 的注释却声称"heals on the next pass" |
真机:删 secret 后静置 60s 无重建、无日志;annotate 触发后 96s+ 持续 RconNotReachable(新密码 91dd62… vs pod 旧密码);删 pod 后 17s 恢复 Running——根因=密码漂移实锤 |
① SetupWithManager 增 Owns(&corev1.Secret{})(controller-owned,删除事件映射回 CR)——真机日志见 source="kind source: *v1.Secret";② pod template 新增 RconSecretAnnotation = 当前密码 SHA-256 指纹(64-bit):secret 重建指纹变 → sts 自动 rollout 用上新密码,未重建则恒定不抖动;测试 2 例(跨 reconcile 稳定 / 重建必变)。提交 56f3abd。真机复测:删 secret 后同秒察觉+重建+换戳,31s 全自动恢复 Running |
| 27 | 陈旧启动锚点 → 已恢复的服务器被误判 StartupTimeout + Provisioned 永久 False:status.startRequestedAt 只在 Stop 时清,成功(markRunningReady)不清——一旦服务器曾经历一次长 Starting,其 300s 预算就悬在健康运行中;此后任意一次 pod 波动(rollout/崩溃)都会立即套用旧戳判 Failed(StartupTimeout)。且 markFailed 写入的 ConditionProvisioned=False 无人复位,恢复后仍永久挂着失败标记 |
真机:20:19 已恢复 Running 的 test-one,20:21 因一次 stamp 注入引发的 pod rollout 被标记 Failed StartupTimeout(锚点残留自 20:16);恢复后 status.conditions 里 Provisioned=False … StartupTimeout 持续存在 |
markRunningReady:清 StartRequestedAt(每次启动/恢复尝试各有独立预算)+ 复位 ConditionProvisioned=True;测试 2 例(Ready 清锚点、Failed→Running 后 Provisioned 恢复)。提交 82b5a60。真机复测:清戳生效(startRequestedAt: None)、pod blip 删→33s 恢复全程无 Failed、锚点重新盖章后成功清除 |
| 28 | cfsetup 把 "policy already exists" 当成功吞掉 → fail-closed 保证可被旧策略顶替:Cloudflare Access 的 CreateAccessPolicy 在收到 already-exists 时直接返回 nil;若该 app 上已有一条更宽松的旧策略(改 identity 后重跑、或此前手工配置),守卫 op.console 的仍是旧策略,而 Setup 报告成功——validateFailClosed 只校验过"我们构建的策略",从未校验证留在线上的那条 |
代码审查(集成侧无真实 CF 账号,无法真机):httptest 3 例复刻——已存在同名策略、缺席、POST 竞态。修复前第 1 例吞错返回 nil 且不发 PUT | 改为按名 upsert:lookup → PUT 覆盖守卫体 → 缺席才 POST(POST 撞 already-exists → 重查后 PUT,绝不吞)。apiPost/apiPut 共用一个 apiWrite。提交 30857df |
| 29 | "configure email" 的工作负载镜像复制从不生效:replicateSMTPToWorkloadNamespace 把硬编码 namespace: felis 的 felis-smtp manifest 用 -n minecraft apply 发出——kubectl 拒绝 namespace 冲突(the namespace from the provided object ... does not match)→ 第一段直接 return err,第二段 felis-config 的镜像复制根本不会执行。于是任何"装好后再改 SMTP"的部署,reaper 的 pre-reap 警告永远拿不到新配置(这正是 8e7c7bb 添加该刷新要解决的事),且失败仅 warning 不中止 |
真机 kubectl 行为实验:-n minecraft apply 带 namespace: felis 的 manifest → error: … does not match …;修复后(manifest namespace=minecraft)→ accepted-namespace=minecraft(server dry-run);felis-config 渲染+apply 序列同为 accepted |
smtpSecretManifest(password, namespace) 显式参数(控制面调用传 "felis",镜像传工作负载 ns);回归测试 TestSMTPSecretManifestCarriesTargetNamespace。提交 ed722d5 |
待决策台账(未修)
前两日台账的 #7、#11–#15 已全部修复(本批核销,证据见下),不再挂在"未修"里:
| # | 主题 | 状态 |
|---|---|---|
| 7 | 异步失败不可感知 | ✅ 修复:GET /api/v1/servers/{name}/jobs(ff7c57c,含 RBAC jobs:list 与 OpenAPI);真机 drill 用它看到 running→succeeded |
| 11 | PG 断连报 401 | ✅ 修复:会话存储故障改为 503(2a8f897) |
| 12 | ready 门滞后 | ✅ 修复:operator 启动期内每 2s 重探 RCON(a2df2f2) |
| 13 | setup URL 截断 | ✅ 修复:TUI 折行(abce381) |
| 14 | controller-runtime 日志噪音 | ✅ 修复:SetLogger 接 slog(a415246) |
| 15 | reaper 启用未演练 | ✅ 已演练(见"第二日"节;CronJob 仍 suspend=true 防误删) |
可达性分级(#1–#79;#62 立案后剔除)——这些缺陷真实使用中到底谁能踩到
回应质疑"是不是全在测边界条件 / 只有内部 hook 才能触发":
- hook 的边界:演练中使用的内部手段(读服务端日志取 OTP 码、service token 打内部面代 approve、本地 SMTP sink、tmux 驱动 TUI)只是测试仪表——代替"真实收邮件 / 键盘敲击 / 操作台点击";触发路径本身是公开路径,真实用户做同一动作走同一段代码。真正本环境不可复现的只有 #28(无真实 CF 账号,仅 httptest 复刻),另有 #4/#5/#14 属工具/卫生级(无运行时触发面)。
- 分级口径:① 日常=普通使用或默认安装下即会踩到;② 运维=升级/重启/故障恢复/磁盘满/删资源/闲置回收等真实运维动作会踩到;③ 窗口=需要并发或特定状态时序,真实但概率低;④ 工具=仅工具判定/审查;决策=非缺陷(评估/演练项)。
| # | 真实触发路径(一句话) | 分级 |
|---|---|---|
| 1 | 恢复失败后 10 分钟内重试(必然的恢复动作)→ 202 但 Job 不动 | ② |
| 2 | 默认安装下第一次点「备份」→ Job FailedMount(felis-config 缺在 minecraft ns) | ① |
| 3 | #1 修复所需 RBAC(单独不产生用户可见行为) | ② |
| 4 | gofmt/staticcheck 漂移 + CI 门禁缺失 | ④ |
| 5 | 依赖漏洞(govulncheck 判定可达):需特定输入 | ④ |
| 6 | 默认安装即踩:备份/恢复恒 503、回收链无旗标不可启用 | ① |
| 7 | 任何异步操作(备份/构建)想查结果:无端点(功能缺口) | ① |
| 8 | 磁盘打满事故链(控制面被逐 → 镜像 GC → 全体 ImagePullBackOff) | ② |
| 9 | 非缺陷:升级策略评估 → runbook | 决策 |
| 10 | 非缺陷:备份语义评估 → 文档化 | 决策 |
| 11 | PG 掉线期间任何会话校验 → 401 误导 | ② |
| 12 | 每次起服:就绪感知滞后 | ① |
| 13 | 首次安装向导:长 URL 截断 | ① |
| 14 | operator 日志噪音(卫生项) | ④ |
| 15 | 流程项:reaper 启用补演练 | 决策 |
| 16 | 邮箱登录主路径:错码/重放 → 500、5 次锁定失效 | ① |
| 17 | 面板 op-login pending 列表字段缺失 | ① |
| 18 | 绑定码并发兑换(双击/双端即可)→ 部分 500 | ③ |
| 19 | 按文档启用回收 → CronJob 永远无法调度 | ② |
| 20 | 两账号验证同一邮箱 → 登录码归属不定(安全) | ③ |
| 21 | 面板改任一用户邮箱 → verified 未清、码寄旧地址 | ① |
| 22 | 全新安装 owner 层不可达(用户管理全路由) | ① |
| 23 | (a) 并发认领两服可双双过配额;(b) PATCH 任意服资源 → storage 缓存清零 | ①(a=③) |
| 24 | 有主服闲置 12–15d:警告信从不投递、静默回收 | ② |
| 25 | 启用空载停机后等超时:三层复合缺陷永不生效 | ② |
| 26 | 运维删/轮换 RCON Secret → 永久锁死至人工删 pod | ② |
| 27 | 曾长启动的服 + 任意 pod 波动 → 误判 StartupTimeout | ② |
| 28 | 重跑 cfsetup 且线上有旧策略 → fail-closed 被顶替;⚠无真实 CF 账号,仅 httptest | ④ |
| 29 | 装后改 SMTP(configure email)→ 复制链从未生效 | ② |
| 30 | 对不存在/刚被删的用户 id 操作 → 500 而非 404 | ③ |
| 31 | 真实 owner 打开 /admin/builds 被 mock 残留误判 | ① |
| 32 | 新浏览器首访 /admin/builds 播种两个假 404 | ①(轻) |
| 33 | 禁用/已删账号重走邮箱登录门 → 可复活(安全) | ① |
| 34 | 同族:死账号游戏内身份面仍存活 | ① |
| 35 | 任何真实服务器保存过一次后:所有备份/fileread 必败 | ① |
| 36 | 任何构建失败的提交者永远看不到结果 | ① |
| 37 | 管理端 fleet:login/lobby 行全是死操作 | ①(轻) |
| 38 | felis manifests 输出残留过期指导 | ②(文档) |
| 39 | 断玻璃 reset owner 用非在位名 → 铸第二 owner 永不可清 | ② |
| 40 | 断玻璃 add operator 撞名 → 裸 SQLSTATE | ② |
| 41 | 断玻璃 Sync 选系统服 → 裸内部错误 | ② |
| 42 | 对无世界盘服备份/恢复 → 202 后静默卡 30 分钟 | ② |
| 43 | #42 配套:409 一刀切文案 | ②(轻) |
| 44 | 重跑 felis setup 完成 s/c reconfigure → 状态框退回“Setup complete”(信息一致性) |
①(轻) |
| 45 | 评审看不到将被执行的 recipe(执行的是上传 blob 里的 Dockerfile)→ 盲批 | ① |
| 46 | 大层推送打死 registry(256Mi 模板被 OOM kill;实测 475MB 层)——用户构建/安装器入仓即触发 | ① |
| 47 | Mac 打包树构建:._*.sql 旁文件混入 embed → felis migrate 全挂 |
② |
| 48 | 安装器重跑:每镜像 start/stop docker 触发 systemd 限流 → 批量入仓中途断 | ② |
| 49 | 安装器重跑:[registry]/[archive] 运维配置静默回退(构建/S3/reaper 行为回默认) |
② |
| 50 | 配过 SMTP 的安装按文档重跑安装器 → 生成的注释块被 [smtp] carry 吞并并每次 +1(纯膨胀) |
② |
| 51 | 改配置/轮换 DB 凭据后:工作负载 ns 的 felis-config 副本永不刷新 → backup/reaper 静默读旧配置 |
② |
| 52 | 向导内 s/c 重配置后:工作负载 ns 的 felis-config 镜像停在上一次运行快照,直到下次 felis setup/安装器才收敛 |
② |
| 53 | 默认安装或 felis update 的 felis-api 检查:坐标仍指向已迁移的旧仓库,现仅靠 GitHub 301 兜住——redirect 一退休,安装器默认 URL 与更新检查全挂 |
② |
| 54 | 完成态安装上照 felis update 指引升级:sudo felis setup 只开配置控制台,任何组件都不会动(三处 run 指引 + trailer 全假) |
②(文档) |
| 55 | 配置的源回 200 但档案字段不可用(马虎自建/三方 Yggdrasil):坏源在前 → 经梯子的登录被静默吞掉(零日志、后续源不被询问) | ② |
| 56 | 给"还没进过服"的玩家加白名单/封禁/踢出(面板说成功、vanilla 实际拒绝)——任何新玩家第一次被管理就撞 | ① |
| 57 | 控制台发任何命令后无反馈(回复被丢弃、pod 日志也不回显) | ① |
| 58 | 对从未启动过的服务器点"文件"页(90s 卡死 → 误导性 504) | ① |
| 59 | 装了 LuckPerms 的服打开 LP 页:读不到被报成"未分配任何权限"(假事实),且历史伪造 [RCON] 输出 |
① |
| 60 | 新建服务器输入非法名/子域名(前端预检比后端弱)→ 对话框直出 Go 英文错误;账户验证撞已占邮箱同理 | ① |
| 61 | 给"还没进过服"的玩家预授权限:面板提示"仍然会实际生效"(过度承诺),实际 LuckPerms 静默丢弃(未进服的名字无法解析) | ① |
统计:① 26 | ② 25 | ③ 3+#23(a) | ④ 4 | 决策 3 = 61。第三批构建链另有 3 处未编号修复(Job requests 超小节点上限 → 永远 Pending、Kaniko chown、Trivy DB egress 被锁)——均属 ①/②。
第四十批追加:#63(demo-up 单起点化——旧镜像臂能产出"起了但无处路由"的演示机;dev/demo 路径)②;#62 经立案复核不成立(全部安装路径都构建 felis-velocity.jar,证据见该批节),已剔除、不计入。
第四十一批追加:#64(三个 vendored gradlew 以 100644 提交、无可执行位——README 教的构建命令在全新 clone 上直接 Permission denied;无任何 CI/安装路径跑过这三个模块)①;#65(三个装载器 mod 的 license 仍写 MIT、Forge/NeoForge 的 issueTrackerURL 是 example.invalid 占位,与仓库 AGPL-3.0-only 相悖——fabric loader 启动时会打印该字段)②(低)。
第四十二批追加:#66(英文 README 缺中文版"使用方式"里的私有仓库安装 workaround 与重跑升级说明——英文读者照文档第一步即 404、无任何指引)①(轻)。
第四十三批追加:#67(build Job 从不设 ttlSecondsAfterFinished——每构建一次就永久留下一个完成 Job+Pod,完成 Pod 计入节点 pod 预算(stock k3s 110),构建量上来后新构建全 Pending)②(时间维度;随构建量从②滑向①)。
第四十三批追加(二):#68(构建 context 拉取单次尝试——控制面滚动/重启/驱逐恰好撞上构建窗口时,fetch initContainer 一次 connection refused 直接打成终态 Failed;BackoffLimit=0 无第二次 Pod,代价 = 人工重审重提)②(运维:升级/重启/故障恢复撞上正在进行的构建;构建量大时概率上升)。
第四十五批追加:#69(README 中英"审批通过后自动构建并部署"过度承诺——数据模型无目标服务器、部署实为"选用该镜像";① 轻);#70(kaniko 拉内建底座默认 HTTPS——--insecure 只覆盖推,任何 FROM registry.felis.svc:5000/… 构建必败 ①);#71(kaniko 以 drop-ALL 解包底座层,chown 必败——任何非 scratch 底座必败 ①);#72(trivy 扫 jar 必拉 Java DB、被 egress 锁拒绝——含 jar 即所有真实模组包的构建必败 ①);#73(bootstrap 测试在跑 k3s 的主机上必假失败 ④ 工具);#74(console 断连测试读写竞态 flaky ④ 工具)。
第四十六批追加:#75(提交上传面无上限——pending 无个数上限、无存储预算、create/upload 无节流;一个账号可无限堆积上下文刷爆 uploads PVC ①);#76(提交无撤回/管理员删除路径——提交者无法自救、运维无法回收占用 ①);#77(未完成引导的会话触发受保护操作 → 面板显示"无权执行此操作"而非送往 /setup;tracker #8 ①);#78(create-if-absent 使新增 CR 字段在已装机永不落地——lobby 无 RCON 故控制台死、玩家数恒 0;tracker #1 ②);#79(troubleshooting [INERT] 图例指向已不存在字段 ④ 文档)。
第四十七批追加:无新缺陷——首个稳定 tag v0.1.0 的发布链与 release 安装/升级通道全实弹(release 二进制下载 → 无 checkout 全量安装 2m17s 零 fail/零 warn → felis update 双向报告;证据见该批节)。
已验证事实(正向清单)
- 安装→hook 发码→Owner 绑定→passkey(虚拟认证器)→面板管理员全链路 ✅
- 建服(POST /servers)→ 唤醒(operator 拉 StatefulSet pod)→ RCON
list→ SSE 控制台 → 停止 ✅ - 文件编辑:列目录/读/写(wire 为 base64)/256KiB 413/路径穿越 5 变体全拦截/运行中 409 ✅
- 备份→篡改→恢复数据演练:v1→备份→v2→恢复→读回 v1 ✅(G2 数据可恢复)
- 毒档案 fail-closed:穿越/绝对路径/符号链接条目 →
archive entry escapes target退出码 1,零写入 ✅ - 混沌:PG 掉线(healthz 仍 200、恢复后连接池自愈)、API pod 击杀(~2s 中断)、整机重启(32s 回归、会话/CRD/停止态全保留)✅
felis update报告(k3s/velocity 有更新、私有仓库 404 优雅处理)✅- 账户全套真机 E2E:绑定码新玩家注册(幂等/并发见 #18)、邮箱验证(onboarding 门)、邮箱 OTP 登录(错码扣次/5 次锁定后正确码也 400、重放 400、staff 账号 403 拒绝)、Passkey 注册+discoverable 登录+邮箱优先登录(虚拟认证器;Chrome 要求
Page.bringToFront才能过 focus 检查)、登出吊销会话(旧 cookie 401) - op-login 全状态机(start→status→approve→finish;早 finish 不烧码、错码扣次且请求保留、重放/重复批准/非管理员批准/未知 handle 全部按契约返回)✅
- 并发:OTP 风暴 8 路 = 1×202 + 7×429 且仅铸 1 码;绑定码并发(同码/双码)见 #18 ✅
- reaper 全链路真机演练(修复 #19 后):绑定挂载假世界 →
felis reaper一 pass:归档 tar 落盘(内容含标记文件)、world_backups行inactive_15d+90d 过期、PVC 删除且宿主目录回收、servers 行保留且 activity 时钟重置(红线②)、CRD 保留;第二 pass:伪造过期归档被驱逐(expired=1,文件删、行转deleted),合法归档未动;evaluated=2 reaped=1只动到期的世界 ✅ - S3 存储向导全链(第三十二批):错误凭据预检零副作用、正例落地(Secret + 两 toml + API 滚 + 状态行)、上传 → MinIO 桶 → 内部面取件、UI 回滚归位、控制面/工作负载镜像对齐 ✅
- NetworkPolicy 栅栏真机(第三十五批):pod 源"身份×端口"矩阵全对(允许行与拒绝行同源对照)、转发型外部源白名单闭环(拒→放→拒,ipset 与 spec 双幂等)、host/ClusterIP 路径全通、Mac→NodePort 面板 200;两条归因注记见该批 ✅
本轮新增真机证据(第二日)
- 默认安装的备份闭环:apply 新 bundle → 归档 PVC 自动创建(WaitForFirstConsumer,被首个 backup Job 拉绑定)→
POST /backup202 →GET /jobsrunning→succeeded → 漂移 marker →restore-backup→ 读回备份前内容 ✅ - 回收闭环(真实 k3s 布局 + 权限):resolvecheck(20d idle)→ CronJob 派 Job:
evaluated=2 reaped=1;归档含 marker、PVC+宿主目录回收、world_backups记inactive_15d;期间先经历lstat /worlds/…: permission denied(k3s storage root 0700)→ 修 ACL/root 权限后通过 ✅ - 磁盘压力三连 drill:①1e6 自定义类:游戏 pod 先走、控制面随后仍被逐(kubelet ranked/evicted 日志)→ 镜像 GC → ErrImagePull;②改内建
system-cluster-critical后:cannot evict a critical pod× felis-api/operator/registry,全部保持 Running;③恢复阶段实测DiskPressure条件 ~5min(eviction-pressure-transition-period)转 False,被 GC 的游戏镜像按 runbook 重导入后 25s 恢复 ✅ - 混沌:SIGKILL k3s 主进程 → kubectl 与 felis-api ready 均在 10s 内恢复(api pod 本身未被重启)✅;内存压力(2.6G tmpfs 满写):无 OOM kill、无人被逐(swap 5.9G 吸收 + 内核回收),控制面不受影响 ✅
- 构建链路定位(当时未修):Kaniko/Trivy 默认镜像是外部的 → 已加
[registry] kaniko_image/trivy_image/build_cpu_limit/build_mem_limit覆写;上下文跨 ns 不可挂载(PVC 不能跨 ns)是设计级缺口,s3 lane 也缺凭据注入 ✅(证据:internal/submit/blobstore.go:40、cmd/felis/api.gocontextBase 分支、jobspec 无 volumes/env)→ 第三批已修复,见下
本轮新增真机证据(第三批:探针 + 构建链路全通)
- 控制面探针(
0c8e29b):felis-api / felis-operator / registry 三个 Deployment 此前完全没有探针。修复后真机验证:api、operator/readyz+/healthz(internal 8081),registry/v2/(含命名端口解析);三个 PodReady=true、restartCount=0、无Unhealthy事件;operator 新增--health-probe-bind-address(8081,与 metrics 8080 分离,零检查也 404 → 已注册 ping) - 构建链路全通(
f79e5eb+02fd2de):- 传输:context_ref 改为 internal-face URL;
felis fetch-contextinitContainer 走 service token(对象来自felis-build里按bootstrap/felis setup同款 Secret 复制机制落地的felis-service-token;netpol 只放行控制 ns:8081)+ zip-slip 安全解包到限容 emptyDir;Kaniko--context=/context只读挂载 - 真机 E2E:玩家 OTP 登录 → 建 submission → 上传 gzip context(marker
felis-e2e-build-marker)→ Owner approve → Job:fetch ✅ → Kaniko 构建并 pushregistry.felis.svc:5000/user-uploads/<sub>:latest✅ → Trivy 扫描(内建镜像 DB)✅ → JobComplete;从 registry 拉回镜像校验/hello.txt内容 = 上传的 marker ✅ - 演练中真机抓到并修复 3 个单测看不到的缺陷:① Job requests=limits(2C/4Gi)在 4C/5.5G starter 上永远 Pending(
FailedScheduling/Insufficient memory)→ requests 改为地板值(250m/512Mi,不超上限);② Kaniko 重拷 Dockerfile 时 chown/chmod 到源文件 owner(distroless uid 65532)在 drop-ALL 能力下失败(copying dockerfile: chown … operation not permitted)→ fetch 容器以 root 解包(= Kaniko 自身 uid);③ Trivy DB 默认mirror.gcr.io正被 egress 锁拒绝(fail-closed)→ 新配置[registry] trivy_db_repository+ 文档 §8e 固化镜像配方(docker pull/tag/push aquasec/trivy-db:2进内建 registry;--insecure已覆盖明文 HTTP)
- 传输:context_ref 改为 internal-face URL;
本轮新增真机证据(第四批:PG 契约测试 + 验证邮箱唯一性 + owner 角色)
- PG 契约测试首跑抓到 #20:
internal/pgint对着真实 PG(felis_pgint,drop schema + 重放迁移)跑会话/OTP/op-login/绑定码/submission/build 全契约;TestOnboardingEmailOTPRejectsTakenEmail当场变红 → 挖出「索引从未创建 +ErrEmailTaken从未返回」。 - 迁移 0020 真机应用:
felis migrate up→applied 1 migration(s): [20];schema_migrationsmax=20;pg_indexes出现users_verified_email_unique。 - 邮箱唯一性真机 409 演练(auditfix17):owner 已验地址被玩家 onboarding 流程二次验证 → 第一次 409
email_taken;同码重放仍是 409(证明码未被消费,符合契约;若被消费会是 400)。 - admin 改邮箱清 verified 真机演练(auditfix18):PATCH player.test →
[email protected]后email_verified=f;PATCH 回原地址仍f;玩家走 onboarding 重验证 →t。 - owner 角色真机闭环(auditfix18):owner 行按 0011 文档升级路径置
role='owner'→GET /api/v1/users200(修复前 403);owner op-login start 铸请求+发码、finish 得role=owner会话;PATCH owner role / DELETE owner / DISABLE owner 全部 403;玩家邮箱登录回归 200。 - 配额原子门(第五批,
bb68fef,auditfix19 已上线):pgint 并发实证(真实 PG 上两路并发认领:恰 1 赢 + 1ErrQuotaExceeded;修复前两路全赢);docs/deferred-seams.md的 audit #4 条目核销。
本轮新增真机证据(第六批:reaper 警告链路端到端演练,auditfix20)
- 演练装置:独立 hostNetwork Pod(
felis:auditfix20,SA/卷结构复刻 live CronJob)+ 宿主本地零依赖 SMTP sink(捕获完整 RFC5322 原文)+ 专用felis-config-drillSecret(SMTP 指向127.0.0.1:1025);场景服warntest(独立 CRD,desiredState=Stopped,不占资源),期间 operator 暂停防 reconcile。 - 投递:13d idle → 恰 1 封,
To: [email protected](owner 的验证邮箱)、subject 含3d、正文含 server/remaining;warned_3d_at盖章、warned_1d_at留空;world_backups保持 4 条(纯警告零备份)。 - 去重与档位:重跑 0 封(已盖章不重发);14.5d idle → 补发第 2 封 =
1d档(elif 顺序正确,不是重复 3d),warned_1d_at盖章。 - 失败语义(三连):SMTP 端口打坏 → 日志
warn delivery failed; will retry next run(err=connection refused)、不盖章,恢复端口后同一 run 补发成功;邮箱email_verified=false→resolve owner email: … has no verified email、不盖章,恢复后补发;owner NULL → 静默跳过(无日志无警告)。 - 边界:idle=11d23h(< 12d 阈值)不发不盖章;idle=12d1m 发。
http_code冒烟:升级 auditfix20 后 panel 200 / op-login 200。 - 环境还原:warntest CRD+行删除、drill Secret/Pod 删除、operator 恢复;
servers表回到 resolvecheck+test-one,CRD 回到 lobby/login/resolvecheck/test-one。
本轮新增真机证据(第七批:idle auto-stop 三层修复,auditfix21/22)
- 发现路径:operator 深挖时先怀疑"计时器无驱动",真机实验立刻抓到 pruning(操作日志
unknown field "status.emptySince")+ 触发后 88s 无动作 + RBAC 403,三层各自独立、各自足以致死。 - 场景 1(超时点唤醒 + 写权限):EmptySince 已超时 11 分钟 → annotate 触发一次 → 秒级内 desiredState=Stopped、sts 0/replicas、EmptySince 清、phase=Stopped。
- 场景 2(完整自驱,决定性):desired=Running → pod 起 → 20:11:43 盖章 → 静置无干预 → 20:12:17 自动 Stopped(30s 到点后 ~4s 完成 stop+scaledown+markStopped)。
- 翻转混沌:3 秒内 Running→Stopped→Running,最终收敛 Running ready(期间 409 竞争为控制器正常噪音,controller-runtime 重试自愈)。
- 收尾:
spec.idle删除后再无自停;test-one 回 Stopped、空 sts、无 EmptySince;VM 镜像 auditfix22 =f650bf8。
本轮新增真机证据(第八批:operator 自愈深挖,auditfix23/24)
- RCON Secret 删除自愈(
56f3abd):修复前——删 secret 静置 60s 零察觉、触发后 96s+ 永久RconNotReachable、仅人工删 pod 可恢复;修复后——删 secret 同秒(20:22:16)察觉(Secret watch 日志)→ 新密码 + stamp 换值(3b24164f…→c465b627…)→ rollout → 20:22:47 全自动恢复 Running(31s)。 - 陈旧锚点(
82b5a60):修复前——已恢复服务器因锚点残留被Failed(StartupTimeout)、Provisioned=False永挂;修复后——Ready 即清锚(startRequestedAt: None)、Provisioned: True;pod blip 演练:删除→20:24:48 重锚→20:25:19 恢复→锚清空,全程无 Failed。 - StatefulSet 误删自愈:删除 sts → 秒级重建(PVC
world-test-one-0保持 Bound,Retain 策略)、29s 后 Running、stamp 保持c465b627…(未触发多余 rollout)。 - 收尾:期间 7 条 operator ERROR 全为 CR/sts 409 竞争噪音(自愈);test-one 回 Stopped。
本轮新增真机证据(第九批:未演练端点地毯覆盖 — 迁移 / access / window / credentials)
- account/migrate 四步状态机全链路(首个端到端演练,0 缺陷):造 user2 目标账户(绑定码
FDTGKYAQ)→ player.test claim test-one → 内部 start(未联动 UUID=404 not_linked;源=201 initiated)→passkey_required 409(有 passkey 强制强因子)→ passkey credentials list + DELETE 204(顺带覆盖管理端点;删后 OTP 门自动解除)→ OTP start 202 / 错码 400 / 真码 confirmed → issue-code(self=400 / missing=400 / 真目标=201)→ redeem 负例×2(源持码=400;target 错码=403 setup_required——onboarding 门正确拦截未验证账户;验证邮箱后重试)→ redeem 成功(小写+空格码兼容,servers_moved:1)。 - retire 断言全对:servers.owner→user2;migration=redeemed(含时间戳);源 disabled+软删;源活跃 session=0;源旧 cookie 401;double-redeem 400;源再加迁移 409 account_retired。环境复原:源复活 + 新 OTP 会话、test-one 归还无主。
- access 玩家管理全组(0 缺陷):players/whitelist/banlist 三个读 projector 解析正确(
There are 0 of a max of 20 players online:→[]);kick/permission/group/luckperms-info 调用全通(demo 服无 LuckPerms → 原样透传Unknown or incomplete command,API 不掩饰);输入负例 4 连 400(bad player/bad action/bad node/bad world);whitelist add/remove在空档案服上得到 vanillaThat player does not exist(fail-closed egress 无法解析 Mojang profile——vanilla 约束非 API 缺陷,命令已如实送达);手写 whitelist.json +command whitelist reload验证成功路径解析(players:["E2E_Tester"]);audit 7 条 access.* 全记录。 - updates/window:unset→null;PUT 正例+读回;半设 400;倒置 400;clear→null 全对。
- /fleet:4 服全量(含运行中 lobby/login 的 endpoint 地址)一次读全。
- bootstrap-assets crd:嵌入式资产含
emptySince(auditfix24 二进制核对)。
本轮新增真机证据(第十批:configure email 复制链修复,auditfix25)
- 发现路径:复核
8e7c7bb新增的 replicate 逻辑时怀疑 manifest/-n namespace 冲突 → kubectl 真机实验裁决(hardcodednamespace: felis版直接error: … does not match …,证实第一段必错、第二段被跳过)。 - 修复后行为验证:两条命令序列(felis-smtp manifest 携带目标 ns +
felis-configcreate--dry-run|apply)均被 server 接受且落点minecraft。 - live 收敛项(随下次
felis install/setup重跑):live CronJob 仍是旧模板(无FELIS_SMTP_PASSWORDenv);felis-smtp/镜像两 ns 均未创建(SMTP 未配置,属正常);与 operator Role 手动补 patch 同批处理。 - 部署:
felis:auditfix25=ed722d5(api/operator/reaper 三处 set;api/operator rollout 完成,panel 200)。
本轮新增真机证据(第十一批:submission 全路由负例矩阵 + 内部面取件 + owner 会话重铸)
- session 重铸路径(hook):owner 旧 cookie 过期(401)→ 临时插
account_links(owner↔假 UUID)→ op-login 三件套:start 202 → 日志取码 → 内部面op-login/{id}/approve(hook,经 service token)→ statusapproved:true→ finish 200role=owner→ op 域新 cookie 生效(/fleet200);演习后已删除假链接行。顺带复证:approve 失败时 status=false、finish 400op_login_invalid(码不烧,复用同码二次 finish 成功)。 - submission 车道负例矩阵(23/23 PASS,0 缺陷):create 未知字段/空名/非法字符/超长 → 全 400;非 gzip 上传 400、未知 id 404;跨用户上传 → 404 不可见(非 403);玩家打 admin 三路由(queue/approve/reject)全 403;reject 空理由/1001 字/夹带字段 → 400,成功 200(
reject_reason/reviewed_by=验主邮箱/status=rejected全对);重复 reject 409、reject 后 approve 409;无 blob approve → 400 且行保持 pending_review(未 stranded);玩家列表严格只见自己。 - 内部面取件:
GET /api/v1/internal/submissions/{id}/context(service token)→ 200 + gzip magic1f8b+ 解包内容 = 上传 marker;无 token 401;未知 id 404。 - 演习清理:E2E-Lane-* 8 行已删、假链接已删;队列只留历史 "E2E *"(approved)行。
- 备注:port-forward 再次因 API pod 重建悬死(空响应)→ 按速查重启即恢复;本轮无需新镜像(纯验证)。
本轮新增真机证据(第十二批:users 管理面全量矩阵 → 抓到并修复 #30)
- /users 管理面矩阵(修复前,39 PASS / 2 NOTE):create/get/patch/list 权限与负例全对(未知字段/非法用户名/owner 角色/未知 id → 400/404;owner 保护 403 复证);sessions 单条吊销→旧 cookie 立即 401→重登恢复、revoke-all 同效(重登两次含 61s 冷却等待,全绿);passkeys 无凭证 200 no-op;links 幂等/跨用户 409/非法 auth_source 400/解链 404 全对;disable/enable/delete/double-delete 全对。
- 缺陷 #30(两处 FK 500 + 一处假 200):
PUT /users/{unknown}/quotas与POST /users/{unknown}/links触碰 user_id 外键 → 500 internal(契约要求 404);GET /users/{unknown}/quotas回零值视图(=unlimited),像 id 存在。修复:PGRepo.requireLiveUser(live 行 +deleted_at IS NULL)守卫三个方法,handleGetQuotas/handleLinkAccount补 ErrNotFound→404 映射;单测TestAdminSubresourcesRequireLiveUser(fake 同步 liveUserExists 契约)+ pgint 真库契约(ghost→ErrNotFound、live 对照、软删后→ErrNotFound)。commit1918da2。 - auditfix26 真机复验:ghost 三连 → 404 not_found;活用户对照 → PUT/GET quotas 200(读回 2)、POST link 200、delete 200;panel 200,api/operator 滚动完成(新 pod 24s Ready)。
- 契约备注(非缺陷):软删用户
GET /users/{id}仍 200(带disabled=true+deleted_at,列表已过滤);DELETE .../sessions/deadbeef幂等 200;revoke-all 对未知用户 200 no-op。
本轮新增真机证据(第十三批:CLI / 镜像面 / 直接构建 / 面板 CDP 全路由 + #31/#32)
- CLI 面 19/19(VM,
felis-auditfix26):version/-h/无参=exit2/未知命令=exit2;manifests缺--velocity-cidr、缺--felis-image、坏 CIDR、坏 NodePort 全 fail-loud exit2,正例渲染 25 文档且--worlds-host-path分支出 CronJob+三条前置警示——整包kubectl apply --dry-run=server全部configured;apply缺-f/文件不存在/坏 JSON 各自 exit2/1/1;migrate up二次幂等(database already up to date);bootstrap-assets crd(含emptySince)与game-stack(tar)正常;update正常。 - 镜像面负例 + builds 负例:
/images列表 200、空体 400、合法 201、重复幂等 201、无?ref400、删除 204(契约;我的 200 预期作废)、再删 404、坏 ref 400;/images/build/unknownGET/cancel 均 404;空体提交 400;玩家打/images*全 403。 /images/build正路径(直接构建):借遗留 submission blob 作 context,FROM scratch内联 Dockerfile → 202 → 12s succeeded;/logsSSE 实收 Kaniko 流;真机 registrytags/list出现e2e/direct-probe:latest(镜像确实被推入)。留档:该 drill 镜像保留在 registry。auth/options:未知邮箱 →{"methods":[]};玩家 →["email_otp"](无 passkey 时);坏邮箱 400。PATCH /servers/{name}:空体/storage/空 policy/未知字段/坏名 → 400;未知服 404;displayName 设置+还原 200。契约备注:displayName:""因omitempty+merge-null 语义 = 清除该字段(本次 drill 先误读为设空串,比对 patch 前 fleet 快照确认 test-one 原无 displayName,无数据损失)。- 面板 CDP 全路由巡检(owner 会话,14 路由):全部有标题/有内容/无崩溃;唯二发现 = #31/#32(见下);
luckperms页对 stopped 服 409 → 优雅降级为「服务器已休眠 + 启动」态(browser 级 409 日志属正常资源日志,非缺陷)。玩家会话 4 路由零错误且导航分级正确(不见管理/平台组);未登录/login渲染正常(/me401 为预期探测)、/account未登录重定向/login。 - 缺陷 #31(面板 mock 残留·owner 判定):
ImageBuildPage用email==="[email protected]" || startsWith("owner@")猜 owner——真实 owner([email protected])不被识别(只能看 approved 提交),而任何owner@x邮箱都冒充 owner。改为消费 TierProvider 服务端isOwner。commit6907961。 - 缺陷 #32(面板 mock 残留·假构建种子):首次访问
/admin/builds向 localStorage 播种["bld-1","bld-2"]→ 每个新浏览器打两个必然 404 的请求(页面注释自认“in production will 404”)。移除播种。同 commit。 - auditfix27 复验:清掉该 localStorage 键后重访 → 只发
/me、零错误、空态正确;面板 111 单测 + typecheck 全绿。 - passkey 全流程复验(CDP 虚拟认证器,面板 UI 实操):注册(对话框输入→仪式→列表出现
E2E-Key)→ API logout → 邮箱优先 passkey 登录(/me回[email protected] user、落回/)→DELETE credentials/{id}204 → 列表清空(环境复原)。 - 环境:api/operator/reaper 镜像 =
felis:auditfix28(=6907961);面板 200。教训:pkill -f "port-forward …"会匹配到执行该命令的 ssh 自身 cmdline(命令里同时含明文port-forward svc/…)→ 自杀式断连;改为ss -ltnp取 PID kill,另起一条 ssh 启动转发。
本轮新增真机证据(第十四批:#33 死账号复活漏洞 —— 全登录门闭环 + 资产切断)
- 发现路径:users 管理面演习收尾核对时发现软删用户仍留
account_links/quotas孤儿行 → 顺藤摸瓜确认UserByEmail与SessionUser均不过滤disabled/deleted_at。 - 红证据(auditfix27,真机):禁用用户的邮箱门重登录
verify=200、/me=200(禁用锁死可绕过);已删号用户start=202(真实发码)、verify=200、/me=200(删号可复活)。 - 修复(
5853589,7 文件 / +306 行):①UserByEmail只解析 live 账号(覆盖 email/passkey 邮箱优先/op-login/auth options 全部前置门);②SessionUser同样过滤(皮带:任何门铸出的死号会话都立即失效);③RedeemPlayerBindCode两个解析臂拒绝死账号(新哨兵ErrPlayerAccountRetired→ 403account_retired,不消费码,可逆重试);④DeleteUser事务内DELETE webauthn_credentials+account_links(凭证与UNIQUE(mc_uuid)占用不再外泄);⑤ discoverable passkey resolve 补 liveness 检查。单测TestDeadAccountsCannotLogInOrKeepSessions+ pgintTestDeadAccountsAreLockedOutInPG(含"删后新铸会话不验证"与资产清点)。 - 绿证据(auditfix28,真机):红期为死号铸出的会话 → 401(皮带生效);死号
start=202中立且 90s 日志窗口 0 条码、verify=400 invalid_code;禁用中start中立(日志码数 1→1 不变)→verify=400;恢复启用后verify=200+/me=200(可逆);bind 门死账号 → 403account_retired且码保留count=1(retryable);删号后account_links/webauthn_credentials行数 = 0。 - 演习清理:红演练遗留(2 个测试账号的 quotas 行、1 条旧链接、2 条死号会话)已清除;
/fleet与 panel 冒烟保持 200。
本轮新增真机证据(第十五批:#34 死账号同族收尾 —— in-game 身份解析与链接接管,auditfix29)
- 发现路径:#33 修完 Web 登录门后做同族复查——in-game 面是 UUID 驱动、不走 Web 会话,因此 #33 的会话皮带盖不住它。两处:①
UserByMCUUID(claim / menu / wake 授权 / op-login vouch / QR link-status 五处共享解析)直接读account_links、不过滤 users 的 liveness;②VerifyLinkCode对「holder 已死」的接管语义未定义——一刀切 409 会把迁移退役这类真实场景锁死。 - 修复(
bb9798e,5 文件 / +182−7):①UserByMCUUIDJOIN users 过滤disabled+deleted_at——死账号的链接在游戏面读作未链接(claim 412、wake 落回 policy 门、vouch 403、statuslinked:false),绝不作为遗留身份存活;②VerifyLinkCode:软删 holder 的链接可由新账号凭新 mint 码接管(账号已亡,码证明调用者仍持有该 UUID),禁用 holder 仍 409(接管= 绕过锁死,不允许)、任何失败都不消费码。fake 单测TestLinkVerifyTakesOverDeletedLinkOnly;pgintTestVerifyLinkCodeTakesOverDeletedLink+TestDeadAccountsAreLockedOutInPG增「禁用链接无资格 / 复启用恢复」断言。 - 绿证据(auditfix29 真机,三面六点):
link/status:活{"linked":true}→ 禁用{"linked":false}→ 恢复{"linked":true};claim:活链 404(身份已解析、ghost 服不存在)→ 禁用 412 not_linked → 恢复 404;未知 UUID 对照 412;- 接管正例:临时软删 holder(
de49df52)→ 新 mint 码X3G3RZZM由 player.test verify → 200 linked:true;链接行迁移到 player.test(auth_source=mojang)、码被消费(account_link_codes0 行);演练后 holder 与链接全部还原; - 接管负例:临时禁用 holder(user2)→ 新码
X5W94S42verify → 409 already_linked;同码重试仍 409(不消费);恢复启用后同码由 holder verify → 200(码保留、可逆); - wake 授权:活 owner 202(真实启动)→ 禁用 403 forbidden → 恢复 202;test-one 已停回 Stopped 且所有权释放;
- op-login vouch:禁用 owner UUID → 403 not_admin(与未链接/非 staff 同一个拒绝面);活 owner UUID → 404 op_login_not_found(vouch 已解析、只是请求不存在)。
- 环境刷新(部署态):热升级遗留
FELIS_IMAGE=felis:auditfix16(job 侧执行器 backup/restore/fileedit/forwarding-init 引用的镜像)→ 已set envapi/operator 双 Deployment 至felis:auditfix29并滚动完成;api/operator/reaper 三镜像一致 = auditfix29;port-forward 重启后内部面绿;panel 200、op 面/me200。 - 运维备注:
account_link_codes会积累过期行("失败不消费"的另一面),巡检顺手DELETE FROM account_link_codes WHERE expires_at < now()。
本轮新增真机证据(第十六批:内部面残面地毯补测 —— ready / join-event / status / reclaim / blacklist / backup 负例)
- 背景:对内部面做覆盖盘点后发现五个端点此前无真机证据:
ready、join-event、status、player/reclaim、player/blacklist,以及internal/servers/{name}/backup的负例臂。本批全部补测(auditfix29),0 新缺陷。 ready:test-one → 204(advisory 语义);坏名 → 400。join-event(临时 UUID3333…):首次 204、重复 204(幂等);DB 实锤:test-one.last_active_at15:02→05:53(重置)、server_allowlist恰 1 行;缺 uuid → 400;未知服 → 404。演习后 allowlist 行删除、last_active_at复原。status:test-one → 200 全字段(phase/desiredState/endpointMode=fallback/…);未知服 → 404。player/reclaim:新建(临时 UUID4444…)→ 200hold_expires_at=+30d;幂等重试返回同一时间戳(首窗保留,与存储行 21:53:51.726017Z 完全一致);缺字段 → 400;protected-admin 负例:临时插 owner↔thirdparty 链接 → 409 protected_admin(不 bar、不 stash)。清理后username_blacklist/player_data_holds/server_allowlist/临时链接 = 0/0/0/0。player/blacklist:bar 后命中{"blacklisted":true};陌生 UUID →false。internal backup负例:未知服 → 404;坏名 → 400;保留名(login)→ 400bad_name(在入队前拒绝,RWO 闸门单测已覆盖)。- 探针:
/healthz200、/readyz{"status":"ready"}。
本轮新增真机证据(第十七批:hasJoined 多源会话校验器 —— 假 Yggdrasil 全链路 + 三方身份重写/改名)
- 背景:
hasJoined(velocity 指向的 vanilla sessionserver 协议面,handleHasJoined)此前零真机覆盖。本批用「VM 主机假 Yggdrasil + 临时将[[auth_source]]换向」的方式把正/负路径全部打通。 - 装置:主机 python 假源(
:18099,按 username 分流ftok/ftnotch/ftbadname/ftdown/其余 204);felis-config的 littleskin 源临时改指http://10.42.0.1:18099/fake(tag=faketest/prefix=FT),api 滚动后逐项打靶。演练后配置已还原 littleskin、装置已清理。 - 结果(全绿,0 缺陷):
- 三方身份重写:
ftok→ 200{"id":"74409c3bbae93acabd2176e517b4f2a0",…},与本地按uuid.NewMD5(felisAuthNS, "faketest:native-123")的预算值逐位一致; - 保费名冲突改名:
ftnotch→47c5527a18b03fe5a49ec50c11154dcd+name="FT_Notch"(Mojang 实查 Notch=200 premium);ftok的FtPlayer恰也是真实 Mojang 名(640c1672…)→FT_FtPlayer,改名按设计触发(非保费名保持原名); - 敌意插件名拒绝:假源返回
§4admin→ 204(不落 proxy 玩家列表); - 源故障不静默:假源 500 → 503(velocity 报 auth servers down),对照未知玩家 → 204;
- 形状负例:缺参 / 超长参 → 204;声明 body → 400 +
Connection: close(防 drain 挂连接)。
- 三方身份重写:
- 顺带覆盖:
isPremiumName的 Mojang 实查(api.mojang.com,超时/错误 fail-closed=改名)——404→非保费、200→保费判别实测成立。
本轮新增真机证据(第十八批:缺陷 #35 —— world 执行器 uid 1000 读不了游戏服写的世界 + 面板备份模块补全)
- 发现路径:给备份页新增「立即备份 + 最近操作」后做第一次真机 drill——面板链路全对(按钮/成功消息/运行态→终态、零坏请求),但 backup Job 真的失败了:
Job has reached the specified backoff limit,pod 日志felis backup: archive: backup: tar walk: open /world/world/level.dat: permission denied。 - 根因(缺陷 #35,跨模块):世界卷的属主是游戏镜像自己的 UID(我们发布的 Paper 镜像都是 root),而 Paper 保存
level.dat用的是 0600(Files.createTempFile 默认权限)→ 固定 uid 1000 的 backup / restore / fileedit / reaper 四类执行器:读不了(归档permission denied)、也覆盖不了(restore 写不进 600-root 的 level.dat)。此前 drill 侥幸全绿,是因为当时的世界文件由 uid-1000 工具(restore/夹具)写的;服务器真实启动保存过一次之后,所有备份从此必死。测试全部是 shape 断言(无集群),这条只能真机抓。 - 修复(
2010961):四类执行器统一改以 root 运行(runAsNonRoot:false,省略 fsGroup 防误 chgrp),容器保持除DAC_OVERRIDE外 drop-ALL——与 operatorinit-forwarding容器的既定先例同源("只有 root 能可靠读写这些文件");DAC_OVERRIDE 兜住"游戏镜像是非 root UID"的任意镜像场景。四份 shape 测试同步改断言。troubleshooting §10「Permissions」段落重写(uid-1000 + setfacl 时代结束)。 - 绿证据(auditfix31,真机四联 drill):
- backup(面板 CDP 实操,红→绿同场景):点击「立即备份」→
进行中→成功(同页面 reload 后仍在);Job podrunAsUser:0+ DAC_OVERRIDE;新归档test-one-1790115428472416736.tar.gz实测含world/level.dat(471B)与 server.properties,共 499 条。 - restore:
POST restore-backup(bk-9df0…)→ 202 → Job Succeeded(root 写路径过关),日志restored from … into /world。 - fileedit:
GET /servers/test-one/file?path=world/level.dat→ 200(base64-gzip 内容 628B)——修复前该请求必然 permission denied。 - reaper(从 live CronJob 派生一次性 Job + 复刻钻取世界:1Gi PV/PVC +
world/level.dat512B 0600 root + marker + 20d idle 行):pod root+DAC_OVERRIDE;world reaped server=reapdrill2 …、evaluated=3 reaped=1;归档 711B 含 marker 与 level.dat;PVC 删除;world_backups得inactive_15d行;servers 行/CRD 保留(红线②)。钻取现场全部清理(job/CRD/行/PV/宿主目录/临时归档)。
- backup(面板 CDP 实操,红→绿同场景):点击「立即备份」→
- 面板补全(
97a64c8):备份页新增「立即备份」(仅 Stopped 可用,409 原文呈现)与「最近操作」卡(GET/servers/{name}/jobs,running 每 5s 自刷新,failed 显示 Job 失败文本——正是它把上面这次失败暴露出来的)。面板 113 单测 + typecheck 全绿。 - 部署注意:backup/restore/fileedit 的 Job spec 是 api 运行时渲染,随镜像即生效;reaper CronJob 的 pod 模板是安装期静态渲染——本轮已按新形状热补丁 live 对象(root + DAC_OVERRIDE),
felis install/setup重渲染时收敛。
本轮新增真机证据(第十九批:面板补齐 owner 解绑通行密钥入口)
- 背景:
DELETE /users/{id}/passkeys(owner-tier 凭据补救:密钥丢失/被盗时切断登录脚架,且不锁死账号——邮箱码/游戏内审批仍可用)后端早已实现并审计,但面板无入口,owner 只能靠 API。属"功能缺口"而非缺陷。 - 修复(
11ac4f5):用户详情页危险操作区新增「解绑通行密钥」行 + 确认对话框(api.unbindUserPasskeys客户端方法 + 双语 i18n + wire-shape 测试)。 - 绿证据(auditfix32,CDP 真机全链):player.test 注册虚拟认证器密钥
E2E-Unbind→/auth/options从["email_otp"]变为["passkey","email_otp"]→ owner 在/admin/users/<id>点「解绑通行密钥」→ 对话框 → 确认 → 凭据列表清空、options 回落["email_otp"](passkey 门确实关闭);审计user.unbind_passkeys落账(同批还可见account.passkey.registered/backup.create/backup.restore/reap_world各审计行)。面板 114 单测全绿。
本轮新增真机证据(第二十批:缺陷 #36 —— 提交者看不到构建结果)
- 缺陷 #36(提交/构建模块·结果不可见):
/me/submissions只回审核状态;构建的成功/失败(含失败原因)只有 admin-tier/images/build/{id}看得到 → 提交者永远不知道自己的包构建死了。修复72c4aa3:两个列表路由(玩家/me/submissions+ 管理/submissions)对build_id非空的行附build_status/build_error,来源是只读Builder.Get(绝不调 Sync——状态推进归 15s reconcile 循环,列表渲染不碰集群);构建行已消失(ErrNotFound)→ 字段省略;其他存储错误照常 500,绝不静默吞。OpenAPI 的 Submission schema 同步。 - 绿证据(auditfix33,真机双例):① 失败例(上下文 Dockerfile
COPY does-not-exist)→ 提交 → owner approve →/me/submissions返回build_status:"failed"+build_error:"build job failed or scan found a CRITICAL CVE";② 成功例(FROM scratch+LABEL)→build_status:"succeeded"。面板 CDP(玩家会话):展开行显示「构建状态」徽章(构建失败/构建成功)+ 失败原因 + build_id,全程零 4xx。演习残留已清(2 行 submission+build、2 个 context blob、1 条 whitelist 条目;sub-bf7dc1…那条是更早 E2E 遗留,未动)。
本轮新增真机证据(第二十一批:面板文件编辑器补齐 —— 缺口而非缺陷)
- 背景:
GET /files、GET/PUT /file后端早已全绿(可读写level.dat),但面板无入口——「一行 server.properties 写错导致起不来」的修复路径只有 API。属功能缺口。 - 修复(
0a36b3f):新增/servers/:name/files页:面包屑目录浏览、编辑器对话框([]byte ↔ base64 编解码)、二进制文件打开即只读(NUL/非 UTF-8 拒绝 round-trip)、>256KiB 禁用保存;停服门前置(世界卷 RWO,未停服时整页显示「服务器正在运行」+ 停止动作,而不是让每个调用 409);控制台右侧新增门口卡。i18nfiles命名空间(en/zh)+ 3 条 wire-shape 测试。 - 绿证据(auditfix34,CDP owner 全链):根目录 →
world/导航;felis-e2e-marker.txt(原v1\n)打开 → 追加 → 保存「已保存 …」→ API 读回一致 → 重开一致 → 还原原始字节 → 落盘复核一致(零残留);level.dat打开为只读 + 二进制提示;控制台门口卡存在;audit_logs两条file.write(edit/restore 各一);全程零 API 4xx/5xx。面板 117 单测 + typecheck 全绿。
本轮新增真机证据(第二十二批:缺陷 #37 —— 系统服务在面板里全是死操作)
- 缺陷 #37(面板·fleet 死操作):
login/lobby是平台自建系统服务、名字在保留名单里,于是每个 per-server 路由都用ValidateServerName拒绝(400bad_name)——但管理端 fleet 表格给这两行渲染 认领/停止/唤醒/控制台,全是死操作(控制台链接点进去也是一页invalid server name: reserved)。修复2f90851:naming.IsSystemServer作为唯一事实源;fleet 行附system:true;面板把这两行渲染为「系统服务」纯标签(owner 列 + 操作列),不再给任何动作。玩家侧不受影响(/me/servers本就不含系统服务)。 - 绿证据(auditfix35,真机):
GET /fleet(owner)→lobby system:true、login system:true、test-one/resolvecheck无 flag;面板 CDP:两行「系统服务」、0 按钮 0 链接;test-one行照常 认领/控制台、无系统标记;零 4xx。Go 侧新增TestIsSystemServer+TestFleetAdminRead的 system 子测试。
本轮新增真机证据(第二十三批:缺陷 #38 + 多节点回收缺口 —— reaper 提示过期 / 钉节点)
- 缺陷 #38(CLI·提示过期):
felis manifests渲染 reaper 时的 stderr 提示还在教“uid 1000 需要setfacl -m u:1000:x才能遍历存储根”——#35 之后 reaper 已改为 root + DAC_OVERRIDE,这条指导已失效且会误导运维(照做无害但白做,真问题被掩盖)。同批落 多节点回收缺口(结论第 5 条):新增--reaper-node,reaper CronJob 的 pod 渲染nodeSelector kubernetes.io/hostname=<node>;多节点集群必须钉在存世界的节点,否则可能调度到 hostPath 为空的节点。--reaper-node无--worlds-host-path时 fail-loud exit 2。修复daf7602(双测:TestReaperCronJob_NodePin、TestManifestsReaperNodePin)。 - 绿证据(auditfix36,真机 render + dry-run + 收敛 diff):
- 固定渲染:
felis manifests --felis-image felis:auditfix36 --velocity-cidr 10.211.55.6/32 --panel-node-port 30443 --worlds-host-path /var/lib/rancher/k3s/storage --archive-local-path /var/lib/felis/archives --reaper-node localhost.localdomain→ exit 0;bundle 内kubernetes.io/hostname: localhost.localdomain;stderr 0 处 uid 1000 / setfacl,改为「已钉到节点 …」; - 负例:
--reaper-node无--worlds-host-path→ exit 2 + 原文;不传 node 的渲染 stderr 仍完整保留「NO nodeSelector … 多节点必须传 --reaper-node」警示,bundle 内 0 个 selector; kubectl apply --dry-run=server -f -:整包 全部 configured(含新 nodeSelector 的 CronJob);- 再安装收敛性 diff(
kubectl diff,本批新增的收敛证据):与 live 对比只剩 一个对象(reaper CronJob)两处实质增量——+env FELIS_SMTP_PASSWORD(热升级期未补的模板字段)与本次显式传入的+nodeSelector;其余 24 份文档(api/operator Deployment、RBAC、NetworkPolicy、PVC、registry)零差异——即“热补丁过的 live 对象”与“当前代码重渲染”已收敛(reaper 安全上下文 root+DAC_OVERRIDE 两侧一致,无 diff)。
- 固定渲染:
本轮新增真机证据(第二十四批:S3 上传通道真机演练 —— 此前只有单测的暗路径)
- 背景:
internal/submit/s3store.go(S3ContextStore)此前只有单测,本装是 local 路径(user_uploads_context = "/var/lib/felis/uploads")从未激活。本批用「VM 宿主 MinIO(quay.io 镜像,:9000)+ 临时把 felis-config 切到s3://felis-user-uploads+[registry.s3] endpoint=http://10.211.55.6:9000+ 创建felis-uploads-s3Secret」把整条通道打通,全程 0 缺陷、演练后还原并逐项复核。 - 绿证据(auditfix36,真机全链):
- 切 S3 后 api 滚动启动 无 “S3 user-uploads store not configured” 告警(凭据解析成功);pod 内 busybox 探针实测可达
http://10.211.55.6:9000/minio/health/live(rc=0); - 玩家提交 + 上传 → 200,对象实测落桶:
sub-12c953e12b0cd144/context.tar.gz(197B,mc ls 实见); - owner approve → 构建 Job
build-bld-1790118222183394193status.succeeded=1(fetch-context 从内部面流式取件 = api 自 S3 读回成功);/me/submissions的build_status收敛为succeeded; - 回滚:felis-config 还原(sha256 与演练前备份逐字节一致)、删除
felis-uploads-s3、api 滚动;再演练一次本地路径:新提交上传 200 且 blob 实测落在 uploads PVC(context.tar.gz 197B);启动日志仅剩 smtp/jwks 两条既有提示; - 清理:2 行 submission + 1 行 build 删除、回滚演练 blob 删除、S3 构建产物从 whitelist 摘除(204)、MinIO 容器 + 两个镜像移除;
/fleet200。
- 切 S3 后 api 滚动启动 无 “S3 user-uploads store not configured” 告警(凭据解析成功);pod 内 busybox 探针实测可达
- 遗留观察(非缺陷):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-oneRunning → 选中 → 卡「is stopping」→ CRDdesiredState=Stopped、pod 收敛消失、审计break_glass.halt;面板wake/stop两个恢复杠杆均 202(复验后还原)。Sync 正路径——test-one(Stopped)→ 卡 backup started → Job 6s Complete → 归档落felis-backupsPVC(167MB)→world_backups行present→/api/v1/backups可见。Sync 负路径——对 Running 选 → 友好 409 卡(不误烧冷却)。 - 缺陷 #39(owner 席位可被静默复制,且不可清理):恢复模式下用非在位席位名做「reset」→
UpsertOwnerinsert 臂铸出第二个 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-<name>-0" not foundPending 至 deadline,全程零失败记录。真机用已回收的resolvecheck复现(留证后删除)。修复508a1c0:Cluster.WorldVolumeExists(直接 Get 与 Job 挂载同名的 PVC)+ 两 handler 409no_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 up20 迁移 → 无菜单/无认证直接铸 owner;local_auth_enabled=true;审计break_glass.bootstrap;exit 0;库/hba 规则/临时配置即测即清);② 台面收敛:reaper CronJobsuspend=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)。勿再在本机跑容器。
本轮新增真机证据(第二十六批:felis setup 重跑向导逐屏 + 构建链 pin 回验 —— 缺陷 #44)
- 范围:本机已装机,故覆盖"重跑状态屏 + c/s/e 三条 reconfigure 流";首装屏(postgres/owner/connect/edge/storage/smtp/mc-bind/migration/preflight/summary)此前各批已有定点真机证据(安装闭环 / 第二~三批 / 第九批 / 第二十五批),本轮不重复。
- 逐屏走查(auditfix38→39,VM tmux):
- 重跑 → 直落状态屏「✓ Felis is already set up.」(owner/connect 不触碰;host bootstrap 已就绪跳过)✓
c→ 三选一 chooser(Local / Cloudflare+Access / Reverse proxy + 警示语)渲染 ✓,esc 无损返回。s→ chooser 预选当前后端;S3 分支表单(Endpoint/Bucket/Region/AK/SK + 提示)渲染 ✓。观察:reconfigure 非只读——选「Local disk」即 apply(写 /etc 两文件 + 重渲染 felis-config + 滚 API);从 S3 表单 esc 退回会把选择重置为 Local 预选。e→ SMTP 表单渲染 ✓;esc 直接回状态屏、零副作用(felis-smtp 未创建)✓。
- 缺陷 #44(CLI·重跑框脱落):重跑后完成
s/creconfigure,落回首装 summary「✓ Setup complete.」——丢了 alreadySetUp 框(smtp 路径有专门分支,storage/connect 漏)。修复abb5910(showSummary透传m.result.alreadySetUp)+ 回归测试TestRootReconfigureStorageKeepsStatusFraming。真机复验(auditfix39):storage reconfigure 完成 → 「✓ Felis is already set up.」+ storage recap ✓。 - 演练事故(自曝;环境 drift,非产品缺陷):首次 storage 走查意外触发 Local apply——它从
/etc/felis/felis.pod.toml重渲染 Secret,而构建链 pin 值当初只热补在 live Secret、不在 /etc 文件 → 重渲染清空 pin(放任则下次构建死在:latest拉取 + trivy DB egress)。当场修复:pin 值写回/etc/felis/felis.host.toml+/etc/felis/felis.pod.toml→ 重渲染 Secret → 滚 API;再做第二次 storage apply,重渲染后 pin 仍在(drift 修复耐久)。 - 构建链回验(direct build,真机):Job 规格实证
kaniko=gcr.io/kaniko-project/executor:v1.24.0、trivy=aquasec/trivy:0.74.0、--db-repository registry.felis.svc:5000/mirror/trivy-db:2(registry 仍有mirror/trivy-db);POST /images/build(context=遗留sub-bf7dc18…blob,内部面取件)→ 202 → succeeded;registrye2e/pins-check落位;whitelist 条目已摘除(204)、Job 已清。 - 文档修正(
ae6e925):§8e 原「编辑 felis.toml 后重启 felis-api」不完整(API 挂的是 Secret)→ 改为「写进 /etc 两文件 → 重渲染 Secret → roll」,并写明三种无效/易损做法(只 restart / 只改 host 文件 / 只热补 live Secret——后者会在下次 reconfigure 被冲掉)。 - 收尾:api 1/1、panel 200、tmux 全清。
本轮新增真机证据(第二十七批:告警模块落地 —— 内部面 /metrics + 规则集 + 真实构建失败实弹演练)
- 范围:把"指标 → 规则 → 告警"链路从零补到可交付:API 内部面
/metrics(94f71ee)、deploy/alerts/规则与 promtool 单测(43df08b)、真机实弹演练(本批)。 - /metrics(
94f71ee):felis_image_build_failures_total此前只在进程内存里、无任何 scrape 出口。修复:internal face(8081)新增GET /metrics(服务面 Public 路由,语义同 /healthz);单测断言外部面 404。真机:port-forwardsvc/felis-api-internal 18081:8081→ 200 且含felis_image_build_failures_total;operator:8080提供felis_servers_total/felis_start_duration_seconds_*。 - 规则集(
43df08b):deploy/alerts/felis-alerts.yaml(plain Prometheus)5 条——构建失败 increase>0 / 起服 p90>300s / 磁盘可用<15% / DiskPressure / 内存可用<10%;felis-prometheusrule.yaml为 prometheus-operator twin(脚本比对两文件 groups 一致);felis-alerts_test.yml为 promtool 单测。VM 上 promtool 3.14.0 实跑:check rules+test rules双 SUCCESS。 - 实弹演练(真实构建失败 → pending → firing):
- 打包含
COPY does-not-exist的 Dockerfile 上传到 uploads PVCsub-alertdrill→POST /images/build(e2e/alert-drill2:latest); - Kaniko
failed to get fileinfo for /context/does-not-exist→ Job Failed、build 行failed; - 真实 Prometheus(宿主
:19090)scrape127.0.0.1:18081(api) 与:18080(operator) 双 target up;felis_image_build_failures_total{job="felis-api"}=1; FelisImageBuildFailurespending(activeAt 08:28:14Z)→ 08:33:14Z 准时 firing(for: 5m精确到期),labels/annotations 完整。
- 打包含
- 清尾(残留全清):whitelist
e2e/alert-drill摘除(204);sub-alertdrill目录、/tmp/drillctx、两个演练 Job、tmuxprom/fwd/alertpoll、/root/prom-drill(promtool+prometheus 二进制)全删;DB%alert-drill%行删除(whitelist/build 复核 count=0);宿主无残留监听/进程。演练期间 live api 进程内计数器=1(重启归零,属演练事实)。 - 可达性追加:#44 定级 ①(轻)——已装机环境重跑
felis setup完成 storage/connect reconfigure 即触发。
本轮新增真机证据(第二十八批:缺陷 #45 —— 审核门"盲批":评审看不到将被执行的 recipe)
- 缺陷 #45(构建 lane·审核语义):被执行的 Dockerfile 永远来自上传上下文压缩包根目录的
Dockerfile(build/jobspec.go钉死--dockerfile=Dockerfile),API 的dockerfile字段仅审计存档(submit.auditDockerfile、build.Request注释均已声明)——但审核者没有任何路径能看到它:GET /api/v1/submissions不含 blob 内容、面板只显示context_ref文本、内部面取件路由是 service-token(评审用不了)→ "人工审核是门禁"事实上是盲批。同批口径缺口:POST /api/v1/images/build的dockerfile字段在 OpenAPI 里无任何说明(易被当成"将被执行"),面板表单也把该框呈现为"Dockerfile 内容 *"。 - 修复(
168a375):- 新增 admin-tier
GET /api/v1/submissions/{id}/context:评审下载与构建 Pod 同源同字节的context.tar.gz;Content-Disposition: attachment+nosniff(攻击者提供的归档只下载、不渲染);审计submission.context.download(actor=评审者、target=submission id)。 - 内部面取件 handler 共享
openSubmissionContext/streamSubmissionContext(行为不变,原测试锁定)。 - OpenAPI:新路由 +
/api/v1/images/build字段描述补全(明说"执行的是 context 根目录的 Dockerfile;dockerfile仅审计")。 - 面板:SubmissionsPage 展开区新增「下载上下文」按钮(spinner/错误呈现,i18n en/zh);ImageBuildPage 表单补审计说明行。
- 测试:
TestAdminSubmissionContextRoute(流式/404/503)+ admin-only 矩阵加该路由 + OpenAPI parity 强制文档;面板 117 单测 + 构建、go vet/go test 全绿。
- 新增 admin-tier
- 真机验证(auditfix41 已部署;owner 会话经 op-login + 内部面代 approve 重铸):
- admin 下载
sub-bf7dc18e9dd97dc2→ 200,attachment; filename="context.tar.gz"、application/gzip、nosniff;sha256205496f2…与 uploads PVC blob 逐字节一致; - 内部面(Bearer=felis-service-token)同 blob → 200 + 同 sha256(重构未破坏构建取件路径);无 token → 401;
- 有效玩家会话(console host 隔离,仅 adminOnly 生效)→ 403;匿名 → 401;不存在 id(admin)→ 404;
- 审计落账:
audit_logs=[email protected] | submission.context.download | sub-bf7dc18e9dd97dc2; - 面板产物:服务端 index.html 引用新构建
index-CwFSKpgW.js,bundle 内含新按钮逻辑(grep 命中 3 处)。
- admin 下载
- 可达性追加:#45 定级 ①——每一次真实的"用户提交 → 管理员审核"都会踩到(审核者此前无法查看将被执行的内容)。
- hook 链补齐(可复用):staff 账号走邮件登录门会被设计拒绝(refuse staff)→ owner 会话铸法:
op-login/start([email protected])→ VM 日志 grepemail-otp取码(no-Mailer fallback)→ 内部面op-login/{id}/approve(Bearer=felis-service-token;bodyapprover_uuid=owner 的 mc_uuid)→op-login/finish(curl -c 存 cookie)。
本轮新增真机证据(第二十九批:镜像耐久落地 —— registry 托管 + 回环拉取路径;#46–#49)
- 背景(结论清单第 2 条):磁盘压力演练证明 kubelet 会 GC 掉"当前无人使用"的镜像 → ImagePullBackOff,恢复依赖人工重导入。本批让镜像自愈:自建镜像全部托管进内建 registry,节点侧 pull 经回环 hostPort(节点 containerd 到 Service VIP 是死路,实测 "Empty reply")。
- 平台侧
a9b275a:registry 容器端口加hostPort 127.0.0.1:5000;同提交修 #46 —— registry 独立资源模板(1 CPU / 2Gi):旧模板 256Mi 在实测推 475MB 层时被 OOM kill(dmesgoom-kill … registry, oom_score_adj=989,上传中断),2Gi 下同一推送 2 秒完成。 - 安装器侧
13d64e0+ 文档fa0e8d7:自建镜像规范 ref =registry.felis.svc:5000/felis/{felis,limbo,lobby,paper}:demo;import 进 containerd 就用该名(首启命中本地,免 registry round-trip),deploy_bundle之后统一push_images_to_registry入仓(推127.0.0.1:5000;registry 只认主机名之后的路径 —— 推/拉落点一致)。configure_registry_mirror写registries.yaml(registry.felis.svc:5000 → http://127.0.0.1:5000),内容不变不重启 k3s;import_registry_image预缓存 registry:2(重跑走跳过分支)。迁移 0021 把 recommended 白名单重指到 registry ref(线上实查两行已落)。 - 真机三次重跑(
/opt/felis/src=765a892快照;FELIS_SKIP_FETCH=1 FELIS_INSTALL_MODE=full FELIS_IMAGE=registry.felis.svc:5000/felis/felis:auditfix42):- run1 失败 = #47:Mac tar 的
._*旁文件混入构建上下文,._0004_*.sql被 //go:embed → 新二进制的felis migrate报non-numeric version ".",安装器停在 run_migrations。修复5fa8b74(.dockerignore 排除._*/.DS_Store)。复验方式:故意在暂存树留._zz_probe_junk.sql→ 重建后migrations applied(过滤生效)。 - run2 失败 = #48:每个镜像一对
systemctl start/stop docker触发 systemd 限流(Start request repeated too quickly / start-limit-hit),第 4 个镜像(paper)未入仓。修复c7e585e(整批一次 start/stop,单测锚定)。run3 零[fail]:4 镜像全部入仓(push digest ×4 实收)。 - run3 收敛实查:api/operator/reaper =
registry.felis.svc:5000/felis/felis:auditfix42(registry ref 首滚命中本地 import);registry 模板 1 CPU/2Gi 生效;reaper CronJob 同步换 ref 且 nodeSelector 保留;login/lobby Running 于 registry ref;迁移 applied。
- run1 失败 = #47:Mac tar 的
- GC 演练(本批验收本体):
- A 控制面:
ctr images rm …/felis/felis:auditfix42+ctr content prune references→ 本地 ref 消失 →rollout restart felis-api→ 事件Pulling→Pulled … Successfully pulled image … in 25ms,ref 恢复、pod Running。 - B 游戏:rm
…/felis/lobby:demo→ 删lobby-0→Successfully pulled … in 10ms … Image size: 182357358 bytes,Running。 - #46 复验:整轮重建 + 4 推送期间 dmesg
oom-kill计数不变(仍 2,历史)。
- A 控制面:
- 构建 lane 复验:
POST /images/build(context=遗留sub-bf7dc18…;refregistry.felis.svc:5000/e2e/durable-check2:latest)→ 202 → succeeded;registrye2e/durable-check2tags 落位;Job 事件见 felis/kaniko/trivy 三镜像就绪;白名单条目摘除(204)、Job 清。 - #49(同一条升级路径的第二类静默回退):
write_felis_toml重写整表([registry] 仅 url+build_namespace、[archive] 仅 store+local_path)→ §8e 的 kaniko/trivy pin、构建上限、uploads 后端、[registry.s3]、reaper 的 retention/warn_before/max_local_bytes 在重跑时全部丢失(S3 安装切回 local、构建回退被 egress 拒绝的上游 executor)。修复765a892+b8e554d(沿用 [smtp]/auth_source 的 carry 模式;url/build_namespace/store/local_path 保持安装器所有)。真机复验:预置retention = "30d",run3 后 host toml / pod toml / felis-config Secret 三层都在,且构建 lane 直接用 carry 的配置跑通(上条)。 - 可达性:#46 ①(用户构建大层或安装器入仓即触发;实测 475MB 层);#47 ②(Mac 打包树构建路径,真机踩中);#48 ②(安装器重跑,真机踩中);#49 ②(升级=重跑安装器,真机踩中)。
- 口径/遗留:
demo-up.sh未改(dev/demo 路径,本地 tag 导入维持原状);kaniko/trivy 编译默认值未动(§8e 改为"镜像进 registry"配方);registry 2Gi 为渲染常量(暂未开 flag);drill 残留:registry 里e2e/*小镜像留档,durable-check2白名单条目已摘。
本轮新增真机证据(第三十批:缺陷 #50 —— [smtp] carry 吞掉下一节的注释块,每次重跑 +1)
- 发现路径:为
felis setup首装连续走查做前置盘点时读 live 配置——/etc/felis/felis.host.toml已经堆了 3 份、felis.pod.toml4 份重复的 Yggdrasil 注释块(同一段文案逐次叠加);用脚本自带的提取器实测:一次重跑会把 3 份全部当作[smtp]内容带走,再叠一份模板注释 → 每次重跑 +1、无上界(host/pod 增速不同步,现场 3 vs 4 即历史残迹)。 - 根因:
persisted_smtp_block打印"[smtp]到下一个 section header 之间"的所有行;generated 注释块正好落在这段区间里 → 被吞并。同文件的自称"cached on first call"缓存因写方是命令替换(子 shell)从未生效,pod 写实际上重复抽取刚被重写的 host,加剧了两文件的不同步。纯注释膨胀、无功能损失,但属 #49 同族 carry 语义缺陷(把不属于自己的内容也带走了)。 - 修复
4d3c85f:carry 改为白名单(section header + 键行),与 #49 的[registry]/[archive]提取同型;删掉失效缓存说明。deploy/bootstrap_test.sh新增用例:配置值被携带 / 不吞注释行 / 不越节 / 写回后二次抽取字节稳定(幂等)。 - 真机验证(auditfix43,两次连续全量重跑):
- run1(
bootstrap-auditfix43.log):零[fail];host 注释块 3→1、pod 4→1;与 run 前快照 diff 恰为 21/31 行(全部是被删掉的重复注释)——值零漂移(kaniko/trivy pin、trivy_db_repository、uploads local、[registry.s3]空、retention="30d"、auth_source 原样、空[smtp]节保留;两文件 db host 仍分别为 127.0.0.1 / 10.211.55.6)。 - run2(
bootstrap-auditfix43b.log,紧接再跑):零[fail],仍 1/1 —— 收敛证明(旧代码此处会 1→2 继续增长)。 - 部署同步:api/operator =
registry.felis.svc:5000/felis/felis:auditfix43;reaper CronJob 同 ref 且 nodeSelector 保留;registryfelis/felistags = auditfix41/42/43;panel 200;host 二进制已从新镜像提取。
- run1(
- 可达性:#50 ②——配置过 email(存在
[smtp]节)的安装,按文档升级=重跑安装器即触发;危害=配置注释无限膨胀(每次 +1),无功能损失。
本轮新增真机证据(第三十一批:首装连续走查 + 缺陷 #51 —— 工作负载 felis-config 副本永不刷新)
一、felis setup 首装单次连续走查(队列第 1 项,完成;0 缺陷)
- 装置:scratch 库
felis_scratch(新建 +felis migrate up21 条迁移)+ scratch 配置(真实 pod toml 副本,仅换库名;root 0600);hook 直插account_link_codes一枚绑定码(等价/link内网端点写入,代替"进服拿码");tmux 驱动felis setup -config;k8s 只读复用(登录门 Ready 等待通过)。 - 连续走查(一条会话走完):Preflight(自动:PG✓/迁移 21 applied/面板✓)→ MC 绑定(输码 → working →「✓ Owner account is ready.」+ 一次性 setup URL)→ 连接 chooser(Local)→ 存储 chooser(Local → working → ~20s 后「✓ Local storage configured.」,含 Secret 重渲染 + API rollout)→ 首装 Summary(「✓ Setup complete.」+ owner/setup URL/access/storage/panel 卡片 + c/s/e 提示)→ 轨道回顾(← 依次只读 recap Storage→Connection→Owner→Preflight,→/esc 回到前台)→ Enter 退出 → stdout 汇总(
Owner account … provisioned (passwordless)、Recorded as "root"、setup URL、Admin console)→ EXIT=0。 - 结果:移动端/文案/切换全部符合设计,0 新缺陷;scratch 库侧复核:owner(role=owner) 1 行、绑定码已消费(0)、setup_tokens 1、
local_auth_enabled=true。 - 副作用与还原(如实记录):存储 apply 会把
/etc/felis两文件重写为 Go encoder 形态(无注释、含空值键如[archive.s3],值零漂移:kaniko/trivy pin、uploads local、retention 30d、auth_source 原样),并重渲染控制面 Secret + 滚 API——这是该向导的既定行为;演练后按 pre 快照整文件还原(sha256 逐字节一致),两 ns Secret 重渲染复核一致,scratch 库/配置/hba 行/tmux 全部清理。
二、缺陷 #51(工作负载 felis-config 副本永不刷新)
- 发现路径:上条还原核对时发现 minecraft ns 的
felis-config是旧形态(encoder 式)而 felis ns 已是模板式 → 挖出ensureSecretReplica的「绝不覆盖既有副本」(凭据语义:防冲掉手工轮换值)把 felis-config 也纳入只建不更,而 bootstrap 只 apply 控制 ns。后果:改配置后(DB 凭据轮换、[archive] 保留策略调整、root domain 等)backup/restore/fileedit Job 与 reaper 永远读旧副本 → 静默失效(如备份认证失败)。 - 红证据(auditfix43,真机):向 minecraft 副本注入
# drill-51-stale-marker→ 运行felis setup→ 输出- config (minecraft ns): skipped (already exists);副本 sha5ec2ff6e…保持,控制面e4791fe1…不同(陈旧坐实)。 - 修复
328e570:① setup 侧:ensureSecretReplica增refreshExisting(仅 felis-config 传 true)——源缺失降级 skip、内容一致 skip(already current)、不同则原地 Update;凭据类保持 create-if-absent;新增updated结果与「refreshed from the control namespace」文案。② bootstrap 侧:新增apply_felis_config_secrets(),每次运行同时 apply 控制 ns + 工作负载 ns 两份(同渲染自最新 pod toml)。单测:Go 新增 4 例(陈旧刷新/一致跳过/空键补写/源缺失降级);bootstrap_test.sh新增 4 断言(两 ns apply、同一 pod toml 渲染、恰 2 次 apply)。 - 绿证据(auditfix44,真机):A) 安装器重跑(标记仍在副本中)→ 日志出现
secret/felis-config configured(工作负载 ns 被刷新)→ 副本 sha 与控制面一致、标记 0;B) 再注入标记 → 运行新felis setup→ 输出- config (minecraft ns): refreshed from the control namespace、凭据仍skipped (already exists)、副本 sha 一致、EXIT=0。部署=auditfix44(api/operator/reaper),panel 200,host 二进制随镜像docker cp刷新。 - 可达性:#51 ②——升级=重跑安装器、或改完配置跑 setup 即触发;旧行为下 backup/reaper 静默使用旧配置(DB 轮换后备份全挂)。
三、环境修复(非产品):现场 pg_hba 缺 felis_pgint 规则(按文档走 ssh 隧道跑 pgint 会 ident 失败)——补回 host felis_pgint felis 127.0.0.1/32 scram-sha-256 并复测连接成功。
本轮新增真机证据(第三十二批:S3 存储向导逐屏走查收尾 + 缺陷 #52 —— 向导 apply 不刷新工作负载 felis-config 镜像)
一、S3 存储向导屏走查(队列第 2 项,完成;0 功能缺陷,走查自身暴露镜像缺口 → #52)
- 装置:VM 宿主 MinIO 容器(
quay.io/minio/minio,felis/felis-drill-9000,:9000)+ bucketfelis-wizard-uploads;tmux 驱动felis setup重跑向导;行动前先留 preS3 快照(两 toml + 两 ns Secret + sha256)。 - 负例:错误凭据 →
✗ Could not save storage settings.+submit: s3 credentials rejected: The Access Key Id you provided does not exist(CheckS3Access预检先于一切写入);零副作用(无 Secret、两 toml sha 不变);esc返回编辑时已填值保留(密钥掩码)✓ - 正例:修正凭据 → ~10s working →
✓ Object storage configured.→ Enter → 状态屏storage s3://felis-wizard-uploads · http://10.211.55.6:9000✓ - 落地核对:
felis-uploads-s3Secret(access_key_id/secret_access_key);两 tomluser_uploads_context+[registry.s3]endpoint/refs;API 滚动;启动日志仅既有的 smtp/jwks 警告 ✓ - 功能链(batch24 同款):player.test 邮箱 OTP 登录(hook 取码)→
POST /api/v1/me/submissions201 → context 上传 200 → MinIO 桶实见sub-853a4e2ba4ba6443/context.tar.gz→ 内部面取回 200 + tar 内容正确 ✓ - UI 回滚(
s→ Local):两 toml 归位(/var/lib/felis/uploads、[registry.s3]归空)、控制面 Secret 更新、API 滚毕;与 preS3 快照的差异仅「s3 字段回环 + encoder 形态」(注释丢失属该向导既定行为)——值零漂移 ✓ - 观察(不计缺陷):切回 Local 后
felis-uploads-s3Secret 残留(演练按清理流程删除;是否自动清理属产品取舍)。
二、缺陷 #52(向导内 apply 只刷新控制面,工作负载镜像滞后到下一次运行)
- 发现路径:回滚后按计划复核「两 ns 重渲染」——minecraft 副本仍为 S3 内容(
4fb80ed8…),而控制面与两 toml 已 local(df206074…)。 - 红证据(auditfix44):① S3 方向:S3 apply(20:18:44)后副本停在 local 内容(20:22:53 快照 =
e4791fe1…)达 4 分钟;② Local 方向:回滚 apply(20:23:41)后副本停在 S3 内容。副本 managedFields 两笔写入(12:13:58Zkubectl-client-side-apply= 安装器 rerun 的双 ns apply;12:23:06Z managerfelis= 下一次 setup 启动的 refresh)都不是 apply 时刻——apply 本身不碰副本。 - 根因:
applyFelisConfigSecret(storage/connection/edge 三条 apply 的公共出口)只 apply 控制 ns;镜像刷新只存在于felis setup启动(#51)与安装器。email 路径早有显式镜像刷新,storage/connection 是漏网的两条。 - 修复
de7fb2c:镜像刷新移入applyFelisConfigSecret(best-effort + stderr 警告;控制面-only 安装无工作负载 ns 时降级不阻塞);smtp helper 去掉重复块。门禁:gofmt/go vet/go test ./.../bootstrap_test.sh全绿。 - 绿证据(
v0.0.0+fix52,宿主二进制 sha0bd49467…已装/usr/local/bin/felis,旧版留/root/felis-auditfix44.bin;真机双向):Run1 从 Locals→S3:apply 后control = mirror = podtoml = 4fb80ed8…(S3 渲染;旧代码此刻镜像会停在 local);Run2s→Local:control = mirror = podtoml = df206074…;副本 managedFields 写者 =kubectl-client-side-apply@ 12:33:40Z / 12:34:59Z(正是 apply 时刻)。Run1 启动块另见config (minecraft ns): refreshed from the control namespace(#51 机制照常先收敛一次旧账)。 - 可达性:#52 ②——任何
s/c重配置即触发;危害等级低(镜像消费者 backup/restore/fileedit/reaper 当前不读被改动字段——UserUploadsContext仅api.go消费——但「配置动了、镜像没动」正是 #51 要消灭的静默滞后类,且与 email 路径的既定行为不一致)。
三、清理与还原:MinIO 容器/卷/两镜像、felis-uploads-s3 Secret、DB 行(image_submissions sub-853a4e2ba4ba6443)、/tmp 残留(player-cookies/drill-ctx/svc-tok 等)全清;docker 停;收尾 control = mirror = df206074(ALIGNED)、API 滚毕 Running、panel/healthz 200 ✓。
本轮新增真机证据(第三十三批:/updates 维护窗口 API+UI 走查全绿;#53 跟随仓库迁移;#54 update 升级指引全假)
零、仓库迁移(背景):remote 已改 [email protected]:FelisMC/Felis.git(本批两个修复随 2c6739a、397a400 直推 main)。带 token 实测:旧 MliroLirrorsIngenuity/Felis 路径 301(GitHub rename redirect)、新路径 200——旧坐标当前仍能工作但全靠 redirect。新仓库尚无 stable release(releases/latest 带 token 也 404):felis-api 更新检查的 404 属发布流程事实,非代码缺陷。
一、/updates 维护窗口 API 走查(完成;全绿)
- 读:GET 未设置 →
{"not_before":null,"not_after":null}。 - 负例全按预期拒绝:半设 / 倒序 / 相等 / 坏 JSON / 未知字段 → 400;缺
Content-Type→ 415。 - 正例:写入 → 回读一致 → API pod 重启后仍在(已落 DB,非内存态)。
- 鉴权:无 cookie → 401;player 会话 → 403(新铸 player 会话,留
/tmp/player-cookies.txt)。 - 收尾:DB 回到
{null,null}。
二、/updates 面板 UI 走查(CDP,完成;全绿零 console 错误)
- 状态流转逐屏:生效中 → 过期 → 计划 → 未设置(截图
/tmp/updates-{1..5}-*.png)。 - 倒序提交 → 校验文案正确;清除后表单清空 + 「未设置」提示。
- 核账:DB 收尾
{null,null};审计updates.window_set恰 3 笔(20:46:18 / :20 / :21)。 - 驱动:
/tmp/cdp-updates2.js(bun + 原生 WebSocket;须Network.setCookie注入 owner 会话,否则新标签页 401 跳登录)。 - 观察(不计缺陷):窗口自身存取已验证;「窗口被 runner 消费」的端到端链路(Applier/Notifier)仍属 INTEGRATION-ONLY 设计(deferred-seams),不要当缺陷重复修。
三、#53(旧仓库坐标残留)—— 提交 2c6739a
- 红(
v0.0.0+fix52实机):felis update→github: MliroLirrorsIngenuity/Felis releases/latest returned HTTP 404 — …。 - 绿(
v0.0.0+fix53实机):同一命令 →github: FelisMC/Felis releases/latest returned HTTP 404 — …。 - 范围:
internal/updater/topology.go、deploy/bootstrap.sh(默认FELIS_REPO_URL+ 两处 UA)、README ×2、两个测试夹具(6 文件 9 处);门禁四件套全绿。 - 可达性:②——默认安装 / 每次
felis update都读该坐标;危害在 redirect 退休时兑现(安装与更新一起挂)。
四、#54(felis update 的 apply 指引在既有安装上全是错的)—— 提交 397a400
- 红(
v0.0.0+fix52实机文本):--panel --force→run: sudo felis setup+ “felis setup is idempotent and re-runs the installer…” + felis-api「例外」块。 - 决定性证据(完成态安装):
felis setup跑 8s 退出,/opt/felis/velocity/velocity.jarmtime/hash 不变、零 bootstrap 输出;同刻安装器重跑日志有resolving the newest Velocity 3.5.1 build。代码侧shouldRunHostBootstrapBeforeConfig仅在 4 个 marker 不全时进 bootstrap——既有安装上felis setup只开配置 TUI。 - 绿(
v0.0.0+fix54实机;宿主二进制已换/usr/local/bin/felis,fix52 留档/root/felis-fix52.binsha0bd49467…):--panel --force/--velocity --force→run: curl -fsSL https://raw.githubusercontent.com/FelisMC/Felis/main/deploy/bootstrap.sh | sudo bash+ 单条 trailer(channel 未持久化 caveat、私有仓库 token'd form、felis setup is not this path);--mc无命令无 trailer;--alltrailer 恰一次;--force/LatestKnown语义不变。
- 文案同步:
docs/troubleshooting.md §15删掉 “orsudo felis setup”、补 channel caveat;测试改为TestApplyGuidancePointsEveryComponentAtTheInstaller(钉安装器路径 + 禁run: sudo felis setup)。 - 可达性:②(文档/指引;同 #38 型)。
本轮新增真机证据(第三十四批:felis nano 全链走查 60/60;缺陷 #55 —— 坏源静默挡住验证梯子)
零、装置(全部在 VM,不进产品环境):/srv/nanotest/nano-stub.py=可控 Yggdrasil 源,路径即行为——/ok/<name>、/okid/<name>/<id>、/props/<name>、/badname/<case>(tooshort/toolong/badchar/section/noname)、/none=204、/boom=500、/redir=302、/garbage、/emptyid、/slow/<secs>;跑在 127.0.0.1:9900,临时单元 nano-stub,请求日志 /srv/nanotest/stub.log。驱动脚本:nano-matrix.sh(HTTP 矩阵 S1–S7)、nano-service.sh(systemd 层)、nano-config.sh(配置校验)、nano-fw.sh(firewalld);nano-harness.sh 从 HEAD 版 bootstrap.sh 提取产品函数在 scratch 路径跑(felis-nano-test 改名件,不碰产品单元)。
一、HTTP 矩阵 S1–S7(60 项断言)
- 红(修复前
v0.0.0+audit-nano1):SUMMARY pass=47 fail=13——13 红全在 #55 语义内:5 个坏名用例「应 503+日志、实得静默 204」(状态+日志 ×2=10),failover 3(坏源在前登录不落第二源 ×2、无日志 ×1)。留档matrix-red.r2.log。 - 绿(fix55,同装置重跑):
SUMMARY pass=60 fail=0。留档matrix-fix55.r2.log与matrix-fix55/(更早原跑在matrix/)。 - 覆盖面:S1 输入校验(缺参/超长→204、POST→405、未知路径→404、带体 GET→400、HEAD→204、400 带
Connection: close)、S2 三方登录全链(前缀 + felisAuthNS UUID 确定性、ip/serverId 转发保真)、S3 premium 撞名改名(LS_)与免费名不动、S4 失败模式(不可达/500/302/garbage/204 → 跳过、记日志、503)、S5 多源优先级 + 坏源后 failover、S6 日志纪律(长 URI 截断、控制字节转义)、S7 properties 转发。
二、systemd 层(nano-service.sh,真 unit + 真 DynamicUser;重跑落盘 nano-service.r2.log,0 FAIL):装服务→起服务;重跑语义(配置字节不动、resolve_nano_listen 从 unit 读回端点);drain(stop 期间 in-flight 登录照样答完,实测 stop 等待 5335ms);SIGKILL 后自动重启;端点迁移 8081→8099 后新址应答、旧址关闭;三种启动文案(loopback「Bound to loopback」/ CIDR「admits」/ 无 CIDR「WARNING」)。
三、配置层(nano-config.sh,LoadNano 16/16,落盘 nano-config.r2.log):未知键、空/重复/保留 tag、colon、空白、坏 prefix、重复 prefix(大小写不敏感)、非 http scheme、URL 带 query、明文公网 http、缺文件、[server] listen 忽略警告、Mojang-only 可服务。
四、firewalld(nano-fw.sh,3/3,落盘 nano-fw.r2.log):关旧「开全源」8081/tcp、仅对 CIDR 开 rich rule、loopback 不开洞、无 CIDR 警告、快照清理还原。
五、缺陷 #55(提交 9dad61f)
- 红(fix54 实机):配置源回 200 但档案字段不可用(三方源名字进不了 MC 字符集;身份源 UUID 不解析)→ 静默 204、零日志;且筛查在
handleHasJoined直接return→ [坏源, 好源] 顺序下 204,好源根本没被询问。 - 根因:两个 200-后筛查在 handler 层(200 已赢下梯子之后),而同类坏答案(200 无档案/非 200/不可达)在
resolveHasJoined里是「记日志 + skip + failed」——一致性缺口。 - 修复:筛查移入
resolveHasJoined(身份源uuid.Parse、三方源mcUsernameRe)→ 命中记日志(unusable profile name/unparseable profile id)+failed=true+continue;handler 守卫保留为最后防线(注释更新)。单测 +3(含log.Writer()捕获断言)。 - 可达性:#55 ②——需要配置了一个「回 200 但字段不可用」的源(马虎的自建/三方 Yggdrasil);坏源在前时经梯子的登录全部被吞(静默、零日志),安全侧(坏名不落玩家列表)保留。
六、观察(不计缺陷):premium 冷查询 fail-closed 抖动——api.mojang.com 从 VM 首查偶发接近 2s 超时 → 偶按「疑似 premium」给免费名加前缀(FelisNanoStub1 → LS_FelisNanoStub);符合 isPremiumName 既定取舍(误加前缀=外观代价,误放行=抢名),记观察不修。
本轮新增真机证据(第三十五批:NetworkPolicy 真机强制矩阵全绿 —— 端到端白名单闭环 + felis-velocity 刷新存活验证)
零、装置与现场:三张策略 live 于 minecraft ns(felis-default-deny-ingress / felis-allow-rcon-from-control-plane / felis-allow-game-from-velocity;apply 于 09-22T05:37:24Z,与渲染收敛 diff 零漂移);k3s 参数无 --disable-network-policy,强制执行实测生效;kube-router 机制实证:per-pod KUBE-POD-FW-* 链、未标记流量 REJECT --reject-with icmp-port-unreachable、每链首条 --src-type LOCAL -j ACCEPT(本节点豁免)、ipBlock 落 ipset KUBE-SRC-*(白名单成员可直接查)。探测法:nsenter 进真实 pod 网络命名空间(源 IP=真实 pod IP)+ netns/veth 合成「转发型外部源」10.99.0.2(模拟 velocity 在另一台机器);全程不改产品代码。
一、pod 源矩阵(源=真实 pod netns)
- api(felis,命中 RCON 白名单标签)→ lobby:25575 = OPEN;同源 → lobby:25565 = REFUSED(端口级区分 ✓)
- registry(felis,非匹配)→ lobby:25575/25565 = REFUSED;同 pod → api:8081 = OPEN(对照:无策略命名空间不受限)
- coredns(kube-system)→ login:25565 = REFUSED
- 收尾复跑全矩阵与首轮逐行一致(
np-matrix-run1/2.log)。
二、host(=velocity 同机侧):→ login/lobby 的 ClusterIP 与 podIP :25565 全 OPEN(velocity 注册的后端路径实际可用);→ api-internal:8081 OPEN;→ lobby:25575 亦 OPEN——归因:kube-router 每 pod 链的 --src-type LOCAL -j ACCEPT(本节点流量豁免,kube-router 设计行为,kubelet 探针等依赖它;netpol 语义无法对节点自身收口,节点 root 本在 TCB 内)。注记①:node-local 豁免。
三、转发型外部源(最严苛模拟)
- 基线:netns(10.99.0.2) → 全部服务器端口(podIP 与 ClusterIP、25565/25575)= REFUSED,包级可见 netpol 的 icmp-port-unreachable(
td3.log)。 - 白名单闭环:临时把 10.99.0.0/24 加入 allow-game → ipset 即时生效(members:
10.99.0.0/24+10.211.55.6)→ lobby-svc:25565 与 login-svc:25565 = OPEN(ClusterIP=velocity 实际拨号形态);25575 仍 REFUSED(端口维度不破)→ 回滚 → spec 哈希逐字节一致(md5e6b07440…)、ipset 复原、复测全部 REFUSED。 - 层间归因:firewalld 规则含
ct status dnat accept(DNAT 后的服务流量放行;非 DNAT 转发走 forward policy 的admin-prohibited拒绝)——受支持拓扑(velocity 同机=LOCAL、ClusterIP、Mac→NodePort 面板 200)全部实测可用;「外部未经服务直连 podIP」不属于任何产品流。注记②:firewalld 只放 DNAT 服务流。 - 装置保养:veth 未归区时 firewalld 会拒其转发属装置噪声(已归因);public 区临时挂载已摘、netns/veth 已删、策略零残留(spec diff 为空)。
四、felis-velocity 刷新循环存活(顺带验证):20:18–20:42 的 server list refresh failed 全部落在 #52 演练的 API rollout 窗口(成功不打日志属设计);tcpdump 450s 窗口抓到 52 条 GET /api/v1/servers 载荷行(成对出现=双抓包点看到同一请求,折算约 26 次 ≈ 每 15–17s 一次,与 REGISTRATION_REFRESH=15s 常量吻合)及对应 200 响应;"keeping current registrations" 为设计降级。非缺陷。
五、留档(VM /srv/npdrill/):np-matrix.sh+np-matrix-run1/2.log、np-netns.sh、netns-probe.py、np-cidr-test.sh/.log、spec-before/after.json、td3.log(包级归因)、tcpdump-8081.log(刷新存活)、iptables-save.txt 与 nft-rules.txt(现场快照)。
本轮新增真机证据(第三十六批:面板错误文案全量本地化(#60)+ Run4c 管理面写操作复核 0 缺陷)
零、现场:三个缺陷批次(#56–#59)已按「一缺陷一 commit」推 main 并部署 auditfix59;本批完成 #60 后部署 auditfix60(api/operator/reaper 三处 + 宿主 CLI,felis version = v0.0.0+fix60)。面板门禁:typecheck 0 错、vitest 117/117、vite build 通过(注意项目测试是 bun run test=vitest run;裸 bun test 是 Bun 内置 runner,解析不了 @/ 别名,41 个 fail 系假象,勿误报)。
一、Run4c 管理面写操作复核(4 段全真机,最终 0 缺陷;3 处为测试脚本自身误报,均已自纠)
- 镜像添加+删除:添加
registry.felis.svc:5000/e2e/probe:1→ 列表出现、API 落库(added_at=14:50:53Z)✅。删除初测"未生效"系脚本用tr找行——该表是div网格,tr选择器命中 0 → 从未点到按钮。换div[class*=grid-cols-12] → button[title]重测:confirm("确定删除此镜像吗?")自动接受 → 页面 1ms 内刷新、API 侧同 ref 从 5 条降至 4 条——删除通道无缺陷。 - 构建负例:外部 registry(
docker.io/...)+ 不存在 context → 对话框红字拒绝build: invalid request: image reference … must target the internal registry "registry.felis.svc:5000"(截图run4c-3)。脚本"未捕获"系其正则先命中了侧边栏"镜像"二字——误报。 - 创建重复用户:对话框内正确显示 "该用户名已被使用。"、对话框保持打开(截图
run6-1)。此前 run4c 的"dialog-closed"系脚本点错页面(进到了用户详情页);且该轮实际是一次正例:真管理员的 username 是 UUID 串(08595879-…,role=owner是角色名),字面用户名owner当时空闲、创建确实成功——测试件已删(DELETE→200,列表 7→6)。 - 创建重复子域名:正确拒绝 "该子域名已被占用。"(截图
run4c-5)✅
二、缺陷 #60(提交 c9af548):37 个用户可达错误码显示生英文
- 发现路径:run4c 复核
email_taken时做了一次系统性对照——后端newError共 71 个错误码,面板humanizeError只映射 23 个;其余走default分支直接透传err.message(多为 Go 包装嵌套的英文,如invalid server name: naming: invalid server name "BadName": must match ^[a-z0-9-]{3,32}$)。 - 三个代表真机验证(修复前→修复后):
bad_name:新建服务器"名称"填BadName(前端只查非空)→ 前:对话框直出上述嵌套英文;后:"服务器名称不合法:需为 3–32 位小写字母、数字或连字符,且不能使用保留名。"(run8-1)bad_subdomain:子域名填ab(前端 RE 允许 1–2 字符,后端要求 ≥3)→ 后:"子域名不合法:…"(run8-2)email_taken:DB hook 造"他人已验证邮箱" → 账户页真发码(no-mailer 日志取码)→ verify 409 → 后:"该邮箱已在其他账户上完成验证;请直接用该邮箱登录,或换一个地址。"(run9-1)
- 修复:api.ts +37 case、zh/en errors.json 各 +37 键(纯映射,无逻辑变更)。剩 11 个故意不补:
bad_request/conflict/internal/panic/not_found(通用兜底)、forbidden/not_admin/unauthorized(状态码分支已在 default 覆盖)、not_ready(内部面)、setup_required(bootstrap 期 SPA 流控信号,403 文案可用)、unsupported_media_type(CSRF 门,面板永不可达)。 - 遗留观察(不计缺陷):构建负例的英文前缀
build: invalid request:仍会透传——该错误无独立码、message 即最终文案;对管理员可读,记观察。
三、观察(不计缺陷)
- UserDetailPage 对 owner/对自己都显示删除按钮;真的点了会得 403 + 具体 message("the owner account cannot be deleted from the panel"),但前端 403 分支统一显示"你无权执行此操作"——笼统但不算错,记观察。
- 字面用户名
owner可注册(用户名无保留名单;角色由服务端管理、无提权路径),记观察。
四、留档(Mac):脚本 /tmp/cdp-run5.js(镜像删除复测)、run6.js(重复用户复测)、run7/run8.js(bad_name/bad_subdomain 前后对照)、run9b.js(email_taken,内置 ssh 取码);截图 run5-1/run6-1/run7-1/2/run8-1/2/run9-1。VM /opt/felis/src = c9af548 快照。
本轮新增真机证据(第三十七批:管理面交互收尾 0 缺陷 + 缺陷 #61 —— LuckPerms 写操作的过度承诺)
零、现场:auditfix61(api/operator/reaper 三处 + 宿主 CLI)。面板门禁 typecheck/vitest 117/build 全绿后部署。
一、管理面交互收尾(3 项全真机,0 缺陷)
- submissions approve/reject:player 会话(邮箱 OTP 铸造,no-mailer 日志取码)现场造两条 pending(
sub-489eda47fe4bc12d/sub-b1dcc4b4a2cc5f16,各上传 tar.gz context)→ 面板「通过」→ 状态变「审核通过」且自动构建bld-1790176562566802097到 succeeded(点击到构建完成全链闭环);「驳回」→ 对话框必填原因 → 状态变「已拒绝」。全程 UI 零报错。 - 用户会话撤销:player.test 铸 2 条新会话(共 4 条)→ UI「单独撤销」首条 → 精确生效(cookie5 → 401、cookie4 → 200、列表 4→3);「全部撤销」→ 提示「所有会话已撤销。」、列表清空、cookie4 也 401。
- ServerCard 启动交互:面板点 test-one「启动」→
Starting→Running/ready。
二、缺陷 #61(提交 b4ef42d):LuckPerms 页对写操作"过度承诺"
- 发现路径:LP 页写操作交互测试(给
E2E_Tester写权限)→ 历史卡显示绿色 success +「(服务器未返回输出)」→ 落盘取证发现根本没写入。 - 真机实验矩阵(test-one,LP 5.5.85 / H2 存储):
操作 RCON 回包 落盘( lp export实测)面板 UI: E2E_Tester permission set e2e.ui.write.test空 ❌ 面板控制台: E2E_Tester … set probe.test true空 ❌ 面板控制台: <UUID> … set probe2.test true空 ✅ 独立 Python RCON 客户端(绕开 felis): E2E_Tester … set probe3.test空 ❌ 独立客户端: <UUID> … set probe4.test"Another command…"(异步提示) ✅ - 结论:felis 只如实转发空回包;名字写入的静默失败是 LuckPerms 自身行为(独立客户端 1:1 复现)。
- 根因(LP config.yml 官方注释背书):
use-server-uuid-cache: false(LP 默认)→ "commands using a player's username will not work unless the player has joined since LuckPerms was first installed"——未进过服的玩家名永远无法解析(与有无外网无关)。 - 面板问题:旧文案承诺"授予与撤销操作仍然会实际生效"(#59 遗留半句)→ 对"给还没来过的玩家预授权"场景是不成立的承诺。
- 修复(纯文案,双语):
luckperms_no_reply→ "……按玩家名的操作只对「自 LuckPerms 安装以来进过本服」的玩家可靠,对没进过服的玩家名可能静默不生效";luckperms_no_output→ "(服务器未返回输出,无法确认结果)"。真机复验(run16b):读提示与写占位均按新文案显示。 - 可达性:#61 ①——给未进服的玩家预授权是日常动作,此前面板显示绿色成功构成误导。
- 局限(记观察):读提示块只在"读取为空"时渲染;已有部分玩家数据的服上不会露出这段说明(后续增强候选:常驻说明或写前对未解析名字的提示)。
三、观察(不计缺陷)
- LP 命令是串行异步执行:快速连发第二条会得到
§7[§b§lL§3§lP§7]§r §7Another command is being executed, waiting for it to finish...(带颜色代码原文,面板如实显示);lp export回包时有时无(响应与执行解耦)。felis 层无责。 - submissions 徽章(
submissions.json:"审核通过/已拒绝")与过滤标签(admin.json:"已通过/已驳回")两套词,语义均可,记观察。 - 控制台对空回包命令只显示 echo、无占位提示(终端风格);LP 页有占位文案。记观察。
四、留档(Mac):/tmp/cdp-run10.js(approve/reject)、run11a/11b.js(会话撤销)、run12a.js(启动交互)、run13/14/15/15b/16/16b.js(LP 全链)。VM 独立探针:/tmp/rcon_probe.py、/tmp/rcon_one.py(+ /tmp/rcon_pw.txt);LP 导出样本在 /data/plugins/LuckPerms/luckperms-2026-09-23-15-*.json.gz。测试数据:两条 pending 提交(一 approved 一 rejected,构建 succeeded/failed 各一)。VM /opt/felis/src = b4ef42d 快照。
本轮新增真机证据(第三十八批:#56–#59 证据回填 + 服务器详情/运维面收尾 0 缺陷)
零、现场:auditfix61(api/operator/reaper 三处 + 宿主 CLI,felis version = v0.0.0+fix61)。本批两部分:把 #56–#59 四项修复的真机证据落节(此前只存在于 commit message),并把队列里剩余的服务器详情页/运维面交互复跑做完(0 缺陷)。
一、缺陷 #56–#59 证据回填(部署 auditfix59;四项均 = 修复前真机触发 + 修复后复验)
- #56(
0790f8d)玩家管理操作把 RCON 回包扔掉、只报 canned 成功。 触发路径:给"还没进过服"的玩家加白名单/封禁——vanilla 对没见过的名字回That player does not exist并拒绝,而旧面板无论如何都显示成功。修复后回包逐字上屏:whitelist add E2E_Bad→Added E2E_Bad to the whitelist(磁盘同步真写入);负例NoSuchPlayerXYZ→That player does not exist(不再伪装成功);静默服务器回退本地化文案。 - #57(
70c988e)控制台命令的回复无处显示。 触发路径:控制台发任何命令——sendCommand拿得到 RCON 回包但被丢弃、pod 日志也不回显命令输出,等于零反馈。修复后 echo + 回包以终端样式渲染在提示符上方(实测list)。 - #58(
4d4cdd6)无世界盘服务器的文件页 90s 卡死。 触发路径:对从未启动/已回收的服点"文件"页——旧行为建 Job → PodFailedScheduling (pvc not found)Pending 到 90s 超时 → 误导性 504files_timeout。修复:file 路由补上与 backup/restore 同款WorldVolumeExists门 → 快速 409no_world_volume+ 面板双语文案。本批现场复查:resolvecheck(无 world PVC)→ 409no_world_volume,0.03s;test-one(有盘)→ 200(~2s)。 - #59(
a2ff2a1)LuckPerms 页把"读不到"报成"没有"。 触发路径:打开装了 LP 的服的管理页——LP 5.5.85 的lp命令 RCON 回包全为空(独立 RCON 客户端 1:1 复现),读投影永远为空,旧页面却断言"没有父组/没有显式节点"(假事实)、写历史伪造[RCON]行。修复:原始回包随 rosters 同款披露渲染;空回包显式提示、不再假断言;历史占位不再伪造输出。
二、服务器详情页交互复跑(5 项全真机,0 缺陷)
- ServerCard 停止:test-one
Stopping→Stopped(启动在第三十七批;收尾复查desiredState: Stopped)。 - ServerFiles 写流程:编辑 motd → 保存「已保存」→ 重开读回一致(
motd=Felis E2E files drill)→ 还原默认A Minecraft Server(收尾复查确认)。 - 备份/恢复:立即备份 → Job
succeeded;恢复 → 确认对话框(破坏性警告原文)→ Jobrestore-test-oneComplete(5s)→ UI「恢复 27秒钟前 成功」→ 唤醒Running/ready(恢复后的世界可加载)。 - 白名单:加/负例/移除全链复跑(证据见 #56)——终态磁盘只剩
E2E_Tester(88 字节)。 - 封禁/解封:封禁(内联确认)→
Banned E2E_Bad: Banned by an operator.+ 磁盘写入;解封 →Unbanned E2E_Bad;banned-players.json终态[]。
三、运维面复查(3 项,0 缺陷)
/admin/updates复跑(队列"深挖"项):设置窗口 → 「维护窗口更新成功。」+ 状态卡更新;负例 end<start → 前端「结束时间必须在开始时间之后。」、后端 400end must be after start;清除 → 「当前未设置维护窗口」。- metrics 端点:内部面
/metrics正常——felis_*样本 18 条 / 3 个指标族(build 失败计数、回收计数、起服时长直方图)。 - CLI 覆盖核销:
run分发表 17 项(15 个用户面命令 +bootstrap-assets/init-forwarding两个容器内部入口)在历批演练中均已有真机记录,本批逐项核销无遗漏。
四、接口语义注记与收尾
- 文件 API 的
path是相对路径:空串 /.= 根(正常列出)、config下钻正常;字面/被路径约束拒绝(400bad_path: path escapes from parent)——面板从不发绝对路径(joinPath只拼相对段),无用户面影响;本批复查脚本初次误用/时曾见 13s 延迟,属首次 fileedit Job 冷启动,非卡死。 - 收尾静止态:test-one
Stopped;三个名单 = whitelistE2E_Tester/ banned[]/ ops[];E2E_Bad仅余 latest.log 与 usercache.json(日志与 Mojang 缓存)。 - 留档(Mac):脚本
/tmp/cdp-run17a/17b/17c(停止、写文件、还原)、run18/18b/19(备份、恢复对话框、完成等待)、run20a/20b(白名单加/减)、run21/21b/22(封禁重试、封禁、解封)、run23(维护窗口)+同名-out.json;截图run17b-1、run18-1..4、run18b-1,2、run19-1、run20a-1,2、run20b-1、run21b-1、run23-1..3。
本轮新增真机证据(第三十九批:reaper 多节点实机 —— VM 克隆双节点验证)
零、装置:prlctl clone "CentOS Linux 9 Stream" --linked --name felis-node2(linked 克隆,初始 1.4M)→ node2 改 host 名 felis-node2,停用/禁用 felis-velocity、k3s.service(server 形态)、docker,以 k3s-agent 加入同一集群(https://10.211.55.6:6443,复用 node1 node-token)。克隆副作用 = node2 自带 node1 storage 副本(6 项 / 3.1G)——已移开,模拟真实新节点。两节点均 Ready(v1.36.4+k3s1);为克隆,node1 经历一次正常重启,重启后组件/服务全回归(docker 随自启后又停回 inactive)。
一、pin 正向:kubectl create job reaper-b39-ok --from=cronjob/felis-reaper(live 模板原样,nodeSelector=localhost.localdomain;node2 无 taint、是合法调度候选)→ pod 落在 localhost.localdomain(nodeSelector 命中;backups PVC 的 affinity 亦指向同节点——两者本就该同节点),4s 跑通:evaluated=2 reaped=0 warned=0 skipped=0 evicted=0 expired=0、Job Complete。即:渲染出的 pin 在真实双节点集群把 reaper 钉在持盘节点。
二、错位 pin 反向对照:同模板把 nodeSelector 改成 felis-node2 → pod 停在 Pending,scheduler 事件原文:0/2 nodes are available: 1 node(s) didn't match PersistentVolume's node affinity, 1 node(s) didn't match Pod's node affinity/selector——node1 被错位 selector 拒绝、node2 被 felis-backups PVC 的 volume node affinity 拒绝(PV pvc-0b4fbda6-…,nodeAffinity=localhost.localdomain、hostPath=/var/lib/rancher/k3s/storage/pvc-0b4fbda6-…_minecraft_felis-backups)。结论:本部署形态下 pin 写错是 fail-closed(卡住 + 明确调度事件),不会在无世界节点上静默执行;真正要防的是首跑顺序(全新多节点安装时,第一次 reaper 运行会把 backups PV 落在其所在节点)——这正是渲染默认要求 --reaper-node 并 stderr 警告的原因,维持现状不修。
三、装置回收:drill job ×2 删除(minecraft ns 无残留);node2 关机 → kubectl delete node felis-node2 → prlctl delete felis-node2(VM+克隆件删除)。收尾 get nodes = 单节点 localhost.localdomain,felis/游戏 pod 全 Running,CronJob suspend=false + nodeSelector 原样,docker inactive。重启副作用按既有说明处理:Mac 侧 443 入面板依赖的手工 socat 中继(本台账开头注记「重启 VM 后需重开」)随重启消失——已按原样重开(TCP6-LISTEN:443,ipv6only=0,reuseaddr,fork TCP:127.0.0.1:30443)并复核 op.console 面板 / api-me / player 面板皆 200。
四、留档:node2 agent 上线日志(k3s agent is up and running、VXLAN subnet event 来自 10.211.55.6);两向 job 的 pod/调度事件原文(见上);/root/node2-storage-copy/(3.1G,随 VM 删除)。
本轮新增真机证据(第四十批:Java 插件层收口 —— #63 demo-up 单起点、CI Java 门禁、插件面真机 E2E)
零、现场:本批不改 Go/面板(控制面维持 auditfix61);产出 = deploy/demo-up.sh 重写(f5a76cf)+ plugins/test.sh 与 CI plugins 作业(c59b387)。三组真机验证(T1/T2/T3)在 VM 上针对该两文件跑完;CI 首跑即绿。
一、#62 复核:不成立(立案后剔除,未计为缺陷)
- 主张:"fresh 非 demo 安装跳过 Velocity 插件构建 → proxy 静默不路由"。复核三条路径,每条都构建 felis-velocity.jar:
- 源码臂:
felis-install.log(首装)L1530building felis-velocity.jar、L1553staged;bootstrap-auditfix{42,42b,42c,43,43b,44}.log每次重跑同两行。 - 嵌入 tar 臂(fresh release/TUI 路径;本 VM 从未走过):
felis bootstrap-assets game-stack | tar -x→ 37 文件(含plugins/velocity、plugins/shared,构建输入齐全)→docker run --rm -v /tmp/gs62:/src:z -w /src/plugins/velocity gradle:8.14-jdk21 gradle --no-daemon clean build→ BUILD SUCCESSFUL in 35s,产出唯一felis-velocity-0.1.0.jar(71117B,与现装同尺寸)。 - 调用图:
build_velocity_plugin唯一调用点在build_game_stack末尾;build_game_stack在 full 模式主链路(L2868)必经,nano 早返回,不存在可绕开的"demo 分支"。
- 源码臂:
- 结论:代码阅读误判,剔除。真正会跳过插件构建的是 demo-up.sh 的旧镜像臂 → 即 #63(本批修复)。
二、#63(f5a76cf):demo-up.sh 双起源 → 单起点(129 → 63 行)
- 红证据(读取 + 真机口径核对):① 版本分叉——demo-up 硬编码
PAPER_MC_VERSION:=1.21.8,bootstrapresolve_game_jars从 Limbo CI 产物名推导(T3 实测当日 Limbo 2026.0.3-ALPHA / MC 26.3;同一登录两跳必须同协议);② 其镜像臂从不构建 felis-velocity.jar,导入的是本地 tag(felis-limbo:demo等),与 bootstrap 写入的 registry refs 不一致 → 死件、旧基座上可致"起了但无处路由";③ 起 docker 后从不停(违反13d64e0起"构建方以 docker 停收尾"的约定);wiring 段在现代基座上只是 no-op。 - 修复后 = bootstrap(未 SKIP 时)+ 三项硬校验(
felis.host.toml、[velocity]段、felis-velocity.jar)+felis setup交棒;镜像逻辑全删。 - 真机验证:T1 健康基座(
SKIP_BOOTSTRAP=1 SKIP_SETUP=1)→ 通过、exit 0;T2 移走 jar →ERROR: /opt/felis/velocity/plugins/felis-velocity.jar missing — … silently routes nothing …、exit 1(随即还原 71117B root:root 0644);T3 默认臂全量重跑(FELIS_SKIP_FETCH=1 FELIS_INSTALL_MODE=full FELIS_IMAGE=…:auditfix61 FELIS_WORLDS_HOST_PATH=… SKIP_SETUP=1,日志/root/demo-up-b40.log):L733 插件构建、L752staged、L941 交棒行、[fail]=0;收尾态felis-velocity active / k3s active / docker inactive。 - 附带实录(同一日志窗):重跑期间 felis-api 滚动,velocity 00:22:25/00:22:40 两次
refresh failed … keeping current registrations.(失败保旧——规格 §11 承诺的真机上演)→ 00:22:55 恢复注册 → 00:22:58 服务随install_velocity重启并重载插件(新 pid)→ 00:22:59routing ready→ 00:23:29 收敛到 pod IP。
三、Java 层 CI 门禁(c59b387):三个手工测试 + 三个装机 jar 首进 CI
- 缺口:
plugins/*/test三个 main 测试从未被任何 build/CI 运行;velocity/paper/limbo 三个"装机即用"jar 只在 bootstrap/Dockerfile 编译(Go CI 从不碰 Java)。 - 产出:
plugins/test.sh(Maven Central 取 adventure 三 jar,pinned+sha256;三测试 javac+java;三生产编译,limbo 按 bootstrap 同源解析当日版本)+ci.yml新增plugins作业(temurin 21 + Gradle 8.14)。 - 首跑两次真跑抓出两处"从未被跑过"的假设错误:①
InviteCardTest一行式缺 examination-api(adventure-api 4.26.1 的Component签名引用Examinable,javac 编译期即需);② limbo 的com.loohp:Limbo:+永远不可解析(LOOHP 仓无 maven-metadata,404 实查)→ 裸gradle -p plugins/limbo build(plugins/README 原文)从来不可行;二者均已修(脚本 + 该测试 javadoc + README)。 - 验证:VM 容器三测试
OK (32/36/48 checks);velocity 21s / paper 29s / limbo 13s(2026.0.3-ALPHA)全BUILD SUCCESSFUL。GitHub Actions run35888009965(c59b387)= go/shell/panel/plugins 4/4 success(plugins作业首跑即绿)。
四、插件层真机 E2E(不依赖真实客户端的面)
- 自建纯 socket 探针
/tmp/mcprobe.py(status = 服务器列表 ping;login = 登录首包),重装前与重装后各跑一遍,结果一致:- 子域 MOTD(§11 只读缓存 + 相位):
test-one→« test-one » 休眠中,加入即唤醒 / sleeping — join to wake;lobby/login→« … » 在线 / online;resolvecheck→ 休眠中;nosuchxyz.<root>→ 回落A Felis server(非 felis 子域不被劫持)。 - 登录边界:
LoginStart(含现代协议 UUID 字段)→ 服务端首包0x01 EncryptionRequest⇒ 边缘 online-mode 强制成立。 - 加载面:
Loaded plugin felis-link 0.2.0(共 5 插件)。
- 子域 MOTD(§11 只读缓存 + 相位):
- 口径:真实账号进服(limbo 门 → 菜单 → 转服)仍按"进服跳过"决定不演练;以上为不依赖客户端的最大真机面。(首次探测因探针漏发 UUID 字段被静默关闭——探针缺陷,非服务端问题;补齐后一次通过。)
五、观察(不计缺陷)
- ViaVersion 自报有 5.12.0(当前 5.11.0):bootstrap 的 pin 是 FL-007 实测版本,属刻意,不随提示升级。
- limbo 插件编译期一条 deprecated API 提示(
FelisLimboPlugin用/覆写已弃用 API):门禁下可见、不阻塞,留观察。
本轮新增真机证据(第四十一批:装载器 mod 层收口 —— #64 exec 位、#65 元数据、编译+起服双门禁)
零、现场:控制面与面板不动;产出三个 commit:f6048f2(#64 修复)、c2fe6a6(#65 元数据)、aa5abfa(plugins/test-mods.sh + CI mods 作业 + release 双门禁 + README 记录)——这是最后一块"从未被任何自动化碰过"的模块面(fabric / forge / neoforge 三个装载器 mod)。
一、#64(f6048f2):三个 vendored gradlew 缺可执行位——文档正路第一步即失败
- 红证据:
git ls-tree origin/main三个gradlew全为100644(blobb9bb139f…);plugins/README.md第 212–214 行原文教plugins/fabric/gradlew -p plugins/fabric build(forge/neoforge 同式);VM 全新 clone 实跑 →bash: line 1: ./gradlew: Permission denied,exit 126。 - 为何从未暴露:无 CI、无安装路径,README 是唯一入口——直到本批第一次真跑才现形。
- 修复:
chmod +x+git update-index --chmod=+x(mode 100644→100755 ×3)。 - 可达性:①(全新用户照文档走的第一步)。
二、#65(c2fe6a6):mod 元数据与仓库 LICENSE 相悖
- 三处
license = "MIT"(fabric.mod.json、forge/neoforge 的mods.toml)vs 仓库README.md:73的AGPL-3.0-only+ LICENSE 全文。时间线:mods 2026-06-26 加入(93f143f)、LICENSE 2026-07-12 才落地(037eb24)——陈旧残留。 - 另两处
issueTrackerURL = "https://example.invalid/felis"(占位域名)→https://github.com/FelisMC/Felis/issues。 - 用户面:fabric loader 启动打印 license 字段;
mods.toml被两个 loader 解析。 - 可达性:②(低;元数据/合规面,非功能)。
三、双门禁落地(aa5abfa)
plugins/test-mods.sh:JDK 17 下依次跑三个模块的 vendored wrapper(./gradlew --no-daemon build);java 大版本非 17 直接 fail-loud(三模块目标是 Java-17 的 Minecraft 线)。ci.yml新增mods作业(temurin 17 +gradle/actions/setup-gradle@v4,版本由各模块 wrapper 自管);release.yml发布前加两道 Java 门禁(JDK21plugins/test.sh+ JDK17plugins/test-mods.sh)。- README:Status 更新为 compile + boot 双验证;Building 段补 limbo
-PlimboVersion=<release>要求与两个门禁说明。 - CI:run
35892544563(aa5abfa)——mods作业首跑即绿,五作业全过(shell / mods / panel / go / plugins 5/5)。
四、三个模块的真机编译 + 起服 E2E(本批核心证据)
- 编译(VM 容器
eclipse-temurin:17-jdk+ 持久 gradle 缓存):三模块BUILD SUCCESSFUL(首跑 4–5 分钟级/模块),产物felis-{fabric,forge,neoforge}-0.1.0.jar= 29276 / 28855 / 28562 B;留档/root/mods-build-b41.log。 - 起服(三个真实专用服,控制台直驱
/link,随后linkx未知命令对照、stop正常停服):- fabric 1.20.1 + loader 0.19.5 + fabric-api:
Felis link ready; /link is registered.→Done (10.670s)!→/link 只能由玩家执行 / /link can only be run by a player.→Unknown or incomplete command(linkx)→Stopping server;EXIT_fabric_boot=0。 - forge 1.20.1-47.3.0:
Felis link ready; /link will be registered.(modloading-worker)→Done (11.751s)!→ 同四段证据;EXIT_forge_boot=0。 - neoforge 20.4.251(1.20.4):
Done (16.094s)!→ 同四段证据;EXIT_neoforge_boot=0。
- fabric 1.20.1 + loader 0.19.5 + fabric-api:
- 口径:三服均为"装好 mod 后真启动"的场景,同时验证 loader 真实解析修改后的元数据(license=AGPL、tracker URL)后正常装载;完整取码链(玩家在游戏内
/link)按"进服跳过"决定不演练,装载/注册/拒绝面已全部真机成立。 - 留档:
/root/mods-e2e-b41.log、/root/mods-e2e-b41-resume{2}.log、/opt/felis/mods-e2e/(496M,三服目录保留);收尾态:dockerinactive、k3s/felis-velocityactive、磁盘 17G free。
五、演习装置自身两次修正(非产品缺陷,如实记录)
- 控制台驱动脚本
/root/modserver-drive.sh初版把 FIFO 写端先开(exec 3> console)→ 自我死锁(内核wait_for_partner、State: S),java 从未启动(无server.out实证);改exec 3<> console(RDWR 打开不阻塞)后三服全通。 - forge 安装器首跑:
libraries.minecraft.net连接失败 →com.google.code.findbugs:jsr305:3.0.2下载失败、There was an error during installation、无run.sh;原样重试即The server installed successfully(VM 网络抖动,非 forge/产品问题)。
六、观察(不计缺陷)
- 无新增。
本轮新增真机证据(第四十二批:覆盖面对账 + 文档/工具收尾 —— #66 README_EN、§28 图对齐、sync.sh 本地修复)
零、现场:本批不碰产品代码;对"所有模块均已演练"的结论做独立对账(枚举仓库全部模块/交付物 × 台账覆盖),只挖到文档与本地工具层面的残留。commits:62c5a8a(#66 README_EN)、62e8c87(§28 序列图对齐)。
一、覆盖面对账(本批核心,逐项)
- 仓库卫生:
git ls-files全量扫描无.DS_Store/*.log/node_modules/构建产物误入库;顶层felis二进制(83MB)被.gitignore正确忽略;旧仓库名MliroLirrorsIngenuity残留仅存在于台账(历史记录)与本地未跟踪二进制。 - 未竟工作标记:
TODO|FIXME|XXX|HACK全仓(go/ts/sh/md/yml)仅 1 处命中——internal/api/api_test.go:263的context.TODO()(合法测试写法),无未完成实现标记。 - CLI 派发面:
cmd/felis/run.go的 usage ↔ dispatch 表有双向测试强约束(run_test.go,含显式undocumentedCommands允许表:bootstrap-assets/init-forwarding等 in-Pod 入口),机制已核实成立。 - CI 覆盖:
shell作业按 shebang 对全部 tracked*.sh做语法检查(git ls-files '*.sh'逐文件bash -n/sh -n)并运行deploy/bootstrap_test.sh;plugins/mods各自作业。无"存在但从无入口运行"的脚本。 internal/*22 个包逐一对账:0 提及者(internal/apis)为 CRD 类型包(被 40+ 站点引用、随 operator/manifests 演练间接全覆盖);deploy/{limbo,lobby,paper}为三张游戏镜像定义,随每次起服演练执行。真盲区=无。panel全部 17 条页面路由 × 既有批次(CDP 巡检、深挖、写流程)覆盖;scripts/三脚本为本地私有(.git/info/exclude,不入库)。docs/四件:troubleshooting(多批引用)、openapi(含 parity 强制测试)、deferred-seams(内容为现行状态,含 CLOSED/verified-live 注记,抽查与代码一致)、sequence-diagrams(发现漂移 → 修复,见第三节)。
二、#66(62c5a8a):README_EN 与中文 README 漂移
- 红证据:
README.md(中文)"使用方式"含必需的私有仓库 workaround(仓库私有 → 裸 raw 命令 404;token 经curl --config -由 stdin 下发、不进 argv;sudo -E传给安装器)与"重跑=升级 felis-api、通道不继承(跟 main 需FELIS_VERSION_BOOTSTRAP=dev)"两段;README_EN.md是README.md:6链接的英文入口,这两段完全没有 → 英文读者照文档第一步就 404 且无指引。 - 修复:两段译入 EN(保留原命令与语义;
grep校验 token/config/dev 三关键串在位)。可达性:①(轻)——英文首装路径第一步。
三、§28 序列图对齐(62e8c87)
- 漂移点(两处,与修复后代码实读对照):① Claim Transaction 图注仍是 audit #4 前形态(
SELECT EXISTS+ 事务外 pre-check)→ 更新为现行实现:pg_advisory_xact_lock(user_id)+ 行FOR UPDATE+ 事务内四维配额门(图上QuotaAvailable只是 fast path);② Link Flow 更新为:取码FOR UPDATE、同用户重验幂等、退役(soft-deleted)账户链接被当场接管——409 仅对"其他活跃用户"。均以internal/api/pgrepo.go实读 + handler 映射核对。 - 惯例依据:该文件有维护史(
676407d docs(diagrams): align §28 sequence diagrams with implemented routes)。
四、本地工具修复(私有脚本,不入库):scripts/sync.sh 排除 plugins/ 与 go:embed 冲突
- 红证据(本机复现):以 sync.sh 原排除清单 rsync 一个 fresh 目标 →
go build立即失败:bootstrap_asset.go:30:12: pattern plugins/limbo/build.gradle: no matching files found(bootstrap_asset.goembed 了 plugins/{limbo,paper,velocity,shared} 源码;排除清单成文于 embed 引入之前)。存量目标靠 rsync 对排除路径的"保护"而看似正常,fresh 目标必炸。 - 修复:排除项从
plugins/改为plugins/*/build|.gradle|bin/(与.gitignore同义);watch.sh同步去掉 plugins 的忽略。验证:新清单 rsync fresh 目标 →go build绿(83.7MB 二进制产出);两脚本bash -n通过。因脚本被.git/info/exclude私有,修复留在工作机、无 commit。
五、release.yml 静态复核(该流水线从未执行过——仓库当前无任何 tag/release)
- 三处输入/输出契约实核:① 发布资产名
felis-linux-amd64/arm64↔deploy/bootstrap.sh的asset="felis-linux-${arch}"及收敛检查felis ${FELIS_REF}(版本首行契约两侧一致);②out/linux_amd64/usr/local/bin/felis↔ Dockerfile 最终层COPY /out/felis /usr/local/bin/felis;③ 版本断言felis ${GITHUB_REF_NAME}↔cmdVersion的felis %s首行。 - 唯一无法在本机完全复刻的
file(1)机器类型断言:用交叉编译的 linux/arm64 二进制实测输出ELF 64-bit LSB executable, ARM aarch64, …,断言串ARM aarch64匹配(BSD/Ubuntu file 同源 magic)。 - 结论:整条流水线未跑属"尚未切 tag"的发布流程事实(第四十批已注记);切首个 tag 前无待修项。
六、观察(不计缺陷)
internal/api/handlers_account.go:169的 reclaim 选择仍为 Java/Velocity 侧 CODE-ONLY(deferred-seams 已记 accepting)——维持。
本轮新增真机证据(第四十三批:构建 Job 收尸 #67 + #68 上下文拉取重试 + 生命周期/故障注入压测)
零、现场:控制面从 auditfix61 升到 auditfix62(含 #67;自建镜像推入内建 registry,api/operator/reaper 三处滚动,felis version = v0.0.0+fix62)。本批证据 = 6 轮起停循环 + 3 处故障注入 + PG 断连语义 + 并发风暴 + 持续轮询。
一、#67(2755e41):build Job 从不回收——每构建一个、完成 Pod 永久堆积
- 红证据(真机):
felis-build里 5 个完成 Job/Pod 最长 26h 无人回收;全仓 TTL 对照——fileedit 2m / backup 10m / restore 10m / reaper CronJob 3+3 历史,唯独 build 没有ttlSecondsAfterFinished;且build.go:69的注释写着 "(e.g. GC'd); treated as failed"——预期的 GC 从未存在。危害:完成 Pod 计入节点 pod 预算(stock k3s 110),构建量一上来先把节点塞满,新构建全部 Pending;etcd/磁盘同步膨胀。 - 修复:
buildJobTTL = 7 * 24h(终态后计时;日志路由是失败分诊面故取长窗;Sync对终态幂等、JobUnknown只影响非终态 → 删除后的唯一代价是日志 404)。单测TestBuildJobIsReapedAfterCompletion。 - 真机验证(auditfix62):① 真实路径触发构建(owner 会话 →
POST /api/v1/images/build,借遗留 blob 作 context)→ 新 JobttlSecondsAfterFinished=604800、构建 25ssucceeded;② 对旧 Job 打ttl=30s→ 40s 内 Job+Pod 被 TTL 控制器收走(机制实证);③ 旧堆积 5 个里 1 个已收、4 个留存对照。
二、生命周期 ×6 + 三处故障注入(operator / api / postgresql)
- 每轮:CR
desiredStateRunning → 等 Running → Stopped → 等 pod 消失。结果:6/6 全收敛——Running 23–29s(注入轮与无注入轮无差)、Stopped 3s、pod 每轮如期消失;收尾 conditions 无 Failed 残留(Ready=False / RconReached=False = Stopped 正常;Provisioned=True)。 - 注入 1(cycle 2,删 operator pod @t+2s):Running 仍 23s 达成(新 operator 立即接管 reconcile)。
- 注入 2(cycle 4,
systemctl restart postgresql):CR 路径无感;另做定点验证:PG 停机窗口/me= 503(非 401,#11 语义保持)、/healthz= 200(存活探针独立)、内部 status = 200(纯集群读);PG 恢复后/me= 200。 - 注入 3(cycle 5,删 api pod @t+2s):内部轮询出现 2 次
http=000(约 6s 窗口)后自愈;Running 28s 达成。 - 观察(不计缺陷):高频翻杆期间 operator 报 9 条
Operation cannot be fulfilled ... object has been modified(乐观锁冲突 → 重排队自愈),目标时间无差;controller-runtime 正常重试语义,生产低频操作下更罕见。
三、并发风暴 + 拒绝面 + 持续轮询
- 20 路并发 internal status + 10 路
/me:30/30 全 200。 - 无主 + ownerOnly 服 wake:403
forbidden(正确拒绝面,非 5xx)。 - 持续轮询(status + healthz + me,5s 一轮 × 300 轮 ≈ 25 分钟,900 样本):收盘
LONG POLL DONE fails=27——27 个失败样本全部落在三个自导演练窗口内(02:24:15/20 红证据杀 api〔6〕、02:29:48/53 auditfix63 滚动升级〔6〕、02:30:53–02:31:13 #68 确定性复现 scale api→0〔15〕),窗口外 873 样本全 200、零自发失败;每个窗口在动作结束后 ≤1 个探测周期(~5s)内恢复。
四、留档与收尾
- 留档:
/root/soak43.sh+soak43.log、/root/poll43-long.sh+poll43-long.log、/root/mint-owner-43.sh(owner 会话重铸)、/root/felis-image-build43.log(镜像构建)、/tmp/owner-jar43.txt(owner cookie);镜像10.43.182.43:5000/felis/felis:auditfix62已入 registry(e2e/ttl-probe:latest= 本批 drill 镜像,保留)。 - VM 状态:docker 用毕已停(inactive)、k3s/velocity active;控制面三处 = auditfix62。
五、#68(b76d0ac):构建 context 拉取对控制面重启零容忍——一次拒连即终态失败
- 红证据(真机):删 api pod 后触发构建
bld-1790187851749257160→ JobFailed;pod 时间线18:24:12Z起、context-fetch18:24:13Zexit 1,日志原文dial tcp 10.43.237.249:8081: connect: connection refused;Kaniko 从未启动(PodInitializing);新 api pod 11s 后就绪、同 blob 前后各一次构建均 25s succeeded——纯粹"单次尝试"造成的无谓失败。 - 修复(
b76d0ac):fetchContextWithRetry——传输错误/5xx 重试至 45s 窗口(3s 间隔),4xx 快速失败(是答案不是抖动);URL 校验提前为 usage error(2);保留原错误文案前缀。窗口/间隔为 vars(测试可缩窗);4 例单测:5xx 后恢复、拒连后恢复、404 不重试、窗口耗尽(全绿,含-race)。 - 真机验证(auditfix63,确定性优先,不复刻竞态):
scale api→0→ 以复刻 Job 直建(由红证据 Job 派生:换名build-retry68、剥 controller 标签与 selector、镜像改 auditfix63,复用felis-service-tokenSecret)→ fetch 连续 8 次connection refused; retrying(实捕日志)→scale api→1→ fetch 恢复、Kaniko 构建推送、trivy 干净 → Job Complete(18:30:52Z→18:31:24Z,32s);同形场景对照旧版 = 终态 Failed。正常路径回归:api 在线直构bld-1790188306964162960→succeeded(10s),fetch 日志零重试行(重试不引入正常路径开销)。 - 附带发现(运维):本批升级沿用
set image后FELIS_IMAGEenv 仍停auditfix61(两代落后)——构建 Job 的 fetch 容器实际一直在用旧镜像;已把 api/operator 的FELIS_IMAGE与 api/operator/reaper 三处镜像全部对齐auditfix63。真实安装器路径随 manifests 重渲注入该 env,手工升级须成对改(升级清单事项)。
本轮新增真机证据(第四十四批:modpack 规模上下文全链 + 并发构建)
零、现场:控制面 = auditfix63(含 #67/#68;api/operator FELIS_IMAGE 同版本),VM docker 停。本批目标 = 把"真实模组包大小"这条链压到生产尺度:此前所有提交/构建演练的上下文都是 KB 级。
一、200 MiB 上下文全链(零缺陷)
- 构造:5×40 MiB
/dev/urandom载荷 + Dockerfile(随机数据不可压缩 = 最坏情形),tar.gz = 209,750,275 B。 - 上传(真实 app 路由):
POST /api/v1/me/submissions/{id}/context(owner 会话)→ 200,0.44s / ~481 MB/s;宿主核对 PVC 落盘sub-cc160b3ad19080f9/context.tar.gz。服务端读写超时设计(ReadTimeout/WriteTimeout 有意不设、仅 ReadHeaderTimeout)与流式落盘(io.Copy → cappedReader)均按预期,无内存尖峰。 - 批准 → 构建(
bld-1790189227767686191):fetch init 1s(200MB 内部面拉取 + 解压,无重试行)、kaniko 4s(unpack/COPY/snapshot/push gzip 层 209,738,853 B)、trivy 4s(0 findings);Job Complete,全链 13s(18:47:07Z→18:47:20Z)。registry manifest 实查层大小一致(推送非虚)。 - 覆盖点随验:提交构建 Job
ttlSecondsAfterFinished=604800(#67 对真实提交路同样生效);/me/submissionsbuild_status=succeeded(#36 面在 200MB 规模下仍成立)。 - 资源采样(2s 粒度,构建太快可能漏尖峰,标注局限):无 OOM——api 17.9MB / kaniko 33.4MB / registry 34.1MB / trivy 35.9MB;emptyDir+快照为临时占用,随 Job TTL 回收。
- 1 GiB 帽的单测已有覆盖(
internal/submit/submit_test.gooversize→ErrInvalid);帽下真机 = 本条(帽上真机不划算,不做)。
二、并发构建 ×3(零缺陷)
- 三路同时
POST /api/v1/images/build(各自 ref)→ 202×3;三个 Job 均 11s Complete,无串扰:每个 Job destination 与 tag 一一对应(conc-b44-1/2/3 各落位)、ttl=604800各自在。
三、磁盘运维注记(非缺陷)
- 连续镜像构建(docker build)把 VM 根盘从 11G free 压到 4.3G;
docker builder prune -af一键回收 6.5G(回 11G free)。即每轮全量重建约需 5–7G 构建缓存空间;生产主机根盘建议 ≥40G 并定期 prune(如需可在部署文档补一行,本批未改码)。 - 留档:
/root/bigctx.tar.gz(200MiB 上下文原件)、/root/bigctx/、/root/stats44.log、/root/stats44-build.log;提交sub-cc160b3ad19080f9(blob 与user-uploads镜像保留作尺寸样本);镜像e2e/conc-b44-{1,2,3}:latest。
本轮新增真机证据(第四十五批:模组构建链三连修 #70/#71/#72 + 提交→装服全链闭环 + 文档/测试修 #69/#73/#74)
零、现场:控制面 auditfix63→66(每修一版真机重演);本批目标 = 真实用户会写的那种模组包 Dockerfile(FROM <平台底座> + 内容层)。此前所有构建演练都是 FROM scratch——用户真实形态的第一步从未跑过,本批连撞三个缺陷。
一、#70(ac403b9):kaniko 拉底座缺 pull 侧 insecure 标志
- 红证据(真机):提交
FROM registry.felis.svc:5000/felis/paper:demo(bld-1790189685537480076)→ kanikoRetrieving image manifest …后即败:Get "https://registry.felis.svc:5000/v2/": http: server gave HTTP response to HTTPS client。根因:Job 只给了--insecure --skip-tls-verify——kaniko v1.24--help实测二者均只覆盖 push,拉取默认 HTTPS,撞上明文 registry。 - 修复:补
--insecure-pull/--skip-tls-verify-pull(对称补齐;构建命名空间 egress 本就只许 DNS/内建 registry,不扩大可达面)。单测断言两 flag 在参。 - 真机复验:下一次构建
Retrieving image manifest不再报错,进入解包阶段(随即暴露 #71)。
二、#71(6e47730):kaniko 解包底座层被 drop-ALL 卡死
- 红证据(真机):
error building image: error building stage: failed to get filesystem from image: chown /etc/gshadow: operation not permitted——kaniko 以 root 解包 tar 层要把文件 chown 到层里记录的属主(root:shadow 等),drop-ALL 后 CAP_CHOWN/CAP_FOWNER/DAC_OVERRIDE 全无。FROM scratch全用 COPY(属主= kaniko 自己)所以从未暴露;任何真实底座必触。 - 修复:仅 kaniko 容器补回最小能力集
CHOWN + DAC_OVERRIDE + FOWNER(fetch/trivy 保持 drop-ALL 基线;单测断言"恰好三枚"防漂移)。 - 真机复验:解包通过、COPY、推送成功(进入 trivy 阶段,撞上 #72)。
三、#72(b14bacb):trivy 扫 jar 必拉 Java DB——egress 锁下必败
- 红证据(真机):kaniko 全绿后 trivy
FATAL … Unable to initialize the Java DB … failed to download artifact from mirror.gcr.io/aquasec/trivy-java-db:1: connection refused。Java DB 按需下载:镜像一有 jar 就触发——模组包 = jar 集合,故所有真实用户构建都会倒在扫描门;旧构建全 scratch(无 jar)从未触发。 - 修复:新增
[registry] trivy_java_db_repository(→--java-db-repository,与 vuln-DB 旋钮对称);installer carry 白名单收录;§8e 配方补 Java DB 镜像步骤;deferred-seams 更新。 - 真机复验(第 4 次尝试,auditfix66):Job spec 实测含
--db-repository registry.felis.svc:5000/mirror/trivy-db:2 --java-db-repository registry.felis.svc:5000/mirror/trivy-java-db:1;扫描ubuntu 26.04+paper/paper.jar+pebble全 0 漏洞;Job Complete(全链 27s,bld-1790190788310114143)。
四、capstone:提交 → 装服 → 起服,全链闭环(零缺陷)
- 第 4 次尝试产物
user-uploads/sub-c006cbd633317ccb:latest(=felis/paper:demo+ 用户 marker,经完整提交管道构建):- 白名单自动收录(
source=built)→ 产品路由PATCH /api/v1/servers/test-one {"image": …}200(image_not_whitelisted门通过); POST /servers/test-one/wake202 → podtest-one-0Running 1/1(25s 内);kubectl -n minecraft exec test-one-0 -- cat /felis-probe-marker.txt→felis-probe44-ok(用户构建上下文的内容确在运行中的服务器里);- 服务器日志
Done (4.678s)!(paper 完整启动);RCON 线程应答平台就绪探针; - 收尾:stop 202、pod 消失、镜像回滚
felis/paper:demo、desiredState=Stopped。
- 白名单自动收录(
- 口径:这是"玩家上传模组包 → 服主审批 → 自动构建 → 选用为该服镜像 → 起服"的机制全链(真实客户端进服仍按决定跳过)。
五、#69(5cbfa89):README 过度承诺"自动部署"
- 复核(读全):提交数据模型无目标服务器字段;approve→build→白名单是唯一自动化;部署 = 服务器编辑框选镜像(产品路由与 UI 均在);面板文案本身只承诺"自动触发安全构建"。
- 修复:README 中英两行改为"自动构建;产物进入镜像白名单,可直接选用为服务器镜像完成部署"。可达性 ①(轻)。
六、#73(2961beb):bootstrap 测试在跑 k3s 的主机上必假失败(工具级)
- 红证据:VM(真实 k3s 主机)跑
bootstrap_test.sh(原版同现)→FAIL a missing worlds root is warned about:用例拿真实路径/var/lib/rancher/k3s/storage期望 WARN,而该目录在"安装器真正要服务的机器"上必然存在 → 假红;CI 从未见到(runner 无此路径)。 - 修复:warn 情形改用保证不存在的路径;并把
trivy_db_repository/trivy_java_db_repositorycarry 断言补齐。VM 复跑 140 PASS / 0 FAIL。
七、#74(96aa817):CI flaky——console 断连测试读写竞态(工具级)
- 红证据:run
35907662213(纯 README 提交)go 作业红:TestServerConsoleDisconnectTeardown: expected the first event before disconnect, got "";gh run rerun --failed即绿 → 竞态确认。 - 根因:测试只等"源读到第一行"就 cancel,"中继把事件写入响应"尚未发生;cancel 落进缝里时 body 为空。
- 修复:加
firstDataWriter信号(写完成后再 cancel,写→读有 happens-before);本地-count=60与-race ×5全绿;CI 复跑5cbfa89= success。
八、运维注记(非缺陷)
- 升级滚动撞 kubelet ephemeral-storage 压力:auditfix64 滚动时新 api/operator pod
Pending 4m46s(事件untolerated taint(s)),02:58:12 kubelet eviction manager 回收后自动调度成功。诱因 = 构建把根盘压到 89%(docker 构建缓存 + kaniko/emptyDir 临时层);docker builder prune -af两清共回收 ~11G,余 11G free。生产清单:根盘 ≥40G + 升级前清构建缓存(与批 44 注记合并)。 - 中间失败构建留下的 registry 镜像(
user-uploads/sub-83f6…、sub-171e…= 已推未过门;sub-c006…= capstone 产物)留档;测试服test-one已回felis/paper:demo+ Stopped;docker 停。
本轮新增真机证据(第四十六批:上传面 #75/#76 真机闭环 + tracker #8/#1 收口 #77/#78 + 文案 #79 + 发布链 rc smoke)
零、现场:代码三条 commit 先落(43699b4 #77 面板跳转、c57daaf #78 converge、b9ebc87 #79 INERT 文案),随后按 b9ebc87 重建镜像 = registry.felis.svc:5000/felis/felis:auditfix77(v0.0.0+fix77;宿主源 10.43.182.43:5000,docker build → push),控制面 api/operator + minecraft ns felis-reaper CronJob + api/operator 的 FELIS_IMAGE 四处成对齐;/opt/felis/src = b9ebc87 快照(上一版 src.bak46);宿主 drill 二进制 /root/felis-fix77.bin(Mac 侧 GOOS=linux GOARCH=arm64 交叉编译,-X main.version=v0.0.0+fix77)。
一、#75 真机闭环(配额/限流,owner 会话,auditfix77)——留档 /root/probe77/create-lane.log、lane2.log:
- create 失败不烧窗口:坏 JSON → 400,同一秒合法 create → 201(
sub-811427aa7dcf6807)——release路径生效; - create 冷却:同用户 30s 内第二条 → 429
submission_cooldown; - pending 上限:攒到 5 条 pending → 第 6 条 → 403
submission_quota_exceeded; - upload 失败不烧窗口:对已审核行上传 → 409
already_reviewed,同一秒对 pending 行上传 → 200; - upload 冷却:15s 内第二次 → 429
submission_cooldown;等 16s → 200; - 存储预算:把 S2 的 blob 稀疏
truncate到让 owner 已存字节 = 2GiB−100B(blocks=8,不占盘)→ 上传 → 403submission_quota_exceeded;管理员删除 S2(行 + 目录双清)后 → 同一上传 → 200(预算即时释放)。 - 口径:预算按 blob 的实际占用聚合(
Blobs.Size遍历汇总,正是"用久了就超"的同一条读取路径);稀疏垫付只是把"已经存了 2GiB 的用户"这一状态合成出来。
二、#76 真机闭环(撤回 + 管理员删除)——留档 /root/probe77/lane3.log:
- 撤回自己 pending → 200;DB 行 = 0、uploads 目录 = gone;重复撤回 → 404;对已审核 → 409
already_reviewed;对他人 id → 404not_found(owner 判定先于状态判定,不泄露他人行状态); - 管理员删除(pending)→ 200;重复 → 404;行与 blob 双清(lane2 的 S2 同证:
s2 rows=0 dir=gone); - 面板侧 CDP:撤回两步确认(展开行 → 「撤回提交」→「确认撤回」→ 行消失)与 admin 队列每行两步删除(trash →「确认删除」→ 行消失、计数回落)双双走过;截图
/tmp/withdraw-{1,2}-*.png、/tmp/admdelete-{1,2}-*.png(Mac),脚本/tmp/cdp-withdraw76.js、/tmp/cdp-admdelete76.js。 - 真机注记(非缺陷,分级 ZT 的对照实证):admin 面只在
op.console.<root>主机可用——同一 owner 会话在玩家面板主机上is_admin=false(admin 路由 403),在op.console.<root>上is_admin=true(200);面板按要求显示「无权访问」而不是假装能点。
三、#77(tracker #8,43699b4)真机闭环:锁定会话 → /setup
- 铸一个未完成引导会话(SQL:
sha256('drill-locked-77')→sessions,用户3f2c1b0a-…,email_verified=f、无 passkey)→GET /me200、GET /me/submissions→ 403setup_required(后端本就正确); - CDP 用该 cookie 打开
/submissions:页面请求/me/submissions收 403(code=setup_required)→ 自动跳转/setup(location.href实测)→ 向导渲染「初始化你的账户 / 第一步 · 填写邮箱」(截图/tmp/setup8-redirect.png;网络事件 = 200/403(/setup/status 200) 序列在脚本输出里)。 - 顺带确认:Dashboard 首屏三请求(
/me、/me/servers、account/link/start)都是SetupAllowed——所以补丁前用户是在"点受保护操作"时才撞到那句误报「无权执行此操作」。
四、#78(tracker #1,c57daaf)真机演练:felis converge
- 现场先剥后补:
kubectl patch清空 lobbyspec.rcon与 loginspec.startup.healthHTTPPort(复刻老装机 CR 缺后加字段的状态)→/root/felis-fix77.bin converge→ 两条全部填回(rcon{enabled:true,secretRef:lobby-rcon/password}、healthHTTPPort:8080);第二次运行 → 双双already converged(幂等);operator 侧滚动收尾,login/lobby 回 Running/Ready(日志converge-{1,2}.log、crs-before.yaml)。 - 语义边界(单测):非零值一律不碰(运维自换的 secretRef 生还)、缺席 CR 只提示(创建仍归 setup)、占名非系统角色 CR 拒绝收敛、未配镜像跳过。
五、#79(b9ebc87,文档):troubleshooting 的 [INERT] 图例仍写「§12 列着唯一仍适用的字段」,而唯一候选 spec.storage.retainOnDelete 早已移除、§12 自述「每个字段都有 controller 读」。改为「今天没有字段处于该状态」并指向 §13 的移除记录。
六、发布链 rc smoke(tag v0.1.0-rc1 → release run 35949621233):
六、发布链 rc smoke(tag v0.1.0-rc1 → release run 35949621233):首次全绿
- run 步骤实况:go vet/test ✅ →
plugins/test.sh✅ →plugins/test-mods.sh✅(release.yml 史上第一次真正跑这三关)→ buildx 双架构构建 ✅ → stamp 校验 ✅(felis v0.1.0-rc1+ arm64 ELF 断言)→ publish ✅; - 产物释出:
felis-linux-amd64(61,378,722 B)与felis-linux-arm64(57,344,162 B)双资产,release 标记prerelease=true; - prerelease 语义复核:
GET /repos/FelisMC/Felis/releases/latest→ 404(RC 没有顶掉 latest——正是 release.yml 注释里防的那件事);bootstrap 默认通道在无 stable 时按设计给出显式指引后 die、felis update的 404 报错可读——两个行为本批均实测; - 产物级复验(目标架构实机):arm64 资产 scp 到 VM 执行 →
felis v0.1.0-rc1(go1.26.8 linux/arm64),且直接可用:/root/felis-rc1.bin converge打现集群 → 双服already converged; - 留档:
/root/felis-rc1.bin;asset 副本 Mac/tmp/rc1b/felis-linux-arm64。 - 剩余(产物决定,未代拍板):仓库仍无 stable release → 新装走默认 release 通道会以指引性报错 die(提示改 dev 或等 stable);切首个稳定 tag(如
v0.1.0)即让默认通道与felis update真正上线,建议维护者择时执行。
七、运维注记(非缺陷)
- pg_hba 的
host felis_pgint felis 127.0.0.1/32 scram-sha-256行再次丢失(批 31 补过一次)——补回并 reload 后 pgint 才能连。重装/动过 PG 后先查这条(已写进速查)。 - 升级域注记:#75/#76 的 pgint 新断言(
CountPendingSubmissionsBy、DeletePendingSubmission/DeleteSubmissionCAS)首次上真 PG:17/17 全绿。 - 根盘:镜像构建后 83% →
docker builder prune -af回收 3.5G,docker 停回 inactive。 - CI:
43699b4success;c57daaf被后一 commit 的并发策略取消(同分支 cancel-in-progress),其树被b9ebc87的 success 完整覆盖;b9ebc87success。 - tracker 收编:关 #10/#20/#21/#22(#20→
d9246dd、#21→f5a76cf、#22→3f2b28d、#10→23792d6,均附证据评论)+ 关 #1/#8(本轮实现并真机验证);注记(保持打开作老装机待办)#2/#3/#4;留存 #9/#12/#13/#15(真增强,超出生产可用主干,未动)。
本轮新增真机证据(第四十七批:首个稳定版 v0.1.0 发布链全实弹 + release 通道无 checkout 全量安装 + felis update 双向报告)
零、现场:切首个稳定 tag v0.1.0 → b9c97ff(annotated),release run 35950722509 全绿,产物 felis-linux-amd64 61,378,722 B / felis-linux-arm64 57,344,162 B;GET /releases/latest 现解析到 v0.1.0(prerelease=false),v0.1.0-rc1 保持 Pre-release。VM 侧事件前快照 /root/probe77/pre77/(host toml + deploys + crs + hostbin.sha=106c4c9e…);引导函数副本 /root/probe77/bootstrap-funcs.sh(删 main "$@" 行、可 source)与完整版 /root/probe77/bootstrap-full.sh(sha256 89d0181d… = 仓库 deploy/bootstrap.sh 逐字节)。
一、Stage 1 — release 二进制下载(真实 github_api + asset + 原子安装):
resolve_install_ref(release 通道)→REF=v0.1.0;download_release_binary:宿主二进制felis v0.0.0+gunknown(sha106c4c9e…)→felis v0.1.0(shaa64f32c5…,57,344,162 B 与 arm64 资产一致);- 复跑收敛:
host binary is already v0.1.0; skipping the download(零 API、零下载)。留档stage1.log、stage1-release-download.sh。
二、Stage 2 — 无 checkout 全量安装(真实 curl|bash 形态;本批主线):
- 配方:
systemctl stop felis-velocity→mv /opt/felis/src src.bak47(全程无 checkout)→bash < bootstrap-full.sh(stdin 形态),FELIS_IMAGE=…/felis/felis:v0.1.0、FELIS_WORLDS_HOST_PATH=/var/lib/rancher/k3s/storage(对齐在册 reaper 形态); - 结果:rc=0、2m17s、
[fail]=0、零[warn](留档stage2.log、stage2-run.sh)。 - 关键路径逐条为真:
use_release_binary命中 → 跳过下载(HAVE_PREBUILT_BINARY=1)→build_image_from_binary(宿主二进制裹 distroless 镜像并导入 k3s)→game_stack_source走unpacking the embedded game-stack sources (no checkout on this host)(二进制内嵌 tar 解包)→ limbo/lobby/paper 构建(Paper 26.3-38)→ 四镜像全 mirror 进内部 registry(实测 tags:felis增v0.1.0、limbo/lobby/paper =demo)→ api/operator 收敛到felis:v0.1.0且 rollout 全绿 → minecraft nsfelis-reaperCronJob 模板同步为felis:v0.1.0→ 既有系统服 login/lobby 滚到新镜像 Running。 - 数据面复验:
users=16、servers=2(resolvecheck/test-one)不变;面板https 200;/etc/felis/felis.host.toml与事件前快照逐字节一致(手改保留承诺实测);bootstrap.done更新至03:32:18Z。 - 收尾:
src.bak47复原为/opt/felis/src;docker 停回 inactive。
三、Stage 3 — felis update 报告(双向对照):
v0.1.0-rc1二进制:felis-api v0.1.0-rc1 -> v0.1.0 update available (notify);velocity3.5.1 -> 4.2.0 (notify);附 apply 命令(重跑安装器 + 私仓 token 指引文案);- 现装
v0.1.0二进制:felis-api v0.1.0 up to date——锤实「felis-api 已装版本 = 运行中二进制自身版本」(panel 内嵌,无独立版本可探); - velocity minor(3.5.x→4.2.0)按设计走 notify;installer 只取 pinned minor 的最新 build。
四、运维注记(非缺陷)
- pg_hba
felis_pgint行又被重装清掉(第三轮)——根因定位:前两轮的补回位置落在# BEGIN/END FELIS MANAGED HBA块内,重写段的 skip 对块内照删;本轮改为插在# END FELIS MANAGED HBA之后(块外,清理条件$2==felis不命中),下轮重装可复检;速查已注明正确插入位。 - 控制面镜像引用:
auditfix77→felis:v0.1.0(release 二进制包裹);回退面 = 旧auditfix77仍在宿主 docker 与 registry;registry 的v0.1.0tag 保留为 kubelet 回拉源。 - 演练后 VM 复位:docker inactive、k3s/felis-velocity active、test-one 仍 Stopped。
五、结论:stable release 通道端到端闭合——「tag → CI 双资产 → /releases/latest → 无 checkout 全量安装(embedded 资产 + registry mirror)→ 控制面/游戏栈全绿 → felis update 报告」全链实弹,无新缺陷。
结论:离"生产可用"还差什么(按优先级)
构建链路的上下文通道✅ 已修(f79e5eb/02fd2de,真机全链路含拉回校验;Trivy DB 需按 §8e 镜像一次)。镜像耐久✅ 已完成(第二十九批:a9b275a/13d64e0/fa0e8d7+c7e585e;三次真机重跑 + GC 两演练——控制面与游戏镜像被 GC 后自动回拉;同批修复并复验 #46/#47/#48/#49)。PG 级契约测试✅ 已落地(2a55a0d):internal/pgint(-tags pgint,需FELIS_TEST_PG_URL指向名字含pgint的库,harness 会 drop schema + 重放真实迁移)已覆盖会话/OTP/op-login/绑定码/submission/build/owner 角色;首跑即抓到 #20(索引与 ErrEmailTaken 从未存在)。运行方式见 CONTRIBUTING.md。面板把 /jobs 显示出来✅ 已完成(97a64c8,备份页「最近操作」卡,正是它把 #35 暴露出来的)。多节点回收✅ 已修(daf7602:--reaper-node→ CronJob podkubernetes.io/hostnamenodeSelector,真机 render/dry-run/收敛 diff 三连;单节点部署不传即维持原状)。附带核销缺陷 #38(渲染提示里过期的 uid-1000/setfacl 指导)。告警✅ 已完成(94f71ee+43df08b+ 第二十七批实弹演练:真实构建失败 → pending → 08:33:14Z firing;规则随deploy/alerts/交付)。玩家可见的构建结果✅ 已修(72c4aa3,缺陷 #36:列表路由附build_status/build_error,面板「我的提交」展开行呈现,真机双例验证)。面板文件编辑器入口✅ 已补(0a36b3f,缺口补齐,真机 CDP 全链)。fleet 系统服务死操作✅ 已修(2f90851,缺陷 #37)。S3 上传通道演练 + 存储向导逐屏✅ 已演练(第二十四批:上传通道全链、0 缺陷;第三十二批:向导屏逐屏 + 上传取件 + UI 回滚,0 功能缺陷并修 #52)。breakGlass 控制台逐屏✅ 已演练(第二十五批:菜单 4 操作 + bootstrap 分支,缺陷 #39–#43 全修全验)。✅ 重跑侧已逐屏(第二十六批),首装侧连续走查也已完成(第三十一批:scratch 库单次连续走完全部屏幕 + 轨道回顾,0 缺陷)。felis setup全屏向导逐屏✅ 已走查(第三十四批:HTTP 矩阵红 13→绿 60/60;systemd/配置/firewalld 三层重跑全绿;同批修复 #55——坏源静默挡住验证梯子)。felis nano全链NetworkPolicy 真机强制矩阵✅ 已演练(第三十五批:pod 源端口级矩阵全绿、转发型外部源「拒→放→拒」白名单闭环、host/ClusterIP/NodePort 路径全通;两条已归因注记——node-local 流量豁免、firewalld 仅放 DNAT 服务流)。面板错误文案全量本地化✅ 已修(第三十六批:#60,37 个用户可达码补齐双语映射,三类真机验证;管理面写操作 Run4c 复核 0 缺陷)。管理面交互级收尾(submissions / 会话撤销 / 启动)✅ 已演练(第三十七批:点击→构建→succeeded 全链、单条/全部撤销精确验证;同批修 #61——LP 写承诺按 LP 官方语义收窄,真机复验)。缺陷 #56–#59(玩家管理回包 / 控制台回显 / 文件页 fail-fast / LP 诚实化)✅ 已修并归档(第三十八批回填真机证据:修复前触发 + 修复后复验,四项全绿)。服务器详情与运维面收尾✅ 已演练(第三十八批:ServerFiles 写流程、备份/恢复全链、白名单/封禁闭环、ServerCard 停止、/admin/updates复跑、metrics、CLI 核销——0 缺陷)。reaper 多节点实机✅ 已演练(第三十九批:VM 克隆双节点 k3s——pin 命中持盘节点并跑通、错位 pin fail-closed Pending、装置全回收;felis-backupsPVC 的 volume node affinity 构成第二层保险)。Java 插件层自动化缺口✅ 已补(第四十批:plugins/test.sh+ CIplugins作业——三个手工测试与三个装机 jar 全进门禁,CI 4/4 绿;首跑抓出两处"没跑过"的错误:InviteCardTest 编译类路径缺 examination-api、limbo+版本不可解析。同批关闭 demo-up 单起点化 #63、并复核剔除 #62)。装载器 mod 层(fabric/forge/neoforge)✅ 已收口���第四十一批:#64 三个gradlew补可执行位、#65 元数据对齐 AGPL-3.0-only;plugins/test-mods.sh+ CImods作业 + release 双门禁;三真实专用服起服 E2E——mod 装载、/link注册、控制台拒绝全绿。CI run35892544563= 5/5,mods作业首跑即绿)。全仓覆盖面对账 + 文档收尾✅ 已完成(第四十二批:22 个 internal 包 × 面板 17 条页面路由 × 全部 tracked 脚本 × 仓库卫生逐项对账,无盲区;#66 README_EN 修复;§28 两张序列图对齐现行实现;本地scripts/sync.sh排除清单与go:embed冲突——修复+复现验证〔脚本私有,不入库〕)。构建层资源回收✅ 已修(第四十三批:#67 build Job TTL——完成 Job/Pod 不再无限堆积;真机:新构建 Jobttl=604800+ 旧 Job 打 30s TTL 实测被 TTL 控制器收走。同批:6 轮生命周期循环 + operator/api/pg 三处注入全绿、PG 503 语义复验、30 路并发全 200)。构建上下文拉取的短暂断连✅ 已修(第四十三批追加:#68——fetch-context 有界重试〔45s 窗口 / 3s 间隔;4xx 不重试〕;真机:api 停机期连续 8 次拒连全部重试、恢复后构建 32s Complete;正常路径无重试开销。附:演练升级暴露的FELIS_IMAGE两代错位已对齐 auditfix63)。modpack 规模上下文全链 + 并发构建✅ 已演练(第四十四批:200MiB 上下文上传 0.44s → 批准 → 构建全链 13s、三路并发构建全绿;TTL 与玩家可见面在规模下复验,零缺陷。见该批节)。内建底座构建(✅ 已修并真机闭环(第四十五批三连修:#70 拉取缺FROM registry.felis.svc:5000/…)--insecure-pull、#71 drop-ALL 卡解包、#72 trivy Java DB 未镜像——"真实模组包"形态的三个必踩点;修复后 paper 底座构建 27s Complete、paper.jar扫描 0 漏洞)。提交 → 装服 → 起服 闭环✅ 已演练(第四十五批 capstone:构建产物 PATCH 为服务器镜像〔白名单门通过〕→ wake → Running 1/1 → 用户 marker 可读 +Done (4.678s)!;test-one 已复原为 paper:demo/Stopped)。文档与测试工具面✅ 已修(第四十五批:#69 README 部署承诺对齐实现;#73 bootstrap 测试密闭化〔VM 140 PASS〕;#74 console 断连测试消抖;CI 全绿)。提交上传面的上限与生命周期✅ 已修并真机闭环(第四十六批:#75 per-user create/upload 冷却〔429〕、pending ≤5〔403〕、2GiB 存储预算〔403〕;#76 撤回〔owner+pending CAS〕与管理员删除〔行+blob 双清〕;面板两步确认 CDP 全绿)。未完成引导的导航(tracker #8)✅ 已修并真机闭环(第四十六批 #77:403 setup_required→ 自动/setup;CDP 复验)。已装机系统服的新增 CR 字段(tracker #1)✅ 已修并真机闭环(第四十六批 #78:sudo felis converge——零值才填、非零不覆写;剥字段→填回→幂等三连真机过)。troubleshooting✅ 已修(第四十六批 #79,文档级)。[INERT]图例稳定版发布与 release 安装/升级通道✅ 已实证(第四十七批:tagv0.1.0(b9c97ff)+ release run35950722509双资产、/releases/latest解析到 v0.1.0;无 checkout 全量安装 2m17s / 零 fail / 零 warn、registry mirror 四镜像、reaper CronJob 对齐felis:v0.1.0;felis update双向报告〔rc1→v0.1.0 notify / v0.1.0 up to date〕)。
剩余待演练队列(截至第四十三批)
reaper 多节点实机✅ 已完结(第三十九批:临时克隆第二节点组双节点 k3s 实机——pin 命中持盘节点并跑通、错位 pin fail-closed Pending;装置已回收)。面板交互级收尾✅ 已完结(第三十六–三十八批:管理面写操作、submissions/会话、服务器详情页全链,0 缺陷)。✅ 已完结(第四十批:单起点化 #63——bootstrap 装机 + 三项硬校验 +demo-up.shfelis setup交棒;T1/T2/T3 真机全绿)。Java 插件层自动化✅ 已补(第四十批:plugins/test.sh+ CIplugins作业;三个手工测试与三个装机 jar 首进 CI)。装载器 mod 层(fabric/forge/neoforge)✅ 已收口(第四十一批:#64/#65 修复 +plugins/test-mods.sh/CImods/release 双门禁 + 三真实专用服起服 E2E)。全仓覆盖面对账✅ 已复核(第四十二批:模块×台账逐项对账无盲区;批次产出为文档/本地工具层面的修复,见该批节)。长稳/故障注入(首轮)✅ 已完成(第四十三批:6 轮起停循环 + 3 处注入 + PG 断连 503 + 30 路并发 + 25 分钟持续轮询;见该批节)。modpack 规模上下文 + 并发构建✅ 已完成(第四十四批:200MiB 上下文全链 13s + 并发 ×3 全绿;见该批节)。提交 → 装服 全链(含平台底座 FROM)✅ 已完成(第四十五批:三连修 #70/#71/#72 + capstone 提交→装服→起服;见该批节)。
队列现已清空。(真实游戏客户端进服已按决定跳过,用内部 mint/approve hook 链替代;不依赖客户端的最大真机面见第四十批节 + 第四十一批节〔装载器三服起服〕;第四十二批为覆盖面对账复核。)
系统性观察
- PGRepo 与接口契约/fake 漂移(#16/#17/#18):
fakeRepo与接口注释是对的、PG 实现在细节上落后,单测全绿也发现不了。建议后续引入 PG 级契约测试(testcontainers 或针对关键写路径的集成测试),重点覆盖「接口注释承诺了字段/错误码/生命周期」的方法。 - 部署知识:交叉编译必须先把
panel/dist拷进internal/panel/static再go build,否则镜像内 SPA 缺失(页面显示 “assets were not built”)。本批镜像为felis:auditfix7。
复现入口速查
- 面板会话 cookie:
/tmp/owner-cookies2.txt(2026-09-23 铸;auditfix42 部署后仍有效;staff 邮件门被设计拒绝 → 走第二十八批的 op-login + 内部面 approve hook 链);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:控制面(api/operator/reaper +
FELIS_IMAGE)=registry.felis.svc:5000/felis/felis:auditfix61(含至 #61 的全部修复;镜像托管于内建 registry);系统服/推荐 ref =registry.felis.svc:5000/felis/{limbo,lobby,paper}:demo;源码快照/opt/felis/src(b4ef42d;上一版在/opt/felis/src.bak,更早src.old43);host 二进制 =/usr/local/bin/felis=v0.0.0+fix61(含 #53–#61;旧件留档/root/felis-fix54.bin(shaa0b29153…)、/root/felis-fix52.bin、/root/felis-auditfix44.bin);docker 守护进程在第三十七批构建后已停(用前sudo systemctl start docker);world 执行器以 root+DAC_OVERRIDE 运行;reaper CronJob(minecraft ns)suspend=false / …auditfix61 / nodeSelector=localhost.localdomain;迁移schema_migrationsmax=21;SMTP 密码 env 待「configure email」刷新时落地 - 安装器重跑配方(第三十批复用;先同步
/opt/felis/src再跑):cd /root && FELIS_SKIP_FETCH=1 FELIS_INSTALL_MODE=full FELIS_IMAGE=registry.felis.svc:5000/felis/felis:<tag> FELIS_WORLDS_HOST_PATH=/var/lib/rancher/k3s/storage nohup bash /opt/felis/src/deploy/bootstrap.sh > /root/bootstrap-<tag>.log 2>&1 &;完成后grep -c '\[fail\]'应为 0 - 第三十一批 drill 留档:
/root/preTUI43/(首装走查前快照:两 toml + 两 ns Secret + sha256、走查后 Secret 快照)、/root/pre51-replica.toml/post51-replica.toml/post51b-replica.toml(#51 红/绿副本证据);走查残留(scratch 库、scratch 配置、hba 行、tmux 会话)均已清理 - 第三十二批 drill 留档:
/root/preS3/(S3 走查前快照:两 toml + 两 ns Secret + sha256 + deploys.txt)、/root/s3wiz/(posts3 快照、postroll-sha256、fix52-red.txt、fix52-green.txt)、/root/felis-fix52(含 #52 的宿主二进制);MinIO 容器+卷+两镜像、felis-uploads-s3Secret、DB 行(image_submissionssub-853a4e2ba4ba6443)、/tmp 残留均已清理,docker 已停 - 第三十三批 drill 留档(VM):
/tmp/felis-fix53、/tmp/felis-fix54(宿主二进制;sha9afd141d…/a0b29153…)、/root/felis-fix52.bin;/tmp/player-cookies.txt(player 会话);面板 CDP 驱动脚本/tmp/cdp-updates2.js(Mac 侧) - 第三十四批 drill 留档(VM):
/srv/nanotest/(nano 装置:nano-stub.py+ 5 驱动脚本 +matrix/红原跑 /matrix-fix55/绿原跑)与四层重跑落盘matrix-red.r2.log(pass=47 fail=13)/matrix-fix55.r2.log(pass=60 fail=0)/nano-service.r2.log/nano-config.r2.log/nano-fw.r2.log;nano-stub临时单元仍在跑(收尾systemctl stop nano-stub);宿主二进制/usr/local/bin/felis-nano-test= fix55 改名件(sha2c8c34fd…,与/usr/local/bin/felis同物) - 第三十五批 drill 留档(VM):
/srv/npdrill/(netpol 装置全套:np-matrix.sh+np-matrix-run1/2.log、np-netns.sh、netns-probe.py、np-cidr-test.sh/.log、spec-before/after.json、td3.log(包级归因)、tcpdump-8081.log(刷新存活)、iptables-save.txt与nft-rules.txt);netns/veth、firewalld 临时挂载、策略改动全部已清理/复原(spec md5e6b07440…前后一致) - 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:<pw>@localhost:15433/felis_pgint?sslmode=disable' go test -tags pgint ./internal/pgint/ -count=1(pg_hba 需host felis_pgint felis 127.0.0.1/32 scram-sha-256;第三十一批把现场丢失的这条补回;隧道用完即 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/trivy pin 指向内建 registry 的 mirror 路径(registry.felis.svc:5000/mirror/...,见 §8e)——本批后安装器重跑会 carry 这些 pin(不再被写盘冲掉;#49 复验点);直构一行:POST /api/v1/images/build(context=http://felis-api-internal.felis.svc.cluster.local:8081/api/v1/internal/submissions/sub-bf7dc18e9dd97dc2/context) - registry 工具:
curl -s http://127.0.0.1:5000/v2/_catalog、/v2/<repo>/tags/list;取 manifest 必须带Accept:(OCI index/manifest list;缺 Accept 的 GET 会 404,别误判没推上);GC 演练配方:k3s ctr images rm <ref>(要清到 blob 级再加k3s ctr content prune references)→ 删 pod /rollout restart→kubectl describe看Pulled … in …ms - setup 重跑向导(第二十六批):状态屏
c/s/e三流已走查;reconfigure 非只读——storage 选 Local / 提交 S3 即 apply + 滚 API(幂等),email 表单 esc 无副作用;从 S3 表单 esc 退回会重置为 Local 预选;存储屏(第三十二批补充):chooser 预选当前值(↑↓ 切换),Local 直接 enter 提交、S3 五字段enter next/末字段enter submit,提交后 10–40s(含 API rollout status 180s 上限) - VM 内部面:
k3s kubectl -n felis port-forward svc/felis-api-internal 18081:8081(Pod 重建后转发会悬死,需重启);reaper CronJob(minecraft ns)已应用且已收敛(suspend=false) - 第三十八批收尾复查:docker
inactive;控制面三处 =auditfix61;/opt/felis/src=b4ef42dtarball 展开(无.git);reapersuspend=false+nodeSelector=localhost.localdomain;test-oneStopped;resolvecheck文件接口 409no_world_volume(0.03s) - 第三十九批 drill(已回收):临时 VM
felis-node2(prlctl clone --linked克隆 node1;node2 停用 felis-velocity/k3s-server/docker 后以 agent 加入)——流程与两向对照见该批节;node1 为克隆经历过一次正常重启(组件全回归、docker 停回inactive);收尾 job/节点对象/VM 全部删除,集群回到单节点 - 第四十批工具与留档:VM
/tmp/mcprobe.py(纯 socket MC 探针:python3 /tmp/mcprobe.py status <sub>.<root>;login <sub>.<root> <name>打登录首包);插件门禁跑法 =docker run --rm -v /opt/felis/src:/src:z -w /src gradle:8.14-jdk21 bash plugins/test.sh(或宿主bash plugins/test.sh,需 JDK 21 + Gradle 8.14;日志/root/plugins-test-b40.log);T3(demo-up 默认臂全量重跑)日志/root/demo-up-b40.log;/opt/felis/src=c59b387快照(上一版src.bak40;此后全量重跑用sudo bash /opt/felis/src/deploy/demo-up.sh即可,SKIP 开关见脚本头);CI run35888009965(含新plugins作业首跑绿) - 第四十三批工具与留档:控制面三处 =
registry.felis.svc:5000/felis/felis:auditfix62(含 #67;镜像源10.43.182.43:5000/felis/felis:auditfix62,宿主 docker build 后docker push 10.43.182.43:5000/...);/opt/felis/src=2755e41快照(上一版src.bak42);owner 会话重铸 =bash /root/mint-owner-43.sh(cookie 落/tmp/owner-jar43.txt);直构触发(含 TTL 检查)=curl -sk -b /tmp/owner-jar43.txt -X POST https://127.0.0.1:30443/api/v1/images/build -H 'Content-Type: application/json' -d '{"image_ref":"registry.felis.svc:5000/e2e/ttl-probe:latest","dockerfile":"FROM scratch\nLABEL felis=x\n","context_ref":"http://felis-api-internal.felis.svc.cluster.local:8081/api/v1/internal/submissions/sub-bf7dc18e9dd97dc2/context"}'→kubectl -n felis-build get job build-bld-<id> -o jsonpath='{.spec.ttlSecondsAfterFinished}'应为604800;压测编排/root/soak43.sh(日志soak43.log,含 6 轮循环 + 3 注入)、长轮询/root/poll43-long.sh(日志poll43-long.log);镜像构建日志/root/felis-image-build43.log。注意:lobby/login是保留名,internal per-server 路由(status/wake 等)对它们按设计返回 400,勿作缺陷误报。 - 第四十三批追加(#68)留档:控制面 api/operator/reaper 镜像 + api/operator
FELIS_IMAGE=registry.felis.svc:5000/felis/felis:auditfix63(felis version=v0.0.0+fix63;升级时set image与FELIS_IMAGEenv 必须成对改,否则构建 Job 的 fetch 容器会悄悄用旧镜像);/opt/felis/src=b76d0ac快照(上一版src.bak43=2755e41);复刻 Job/root/job68-new.json(由红证据 Jobbuild-bld-1790187851749257160派生:换名build-retry68、剥 controller uid 标签与 selector、镜像改 auditfix63、复用felis-service-tokenSecret);重演验证法:kubectl -n felis scale deploy/felis-api --replicas=0→kubectl -n felis-build create -f /root/job68-new.json→kubectl -n felis-build logs <pod> -c context-fetch(应见connection refused; retrying)→scale --replicas=1→ Job 收敛Complete;对照:api 在线直构成功且 fetch 日志无重试行。 - 第四十四批工具与留档:200MiB 上下文构造 = 5×40MiB
head -c 41943040 /dev/urandom+Dockerfile(FROM scratch+COPY payload /payload),tar -czf /root/bigctx.tar.gz Dockerfile payload;上传 =curl -sk -b /tmp/owner-jar43.txt -X POST -H "Content-Type: application/gzip" --data-binary @/root/bigctx.tar.gz https://127.0.0.1:30443/api/v1/me/submissions/<id>/context;采样 =k3s crictl stats(此版 无--no-trunc,列解析取$2=NAME $4=MEM);docker 缓存清理 =systemctl start docker && docker builder prune -af && systemctl stop docker;留档/root/stats44.log、/root/stats44-build.log、提交sub-cc160b3ad19080f9、镜像e2e/conc-b44-{1,2,3}。 - 第四十五批工具与留档:从平台底座构建的配方 = 提交上下文含
Dockerfile(FROM registry.felis.svc:5000/felis/paper:demo+COPY marker.txt /felis-probe-marker.txt);装服验证 =PATCH /api/v1/servers/test-one {"image":"registry.felis.svc:5000/user-uploads/<sub>:latest"}→POST /servers/test-one/wake→kubectl -n minecraft exec test-one-0 -- cat /felis-probe-marker.txt;trivy 双 DB 镜像 =mirror/trivy-db:2+mirror/trivy-java-db:1(Java DB digest5766dfbb…;两键在/etc/felis/felis.{host,pod}.toml与 felis-config Secret 双副本);bootstrap_test.sh需在 Linux 跑(VM 留档/root/btest72/,应 140 PASS);本批提交样本sub-{b45d416f4af811ef(无镜像),83f677e41acd80c3,171e8f967f0de3bc,c006cbd633317ccb,cc160b3ad19080f9};留档/root/probe44/、/root/probe44.tar.gz、/root/probe44*.sid。 - 第四十六批工具与留档:控制面三处 =
registry.felis.svc:5000/felis/felis:auditfix77(宿主 docker 源10.43.182.43:5000/felis/felis:auditfix77,v0.0.0+fix77;api/operator + minecraft nsfelis-reaperCronJob + api/operatorFELIS_IMAGE四处成对改);/opt/felis/src=b9ebc87快照(上一版src.bak46);宿主 drill 二进制/root/felis-fix77.bin(Mac 侧CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w -X main.version=v0.0.0+fix77" ./cmd/felis;scp 到 IPv6 主机时主机位必须写root@[fdb2:…]方括号);#75/#76/#77/#78 留档/root/probe77/(create-lane.log、lane2.log、lane3.log〔含玩家会话矩阵 + 分级 ZT 对照 + curl 7.76 在-H Host:下不发 jar cookie的坑——host 路由 drill 用显式-H "Cookie: felis_session=…"〕、converge-1.log/converge-2.log、crs-before.yaml、各步请求/响应 json);面板 CDP 截图(Mac):/tmp/setup8-redirect.png、/tmp/withdraw-{1,2}-*.png、/tmp/admdelete-{1,2}-*.png,脚本/tmp/cdp-setup8.js、/tmp/cdp-withdraw76.js、/tmp/cdp-admdelete76.js;锁定会话铸法 =sha256('drill-locked-77')直插sessions(用户3f2c1b0a-…);玩家会话铸法 =[email protected]邮件 OTP(码在 felis-api 日志no Mailer configured行;edge-dis2是软删死账号,登录门按设计排除);pgint 前置:pg_hba需host felis_pgint felis 127.0.0.1/32 scram-sha-256(本批第二次丢失并补回);发布链 smoke = tagv0.1.0-rc1(release run35949621233),prerelease 不移动/releases/latest——无 stable 时 bootstrap 默认通道给出显式指引后 die、felis update404 报错可读;首个稳定 tag 是产物决定(未代拍板)。 - 第四十七批工具与留档:stable tag
v0.1.0=b9c97ff(annotated;release run35950722509;资产 amd64 61,378,722 B / arm64 57,344,162 B;/releases/latest= v0.1.0、rc1 保持 prerelease)。VM/root/probe77/:stage1-release-download.sh+stage1.log(REF=v0.1.0;host bin sha106c4c9e…→a64f32c5…)、stage2-run.sh+stage2.log(无 checkout 全量安装 rc=0/2m17s/零 fail warn;"unpacking the embedded game-stack sources" 行)、bootstrap-funcs.sh(可 source 副本)、bootstrap-full.sh(完整版,sha89d0181d…)、pre77/(事件前快照);Stage 3 =/root/felis-rc1.bin update --all(rc1→v0.1.0)对照现装felis update --all(v0.1.0 up to date);"无 checkout 主机"演练配方 = 停 felis-velocity →mv /opt/felis/src src.bak47→bash < bootstrap-full.sh→ 复原 mv;pg_hbafelis_pgint行必须插在# END FELIS MANAGED HBA之后(块内会在重装时被重写段删除——三轮同根因;本轮已按块外补回)。