From ce8c7893fe284dbe2f24d28cbc292f88b51c4dac Mon Sep 17 00:00:00 2001 From: Lemon-miaow Date: Fri, 2 Oct 2026 15:23:08 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E9=87=8D=E5=86=99=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 4 +- .gitignore | 4 + AUDIT-2026-09-22.md | 1013 ------------------------------------ CONTRIBUTING.md | 8 +- README.md | 132 +++-- README_EN.md | 131 +++-- cmd/felis/update.go | 11 +- deploy/bootstrap.sh | 4 +- docs/assets/felis-logo.png | Bin 0 -> 211187 bytes docs/operations.md | 2 - docs/troubleshooting.md | 3 - internal/updater/doc.go | 7 +- internal/updater/github.go | 6 +- plugins/README.md | 2 +- 14 files changed, 181 insertions(+), 1146 deletions(-) delete mode 100644 AUDIT-2026-09-22.md create mode 100644 docs/assets/felis-logo.png diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c63363c..6074dd4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,8 +9,8 @@ # The push trigger is limited to main rather than every branch, for the reason release.yml is # not repeated here: a branch with an open PR would otherwise run the whole suite twice per # push, once for refs/heads/ and once for refs/pull/N/merge. Those are different -# concurrency groups, so neither cancels the other, and this repository is private and billed -# for both. A branch with no PR open yet is the one case that loses coverage, and opening the +# concurrency groups, so neither cancels the other, and both would occupy runners for the +# same commit. A branch with no PR open yet is the one case that loses coverage, and opening the # PR is what asks for the answer. name: ci diff --git a/.gitignore b/.gitignore index 2585e06..14ce450 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,7 @@ AGENTS.md docs/Felis-Spec-V4.1.md panel/DESIGN.md panel/DESIGN-WEB-3SIDES.md + +# Audit ledgers and readiness reviews stay on the maintainer's disk. +/AUDIT-*.md +/READINESS-*.md diff --git a/AUDIT-2026-09-22.md b/AUDIT-2026-09-22.md deleted file mode 100644 index 3e9ef8d..0000000 --- a/AUDIT-2026-09-22.md +++ /dev/null @@ -1,1013 +0,0 @@ -# Felis 生产就绪审计 — 2026-09-22(真机 E2E + 混沌) - -分支:全部修复**逐 commit 直接推 `main`**(不再用功能分支)。remote = `git@github.com: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 的 `/` 布局不存在,且 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 恢复 ✅ | 控制面四类 pod 挂内建 `system-cluster-critical`(用户自定义类值上限 1e9,达不到 2e9 临界阈值;preemption 保留=管理面可调度,已记录权衡);troubleshooting §13b 固化事件链、5 分钟条件过渡与镜像恢复路径。提交见下 | -| 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 /backup` 202 → `GET /jobs` running→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.go` contextBase 分支、jobspec 无 volumes/env)→ **第三批已修复,见下** - -### 本轮新增真机证据(第三批:探针 + 构建链路全通) - -- **控制面探针(`0c8e29b`)**:felis-api / felis-operator / registry 三个 Deployment 此前**完全没有探针**。修复后真机验证:api、operator `/readyz`+`/healthz`(internal 8081),registry `/v2/`(含命名端口解析);三个 Pod `Ready=true`、`restartCount=0`、无 `Unhealthy` 事件;operator 新增 `--health-probe-bind-address`(8081,与 metrics 8080 分离,零检查也 404 → 已注册 ping) -- **构建链路全通(`f79e5eb` + `02fd2de`)**: - - 传输:context_ref 改为 internal-face URL;`felis fetch-context` initContainer 走 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 构建并 push `registry.felis.svc:5000/user-uploads/:latest` ✅ → Trivy 扫描(内建镜像 DB)✅ → Job `Complete`;**从 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) - -### 本轮新增真机证据(第四批: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_migrations` max=20;`pg_indexes` 出现 `users_verified_email_unique`。 -- **邮箱唯一性真机 409 演练(auditfix17)**:owner 已验地址被玩家 onboarding 流程二次验证 → 第一次 409 `email_taken`;**同码重放仍是 409**(证明码未被消费,符合契约;若被消费会是 400)。 -- **admin 改邮箱清 verified 真机演练(auditfix18)**:PATCH player.test → `player.test+edited@example.net` 后 `email_verified=f`;PATCH 回原地址仍 `f`;玩家走 onboarding 重验证 → `t`。 -- **owner 角色真机闭环(auditfix18)**:owner 行按 0011 文档升级路径置 `role='owner'` → `GET /api/v1/users` 200(修复前 403);owner op-login start 铸请求+发码、finish 得 `role=owner` 会话;PATCH owner role / DELETE owner / DISABLE owner 全部 403;玩家邮箱登录回归 200。 -- **配额原子门(第五批,`bb68fef`,auditfix19 已上线)**:pgint 并发实证(真实 PG 上两路并发认领:恰 1 赢 + 1 `ErrQuotaExceeded`;修复前两路全赢);`docs/deferred-seams.md` 的 audit #4 条目核销。 - -### 本轮新增真机证据(第六批:reaper 警告链路端到端演练,auditfix20) - -- **演练装置**:独立 hostNetwork Pod(`felis:auditfix20`,SA/卷结构复刻 live CronJob)+ 宿主本地零依赖 SMTP sink(捕获完整 RFC5322 原文)+ 专用 `felis-config-drill` Secret(SMTP 指向 `127.0.0.1:1025`);场景服 `warntest`(独立 CRD,desiredState=Stopped,不占资源),期间 operator 暂停防 reconcile。 -- **投递**:13d idle → 恰 1 封,`To: player.test@example.net`(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` 在空档案服上得到 vanilla `That 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 真机实验裁决(hardcoded `namespace: felis` 版直接 `error: … does not match …`,证实第一段必错、第二段被跳过)。 -- **修复后行为验证**:两条命令序列(felis-smtp manifest 携带目标 ns + `felis-config` create--dry-run|apply)均被 server 接受且落点 `minecraft`。 -- **live 收敛项(随下次 `felis install/setup` 重跑)**:live CronJob 仍是旧模板(无 `FELIS_SMTP_PASSWORD` env);`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)→ status `approved:true` → finish 200 `role=owner` → op 域新 cookie 生效(`/fleet` 200);**演习后已删除假链接行**。顺带复证:approve 失败时 status=false、finish 400 `op_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 magic `1f8b` + 解包内容 = 上传 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)。commit `1918da2`。 -- **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、无 `?ref` 400、删除 **204**(契约;我的 200 预期作废)、再删 404、坏 ref 400;`/images/build/unknown` GET/cancel 均 404;空体提交 400;玩家打 `/images*` 全 403。 -- **`/images/build` 正路径(直接构建)**:借遗留 submission blob 作 context,`FROM scratch` 内联 Dockerfile → **202 → 12s succeeded**;`/logs` SSE 实收 Kaniko 流;真机 registry `tags/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` 渲染正常(`/me` 401 为预期探测)、`/account` 未登录重定向 `/login`。 -- **缺陷 #31(面板 mock 残留·owner 判定)**:`ImageBuildPage` 用 `email==="owner@mock.felis.local" || startsWith("owner@")` 猜 owner——真实 owner(`felis-owner@example.com`)**不被识别**(只能看 approved 提交),而任何 `owner@x` 邮箱都冒充 owner。改为消费 TierProvider 服务端 `isOwner`。commit `6907961`。 -- **缺陷 #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` 回 `player.test@example.net 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` → 403 `account_retired`,**不消费码**,可逆重试);④ `DeleteUser` 事务内 `DELETE webauthn_credentials` + `account_links`(凭证与 `UNIQUE(mc_uuid)` 占用不再外泄);⑤ discoverable passkey resolve 补 liveness 检查。单测 `TestDeadAccountsCannotLogInOrKeepSessions` + pgint `TestDeadAccountsAreLockedOutInPG`(含"删后新铸会话不验证"与资产清点)。 -- **绿证据(auditfix28,真机)**:红期为死号铸出的会话 → **401**(皮带生效);死号 `start=202` 中立且 **90s 日志窗口 0 条码**、`verify=400 invalid_code`;禁用中 `start` 中立(日志码数 1→1 不变)→ `verify=400`;**恢复启用后 `verify=200` + `/me=200`(可逆)**;bind 门死账号 → **403 `account_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)**:① `UserByMCUUID` JOIN users 过滤 `disabled`+`deleted_at`——死账号的链接在游戏面**读作未链接**(claim 412、wake 落回 policy 门、vouch 403、status `linked:false`),绝不作为遗留身份存活;② `VerifyLinkCode`:**软删** holder 的链接可由新账号凭新 mint 码**接管**(账号已亡,码证明调用者仍持有该 UUID),**禁用** holder 仍 409(接管= 绕过锁死,不允许)、任何失败都不消费码。fake 单测 `TestLinkVerifyTakesOverDeletedLinkOnly`;pgint `TestVerifyLinkCodeTakesOverDeletedLink` + `TestDeadAccountsAreLockedOutInPG` 增「禁用链接无资格 / 复启用恢复」断言。 -- **绿证据(auditfix29 真机,三面六点)**: - 1. `link/status`:活 `{"linked":true}` → 禁用 `{"linked":false}` → 恢复 `{"linked":true}`; - 2. `claim`:活链 404(身份已解析、ghost 服不存在)→ 禁用 **412 not_linked** → 恢复 404;未知 UUID 对照 412; - 3. **接管正例**:临时软删 holder(`de49df52`)→ 新 mint 码 `X3G3RZZM` 由 player.test verify → **200 linked:true**;链接行迁移到 player.test(`auth_source=mojang`)、码被消费(`account_link_codes` 0 行);演练后 holder 与链接全部还原; - 4. **接管负例**:临时禁用 holder(user2)→ 新码 `X5W94S42` verify → **409 already_linked**;同码重试仍 409(**不消费**);恢复启用后**同码**由 holder verify → **200**(码保留、可逆); - 5. **wake 授权**:活 owner **202**(真实启动)→ 禁用 **403 forbidden** → 恢复 **202**;test-one 已停回 Stopped 且所有权释放; - 6. **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 env` api/operator 双 Deployment 至 `felis:auditfix29` 并滚动完成;api/operator/reaper 三镜像一致 = auditfix29;port-forward 重启后内部面绿;panel 200、op 面 `/me` 200。 -- **运维备注**:`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`**(临时 UUID `3333…`):首次 **204**、重复 **204**(幂等);DB 实锤:`test-one.last_active_at` 15:02→05:53(重置)、`server_allowlist` 恰 1 行;缺 uuid → 400;未知服 → 404。演习后 allowlist 行删除、`last_active_at` 复原。 -- **`status`**:test-one → 200 全字段(phase/desiredState/endpointMode=fallback/…);未知服 → 404。 -- **`player/reclaim`**:新建(临时 UUID `4444…`)→ 200 `hold_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)→ 400 `bad_name`(在入队前拒绝,RWO 闸门单测已覆盖)。 -- **探针**:`/healthz` 200、`/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 缺陷)**: - 1. **三方身份重写**:`ftok` → 200 `{"id":"74409c3bbae93acabd2176e517b4f2a0",…}`,与本地按 `uuid.NewMD5(felisAuthNS, "faketest:native-123")` 的预算值**逐位一致**; - 2. **保费名冲突改名**:`ftnotch` → `47c5527a18b03fe5a49ec50c11154dcd` + `name="FT_Notch"`(Mojang 实查 Notch=200 premium);`ftok` 的 `FtPlayer` 恰也是真实 Mojang 名(`640c1672…`)→ `FT_FtPlayer`,改名按设计触发(非保费名保持原名); - 3. **敌意插件名拒绝**:假源返回 `§4admin` → **204**(不落 proxy 玩家列表); - 4. **源故障不静默**:假源 500 → **503**(velocity 报 auth servers down),对照未知玩家 → 204; - 5. **形状负例**:缺参 / 超长参 → 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——与 operator `init-forwarding` 容器的既定先例同源("只有 root 能可靠读写这些文件");DAC_OVERRIDE 兜住"游戏镜像是非 root UID"的任意镜像场景。四份 shape 测试同步改断言。troubleshooting §10「Permissions」段落重写(uid-1000 + setfacl 时代结束)。 -- **绿证据(auditfix31,真机四联 drill)**: - 1. **backup**(面板 CDP 实操,红→绿同场景):点击「立即备份」→ `进行中` → **`成功`**(同页面 reload 后仍在);Job pod `runAsUser:0` + DAC_OVERRIDE;新归档 `test-one-1790115428472416736.tar.gz` 实测含 `world/level.dat`(471B)与 server.properties,共 499 条。 - 2. **restore**:`POST restore-backup`(bk-9df0…)→ 202 → Job **Succeeded**(root 写路径过关),日志 `restored from … into /world`。 - 3. **fileedit**:`GET /servers/test-one/file?path=world/level.dat` → **200**(base64-gzip 内容 628B)——修复前该请求必然 permission denied。 - 4. **reaper**(从 live CronJob 派生一次性 Job + 复刻钻取世界:1Gi PV/PVC + `world/level.dat` 512B **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/宿主目录/临时归档)。 -- **面板补全(`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/` 点「解绑通行密钥」→ 对话框 → 确认 → **凭据列表清空、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);控制台右侧新增门口卡。i18n `files` 命名空间(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` 拒绝**(400 `bad_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=`;多节点集群必须钉在存世界的节点,否则可能调度到 hostPath 为空的节点。`--reaper-node` 无 `--worlds-host-path` 时 fail-loud exit 2。修复 `daf7602`(双测:`TestReaperCronJob_NodePin`、`TestManifestsReaperNodePin`)。 -- **绿证据(auditfix36,真机 render + dry-run + 收敛 diff)**: - 1. **固定渲染**:`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,改为「已钉到节点 …」; - 2. **负例**:`--reaper-node` 无 `--worlds-host-path` → exit 2 + 原文;不传 node 的渲染 stderr 仍完整保留「NO nodeSelector … 多节点必须传 --reaper-node」警示,bundle 内 0 个 selector; - 3. **`kubectl apply --dry-run=server -f -`**:整包 **全部 configured**(含新 nodeSelector 的 CronJob); - 4. **再安装收敛性 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-s3` Secret」把整条通道打通,**全程 0 缺陷**、演练后还原并逐项复核。 -- **绿证据(auditfix36,真机全链)**: - 1. 切 S3 后 api 滚动启动 **无** “S3 user-uploads store not configured” 告警(凭据解析成功);pod 内 busybox 探针实测可达 `http://10.211.55.6:9000/minio/health/live`(rc=0); - 2. 玩家提交 + 上传 → **200**,对象实测落桶:`sub-12c953e12b0cd144/context.tar.gz`(197B,mc ls 实见); - 3. owner approve → 构建 Job `build-bld-1790118222183394193` **status.succeeded=1**(fetch-context 从内部面流式取件 = api 自 S3 读回成功);`/me/submissions` 的 `build_status` 收敛为 `succeeded`; - 4. **回滚**:felis-config 还原(sha256 与演练前备份**逐字节一致**)、删除 `felis-uploads-s3`、api 滚动;再演练一次本地路径:新提交上传 **200** 且 blob 实测落在 uploads PVC(context.tar.gz 197B);启动日志仅剩 smtp/jwks 两条既有提示; - 5. **清理**:2 行 submission + 1 行 build 删除、回滚演练 blob 删除、S3 构建产物从 whitelist 摘除(204)、MinIO 容器 + 两个镜像移除;`/fleet` 200。 -- **遗留观察(非缺陷)**:registry 里保留本次推送的 `user-uploads/sub-12c953e12b0cd144:latest` 层数据(与早前 direct-probe 同类,filesystem registry 无删除接口);`felis setup` 的「S3 存储」向导屏本身未演练(列入 TUI 逐屏待办)。 - -### 本轮新增真机证据(第二十五批:breakGlass 控制台逐屏全量 + 备份/恢复门禁 —— 缺陷 #39–#43) - -- **背景**:breakGlass 此前只验过 Owner 首装与 Add Operator happy path。本批把菜单四操作(Owner reset / Add Operator / Halt / Sync)+ 首装(bootstrap)分支全部逐屏真机走完,并打穿 Sync/恢复背后的 API 门禁;共抓 5 个缺陷、全部修复复验。驱动方式:VM tmux(`remain-on-exit on` 才能读回 alt-screen 撕掉后的 durable summary)。 -- **先落的正向证据(无缺陷)**:Halt——`test-one` Running → 选中 → 卡「is stopping」→ CRD `desiredState=Stopped`、pod 收敛消失、审计 `break_glass.halt`;面板 `wake`/`stop` 两个恢复杠杆均 202(复验后还原)。Sync 正路径——test-one(Stopped)→ 卡 backup started → Job 6s Complete → 归档落 `felis-backups` PVC(167MB)→ `world_backups` 行 `present` → `/api/v1/backups` 可见。Sync 负路径——对 Running 选 → 友好 409 卡(不误烧冷却)。 -- **缺陷 #39(owner 席位可被静默复制,且不可清理)**:恢复模式下用非在位席位名做「reset」→ `UpsertOwner` insert 臂**铸出第二个 owner 行**、原席位继续存活;面板对任何 owner 行都删/降/禁 403 → 永久无法收敛回单席。修复 `55d515d`:`provisionOwner` 先查在位席位(新 `PGRepo.OwnerUsername`),非席位名 → `ownerSeatTakenError`(`Is api.ErrConflict` → TUI 路由回表单并**指名**应输入的用户名);bootstrap(无席位)与同席位名复位原样。真机双验:新名被拒(表单原位显示 `an Owner already exists as "08595879-…" — enter that username to reset the Owner`);改席位名复位成功(owner 恒 1 行、id/邮箱不变);面板删除保护同步实证(对新 owner 行 DELETE → 403)。 -- **缺陷 #40(operator 撞名 = 裸 SQLSTATE,「换名重试」分支在真机从未生效)**:`InsertOperator` 冲突返回原始驱动错误(23505),TUI 却按 `api.ErrConflict` 判定「可恢复、换名重试」——fake 与 PG 漂移。真机复现:输入已存在用户名 → 控制台 exit 1 + 裸错误。修复 `55d515d`:`isUniqueViolation → ErrConflict` + pgint 契约断言。复验:撞名**回到表单**提示换名 → `drill-op-2` 成功(审计 `break_glass.operator_create` 落账,演练行已清)。 -- **缺陷 #41(Sync picker 死选项)**:picker 列出 login/lobby,而备份 API 对它们**永远失败**(保留名 + 无 servers 行);真机选 lobby → 裸内部错误 + exit 1。修复 `ac3a557`:`backupPickable` 过滤系统服(halt picker 保留它们,断玻璃完整权力)。 -- **缺陷 #42(缺失世界盘 → 202 后静默卡死 30 分钟)**:对「从未启动/已被回收」的服备份或恢复:202 → Job → Pod `persistentvolumeclaim "world--0" not found` **Pending 至 deadline**,全程零失败记录。真机用已回收的 `resolvecheck` 复现(留证后删除)。修复 `508a1c0`:`Cluster.WorldVolumeExists`(直接 Get 与 Job 挂载**同名**的 PVC)+ 两 handler 409 `no_world_volume`("start it once to create it, then retry")。**修复首跑翻出配套 RBAC 洞**:felis-api SA 无 `persistentvolumeclaims:get`(403 被吞成 500「internal error」)→ `APIMinecraftRole` 补 get-only 规则 + rbac 测试锚点。复验(auditfix38):resolvecheck 两面 409 + 友好文案;test-one 照常 202 → Job 10s Complete → 新行落库。 -- **缺陷 #43(409 一刀切文案)**:TUI 把所有 409 当停服门 → 世界盘拒绝会展示错误原因。修复 `ac3a557`:按 body 的 `error.code` 分流(无 code 的旧体仍按停服门)。复验:无盘 pick 显示 API 原文;停服门文案不变。 -- **同步完成**:① bootstrap 分支 scratch 库演练(`migrate up` 20 迁移 → 无菜单/无认证直接铸 owner;`local_auth_enabled=true`;审计 `break_glass.bootstrap`;exit 0;库/hba 规则/临时配置即测即清);② 台面收敛:reaper CronJob `suspend=true/auditfix36/无 pin` → `suspend=false / felis:auditfix38 / nodeSelector=localhost.localdomain`;③ 镜像升级 `auditfix37→38`(api/operator + `FELIS_IMAGE`)。 -- **流程修正(教训)**:pgint 一度误用**本机 Docker Desktop**(启动 daemon + 临时 PG 容器)——已完全清理(容器/镜像删除、daemon 退出),并改为**经 ssh 隧道用 VM 的 postgres** 运行(`ssh -L 15433:127.0.0.1:5432` → `postgres://felis:***@localhost:15433/felis_pgint?sslmode=disable`)。勿再在本机跑容器。 - -### 本轮新增真机证据(第二十六批:`felis setup` 重跑向导逐屏 + 构建链 pin 回验 —— 缺陷 #44) - -- **范围**:本机已装机,故覆盖"重跑状态屏 + c/s/e 三条 reconfigure 流";首装屏(postgres/owner/connect/edge/storage/smtp/mc-bind/migration/preflight/summary)此前各批已有定点真机证据(安装闭环 / 第二~三批 / 第九批 / 第二十五批),本轮不重复。 -- **逐屏走查(auditfix38→39,VM tmux)**: - 1. 重跑 → 直落状态屏「✓ Felis is already set up.」(owner/connect 不触碰;host bootstrap 已就绪跳过)✓ - 2. `c` → 三选一 chooser(Local / Cloudflare+Access / Reverse proxy + 警示语)渲染 ✓,esc 无损返回。 - 3. `s` → chooser 预选当前后端;S3 分支表单(Endpoint/Bucket/Region/AK/SK + 提示)渲染 ✓。**观察:reconfigure 非只读**——选「Local disk」即 apply(写 /etc 两文件 + 重渲染 felis-config + 滚 API);从 S3 表单 esc 退回会把选择重置为 Local 预选。 - 4. `e` → SMTP 表单渲染 ✓;esc 直接回状态屏、零副作用(felis-smtp 未创建)✓。 -- **缺陷 #44(CLI·重跑框脱落)**:重跑后完成 `s`/`c` reconfigure,落回首装 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**;registry `e2e/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-forward `svc/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)**: - 1. 打包含 `COPY does-not-exist` 的 Dockerfile 上传到 uploads PVC `sub-alertdrill` → `POST /images/build`(`e2e/alert-drill2:latest`); - 2. Kaniko `failed to get fileinfo for /context/does-not-exist` → Job Failed、build 行 `failed`; - 3. 真实 Prometheus(宿主 `:19090`)scrape `127.0.0.1:18081`(api) 与 `:18080`(operator) 双 target up;`felis_image_build_failures_total{job="felis-api"}=1`; - 4. `FelisImageBuildFailures` pending(activeAt 08:28:14Z)→ **08:33:14Z 准时 firing**(`for: 5m` 精确到期),labels/annotations 完整。 -- **清尾(残留全清)**:whitelist `e2e/alert-drill` 摘除(204);`sub-alertdrill` 目录、`/tmp/drillctx`、两个演练 Job、tmux `prom`/`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 全绿。 -- **真机验证(auditfix41 已部署;owner 会话经 op-login + 内部面代 approve 重铸)**: - - admin 下载 `sub-bf7dc18e9dd97dc2` → **200**,`attachment; filename="context.tar.gz"`、`application/gzip`、`nosniff`;sha256 `205496f2…` **与 uploads PVC blob 逐字节一致**; - - 内部面(Bearer=felis-service-token)同 blob → 200 + 同 sha256(重构未破坏构建取件路径);无 token → 401; - - 有效玩家会话(console host 隔离,仅 adminOnly 生效)→ **403**;匿名 → 401;不存在 id(admin)→ 404; - - 审计落账:`audit_logs` = `felis-owner@example.com | submission.context.download | sub-bf7dc18e9dd97dc2`; - - 面板产物:服务端 index.html 引用新构建 `index-CwFSKpgW.js`,bundle 内含新按钮逻辑(grep 命中 3 处)。 -- **可达性追加**:#45 定级 ①——每一次真实的"用户提交 → 管理员审核"都会踩到(审核者此前无法查看将被执行的内容)。 -- **hook 链补齐(可复用)**:staff 账号走邮件登录门会被设计拒绝(refuse staff)→ owner 会话铸法:`op-login/start`(email=felis-owner@example.com)→ VM 日志 grep `email-otp` 取码(no-Mailer fallback)→ 内部面 `op-login/{id}/approve`(Bearer=felis-service-token;body `approver_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(dmesg `oom-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。 -- **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,历史)。 -- **构建 lane 复验**:`POST /images/build`(context=遗留 `sub-bf7dc18…`;ref `registry.felis.svc:5000/e2e/durable-check2:latest`)→ 202 → succeeded;registry `e2e/durable-check2` tags 落位;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.toml` **4 份**重复的 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 保留;registry `felis/felis` tags = auditfix41/42/43;panel 200;host 二进制已从新镜像提取。 -- **可达性**:#50 ②——配置过 email(存在 `[smtp]` 节)的安装,按文档升级=重跑安装器即触发;危害=配置注释无限膨胀(每次 +1),无功能损失。 - -### 本轮新增真机证据(第三十一批:首装连续走查 + 缺陷 #51 —— 工作负载 `felis-config` 副本永不刷新) - -**一、`felis setup` 首装单次连续走查(队列第 1 项,完成;0 缺陷)** - -- **装置**:scratch 库 `felis_scratch`(新建 + `felis migrate up` 21 条迁移)+ 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)`;副本 sha `5ec2ff6e…` 保持,控制面 `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)+ bucket `felis-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-s3` Secret(access_key_id/secret_access_key);两 toml `user_uploads_context` + `[registry.s3]` endpoint/refs;API 滚动;启动日志仅既有的 smtp/jwks 警告 ✓ -- **功能链(batch24 同款)**:player.test 邮箱 OTP 登录(hook 取码)→ `POST /api/v1/me/submissions` 201 → 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-s3` Secret 残留(演练按清理流程删除;是否自动清理属产品取舍)。 - -**二、缺陷 #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:58Z `kubectl-client-side-apply` = 安装器 rerun 的双 ns apply;12:23:06Z manager `felis` = 下一次 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`,宿主二进制 sha `0bd49467…` 已装 `/usr/local/bin/felis`,旧版留 `/root/felis-auditfix44.bin`;真机双向)**:Run1 从 Local `s`→S3:apply 后 `control = mirror = podtoml = 4fb80ed8…`(S3 渲染;旧代码此刻镜像会停在 local);Run2 `s`→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 已改 `git@github.com: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.jar` mtime/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.bin` sha `0bd49467…`): - - `--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;`--all` trailer 恰一次;`--force`/`LatestKnown` 语义不变。 -- 文案同步:`docs/troubleshooting.md §15` 删掉 “or `sudo felis setup`”、补 channel caveat;测试改为 `TestApplyGuidancePointsEveryComponentAtTheInstaller`(钉安装器路径 + 禁 `run: sudo felis setup`)。 -- 可达性:②(文档/指引;同 #38 型)。 - -### 本轮新增真机证据(第三十四批:`felis nano` 全链走查 60/60;缺陷 #55 —— 坏源静默挡住验证梯子) - -**零、装置(全部在 VM,不进产品环境)**:`/srv/nanotest/nano-stub.py`=可控 Yggdrasil 源,路径即行为——`/ok/`、`/okid//`、`/props/`、`/badname/`(tooshort/toolong/badchar/section/noname)、`/none`=204、`/boom`=500、`/redir`=302、`/garbage`、`/emptyid`、`/slow/`;跑在 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 哈希逐字节一致(md5 `e6b07440…`)、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 处为测试脚本自身误报,均已自纠)** -1. **镜像添加+删除**:添加 `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 条**——删除通道无缺陷。 -2. **构建负例**:外部 registry(`docker.io/...`)+ 不存在 context → 对话框红字拒绝 `build: invalid request: image reference … must target the internal registry "registry.felis.svc:5000"`(截图 `run4c-3`)。脚本"未捕获"系其正则先命中了侧边栏"镜像"二字——误报。 -3. **创建重复用户**:对话框内正确显示 **"该用户名已被使用。"**、对话框保持打开(截图 `run6-1`)。此前 run4c 的"dialog-closed"系脚本点错页面(进到了用户详情页);且该轮实际是一次**正例**:真管理员的 username 是 UUID 串(`08595879-…`,role=`owner` 是角色名),字面用户名 `owner` 当时空闲、创建确实成功——测试件已删(DELETE→200,列表 7→6)。 -4. **创建重复子域名**:正确拒绝 **"该子域名已被占用。"**(截图 `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 缺陷)** -1. **submissions approve/reject**:player 会话(邮箱 OTP 铸造,no-mailer 日志取码)现场造两条 pending(`sub-489eda47fe4bc12d` / `sub-b1dcc4b4a2cc5f16`,各上传 tar.gz context)→ 面板「通过」→ 状态变「审核通过」且**自动构建 `bld-1790176562566802097` 到 succeeded**(点击到构建完成全链闭环);「驳回」→ 对话框必填原因 → 状态变「已拒绝」。全程 UI 零报错。 -2. **用户会话撤销**:player.test 铸 2 条新会话(共 4 条)→ UI「单独撤销」首条 → **精确生效**(cookie5 → 401、cookie4 → 200、列表 4→3);「全部撤销」→ 提示「所有会话已撤销。」、列表清空、cookie4 也 401。 -3. **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` | 空 | ❌ | - | 面板控制台:` … set probe2.test true` | 空 | ✅ | - | **独立 Python RCON 客户端**(绕开 felis):`E2E_Tester … set probe3.test` | 空 | ❌ | - | 独立客户端:` … 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`;四项均 = 修复前真机触发 + 修复后复验)** - -1. **#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`(不再伪装成功);静默服务器回退本地化文案。 -2. **#57(`70c988e`)控制台命令的回复无处显示。** 触发路径:控制台发任何命令——`sendCommand` 拿得到 RCON 回包但被丢弃、pod 日志也不回显命令输出,等于零反馈。修复后 echo + 回包以终端样式渲染在提示符上方(实测 `list`)。 -3. **#58(`4d4cdd6`)无世界盘服务器的文件页 90s 卡死。** 触发路径:对从未启动/已回收的服点"文件"页——旧行为建 Job → Pod `FailedScheduling (pvc not found)` Pending 到 90s 超时 → 误导性 504 `files_timeout`。修复:file 路由补上与 backup/restore 同款 `WorldVolumeExists` 门 → 快速 409 `no_world_volume` + 面板双语文案。本批现场复查:`resolvecheck`(无 world PVC)→ 409 `no_world_volume`,0.03s;`test-one`(有盘)→ 200(~2s)。 -4. **#59(`a2ff2a1`)LuckPerms 页把"读不到"报成"没有"。** 触发路径:打开装了 LP 的服的管理页——LP 5.5.85 的 `lp` 命令 RCON 回包全为空(独立 RCON 客户端 1:1 复现),读投影永远为空,旧页面却断言"没有父组/没有显式节点"(假事实)、写历史伪造 `[RCON]` 行。修复:原始回包随 rosters 同款披露渲染;空回包显式提示、不再假断言;历史占位不再伪造输出。 - -**二、服务器详情页交互复跑(5 项全真机,0 缺陷)** - -1. **ServerCard 停止**:test-one `Stopping` → `Stopped`(启动在第三十七批;收尾复查 `desiredState: Stopped`)。 -2. **ServerFiles 写流程**:编辑 motd → 保存「已保存」→ 重开读回一致(`motd=Felis E2E files drill`)→ 还原默认 `A Minecraft Server`(收尾复查确认)。 -3. **备份/恢复**:立即备份 → Job `succeeded`;恢复 → 确认对话框(破坏性警告原文)→ Job `restore-test-one` `Complete`(5s)→ UI「恢复 27秒钟前 成功」→ 唤醒 `Running/ready`(恢复后的世界可加载)。 -4. **白名单**:加/负例/移除全链复跑(证据见 #56)——终态磁盘只剩 `E2E_Tester`(88 字节)。 -5. **封禁/解封**:封禁(内联确认)→ `Banned E2E_Bad: Banned by an operator.` + 磁盘写入;解封 → `Unbanned E2E_Bad`;`banned-players.json` 终态 `[]`。 - -**三、运维面复查(3 项,0 缺陷)** - -1. **`/admin/updates` 复跑**(队列"深挖"项):设置窗口 → 「维护窗口更新成功。」+ 状态卡更新;负例 end` → 回落 `A Felis server`(非 felis 子域不被劫持)。 - - 登录边界:`LoginStart`(含现代协议 UUID 字段)→ 服务端首包 `0x01 EncryptionRequest` ⇒ 边缘 online-mode 强制成立。 - - 加载面:`Loaded plugin felis-link 0.2.0`(共 5 插件)。 -- 口径:真实账号进服(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`(blob `b9bb139f…`);`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 门禁(JDK21 `plugins/test.sh` + JDK17 `plugins/test-mods.sh`)。 -- README:Status 更新为 compile + boot 双验证;Building 段补 limbo `-PlimboVersion=` 要求与两个门禁说明。 -- 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`。 -- 口径:三服均为"装好 mod 后真启动"的场景,同时验证 loader 真实解析修改后的元数据(license=AGPL、tracker URL)后正常装载;完整取码链(玩家在游戏内 `/link`)按"进服跳过"决定不演练,装载/注册/拒绝面已全部真机成立。 -- 留档:`/root/mods-e2e-b41.log`、`/root/mods-e2e-b41-resume{2}.log`、`/opt/felis/mods-e2e/`(496M,三服目录保留);收尾态:docker `inactive`、k3s/felis-velocity `active`、磁盘 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.go` embed 了 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)→ 新 Job `ttlSecondsAfterFinished=604800`、构建 25s `succeeded`;② 对旧 Job 打 `ttl=30s` → 40s 内 Job+Pod 被 TTL 控制器收走(机制实证);③ 旧堆积 5 个里 1 个已收、4 个留存对照。 - -**二、生命周期 ×6 + 三处故障注入(operator / api / postgresql)** -- 每轮:CR `desiredState` Running → 等 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` → Job `Failed`;pod 时间线 `18:24:12Z` 起、context-fetch `18:24:13Z` exit 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-token` Secret)→ 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_IMAGE` env 仍停 `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/submissions` `build_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.go` oversize→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`)→ kaniko `Retrieving 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/wake` 202 → pod `test-one-0` Running 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_repository` carry 断言补齐。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`,不占盘)→ 上传 → 403 `submission_quota_exceeded`;管理员删除 S2(行 + 目录双清)后 → 同一上传 → 200(预算即时释放)。 -- 口径:预算按 blob 的实际占用聚合(`Blobs.Size` 遍历汇总,正是"用久了就超"的同一条读取路径);稀疏垫付只是把"已经存了 2GiB 的用户"这一状态合成出来。 - -**二、#76 真机闭环(撤回 + 管理员删除)**——留档 `/root/probe77/lane3.log`: -- 撤回自己 pending → 200;DB 行 = 0、uploads 目录 = gone;重复撤回 → 404;对已审核 → 409 `already_reviewed`;对他人 id → 404 `not_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.` 主机可用——同一 owner 会话在玩家面板主机上 `is_admin=false`(admin 路由 403),在 `op.console.` 上 `is_admin=true`(200);面板按要求显示「无权访问」而不是假装能点。 - -**三、#77(tracker #8,`43699b4`)真机闭环:锁定会话 → /setup** -- 铸一个未完成引导会话(SQL:`sha256('drill-locked-77')` → `sessions`,用户 `3f2c1b0a-…`,`email_verified=f`、无 passkey)→ `GET /me` 200、`GET /me/submissions` → 403 `setup_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` 清空 lobby `spec.rcon` 与 login `spec.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`/`DeleteSubmission` CAS)首次上真 PG:**17/17 全绿**。 -- 根盘:镜像构建后 83% → `docker builder prune -af` 回收 3.5G,docker 停回 inactive。 -- CI:`43699b4` success;`c57daaf` 被后一 commit 的并发策略取消(同分支 cancel-in-progress),其树被 `b9ebc87` 的 success 完整覆盖;`b9ebc87` success。 -- 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`(sha `106c4c9e…`)→ `felis v0.1.0`(sha `a64f32c5…`,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 ns `felis-reaper` CronJob 模板同步为 `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)`;velocity `3.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.0` tag 保留为 kubelet 回拉源。 -- 演练后 VM 复位:docker inactive、k3s/felis-velocity active、test-one 仍 Stopped。 - -**五、结论**:stable release 通道端到端闭合——「tag → CI 双资产 → `/releases/latest` → 无 checkout 全量安装(embedded 资产 + registry mirror)→ 控制面/游戏栈全绿 → `felis update` 报告」全链实弹,无新缺陷。 - -## 结论:离"生产可用"还差什么(按优先级) - -1. ~~构建链路的上下文通道~~ ✅ **已修**(`f79e5eb`/`02fd2de`,真机全链路含拉回校验;Trivy DB 需按 §8e 镜像一次)。 -2. ~~镜像耐久~~ ✅ **已完成**(第二十九批:`a9b275a`/`13d64e0`/`fa0e8d7` + `c7e585e`;三次真机重跑 + GC 两演练——控制面与游戏镜像被 GC 后自动回拉;同批修复并复验 #46/#47/#48/#49)。 -3. ~~PG 级契约测试~~ ✅ **已落地**(`2a55a0d`):`internal/pgint`(`-tags pgint`,需 `FELIS_TEST_PG_URL` 指向名字含 `pgint` 的库,harness 会 drop schema + 重放真实迁移)已覆盖会话/OTP/op-login/绑定码/submission/build/owner 角色;**首跑即抓到 #20**(索引与 ErrEmailTaken 从未存在)。运行方式见 CONTRIBUTING.md。 -4. ~~面板把 /jobs 显示出来~~ ✅ **已完成**(`97a64c8`,备份页「最近操作」卡,正是它把 #35 暴露出来的)。 -5. ~~多节点回收~~ ✅ **已修**(`daf7602`:`--reaper-node` → CronJob pod `kubernetes.io/hostname` nodeSelector,真机 render/dry-run/收敛 diff 三连;单节点部署不传即维持原状)。附带核销缺陷 #38(渲染提示里过期的 uid-1000/setfacl 指导)。 -6. ~~告警~~ ✅ **已完成**(`94f71ee` + `43df08b` + 第二十七批实弹演练:真实构建失败 → pending → 08:33:14Z firing;规则随 `deploy/alerts/` 交付)。**2026-09-24 补齐无 Prometheus 的告警面**(`d17524c`):主机 `felis-watchdog.timer` 每 2 分钟巡检控制面/登录门/系统服/失败服/Job/reaper/节点/PG/代理/DB 备份/磁盘/内存,按持续时长门限给平台所有者发邮件;operator 增加 `felis_server_phase`、`felis_build_info`、卡死存活探针,规则新增 `felis.platform.rules`/`felis.jobs.rules`(promtool 22 条全过)。真机:felis-api 缩到 0 → 5 分钟后告警邮件落地("Felis 严重告警…1 项异常"),恢复 10 分钟后收到恢复邮件。 -7. ~~玩家可见的构建结果~~ ✅ **已修**(`72c4aa3`,缺陷 #36:列表路由附 `build_status`/`build_error`,面板「我的提交」展开行呈现,真机双例验证)。 -8. ~~面板文件编辑器入口~~ ✅ **已补**(`0a36b3f`,缺口补齐,真机 CDP 全链)。 -9. ~~fleet 系统服务死操作~~ ✅ **已修**(`2f90851`,缺陷 #37)。 -10. ~~S3 上传通道演练 + 存储向导逐屏~~ ✅ **已演练**(第二十四批:上传通道全链、0 缺陷;第三十二批:向导屏逐屏 + 上传取件 + UI 回滚,0 功能缺陷并修 #52)。 -11. ~~breakGlass 控制台逐屏~~ ✅ **已演练**(第二十五批:菜单 4 操作 + bootstrap 分支,缺陷 #39–#43 全修全验)。~~`felis setup` 全屏向导逐屏~~ ✅ **重跑侧已逐屏**(第二十六批),**首装侧连续走查也已完成**(第三十一批:scratch 库单次连续走完全部屏幕 + 轨道回顾,0 缺陷)。 -12. ~~`felis nano` 全链~~ ✅ **已走查**(第三十四批:HTTP 矩阵红 13→绿 60/60;systemd/配置/firewalld 三层重跑全绿;同批修复 #55——坏源静默挡住验证梯子)。 -13. ~~NetworkPolicy 真机强制矩阵~~ ✅ **已演练**(第三十五批:pod 源端口级矩阵全绿、转发型外部源「拒→放→拒」白名单闭环、host/ClusterIP/NodePort 路径全通;两条已归因注记——node-local 流量豁免、firewalld 仅放 DNAT 服务流)。 -14. ~~面板错误文案全量本地化~~ ✅ **已修**(第三十六批:#60,37 个用户可达码补齐双语映射,三类真机验证;管理面写操作 Run4c 复核 0 缺陷)。 -15. ~~管理面交互级收尾(submissions / 会话撤销 / 启动)~~ ✅ **已演练**(第三十七批:点击→构建→succeeded 全链、单条/全部撤销精确验证;同批修 #61——LP 写承诺按 LP 官方语义收窄,真机复验)。 -16. ~~缺陷 #56–#59(玩家管理回包 / 控制台回显 / 文件页 fail-fast / LP 诚实化)~~ ✅ **已修并归档**(第三十八批回填真机证据:修复前触发 + 修复后复验,四项全绿)。 -17. ~~服务器详情与运维面收尾~~ ✅ **已演练**(第三十八批:ServerFiles 写流程、备份/恢复全链、白名单/封禁闭环、ServerCard 停止、`/admin/updates` 复跑、metrics、CLI 核销——0 缺陷)。 -18. ~~reaper 多节点实机~~ ✅ **已演练**(第三十九批:VM 克隆双节点 k3s——pin 命中持盘节点并跑通、错位 pin fail-closed Pending、装置全回收;`felis-backups` PVC 的 volume node affinity 构成第二层保险)。 -19. ~~Java 插件层自动化缺口~~ ✅ **已补**(第四十批:`plugins/test.sh` + CI `plugins` 作业——三个手工测试与三个装机 jar 全进门禁,CI 4/4 绿;首跑抓出两处"没跑过"的错误:InviteCardTest 编译类路径缺 examination-api、limbo `+` 版本不可解析。同批关闭 demo-up 单起点化 #63、并复核剔除 #62)。 -20. ~~装载器 mod 层(fabric/forge/neoforge)~~ ✅ **已收口**(第四十一批:#64 三个 `gradlew` 补可执行位、#65 元数据对齐 AGPL-3.0-only;`plugins/test-mods.sh` + CI `mods` 作业 + release 双门禁;三真实专用服起服 E2E——mod 装载、`/link` 注册、控制台拒绝全绿。CI run `35892544563` = 5/5,`mods` 作业首跑即绿)。 -21. ~~全仓覆盖面对账 + 文档收尾~~ ✅ **已完成**(第四十二批:22 个 internal 包 × 面板 17 条页面路由 × 全部 tracked 脚本 × 仓库卫生逐项对账,无盲区;#66 README_EN 修复;§28 两张序列图对齐现行实现;本地 `scripts/sync.sh` 排除清单与 `go:embed` 冲突——修复+复现验证〔脚本私有,不入库〕)。 -22. ~~构建层资源回收~~ ✅ **已修**(第四十三批:#67 build Job TTL——完成 Job/Pod 不再无限堆积;真机:新构建 Job `ttl=604800` + 旧 Job 打 30s TTL 实测被 TTL 控制器收走。同批:6 轮生命周期循环 + operator/api/pg 三处注入全绿、PG 503 语义复验、30 路并发全 200)。 -23. ~~构建上下文拉取的短暂断连~~ ✅ **已修**(第四十三批追加:#68——fetch-context 有界重试〔45s 窗口 / 3s 间隔;4xx 不重试〕;真机:api 停机期连续 8 次拒连全部重试、恢复后构建 32s Complete;正常路径无重试开销。附:演练升级暴露的 `FELIS_IMAGE` 两代错位已对齐 auditfix63)。 -24. ~~modpack 规模上下文全链 + 并发构建~~ ✅ **已演练**(第四十四批:200MiB 上下文上传 0.44s → 批准 → 构建全链 13s、三路并发构建全绿;TTL 与玩家可见面在规模下复验,零缺陷。见该批节)。 -25. ~~内建底座构建(`FROM registry.felis.svc:5000/…`)~~ ✅ **已修并真机闭环**(第四十五批三连修:#70 拉取缺 `--insecure-pull`、#71 drop-ALL 卡解包、#72 trivy Java DB 未镜像——"真实模组包"形态的三个必踩点;修复后 paper 底座构建 27s Complete、`paper.jar` 扫描 0 漏洞)。 -26. ~~提交 → 装服 → 起服 闭环~~ ✅ **已演练**(第四十五批 capstone:构建产物 PATCH 为服务器镜像〔白名单门通过〕→ wake → Running 1/1 → 用户 marker 可读 + `Done (4.678s)!`;test-one 已复原为 paper:demo/Stopped)。 -27. ~~文档与测试工具面~~ ✅ **已修**(第四十五批:#69 README 部署承诺对齐实现;#73 bootstrap 测试密闭化〔VM 140 PASS〕;#74 console 断连测试消抖;CI 全绿)。 -28. ~~提交上传面的上限与生命周期~~ ✅ **已修并真机闭环**(第四十六批:#75 per-user create/upload 冷却〔429〕、pending ≤5〔403〕、2GiB 存储预算〔403〕;#76 撤回〔owner+pending CAS〕与管理员删除〔行+blob 双清〕;面板两步确认 CDP 全绿)。 -29. ~~未完成引导的导航(tracker #8)~~ ✅ **已修并真机闭环**(第四十六批 #77:`403 setup_required` → 自动 `/setup`;CDP 复验)。 -30. ~~已装机系统服的新增 CR 字段(tracker #1)~~ ✅ **已修并真机闭环**(第四十六批 #78:`sudo felis converge`——零值才填、非零不覆写;剥字段→填回→幂等三连真机过)。 -31. ~~troubleshooting `[INERT]` 图例~~ ✅ **已修**(第四十六批 #79,文档级)。 -32. ~~稳定版发布与 release 安装/升级通道~~ ✅ **已实证**(第四十七批:tag `v0.1.0`(`b9c97ff`)+ release run `35950722509` 双资产、`/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〕)。 -33. ~~世界归档与数据库备份只在本机~~ ✅ **已修并真机闭环**(2026-09-24:`felis offsite` + `felis-offsite.timer` 每小时把世界归档与 DB bundle 以 AES-256-GCM 分段加密推到 S3 兼容桶,`world_backups.offsite_at` 记账〔迁移 0024〕;配了 `[offsite]` 后 reaper 只在归档的异地副本确认后才删 PVC;watchdog 12 小时无成功同步即告警;安装器 `FELIS_OFFSITE_*` 生成密钥并在收尾横幅要求离机保存。真机(MinIO):首次同步 8 世界 1.2 GiB + 12 bundle、两条"记录在册但盘上已无"的历史归档如实列出;`fetch-db latest` sha256 与本机一致、错误密钥拒绝且不留半成品;`fetch-worlds` 取回被挪走的归档 sha256 一致;reaper 演练〔20 天空闲世界〕第一轮 `awaiting_offsite=1` 且 PVC 保留 → 同步后第二轮 `reaped=1`、PVC 删除、所有权释放;watchdog 把 status 改成 35 小时前 → `[warning] offsite`。演练装置已清理)。 - -## 剩余待演练队列(截至第四十三批) - -- ~~reaper 多节点实机~~ ✅ 已完结(第三十九批:临时克隆第二节点组双节点 k3s 实机——pin 命中持盘节点并跑通、错位 pin fail-closed Pending;装置已回收)。 -- ~~面板交互级收尾~~ ✅ 已完结(第三十六–三十八批:管理面写操作、submissions/会话、服务器详情页全链,0 缺陷)。 -- ~~`demo-up.sh`~~ ✅ 已完结(第四十批:单起点化 #63——bootstrap 装机 + 三项硬校验 + `felis setup` 交棒;T1/T2/T3 真机全绿)。 -- ~~Java 插件层自动化~~ ✅ 已补(第四十批:`plugins/test.sh` + CI `plugins` 作业;三个手工测试与三个装机 jar 首进 CI)。 -- ~~装载器 mod 层(fabric/forge/neoforge)~~ ✅ 已收口(第四十一批:#64/#65 修复 + `plugins/test-mods.sh`/CI `mods`/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`(sha `a0b29153…`)、`/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_migrations` max=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: FELIS_WORLDS_HOST_PATH=/var/lib/rancher/k3s/storage nohup bash /opt/felis/src/deploy/bootstrap.sh > /root/bootstrap-.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-s3` Secret、DB 行(`image_submissions` `sub-853a4e2ba4ba6443`)、/tmp 残留均已清理,docker 已停 -- 第三十三批 drill 留档(VM):`/tmp/felis-fix53`、`/tmp/felis-fix54`(宿主二进制;sha `9afd141d…` / `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 改名件(sha `2c8c34fd…`,与 `/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 md5 `e6b07440…` 前后一致) -- pgint 正确跑法(走 VM 的 PG,勿在本机起容器):Mac 侧短隧道 `ssh -6 -i ~/.ssh/id_ed25519 -N -L 15433:127.0.0.1:5432 root@…` → `FELIS_TEST_PG_URL='postgres://felis:@localhost:15433/felis_pgint?sslmode=disable' go test -tags pgint ./internal/pgint/ -count=1`(pg_hba 需 `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//tags/list`;**取 manifest 必须带 `Accept:`(OCI index/manifest list;缺 Accept 的 GET 会 404,别误判没推上)**;GC 演练配方:`k3s ctr images rm `(要清到 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` = `b4ef42d` tarball 展开(无 `.git`);reaper `suspend=false` + `nodeSelector=localhost.localdomain`;test-one `Stopped`;`resolvecheck` 文件接口 409 `no_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 .`;`login . ` 打登录首包);插件门禁跑法 = `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 run `35888009965`(含新 `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- -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_IMAGE` env 必须成对改,否则构建 Job 的 fetch 容器会悄悄用旧镜像);`/opt/felis/src` = `b76d0ac` 快照(上一版 `src.bak43` = `2755e41`);复刻 Job `/root/job68-new.json`(由红证据 Job `build-bld-1790187851749257160` 派生:换名 `build-retry68`、剥 controller uid 标签与 selector、镜像改 auditfix63、复用 `felis-service-token` Secret);重演验证法:`kubectl -n felis scale deploy/felis-api --replicas=0` → `kubectl -n felis-build create -f /root/job68-new.json` → `kubectl -n felis-build logs -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//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/: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 digest `5766dfbb…`;两键在 `/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 ns `felis-reaper` CronJob + api/operator `FELIS_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-…`);玩家会话铸法 = `user2@example.net` 邮件 OTP(码在 felis-api 日志 `no Mailer configured` 行;`edge-dis2` 是软删死账号,登录门按设计排除);pgint 前置:`pg_hba` 需 `host felis_pgint felis 127.0.0.1/32 scram-sha-256`(本批第二次丢失并补回);发布链 smoke = tag `v0.1.0-rc1`(release run `35949621233`),prerelease 不移动 `/releases/latest`——无 stable 时 bootstrap 默认通道给出显式指引后 die、`felis update` 404 报错可读;首个稳定 tag 是产物决定(未代拍板)。 -- 第四十七批工具与留档:stable tag `v0.1.0` = `b9c97ff`(annotated;release run `35950722509`;资产 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 sha `106c4c9e…`→`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`(完整版,sha `89d0181d…`)、`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_hba `felis_pgint` 行**必须插在 `# END FELIS MANAGED HBA` 之后**(块内会在重装时被重写段删除——三轮同根因;本轮已按块外补回)。 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 61d1eb2..ca35761 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -212,13 +212,13 @@ export FELIS_IMAGE=felis:dev export FELIS_ROOT_DOMAIN=.nip.io ``` -By default the installer builds the newest **published GitHub release**. While this -repository is private that lookup — and the clone itself — needs a token, and building -the development tip needs an opt-in: +By default the installer builds the newest **published GitHub release**. Building the +development tip needs an opt-in, and installing from a private fork additionally needs a +token for the release lookup and the clone: ```bash -export FELIS_GITHUB_TOKEN= export FELIS_VERSION_BOOTSTRAP=dev # build main instead of the newest release +export FELIS_GITHUB_TOKEN= # private forks only: read access to the fork ``` `dev` is also the escape hatch before the first `vX.Y.Z` tag exists: with no published diff --git a/README.md b/README.md index 221ffc8..0521046 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,22 @@ -# Felis +
+

