# Cloud Relay 部署与生命周期操作 状态:代码路径可本地验证;真实区域、mTLS、共享备份存储和告警仍须部署演练。本文不能作为生产验收记录。 ## 构建边界 `relay/go.mod` 必须精确依赖已发布的 `github.com/klarkxy/nekonest/relaycore vX.Y.Z`,不得提交指向相邻开源仓库的 `replace`。发布 Relay Core 后,从该精确 tag 构建 Cloud Relay,并把 NekoNest commit、Relay Core tag、Cloud commit 和镜像摘要记录为同一发布证据。 ## 必需配置 | 环境变量 | 含义 | |---|---| | `NEKONEST_RELAY_DATA_ROOT` | 每租户 Engine 的 SQLite 与附件根目录 | | `NEKONEST_RELAY_BACKUP_ROOT` | 与数据根分离的加密备份命名空间 | | `NEKONEST_RELAY_NODE_ID` | D1 中登记的 `node_...` 身份 | | `NEKONEST_RELAY_CONTROL_PLANE_URL` | 内部控制面 origin | | `NEKONEST_RELAY_MTLS_CERT_FILE` / `NEKONEST_RELAY_MTLS_KEY_FILE` | 该节点独立的 mTLS 客户端身份 | | `NEKONEST_RELAY_CONTROL_PLANE_CA_FILE` | 控制面内部 CA | | `NEKONEST_RELAY_INTERNAL_CA_FILE` | Relay 节点间内部 CA | | `NEKONEST_RELAY_INTERNAL_ENDPOINTS` | `internal_endpoint_ref → exact HTTPS origin` JSON allowlist | | `NEKONEST_RELAY_PWA_ORIGINS` | 精确 Cloud PWA origin 列表 | | `NEKONEST_RELAY_ROUTE_SECRET` | opaque route hint 的 AES-GCM 密钥材料 | | `NEKONEST_RELAY_SOURCE_HASH_SECRET` | 注册来源限速摘要的用途隔离密钥 | | `NEKONEST_RELAY_FORWARD_SECRET` | 节点间 mTLS 通道内、绑定 method/path/节点/30 秒窗口的附加断言密钥 | | `NEKONEST_RELAY_HANDOFF_SECRET` | handoff 重试时确定性派生 phone ID/token/route handle 的独立 HMAC 密钥;同一 Relay 池必须一致、不得与其他密钥复用,轮换前须停止签发并等待至少 60 秒让旧 ticket 失效 | | `NEKONEST_RELAY_SNAPSHOT_KEYS` | 当前与轮换重叠期内的 Ed25519 公钥 JSON | | `NEKONEST_RELAY_TRUSTED_PROXY_CIDRS` | 可选,唯一可提供公网来源地址的代理网段 | | `NEKONEST_RELAY_MAX_TENANTS` | 本进程同时打开的 Engine 上限 | | `NEKONEST_VAPID_PUBLIC_KEY` / `NEKONEST_VAPID_PRIVATE_KEY` / `NEKONEST_VAPID_SUBJECT` | Cloud Web Push;缺少时 Push 明确禁用 | 三个 secret 至少是 32 个随机字节的 base64url。证书、私钥、VAPID 私钥和 secret 不进入 D1、日志或仓库。 ## 稳定入口与内部转发 客户端始终连接稳定 Connect origin。入口节点用设备凭据摘要、手机 route handle + token 摘要、handoff ticket,或已认证 opaque route hint 向控制面实时解析 placement。目标是本节点时直接进入唯一 Engine;目标在其他节点时,按控制面返回的 opaque endpoint ref 从本地 allowlist 选择 HTTPS/WSS 地址,通过 TLS 1.3 mTLS 通道转发原始请求。客户端不收到重定向或节点 URL。 内部终止层必须验证节点证书并剥离外部 `X-Neko-Relay-*` 与 `X-Neko-mTLS-*` 头。应用层附加 HMAC 不是 mTLS 的替代品。未知 endpoint ref、重定向、过期/篡改断言和任意 URL 均 fail closed。 ## 备份与区域迁移 状态固定为 `active → quiescing → copying → switching → draining → active`: 1. 源节点关闭精确 generation 的 Engine,做 WAL checkpoint、SQLite integrity check,并生成逐文件 SHA-256 manifest。 2. 目标节点只恢复控制面给出的 opaque backup ref,验证 manifest、全部文件和 SQLite,再原子安装租户目录。 3. 控制面一次性切换 node 与 generation。切换前失败时源节点保持权威;切换后 `draining` 已由目标节点独占写入,后续失败也不得回切到可能落后的源数据。 4. 五分钟稳定窗口后 placement 回到 `active`。任何阶段都没有两个可写 Engine。 当前备份引用实现要求源、目标看到同一个经过认证且加密的备份命名空间,例如受限对象存储挂载或同语义的备份卷;它不是客户端可提交的路径。若区域之间没有该传输层,迁移必须保持阻止,不能假装本地测试等于跨区复制。 ## 注销与永久逻辑 Purge 管理员只能对用户尚未处理的注销申请输入精确确认词并启动 Purge。控制面先暂停授权、推进 revision、把 placement 置为 `deleting`,因此新连接立即失败;Relay 默认每 5 秒检查 delta,并把 delta 请求与完整快照刷新各限制为 3 秒,健康控制面下现有连接在 15 秒目标内关闭。Relay 随 heartbeat 接收任务,关闭精确 generation,拒绝符号链接/特殊文件,删除实时 SQLite、附件和该租户全部备份。节点回传摘要后,控制面才清除活动设备凭据、手机 principal、route handle、handoff ticket 和注册重放密文,并推进 generation 与 tombstone 状态;所有关键状态必须各更新一行,否则返回不确定状态而不能宣称完成。 失败任务保持访问暂停并进入 `deletion_failed`,只能由管理员再次明确确认后重试。这里的“永久”是应用层不可恢复逻辑删除;SSD、快照、对象存储版本和法定保留的物理生命周期仍由基础设施与最终政策验收。 ## 验证与发布门禁 ```powershell cd relay go test -count=1 ./... go vet ./... go test -race ./... ``` 本地验证还必须运行 Cloud Web 的类型检查、lint、build 与全部测试。公开服务前另需真实两区域演练:mTLS 头剥离、入口转发、迁移与切换后写入、节点重启、备份篡改、Purge 重试、Push、15 秒撤销、5 分钟快照过期、资源泄漏和恢复。缺少 C 编译器时 Windows 无法完成 `-race`,必须由 Linux CI 补证。