+ Felis logo
+ Felis +

+

+ 基于 Kubernetes 的 Minecraft 服务器托管平台
+ 单条命令完成部署,自动管理服务器生命周期、备份与安全 +

+ 简体中文 | English +

+
-**此项目仍处于早期开发阶段,您不该在任何生产环境使用该项目。若产生任何问题,贵用户的使用行为与 FelisMC 团队无任何民事刑事法律关系。** -**THIS PROJECT IS STILL WIP, YOU SHOULD DO NOT USE THIS PROJECT IN ANY PRODUCTION USAGE. WE ARE NOT RESPOND FOR ANY LEGAL OR HUMANLY PROBLEM.** +> [!CAUTION] +> **此项目仍处于早期开发阶段,您不该在任何生产环境使用该项目。若产生任何问题,贵用户的使用行为与 FelisMC 团队无任何民事刑事法律关系。**
+> **THIS PROJECT IS STILL WIP, YOU SHOULD DO NOT USE THIS PROJECT IN ANY PRODUCTION USAGE. WE ARE NOT RESPOND FOR ANY LEGAL OR HUMANLY PROBLEM.** -一款 Kubernetes 驱动的 Minecraft 服务器托管平台,一行命令部署,自动管理生命周期与安全。 -A Kubernetes-driven Minecraft server hosting platform — one command to deploy, automatic lifecycle, backup, and security. - -[简体中文](README.md) | [English](README_EN.md) - -目录 +
+目录 - [特性](#特性) - [使用方式](#使用方式) @@ -16,53 +24,73 @@ A Kubernetes-driven Minecraft server hosting platform — one command to deploy, - [开源协议](#开源协议) - [致谢](#致谢) +
+ ## 特性 -- **即开即玩**:玩家尝试连接时自动唤醒服务器,空闲后自动休眠,像游戏主机一样省资源。 -- **Web 控制面板**:浏览器中查看服务器状态、在线玩家与资源用量,管理备份与恢复。 -- **备份与恢复**:一键把整服数据(世界、配置、插件/模组,即整个 /data 卷)打包进集群内的归档库,支持从任意备份点回滚;默认安装就已启用(归档 PVC 与路径由安装器一并生成)。 -- **控制面数据库备份**:账号、服务器归属、配额与存档索引所在的数据库每天自动备份,每次升级迁移前先快照,出错可用 `felis db restore` 整库原子回滚;面板「维护与备份」页显示备份是否新鲜(见 [故障排查 §16](docs/troubleshooting.md))。 -- **运维自检**:`sudo felis status` 一屏列出节点、控制面、游戏代理、每台服务器、备份与未解决的告警;`sudo felis doctor` 把健康检查全跑一遍,按区域给出问题和下一步去哪看,不发邮件;`sudo felis support-bundle` 打出一个脱敏的诊断包,求助时直接附上(见 [故障排查 §0](docs/troubleshooting.md))。 -- **智慧回收(可选开启)**:超过 15 天无人游玩的世界自动备份后删除,释放磁盘空间;安装时设置 `FELIS_WORLDS_HOST_PATH`(k3s 默认 `/var/lib/rancher/k3s/storage`)即启用每日回收,不设置则不删任何世界。过期备份无论是否开启都会每天清理。 -- **多核心支持**:兼容 Paper、Fabric、Forge、NeoForge,经由 Velocity 代理统一入口。 -- **模组自助提交**:玩家自行上传模组包,服主审批通过后自动构建;构建产物进入镜像白名单,可直接选用为服务器镜像完成部署。 -- **Passkey 登录**:支持指纹、面容、硬件密钥等无密码认证方式。 -- **零信任安全**:面板流量由 Cloudflare Access 保护,集群内 API 不暴露到公网。 +* **按需启停**:玩家连接代理时自动启动目标服务器,启动期间玩家进入等待队列,服务器就绪后自动传送;服务器空闲后自动停止,释放内存。 + +* **Web 控制面板**:在浏览器中查看服务器状态、在线玩家与资源用量。 + * 控制台(RCON)、白名单、封禁、OP 与 LuckPerms 权限管理 + * 文件管理:新建、删除、重命名、分片上传、下载,以及停服状态下解压 zip,可用于导入世界 + * 计划任务:按星期与时区定时执行命令、重启、停止、启动或备份,执行前在游戏内向玩家发送提醒 + +* **备份与恢复**:默认启用,归档 PVC 及其路径由安装器生成。 + * 手动备份:将服务器的完整数据卷(`/data`,含世界、配置、插件与模组)归档至集群内的归档存储,可回滚至任一备份点 + * 每日恢复点:当天有玩家进入过的服务器在停止后自动生成恢复点,默认保留 7 个,保存期限 90 天;恢复点单独轮换,不影响手动备份 + * 下载与导出:支持下载单个备份(附 sha256 校验)、删除单个备份及导出完整世界 + * 异地副本(可选):备份在主机上加密后同步至 S3 兼容存储(AWS S3、Cloudflare R2、Backblaze B2、MinIO 等) + * 控制面数据库:存放账号、服务器归属、配额与存档索引的数据库每日自动备份,每次升级迁移前额外创建快照,故障时可通过 `felis db restore` 整库原子回滚;面板「维护与备份」页显示最近一次备份的时效(参见 [故障排查 §16](docs/troubleshooting.md)) + +* **运维诊断** + * `sudo felis status`:汇总显示节点、控制面、游戏代理、各服务器、备份及未解决的告警 + * `sudo felis doctor`:执行全部健康检查,按模块列出问题及排查方向,执行过程中不发送邮件 + * `sudo felis support-bundle`:生成已脱敏的诊断包,供提交问题时附带(参见 [故障排查 §0](docs/troubleshooting.md)) + * 看门狗:每 2 分钟执行一次巡检,异常持续时向平台所有者发送邮件告警,支持外部心跳监测 + +* **世界回收(可选)**:超过 15 天无人游玩的世界在备份后删除,以释放磁盘空间。安装时设置 `FELIS_WORLDS_HOST_PATH`(k3s 默认为 `/var/lib/rancher/k3s/storage`)即启用每日回收;未设置时不删除任何世界。过期备份的每日清理与此设置无关,始终执行。 + +* **多核心支持**:兼容 Paper、Fabric、Forge 与 NeoForge,统一经由 Velocity 代理接入。 + +* **模组包投稿**:玩家可上传模组包,经服主审批后自动构建并通过 Trivy 安全扫描;构建产物加入镜像白名单后,可直接选作服务器镜像。 + +* **安全** + * Passkey 登录:支持指纹、面容识别及硬件密钥等无密码认证方式 + * 零信任访问:面板流量经 Cloudflare Access 保护,集群内部 API 不对公网开放 + +* **多机部署(实验性,默认关闭)**:由一台主控节点统一下发指令,其余节点仅运行游戏服务器,已停止的服务器可迁移至其他节点。该功能目前仅位于 main 分支,尚未完成三机验收(参见 [多机部署](docs/distributed.md))。 ## 使用方式 -在准备好的 Linux 主机上执行(已验证的发行版与架构见 [运维手册 §1](docs/operations.md#1-supported-hosts):CentOS Stream 9 aarch64 实机验证,Ubuntu 24.04 x86_64 每次推送由 CI 跑全新安装、重跑、升级和下面这条命令本身): +在已准备好的 Linux 主机上执行: ```bash curl -fsSL https://raw.githubusercontent.com/FelisMC/Felis/main/deploy/bootstrap.sh | sudo bash ``` -脚本将自动安装 K3s,在 K3s 内部署 PostgreSQL 与控制平面,并启动设置向导。完成后浏览器访问已配置的域名进入控制面板即可使用。 +脚本将安装 K3s,在 K3s 中部署 PostgreSQL 与控制平面,随后启动设置向导。设置完成后,通过浏览器访问所配置的域名即可进入控制面板。 -安装发布版时,二进制、全部镜像与 Velocity 插件都取自该版本在 CI 里预构建好的 release 附件,逐个核对 `SHA256SUMS` 后导入,主机上无需 Docker、Gradle 或 Go,也不从 Docker Hub 拉取;某个附件缺失或校验不符时,只有那一个镜像退回到本机构建,并给出提示(见 [故障排查 §15c](docs/troubleshooting.md))。附件也可以先拷到本机,再用 `FELIS_ARTIFACT_DIR=<绝对路径>` 安装,Felis 自己的二进制、镜像和插件就都取自这个目录;k3s 及其镜像、JRE、cloudflared、Velocity 和 Via 插件照旧从 GitHub 与 PaperMC 下载,RHEL、Fedora、openSUSE Leap 这类开着 SELinux 的主机还要从 rpm.rancher.io 装 k3s-selinux,系统软件包来自发行版的源。所以出网受限的主机要放行这几处的 HTTPS(或设 `https_proxy`),preflight 会在改动主机之前逐个探测,完全断网的主机目前装不了(地址清单见 [运维手册 §1](docs/operations.md#1-supported-hosts))。旧版本装在宿主上的 PostgreSQL 会在重跑时整库迁进 K3s,宿主上的那份停用保留,供回退(见 [运维手册 §4](docs/operations.md#4-upgrading-the-pieces-around-felis))。 +* **支持的系统**:CentOS Stream 9(aarch64)已在实机上验证;Ubuntu 24.04(x86_64)在每次推送时由 CI 执行全新安装、重复安装、升级及上述安装命令(参见 [运维手册 §1](docs/operations.md#1-supported-hosts))。 -动手之前,脚本先检查内存、磁盘、端口、网段冲突、已有的 Kubernetes 和外网连通,把所有问题一次列出并停下,主机上什么都没改(检查项见 [运维手册 §1](docs/operations.md#1-supported-hosts))。 +* **安装前检查**:安装器在修改主机之前检查内存、磁盘、端口、网段冲突、已有的 Kubernetes 及外网连通性。发现问题时一次性列出全部问题并退出,主机保持原状(检查项参见 [运维手册 §1](docs/operations.md#1-supported-hosts))。 -> **本仓库当前为私有**,上面这条会返回 404。请改用带凭据的形式;安装器自身也需要同一个 token -> 去解析并下载 release,所以用 `sudo -E` 把它带进去: -> -> ```bash -> export FELIS_GITHUB_TOKEN=<对本仓库有读权限的 token> -> printf 'header = "Authorization: Bearer %s"\n' "$FELIS_GITHUB_TOKEN" \ -> | curl -fsSL --config - -H "Accept: application/vnd.github.raw" \ -> https://api.github.com/repos/FelisMC/Felis/contents/deploy/bootstrap.sh \ -> | sudo -E bash -> ``` -> -> token 经 stdin 交给 `curl --config -`,不放在命令行上:argv 在 `/proc` 下对本机任意用户可读, -> 而这正是安装器内部 `github_api` 采用同一写法的原因。 +* **升级**:重新执行安装命令即可将 felis-api 升级至新版本;`felis setup` 仅使用本机已安装的二进制,无法用于升级。重新执行时沿用已安装的根域名,发布通道需重新指定:跟随 main 分支的主机须同时设置 `export FELIS_VERSION_BOOTSTRAP=dev`。早期版本安装在宿主机上的 PostgreSQL 会在重新执行时整库迁入 K3s,宿主机上的原实例停用并保留,以便回退(参见 [运维手册 §4](docs/operations.md#4-upgrading-the-pieces-around-felis))。 -重跑这条命令也是把 felis-api 升到新版本的方式(`felis setup` 做不到,它用的是本机已有的二进制)。 -重跑会沿用已安装的根域名,但**不会**沿用通道:若本机跟随 main,需一并 `export FELIS_VERSION_BOOTSTRAP=dev`。 +
+安装来源与受限网络环境下的安装 +
+ +安装发布版时,二进制文件、全部镜像及 Velocity 插件均取自该版本由 CI 预构建的 release 附件,逐一校验 `SHA256SUMS` 后导入。主机无需安装 Docker、Gradle 或 Go,也无需访问 Docker Hub。若某个附件缺失或校验失败,仅该镜像回退为本机构建,并输出提示(参见 [故障排查 §15c](docs/troubleshooting.md))。 + +也可将附件预先复制到主机,再通过 `FELIS_ARTIFACT_DIR=<绝对路径>` 安装,此时 Felis 自身的二进制、镜像与插件均从该目录读取。k3s 及其镜像、JRE、cloudflared、Velocity 与 Via 插件仍从 GitHub 和 PaperMC 下载;RHEL、Fedora、openSUSE Leap 等启用 SELinux 的主机还需从 rpm.rancher.io 安装 k3s-selinux;系统软件包来自发行版软件源。 + +因此,出站网络受限的主机须放行上述地址的 HTTPS 访问,或设置 `https_proxy`。preflight 会在修改主机之前逐一探测这些地址。目前暂不支持完全离线安装(地址清单参见 [运维手册 §1](docs/operations.md#1-supported-hosts))。 + +
## 从源码构建 -本项目基于 Go 和 Node.js 开发: +本项目基于 Go 与 Node.js 开发: ```bash # 后端(Go 1.26+) @@ -79,22 +107,22 @@ docker build -t felis:custom . ## 开源协议 -本项目遵循 [AGPL-3.0-only](LICENSE) 开源协议。 +本项目采用 [AGPL-3.0-only](LICENSE) 许可证。 ### 协议注意事项 -1. **衍生作品同样是 AGPL**:分发本项目的副本或基于本项目衍生的软件时,必须以 AGPL-3.0 开源,并保留原作者的版权声明和许可声明。 -2. **通过网络提供服务同样要开源**(AGPL 第 13 条):如果你把修改过的 Felis 架起来给别人用,即使从不分发任何二进制,也必须向这些用户提供你那份修改后的完整源码。这是 AGPL 相对 GPL 的唯一实质区别,而 Felis 正是一个跑在网络上的托管平台,所以这一条基本总会触发。 -3. **免责声明**:本项目按"原样"提供,作者不承担任何因使用本项目而产生的法律责任。 +1. **衍生作品须采用 AGPL**:分发本项目副本或基于本项目的衍生软件时,须以 AGPL-3.0 开源,并保留原作者的版权声明与许可声明。 +2. **网络服务同样须提供源码**(AGPL 第 13 条):通过网络向他人提供经修改的 Felis 服务时,即使未分发任何二进制文件,也须向这些用户提供修改后的完整源码。这是 AGPL 与 GPL 唯一的实质区别;Felis 作为通过网络访问的托管平台,几乎所有部署场景都适用此条款。 +3. **免责声明**:本项目按"原样"提供,作者不承担因使用本项目而产生的任何法律责任。 ## 致谢 -- [Kubernetes](https://kubernetes.io/):底层容器编排引擎 -- [K3s](https://k3s.io/):轻量级 Kubernetes 发行版 -- [Cloudflare Zero Trust](https://www.cloudflare.com/zero-trust/):零信任安全基础设施 -- [PostgreSQL](https://www.postgresql.org/):数据持久化 -- [React](https://react.dev/):前端用户界面框架 -- [Vite](https://vitejs.dev/):前端构建工具 -- [TailwindCSS](https://tailwindcss.com/):CSS 框架 -- [Bubble Tea](https://github.com/charmbracelet/bubbletea):TUI 框架 -- [Minecraft](https://www.minecraft.net/):让这一切值得做 +* [Kubernetes](https://kubernetes.io/):容器编排引擎 +* [K3s](https://k3s.io/):轻量级 Kubernetes 发行版 +* [Cloudflare Zero Trust](https://www.cloudflare.com/zero-trust/):零信任安全基础设施 +* [PostgreSQL](https://www.postgresql.org/):数据持久化 +* [React](https://react.dev/):前端用户界面框架 +* [Vite](https://vitejs.dev/):前端构建工具 +* [TailwindCSS](https://tailwindcss.com/):CSS 框架 +* [Bubble Tea](https://github.com/charmbracelet/bubbletea):TUI 框架 +* [Minecraft](https://www.minecraft.net/):本项目服务的游戏 diff --git a/README_EN.md b/README_EN.md index 7bd9752..683f58d 100644 --- a/README_EN.md +++ b/README_EN.md @@ -1,11 +1,21 @@ -# Felis +
+

+ Felis logo
+ Felis +

+

+ A Kubernetes-driven Minecraft server hosting platform
+ One command to deploy, with automatic lifecycle, backup, and security +

+ 简体中文 | English +

+
-A Kubernetes-driven Minecraft server hosting platform — one command to deploy, automatic lifecycle, backup, and security. -一款 Kubernetes 驱动的 Minecraft 服务器托管平台,一行命令部署,自动管理生命周期与安全。 +> [!CAUTION] +> **THIS PROJECT IS STILL WIP, YOU SHOULD DO NOT USE THIS PROJECT IN ANY PRODUCTION USAGE. WE ARE NOT RESPOND FOR ANY LEGAL OR HUMANLY PROBLEM.** -[简体中文](README.md) | [English](README_EN.md) - -Table of Contents +
+Table of Contents - [Features](#features) - [Getting Started](#getting-started) @@ -13,54 +23,69 @@ Table of Contents - [License](#license) - [Acknowledgements](#acknowledgements) +
+ ## Features -- **Wake on Join**: Servers start automatically when a player connects, and stop when idle — like hibernate for your server. -- **Web Dashboard**: Monitor server status, online players, and resource usage from your browser, with backup and restore management. -- **Backup & Restore**: One-click snapshots of a server's whole data volume (worlds, config, plugins/mods — the entire /data volume) into the cluster's archive store, with rollback from any backup point — enabled by default (the installer renders the archive PVC and its path). -- **Control-plane database backups**: The database holding accounts, server ownership, quotas and the archive index is backed up daily and snapshotted before every upgrade migrates it; `felis db restore` rolls it back atomically, and the panel's Maintenance & Backups page shows whether the newest backup is fresh (see [troubleshooting §16](docs/troubleshooting.md)). -- **Self-check for operators**: `sudo felis status` shows the node, the control plane, the game proxy, every server, the backups and the open alerts on one screen; `sudo felis doctor` runs every health check once and lists each problem by area with where to look next, mailing nothing; `sudo felis support-bundle` writes one redacted diagnostics archive to attach when asking for help (see [troubleshooting §0](docs/troubleshooting.md)). -- **World Reaper** (opt in): Worlds idle for more than 15 days are automatically backed up and removed to free disk space. Enable it by setting `FELIS_WORLDS_HOST_PATH` at install time (on k3s: `/var/lib/rancher/k3s/storage`); without it, no world is ever deleted. -- **Multi-core Support**: Compatible with Paper, Fabric, Forge, and NeoForge, federated behind a Velocity proxy. -- **Modpack Submission**: Players submit custom modpacks; admin approval triggers an automatic build, and the result is whitelisted as a server image you can select to deploy. -- **Passkey Login**: Passwordless authentication via fingerprint, face recognition, or hardware security keys. -- **Zero Trust Security**: Panel traffic protected by Cloudflare Access; the internal API is never exposed to the internet. +* **On-demand Start and Stop**: A server starts when a player connects to the proxy. The player waits in a queue during start-up and is transferred once the server is ready. Idle servers stop automatically to free memory. + +* **Web Dashboard**: Monitor server status, online players, and resource usage from your browser. + * Console (RCON), whitelist, bans, OPs and LuckPerms permissions + * File manager: create, delete, rename, chunked upload, download, and unzip while the server is stopped; also used to import worlds + * Schedules: run commands, restart, stop, start or back up by weekday and time zone, with an in-game warning to players beforehand + +* **Backup & Restore**: Enabled by default; the installer renders the archive PVC and its path. + * Manual backups: archive a server's entire data volume (`/data`, including worlds, configuration, plugins and mods) to the cluster's archive store, with rollback to any backup point + * Daily restore points: a server played that day gets a restore point once it stops; by default 7 are kept for up to 90 days, rotated separately from manual backups + * Download and export: download a single backup (with sha256 verification), delete a single backup, or export a whole world + * Off-site copy (optional): backups are encrypted on the host and synced to S3-compatible storage (AWS S3, Cloudflare R2, Backblaze B2, MinIO and others) + * Control-plane database: the database holding accounts, server ownership, quotas and the archive index is backed up daily and snapshotted before every upgrade migration; `felis db restore` rolls it back atomically, and the panel's Maintenance & Backups page shows the age of the latest backup (see [troubleshooting §16](docs/troubleshooting.md)) + +* **Diagnostics** + * `sudo felis status`: a summary of the node, control plane, game proxy, each server, backups and open alerts + * `sudo felis doctor`: runs all health checks and lists problems by area with troubleshooting pointers; sends no email + * `sudo felis support-bundle`: generates a redacted diagnostics archive to attach to support requests (see [troubleshooting §0](docs/troubleshooting.md)) + * Watchdog: runs a check every 2 minutes and emails the platform owners when a problem persists; supports an external heartbeat monitor + +* **World Reaper** (optional): Worlds idle for more than 15 days are backed up and then removed to free disk space. Enable it by setting `FELIS_WORLDS_HOST_PATH` at install time (on k3s: `/var/lib/rancher/k3s/storage`); without it, no world is deleted. Expired backups are cleaned up daily regardless of this setting. + +* **Multi-core Support**: Compatible with Paper, Fabric, Forge, and NeoForge, accessed through a single Velocity proxy. + +* **Modpack Submission**: Players can upload modpacks. After admin approval, each modpack is built automatically and scanned with Trivy; the result is added to the image whitelist and can be selected as a server image. + +* **Security** + * Passkey login: passwordless authentication via fingerprint, face recognition, or hardware security keys + * Zero-trust access: panel traffic is protected by Cloudflare Access, and the internal API is not exposed to the internet + +* **Multi-node Deployment** (experimental, off by default): a single controller node issues all commands, the other nodes run game servers only, and a stopped server can be migrated to another node. Currently available only on the main branch; three-node acceptance testing is not yet complete (see [distributed mode](docs/distributed.md), in Chinese). ## Getting Started -On a prepared Linux host, run (the verified distributions and architectures are listed in -[operations §1](docs/operations.md#1-supported-hosts): CentOS Stream 9 on aarch64 is verified -on a real host, and Ubuntu 24.04 on x86_64 gets a fresh install, rerun and upgrade in CI on -every push): +On a prepared Linux host, run: ```bash curl -fsSL https://raw.githubusercontent.com/FelisMC/Felis/main/deploy/bootstrap.sh | sudo bash ``` -The script installs K3s, deploys PostgreSQL and the control plane inside it, and launches a setup wizard. Once done, open your browser at the configured domain. A PostgreSQL an earlier release installed on the host is moved into K3s on the next rerun, and the host copy is stopped and kept for a rollback (see [Operations §4](docs/operations.md#4-upgrading-the-pieces-around-felis)). +The script installs K3s, deploys PostgreSQL and the control plane inside it, and launches a setup wizard. When setup completes, open the configured domain in a browser to reach the control panel. -Before it changes anything, the script checks RAM, disk, ports, network-range clashes, any Kubernetes already there and outbound access, lists every problem at once and stops with the host untouched (the checks are in [operations §1](docs/operations.md#1-supported-hosts)). +* **Supported hosts**: CentOS Stream 9 (aarch64) is verified on physical hardware; Ubuntu 24.04 (x86_64) is tested in CI on every push with a fresh install, a rerun, an upgrade and the install command above (see [operations §1](docs/operations.md#1-supported-hosts)). -> **This repository is currently private**, so the command above returns 404. Use the -> credentialed form instead; the installer itself needs the same token to resolve and -> download the release, so pass it through with `sudo -E`: -> -> ```bash -> export FELIS_GITHUB_TOKEN= -> printf 'header = "Authorization: Bearer %s"\n' "$FELIS_GITHUB_TOKEN" \ -> | curl -fsSL --config - -H "Accept: application/vnd.github.raw" \ -> https://api.github.com/repos/FelisMC/Felis/contents/deploy/bootstrap.sh \ -> | sudo -E bash -> ``` -> -> The token reaches `curl --config -` over stdin instead of the command line: argv is -> readable by any local user via `/proc`, and that is exactly why the installer's -> internal `github_api` uses the same form. +* **Preflight checks**: Before modifying the host, the installer checks memory, disk, ports, network range conflicts, existing Kubernetes installations and outbound connectivity. If any check fails, it lists all problems and exits, leaving the host unchanged (see [operations §1](docs/operations.md#1-supported-hosts) for the checks). -Rerunning this command is also how you upgrade felis-api to a newer version (`felis setup` -cannot — it uses the binary already installed on the host). The rerun keeps the installed -root domain but **not** the channel: if this host follows main, also -`export FELIS_VERSION_BOOTSTRAP=dev`. +* **Upgrading**: Rerun the install command to upgrade felis-api to a newer version; `felis setup` only uses the binary already installed on the host and cannot upgrade it. A rerun keeps the installed root domain, and the release channel must be specified again: hosts that follow the main branch must also set `export FELIS_VERSION_BOOTSTRAP=dev`. A PostgreSQL instance installed on the host by an earlier release is migrated into K3s during the rerun; the original instance on the host is stopped and retained for rollback (see [operations §4](docs/operations.md#4-upgrading-the-pieces-around-felis)). + +
+Installation sources and restricted networks +
+ +A release installation takes the binary, all images and the Velocity plugin from the release assets prebuilt in CI, verifying each against `SHA256SUMS` before import. The host requires no Docker, Gradle or Go, and no access to Docker Hub. If an asset is missing or fails verification, only that image falls back to a local build, and the installer prints a notice (see [troubleshooting §15c](docs/troubleshooting.md)). + +The assets can also be copied to the host in advance and installed with `FELIS_ARTIFACT_DIR=`; the Felis binary, images and plugin are then read from that directory. k3s and its images, the JRE, cloudflared, Velocity and the Via plugins are still downloaded from GitHub and PaperMC; hosts with SELinux enabled, such as RHEL, Fedora and openSUSE Leap, additionally install k3s-selinux from rpm.rancher.io; system packages come from the distribution's repositories. + +A host with restricted outbound access must therefore allow HTTPS to these addresses or set `https_proxy`. Preflight probes each address before changing the host. Fully offline installation is not yet supported (see [operations §1](docs/operations.md#1-supported-hosts) for the address list). + +
## Build from Source @@ -81,22 +106,22 @@ docker build -t felis:custom . ## License -The source code is released under [AGPL-3.0-only](LICENSE). +This project is licensed under [AGPL-3.0-only](LICENSE). ### License Notes -1. **Derivative works are AGPL too**: Any distribution of this project or of software derived from it must be released under AGPL-3.0 and must include the original copyright notice and license statement. -2. **Running it as a network service also triggers the source obligation** (AGPL section 13): if you host a modified Felis for other people to use, you must offer those users the complete source of your modified version — even if you never distribute a binary. This is the one substantive difference between AGPL and GPL, and since Felis is a hosting platform reached over a network, it will essentially always apply. +1. **Derivative works must use AGPL**: Any distribution of this project or of software derived from it must be released under AGPL-3.0 and must include the original copyright notice and license statement. +2. **Network services must also provide source** (AGPL section 13): anyone who offers a modified Felis to others over a network must provide those users with the complete source of the modified version, even without distributing any binary. This is the only substantive difference between AGPL and GPL; as Felis is a hosting platform accessed over a network, this clause applies to virtually every deployment. 3. **Disclaimer**: This project is provided "as is", without warranty of any kind. ## Acknowledgements -- [Kubernetes](https://kubernetes.io/): Container orchestration engine -- [K3s](https://k3s.io/): Lightweight Kubernetes distribution -- [Cloudflare Zero Trust](https://www.cloudflare.com/zero-trust/): Zero trust security infrastructure -- [PostgreSQL](https://www.postgresql.org/): Data persistence -- [React](https://react.dev/): User interface framework -- [Vite](https://vitejs.dev/): Frontend build tool -- [TailwindCSS](https://tailwindcss.com/): CSS framework -- [Bubble Tea](https://github.com/charmbracelet/bubbletea): TUI framework -- [Minecraft](https://www.minecraft.net/): What makes this all worthwhile +* [Kubernetes](https://kubernetes.io/): Container orchestration engine +* [K3s](https://k3s.io/): Lightweight Kubernetes distribution +* [Cloudflare Zero Trust](https://www.cloudflare.com/zero-trust/): Zero trust security infrastructure +* [PostgreSQL](https://www.postgresql.org/): Data persistence +* [React](https://react.dev/): User interface framework +* [Vite](https://vitejs.dev/): Frontend build tool +* [TailwindCSS](https://tailwindcss.com/): CSS framework +* [Bubble Tea](https://github.com/charmbracelet/bubbletea): TUI framework +* [Minecraft](https://www.minecraft.net/): The game this project serves diff --git a/cmd/felis/update.go b/cmd/felis/update.go index f94f6a2..d9b7a69 100644 --- a/cmd/felis/update.go +++ b/cmd/felis/update.go @@ -62,9 +62,7 @@ type updateTarget struct { // The URL is the one-liner both READMEs hand out, read at a tag rather than main: the // script's release channel installs the newest release's binary, and main can carry // installer changes that binary was never tested with. installerRef picks the tag and -// renderApplyGuidance substitutes it for {ref}. While the repo is private the URL -// answers 404 (raw.githubusercontent.com hides private repos), which is why the trailer -// below points at the README's token'd form for that case. +// renderApplyGuidance substitutes it for {ref}. const installerRerun = "curl -fsSL https://raw.githubusercontent.com/FelisMC/Felis/{ref}/deploy/bootstrap.sh | sudo bash" // installerRerunDeps is the same re-run with FELIS_UPGRADE_DEPS=1, which lets it move an @@ -436,12 +434,11 @@ func renderApplyGuidance(res updater.Result, selected map[string]bool, force boo // completed install (shouldRunHostBootstrapBeforeConfig only enters the host // bootstrap while an install marker is missing), so the installer re-run is the one // worked path for every component Felis installs and there is no per-component exception left - // to scope. Two caveats stay because following the advice without them bites real + // to scope. One caveat stays because following the advice without it bites real // hosts: the channel is not persisted anywhere (a bare re-run on a main host quietly - // moves it onto releases), and the private repo's one-liner needs the read token - // back in the environment before it can resolve anything. + // moves it onto releases). if offeredInstaller { - b.WriteString("\nRe-running the installer applies each installer command above: it fetches the newest version on\nthe channel in effect and re-applies the bundle (release is the default). The channel\nis not persisted, so pass FELIS_VERSION_BOOTSTRAP=dev if this host tracks main. While\nthis repo is private, the one-liner above 404s without a token; the README's install\nsection has the token'd form that works. felis setup is not this path: on a completed\ninstall it opens the config console and installs nothing newer. Restart game servers\nafterwards.\n") + b.WriteString("\nRe-running the installer applies each installer command above: it fetches the newest version on\nthe channel in effect and re-applies the bundle (release is the default). The channel\nis not persisted, so pass FELIS_VERSION_BOOTSTRAP=dev if this host tracks main.\nfelis setup is not this path: on a completed install it opens the config console and\ninstalls nothing newer. Restart game servers afterwards.\n") } return b.String() } diff --git a/deploy/bootstrap.sh b/deploy/bootstrap.sh index e64875f..e5727af 100644 --- a/deploy/bootstrap.sh +++ b/deploy/bootstrap.sh @@ -87,7 +87,7 @@ # Docker Hub, nor built (FELIS_GAME_STACK=latest aside: no release # ships that stack, so its game images are built here). A file missing # from it or not matching its SHA256SUMS stops the install. -# FELIS_GITHUB_TOKEN GitHub token; REQUIRED while the repo is private +# FELIS_GITHUB_TOKEN GitHub token; needed only when installing from a private fork # FELIS_REF branch/tag/sha — pins the build, overrides the channel, and forces a # source build (naming a ref asks for that tree, not a published asset) # FELIS_RELEASE a published release tag (v1.2.3) the release channel installs, from @@ -197,7 +197,7 @@ DOCKER_INSTALLED="" # hour. RELEASE_JSON_TAG="" RELEASE_JSON="" -# Optional GitHub credential, needed while this repository is private: GitHub answers +# Optional GitHub credential, needed only for a private fork: GitHub answers # 404 (not 403) for a repo the caller cannot see, so without it both the release lookup # and the clone fail as "not found". Exported because git's credential helper below runs # as a child process and reads it from the environment — which is also why it is never diff --git a/docs/assets/felis-logo.png b/docs/assets/felis-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c7b794c5663e6a87483c96feffabd4182a5b8478 GIT binary patch literal 211187 zcmYg%V_;-Ww{`4HY}?69Y}>ZgnP4)pZBJ};%#Ll_6K9f4Y@1)6=Y8+J-`{h(s!mtc zuD$kJt0I&YrH~Qu5x~H}kY%LBRl&d@2*JR>HQ-=ApA?F4N`Qf(g2{-BsJnxoX2a&` zOL<&;Y_4=FG?%UCS8TMKuOwKk+ePpF(ErNZGE&E6Fq&eQ6&y@S>2V|sA>;0mWGw1n zZ%j%F1s4n!AS^8^S`Cg4(e$lFrulU0aq&YYEt;WKk)N|`Qj6`2ssS&i_xR7 z{om({NUtM+yt|$&if*VXsQ>M2Ycn$-<$WtR;Il=qAF>|B|Mm3QV)@@OhTqW}(f`}i z<;A41+-61q$p@a5z|Yr5qXNy)|9|)(Ul?5!LfAJdTjc+I=7VKkk^Mhk;d{$A!xXRkj;G@G>h6nuKG(NdWViF@dI7id*R82FknhGJ(Hcv~d+fP}dbx zJ4wqC2xp`AWbur_ZQbu_Aa^XD@!VK8-ClckHC3p0zqnui3XfJm>!TT#;&$&Yd>ckA1z|b~D%Mo?Mi> zFP%<>ZjT_I`Gfbr9e7UfOY297zHS@QPu*txtUenTli96XDEnU~TkXuBdpaq^oIad? z3~$H1_Tvz@IW1*YcRqx;ZT@_n;sg2YO`>dE<{Q|r_?{~~ybYXvoQPGspDph$n|Tcn zH}6g9Df~BF0W(OK61ERH%@-bLhn)qPhPSbLz77XxYeru4f4J-k4~;H!T7&90x#xtg zouU9toeoD67O6}mx}KVTTASM&QyU%v*_}?F#dlLGvTJ`{a6A3Mc-*f!@jXZ>!r0|u8EiL@Qh>wrZi_pbMhhiAvx zYkXCHh0X2uh1C`Ig}Kn1TvdKQNUq<%AM$zuUk>X05ANI^SPn0ajT#)ypUTD$82!%c z{uZUOkm$SKOqf11QLycIxE3~F1Y8I`J$n7<@;dPP;bDAxILlYv=5U|~5=f>w_255y zxFmIa^85OhqVwMwBzeJezaFKLa_?EbJ7?+idY|HRCGI={o&0Eh3J@5^G`nlJbo;Wt zSaoi|5UP2mE!eSrdNgmzczkk7baZr_W1y~HSW0(gr6R|nP%N|X8lbox(5EbXl@$+` zBgi=U^)5%b@Y zFb?|7oa22Tvgt^T?ecQATKdE1N3}qGp7Dq4LgVHcTflzrax4+xtVD8ta!eH6;l6h_ zUN=e0Rk;Z>O&<1LkY~v~5L6E@Yh2d9!!#U0-XkMm(An=Z|7`i-Hi?_QB2Z&{mvx6z ziL32%_IecM>v>+uW1p3_{PJwzrvH$UdA?SEW#{+M)Eu|rGTDaXyPFS6Qr&d7+l+}_ z3jA*f!;T~Ube&v(YRb0x;qjuB?Q!$QW$$}3eD1Zg)qwXfagOIP8<{@NqQld&9oz-# z0-jIhn3Nd`B0UoB*@^oq^y29~j)JXoyMNme>Bqv8)|m?KnS<#FxM|%0o7#R&DbS=T z6!GPN2#(DEC6rw1FE-!)jANTsf3-eXk4>Z{9(~?%vYL7(7uV@*Yie|I!FTJ&GWcO| zw+Vc@I@>ApQTpTlx&7ZHJPTcyem>0HCyOymU6P-iXK*%8^_e%)zg{JZno+^_k1{F^ zR;m_8#KhxAEu~q+v1B6ZjJ`yyzYKJ>Fpz^uaRGX03K1$gn+pI_w^Lm_Hep)1O+TiN zaVqijeQyBoM~RuAXVZt(g_hU(n#&7Ex8sgL`@^~C4oHFJbp)~fbdoESXNc*)N%3;$ zE2LRtZLSM(R#3fbm&LS6yZxreJZR0eqs_hJ9kzeovcp;BJ$0mv;rxP*)fnpD_KmYd zOR}&Buw18uard6Nhavy!Ti5&P^aX(y z|0Qn#2-nc#O?tY+h$!pZTY!&|&+B=%+o}5sB8m6k6rs0c5<5Zyf_C8~pTr$MFzbc? z?z#^^LeXeccWSzX41rwa#4=O>Q?LW1v4tuNFMz5)b9 z@{@jIpeoo{NNNvT)6*g@&COe(A6+k@-C1i~jy_6Sn-d_=cQF~4r{+%Hs01wf+RI4t z0my3BFEYZWwe+b2qR{T>Uv{y3lUbXW(^Hd$Il<+C(c@Si@aEPR*N`n05!VAex zfd{j>`szp7ivsKgfT2LFeuk-ZA)kA!o07PW=XaFNj|#3o-8V^pQ1o5)m{3lwJFjop zoBji&fKy=htiHu7y$WvY&KHLQOXv8VH>+z=o^M!o$=i`dL2^_2ybbPhN?H$K2U^MV z^1V7kwGjjdL}vjD087Cy^&$yz;v5sI0&w7@;c!z!q;tL({}#w2E+1iTw^nl{}=fq?!P7QD{WHCQ+hWIC>*Mjg^xF5TDKD>D|)giT>-Hu0nVB&Hb4@ekWiUd1=$(X!YuOIaTgN zbL~Hfc?*^eGBGg`@OszneH--fe%5Kf>~R2ET8diz$XM#!!qNHyYO`9HImYl>^@0(N zAmAL<7G&Vr@@|^-5g!3CS|_QGm;BCh$%+DJskyk(41*3CPz%#Xm_XQ8=T`H%UbL3M zNK7mh6FBSKMl4#4sho9?i=ACuC^^i!1021^OfQ6l8I?M5W>w8=+AYA#Db75v*l^77 z$Vqf6hd6bWs*hHAD?4;6ie$B)7l`^nJ4<#$CPr9$>b9t-ol<*J#JIB7;U73~> zC)r+%-9%G#ksZPKQBg6#0aL8;h-AVLdVVWXmH6_E6&^K)Fs;KhDm~}xNQ?%TJtybm zF}1tXz3IhoQ8`61PK)11D?{*4@zk}77=|zBofTvxij*6z3UU#yAqdfZ#-p0yl*3`< z9F0TTmG(?M)HG!&$X~)lnd2SCP(&oe_3X7aPw(oN>%QL7`3=p_d??CWTLsLWd_0 zyh(&by19EUT;_UODb^2ETMX1~333ei1U7*6~PaVq7ik-v=!m z1F>{DYsBRM%)iHgd%9d+F|lcW!8x6d6*=8ctOjj+jzoeFKQ&Y(paFgcKwcCcmjpKP z;6sCC;kX|g(H0#kzcPt-)$Ylo(xcAz8|PJ{H665PfPTF*UcaQLKVvT|kbAKNu6pwS# z5rbnzXV1Y)Poy2*4A)V8iD2%4FcO8A<9=NcKhVeYON+`yB5eP5Vle}l%xify%bQzr zW;bj%zb9o-zAiWD^*>yIT1s{$79_)OjWf+`Sk(OTtulc?xnz+*RaB{l;N++s8b?eS zJuQC=@}T-<6pwn!M``*ppaPB*sGU;drOe{j`xv=<(Lc_x`(f zcxY%bRw4SW;pjghRE38tXGXw}$kll_)ZMWckjd3~apUKClJ)-7U1NKHT@SdO&|Y4c z<@l9vxRK_w_{ccX(`vRcrLYw(o27+@3en;PyKD=U1im(+TpBLQ;TxI`RqUp?v@el) z7s!-)+=kJ7(n^q15#v2ea7e7G*MJdXTvkk)`b#LwL-0W~60WD@I~i?W($$%~V1E$j zNUJd>Fyr_RzR>~uQ2ocH^b&hPNrXxfM?26 z?~|Akai48dUp;=FGD8{0uhTN&D2SPROq(51sMTvJCvy}zCBW}&+KCvd8K`nXV!IOQ zm*}Wq++d~B;$)iQrL=hrz{`pH%Rjk5^0I2PV$Imfxw49!0)`xybmkTBT19Pdh;FW_ z73Y&6-a@4Z2Pq4s}7Lt z=1pYbCSm7I8`tJ*?4L}(eV_IlzdwCJ!%?!+|H&k;uz;;!tZP^AxwtJyIeMHIZ8isQ z9W9TI9&d=uN4cD4fuNy=Jr`uBFl_}T-<{6g>B=_*I({mm49E+l5wXxk%+6*@206Z z%2?%<6{{*LCo%RUt0j7g$|eTy^+IP+54)Sy=?^n=YYI-=r3X0ikhgGc_4g|aEL5CG zapq`M%Nw@b5Pg=6)iEEO0lBja&7FW#DE*I~yZXp08o$`3pP4%3SlDn*;DZo|*h-D6 zD$rIHnhnY0BDjc??&`NOm7JHaX7_V%PZY9DhZ5k~!ohJY18e}J3@Oyd_lqqDsT zYODV!o!YOVA}z7Bc&u~2(VLk+f_C?9@pmO4Vng36<%1rUbDsv^HI~ks!RppMkibQD zS{ip*^sKesKTMdI56sYT?)pTIkja4n-%|93;IrJIkbvufFkq8~DW~-@3#)%zDQ$6s zNBclB*L&Zcm0QUh8;|iuC(#?5V^{Ed(vvC!LB5=K|JAI8Cc;#0r28l$mM(jXHqND{ z$HCRoHTKT>D;Ktp-JkNjJ3PVx2K62XY-0D3IXn$!w1BRLZ!FwG#=l3`n2Q$}xuKom z;pJ$bFSX$yg~}-Qs}|NlWEyY7g-?u4$k@v~mf37=Db~rYK2A#~SNEy$J1)WAl1tm^ zyw9Vfqii-iJKcQC0uA}-5+$j=rmRR~4D`DMZhDOhPR|ENw;X`_mZDd2f}AE}i@zaa z+jOf@Sghj%#}W)U(#w_c7GDe;7a@Gdq^$TB={T**0$C|J>oyCWF|n93zA2SdE|=R$ z*)QFE>6W$uRI32KDY)bmN0rhd(PR3Ld=sH_tmX-09o{{oaywD5Z+uCv8IQ&lGqAMN z5uX*T@cMa`5$^(UZg9yF(y8jUordINS#w!(0hDU!vRwwH1!zW-tl{Bi61MLSxxMRe z#-Oabf0ti0Vsjfh6_A5T&H5U|h5v~cvV)>B%a7l;p(p>6DWXdRUza$1N&oSMwGRba zyEqie*5~Jskm;}9Lp0PJo%Kc~mHawOm%kj&pAW1@E2*;FGzfWe%}bO8`zb|q$Z_Tg z-KnXbBS+B1AB{C~g2t%GF_lU2Duap~pTAM__M>16HIqxl8-c5HKPVabg+8x7tN#^L zVRuaB@8U&~<5;&jJ~`Io_S@gC|IuPLZF7snGRWrg%GKecl{>c^m=l}Rx?Y_QgAH#7 zLW_jMVk`$8vuD?z+g@E((MF=1`v5V?G?RF~ViHUtVnf+y1NC>U;?rYobM;<>+6A!i zjwNG+5w+o{IGdm@?BOFeDHzBmFEXnVQ*tXA;#yS?NQN@w0xWj5P2%hEM%V6vzrdO) z8APJE^Tka&6k!=f7;0OT;dkeuyu@50aH3DoYEapqBO!qf|#hFHA;FoYW~mk8~V6 z8u8oJ^$}c3W2sok>wGLXfUkmx*JAK01KXe+Qv=JS>NL#>uUg^QEAC?9QM?mfMZo?x zfM5yeGqkv!&V6m*cg4oW9^b^0QEQHIGCUp*J!$!OwEtubu~Jy~H==;o-c>pFnJLjX zjYTl3AVyYcGMp$1Q8Y3Y9T)4Aa7KS79Jb)P@X#ES_%)~v1pBOTf26)7)b0M_rY|w!D<3aI8?xzHv{fB?FHd}U>aH8`TyCQs=ahtbbSs|-PKlyq4 z%z!^1HSK^UA3m0kD!0yCF4%ThUoVr7SG7f?Y93&_&uZm}4io@lt3Kj}Gzwo~J6igQ z@Ncwu%5?I6FFG8)V8|ns@?Rp6cWNA6ey0Ft?pFu{ms>1ehtXO5!;Dd#fzgq4Mq>8J zkpKdLlm2>zaCAyvl4t8$HL^lS6lS%`D z6WRDQCd>q?1AK#E&R@KBORO20^->lO4ni^utn@2uj6-iGZ1+=Xq7k6MW^ju&yZ&9D z^E{c?8vIF%mt#~fK3|oVU-Nk*E<)Vp6=`5S7X`r8M3QSq?3KG@dyTQi?h_%<+l8O@j*{TCgV;mDYyyxkPT=w&nXbrSW$ z{6>`mV)Nm3LDNwnGLE&$2l)`WKK-_c2zvabS)7PyW=UMQKRxBDe28NN9A!Eqfp<0# zsESEc-v+B`o7Vm}tEgT}-$=gc?H;sN?fayFV)$@UbcIrD7xLpDrRaT}?dOeNvjR!y z$uR+ahn`g}2qrnuA}BTMdyQj1qGJj@uB1E>Rtxe~1@7)41%3|+n# zB!(IdGQToC)*9_pVvjQBO~+QwNdbL<^-9FkKrXz$kP&IYBj3n;282_6B6Bi%^$*38 zz!;gx`Z~Pz^P3raQt*=G$V(DNs>o-^5Fr~ny#)bG^0cf+oPAda5=lFBxO(Ed9~m`# zcPB4i58vk=ko_F?-nY5Y?&Ynkn-u1?&sIyQbDuj2FYy*Jr)#e0i4}x83;m928NE8E1~!62)0YWQroP_r@> zQIq&+_tjrfIJqKl6f;o})PbmUe1)NeWE|t`XGE6w{%`<|a&Lsu}9Zu)m;R zcO8dT>pe~!pb1{#o-S06y=~|ax)e~kE*+4+K>fAZ0HaGr_HmCbgkf+}od5IucerH= ze2^<2*yyyF-DPEUq>)kix(ec}_I!$6W7ZWC6ztNen)@fuylPMMa1yh-Zq(X6Z1@b? z1^ww~(dTVB-Nh4_$H7jvn)b@P>G$HLA<#*X;Zg%YI_7Gj>6l{CNCPMS%EMuGK&O=| zl*dfRcYwF-r=2)IAG2|^1C-Z6$+-xpV2_+2@6u(RR^c+F!<>Y54O)EGp9vY?wkS{3 zu>nNLg2T*%b#nIsEECYR*^CW7-hvdW!FqI$hyDqyBs$jv8yA3TUKAXTA&>RQ0*=vC zts589Vq1v|RCSH0ru%dsHk@_GkEpIxjp>?Vcj}l2u8oJf%}Ct|&{Ba{-PBn;G#R#r zIISPP`^hs#i&nvbB~wmYF1}Z!L`cY;in$(3PET%8FepocQI^OR6;6z*$wfwzRINUk zJm1{b)ZZo##v;}*8mw)1-yX}?)?L?>(!>Nv&KSU@`pKs`V;tM%7E|C5jW$P(JL6~i z*4QYQS?Uc9w0zYx@$;szx^(nYr6o^74D6u8Yvi(o*Xai(+hR4m?(?!iko8$+fKUVxT0fX1hoFUDAJb3FF|WwQWqOO1_xbZnW3Kh>vfJl7o03zrgi(e zE{x?Ey!-9Vu_rroP@#m8@0++0ZBrZ*slr7=x{n~Cg`(A?BB_rtGG+zD4s5yQmWZ1L zJ}AQe*kzaBNJz(Q3kJqIC6)PZt%p5{i%o_RFIO7W{<65o@(`z^M8Uufw9T|y55x36 z4g8Y6cORA*RcldN)1UIr0o_zn=?{h|%q94gL~XMpyDo=r-)keRA(M$yX%X#`d_}19 ziLd%l4y{hI%4D*e4h&ch=h-STr<1OJ_+1ut8ok_e#bqvW`8jQ``D7~4o7EoMX3$>3r-;MQJ<_H^LE~5y=c&pvdX78)!1q~g%i}#l)A%1Qz-Zi) zw-_0e+x~ZgiY-MyerVOub?2=;_K(jJR>koA2u27()d6`lm{M`s)N(qSDseOWN_82aZgDytedWr3 zHxmU=$n1))Fu3ZIV=Pv_j7Pv0O)C#m)OSL9@ZuhCxq?xqqxXEQy%fd zV*a#{lLlAvexSfPjYjh5&9D-N8Y*U!ZNWEQ-twrWG1alJvBOX{O(e;;{xbf(IFX4DTR~Z z$T@{awkT_=Ji1aHU>uLEPw@4*+;+ap!D1U^tM0k^?!@^$?Gv20?pI-8yfj^JIY18m z2WP*60gOKd0-`Sup-LT3r&?VX&Au+BZkITkNFZ(>&4-C1@7`l`DZT6fJI2|^SA^DJ z2uu+~?Jt=X(^HkVy~gm*flhUr72iiEB(kWq4xb2eOQ1WQWZ7)#vv_(v#Mm8r5K@uG zAolvcfuB^OcYsrANK1?gO0aW7{XWG=aLHem6P+1h`cyj{Wq6G-!|_ckSj9;vk}`K; zNJ-ovupX7JZ=NaE5pxLr&}GWxq8a=|3YFH>dHAOJww(=4DWPvnb%OQu9Tko%U8g8d zYu~l>>JRbePP@H``ye;&-;FY+)ZTfr7w+*~a>P%(GXZ6#`jkjngFP_6zsaF!0i0>c zRHe~W0(%m=9tJ2pZzICgcOtN{%k(Pyxw%%jY7>6xj=W)yc|*D*4GlFhzSHTjn;vR4q`) zevfL>d*H8edOIXBa3rw&za^UvT6#a8@@nq>yJPe;o~VVyDCCvrX8c`c&wpoK`c%S| zs*}p2x=m!laFRU3(M^n2ZDm9s{;8!FhUUZMEi#~J)%8|`*J*O)6L^CLcusDT4}wmG zS@8HY3wzkOQR`^A&@OLtX5g#Nm_#UX4vw9eRTFArMs)WA8ULpKQYk7at5NdTN1x`^ z_`1!R>xhPF7-!Am(DTvJy%< z9w#VQR%_A_hDO3&WPd@0S=l`p0ecFnd&TFWHq5=~KDcmh%E9{vMZZIYkzz-OSck ze+MDKR5WuQRlG4t13I_F2o^VrwgT36FwW#~|!e@A|PeN)st$U9a5E{X)exlU%viIk^>86gs!o6Va zPw(1Xn7#R8MSYK?z+Cl&)KIOs0GTVbHk@3XNR4pw6%4bulUW*)l#YTLyCFFWWORo z7BbYH5(gkf!F|1-BK@Xx#-CnIWLO}_X^@}zNm@_IYUPy4stjgY1o;G4!AFshK`~6j z#s_2mj7kv4C3+%fMGh8S$e-g`W@|BB7|CD*xJ^HiaQ&&Zc{{YzBI4uZY=RlrwEGvd zW3bh{fS)UO8glo#;Di}p)G`4yE^$q>y8@aGQWujvM z@|DPz{gI0M^ird>RS4*B%+OJVl7=7bgWQHy62ZEnYOv3GQNfy2Yub$t>TNDr&9@#g zlOSwf@UN|s#-u?^d1nB)q|Ac?>T69hD>Jv}_D-P$@H&);h$(zyd z*=3l4m-K<&yh6Pf9w>evWD3MBxV4dRUcwyo9bojdbu&@xGmn(M zOwOdjHFR_(RN`Eh(1E@OGE}t5aBQ(m6f0KgCRW;UC%tlUZjBY%PwT=_-K5sx%D9rp zYLJwyJe}nE=PBBKMa#yn&LNAJoib_bAWlm@6B^j0tIh}O1*kUs=C$`!jw$4{qv!Tx z&F6sXhQ``VD5g#0Ujp@G80l2O3+KGe>815<^qp(^oM5Hi)BUcDf28_KVVa-S6CBo10yFh?ZZ9ty4~TKZ0?&f|E%@gusP7Ez~yp4L^~>_-1pf;#F~cck#> zK^;8ypIOML(usj_{;1HcROEt$SbDm2*TZeU5P3?_*VyBN2!oTuO{(D-!F8V-JIhWf zO>n?BFX7$Mu{BAXtufiN6zT*a)ulhx`J?tYp~`UO@HwQ+Z!saHIzrH@muQ3KiJAg( z7>O>#KcuTSP+7IM<%rN-)Ym*!J6+$qMmwj&Kc*4fkf{Hnf@^TlsdO?ozl90vw{7LQfqUas*8B}Z& zK>1~cvid5gpMOR|>!my;rz*>C7QD_k?mO)+gvy*89Zv&`^$?r?@qcdLy>HkEpzv+F z++}25biPyBE)ZyaspfXd11y%)=2r|MGhNVQdPJ?@N^eXU+iO?Ir}PMlqk_Xglx(Tw zZz&uUTI+I~#e^^5WoB zESmV)ZSfsz75au6fJt*9fhY+{yN|3m)|eP6+lM{UKq~_=G;kC6Ul_MK!Qs}E7%#+2 zW=cLRYdi?s@&r5JeUXJ(EJ?PInbTG`2|HAc5*jw4spGDF@#S^F#q_UL>hFWwwQokqUJa;u!eju^MS$yy=d4&8(*V1Ch26@%XGI$rQ8VH8tQC zJ^qn&WD4exoB>CwyTBGmIYKTAq0>6GmEAA~=?6fJ&mnX(FA5nDOKjZbNQGraMov)~ z@E9kTIPkaN7x{Yl6qqzP-5&LLp7Lq+equ}=96c5CEmEZco7ZH0*fUN?&5fSi$K?>L zs}D`Zf82h*09%73zE?i&=biU}om%(7>m#{oBesTTOyX0%SL{IKTsy~p7&%m!@lJt- zb$hrQk3@%!oYSkMi-+X=87Ltdq=(qUISS_aPhY_}3O-NgEE$82zHko%BOgKzWBMeQeSuaiQTtFk{zc6$ zU$J9AkGxwWwK7gpuM9i;>TMYa5auZ);cn34LcT+0zIS2FR~Ap3;KtnE3s%%^g)Yyx zI<(uCpnywJ`F=47MPiJIKCTQ#J`rw4cDcKp`23!@nVdoHN~+;nU$uv}0R?_^2&1yq z&mKRk77|dQe>viuLU9>5wa8xlmeTjM8@mn+Pjp$)UWudbi(qo##5+s0$#@-(YuHJ)Ffe)W1~M;ra7CP#)QV<8jy6*icaX&Zjkm@-mL zX@E=ZEWQeUt6{O%Wt-1K?dbQSUw}9!!q(6I>Z%>pcd!6f1gjJA97z0p&YqYb`5H?;~(jyG}5q zj#pg9TFvU_mBM8(#ZH&2L$NI9(}!d`CyMqBI!5^sO4yEcSRr1IZv1e1lv@QV+s=S( zX~Lkn5wxlj&uiHJGF8Fidt6q6{jX|;lo>!k(sDmG+Ya*lB;o9JIU4qF19Ez?R4@%9 ze9(LMWwuSo_Ns~{UmED@kRdinF$8TbbiqRk(doUvb2~iMyA1AEG}PFGzqBY8igS~G zEuh|}>`3a+LQ_SP)A@;tjr%`b07(B)0+vMDc?G*JLJVB193u&QN!|g|+&29NX>Cp^ zg&oxSpK3ph+XX77TqHk(Qa=W)-)B;JH_?1|zFlHG{vQx7%wqtya)3LBh@ zO5QWqX%T_E|KQvDOu&Vgr**XB+5^I8f)ipX;m~#%H+XPTTk>tS%ys@<6I;m{{LaR^ zBcX%@=?>(8aq=2z#U*YFCH-ZFnOkHNO*EjttPCNmKP{XiQsQBV1Vr?w=8EVc>rGMK zaMVIIe3GD$YEuY^weE;TrtbNa#QZG@(DEUSLlP6^(z$lT&2xaX)n4`!==NiRhi|km@(8 zH#&GUQKR3*M@L|a!4uJbOd7*FHX0E_c8zA^*{0TF2E;Ka-v`NyGPg#+@r05|xnPV9 zj`)l0XWR4{f@$>9JWO(*++}p#yj@)OwHIHXHT5{AT8V%cvbx6x&||`t@!+zvwyf_% zVooy-GMTIqV^tCAlS{XLHBSbS`ME=GVwc-Do-4y_qNB84oxO)`UdWW&TUV&Bp%+BK zVd*c{iPlD7%af=m=~XM&ve)E4_eqV}rN{w;1G)oVhSq1a5u(1(Ut$;&j$*rq?D}my zpyi~6bmQbN*{EL9-8Iy$ca18P1<2}T{i&g4V8!?g!_kX=kG2aYu8k&LDYck4VIfRH zeHj({g*6V?OHW<{w~}e{D=bd~n*bn_z@@g)k#B4j2Vu|?$+Wh)Qoy#9Q&zu}U2txS z|7~V>*{YTXVi8v?^YSmWo3O$o<(}^;J>92nqlam5a!@H`ws)B!=l9TA&F`?(nQGL@ zb=DcHHMvy&DJ=gCpA?o=Vp_fd*&M`d_)OZE@*8w|{=9?dcdw?KM!bjHx$6R}P5s_k zvP@^%?QXw9E|x^Bt$cNW@afAmCqUeuE4G%rzat_=q0Kp{C!kiH0%3WNh)?B@_1TyL%(mbAqnO_K!`Zc7nMzvHQ;DtCTTUGLCrV6H%Qz4DE!oyLvHSuha;m&30+Kbv5U=qDLQFsNdl?WJX<*X9B+zj&nZmns=mRi*USCKl?K zvcCTQcAeO~?(n9P>$f|9qug|iVgaKo;-}75a}%2_rQqFtfBYAuBUPPU|HDS5%$b@- ztluYr$aLrh1Ds&JJ^ifr336{TXw{3MBX?`1I5HebaEo`1*Lmu>-etq&z{Ydf8Dc>L zzH4s{g(Qc|kF$fqxE zSHyy32K5{p<3(}^TReCSCBFuXTZ_%VW;j=u9&L|@G-e<*e}F)T;do0b#~p6k9A(r2 zxlcqJl~j!q3r$)LrkCcH2j?dkD2>9Ak(>CKwc3_0&O^6IEXs0115IP4QaOv8=g=b;f<2X1eSXYN7pO}sezowxtjL%)6ezn+ zS5-eI`4?tBJP(V2-U8J-p(BY(9gY2#LYC-AEY43#h6CHope*N7M5KsD7s!TS!6m&J z&P0Xf1SW{gKvzn(Mv0V=9i8|Sp39h?wQST06ROifuTVk+LnjJmr>zow@LRI1!L>Ij zA|wnI^_PzD3ucEDTs3TXcvl%u4*WX3lLwzzmB0_cxaP}5Q)Q#glVo|$$-8DCHsr! zbBGU(#*ZwG2&}}T9#+D4L2VNTs{vwY z5>xrLGH??`U7~35v{i%3lMme;Pp|g=y)*DtkkZDXG{*Y2zd%g(F21SBTFa@o7D*d- zJHKwA^?F*@llz1}X$c+!6%3+T6xniCMBE&i-?u>lmW8)IeHi9s;$08fBs~50t59{J zH{|e|2{PpnvC8H5XBcr9u9^E1#8N+vvnblk)^9x&DZ}UiJ-sW@5|9+}{s%CFSVaSd|0n_8b9O%Wo1r&WM*h`eHt7@ zrh0NdFP!X+4$oJ;Iy*kEwstJQxlk#B20Yz_44;Om+yP+fO|3|_jPwV|T*{$ys;3ek z(==gTVa|goXS~*bXS?>`<&e;EcOy~$Zb8=gSLu*P-~uJTnON1$loIGiWOUq+GaeX^ z$kcj|TFcDn%5iIe0{X_k<5+&T+ zJbWVL9=dk~yQ-&zVgXHAY_#;1$16sM`TX;F;=MtSsN{h*;kj2O-z^WEC3^B$uXz@~ z{=V(P8-Ea-bNK!ExJ;l-;@HUjTN3j_zcpG*ElR9+1~&%{G4N#4qT0sw!vA^=`A2bz z4im_;^eSKq5tl=g-w|t8@HsDHDm={H<^{XSxp*$%wP+sycxBhx4-iLL%4p)$6NM2Y3=NOJvZuT z#Er|<=V;mg4gB=jkJ*o&9_;4>_M`h@D=NAfYWh|2Kqi!g7%Y7X}EWYu}w zhZxq@*7%wz1a|4&cExPu%TlI}tuIb7aAt9|(k~a1j@NhNblM`I8D#y%>d>So9Z9G2 zYq$eLq{E?55+-9Nql(g4jnhMB8@f* zQ_L{Hh`&ImA%5kcaJ-EiqV^AtBXf(4c&iynGLsBW1N)6T_bfvkK#uSD{P2i0HT-Uk znYp}l{q{{|Tb4oJg0(OjQTM|j%#MNHc{iWJW%N_YlUzaq!PnzJhbo}RIHRITI*#J) z*NOxXZ9WAj3yahC)W1k_g79>_tbOuZTyH@YzAkHe)0wd?pZwmXnE)hLhuLuXeNKQ@ zoRXl+ZpO-q5=-QvxmL|UXL;xT_$@{CwR)3`ZSSwB2eHDZjc@(u}I_C(l(khD3J<*;7 z-2)kaC3~7?K@u<9XGH+HZlnz>K6(%ay^XG@22F$=PI?JU?lJE}Wrf9NWOs~XvH!_W zX+O+Lq9R+~5M=i#0+i0-`{^n-No*ID!~LTQyFsDA_#n~7575lK{K!w#eG8>8rtqx*S% z)E5b0yd<#3oaybjjl}?~l_Ss&xc96r?QB|+H6PC?wrH=1UWO_L>XX%5h_f&M%3~|# zUtBA<^V~4OIciAOTOiheZZ8bK77z)1kEZz5%|H-z?{VKM$DETOrm zYNK~;2{zgCGYe0foDI1EPA}D0A>FSPqicsoIxTvd63H}`6C?x)bFgsJG20a z5fV6%;^E_-lyw42&pD!46q|Xb^7NhG{k%>67OKryvPU>l`TO+FLEJ=ICnrBK3t>1M zK6CAgSqd@P!el*Rsi@Y*#7$iv!xI_K>DX^=`#FX_aCr*0u_!qQf1d=r3bCYh|IB2C zfhCXiV}6W zQ90x5)N?RA>*3s5N1djQr2+g8r{NV6ftTgi?+&zV|gs`}hV*B;NGiX5G*3|Em zvC+z+FhG6zHAR^`ba8kK%_Lh0W`#;P#UOn9lzGUxZvQiypVro`{mU-1A9&t&f3Z7_a$%P z40yf6WgJ7y(o}iroZ$g_WHKrOmN)4FnjQXOI+d$`qb>da12;j)zLpME*o35_T#e%T z!<986(AvX1#vw(7@&|h)*oU%(5xS-<2vqJ%Bqof|V~jr@*gx`wH$kB+2n>+XqNK$( z0dyc?P(JBfjv715yPq?})t>S?9!yck=cDz_t{+GMquAIS`S@!`+E zs^h8pYX+vxK7IY{!%nXhRenMdD^^lo?_Ry(rJi~36?UAm0LBj`i4E?x9`IPo;62gV z-ldCaYkedLL^0mf@mNaAg5}CHHq^fWtq1VPAc~vI`k2th)~|E zJ|P-6*9fd|K+BnuGT0Xr87FiQxteC15(f>9nLlPZ@8OKeTlGzgAkqKX_ zOy&k602@Yxh%Vckq*uud5MiBx`oAPdO(E9W?-A3OQzO0ZB}hRqN+&(8ZfB)AY{%JjW48vpxQhRBT=0@w8gG z5vkBO<0?>vpp8?g8r9C>zM;d0x@K-F9&h{o|M};?ekRtf+M0nM$qbC%ef4Rt_|sos zxMFzKIkWqQ4{N?*W$EU<=zYOrHmeA)zsbL$62)QJ%?MV`u0PJ#>(2Z~LS&yA45v3m_ zmDsQmp@54BMFbRbg-oUpdBT&2<77~|j}ghGfDgcPmcZt5f(p@;**F+Xl1tK~PduWw zN>#i;0@~ExRzh$uuuVjWN|>;N#Jxg1koXz|NjZ;9%ht-adE_Kz9Z11PE;fmzh!BmY zN)l|8Ac;{CEosxjHL6ccikpve38~-{!GWkW>-zUz?{COib*MDOp)!!JiFCrOzFA8W zizRs#cwiBV;PTcAtr%Jgy??k~G!Y@Ld@E2*yia>>!CaO2WiV2$d9)ngrs6EeM;eKxNLeqy5G92^oN)Frr3=l2=&*WOs!k zLe3!qtOn4RC`6JhXI_Mgj1>VPsjt18w;dO2%lsqdHz`F_=>iLUldws!V2@wMh%Cu5 ztus%ao-?^XTQuA^_lVUa>!14%-}=%Gqjz4h?a3#foMUwhS~Kw9F#{Lfb5Z-~9ov6` z_vkmS9$tHDwW#*`ONQ5J1~2Hm2a>l9E8AIl<%S|cq8c(vnksc3I)aTh z`GFfFc8twABiJ4AO$WQOB{FNA`$ow2%14D2?oN1@n4N_(+o~*6J{BT6AUvHB44G?0 zLt|_}Xl>~amE$^oa&NhR>mnL{*vlID2xNJV5~7gb+Tbb4wCI-+XB%kdq*7G$=u_X- zHy`^az2`F@(Cr_)Pk;E~yMcRj?p9?it-sse5xXdNf_4%;+jax za@yRNEi#}-ASue)7?UR+7fa=0+WDp|am=J@s%YWNx%7>%QW*wg+s*2mweql@O*g{+ zc+?+I7W?nOKcIj=sxWC%XSWsAcCNQC9chFj05;?Dp(Xhz6@?VR#cpgABQCS@l|~-o z1C}x1nsu27NI-%EW9Q9bkc)X^AjhOhJp72a7#)>1ed~#b^wz(9uWtSDo%+L%-mfa&l0Uf+A-UsMnyC!c-kj#$Bff*zA0EC@veM3O*>rU~}&V)cpTvDw!& zCanLgF_}TR;28-w0>PUU{T*N+cV(>slUz+FIxtue_&9pWdwvN?*Z`npUAD6$0jU-P zQyTIU<4?K20*}q}H|v58&*QpvC^a1XvrRN4U27Y)maQy>U&Kjuo{_bGU;1z3z*FC!^ica_DHS3>6 zyiD^Vw$Kb~Jd@2Pl|-P6C=>-$6dLEyo7X>IS08na=JxOgo()j2>4F<>>ESjcs$9ZE zzy@3>!XR)Glp;bd5ytEz;z^;BEp2o_iC~Wb7Y5IUZ zuto%~B|PoyvcH`YI%t9S`-PNi$X`;T=+Hai52(CnlcKSbaj{ z^_m{t`?x;z)xXvse&jxV<$M3gxlqBK0K`q4*&roR5DAUJyyYHCc#g3M12&}GOchBg zJupV4WThy{o-{H*qpTx7YU$ykODF`ie(UWqv{l-BKKaGJUW(|Ds>Pu&nz8Y*+oC?c zmRDz8tOc&7Yq^+x0gqU;js+1Jk{1GtAo9QyGVG8Eh$y898_R+?sUbsR-|S`)_Sq*3 zB)djr1!9&GiPZVsB>2j4_s8F*cYgYP`u39#ODa_1un4F+EA64D9?`o$_aVLNgSY9U z|M)Qlum2p{`V2wqN**A*s5SuY=T-KvAay_jlRG(w1f7&i!_OM?VS5PY$Sjbz?F@kw zE66FAERiDEW(cR|LZQ3?k)xf8fvZSaQsio6ncNs4+-G2f1!`K;AQGnYy8NGyUApBm zt(>`%gM=^zZ#&#i1MH6>zUb^n%qA}inbKm#bzrKqwo+6&*Ui~*_F&)0DYe?-*Y9}C z`gq&xW_8Uva-?_mVUoKZ<6 z`Avw>i*rNy{HW%AcIt{#_1yL6tI8(=i%2v8wr*jdnk~`Tx34rRAcKuB_jLVf^9p**MAEfkU>Un^0vP}Uc+Mr1?=d=Yil(>r@L ztC`l2RZHvDBMQ zQxA_lq6#-lv@EuIhDmP%Hb|{LiEq-RYI?`t|C!$Z#XnaRK^MsZ#JJ$v)EM>b0^2*LO#aY4y zOXQ+h65&>IYoKE~g|eSP2_3uymfR%mFE+_q8hYa>t>`3PN}RFg44bNgB>4SR=AvA_ zWFQxJ9T6s#5lw@WG0%P+bdjfMq<78{gFQ2rRVvljzU=+Kbar-XHLV%=0nWg#H(hzg zE8qR{xvPd(ojP~M+)a5ADrFSSepv8nqcUQMAKWdxS z4f92c8<*c|;FcrcLV!d>kR|v8TtFL|`!Ub*IBSI03j|h&LppginEX~=bkP9j$8rzk8DaX)khJ+|VWgBc^Rq(KJ|0f>Q z7r*-j)h5UJl2YTW6<|w@2@pjge{7$)KS)WRd+00rlTW=@)hZg!-T-7<17lBs?aH~+ z{zAWIthb4CvYmU&XOcUy0hnr)x&44sV7z(pdWk-%*kn(s9-7fLW9d)5|EGR-zr(># zl_&N=K8}rpOMkMx(l#xE1;bVM0^l73>!YS47jNX^l>_A3a$y^EW|9In8m%%;VhvJD zEaHh$ZVCv**&y4RFBeD_2Rw_gUaM<@M~F9k_&%k!dd{Q914f<0K^U^mFqG;LR76Qx z$BKK;XFsIB{>I-By-+Y78xkSgBvz-#W*OZOSBjuh=V*|yBU6IXN>)LOVl!AMS19su zfNYn2k)dkZ8H*+YIWgR?0h6g(h9ClZ$8zt6h_LIFT_&1+pi5+#WzhEXxD-f$2pTiQ zG->cpEdGB!yx_>I*o4!wcVNSGlupn(Aim(8n;*oCt#TixbAK|GG}1S3)9R7+Tfg${ zuiY?u`z2SXRa!If1Db(dw_SC8s#bqu&B(eF+lsdG?=@JkUU}}~X?q}~h-iRvVflNh zW?BkrXp)}<){d;#Wt*7y+t&$^WB>pV07*naRD`V>rAe|x)Ra3!1dEDA+}>W*tslIP1;ee9KwkKEP2$mk zMVG_}x$|IWT9x~dA4quUxc_hdSf797OS}@K2dZP^fvH<_!J-vD=7DA`OEOI!&}x*t zfXPi65C%ZCsR7a%f#^)3h{&lqpX`ffs#@1zu*x|#*ePV660pdXxZrF6NzG*0L>>!) zN)~15>lDyg%AE1VB)@t*f8+CY_`D-9$53HyZk?97fQxX8e~O0QwwaVgfsHaqMG+mV zhE|`XO6Lnk@3`XUc6?$-n_8tc1OLSt*z?8T>v+kZ{M@ao#JRlLIIhC_s(asyeF}K7 zdkr?jM+9U8jNn{@HO~sRNA|99uAIH@T%EY=B;K?qMFe0=AeDd!S-^PzgIv*hj~mQR z!IS`dYE$mL*QAYcH#z`7ee^D@t7AMT=bc9g{N#+6}S6K|KT`{>vH zP7gi#9Zl3~OnvU5k#i^lAT#%e(iFO+(u$}7Bs8{bfY2hzb_ow1c;O{sCYNfoXeiGY72u~2Uk`RrV2$Nt)9J%{axPlLlhQ-fKb4M zBEqc77E!LHJTi^3F?L2WMv+8A6}*w$0IYv?v{m)y54}xMO`>yo!LhPQ4IZwnWsi9g z<`_YUP{F4{Ms3xK-uU6W^_?dlQr!tUp^&ig0GyL3o~NdbWmrhLu@qoq zMnoxMK3qjXM3}mcT(DW^tUrgx$u<>Hm9l~hZY*V16pz?r$tjl&QpP19^RNW37L|_G z!)s0&=o!4Q_|n8HcHeVR>yJs98s@GO^#82baBty){Xeaq8OlBHdO_dBU;o`JYx~BI zSTnqCYo#bUyaw5HOaK&Xj@8W?hHca&L7`(Eigjs-C=?CwrXJ{;rEUC#vY>yy>csHh zT-iLLu}2(K^a4h0Dg!JS8`}Xe*@$bV2=4()N-MXe+%jdo8YRLpV8FCaDaR=ZBxR=% zRBR$b1aOs0L_izNDbu1Lp{2TW2IT%tmfCQ&$Sk3aBd`OkHE(fXXF4MUTNWLr8gF8<4BH~4YE(Lt>dUh15NV2oeH^!Z@WsC{ z&^NHk7c+vZ#1g0g1!v&;IV%+0ua*U-mDWc)$+zFU$ z1_nK?+A0*typ2K;AyGxKxk>Ly`&(sQmNoPs(y$~`+MgHslp^#H1js!Qbav0MF5Ys9 z`aAlhJpMuw5THi2t>`%8rVKrmK4^*ObrKQY+#MtR^A8`KHtWcGt?PHMf8hLC4VOa} zjZKbyjKlHY{Qu3|`pH+#+4aun&)Rd>j-}VV?&1|YAKbZl*Bw`%xclB~FW7a*<>$TT zy)V0I=dBlCx%<`&ui16$c{_H!`RvhMuitvr=o`1b;3Hpo$Cg8O(*BKsjZKWbbjKZ+ z4op1S{_2_2`i~mvo4wI%E(2?a6@GjK?_7QF?uSTvWUJF^n!qO!I4Hj5$G66 zl0q)zOCJgOfi|Vat%sC^8m9DsAeLL}O2~TDC4PE*#Mrx^(#OY7c>S!v_g7bWQ>8maR!4%)alDzl*$`9; z{m#4pm+FdPJ)ZB{V0rV^afmt`oah^SM9N>7H2RPG3st<4rB z+*N7kFn+$)&R(BwgAFS=zpx482GvXdGFHl=CinLL{^&cd zJR~;ycx~*<%*4~Gzm4y?ZO>)9@4kA~Z-3x7UOal+RWBNS^S0j@ebd#q?z;WzyFT&x zzkb!qX`4U2aA48jtXi<)lN)+heel?&C;rKK>z@1O%Z}Ri#tn-%{^ItNcD`xbiM#(` z^we8k^SmRje)WaN?0jP?rY$&p@pHcOw9fv2m>+Xr9yVUS=dF8wzfjk$Gka#OnA11! zh+u7cZ7B-nt){%nRLVqQ9a_YiOQJ!NO`wA{Tqz1%3!T08Y#q1wcvT3JL-GL9u$CZ_ zT!@tsI1H?JbQakrN~0V;<%OO#7?n7iViWWsI|voLZ6iWdnMOQ?P5aB1@xO-sw#vQ{ zp|TKEA+v!Xr-I1GTJzk<*aqk$BHKdlugE+Q+%&ap5SsFb zluOyCy=@C*0b1e)*D9Fgg1P_h`1kbrfBBsJPmiNj1~{+BS%GA_G?*S9T8S! z2>7fG_ZwMOe))y82p|(4cBD0PmT|-*unK!y5BCqRO5b_xXherZEe`fk{LMH1rk_{h z?fIfLE;Rd68yBqOBKne*BaD3E%MF=V2#wd9!8*Gh2-&=B2oMdRm%JWwBRWvtrv&Nj z=+xaG`y*A`3PptSgHM>|&=E_{E*N8~Od^xR!evAg=q-apn|NJVQ;kjV%0K;|sf@^(5G3TS<$db5^N$89JSm)=8+Qv5qc^yvMzUo&KNN!v}Lu( zkU13gbtLl%e`7yztl^cU^tN%RlgIFCM*f+pq1q^UB*NzgN9`$;iBquNYkQXH`{x;jl$V z{N~|HHvi1#<(sx|UbN-n%?ppZU}n$s{>8Hv_HJHsc=u6@j_6uHysUFx_i*>(-nsp2 zhStvTx9^RMx9HKwzOSMhs@Ln2z3nr;+ZA13``0-fs{C*N&j~k1RG*`Wh^x$tj91NSg~N6UqFOqXM49UIs7us?^}!oR$(t1>`-GuG}C4f zVId zOxo8P0RK{FSpz^JKKS^|7UxHWFEW4r`H!h{TKUt8(23$q90F2V7CN9XCryKlQK9!A z#+qefot+S=XvC8-_lS%YS~q7Ednj9HXRGpfCe?c2U`BrAl~=;|rFwlii+~62Bws%% zxS$m+oH<9~D<)rJl_f9A1_`pJ^nuAW6cM741ojdkI4E`MPOw>XA4J>km-OzOmmhxH`50~1YX!Qb;iw&-L$iBo-aDd^!_I2 zBKow*AW8%=!MWmN-~IFf(XGphf7KN5QP+Ha-+un4=c}vQAvvZy0E;yc@&~af(Ew!5 zp9w|aTK2{8xQ5c4zL70!=B+>b>)-y?&y3!??Ic_L7{dRN(nr7jH$O4@;GV;G-M@3s z=pEPGy7RW}@2)@g)NSjB*8D|J(fbQWEfJ)^y?(#0cj=fwDU z)M}IJuJ&l@z!Dv~;BcL|_*fl1|46MLUZ?eQR`U&Pm6nez*YNbY>gk-R_M%5$`qt-p z*huUF>8&4s!_WWH`LF!x|0AhGR51F$)zAL=x4!y|YiF)GX-@yhW-lDCAFnH858KPu zyw{nrA%aC{$Ogem3<^BII=dy&Rpr(hLkQA*TbRrr2LF+F#IS zhR_)|8?SWx7&r5>&fbwDnL7}W9vH-is1ve?fNE6J&d-oVA&W9eV{vw6NJ$|bpo{`u zWl5l%8#q@~p@0MkQmwY@4Ig@w#wNy@lzP@El_SCeSwfa6IgSCL#97g#GY)0Df;=J$ z`RxWIK6TYAg&zFt_sG1h{NRAOq_Pzy0vbrrIX<{aqbWt?>%K@2?bBv zda7O74;FE7=C^eep*1>03YXyE9m$^e{*OJgc#Y{zQ;vd5%ME=_-;gSNEMq<7ONu66 z$SGSZ@>R9feqeoI$FhO2+GYrdfvmAGY>gTj6hv27r#|$hkF#Kv-$J+#_h(w_;(#D>0x+_zZ6Fa)i5slaKtEYa`sc?U(jR^5o%GR$8CFrK^f8SHJ1LY9=5I@2 zWtkh~ARl&W7UDb4l64FK1~Wwj0CPZ%a13jOF60ssQ?&Uegb62}69~p>KhKCUO6xK} zQv)b9WeZES6hzHbZkQK%hYgVrbQuP}kox!u?b6McXrOBV4fDeZLyE#wDjNcjhsW+9 zqL>Df^{M1NPl(7{?W;%DK1UP!*lP?itdi1?zSC z>XUTZ%9C`|!p*`*3uZ0L4;Q^%(^U!9UA+)rCM8Zl&(GsykLX{%_jO)xPb!7~WAKir zdOD^r`LUa%A3{Pq@7Q*Z#^V=P4y-;Y3blJL`Ab5^$_t5J6Rc^-5GyPJ+Fv3R5lm+= zhZ8z%&bX!v-2ea(07*naR7Rb{&nSLF8_aP;O?;D*!zGm^A~KPTd=XHFBFi#D*0iow*b|d7R~w>)-z;eSh-%swMitHYwyP3Ka1YR04wuwSPoB&9)gY+cte* z!U@x#y!jO5i>3KYV@M^GTT3JuXCq1k_GQK0QWyb_t0caq%O+KYF4%aXRt>IH9bKWS zgscQn%03gteBi zy`~gipHK-INhNe8AZyT=$Tv9yw=Pt?lPoq^bf8b}JljBJJe7$}^HezQN5A@4`s}y9pu}C-y?e+RPg(8fvWO-W1f?bz z!2L%A(m)W3*bnx0XLZkirO+RJ^3T-K(T1IC)7i8EKZ_utlmXr`5}Yz9mvEcMRcb_^ z$ufl^LZXVYa&VEv*rFkDtfBtF)zS4h3JebNdQd-bwAcEcT~wkjcD=YjNm5%;)y&R* zC|@ec*Erxx2t|ZMRil{ohRKlSZU7XPIY2}xBJc<{nG}*D+$K#_>-y4nzswq~u}Dy2EKbM^pD#er^ zM6Q<5pM#+dSc4PKSV<8GZ3~Ks%;&Kpiwtfda)qW65j&|tGA(4v#-J@}PqZl608Axo zyWtaxh*Dxi4mi0L3P4hpi;@7`vc1Vt;rs#>6=B(yF-qEbL|_X9hxSR!Pu7V`k5i2t zgPSE9ImObN5Q_X!qb-w3im=>L1J@}@l_J_#&tCKFP+Yz9j%zR1e{i4Xfv+_6A&RKo#s*!ak6ovou%X&wR_iH{V6Ne>fi+oiS6~>)3 zJ~^S@YPX)X@JPMjS(oZ;9x_%9tyfRSfGW|ch>8>iU~ErENK8vzF1|U03WX{vDsB49 z&wWs-sB4lwC(}ed)jOVi)tP_ukPh*(^PVf87ZYux%LY~)#VoZYW+)Fi#5@BL%#nbQ zXuWQ;37ge)38I8BRh6FyF4=UMmQ7y{AEKeM?j*rwR|k%Oqsq9Hay2Abmidq$E|m-Z z0b$6fktLTj#3$qykmPvqNx?^wc~SCa!OuA0sa#|RSjs#OL~5vV2F}wm<5Eu^DNAx= z+0y{2c~GvVoAJU=x|Z7CIq;VE8;||7-us#N=N$4_3k>W)&SWK~SWYOyLgmt=!V2YJ z$V0*=+w18$hI#f+IflBvH}QnN@#r_D{GY6%B^TNp$!98oa&`Zr`0NIWFp{mXhoHJ| zqo!p^XB*Ba*@2uoigpe646?@x74X&^wg5}N_%&{E;My6 z@X~>Ws_?4Vm)8p;7+q>yOfP5yX0B+gl_FSla!D!|QIa(gv@DqH11Xn4huo|W^FRLN z`^2|NYh+*B7L8f8O|Ytsy?9I~crRwll2}e`ti%GhpXG=MgO)x_i%i16m?9;$@y+tq z557hIV^ZHteF%hHhz4Ps#^CTmM(en!Y;*twom&bJ*EY5;A;AI!D%0@%lu!mpL=8m% ziY&_cLxuT}BHUmX#DVm%Ez$u6amghq__!?r0Xc=r0!JoJH>RxMC~Osa_PuDHgPwl#A$Y%L;PGM-)+s#=%6giM3Sc zVJo0Rp~+fZ9qQ8IbC1w9$8OVQTQ1RIbJy~fyiLh(FPNhMFr^lXs3?dh=7Z^%a>E0M zF)|Oi0DtM*{(Ge-}|C~wcURHA6}92@eOJ?9}& z8}kOuTm=$TOr_h9i`&GW?{4qa6`Nk5=^Znar(M_t+FbL>BbGe?Ujx($oK5mY#t@m^ z=!QN*5nS6H7E?8g#m&1USiAFA&wh+i>#C8y~=pqopmpvdP zutg+6n8+X*(`JPvV*shjk~sewxe&|LBsb)J{I2=t58t6mQRPNVzZhqq1VNpm8|OeE zqkC5<=if9mjfk3UG7bgUHAl9o`}X24lHT_ZA5jNCn-}D%C>k-z3_Gfam;Fs!8E`&_ zeXX2M5IBWf2;&?(DI(;LGKNPjZXO%?S*0mY$PjgY@~eN_33<@h;$R)YzKMNrO_OQR z3&a;EB6!s+t(mhTF95mt!UhCz04dlEUJ%Mu&>F-l611ku)Iwn;1=55zf!)b?QPX#x z_>L+nEWtNx6wz4JB`e{GND(GP0n1<)kDzG?kG9-#35!rfsE9&P5-sarsUrRU`~E=H zYNu-4NT(t+2Z^0rf+hjP5V8mh=)-o5$K$uJC=r1LI@6}$G4U{)LQBz>HH8!r8AOQ) z+Oh`99Gkuqw1bp9y(NU%tYHstojY-elE$E+hSC&?ZNwC3)d4_kIQJb54_}2jee?7@ zem1BS6@?+_Kd@*3E>!y>XTZ6CNK2e|M8lSt)uPh2Zq9}?Qhnd^cHVx~l{o&u>+ZWQ zUVYQuqrdhO_wKGN>s|8pHAAa+tsh#ywX>~#Ad1wHxKRbYk`{+=p-QEY*GE1Kkx%fm zNgbHiJ5QG#d6~8yd##RMe6)IsE(Hg7a6RmcDN~*pl`=@IF%-dCYf6|7MUvb^npENd z{?_B))K?$=g6i-n^cP=#=db?N7yjf0Z~L>iFC7~j`sEfeEQ#pj}fY>*#s0p=za~*MHy-S?lH7x|)*%oNM+= zQg#rrTmh4Y2s6sXWFU+PcLJphUf*RL2Le!A<9$_8k^gA_>)-pPYW#HQQH#P<;u~@( z*r(S05^});Vsg3ZgB^;4bpX5H^^?~RO&?t2 zh2o7PE>?G2xBAaIl5K zP*H^5{kMOnqEbPxsgmtkT&S^x%N5@+5h_1Z=0cD+pj?6m-&77r!Y3lw5AGLM#38DG zkAJehSNDAEUbR*EwwmpgOR{N@p}{ov?jUc@tjuN>K`=kr29kaRG@r~%1A^dhU-KDb z`mbw*?eM7u+zKR@?XDp)VV?VO;){L<3?YX z-z+$@f^k#}ZdsM8>b&ypC^~ff;uCc3al3WK+A}r1y-%u7Fp@$AH%JBB9S9^+DqF@% zg-eV?$gB;B28hCg#w2e`o}7GAfAh7!(gY71b#9XH`~3aC{MciU?fuzHUiTLlo%+1b zMiE_DA98iqgV&tJbNx+gXRkl8649n$ZWM|Li7FSSubf8tspAw*U4A7KF3PprmFlYElB_~qR`mW)e^{LzZ5T?Zup(tz`w&LL))F;}`)rVi6Fw>9rVOsk-~F0tC=zkn zSz?3TGZjH(YVIRq=6k;Uo&^oFgBBGBYvyf5u`#0P_QLZR>MX=HBTKok6|;)qN-6l5 zP36U&y14Kb2aW9-tc}a9Yk`&M5t{ObvD6ozbteB~T;F>9AxNYc1P6+UmE;J@2MwL( ziV-*f$7=vA=2(X!q7)`&IwDFeWt`w4RF)8f)Fi*jf99bt=r6zWQI&x$m)yH^ZEgxt z(Bu>#6=4o=&9tQebHfgRAew1Y4g$6rsZGT|WFvTHBimX_Kp7<4>wuDZnut<=fC}5l zP|6&~R8kln01-%lWLfSrumQpXDIydiEwb|b2+llj1r!l#kBTnde5q!2&cY%Tk`U`@ z1wlk02dtQ)gTRaxPq#&+wzYHCofT1BxbyC9qlMzZ|H?zh$Nu>vm+iTC$8Qx+Ogz}# z-u2?ub5?C#J!kD!I%}^*t4b87cIW-ZI>VO8}EI|jvcpMymhQT_NDKB_q+Y$ljGlEj=r;Z zeD8PmPCrnOhX4Q&07*naR6Lbw9Ry|9JFh)H?JI6sJGAcP2wq1ppH7>rB0u;gX3G1G zOJs4Lz)UA53|&O1U>+-l=QC-`f-O32)oCnIJh^^F)UgtPeNtHnkV$;Apoud>W4%Gy zO3p(inNgXK1;nJvxtPUO4P1tVq54aP_rCoia4^`m0Z7XH;B8}Q+9Og#WU#!P7fY38 z5mC;GK;#U{HQP0KEQGOKNJp8@g!ETm{kXpL@R#H-S&0X;1 zgo_NKG}O&$C?&*#z_y|Fe91mi1X#)sJl}cpVb$1wS&b}P)0l6DK%9M~@DvdfiWsy= zh=qLwSb_(jGttP%tSIZ}tW^0X2L_*+Ju?^I`>A`&CmuuxYh@Swms0J73HozkmjDvAiI}1wC+4pg|Hw*^WRq;UKVx#bN|_0hE3r(^9Aa z{`6CSp^}%-$h5I25%#YWFUh{7kZDx8CN(TcECGTqffnD88hHpm36V??t;8NL-)Q}x zYrXyN{#1`V^@!@;2nlsIHjxy31fMKc$Th)Gg%}7xWkE<~PGC`H+6}CYA_5A?7}6Cz zHIFfiQJZle4YiR)vMMufvN0oS80NW0?(rES_FG$LK&^;SKqRp3EN=qhT&)*YB^I58 zIPXXDm`ZAy*+`i;-M{1~aJ$1yG=aYRrkPSZhA?0Af{F4X-|R>8zE% z_lgg^^rw~T*W&&ksyp6u!}?wK?R@PUKlu8qRt&DbarN+;V>+wtGgJ7zfym9HlRP~^ z#Cn~?VOZ_RLN%_v&)ww%t8~SdYjp0$3pBHHfIJha7b+@}A0mWA3t46l6l=-pS{2)yFvyRUih z*)z9(`jq*nyl>geWxu<1<(YThbl3Cmees`M{c}I{;MEV@_lY-j-S^k8@4DyXuj{(` z?n@sWee-i)xAQIMzIOC2=f7t3E$99IuG`Q5?VWEt@8?I~a^bFD{^LE{cHe)&kvraW z_JUovpEYB=KK>E?Hz1WJ4O&YkYb36UJxepxe0}7L!|7U zOw&Y^n%ufUR;q|$!g^wPO*MyMvG7He5D6NmM%zSIB|0xH z0|NjaLbwA3Br>Xy8!3@F5CLaUQshseh)^LZ*i`TN)CbEq%5v{Xo_JcUITvUVU@w$N zBa|6qAp&wmY#b5FA7j8WhwvO2&zmu#+BhqV96k0*MCF9}C*?P)+4dl9JeLP$>a54L z{HET?QsMg4)KzU)cXgT+zPMcQTwz?H1BI_EB=%~Ql5;EcI-uBI$Q<7h%L81HNl^`b z?YmzmPJ;Jhv%KL^1Pm2at*|+4#bU+~A@=ZIs9xlRSEfbDHaJ1nplmSzSAFoztgp`8j=noh;E+~C*-tL+jdf(T&r zR1mZIt&ZX6lS)*Xmb!{UvYmKH2;Sy|3WZcclvI?Qa#Y5md;=BI zq->&)i^nL_2rF$J`q1Y;AhG6E;Wua0{cxy5!?+B9?Pn)83=)K$;@spA%(ytA{tqo=dIv!|>Y^1M6!N zU^;91a&f6i5n(s5Bpxz^9U^q1gd!qin*G)B%o!)?X%sz{4AZq z5d{uUujP8N4?4`#a-_Ah9ejhKXD)ThGtC#VK*rB$z&h{rBe|)w6&2sPvJ^u$!wg+U& z}Y!@(`FJ=azV|7qP2{-}1@F3YFxsgj!fWob13C1*W zKk&uBQVR1v7Rp{sSz>Y6t~7wM2Ed;*ZBjyxnG}(&!G#)B)~DPCt>cFABzz5) zLNcyL(i=bcW>u)v|V#?hB=9U7Y) zd$<0Jjn;RjidqsCju zeLNsOd)290K65!ssJsGr<3UT6d<6jfyk;%R;6`PMD4Eh*c$f!LG-&125;0^zDj*`1 z3pk>rvkgMC5v2@MYACbz2lm^M8}?aQ8kmE%y&;7+P!sCV{!-FYUk;lJVqmD?lPsgjBKprP zd^0`UScj=qB??FkqEhJ3KK&6zg#2+XXr?MxKj+j4i;YzT2jSuteSlq4Ll2+FgUlrT zB-UD;jkRv>3Qa;Q3RNQFZ@>Aehp{@SYjH4UfBz@$ACZcVD1wU?N*rd(hn8~vQwhPS zf`%$CAVQFJp@;^sD)7WGaS&ugBj~U>M2XVWVn8mbV_KKK@U1U$_;Ra>BphT*!h)%Z zWoJ_WN<{^Nibe_hgx)NkA{pERv>#*z!Gf_lu47ncKLZ0%vjn*XOr3AOU;F;I^yi=d z3%jsseOW_+j!US-?3(GY&UVI@O6Cl5VQfZ*h{!tA zO^623#5PFSDNRY_@dkA$5Fx!R)H6QPe)s4b-L!h?v}S-|6+b_xQF?g_VIuE z==G!bU43_&to_`&k@e>uHh04X^{BViI7IS|mQHY0K@(BNDH>6Y6PelesKrNX*GV^N z^Sr|q{G3q~XtB>%lUU*8$S$u9V#=jVQNoUfyaUpJuZ#gTOTyJqKIA0UQ^8z({L3HG z-t>fO9Da2_EYOGhW~PXMuh#>R$iaG%FHz(kJf=#4Wd$IIz`WFpC|EgX=UOV11DeKG z26=SYAu9k?BsPnfKcynIgWCz;aqP-du0C%0@mC+W>ZG69wBXo3IA-?IA6vcY#J|1y zj&1)kddC&N`g8BT<>K9UUOiv`!LsfB+opf(k#GH8QB*ip>Z;?HQw=AZ$Vv?eFpzT< zgx=rvvc+Q$Lv~2o9Yp)dlRH%JjpNjdkHX}>75nNQXOIHo3`oiarRn=7Uzwya@b(tf3aIGKH4&K|mw}bNA7&eNtcl{x_6ZbD~6`%>YBqGfig^0VljlEQ&=DA*#GyOyudrUKvD5 zIl08S<5&^459Ai3gF28a$COmC5WmSeTU4zr?J#v0jnI}6kq(0Z0m1~-6l%)wfmWy&h39Gy!M6oXi?`OXGdBW8deCzQl%L^;n7#k`S zDWvQc5&7`-4I#%(l5!kj6ObXA5@aPLKD3A%x>8J6DaTYU(6+;bKn+RKpMK`g^xd)V z=@Q_mna)PJ$*dd4iNFA`W{S7+KuCE8 zrX<)I2hKAtkZK= zo`SfdBB~13lNyEzDHFec9-R+RdJ8iQ>9^o+SXH5-NKbH&`Q4lDzWIjTcU`;Z*1vrH zr7LHxy>Ruc)u$=d+a_5*zA@MN=9@TJb9_k=p(5OWMHO2wDdw3$^j0qN_4Z{hiY_t7}NJyXR*WZQUcvb`E@Bdh0ayz;QQNBr!(jzzcE>&2^fz2%y>kN)A+ulcF_Zn^&Y zKe~PeKdG%?#sB~i07*naRGU1g1IoUMeWzV?&qeK>4^@6;dZlO1?B3ZM2`vUyT6wM- z;IT%~0tFBe5)caP4PwqFbv8fUE$p9*G|AUHC zAzF?J*{BW{O3fHZZDSycrZBMrPt+EZlM$U*&_E6mfmIXm$?66+ogwo?wx6LC5)c`* zb8alTW{*e@sLl8pXE!CT=cAlo@A-%KWFPNOG%|x(Ce1{Y`H2F__#m852F$?jKP#Zo z=9<$C>uHeKH{w8AGHbi0oALfw=qu9SeD&{DBiHOnV;iE%rSwfgY!-1+$&}df4NA7) znhCjF;LAV~1_|imO;2yzG-60THzdt`;7bn-fe+eR9E_PyKK0n@B8o~x$e)EX=ka>E zym;l}V9|5&d?~{NqzNqLi)(pSAjud5;$*dVNZ1O0Y$45HK*hGLtwSIG(%-4@!H-X* zY|*g6CosZP^nseQzvL@LDDwgvLNYHGh)|fK$Bd@@BF87L4=U{Pg99d^LJ_5pY(qCe zpVewrumAHmtJ>D4I=mA@MLd-SCZhDS+H-}-W&l*FA|yo9PROYd$mBULL%GfV1TrqQ zQiLhxg341t1OfRd~|F{trVh;nQZPpVWZDk{7+E2PCU0JT6$zn18_ z<96x9r6;Q39u*S(Bt?V*${=WiQ5h#?8#FbQ0S9EA**9Zu&(hh4 zU958szf@-(_I#bb?n0fi`fQ!J@>CtW{6rnS>PX-y;OHeM=$K_E z>bRvR>12L}IDOrDI(Os6dfuihbkXK*dj96Cblwq{=+rf5YV(3)w0Pz!zG%!;s#P^U zR@1(5W_8kuRTQdtxKUK2vT@-N&pmwc5$9}Lwt2_$fu+B@xP9JV+;s2m{?XgF-}X~) zzxmSL_iUSu!!5JY^sD+i`%f9}9p1#Osc6@;V+CQhLUfa_EUYzV2yvcd$Cr1LgD zS4;XAtANYbB#6PJr&MP?OPYw}Ske(8mxPU2pQalkNUoo-gUZ!pLur&fX>u2~BSyGi zlnI<;W=A)Pri7eVf~7_TEu+TQL`2C8Y%0R7HBYyRAPPQIOe2G>5KBRYII#aF?Of$T zDs{$N2N%^&-SLsT)ZSJVZghl!4bcgkQYcVlfKszqogv$n(z37pbC)R+CF6W124*2X zbcQ%zm(s^wV{M@vFc1Vjp&}~!%s+ibot^Dj<0d3@4G_gHAXk$ff@#~?-ZJwgIGd7k zYc8)Pn1TVVn!SX)>YC(B%8c&mOW*rXAL>AL(AMH$%)BkNt>ZLE&70NQIV)KZMZV}$ z<6#j+gqIT{U*goDVO^6O+!|O@wt@`hf-XTOR*4tlBYVHEr)qoQd}v^S55~$M3ihlH znfF>Xn@#vOs9dr{))O{kQ5;_iK1m)Eja>YIksz!gQ$pJkaPYb?Lqi9)gOP3g8uy-0 z+@nghQz;5$Wexj4`avWqpd|O@1+HdEjsRm@(*T_!IG|bv8xi;$@WI~m6A@@io;K52 zf=^bNF=4N|H4hbmTo?mWWA??x*R!;E z)>56a^5pytI@Phjlv)lW6MiNJEWm=_M zi>EKs@ykxu1)DC?g_|zbnHw+ENh_X{|Cn`7&uq2xRwCh(CdYY*8&~qb1;S53a{hS; z1$|c}4)35(gS=KFXSJ}omgpdQf;-+%(i|lpS4KOTDVzfZ#Y+19*m^?`+!NKs|M0wyBln@~{{G9(`M&Rl<% z7WOSrrSLclDGCK2kweHOk5ErPu(ZFFM*Deu3u0K{RP1dKazm!w;1|eD%EbhI3Yj57 z6gN>yLb9whOHR*&mP>Q3nXbDbPE+P_S|mAul#xjXlg!E`qFifgNFqWJ$mlRJPq{Ph z#;}T_O>g__w`yN)OrZk`N~$b0jtI!eJY>EmY8DI<@f4RjfZOofQsj>)$^B=egbV@IHWB2S8uJL2d31S>0z#pPFu^cwpazeODuc=r zFj10WGKRlpCeHJm4EHEb&y>7J_-mOoo(t-DCiS#^%q&D*3wej16? zA#pHAj?Xxwq5!;p!#5OSs9ACX^ecmfV5%vnVk{>_EHMVl7)}=3%=^!AMVz8ik=XCv z^JI@XzqfD`h^yBhcBgipxLcQQxkQ`iY}R1sfP$ZSCMWBvm7hpO zD!Mv4tCJe*tP(H^KJ*b0a(@ITtt9WOoRb>Gj7iK2pb`aK(pKK$EStGp73NgTMapsv zvOU;VnTAj(B1DtB5hVg1+^i7?@X+Q!k_jdJ%3z5(gLRdQS>tSLX|~Tpfy0jgi3zrM zLnV(lqU0>cTDc_KXDauX`-06F!yp!Ht`U*38&n|snnK<{rAZJ=%xj@K52s&w_{;kH zfBBp&%yIF|VR4HnZM~c#3*}U?1;e9m(D00jRzgnNgmeGO`bPI?;F*O8MC<3Yo z^>$2??{OYLL@Kt7Y}%%SxOkTwgy~NVbLn#4vwH{CR#X+i1znD^+*2M*F7&{1!O~KTA3yay8;!;GDF0u3KcA#h=2}s z5^ctLfxBa6wv0^}RwEK&=j;(sBVs=v5=_oJqEJN0A4h@Iy&r#@>cylIhj2=c5web^ zfy`Tu1Tye0E0hC-ak^sHgqbW^@a{gIQSPT9Qx*^J_oRv(LO~WUmj2aP&h*C!z zv7gR;HTZ~99ue7oDlYLz3(rPA=1C>(nvvz zD!1Uwmokx`!ImN>T70b|3c^f9fvS+`C+Jh~Qg}%8U?cK^DoCvG?>%XP7&Y~_^=Ttx zIB(PQwd2H5V7pFTaf;?no2P=GW1>zBVtO0}*Tx*tNGA~;E&>eQ%rU9#(|lbj&||Dk z^EYbc@Nz{#ju99IejysNvI}#8Z&rzdf4(MQI4ZWZQx(GqWYj4;&zS)uqS2N$f_b7B zOcfD8L#`-^+(!tNXtT_sCT&4PkR(y9Fksn~M}#6uu*{Hif`|xOyD~Va2zY7^p^#a` zWQU$QC9s6`7S+%_AAf*zypSK(ZIU53#1({tH3KOK7qnsJ6iw#P`zzW~Z(pn(#@b}M z5i9#(ShC6L1Hf}?HsO(og*E3yot*#Wv2RP20(sZ~NYDb{siH(Q2-8WWj%%0mKo=2N zmd6~9tz6=O);%nmH7~nV+N$jz`|@9XE2|IEra0L0-Z!!DPubjkTzyhcZ15$SjEH>k z+@hc+cu-_DL;{{(Q6aG00O<-zC{v|BlyBN!_?NG!#<#XQkR!5)13*1xCn(1xgao}G z5(l3+%0!8*5p4uCphn5@h?xL+VJe|Xwgj{I6Fmo{E3{oBNj&PX zYJ0oHRYonjaqJwISO?GwO_gMMhEim=2n?u}M8nv25paOz2DS~+lOm8KNPw%c?VDjU zkHRw0RLy)S`-_tK297{MfILt1Vir}dp0h9&YtPo1>o3&$k&XN`(xJo~n82_IXHcW5!vW+NA_kmWQ5-)a z6agrZ?5|uA<%6{$j|L`SNrfq8I-X*h z=j>VEKgL{p?~|09>`njx5CBO;K~$uh2UArig1P6IV~l_NW6Wl)wa-5LT%{g+_Hq6! zcU@^VIWjyXIs&F>b;~ubXa?n!pc8U9%%42~(~03%)PrakhAG+TbVP{Fpl&O3w7VNa zse5RLh$AA8fGJ{IYqKr81w$p;w_4+LV@IU$nKf(Xy7gSyt-3io+34u#gby44r<7lg z;3l_(9fvqlr}B*jVkDjQG=MV@aGL?MI%gDZgCUt23yARr^VEFQAVNtDSm~tU2aR_i zP)@zo1D|+6+x(&sBFygLBNMX?Oal91+IB70Km51<@VoB4^`0+smdtM^FTL#^E+|f2AY&V+yYa*o;#z7bx7=6* zxt0hR5S?h)7DnMVF?BoPhxYG$_}yAIVBE-kqd=o7w?~a(lFggB`@u~iY5>q-k>{K? z>m0;v3nd$^Zle_={8WXDLwE6PXHwzFyjK1S3!^g0JL5)u_}Rzw@80*zN*lvbR4v6Y zpbxZqy)!P_y1$x?nho9d3KIx>W$iU#5d}zm3cvte2!82H@7LFV&Kr3{8Ojf3n{_5W zD7j_@iw_P|XUk<)KHyPCi$$(wuKSkXwqJ5`vwrvuKcerr|GUI@asrmUk(Q0@MYbtE z_mR5n2QQ^tz+wP%J+P)Pkyp}|(k4`w^^lij;(f+vdjCUzpu6vSxxVEKze8XCvai%7 z-Bf$tu&(S|`IyYw!tY79wZ_H^U@e|U^+faPVKk-`1FRecEISB5OYi?wj(xdz%^*DbzDU-Rm( z)c3#P`}N~*`ftTrpKOrL}y!U(MYgO>e3=h_@dSMMI%{P`$I6;#Ryc9b+8iwt`Gx(AUPLI0(ihX zzd_b3Y7!Ha0Rhbf(yXVngpq>Kfes~U=j)1e&YM70tVHb_pt(j8oSIArEtSk=+Uh-@ zdY}IAk&kNdUOxCCp2#Y!R$Z0_m0_-Bm+7+-aaVPw6Hx%sVj11NEvrfZ)w0mllNhNg z%xKVv7$%NYQML*packKu`n?C=rIV9m?OOOAWG180`khR^xhOIhkJ};_RK%p* zh+IA{TH6Xib;kw^f8HD}u2eDWpplPvsxv)tP@v2jZa`@I}S+w|w?LXt?;B8&)$ zSDw3^C%t&WKLcvc=$n$uC?H#lb$KrjEDrmtjeVW*hC^Y zHa5EZm^snl#4?|AoPaAdpuymyq5p|GyZ@dy$f zUd`Lp@Cw&##SWQpVrIPw*{)pUL{M~tq3F;h@~oU41HEhlXZe%mW~+bs`~ONuoIRza z3W^3{&Vj=b;;U96mI>6pP=lo8GEGl12oo@PS*e308wE5}d>K1^C~K@_AcIFlBLYlj zB@QJISY|K_dHVWux;n1yuT==(WQ2iq_dtUuoLrVC8!MvR7sS(xhe1e*F>j#bvC%y* z;X}tJ??AB0gHCp|^h;5l* z1VxX91V;hLZo_U(AvQoHZeh&Fda^#dKyvsyEenkYvpU-=yc`C5AKg~IhzE-D<(?q{wIA0m#lEQ`z!tHk&; zQTxsO%*ecC5mXSqb~!bPW7?ly;J!xe3=2*t@sz@p(6 z*8#!>OJB{*U!fX`P1v0iH$+^(T4UT$#TjhtABKi{#t_L*Rs{0U!Z;I^9h$Tj0w5X1 zeMM}H8IveWUt6kO-QXKF_-%aI zShwAHhu-)FU#GztW)0a>WXp#ZQ~0r`ALT>HscalAXh9p?HmrafpkP_8iP76m+>#fh zy6_}MBhjK|&8S^xOJ#=>iDQe58pPz-s5vqV&`{Ql0Sb@sieq*x0Ae4+6D$D(PmNwr z>Jb2D)aXl>@^Lv;F^_b7bV)z+8$YXs+-0$Bgbj>n(MDfkEI9)b-SuLv!osL(<8!9a z=;Zcd%4XQr*c+^qy!d27A%`(&uv##sSq7+pIxgn1=4)B>z(en6k7FWaB~DV7VjZ*q z2X4-bM1D4nLsT3Heq)|h&3*6%Fa2CuymxMwlOO$=|NdrO(6(H(bst^XzGAc4m~Y?o z>U&m*y#$xR$7l0VGowgUL!K#V6^NOfTWZRM%#?q#dCJ6 z^gNvrY+7oK67!@8I^HUFYDOfxb@E}8nuN$Sr5#5vXmxc-_dJ_q*#Ph|mSn)M7+L~| zlM53rxo`OVZ`9}B@i{#18)VVic%PKu(8X#H;bxVm`XYWy{-M|ZEq&hYpC>MYHVbbS z5^*Ve5!0v|lQD;7*^EfRdqNU`;LQUK#Wq-Ek8|VEr~8y}k*Nl%0~GzycS|7~bhv(qX*j5LwJoOF-k+lAT;elj5XgU#4KIJ=Us?Ez?6FRC>!n-%$jS}U7mgdC3;-^;atXP3bbuM6^yr!>jzTRN852V)caT;*3>`G$M0y@VuDtxfDobob zH4xjSv(8Qs%@V!*EhLP$ec)I1)bmgBrxnhnf|&st#caqags^>XY*vR28Uoo}BM&vO zx={xwSo=NNm8Cp*F>Tju;jJRDW((8mm5gcQQI`??u#cTnVOEtgZEKfnr=F@=?lK`o z3Y`PzWfRA>Ej#1vvgK3o-8bH& z@BNw|(i>m(Ms2TO(_%|Px+~3g5cQyLmTU&mqCHd|>zSJ%Fv=_lFWt6m7fwnb4dIv7 z!)OH_1217DX+bb|8TkR&I6Qg+BSVN&2AVsBdY0{;Tj&*^3k**akLsL8?C@bkRVDDk zC`Z2Sq^KIS$_0-uYz}s=TcgmZL_TBPYvDKpadP(sgAu5UyU*&q4`{>~MQz0-msu2d zdv--Pj3d4Nm0zP*-STSPb@SaiT26K!IF`B>NP?U-WHnDb`Q zp=J-?>K9~EM@6#C7>P$moB?5JjIJyU*^b(Y9pB0;U~06a`~VNSmx{$5Jyd#`1;nbQ ziNGGA7QSSuL;%$lH=*2{%^x~CUi2^C{cq~o-~=WS>LjL0@@j9L)SMJaK(vWBmyz6t zJQ=NROC=I-N(jEvCOdB8ft0JviZXG{1&70D?5h?oys5CHXRciqe@J6}VF3|LI#qD! z1p`t$)lq<GHra-GV_x zC@U?aZ8=T^n^j}gtZ2M-A$S!=?>m_RBL(8`s}+)AT}$j$*eb=_<20}tnd^^oxeutH z6=A8$$KhPJXV>)S@Ba>6(h)EpG?tBykj>GN@@JMKZ}i>w|2O)M`~Q-Tm~bHd15DLS zR*Yy9tGP#tTOH~(+4xF}M(ApS6kWb}(;s;4RIQkmJ4CcP%f?>&fZuj3n*`ReBCQ$0 zs$L}z>c9g|$3wz-Mmhih5CBO;K~#BHr%KcwfM>7VU5y6WS*m4Yi+ zgJb%(2_eK%BWfp5?T+O`Zg4YLd10WT%=w3)mLxKq5h=|O02zVUmu(q)?YSp_1+4mD zv6XH-xmow!`3l{0+e>x4IjMQ9yvVZVZ}3A;Ue1S(=U9HK6Dy8o8z-V^t6I*nBwi*( z+XuX|yO!;9xln5zl|OS=EIN{y?U?7pZfq**2+0*Zph2OZpR?u}U@LE6M50;`if%Zg zpZ)!RrR(FGM3GdkD0Y0&@_eAQ`dMoWYvBZ05WO=iHv?i4P+@2?OG8Gdn;{IrL|Bgw zS}K}|ZO}-oxF#)e!YYwa+z|vm{F#q11q6`narX@%C4%mRLs86LX!^7MFzW86P_Sf;MLx zEqw6UY&Np3uYAQ<>4(4mZ|klb@8tC1E?Yi?3}OpHH8?q2%{EqzL6nfm17!ZpiM8uc zc6Q3xl|fz7$VxmC(vW4h#AlJJ8>Kc&W#c9;a=`{#15nJ5U#<#`DZy33lJ;iD0|%p$ z_H8J5`5kO?2%N~ouqw?#!A7kC(mFSoUf6;mwku*=w#z;o@}VR3s-%dgYzS~U0p=aD z!NJ!1#d6^ctYE0za4_RIz=4#%EUNQO2Hzr@51hB(e2?zC%BjIM)-X+b8i2NC zx8P!D6xur1=6pdThcVLC7$Z*YrtWKLoNe{-r#`6<@@EJ(&tu24Qps64{3a7Jw{1&> z*NV(bl8Ws_Vj;nC$K+wkHWG_?WemyMHNwizheIIY!C<*yGE_X_(*x!_gU4oD+yd5n zKJfv5R`6cHJXv2;nRl*>ofE-lOe~Tb(Jh1hR>jOzlAIkfuz2rz#jW@84fS<@-OIo7 zyFo5w=c&19>pr_JQ#r1KRL7EXKIZ9mRyGgltonUBCgVc%rEjKVmn>c3jpJMwtxLPJRUBu&;Giy~g54 z;%B|lncQX$x)3^Wj~G&7qA^dSJvV0wo ztgqoi+|a@gA&;%hi&Axn1G@}#))m2&GAB|#3$$nhp_j^lSb1RtUyGK_KwkD3MhjR| z#Vv=!%rV?74p@}a+OntIFR;W?DjYj;I~8M*n|V`*+IRsq!RSns4$Z(W-EcZQ)B9^$8fW8GmVL&xK|c!j&9Ju{QaLtFJTa|D$ST7l_TY>$4pL0X~YMSm|FEP z7PXelDu%)EBHe-~;s`zKj&RY+OVByA%n1@=Oq)df!>U1cLfp2VdhSUr$D5=zn*^4o zngc{|L{jz@w{XPzF6yJ9t|eSg~d~V!EKR;BWlHhpYe(EgAad1 z+d82kQY5R|RC{}f5*eg$uu@KIi=UcvwxzgLQp`d<0kBLQEK)~& z(TX%Mx@Qi;BAnH!Qf1k_ru>7x1TFCcV=G}e1Bn)mQaO_(4={O=?G{;VtfbEaGCf%Q z7YfzLux!UHDbDm`6Rdiv5kg^YXrCq7Mxa_WCLL2$09bb$pB|4ZpbEpKlMF(ds&O9Z z;DHRW^}vE^f*iu&KEPAJ*f;Qu-Fy4Xb;qrDYvUuHAI@i4^bEhmTs^&-46vf3tB7`O zU`0^XnVc1sNn%Yif=!NV(u{}=&nktL9>25!%IlP@vSfofKVv66_sks91=Jl=NxXN z*_>-4yFCOYnXHVeBGxVpH@OwV7DiFfRF7Ly!(3p6!ktCpXAiP?^0)fL6Q9zSKf#9* zEPF2dg~P08o|QF}wd`!_#U7a_ldy=)!_%j`ZpsH(%UqzJbG6V`M}_F$B3Vn}Wx!c>EoF&J8D1S*cbS@I(ZBfU$+(puLxHGw8D@Q0bt zA)9B=IKbR%NDHWq!lQBx*3 zy(B_U3P@Hm(30&Ey`ULCXJNbIwn2=IqNf&$demAS0Gl;W-wwZp(7A4V=hr;s^okDa z1&MH!lOEO5ESR|&71hFQ;LPHG#L9_LKirYmdF7A;kfkrH+SQjs(c%VaP`$(HDF533JM$n?tYM4`%MsuT>`6eQ-mv21;C)l zW-SPZBCJ|axWHRWz$3pQqH8UjMg!0gOTn%mnvq1)7u+42+RQcDwC)@1C0`=kC_`h>&VZC_kQw$ z&ZiU!8t_bka8wu-qLZt%K{V5%!CcS)h#_vzWNtY-G#_NlL`Rz=-OQiswqx9^3)+^8 zwC<;$div<>Yn!#*ia-k;+ht5T14BAng^l$-^AI8b? zi9Yl6BeDT2E^N&<aqKjA{+n7@V zM2KhZTJL^R&V+y*MoE6CxO|sC_+cr&{rlP9{ueqrz5zZTcu2|mxXTr4erwGGql1=t zDwg38uerN+VB1Q*=9%LKF0oxYgWY!aV7DE7FO99C@Df{f>{V9mP_K44=WGCLzFf{6 zEPKjaJAh=(=ACI<3*O*2EtTW|)s2{liDq+`&4a9L1LnNyq7Y*q!;PRc5`&^R&S>Q< z8CB=WG%{xL(8jSHpd?oKq3r++n+P3KT5a@hIuWEt?dFjLMFnHnS+6P_tk)W=Al1Di z8buS^tpOJmYHg{D*d5>3B|kVKs)?2u zG%O?CZOdlA#bccfvyK6dlj|21foF7g6Yo3&m}@^G*phYRBi(uP-MaIZyL5DPtS6p( zLf6l(sb?7Ieb2g9k11rQVsp;N!09uxrqo(@&VQznx{=Ur&pBCL7Z1FXm3J=Wa0hwN zuvL2>TKK^AFW>p|{Mj)d(6=FJKoM~}M3ZAN$h>mRMKe-;gpEg!Uuq7Oo}5UWNbC@I zlKhqmuXrHrsaThk_$;kmE3i<7YX*x~@LHSn$;ThAf5*`|5Wwp_6JBY2Ve7DwZ@UmX zntLaln3SjhGbTThd0&3Z-8^b%7yAQ8Z|wj7<4wocJeKXzD{jBHZjRiTx$$!`lnWNg zHqg0dk+>zf;B^r}bL9XBH~augjjk0?f>Hl-r!(_`CJu<<+U0294*h9UuWy^_lPe@% zaHZkcp6K9P6t=@cOw49vE=A)pOQaXDd+Vc5pWDDGkR=Bo>Au%`-A1w;x zA8%d0ep$cx2X7-&)TxY}W*R`;8hfZ3$)}F(1{vF=D0X_Sm2l&djU#GNG%?I`eZ}lO zFNENBWASZQt>CL1;;~oUZtfvgha(5t3%tD(i+#`quM}(UJP=X~6Ai;~O0enKpz_1M z&Nq3`(tC=ba1-MK!KH`e)?7J3NQW}8k$6VKiHSu_Bvi+Uo@it^i!Hj!T(WG<&iL|9?z!zgJ$vOjK6IQVa8B$ZpTo+@d}Z{IJyDWA04&?+EEgZs zw(=@)H?Tv(J9ip3?*lKWKSL~kO>jZBWxMPQHZ~N-;+7_GM!koxd|H3-@cZ#HFC;#s z*SbSe2IeJ7Vme6sb-AI7+GK{Iu-j-W;Y#8J1Wz(A!sVc#9SB_&o!9WCRb$0 z4nlw)#<>eVL!)90RH-olcHT?M=R3fUJo2FGlR)aZ?n4-fBE<)*gv<~Oklu6>=%CWn zgl0Y#H*X#|ED1t%{_B&`S+lCGiH&TzJJ+1>m|gsuT})Afr%TmLg!-`^oLS2y-wg&q zq+faeZ|K^1zCU#AiZLHV=&~`AgVdY>rzB##IJRMA-eT2KY!fSZH`p$__CcNK0MU`y ztJuwrzs90%%YYG7b)eFRgWZhe1h18Gy#fb|h=VcJVpn{%SzB@xatvqC&Q%(iWr;t% zvGX8E?uDVGr6$1a;5E0Em*$v*>aUm8jl_zNwIX@35jzol;gFw%SYi)~Zz-xXjcLqc zfV)1B0_a?;+{sB`og@1voh}MxnFu_(Xr;o1U~2N+>Z6_Y8$cG)S*-K9bx(bbU( z?}qj4R_$WB){q*>ORR)ytKJpBu+5lWvtd|)7|CE+Ad%XLb@q7PT8|^n;}9qW~eArD?)@$5Qm;H zKAmT?oq>vgp&02gBA6mKirQIAq+I+he=_>mBM)hN#)n7f18&w%h?=mN(C++oOn#&Z zaMH(MTb$I0gP`eJD%ZOE);s0JcM!Rd{X%`g%>_s-0j-HdxJ!ARY zwNrd`%SGcY3nvz8*YQac^>TyYad8*D@8LhJhh#@9gD6~^0l!C#bezC>4%VB7)EYZHl1jEDS` z$$sXyevUUh{;pg#C`Vkxt!{B6ymKYBLB_(+>Ahdc=7eh!W-HzP8rMOyEg$&*G1G3Q z-CF3(-yRk{i;Hh(l(yS?TZ3xlwbl={?Bb1z*)UwF0F+j4_7G!$ARZ>no^bh0nj=vL z8_a<4qp)OyWl=bm;n9iHMMxQj%$tkr+6z0iN=e`%LAY=%?2(~-3}KMhbL75B-9Bf? zbj_BmHUV*r)I0%kvXEgV`e8{)LX5fe^V)d+`Y2bHFh?2ZGHU_&7BnWw#8*|Mw6^!3~2HRn@>| z#u)GlBNumnuf>Uk7|k4T%gQwbrUQyn)(82SVe?^=`2!|abdHg(YJgco#aSP2%v!r} zfQlBdRs&#>U4^8Zj&2m~($9VJ9e1NITxTa2XU(T)M=!tc)_a{#%jQ8bS11=HvfG7Y z)!h@LE@WMQxQcKA1LmSv-kz#%L&qm4dgAiq?anc2PCk-rpWM{tRs|7abt8glGc>>$ z783>IMun04sqnB&c7UVAc>{^-OkyZZLM8?bW0gNb0T9ndNK5%d=-sp{g3;Jf%^Qq(mwTbPwN3nm&~6B~-%lJ!I&DRp(w=2YjAfPBK(@XgKJ-u*V6EJxbs z5oCg@hvw2r1Ti_SEh28^0Rj)R0pW{gnT4}b?0p}rNfG+$^|sbb@*LZ1xQ*~R1rdfn zv6X_hc$p=!kbL{nJOcAtP{oQaI#}-)qk1A`Faf*5rAw7x zmXdEh- zi2~roQs}rdP*4p?k5RE0$kSJ!QGWa97QmBgrWA;Uj$h&-J66gLrP)?qski0_7x}}Zww{%OI@Cqi3**wwUI3G#PoyN^S2m&e6 zdy!;iNen78_k*3<34ozYgjMY8Dy@*hcD06zDO#-uz4od>e8-&u+p9%xFQquZ4&n}z ziZQt7QEMLu{5qicy{_z|>eNEc6_MS3;%&0u;;CY`(Ly9b%(Xi)ZgVFI+;*)qvC4nJV0$8~ zo-V4Z1ZO1_IIB6WUwPo|x_rvdMRF~}be}=RvnQmkQ7S|XCm0g9ydL73dv;JuGIc<_ z@~~5T!luJt*2=QE>h%OIX3Cf&MncCCJHD0tO1 zrdcal@i`lj8{wo_gA`k|nkR916$=miT(6jP&n&nM@{U!mCABvZ%H(gl_a57hyx{XsM_$MV`bL^LOg!ByScJ~ z;H+_wJjk@oNEyRU9CPi-t9&M{ns5@cG2lM@%%ghOgYV>w&iIxik;q^m&PYI^#*4&H z&46|xK;cW~m9gT?r-Np5W=)grT_nFb?3`)X`*_z<<=Bnet=E{=#(om9z2jr8(VezI zkrlA7^C9Zrs+NyUQ&6bN6GcY2U{X=I6I+X^{ zD7$`QsC4JfsotK*)uxs2X{&?Gj7yX$SxXrV z!BI@hF2Nb&mLxXDm@$Kml2~@et`!~uI&)D2qq^N3+p=9)hNgIrH6W0L47whaeK zEy2{lSH~dHr>(i}#Gs6DBZELX+Rl38K!}Q9_UuEsUoy{Q&pcjr359`7Yo6NxNe@cN z;>Irt7z0x79n0}pT+b56wqA9|E5?&gpKjt`G`Ub8IDYNJzxK{sF5Pbq`pD;P1p3M*& zsrAJzEQUhK?OY;SZcc&P#OXp0#o$jmNBkX7v>`qKP%UeS8+{;Qi;CHYjTa%FGNdXl zY1P+<+6j=O&>2u#HjDn%yM9SNy2N}rMN;4;AB7{BnMu2ryp|!`B?)Hw^UR>y#cfM! z2{-GqExKOVY!?(xj8z}nc5`gjO~H{MVw;+cWxGJ2McGY6lxB6vZC6~b=`Rd_4*1+Vz5;L9_+vGufcMV z;$*-r#R}mj1*BC*u!JLI)vA{ zOaMw>=ZejO>N&150oI%yAf~{fEA2p=NOf{-CyG#=wPbBrvW);U5ioZ9u_eyFHm7U> z1{yvv2GAPAOFfkKvEHb)Z}#-+f$Vrisk4TtTF^S!GiL(!-nT8fyD3zwyw)= zU_f56RomGo5i}`^N~~%HSdElOQ0Dz`)O^B@umJ^wh=$J_K08dTHc8O>~QIgZ+A+g~ES#col zKW1T#FxqDB;G!+&XUAtA)Hs`e8UpLxNw#IF&dSNBK+csS^dXxl`w_}EYCxuAYi*8U z-FwSj*EgrPT&SPjS$X6_E!x|0cCs9CiL-PrB@Zi?g$uUr3shqe;_HRtBGDDMBUZoS z8KU?hiyaeE{`c{ZJ^Q#QN*@B^CXvibY1}Mr8ZUwxDo)`^z!>l%q6HcZcJl{u4QzTi zE`Puj7MFxvKKCeym8D?ORrxUh%NFy(pljFKv3RhklOjAK741wSF zepU>Z<*_rc>@mJgUW_R#7gizhi_X+-CxVk`1ypAt+C-UK^1-X-$~=f|u`1co?1NzP zEM8!lBX2H{pmhX;bqB$p9H9+_A~xdUg;|OLOtU`g3>T2z*1(FOU}e-!i5FiT5F2Mp_a>Jpf@xP0<|}^$j?mKW!D}`tLH2mO`QNGqu6CK41{pT zRS{>73aIQ^LTQ2PI%14001YW!J-50ACvIC0J^3je9p&GwP?ui$VG$D&YP1ropI?Q9 zflQm2-~ycMmX&_<(M^|6Zo7`73)kxB&P7?ZuU`4&hEI_T7h~kwAi1D+Zol>XV{=Ou zft{@y(Xq#I)2!-hFX&)5^bZH zg)_m>uYd5jq-DdKVX@)Rt!h)Lb?0P`{3czRMRqLo6pU`kHJXV9gdf9Bo#r;y6=GLT zNQm7yrtI^!jZaT3VpEe7OpW*v`+z_!y9*w%Ty9pe<+P)d>)<5`XEljvhjd&Ca?IOl zh`5!ut$DCBcg~#jn5to*SSM#T;*Vt1ATp>KyDVyBr^B@^+a1TREMg#y6U*8o6=Myd zZGH8}_H*jS)eTIx|S9c(Dy;FOaAO6sB%Np5lm zLwr>A_(fnC1j;AU^N8EnM2smsnE6D4DcAXMW6x{g%5vF16`Z2;g6nkYfhZO+GKsUe0^$ObonKmeunKM6Zf8jlRTgtvh z{yRqb8+91*(K9Pbb)QheGCo2HE;Kc9=^(1 z62<_sjCZ~lLQYc|a|3EXAhR9T0;?F@yaCw&*syA4 zm~}0pZOeAd|DYsn`CL4;C??xGIV4mZfaq?n6_YHLs2ep>tK#f~t&ziV25g47_~{^t zDnaEar$@CuPn+7Q;Y6^Z5>&i86XISLDH_Q{&FV302n>PHkn?JQIq|XS%G|;jlETRd zp>rlahVVgIOX_OZERtkr%`siJjSp9}t~&&RM;uGYkQjBY*)G3EY`d(&M8Y70BVpXK zn9(?t>>+}=!}9^!Fy}IwJIA$bDvUT3rO#-qF~}xnyMUZ|OQs-`UWdXQg~M`e1d|Xa z6;7PUtP?Y0JE|o4Wz4}gRa%6l|3oWha*)GXhjz{{WuQnsWFxgsrHx)}jl>=7KvFAW zn{-zsei`dPafc%bn3GYpHbX}$7c+QlPO;+AgIZ9g z4=FwXU6@*1`vZ3s%yj0nExH^Z$sBgP2z>gw_RI-IyYOm+L%=q%zO!+=&l}F%Ka$lA zO~1B1)wQ$h4H2^esa5k}3QV-Xp0*A(+?-X#cD5uEQo|-{7N= zRhl!X(0VGA77$?$Q`AI-MB5qLqYNn!L<3iGL^GnD2T5z&Y>1_kW*DkG$&zeVmO0Uy z4HpY?CSx(VNwfAbZQJBQvCs^f4U@Nc{lZT*b6Ae50B;j-~ zh>BFHEY&#FIb1;uIt*3XFp7DKpFqKbQ6fb7wl$k=*_P;vR=4%K#9Qss@GJwblKF7c zDLsf~?ns~(Jk|uWUB>I%Yr3|*%A55S~t*qxe(8iT=&I z|E>Bt;OcVNsbA+`jQv2UoYX3VA=pmvv%V;?{(ymr!h7i%4S%G9>4rVHm|ioSFTYc*BTZ+ zln@492(dA32BLumcDzhs8|APrv@92D^|?ETb3bsk(Cgwo4K4~-rQ*4<0@sbf1Ge>(pCM#B!$pSG)t%Y>#jwUBoi34*B zMnlR?&N4_H1Q6z25W18Bp2aJ+ng**=@Uus%l^=I+;Du;3-L~wArp9JrgKtyvrvFRt zc{{1OHLSFOS3f+_%qjuXqnMIta;J!v{YS!WZ82S3qQbWJ4VxaH5>YK|=WN zi*Z8OugT^_4htXJYn`dH)?LLI5n@@&sJ*iV^5ZK*rXsHJ5N@S;y_pld!oji0q>rNJ zON*rE8=WMC&5AYP1!D?$pfSLrvNmNe&B9Ea;>5d?O`HpY8=QDqqqvy6-q9>)6lMp( z%my+UX7|DJMw>P6?a(YI~Z1JQ^h!RrmI@f zY|NfGA=|~v(l8Za(}^Hz-_dScw2&j1tN=W*y#QCwoDm4+*U%FHHNP;)BI?ZPp#hTv3)TatB`F^4S64vV?Ln4Yn_-FUpV zQpv0$5;WpUtUT7qa#?pl31U>FbD*ZA9b?G0@)GAD?1vuzWIu+LIcgLtHQQmtmnA!c zs#z|bP*zw@4X!g~EHAZg8+ZQ6|17po|uAeYWP#AS6HF#59D=&38u1W;g8hVA#o-QcgD{z!ZsB$O}NiK7*!m7-p6wc&;^A zmyRy!j+^h|Yx%RPONdm#N(W6yp@4?p=yK9oG7D_5UWK73d&Y)k@V zXTIKJBY|qrt?9Q9?(59K1<@UbXBqKS=Y(3#psT*U}cF%3MPHdHxv-*eH ztRN}24`IE)tyr+G#Xdl|Ots!Rdwdp5Uh4FE?bw>w)L@ND1Qa`>Y8K&?;XUe-e(}9; zQ-Cc<&AXcjUTmw{q8Pm<8{60{E@nbzusLJcRcF=aY*{Kw>os49IedgDUU#Ex?@;5j zdGegwapKHt0tjUjo+Wy2meoN7+RZ45 z{8*@%_*m?z`7=_!su@;f)OEKs=UC9WW*}^pJT}c#>NO9USFT6=F@zT}E)G3jOF4z- zUK0%#*u{#<01TjIdY?hHYk)?5stz4c0WeE;Vfnf*1?aY&Hmz zb#iLTH&fc4>5Fja{b)ODn!6D3>SRsb(%1(?@1$k6HIpNfHfvHE)b zF2t(Oj-K!LX;zyHWf_Z}i-1`wH#SCJys#vjpoCVn+y^m6@DTfI>Y3-B&8ZvKAO0*BCVBtdP=jN-cPhs$Q9~Fep
vTPSGYsz|}3s7)QJRm^h z1~n7b%-nD2id%L>Q_sA_ECFVPomUtj{D4`-vN_Se`-8V@y9~HPL`$enPCTKJ*|{8L zaf_4|Z1YBE!=;Z&80s`2taK{kcmW@=Vc>U!k$k6CwDHri1(q&s+bH&DRii?-v6BhU z7*V=?9!=1)(v=u*&QVkqsIHz~Q{DR_8Oe5>AyJ^1D5V)J!gg>6o?046Hlk|mxUQL) z?Bx30F7Sd4T)GRi;`#+9$1cvC^QMbdiXoS4;OBzkDuSE46GemzrJ!p7C=X&}%Ma{N zKl4b1Ma#{Dk_{PQ#Fktyop00$;0y;<*!wy_g)abpR~*#uf?WOf{)t$Rw-d> z>_w{kT!YtEJ=fqDhiCNYGY{+9>2ne&Kg%R^=CPA4=Hf8u7&!ZG6Gws!CQMH3KPp2# zcSr2Zo^>Fju^ugmVpQ5;Jx|mo*XNQ#&1V($pk0HiPO}c1ectbwWxGwSdb*rgEPKT* zyCwLc*yfnduP=}Sb8Viw{)|5O$cMB6@Dvhy>7&o1_FZ=BfMo)T#51iTk->FH%+8poi0|$4 zaBYSZ60K@dJ0^0r-RjEq`eCIVg(@P4Uej7uy3#^Q0AYF%VvOktrE(;{>;)q_mK#r( z*cVMM)S}OB+H%&G-BLH=dg5Fe9$OLV8y$`i6CcWm46T;ZKx8x0B89Oq#T5!%R`GPJ5@3lo{@uYgTqUW%9$}6BK;7Xp*e-MzJeFsSjg^&1~y2J zwA4EET=B|^Q4+6<_nO2EqM0|%;Mt!GgUOl+(T&hOlr?0%WCPF%tv05dSCQ2Bg3y4KUP4KP6(f)tGk%PvDvEmmhpwi!g52=2gJJ1!-H z#ERe&%zja;!!g0(ECPfz#5iD3|Aylmb;nJ&HwP#iGG}XBwu4NG9id9?D=>Sd&eK<( z))SW>p#~pTC}1})jC%8qi`WbSk=+`xsaRtM+XwSfT z5zCyrr4weGRL|@Q0)&UyoHr0#!8_kmgK&Yd%Xl#&Smv@VIuh-oCvTMk7_7V`+uAh> zSgZwE`0f5z-v6sQ9j6-RoQY-SB9@-L1j>0-ZD9daIfm3}9vd?;2{*cH4SslU>wm3; zk8P>mqcZQ&W~1nZI$8+0XHK@LB3eT2<75g{u02bLHCf^rTazbAA_hhuAqP$|WsdAU zfZ(VbZX42;b9nN~Q{u*3A$S7;DrD3s>XpSD^ulspiW33JOv!33LJ354AzLof|JMuu z&z^tt+_fPtlEv|lPOE#NhW16ggt!CY)3NaAi8UPpXf9^d5kFWTzx<@A99I~75v_`( z0QN!8&0Acs)a7}?3J)R22{Kj1@N*OLTtyYn88aji4OR7qwcuqt$uRyAxZuV5-G`?u1}CJdJK*}+rW{7DN0-;gQ~*tY0YH|V~{ zqYOA0JbK$92Vx;qBNryYy4PR~t~KGpiGWDaqS@wtz<|%DWaG=cBuq(GwS(H2N^D?+ zjVem=r$J2$LSk%tl2R>Z@|w$vJ*r;&8RJJ_L8eoK>O(RwrCG9TU_S{AogCf3pI7dH z7<>UoYcEv?H0wqYAx3KKsdUPpS{{9xUnJ_xf>JJt68!d6jdg=pt@0(Cdg>NDasYKe zlS8g!Lx_%0adI-Jh+4aLU{+o7D;CYN=$z33iC}oBKhhg#w&apxGfY-ut2AWUE;IBs z79H@#bTE6($;#Ld!Ra6`pjcoKNTmD0XH$LU$L1~)rggz^OViNX))4^Y^4UCD4~nm zmJ7A;OPXiR6|iX5daaBnaZ!}ZL|oi^*y#p0{<#hiW?`Z8L;cw+&(uv4Y>W^Pa>I*Y zQqEDKWP+)QjugRVLDPa~06MX<(0I%-h?PY>G;fB3H)#Uufc3d|zgl-5-L4ZKYvIB2 z(MFs3iGH&zO55^CW)WMeY^lvEl&7KQVpu3 zA3f#=X=0W`fGZ?#M3j;M!`|{6h-idl1F|mMOjuhhEDX!8oM;tT6 zj!x+ohf?)T+;;oe(`Z$2R@iQqHMqX1$L{0Kz7L@JDw#>E4 z95@QjAW-5#N7;p$dv^>05L2}UOgjO>HrF%XLXBF$T5)R9vnSh-yALobw=>5Ihe9k+ z{P1E+2~dw%u+)p#R0%NSMuVstT*bE}cy*xYd~oKb0!uwt$hPnhQKdF_*O}bW- zEZbU}ZTw7;gv_(r&9GgDotlXwL8>qY;mX7}??1o&(Rb+b^(zDlMW;iGlDQ6UYZu0# zJ24YN=y-s2Lg?!K!C~-iLEhi8f2P#g*_n>K(aUaqsowb6U#q|Vb$>-a`KF)LPk-w_ z);%w|SDSnQ8PtuSs&kKUB-to46w>pB6T!iiJ#Tz7uR93LI)`nBivci#M?`ulJG`}$ko_V1!-BV zC$2o9k38}b@H6HbNaCsnMJ<&fS=Bgh<{yHs#uC6YqHZ=Ay}a1$-v)q^Hy0+XPz}Ml z8Y6Y%GfC))yC;cRR^`JfU^jSYwXr88QaNPX)_U!kg877R2F*6)yv$LA5Oj#;4w0%| zBc9~cwYV0=9S324V^0E*-EngtEP_4Hi4dN&AqSh0V6;>Os}wr(QWSMa77wjy>y5#BQAOeGcGRMAi?zaz#r#61o?F3QF-s ztNR&U-{e%XWL`(hiSD}PZrK|-iHT7tF(&LP!7Q4&|2d~Z(eUzN{A_!sr=NR5nJYBg zSt9%-4)CD3mB*6Fi}ymI;)K@xFi@eTbXOQ@sT16+Wc~5Z#2Lc9qE>noAt`uUa8l;@_v2$ zSNu8s&2Rc%{ew6E81IRHM}O^WzDs}V)nBW-Zg{E8>x_CIf9xUg*B34LO3Nb9HtRx9 z7pwgpjrFP=7}!H-t&QX|om&ly>rOn^&Tbp9d(t9!Pd@jwj*d5~vhdUEu}-`UReQ0y zQnm8J&B4K}pM1thN1q;V8RJ|mSuVh# z5J6R6c+iaGjrXCaKBMd7YCSx+9Nn(_@4H{$`Q_iHAN%INrJsJwkLf4A`01yC4L_t(BI|n?BtZSC4SnNK6SYhlXb=MkA4u=Y; zktD&L4~|zLHl^X`>#!9;jcjAxOG<{i?2>UMZvi>aIF7 z*;Jx%t#yzi$qvTK@fl@anr-DpPbfz?O_<~gKB-DOFFtD+0cnsS^oQajO)5=89m_2e{GuUNNBTGw2`@7nAu0O6|qcIMp9~>H$4=<*9nF zuyb$nA(~*Q*>mbe*rq6jO67`TTX_vjo1;bd+iH_U53_nIsXBn)CS<%@2_oqMC1faOtY2E;KY!TxrjyQMm%=|fh(Ljht(~* zg!Nc_P>5ik93Sh!M?cP#{TprmGtZ~_&pq>ox$sTQt^9rawfB9lzT->Zq#ygH@6&(# zmY>pp_vd~}KmN@>p#S={->NUT>+^KW@k^+-(XwnL^Jc*MqH#DF`e221C?JVRvW>J} z$f4`OCm+&N&p)pG1Fl*=Io8^6rfJQWC-6eGr|u#qCywo?mNJJ58i9=M80REs93Xm? z_&P_8gK!hrXoC4b=+Z8_3YX$#I!;9ss_k-8iX^T&vq9YTGtESBG9p7NAPO7DKL2Jt z@HLwanJ%;)Lzrl~Xi>HsV1GLjHNvDI{y+#F z+`1C0<0e}-5*(@R#+I-})2CkLlaK^euY%P4{RaN7Sx!%9{hZ16}pVrK(Z8>QGvZ41*2WCg#8;cH3h>0I}Q0Jm}|RE4K)Q;-kv_PYw2Aee6oC z)2-ChDx(H);*8mSAsNV$GR#^~a0`GZDLC$s>y`Uxh~q%gdk&BtPl}mjXW-r%G6{?X z>S$K2f-@=~h9r2J4NhYcCY6#eT5|?g)*`iUN3%OVV5u5@NQD=mm_y+0GI@-_FbeIx zSm6Pp9mO=%9c41GD{R}uap@p3I-StuW33?CW`JOrwy}t_E8*9fNV0=lR$7m1Z1JMI zZ@EV*4oc=FTIysg(Fh%o=O&{jhd70vyZWqbJP_0J-(!wmy-*Mnuh z0c^7d$CP=M!LVDAB`8Xblssej#8VIH!6*0=2J@y)K5%Te(iv~2r~LWqjQ6CTgETrN zbi5qt{+E5Je(0NifIo};n7;Lkze)F8x?SQIs54~C8(lsWE}4w$Y<^M72Z)7ay9Qaj zizUVy#CzA44<)DMKJe-HYw-6JQ$w?nArinFlTc3=}AQ>Kv=E zyC4PVN+Zv~v0IP&Pw;z~$tBw*SlTsms!p^)qa(3biBBj+b|(I{)2oLr$i?W(#aL#q za^a%I%C%62NPrxDE-b$7dIZ*Z;DO_jOO_a#n-y2Uh_w)WA|E)&C#nc#P_WrI$sXTHc5Z^0vq+5BVkC{|X-RZ~k8W^q>8^`io!w7TtT(-IB>cv{+l# z$TGk@g_y9Jj}_JUo`)XLShoFq)EXt(_yf@knhCWeFjhW}ZAPlrgwbsra>@=IDfXT> z-KkQ`%r)IoowS?MBA#_()J~#XG6pKG%n_^eaV6X=*D(q%YX#S z4nTH2eq;w=#9!AP9lr`OdHgIk^FdXAkZiB&XAub?Hj|>$SIcgz>m)LT5*(qNh4XcN z^O2nV$K1Z{b$?br`7M7}f8lk1PIupMhxlxup)EOEExcnb*o)6(o4Q6XRb$8!;yN4d z6Wn#J;seK`Q@#6B?^4nPC+iWFEh`PEt+*oy5)j)C81_)Eg~cn_2nQ)&3S_|}FQLqX zOUDDlsE#K=q(d3?-{Kgor9hF7Bp$5NAleMUOR54^>+BOJDN#Ee=j7;5zTwC@{J$W5 z)8z)_8Rl|wadOePZq6ma&D#(GE_yG=rA(AyZ20k#UoN-&6PKSRz92!6x+Azrj6FNg z8UQs&%uRz+k$fuen}b**HD(PO6kxtCOq{wlSQxnOfJXEHw8Uo%WXK!ychYxj;RA$S4>MGA(!b)?t6^h@=l-~5C8hVdiVUkEc9 zi_C|Q1@U6#CA4VyfwyHr&z)V-J3sawR$U< zj3L<8!WrevG+X7ekkT1O1~PbKWK79o<_yGj5ndGprHDPjg^kV?Tx9ihXG zxFb@uiB=5=A%U?R39Z>O_bN;a3ody%J3B^*U{Od}84%Apb}Uyk_hae_OhEBjMfD7% z1%|8ASx-*0atZ+_PqdUs4kkUBcuBxJ-z|I5UANt7zWwQ<@EkVUS+Skd0@+F&Ng4P0wi%E^yW5Fmyf!-8OYF(y3E1)@=L|hTL=4$2EQ6 zGanKzN5|EiTuwrHCN_K^S&pn5PcG>%ebu+uzsY#*yp-iSypJJ=<#G8{l7l-`j~u`Lpxj{%1Mmueu#ZSNX2b%F{vdjl@C* zLeF}{9GG<1`)=yhnH0h>J7viy5khP+@R>``TzQ5(F->BSqY$>M63&C5pc53jZx|>^ z3ZS&Cr7#@B;>VA!J@Y5uaBv)djNQ`z@Xx;Ud+SPYVfz9A#wg*kfF~Z8i0q7bT%py> z3T-RbI(GPZK%Ra6*>YivN+Ma+0Lq`Wg8@asEA=A7Ixm{Jc0}CH&4@#0)yAY3EpOOc zen6e^0b$Dx|F#F-uH|UMGQ%7SkJaP1W#=ZfJavlXq2vircx5wBdLDA~;X~u9Zauk4 z-}RN>p@00ApU|Ir)&1I#zu9b*SAK|^0~#b{4OvfS|DBJ#lRx#G$fhzP!!S#Mm*WYi zbcVt}rme{XW@$r}&d?_OV{K`c@}HIiFDxgalqoD!&IBZ_A!wQOjM%;x(I%-AhC zYteSN$V*{zqJ&@FSjc+3S&*}Ni8&?Ph$Q9<$F|b!=mA$yM4 zmH|_;Q>^XSkhRe}KmGvUUR>9*gh9>vAwPJxO2Xh<)kS~)wQtcsddpAhi|+lR5afKo z$R?@4!K*XFk1)e%u}!Q+SYiRLJf0U(DuO>0{&hY0_$Tz((~s-y>>6?gng~MJa4Q45 z%o>L5MBLmVd{xW7%OFyec3AExj>XGTVnb<+weHwcfJm=tpUKY@;`W@204DVkW{vne zIz|xN@+?(~y|%8gGrZ`A8#miVF4nkQl;s{DoqS9FS1RIi*p}TFQM7!+fKq+{t~S?C zX?4B0=((9zESH-rmvBiQoae4zDLVtzxha}mKd_TeA?|(2bV9i~^kR*8*0F|)7c4*u zV3W@D83VS2S8HUhE#Ehu@psT`{B81)XCKu=Pkf5?Tx04j!sCMCyKzQOMLjav^mzd-T!U+sXz1g^o4hQzBbE7=7v^kkvmwA+55&DJW3Acox>5TPGy#8kKu-Ajw0?h7*He*0JKzCBzAZF(rJ~&I}X48VU*(n<;9HZbn^m z2!_=H?kT91Sx^ReBY13z8sBD&1fK#TAkxpTN0x%4v^L9a&cd~~LIMyYAt$DOTjp|5 zlTdh?`w88K8>0k5G|;pH&4CpF01yC4L_t(4FHrI#u}i|qwK`Y_Zim*I6UcR_0RFxP z1?D>JFya@BiugNj<7=MXels<>>-vj?>RaSxyA`RjbkQ8-8zi&8ku$3aWjnF)$)a)WE% z)-wn~7-~4E0GhKO?605M3bOjv(8>rc5+`*dA(ak@u=^U56Rz>>j4==rrWZ#_46#9z zAOU5KqqGgKeY5wm6$OFY9$_6Fo#^);e6No9<;2UPuE5Ip@GagizU-x6q@Q}r-`7{a z>MOKxuP=PiFf3DIm7LJPXSwSAS+;`!jj|bqfe5;l7uLRSrOok;`i1xYl1@%es?L`A zwp^opU{2i;b`IS!C<8F!~RfG#abkK+yH-b`bl*EDKYGg93HEwE5sHRcKi3h~) zo02LNh&_0CBR<|JUm0#Tm&n_)Hl8$-IIN^fRy7olEaquE<}-(99b`>n(q> zxq%NJ-~BazQ9tu_ zkH)vXqh+llekAdlfR)ZYOjQxr_~L`@s)^!Fs3wu!Yeq??BaEsRJAyONWQ3yP76!;# z5-Y5R1Q7ZV@n8rYBt9xRh>af?VK*wbHe>9pi%V0!%4d*dy|B4315<_PmJwUBgQ?p% ztOZ2OHXLlak+oaaUANrBZ`LOgoJe1Tu2D;(9v7QX8SsXlyTS*KMUW7BQp<^9fa=*- zJ2pD$^DJz9I8$K_IWGX)_^HP3SU}uH3RZjxgc#+tZ>ceME)3a>^rA9gidBh0hs_2} za0TJUtg~PeyMuP?;EAvD;y!Chod_Q)p1S(99)0FB+H8)rSvJU#j+P_c#5blt^oF@CayU$r)ZfIWRwhm=7@FVL&c{rKcV`6 zs6FG1;ZU48Idk%Y1hUCNY{#WW_rtj>so|8^yO}b0)r)~ltm@;gMJX?3K%n59<1;!K3d59c5rrfs=IEyLqGIQ-^*8`Z`Q_(jy5McT8?#ca;&4{BYoi0 ze+YL_ZYXQXnj^6RcHKK`Z!~Z3$I99wvHxR|+=!Xoc2iNt&&KSs3LnABVpX|b1C5`2 zRSdu?(DPc=8h}9yFK*>FxR95W#KVnnfN@2LC6EE%hSh!65e*Hx zG&{BNpyeB|o&$1%h)n>UZkrk{@r!H8ykgsqqqO+!7?ZIUM@_;23N4yhQ2`M;$075& z{g%6Rk`LyILS}_nu&I-8!KfIA%Z3GZ$~TDDu3s%qhMrqgVot{FE1qbHp#-e8+05!H z31r!p=%N*W+PHKpVc6KzXa3lwdIR7=qMBQLOSKPD5;fkA4#rNDW(HiweXliy90)@D zai)O93Lc4VPY}m$JF$Fokon*Bv3Kc|*Fx-w4;)AQ67rh+K41Un&;C8V`p#D<@1vV# ztA$^fIB4D{lod;J%yQ~mV+=|tB3kA^RUbT)nb~F>1%-_z2wH1 zXgON6EIgr(hPnQ!}4qk8e2E&%fsvb#!t`axPg`Mo`p3 zVp+zb09C6prVEqu#>tdww(zakj>6iKBW$xO@n$l%*YxEtd#(P#n}1ySpV^)qoe
    1gyNtt+pniloQO5wPMp#Hi(@OoPEt*RJ72HMM=Bw?*>S&UB*fN7cBSe$1$xPs!9~oAo`f ze=8p_Htd@p{5IruJ<+(Z2lrn>qUHWlW(S}vjA)zd#i8ZQ2fB7bxsF>lza)91GxR5( zencO7^dr0v4cVp!EOHp7axFXcG}}O1fJKQe1deD?ma&FnhmvM zwzBIJs&fm|Y+s}nl}bcF^HL^w7#pJ@0irLHMy*ZIv}JP_-}jLyvlRW<|? z=t)K-kWiFihhuE{0kEy72`40Ss}9Uv2g};kRcbVJI?G`s&o(&19)&&lmg9kkKcr7R z^SI9P54U_IX@PIT>C{nFKRE`-)Z!QmC-w;wrwmpD%50(BJ%q zAJkX9^0ho4Tb-WryA2;S-u2)+;T-X>hb`draCC}CJ1(qh9E%{FPq$`S@k z8arRWRgaxEp~mSAoDqs7O!h}M#-NbEjfgf_JCHHc(Wx1;CA(d%1I@YyZp3S)T#wkq za>i7ZZCKswVancFq3w>LIVe8Lb)_(ig0VjjoOCGpO>!{Lz_uL}8C`2g%Tlx81Dh3q zVPRwhFmbRk@8?0Gl!=5P!BOa~^)B#&64q?IKSw{>$72Z@<;9>QNqgA7Fcy&kx( zAyhh*$byh2g0)2AG&sWRr#%)G&VsX2AtH9vPa-x++Q|vFwNGSb`pwT)X?As)6|JkiB+mw z1=$aC2@V!ZDo2OS6$|1FC7GiQ#$C!9XU+RrAOwFQ2E6N*?ebD;6?PYVw3dqy@#H|b}7>*sWG!wt&gKowPk0zJ%E7G1g7K)9PJRCA)`g4T7}t`7zdZoO#B54Dyf-Eiqx|JU#S zoR0YIZCQ?F&xcTHLzI)KlOJM(C%eumJ`;`vhh+!Nvi5r&tgl3pCE1J-^70S9G*0#A zFMgBW!f!I;^h`wc;A0QUdD2A-P^cLZGe>HYUwy#Y^~9zFhXmQ4Kr1_(PRhCp#XYpE zZUUsPZxX>GDVtE_(2gJBGS{3{kNjMBP9!0ze94)p5W{ zOlW(*4^hqxU2@3Y$5JB#NcE1T1{8cOg0Znw9DG%yh9Uzmf}-cH0LGG%7pc9s3EI*$r4Z`KhTom`4_$G4MUYOK6UuKj?3#}}IUN9)uUZKlfD z>7^Mc3&n*Or>en7K0>ga;E->*@mAe*@{)#=WtPXq&srV71Pt!g>(9%slcPpfDA|@2 z5zSae?|XZ2d9H#|CNrzjWbJh=ZtsU&G77U@p5@Z(u)k*TDmK=T;>N3~*;9bbUkf1M z5G2`FTJ{`mPajU|M>qiq*k%x33PrGO%Vv*=IiL4@@&TPO$uZwD{J=N>M2=bzN`SFZ7A zX34~a*cRWhNa&%%7LR?fc2<|LdQdg!${Z~~%rCfDMYEF|55cynLdPTCMp8111Q#do z+qDPk7@rBcWaYZvQV6N`r!+8fV+m~uPDXVe#j9S`+DfMnUMF(l`udbzh=reR^54=F z7lP9l$6tq!y@i~;b9`EC$2oH-+AyIwTvxTI1!~~ zjrBt?d+LG)k5!ssP)mqy+029JTr;&-kzh#tZocJ{`u#8e68-hB{Vtt=T;5*MC!hW_ z)<7&ff}Qg#+l6OI7`qo2AW#bEc^xBv+wfUwtUvRP+_VStH6H>zdhNloL?CAnDNy^? zby({d6^frIDM^djLcC&VDt0U>NSwh3jOF+SeejV#*9Uo3@AUiAHDpTe);{sUjJ4n|BIQk zt#wL}GOC7=r3ij>@-cBUh5>ts!(Ult@r5m$1dF95g}3dS1dI+zLENgZInQ zOg^{|w|wh2$PcgbrX7FfQLNmG55ZudYDT3wgTOK9$-DH*FWiN7f!cC`*2;}_jmyY! zbLfNPA9L}>jyZ@%xK^5w6a%6~9?~5E689YL(DT=?;TdL2K6RQ%-DBvm%QjZ^imD%S z2n3lLW9>s>r2Z%y1KE6in*~e4Ra)GUc$|{aZ;6>Kbt17V zmcisY27<0!gRyRqTxS)AKZcSnSwe1G%+j!Cbyk?F(50Lxx&i;j1`2eVTfJVcZoZUjffkXRk2CI(U24$Ne9;JLWQ zW~iNvdmz*xLtykMG1PU{+A?qKQNY*G|Kqz8oy{S3`C>vi^|G*DqrP+IJ zW2aBeJ2ZO@iyBd^$9w>~_m+F~{crewef$Z&;n*Cj{-zIw9u_&w%eq9Z0$!*KlStXQ5md79E00wN5v{49H95fA zQ+vm=GEG7&UsgJ6veuZWUqYU3Nf9#Ai5VXy@k4JA&%>B|1b{TwKrsG7OvOziQ=oLv zV3xPd-`11Ee5k5n`~P9@--C8tx3WI)y54WjwdOkPwf5Q@*c%aXw;B|*MKPS@EE2KM z=8y)3fFK>{U`gyE>8^D0SJG`8jatzTraOt1vJgAQiY9ayP1M$Ob(erNNeiZ|j#knk z3dm`n*J*z5o!@WV&+~nA0aZzrRmQn1_j`|fjB$-?jCF=9b`@!Bd0I;3b*_d9Yp6f7qb9OQR7}9+{8#YB`{t#*@Z8IgVG>}4!b;Jma4vc6jSr)-!AQhTTK&w> zoZ0~!88}axln$F5B5xyuk-Yof*g6t{;h%G1EFueiKNm=zl)28Cv1`r_`0g2)O{-uR z$rIy=4VUmN89qzS(RMC_^)LLu|H%*hbiUy;zcw%2JWA)Y;3gQkAI_FpV5NiA|S=?{)iwi&xxADInyIpVO|MJ(rE6+Z2lV@+5hc}lzgmk0WPj2|k ze2|x4d?ByA__o~iCNzYw%hU?{W`_0GVr6R-SVI}&2Y^bNyW#a7Oaud;7024}nA_ib zyv3dk)?GFCQR?P$$;&T1pLe|Ng*^Z0A#WUhYlY_z zlUv^S{x9nN^oM>X|HFwAod(unp21Utt4LS^Kp%V5D0b8L`_Rzyp zO`S$sQl`BmZ-4fMeDNoJVcxiXmG33DMiAm*n1|d0 zg>YK#w*|_CTI-L=mMgg1!QgUbXR_j75rb@(BY-9!XN7GL%)>aYAUVTlnJ;{D5Mb5} z{j{OvNKVV1c;g#z1AJk{^BvF=TnJ3*ewmh+>^j<73*X!q`KOh)J$n0GLeRTOw|TSaZq5Zkx6wNJ5w}XJw(J*Ty&^pe*Qjf^lFfza|mg zy;>izJ#(q$jUtMb?nff!1#PCd`A}wW!QF4tHjY}CZ8uls34wE7PuXqr?w4Mf|L@cD zT;nI4ko=;f-!K4JOMg=r%+T2fTgLoL4A3F;7F%BuQa+J8{*L=u^+q1&JHPKw=hfQ} z&tIb6n9sZ`pL^^=jVr(PV3CExeCF~X?|S>&^Zdg{3H4a^tSkuA2oOGNxJb@1UFt$F zLxUd9GZ=Q+MG5xMT(GNFuvH^~er6AZO{#Om`!O5}RfIi)o|YyddzALz5W-(+o7>w* z>*2#kpAYcV&C7pR$NwnBA6y<>Uw{1C_xsS!hZ%Q=`$3EKmY{>)HzfgEtPr?InSnn~ z7MRSz$awAX>#PKM!dim}y4{+{P}z>Ejj)gI)FLyr+OjQ;Yy*{~?MRv#i*@lPSp3`G z&L4o!z2-&!sp6e4zsNWE2f2_?g-zQ+V)DlFzZUn;zd!Qhe>4BtkN;q9jq^j+jTgQ6 z+_ndwl$p5V+V0;1jDzRo+@w-sf7D?Z4cljII6I^JXz)Mk`M2>K#)Y^H2xIM~Td^)m z^iDEOEO2sE)EOr_Sg5ub#^*HHoqR%L2QTMn@R|!j%&}W7fH>6Dx!Xs~=RzDC#hlwv zvKLa|bM6m?oj+v>7LEyOBFO|YqXk7~yE*GVhsV3lJt{neiIMUOd|0UazSWN zo9i%MC*Go!yIpxs&g;SRx=2!BVIAuz(Zp0OTRH`l+B0*m82f!f?mFsEuF11E0Z6(l zO6>aw`xBQdAWB*$&K<0s8xd-E=wbt4in;Gs{)Q}I}(qzZ`xq$9G zOTFeq*Ui=fw%B`81N8cXU>w>i08T_~Ni`&c3zc9UD`RkuDwcW$B!htz8 zP95@rQFOIuC^x>j(|7_#kUC*#p_o`sMDCTi{aSl!!XPo8vlSIU>;)C`5X~i~WC_o< zM<|!u4=y=Rqg0y8*hj1D{n&HS(Li6=d!tkd@TIT z(1tp;%c7QAOiA{2-N`wI5pZG_vW9`JiLnRMsPKq&m*nwSHaSC*oQ#>VsS)KM9qO(J z?pYX8cWAHra@=Q4z9bON)D~n7poPKIS3<0bt11cfJUJ-ArGz_y65oH>8IJvcdmrQt z_Vbp4Eb=m9k4=+Y6OtBItB)~r+vNjTt92y+w<(6t0LrPW9>HM-4%7tA1~X?LLE>9+ z4>7zq?E&JC8B^9|o2Svj;U3z10Or^^#)MY(n}xdiO02_TE@9GfoH!Klx8_hwoGcXU z@gBCX%&<_Wv}F{sccdYErNtoIUO9qR_E;!{@S-kOFcJ5`TPu&Ac|I>a|H{r0B16Y> zzRq7d!=)CQJZfl@hJws`KV{r|XaBAEmW`RGFIQX9-md=Y78}!ac0&~pj6Ajr0M%kB*PDk1^E8gj)712vH~boMT=}9G z;`9tWOJR+=*ke zXH)h8!}Y210iCXZL3fOv+UAwFy%_&Z(w|2?-Z*Kn`Bf(G)XfVeA6w$c*N%g$j#vL_Y? zehA;y7ZVu$^ZVHFBw+Sn7Mr~mh6~p%000mGNklkn$HLjOCP_fX2F4@{<5;)a(F!BB;@L3o=F}#c3AUM~zW(MOeS!rMw{1Gj z-Pk~^pEQnb!)8AB%yW5}FCBxsDB!QX@v7FH9GUoDKX!nFb4;U6Tl}I?Ot&5gyDjUU znr-iR-7u?G!Ha$DG5a+0YS0LJ9T%N$vd0j&IV&W!ioG(Kq39u~na0N;WC{GCAK0nx z_sLX(HVNDTbQmJ1;4&BWp3DPcmXEEXkLhN(9>@DV#q`PLJ3?N&z2S-a_-=OQ`3qb^1 zbuSD48kf}3QwygpvEE_9%Q1Gw>EQV^senk!4a+-Uep?=(t+&W6a^cIyg)bA@P~+Je z-+1;I7{M!ohWA51`Zx03Kk|Re8~k2y<KHC0B#+ox2i-d!y#c zY;7kc-sGqe#`)O+3Ss|H1!k6ecz)2ZT>^yzG2Qd60_a36vLb(lv(P^(rrJscF&424gL$)7A6&9|Z*2LWz zh}2mJXPGIYjB_k)Cl3X(M%tnk6pcK3_*`Ck;gysZ4>Dl*#p7{q`7Ju8)Llaq=gHih zW2J=@C2v_NpPxI#PBFHQMNIQ8GIiY_78kW=^|^T~ZQnw_ch)v3de7L;iPa`(0NUI) zc!Q%`1LYdSp9`=eG9K~CJ(fF{9Oi-UT$5TdnVYvJzU&>$_>|Wm)S5>ZV_)(Ef9Hqt z$N$ow$PfM3Kb)U_-v{!O??uo*@iXtw-}~wJ=f}`L_A~FxPayC8z=!hc>u=^w?-@nw zd&@nr{pIU=y=w7;pMOr{3&#V#l(5b8!?t#SU39}TXWz!GK;UtpP0*wwfwWEWt=F+Q zgRv0;oOfS4Y_SqY^aznblo}WWM8jhU7F=<(O2GsqWHPF^q-y9E2Pn&I>sc_Fb}aH-Mw$ zzzh!{ncOHTFH_J}+uq^Xd=G%F* zM`7OZ<|#jr2H^|ukA2`r^QXV>Pv=M9_oK8_V5&{c1Qk3av0k;<#t$8ZF4MvoEDQ|R za}pvRgv@3SDF*Nk6+`>p5Y!=d&XY4FV(dl;UQPkgBxXMZV@|tqFNfF`iCRO(*n01c z%iKuv7Htvhg&nZ>*>lGUqdhYYj?sg=`A@{@ePi~sgs_xLU{-7?4uriFJ#`x?>Nd6Yd2-nSB zl@|Yk%pUM3k42o^1IB7vc`Y^FK;(wEKCDZrS%5}bUyz;{H^Lg8r4x6BZ!mtgL&94aO7!5ChC4h|| z#PbXkQgzVMPK9FK#6JfSU~1-Ew2?!j-J_ip)OWwRZ&VT&3{%ZdI`+q9(XgrexGYd}ku$Qr@!yJ?Hqn}*bopZ+a zSz@)c_C<9k1eyL8(}=smYr=&{z!HdIpsHW&yV+1~d&HUP_E=ZiBA7Fq?W|bSRgD8o zjD4n0dct(3yfzP(ILcWUlGeDoH-@*Lr*BIoehtJX)N3sDopTuq3rQ{dE)XiiH+G2E z&TzZ$mC+DeBP(J5{r!wMwg{m>$1{A5_vP7#kMcIYbj)8BZ!US`@tY|x-#e|j!JO>T zEl~uAxpAt!W%fyr>$2U=HB}KS&xZP5E@R%M;GJAC1t?RAg{hnNe#f~ExX0~*i^H0P zgJ_$wbyJmTq?hMbJ4rVJv=_quAr@XWtt)BE6-?;;Y5ti^k*p%!gmi z-{Y?!|IJUnH$V0M59E#8H^Tv`d`)}FJKy%UG?#SpLkyt;pzHX)HxHE51%?2)BT8ya zzv!Gd1$)d*B}aDCHemup8Iy%e!AIkwe$Zh6-mMtNyemg{u+v`FzxBQU%3nRgPo0`; z_Ox{U@Zod+!sWrE)i&iFH8(ie+zcS@C$|K!PacQ>=)^X>Z=oDQ1JxS6q=zI;5aW|e zeX|M{MiFO=rRYdNcQ|Af=fD;iZ2@H7obTYDQ3_`2g{8Uh5=nge$VNATbLIt}{>{;2 z$(T3a<&vArgOT^V{XO}H&-#XZ?Aza+@LT583Bw|mJI*0~zNs=e9A?|JTW})>R}&kx zC`&CAyaeV{Y?~PdF!WkhX4WjA=8ifRaWOR=9l5h7l@HRrk_J912YEa9oTE2^1()iq zlD4;TR(K(@A|shrS=<|svo0|5m>7^&5*#=_cUXF9i}2jc8DT)MF-w3$B z5iz6I2iAP6P+MN8)DBBiFLc25>tW-v$Mme`nG8Q9+~@qEq%A9Eu$k2;871U6@2)xT zd2#nXcQ9X(H~gHd4DCg&g6@fjFMePszjVCx$p7n==JD<01Z*Y8In+8yOCS!S*gtPy z;3KNqiSU@)XI+TDSee=!O6~P)jprC^^a(9poKZ#qv=vH-B$Ey@`=jx# z8wRPYV61^yB#bJ+YRt4+$f3PgY()rCz_FZ$xmB3pz+=0wHfZERD%xicHP?D!T>LA> z_4RzxyFW3%?~A@Izx3@Nm-ay}e9pBux%85jOaz0cDF|OU_-B>d#g`An&rtso^5NGX z=f{8c{rpqRd-H);-(ZjA;mw2jKbLJTDKBRnE2d>g)=o02VKlxXP+X?xUOAlc@pR?j z--iYip|%R1!KF)#>w?K`@@{Cs*~Kv;6KUEeQC1&5c;NpucAmy@dD@y@y}5QSd@}bS zjm`}(6SqFG30&NE;71Rev)v2dv z{>%PZ51z!89b-+~umG1YC2PK!zxu`-yis1|&xD8Y?ftzU)P61Rd-b)v|HH5It^P_s z5A$0-@k{b0zxqq_FwMI4kC0TSByJpD7mDM81GL4);DKj7Cb1soTh!uxxIhpl8(IoK zY3~!7=6mmVhLZH` zZmLdi_9c4v;T;CNKpvGn?CPv#%krlteaRSG!{F$>v`wq9i`k)s1b6fp?x$=7Xr(0w|C~+ZbislX2K%jS zIEg9E#@yrC2hZ})D=+b<_}k2X$3ct>pP*ZKUdWuI7|X~L8uM-%$03h(&jWHV0Yi%R z7Vq`8+J-sXdmXxARxM;S} zZKStcAL>n=!o=`7nINun-(M`_C@QnAOmm zVRQiNhp4^nd$ZTgW^SI*cN_ZplHc3(%H75nl4>nTPTJru{lUZNh-V8|X^S z554v}e+K+u{12r5{?C3OKlQ#3AvYW`;83D@-{kCI!uJ*JKh)nUExM6LDrmWoX!mlYT z-d7-}$cVg%n6o#fECzXToH!gx%xb?o438-;^P1WTskpqfDo?mWyfRxEDsg+o>J)=h*lX8x3 z1CGYd(t>-(D|M2#f?)|3T8k?p&%^;)v=4@+?>)6pOGg0uiWNqz!F38GDr7KtxMNQk z%ZuF&#{Z_n|EF*M@7x$_i$a)zLaiV9000mGNklcgW=*`*z3m6MgDT}neX}Z z{GKoPraaG=LO-i7>t^1y+3dxdw($$cr8mLKx?K1Sz0FU&_x-sgp*;M} z{jin5R8P+s3yxUWagCq14EGncl&n8@_O0O<29f$}1;r*t3x0-iO_8L-w{<=c=NaNmJf9i!TcAc?lJ@gSDa!^1fdCOwrxP{-cL?MW3S%}}|5EEL2G0Af3Aa$SZpO)Sh>tG!! zlT1$Thu+jZpxFnzatRhYfGb!|>}kyf+p{gSG-Y~#x9x4r$|eg?TaLD3nMG}mkx&O( z92acQziUb;}i=|jn7vQNe6Y7S2(hFw!CGDCkf9-hZ3$Ntc zzxdno@%$6Zgu{L$PcF+mU%ZL^qM+R-7iw&g3kvV5WaUl%3JgN~6bTBxNSDZ}^7))t~z1`Ql&s1$oRDlnZy!-v~nP?w4#nf!q7@ zEXyDrobzMHuQCRx8TS~hdnH$6Lc}sKc~?<{_@ok;(CBT!g22>8>}e5q573#UkDdk2 zjx9c1R$>rgCNU@mgJzf;BDm;l86Plvh%dRi8ro=G#d7HOFwDolXK>AMyw;SDVO&gB za@7dLkod~aCGf#H?kvFpMRQPd`XvMVc!eGgR;z8N!z$j z9F?W4jklu>CC!XwoC||{)(VGM?6u<%Fz$S8%CIuw+~trD!jz9dV&YRD7b=DmXHc)9 z+1oe0Wkp05{f$f_d1}G|Z#9r5ZXI=4nDh<>M|%s*tO2zMQHwc2e*wY(Onh#_n2J&t z4)xmdNg+!BjF{I0#Vry;cv$p_ro2Ex8m`ug7!>*lkNJwDE<~JGAD{iStv?!0`NDCr z1MdAdeAYje&;1pjncHcczKHqRxhtAc${}v z8=^KWSDEkz662^SPH6pUE+@|^#( z=Fk3%LQkXcqw48u``f?nJ3rN1;Qa{q#O~W|ctaF1@6I|2-mL+TWo05QHxzluZ#2W_ z0mV&AJvd=tvFa$v>8m#l9H%~^3Gt2uUyL0rofjTG!vn-O>&K7tv+w&*KKS9+_~Duv zK77kJL98{8dFq`}N(Bz(2eXlvpL;pq{CVG)U-r@eldS9G_(h{u8QvIa2MKAZFN*o$ z9BgZZA-?dP>IIf@u$sFurCfj)am@*?SwRx9NddEdL(dHZfYmu4W<#6+CzY89g~8dw%n&+?FmbLG+yo4nyfAQ=Ts6-I4G(C1z6{~T z0_3hpO8jlEwH{(FoECdUMz}P=jhh2Sr`f~0`g*gk1jyow_z3%{+anX-xME!nN~G;bJDcMhK_=cF>Y|N zS6c3$830WQF7O2SSi%5j&ZWTf+$d(@kx${`%_Kc(E`BMrNCDKLtOX!0; zuR1*P+HK2W8-m-X+B^qG7&|^1#FMu1C7JTJdsBV?-fyNhbr1`?!8m|;=FrZd}&#Ed<~;8RWiunniO~DoiUyp zq}`xw(ZkgY!5F2=^wmpaTpu7a8nE6;s*gxgAX-}i#beuq=lA;FfBYZ&&2PtiYUkx? z>-?kM@d^K%554xjAD9Qqo5RhzJA*c|f4G7|k`nHC%cG4C8vPuDXDj|c`_UyeTPAC4 z@sO+bK~*=@!f3U&;4;CRv_-;LDXah8hEL50_$QP1fB04Y$pjpZm`cvr3o;q@FCIbP zKoI;E{|WE-#rfvX{)YJfu`#}VZC(!X$;97z0*&^R98SZhyzf19lI-aJ*Scx zpjuN{i7^rS!K6ac z@__>tYwFJjH%l#B#C~<>`RuTYrl_ zyr^MJEh3n#l-)+VD~kEPAduNw7|O2Dv;`j^SlD6UbH<$m$^=a0X3#_o|GaJ$Ag5uV zX)in$qM3z&aL5U^!Y;&g!k{Bf;)`I5qvSZJ8rC`6&2QnY1I65y1|W0$p<)|27z zLzKf`+??~~-^H^B_i-C=4}*VFc{m?_!(|x`55dH#Rk#W&BiX|(SKV75gP`l=8w9>0 z4<9_pPk-=(dBZ;gUM@U*vYNIe&kPR26cUf4!0<7D{m1{>eBG!0j$C=OU2aO!Ey7Nk z?xn!n)6*+>cjv&C4nN_I&GkdY4;f%;Wsiw(SA5F>!mzcCxmpMCxCjle;xi|*d&0|c zX6k}1irmLZpcHUs5DRtUSj5z$R$7d;0jy+}72z=20r1gGASUsYSJJZU-9}+>jobq~ zOhE zsI$e-L|7M)K4L)yQwoZh6YCNxB`$uTZP6tLXce(Igr>3vLNTV1wD_S%_F<;&mVgZ- zX~BX|(S-)#FthGu;y}9qMv;t64+~q!IDJeBppd5;SVI}!Ec1-D>>GUZhe%)Y4wYvBM9yFvKPgdnSqFQ$v65D#z-PqcK?K@ZeS1!zh1;TA>rfS z{_gy~-~6p$pxz6wiwSW+kc4TT*A4UT+}egs@nd;>iKN+Y2-2Bq3x1v$&<2rv_S=on z@7}F+);9N}FuGm#nmijorp^&+8z$E>X}At>DeBCh{VRXuZvZ{DbKXWzQ};JF&;HAo z+sFL0oyUO(Xm{UoYq&AqM63-StC*tOmYaCwVmY6E_^hF+H8-AxIXBpV?D%PO^ogIB-~P#8%s1RO z<9Chvt8f|~MfOKM*(rOA_(m_B^3ccOL#-ZJM3S~ikTsR4vh>n|MG0eNHsM@zf$nf% zGK!d;8OIyV3t$b!yn|!K*7eL-Q+j(VXKC+M52x9~GU6NF`|JqW1}6Y}>Qjq(#?>vc z>0|RJg}}gSWa>|zT!6cp+R6kmI5p-S>nP#}Vac(MQkcu%;E8MR>$h^@T}OIaP-M}} ziuUe}v~asrqUbdOBP}cZ0GZd~GlP_8<;;^p5|aR{{1_xpq$GHo;rS^H zd~3N6US^c`HoGUrUVv#k46@L0biEkl2ixmPVD}1_E=g;=zAc&{3XBTYq{Pl6@Z=OX zppei~3GA$YPu+D0&7K?A!x}}L{=9}Dv0Q%6n3@$N!F@Ojy9xXPvM4d+`6S^WaM*(! z*YQ0UtWXP>xx&TX8k3o+fH685-!%QHgFzU4cVcY%48yvp;i30jX-Q)0b7ZbZ!Ssl! zg1?gar{pHDJbGLH*T41m=7opPakyjg000mGNklpgtRd2n-Kl1$<-&6Y1bv?B|_PyUUzQUl4|25B#;KgE(53Bzb{=EEF=$2%=R z6bMYe|Lo&}F@dORCYFK21Oy0~mpF>0zQe2M^(YZ`B5zq70w(>kGDFluH4J_G);anr z*LcUNMXq$1OnoIlCayzgM)mBMoF}jS`FvR350#<7ZM*YE(Jy%!Jd6SF^yD0UZ6@mm z;q0T0LH@#!y31RqpcUm(CrvEnRbrhR2U zXVk}j1orK zcWUc*+fR>FNwlYK86@#J2IK}qtD`-49d(4C57 zU)+vlCuao|Kpfikb3c~nIk*`Oe}!I=(92rbCFGt9WNS)d>%*Zcm!7_`f$W8{hjZm~ zvU#Bo^X>elle_qcQ9O z*VK)3w$ndzD3a-c+7e5i)7*Ffwg`FpO7!Evqew9o6thzE2fp*GzxLnw-~Ql#2=LTS zp7N)!_ka6){^h@%a1#<%ZcrHBFm51Eu=g53B{(Ka5wR`f0JY6C4<6v^Z2tKM0tp+$ z0ZkR2h0!klFgL>o_N>}AEsthlKxWRf)HVe|WgDuMwn%a;lqqkT=5{^KSN-}g&#!ye zC$T4AHsDzcCyuh%XEGMw()qVd3AigsFZ7T=7ZB0j8h9p@w8ZVu+(F6HCTOVXpepxM z1m;5lf)%9?22~rCOiZh(Uxuh>Y|A3t&=HcbW7@+)|Q+Yr40M7FmPLNwC62x>nJTAqP_T1sU}QG&ct=PlD_>?5C=stVl6e{ zXj|9Z7Ay%zXTc^eZVpGHM$>{%f)wnd$rc;DUgL&A53^=+2qz)HTsRyLHYF#25m8{U z=fxWhFRNbOla#iZak(UzD~V9M9j}4C`in)N7!RXuJX4DkP-Z(0^p&13uy&t=O=YGg zK2fZc@MO@ins!S<9=*rG-qS;=0_G0p@R$WU-~>`%T!>}Pso{rnhZw|^YcX+`X2v4c zLk6fGJIoDFa9NVn3QFViH5U8WTf=}jTWKNzTx!KW1j=F1ch5mqT8P#`W5IT9igmfU z$ur=;?~A@AkGO7^-ed;nkg$l2FBzjPFj5@^&tFIaZoiz09l?z+byHMWJrivl_Yh=y zh9uz-%LqT<9A1CYa*pCmTaw(RXGBe%1)#!3!N%97;LYu2J$mN(&%u3ar+4-r<>r6E z-fmud$~lUb8C1C0KA1++|0IfvwUa^3zv02P9>rvrv32ZKdIE;v9XDCt z>Z~m66ND-G2*-!eeeMK~Dbf@Y_XMQPvD?OPz{a~b$kc@Mn8LsFQ@<*o_|9LtAMA3Q zAT~|N3krAa(JtUESAg6OVb;lC`ouD0xy=YCoOs)bw3UgVp|uT8;zEz7Yu-TY6$aG3 zc0~>jbFl})wP2HNTh4b|Fw7|B%aa+%o9v7YCn1x-Mv4$_vo%?#{C zW6JnPd~Eg{{GfXO=th(@2BqxBaE8xwOq@w@g>YOZB&dWsqiaZqa`efH zGno7Z&-)@!95K4WH4i?Re1K8$9Ab!H1VRF=q%$K4GhQz!aX?924A9f$B|rjUEa)rI zMOh;ZsI*2m;KH+yMp!O@>Us7ccP z!}1H{D1}L3CKt`{E%&U{3)ctgr08axZaVgwqI62qlCY>T(cpoT zU{4Qjo=N=65^KP+nzmud4DzDr04FT?RV%|?a}Hzc*~|X$$kRLKefYGs|F3`h_y74n z@Tb1=Ywdd;9`D-H^Hyz%Hw#EL9E_DSma%*&R+49UF!nPElZOau;!++H3CQwc2|&OQ zpGMeN1X-Q!j%yol8|Jt!Wmy2~mKB*dE)V=sm6KfaJ3jR*@}5_IaWWK^wbFJ(o_!l^ zi@PwJdCU)<@hyaSQybf|>2Z(?J*^RHfK3is1SUrizn2|fyaATsU?5Ctq(H!Gth7ap z>1}a6uh~i`*zVoJIQyB)VO#**Ip!?n0a%CBZGnqUf7pP@4rcF}`9W_^!hUjyx0A&+ zC$ci;%mW(WpasvDi}&jY|1YlyA)Ix5A(wIZ7sZjcNyp zHR&$$aWdnGEgaiXTNjs!MNOM_|3wnWPG^4(nwZ~E)@X3qg&bx8V+aFX@(`RN0Hm*k zd;(Ch*~nFF_Q^2!3U6V}bB`Uy{8?Yy%~qpH2_nHROJ+3ogt^>?rP?dQ56(*3%|X%E z`FTti)=ndy?)++G+re%wX+e>dt%LTOjphf?lnJBcd7+87IA{<83w6IA)?j`yY3YrF z!GPl&>n@XUE-Y4q27x#;FyNR?=XAwOv zT-wHN!H*PTe+4o*R(T8^VOL zGB;6(R3C0Yyp;z$s23b>w&KHg#?1|xyz^jW#?)uwd0-#r3JjP$Fu*!#8(-sOa|nZB z6SKY&ZQ|-HzmTgcw7sE#+z^j~EvG^=>mi)4UfPY z2?Li)h|k&EN>2*pxzJ%yH7zTg#J5@@nA5vDTojnXD=Cxf5r)cMmihQOG}Qua#F!zj zNCdiwkk~d%1RaGi06VZ9Yr}>ivXCUk$ZdQj(GNpSy@h$jCVBGa!xwyxTA+Dg(0pre z>yyj*2r%aia0^)*MKvc?i9=*!Fliya2jn!9!Af1}3UyS*MHf*aww%^lt}#F%wT%_z z;Z6#n0PCU`ddrnB78dY2PLD)}R}hmFFlSv5p+&>HLa7JHE^RT`nSh@o1~7@R8e6+& zQ+Rf#k4dZrV{$=u6uYGs+P+{y+>>fkldwhL!yAOSeC;7EVi^J2I;KEEPd+9yEvsh+ z=V;OsAg_%Xw2t2m$qi#b^fB$?3`Hh|nrN|LQJaYn39CmeCb`!&x(7*K*N zI`y;2Ukq{aOarA)En~2mbOPpkBWwaAFFgA~*7f#22Mbp0HEG(S8nMP|EHlZbR=sv` z3Pbo$eeb{YpQxW^;T`&Pw%=DD-})xx9&&&pOq7`7J>s^_&GiP?8%Uh{M~V^o01%7# z&l@0#gfK`VB|bpyHFA6;77xK9Fb`ni5xJ1VOjs825MP;KS~SAUlS7*IY|xvq)@{E0 zQ@%96;+6M6qrbHXFWzh__0k~5Jd05pCIB%H7#>|saWs>d6Pz5c%j`>SWBQ#uJmMuE zW{yn*l*VoMF6~4?nAk*^*IfC)wiXV7HR&ajZPMQ7TrQosI01MIpE*E)j`;MHT*#ek zNz-$^aoS6SbBxyd*Tb`eT@%<#YX-XEryzDhshDxs@9b?3XGARs#^l5zq%LrZFhTJx z0xe^@LSSZHi&uJqhZTAickjyu7{@%ipadM88Ynhti(|}I;{#Y@atBg?2F~Jj9j8qUyYHr$TAf^*9dag1;1F%61UFQgV+Q2 z&^`)43D>My8(-rHfX%+x#cn(C7LDMfZCIG-D7xw+iT&cxJ-8e)2Nkm!_=4#*$1z2b z)HaSwn#-kWIS;`W1Uog4;!bgZ4Fe>2XmJ#81^|s_I9!MQ9>a&j_Tbj1CKthLw6Cf; zq&734LIA@kNdsfcr@(N$KVz`b;5d!1q}@8Tj5{pNNm&6K(MnqSQLG;aIFkT;a>1MZ zav$1}1TtzkS`&jyMsin=LSXt3ot_$(dW9ASl!PxE6QMWw)*3Ql5ay78R_GSym1mHh z*he1+W>Y?klAAokz4J@Qdi)rECx5|>5{4QWix{i+YTmrP&K_(4=w7I}1B4ZRP2+5%@XyeU-!^CVJz5oCJA&jSbUY^$O zzv(Of*r&hw__ZJ6{v_p=Gz>KBD}njg000mGNkln z5Dr%EhWmo)x9maMn)6B?_kbB+Po%v^9TO4hMK{iGtn4>CjY7yIec2-HOsbaG{EUyCBl zW}kunVRK{5PTg|3tbVvT#8E$`xFdxR&~MH2D5{?$2<}P zCKvWAk;(3gwX8qye+)V!mpOK*COZjv*Ig(JJH*AtzBIiYXYbCv@+FicC{%3YE9wr4 zi4nOL(%61NXYdB4ztOp*W*78)#! zV*~p(2D0m8^aQX4?aCr^?ZMjS9WT6+dPWMYC{$y^{I8BQLK^wrD1M7}q z0uRHZXC7_Wbsn^XNii)R2_jPzy*&(`8lnTEE!t!rI%{CFb)r268Pp5=VS#Y@d6RwS zFZ;}V`p5r0}`LNPe4dvHShws47{4oYhrN493?ZLq^(6pw?$8A zmieKKW!^%kP%)SZ+~C(GnN{3g!kFWs!-qykH>OY_DXV19!MVaP)^fhX4|Y=`aM49V z(KYY>rAMssx-KkPIZ$;Yst}o)i)i_HzN9s3rws)X;(jQ%`e78#z=X^LqMl)`vRI)h zBrQ0a)BzxhZ?A~gaAOh-Efx?|$zO6ns({(Y;#05%QNUVkxU}G!sVO!R{H9?n$YXgn zd~7MgN=vWo$L@13+3p7lWB`*uY`+c&dyA2pmWU8KXC7tS0}p!q3o9iCrw$qqtS}mu zY#%ifjJo{Zh=Pp&SOsbaGEsR!A>e?Lr;#+5-5!OVE-ol{b)CpXUhP`(c z@8uGxgF!`4_F&MhiVc%mnKi_=u}@|#I~KZwp3zMo)HPJ@GI;=EZm9cYn^;hCf`Smm3fnp(a_Y+zmTg zacF_g|J-?t#SFUDFb|$4zFa)I6-RXRjHkRVdHMObr7fj9x>#{Rrd51QvPi~Yj%`XI zs-euhy?L?EJbdo6$9amE%hTHX@Bd%^<3HxyR;qbyUFQk4Og_A=FL$Z>OSEs6>#C!yY8J^n>B}BTV+2bhU{sh1bc-f1) z^2s0f$@%eE+pH=jdH~7gySTDb~d%4OBo!ScVwmjYpA>6l8z?} z#T*R1C-_WJj1@C7gMNB=9U$WYCs^v`qL3$+8JjKKVlxLOXFkbrPI#YT(dwLtNYaw9 z_9=|FO~Y8TVtXC&nZ+Z#EsdH-viJ!8;El?98l%Z_zSWN5EUsM~Tv$7+c6QxmABsM_ zK_x6KG;`Sdhbtz)Ne{L%f4wlDu>NW4W*X&gQ!esdv=s` zYSPvu1s;_a0mEo(08T4j6W|13G;sssWN=YujpHbb+07OxF5XP8k-6Mt`ZAAw2`_^S zrerD#phR$``o_Kf+ z8Z6GU(qc1b&#`fcaQSuW&EWhaBk8I znZ>3?7}l`gQXc|a;+d}ypD!=iHHg0_m5YGuw^ct)<5}kyPx~T4fDp88>z1Z zI}V=&gBygI-13e3mFHi=UNO9vq+txe<1tTVc^Cr@kCg-h*<%IdjBh>hKp~e$7z1as z!ZV+Dzx`eLZJ+#4I_dMH^E$xnalmO7S*7-Hx}2_l>iuR zN4zPGAlr$`v@w8TQ-0@(vm;O^JU$`juB}nR=Y-G#pfieWdJB$_62=0n81`H@^+;JQ zio%3Ouh`5vFBr{bxd$>Bu~3Ukqsk0R5D$8AK@9{*IHY8XFN%gN0FKCi`f&tcty1l0vxTt@tH=9{{x1fNr;x z1OmuB$7bL!>MUZ>MhXE%^nSAK;Sh!2s;#%uHZ1vt#yW~Ff(j2s0^MuGrPfSbsR2wu zys?Kp^aHfgNT%6AIj>o8QLLFkzR|_+(7;)|#M`=)pb#)`GQ z&c+}IATvrS@)mVk9ee@}dG;|0U<(AlEl~{CF_`ThUZ_xxAedb-!A_6x_uhmZcXH?Pm#@^9lm#huH(nVBEt zNzbW;;D^CIl(#_e(iYKLdB^i_Pc>&<2{9HPPVDUA1F|{5>=+!LlO1H84_P&_y$|cb zX?d`S&4Y~n+%qrcpZc_~$>Yb5kDilub4X_c5;1rdU3Uy-cW1%js*z8QHQ^XjK;}Ag zbj@LoEf2&}q1Q!Dlu&7bB9kxXx!_XBb&L7LV2*FU3qOhoab{Lvl3vAy@E5N7Mf7f1pyb<#L+RUKE!y4E*hCk3DS~@LpQjX9Qij#AQ731u?qJ`kI;&QoGT6|`XvCKgd!L?N~o@=$t znG3P-nmx8&IGvas_J|+7OkMCHZ47VvAZ@ObhpKVJY<&Vu&L9bCCqifi6U13cEe!-N zMVJ8%$q81GFnq53IJ~ageC|K?x%r=d>?h{p|1g++Q;?ZcpwB)md@|~lebnsXd2o#- z82EP`U~{m{5JYa+l;6seP`wnT78URTu=579i!kmt^eBxbknf8bV%I-?-Z5wRFriNgB+EY4{<6;}LPI zIiX;>r(8_-3Q*HF34*Q~nNxD#O=K532Jsp+3=g!Z-?YU~T*uVFTVdE69~vs_0H?JY zF)HFvL*`0(4JvuTzH%L|YMdjs<6v@dC`F`LN!v8X(56>9=C+VefSG`c1;;MNrk2-r zo+sj&(Xe}3h@|hh8GG-Q#!%y+3N`enhUY?#e?re9u%Ibc@cKgsU({&C^xIM#PiHlo z2PXyRLQsPvW!wei4`#URo)iv&i6>ms%&_msB^_$6=uq3>j*(|vF&|kpJ{0vL8S#B<-EH;a55y$ zVO)D?EJtjmyv$k|L(Y?uq%BJrT)(mL@uK4TUTI8|Xcixm+KLy5s?hfSrgE@E!+yfx zEI!aM!XMK(%OX74G30WSTb_?M^A*40EAmm#@vjqvJHe)u2ZnK}2 zMeQ)q;G|$@)RfBoc=q8#_PeFkh0LT4Z2OjGQ8m&w%&>;VnaMbce#gK6&HvU%zxazk z4)Cd;e(pTAhxqRQ#~=C|@B852|BLH*GjluVENXAyI4muCuba zV?0TS;q#f`@q$srL=W#KI^a9v7s%k*?ss>odx(M||u5v%xOg##f#D4j-!6&*%( z(aZ*$#ixWmvG_CM0t3&Y_7=)wgB3pvRCzRuivR!+07*naR8!t?*lfh)h=AlQ`YoEs z%vR=@EJY|q!B(3b_5~rIwKl@_q4t8KWuW+N!^KFj!?>;$1sWc?lBdNp4SGuC+0@*aGh)vMve%NFX2?_QUlIICp znJ9I_Xd35Yv1gNtJs{c?ap{9k(~ZL}7T3;Ps6r!c({7c_%3;qvvN6lRCPEc5c3>C&$HH*#?iDF4Frt1Ud#Po zN!vKuX20;TLk?SVwxc(=YrZuZPugZjJX7y{Ve>c&xohKV#hz&COu-NK5{83Wd~3c9@Ku8&{MH-G*&6zUM@2<~2+C&$W5&%VfCuWu)A#N4vkX0wvOY6w}G=MQ$k z-6QnoKG${o)7L(Cy}5b#uR(n3r|*lW_7H#gpZmeD|2O~KxBa`}&a@4W%Lof+!Uyvn z&eO4t331};zs4`(ndX(!+ynXw|LZNjwgEmT-r+J>1K^Vvgy{1!Y`)t~5IY91Z-2^_m^ z+ASwh2F#5Ma;?OuFjGj`_W-MiQ=T>HV{yZlbxCAWYax;#ucd)t!@-!jf{%1HZ8x@7 zxX81wANFGyDIG2h1?qhH(zZ4}VS=GfX3X`&Y6)6j9(-o@mnbR^26Ae+E&QX{lQrhO zK|S6`v`0?hWu)hQ7wc4hEe@9fupBTl0Is*2%t}#*wxFPI;5SVkKl@633eEU`}q^_#zzwxnnG_ zgwSwOaI$74F3u$dUJ)z3FL4ld9wq_2z1!H-Xo(&?vA}2vkYWC=mT<#Fh+gTqHB#=7Ob0F^eRw$etu%P$3MC z!3yMx(}TOP;|A1b8XeMHOFiT25w6kHTRHQ$PAt)ox)!TvLNUiB2Lz6*90HMj+WO`l zJ|Sje(U>zHRv2q*3fp7pj|sjx{4C&d%OP*=dJ96yIt4NvxfTMv(_=r7_)LWyPn&?u z#G{`6Qp07VEaR-s3s_gEi2%%f1REap3>a=HNSum7l1I#)5Mj@@gE;JAo}W^qVNVa# zAOzeI_aSsDuw@vCIEC9{Q`L>IblV=Bpv%Jb*eZK05}0h*Hf?!#8EM!cNz*E8^zK_e z5pXQK+*GU;M_u5Lg5s4BTPZJqnO?t%P-pIm^iF8bSh%N9nm}a)I5Tv)?o1$@l~9UY zj3gjAhj>^{40B30jql+?R8VQPtg)qqgDKin3cVa^u3G{HIe0pOLJS2?GDb@-W?Qg^ zC7wT+VYC>;_jv-WztT3o2Eh(v^3;;i6dKOBaQ1y#M38QUC2e?}l`8bOCJ7FCxg3$w zKae>s&LP$nBQzC!)LPHI#%CcB{t&>??e_K9F&SSES!;aN@eqXqCuQGDg6r%tP5Eei z`=b}~JOA;ojQ@YDu2*U-DkCAjYTZy~W4mYU4m8(*XrMat$_u-%Xp6~ho@A%L25i+>+Pxnc_Y%^@mLha^v!~Ym9!m+qcG1v za3H>&Ke7GRPyUizAHN|ZEjU)wIPFQn<5?^39lgX@+fWfnG*>vM=KyZC!w}spLNXF# zVZif4k-|6$E?0)UFmT$YMR2Wf?2Ne(z)_$U#)cf(9E+e+5UL9C4T@(V5aadJU)+nf zn7fudV{PFy1UNVAB96DGcz2xISP&YC?*Akc5FUR94ip3@D<)M_1i{Zfdwb$w>}l3n zuPxkz2Oo#aM^q~Q^o{lm8SR=q$Q4ILu{3Gq?=V%8DbGUC*pqut970Q+iAnfip{#9z zW#EY0PvE!q4CutpG1?Mt!c@2wSzv4*2j=XdUkL#AG1Zm~q)1j;P-SgjH6dam+G4v; z5P-ImhsP}9cJ3fE8Dvaq(}cNeP&tX_TCf%-y2Va_0oZq8%{3rK<+;|@2!o1g;g2j3 zCJ&GCg{y!hP{JsN)XYLPI0;pT*f@kL#@?Ad8{9aC7Wy6o9Iyo2Vo`5a7K?z}2Xq1} zQttj$1;^Nwk-ng$X-`S8I4gk=gE;SV?7&Gi4jhWL@R%!et=Lq(q`{v!XBiC!#L9%Q za43&MIxDAR-%zJWl7_Li(q8c`3K@fahD?F>2xbqX#s=n|WGaXr*ZYo+Pn|9R!qekf z51)-@PP)tR2uMd}4>4*iG?9Gr$NtKE%DX=~mz!q-ly->IcZ`b{lghIBNR|gg^0TzUfc?;cxrCZ_G!?c;-~<)yl-8;}7O1V|nmMSP`geA(z|DDv$v#7yH{ab2f6A6PCBwg95qo6LsS7N>R_UBqD=Tq%GBgp!uzt6U0J` ze*yutlZX!nxR&>?3eCB`UZ}`%@ z^x%cu^dYD4mlYz+FuWv6d1J2vI@QD=bDIFP!P=(Xa)JNSb1x>%-RL;AMTA2V!J8WH zVVLyYzhPuuZ+|K;eN=949{o}19})BNw4dUSe#;O2hyUt#{m!q6cP#f~c{jy0xOqMx z+)Zva9AZ4GL@{us;N;yee-sY@k1Bj75hlhmC{+QPT09+X(<-~HZwkdYRZ!R5UT^a? zpYfG>ba`&L(*rfE+0q41+tP1>cNW4~DQ}X3DyT_aN!o1Qd~&Vv7rV{g1euu+O?glo zDzi@rzy?{-wn!YrCO0tQ3{+O6Vw&L0?UBx;pS3?ki6^{SxsdEMWhXxPxqw(;SBNIa z)JU3hTnuD^Y;5;swDEczmI?vusF0Ab6j=#t7}R$fP2uGM~GpYfAaJ_|FBJB=}WBz_5E z3CJN@)S@Gz6nMf0qjVhZJ6d2Ic$^CgU#(2D&u}Jm7?y=wcJV$epqYGm8GRu_JWs^s zg+T=5fT9Zj&Wdeq{W|FA^=Ji;(v3TIni@qX+OAb}0hQx_zj9efw(~`?vBwWy* zkF1oJeMoLgEv7jAjUSEs z5i@=MpX$Rr^YHo4etg@-5B8r7H~%e%@s4sceZV&E%}uf@fVH;LrONpOKG$$^VO{Ybb$G1M=OQGR@#%rnfwd0i%w! zeJ7vwfTPF@qv4^3AEa&eFcBLPGgy=nqjdmjZ3jnDVQ0;HoO?$>x`j(cvJ=3hJJsm2 zL3Y>dQ+a4o#iw`n4UFMn55|?guwP38_NeUKR%Xbs4mN67f*`)rV$6q85BjvhWL86=eCew&9$XYu~f0NM~mj`~9%JMlS~zg!T$ zuZ07tIR*w#$aWwxGqvHcU82r8Ad`NDWWS?{_Zrs3r7m&UWi@u0v0T7Cygec<9Ar+1 zY27&&R55PVv;!q~rDy+m@U#sTanvYwqwO*W`v3qC07*naRK-0MrWM` zQwoJ}A+qjiMS?n<&3+a{`&sOkm3?h7tbM6Os({NK*T%`2$TBl&OI~7iAPjJf%{QL{ zdms(evy5O%Z9CUK z4SiqI_DEY>AFgo8+31S)CLDDS=Q2c+miPwU@32ri00X=1&7NEjN>dZxe5{?_Xp(_m zn4Y7Na;c2PvZ%4e%UNpSC28h)XlAq>-G1-Te&A9wCzOi5Np& z`j;fNj4DII#~`)w^XCHP`J0>k?$7?3ys;jqEdtow4{GT&yo}?_`x$*+w_+=(Jpbsq z@B?YK+i?mojnx_m4d4QDK5ed=ipWaag!!`{{PFMpD}U?X`g)TQ0`JfVYA^=0dlHRxF++@~jyH zryXKG>4JRv|B{zJHlOvL&j`I`F0|o8ND1YT{f5}Swgssli0tGZ?L8iFTig%m3Zayj z`Eg({L5jCoasbkTt?=eoFf1g7G<8z~s5t}LLfOMgFmTM<3I;~VBti`g-rS957;3z1 zzzRo(KZN3$nvG~Dg1t#Il02fmNKK*$gB@hea#Ds7&4?-MG+k_cLPvg+mG^Z`m1zIF&n;Ghrl$SkX z;^2XMYE;6HS{7!Ja9&wdR)N@R3;Xbc4Qngn)9YYKeZf=DJq2&e4!hoog<)NPvm_GI zm}PG(hhc?K8eMc8#8upx%Vr`asWIi_d{;TIyp* z8f??rafa45%sUa3qlQlJ3!(ozFd-x6OaMb);5>H=Qh4Yv7FWtz&3*yRY)fJ0QM<+W z0wO}XTr3D7Gc)FlUYGG)%t0_GcE<#xK#L|eD~p#x3Ap^RXMyCV3NdEbW{yZCq@vcH zFCf*_5fcK!)STL6O#f@qZFa%)JXiArouGk}b#x5atxcj5hec@#p{9KlKN{_W%BMLLVtIKL?-o z!~D6w{wII`|B?6UJc|#5H@5}d^9boI?9P3SaXgU+z2xEiKlo|xp2uiXiGr+n zPT2Sp;ms(rMZCG;tNpWil;8F#{{$4U@FBxKRE@(_T+P|0C4z_xVxq`#-A*Wxwj=4d z`@KVw5At#^1cKwj@KTO-E|NqIFlT&pTU*#B0`>P z6APkpjB#BKf2&Ox*nR+;va*~0QsZJ{VK61rlMrF{V`UQIgo|t?+&A>TFgukTjl#$; zFbX!MF-F5{8)kaMq6OgIVJswrSvfOK4BGT_f^(%z9D)@mlDSV{)84>1OrxX78CU>= zyO#3ORpzMK$->?3Ep#XR5)A zl&vvmE>_O524!#D+v}V8t-toSCHs$6Bi7g682nIY&D2+@3(qf7sBs)nSTXA zkK1J046HkhTH_N$yEZ+n&3og`*MH=5Kk3Wf`OGtq{y2e;nE4O%h2tNUtMKo9+xPv> zfAhQlxj$n+-Zu3-aNA(zp(4PX+vGt-=1g5nOiV-ru+;MSLL*>Io_BV_ zDBy^;1>j?X9@8MEimkEsnFCL08)g#D><^*rcq?8!{iG$nIy%({PttXHE*das6#HUYg%wO zs6nnRsFvb>D+I{l64)q@<02%6*f-ue7hKTI;mZ?l&b+XXF?NeO_|^{&X4J#YY=aAv z`oS7rf)z&7B3YXVvM2o8Yrf_4e^*|8?bWO+{!lm*`U>fq1H%T57Kk*r#{7-(&n~z`r z8{VC}d&@m!^7*z9Bi`JF;bV~fr!j9YPmX^sdwlywKKjLX@jDtnk2Y+gfdAwDxwT-+83L2+&B$ z@Qovv%M-0BHFNBx$pYypN&fbO z;!yZ-4jXIF;SY%Kj@n79onveaY6ygrP|~57MR`o5Tv{S;#hY!rLf@xTU(zLygiE&_Zkkq z1VEW$#;%;xy|jsPF$hwK6}LP$sAJ-#Et~_O-sD*Th{zG!JptiE!q~i|JkNo+QNmtN znghb&%Cu>l850|L+^I2E=Dp?_w%ZiLG<7hTAk?FFkikK6_oNrkKILG-IS=bT%9V35 z4YCy-AN?dp9>ELe>G|l#o#257PM#ct8Qg1$fH3HhmDwYX8TwQyQ>s|(GuKPI znO`_Si!2Lsz32E5$*gl3Jzt(2J3i+lVZ6o4KI^D6wSA7M0u0sF8mGoS>Q+v-Plt4I zUWJj<#c-J1smV!fY^ae>Ct+&57e} z4H2X5q~utGKQ)OhlVYEX{>q0~7*rm7Ve?i&joZM1i_bdFnAxN0hN&Oyv$l2|UZ^{K zO|9ZfU&cq+8_fRPb8zz762)^Gz}pkP7&-d0CsZab0oF{cDIa8-jbnDm_C*l(nfzpW z%M<%7iO9rP81VOx!=&_(0X380AGxxQ5GWpdu*Ok7F%%vfwjv+j`o;@8)NOgIk+T~j zt6XAg!`2Ay=)fIeGj>nujiMwZEi9(B!_Xy(@No^u0S?3w%1=xVMo7&J6A|0lAYJI2 zaG}p39SflhF$dKu&A_2DInxhXa8YNDszZEF*ptS>Gz4vjW+gt_MS>FcQxn^hG>ii^ zx|Z?XLnwGW7KB#p0b}Mm-pep|jg@3lS2{Y`_vLWz6mc>nEs5OVy(i~inX%SNc`ZEc z$P0+gfCbxYpSv;4@jC17N|RFFZ2&krfLHU87B(0w@9lzYPFq+^T)&KBy>*^pat*Wx zptCp!dio~cqmGTSh!MhqzYK2foCTLlL7yPTgs6}jk`va7Si8A7$D$3MmCycVpP66u z!pEe%7jE&V7My)aybed+p`zw~mxu1T2anQCB!hY(NZp_ZFMjqAw)PYB>FxG<`%{+( zUC%!A!l!NKN6z|lK3$&PgMG*E`GLRiulze-_SO3){@imkZSO(MaSMCiOlYD_^D%FG z2P@7o+vWu7lQC4VIGXa1)qnH5x#X6A|NEj({M@|!=q283d{meVZTX*AlgGq!Vl_ON zNHFdsGkW-KbC_H6XFiSgc<7JbTYPM)#^DzuA7tL$X{kXh_8u7uZv{_(+Sk7Qy*o!53+}b4p<1o{yf% zWo2>(>=ke*MWbM{EBJORv}KvHLRow`Ftw$g`x20JT!)3*SY4hX0uXJ2Nug6G1K=d& zHon3*HReqB(I|zM*GCO7>1a!-tf~+mXffkf!I_zOGI={?aJGu0B?7KJZC%F-w`mN|Gq^kq z`m zO?Scop^+^i%Y62px|;>c1dGIDugSGg99|D|0m!dBV0&L<6E{!01e^;#^_PVdd|^## z{_k^q{bzqoUVHs@+)EaD#){|E4~BlrT0B1^vES)m$RG0;@*G9OFpd(L_XoCN?P*OP zj+o}{_3_{Tf?xMduV{ay$w$l=j%OY||6(tVNdN#407*naRGGi&Q@{RW9zTBLhrLOC z;(_q-5*zWZdzaDsp#;U|?J^n9+&s)P^Is}A03%r2<#Y8fN#g72>#Mk79Q5bd8SVEb;i8#$7%AQg<=MquyN3lpq z(>7u5{Ay+o_NO11<5FeUqZV^8$=9*WQ*h26%U;%&q~H?;WZo43kwdIy!HwK7youBj zRY3L}$8IplY3XyC1hekpx5n9vGu$T*5w~fJcB(u6c@3zvvr1gvkv6#Y>|-pG$C5z``Sq)pI6o<5(_ZYVAD+GR`#( zG0nkr4zYW&pD!k`$1XQv_Z@>}??AS->4Ykgxr;|2ar-xnp2qG-aLBVU=rg*%E#pfcz5vLT_x?A3?(=`$SAE(zA7RVoBk4Tf^Vk0E{~x!ETiD}Rher7z`fzUH#&3iBY{o%H z-QHgF&KF*p2cbD$C7_@}^_(`Ti#LM5`M>SKi}{rw{mb*V=U>XY-pU-QWOCYWpFh)S zba1xUL6-fXbKw5`h}fBjrfZ^2SlK%F!-w|X3*0BXd7OU^V~39-TV43PptgIOA*hRe z?rV=q!0oRXZG*}lCf%P&YxChy$Mdw>uzlA-zmJ144e$MM%u%s{S;NI|^$_xcLkf`uUX6W3>JFUSZJ)a zILTHk33taD3cZGLFvgPsH2E}u*4qr7i66OhzR60gFTCjgWM>8$%pJ!Vj71YJ7G*gb z{zCFZvh~fA(;8SAb?!-EN|?BCm{ekAN|!g9Ee7K3VAeeam}9Q{x=Z9<#${7AY69ja zN8vCgc(*gaG0%j%J?X<<`N3E6d(ebvLe?NtAJB?tiOxX@pMdPsW`9jmK;bySRHw{d zE{dQD>VtQ>r#57cErCJ*uRM4xulHd%t>&KC`S&a$Md}Ma1Rb! zO#^%PjhnOPc_+H(+RDhb?toZ#Y63B{jvjiU=1R`<`nehhaO3OFp)5pP(V_{vQi~AQ zVD>o(z%k9LWQ~c9K~fP(jr)4;U-j-!%rAZA<8yQK%zT}Q{m~o@h^*L-oK<86msu|af6y0bE$2q5GFZXTrl0n48lv6&~0&kiet9C=e!7ny#7VIe9( z-d1-`JM6Su&z9C1mu1gHJ>p_L^OKD?lU{p9)JVgtVH67-E8#xsO@eg>q%G{(JVeWa zuP)nlwr@Cw0b>slIBsH*miGAKQf$g2er&| zb9E3_BlJ>q{spT>jEU#4jO)wrvv6hUqsnd=dmU z0#8!#782S*k``W)^2%-7G2JPFQRLj)

    |Cx0!P1Ts2NUI`HXx+hYJ8$%oo8VZ=T( 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

    )|$}6-YN!t;u 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 z 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 F{|BJQ#Hj!P literal 0 HcmV?d00001 diff --git a/docs/operations.md b/docs/operations.md index 20c4774..0c6f772 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -396,8 +396,6 @@ curl -fsSL /deploy/uninstall.sh | sudo bash -s -- --yes # keep the curl -fsSL /deploy/uninstall.sh | sudo bash -s -- --purge # remove the data too ``` -With a private repository, fetch it the way the README fetches `bootstrap.sh`. - Both modes remove the `felis-*` systemd units and `cloudflared-felis.service`, the Velocity user, `/opt/felis`, `/usr/local/bin/felis`, the release assets an interrupted install left in `/var/lib/felis/artifacts`, the installer's cloudflared binary (unless diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 9d42f3f..9be9adc 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -2534,9 +2534,6 @@ an unpublished tag stops the install before anything changes. [SH-TESTED] `curl -fsSL | grep -c FELIS_RELEASE` prints `0` for one of those; run it with `FELIS_REF=v1.2.3` instead, which builds that tag from source (slower, and it needs the build resources of §15c). -- While the repository is private, read the installer through the README's - token'd form with `?ref=v1.2.3` after `contents/deploy/bootstrap.sh`, and run - it as `sudo -E FELIS_RELEASE=v1.2.3 bash`. ### Whole-host disaster recovery: what comes back, and from where diff --git a/internal/updater/doc.go b/internal/updater/doc.go index bb2658e..675fc55 100644 --- a/internal/updater/doc.go +++ b/internal/updater/doc.go @@ -26,10 +26,9 @@ // verified 2026-07-05) so Felis's UA is load-bearing there; PaperMC does NOT // enforce (a bare request got HTTP 200 on 2026-07-04) so its UA is only etiquette. // Also: felis-api's topology coord is now the real repository slug, not a placeholder, -// so that component resolves at runtime like the others — but only with a credential. -// The repo is private, so an unauthenticated poll gets GitHub's 404-for-hidden-repo and -// degrades to "latest unknown"; FELIS_GITHUB_TOKEN is what lights it up. k3s and -// cloudflared are public and need none. +// so that component resolves at runtime like the others, unauthenticated. A private +// fork answers an unauthenticated poll with GitHub's 404-for-hidden-repo and degrades +// to "latest unknown"; FELIS_GITHUB_TOKEN is what lights it up there. // // - ALSO BUILT + UNIT-VERIFIED: the VersionGatherer's extraction core and dispatch. // Three pure extractors turn raw system text into a Version — a `--version` banner diff --git a/internal/updater/github.go b/internal/updater/github.go index 5cd8f2c..2ddfd94 100644 --- a/internal/updater/github.go +++ b/internal/updater/github.go @@ -40,9 +40,9 @@ type github struct { baseURL string // e.g. "https://api.github.com" userAgent string // MUST be non-empty — GitHub 403s a UA-less request // token is an optional credential. Empty means unauthenticated, which is the right - // posture for the public repos Felis tracks (k3s, cloudflared) and is what the - // 60-req/h note above is about. It is load-bearing only for felis-api's own repo - // while that repo is private, where its absence does not look like an auth failure: + // posture for the public repos Felis tracks (felis-api, k3s, cloudflared) and is what + // the 60-req/h note above is about. It is load-bearing only when felis-api is built + // from a private fork, where its absence does not look like an auth failure: // GitHub answers 404 — not 401 or 403 — for a private repo the caller cannot see, so // "no token" is indistinguishable from "no release published yet" by status alone. // latestStable says both in the error rather than making an operator guess. diff --git a/plugins/README.md b/plugins/README.md index 60a8288..59ca1af 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -282,7 +282,7 @@ before the proxy Connects them. > no real game client has joined through the stack, so §27 scenario 10 stays > **FAIL (live-unverified)** until such a join is exercised. The client-independent > faces (proxy edge, subdomain MOTD, login boundary, backend registration) are -> exercised on a live deployment — see `AUDIT-2026-09-22.md`. +> exercised on a live deployment. ## Building