From 417f378d682e553f4b9ca418288de40968bf325c Mon Sep 17 00:00:00 2001 From: anonsaber Date: Tue, 29 Sep 2026 11:24:13 +0800 Subject: [PATCH] =?UTF-8?q?build+docs:=20=E9=95=9C=E5=83=8F=E5=86=85?= =?UTF-8?q?=E7=BD=AE=E5=8F=AA=E8=AF=BB=E8=AF=8A=E6=96=AD=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=EF=BC=8C=E6=B8=85=E7=90=86=20Dockerfile=20=E7=9A=84=E6=AD=BB?= =?UTF-8?q?=20ENV=20=E4=B8=8E=E6=96=87=E6=A1=A3=E8=BF=87=E6=9C=9F=E8=A1=A8?= =?UTF-8?q?=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 生产镜像此前只有 ca-certificates + tini,容器 rootfs 又只读,运维在容器内既 装不了 apt 也没有 curl/ps,排查只能靠宿主机的 /proc。把只读诊断工具装进 runtime 阶段,实测体积 +7.7 MB: curl procps(ps/pgrep/free) iproute2(ss/ip) jq bind9-dnsutils(dig) netcat-openbsd(nc) 全部是只读查询能力,不引入包管理之外的写能力,也不新增 shell 解释器。 顺手修掉四处已被事实推翻的表述: 1. Dockerfile 的 ENV RUN_MODE=webhook 是死变量——该标识符随 Config::from_env() 删除后代码零读取,文档 5 处都记着已删除,镜像却还在设。删掉并留注释防回退。 2. deployment.md 说「runtime 只装了 ca-certificates tini」「镜像内没有 curl/wget」, 两处都是健康检查建议的承重理由,随内置 curl 一起更新为「技术上可行但不建议」。 3. deployment.md 说非 root 用户是 uid 1000,实际 useradd --system 给 uid 999, 已实测修正。 4. retired.md 里 compose healthcheck 示例的删除理由把「镜像无 curl」当主因, 真正的承重原因是 src/main.rs 没有 CLI 子命令解析,已改写并标注 curl 已内置。 同时补齐 hoststack.yaml 缺失的 install 段(cargo fetch --locked),并在 start 段 写清临时开 --debug 的确切改法与「必须在容器内探、别走公网域名」的约束。 验证: - 包名与安装步骤在同一 debian:bookworm-slim 基础镜像内实测通过 - useradd 实测输出 uid=999,与设计一致 - docker build --check 无告警 - GATE-DOCS PASS 6/6(SECTION-REF 217 / 0,PATH AUDIT 364 / 0) 注:install: { command: ... } 的键名是按现有 build:/start: 的同构推断的, 若 HostStack 拒绝该键,删掉这两行即可,不影响 build/start。 --- Dockerfile | 11 ++++++++--- docs/deployment.md | 29 +++++++++++++++++++---------- docs/design.md | 2 +- docs/retired.md | 2 +- hoststack.yaml | 22 ++++++++++++++++++++++ 5 files changed, 51 insertions(+), 15 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7c6241a..79b637a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,13 +8,18 @@ COPY web ./web RUN cargo build --release --locked FROM debian:bookworm-slim AS runtime +# 诊断工具有意内置:容器 rootfs 只读,运行期无法 apt 安装,只能在构建期装入。 +# 全部为只读查询能力(本机探活 / 进程 / socket / DNS / JSON 解析), +# 不引入包管理之外的写能力,也不增加 shell 解释器。 +# 注意:不要在此加入 RUN_MODE —— 该标识符已随 Config::from_env() 删除,代码零读取。 RUN apt-get update \ - && apt-get install --no-install-recommends -y ca-certificates tini \ + && apt-get install --no-install-recommends -y \ + ca-certificates tini \ + curl procps iproute2 jq bind9-dnsutils netcat-openbsd \ && rm -rf /var/lib/apt/lists/* \ && useradd --system --create-home --home-dir /nonroot --shell /usr/sbin/nologin messageweave COPY --from=builder /build/target/release/message-weave /usr/local/bin/message-weave USER messageweave -ENV PORT=8080 \ - RUN_MODE=webhook +ENV PORT=8080 EXPOSE 8080 ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/message-weave"] diff --git a/docs/deployment.md b/docs/deployment.md index 9ae73ed..f25abe7 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -124,9 +124,12 @@ ### 5.1 HostStack 原生 Rust 部署 -HostStack 使用仓库根目录 `hoststack.yaml`:Rust runtime 执行 -`cargo build --release --locked`,服务命令为 `./target/release/message-weave`,监听单个 -HTTP 端口并以 `/healthz` 做 interval 30 秒、timeout 5 秒的健康检查。`REDIS_URL` 与 +HostStack 使用仓库根目录 `hoststack.yaml`:Rust runtime 先执行安装命令 +`cargo fetch --locked`,再执行构建命令 `cargo build --release --locked`,服务命令为 +`./target/release/message-weave`,监听单个 +HTTP 端口并以 `/healthz` 做 interval 30 秒、timeout 5 秒的健康检查。服务命令里**不带** +`--debug`——远程联调开关只应临时加上(改法见 `hoststack.yaml` 的注释,双因子见 §2.1)。 +`REDIS_URL` 与 `CONFIG_ENCRYPTION_KEY` 必须配置为 HostStack Secret;不得将密钥值写入 YAML、镜像、日志 或代码仓库。现有 Dockerfile 部署方式仍受支持。 @@ -364,9 +367,11 @@ spec: > **本仓库没有 `docker-compose.yml`。**此前本节给过一段 compose 片段,其中的 `healthcheck` 写成 > `message-weave health --addr 127.0.0.1:8080` —— 这个子命令不存在(`src/main.rs` 无任何 CLI -> 参数解析,二进制只起 axum 服务),而且 runtime 阶段是 `debian:bookworm-slim`,只装了 -> `ca-certificates tini`,镜像里没有 curl/wget。照抄那段示例必然得到两个结果之一:healthcheck -> 永久失败,或容器被判定 healthy 却什么都不做。示例已删除,改为下面的事实约定。 +> 参数解析,二进制只起 axum 服务)。照抄那段示例必然得到:healthcheck 永久失败,或容器被判定 +> healthy 却什么都不做。示例已删除,改为下面的事实约定。 +> +> 镜像现已内置 `curl`,因此用 `HEALTHCHECK` 探活在技术上可行;但本节仍建议由平台 ingress/探针 +> 探活——`/ready` 是唯一同时校验 redis/jmap/telegram 的信号,`HEALTHCHECK` 只能证明进程活着。 **运行** @@ -377,8 +382,11 @@ docker run --env-file .env -p 8080:8080 messageweave:latest ``` - 单端口 8080;平台 ingress/反代映射 HTTPS → 8080(`C-HTTPS-INBOUND`)。 -- runtime 阶段为 `debian:bookworm-slim`,非 root(uid 1000);无本地 volume、无数据库引擎 - (`C-NO-LOCAL-WRITE` / `C-NO-DB`)。 +- runtime 阶段为 `debian:bookworm-slim`,非 root(`useradd --system`,实测 uid 999,非 1000); + 无本地 volume、无数据库引擎(`C-NO-LOCAL-WRITE` / `C-NO-DB`)。 +- 镜像内置只读诊断工具:`curl`、`procps`(`ps`/`pgrep`/`free`)、`iproute2`(`ss`/`ip`)、`jq`、 + `bind9-dnsutils`(`dig`)、`netcat-openbsd`(`nc`)。容器 rootfs 只读,这些工具**只能在构建期装入**, + 运行期无法 `apt` 安装——这也是它们写进 Dockerfile 而不是留给运维现场装的原因。 - Redis 由外部已认证实例提供,不与此服务同容器运行(`C-REDIS-EXTERNAL`)。 **就绪探测:用 `/ready`,不要用 `/healthz`** @@ -389,8 +397,9 @@ docker run --env-file .env -p 8080:8080 messageweave:latest | `GET /ready` | 配置、Redis 或上游探针失败时 503 | 就绪探测、负载均衡摘除、Uptime Kuma | | `GET /api/status` | 缺必需环境变量时 503 + `{"status":"configuration-setup","missing":[...]}` | 排查"起来了但没干活" | -> **由平台 ingress 探测 `/ready`。**镜像内没有 curl/wget、也没有 CLI 子命令,不要依赖容器内 -> healthcheck。 +> **由平台 ingress 探测 `/ready`。**镜像现已内置 `curl`(见 Dockerfile `runtime` 阶段), +> 所以容器内写 `HEALTHCHECK` 在技术上可行;但 `/healthz` 只能证明进程活着, +> `/ready` 才同时校验 Redis 与上游,因此仍以平台探针为准。 **缺必需环境变量不会崩溃,但也不会干活。**缺 `REDIS_URL` 或 `CONFIG_ENCRYPTION_KEY` 时,进程 降级为只读配置路由(内存态、空 token、`NoopWorker`),容器继续应答请求但不做任何业务。排查这类 diff --git a/docs/design.md b/docs/design.md index 5477631..12d19b0 100644 --- a/docs/design.md +++ b/docs/design.md @@ -304,7 +304,7 @@ message-weave/ ├── Cargo.toml # jmap-client 0.4.2 / redis 0.27 / reqwest 0.13;不含 teloxide ├── .env.example # 仅 2 个必填项的占位样例(REDIS_URL / CONFIG_ENCRYPTION_KEY),不含业务配置 ├── .gitignore -├── Dockerfile # debian:bookworm-slim + ca-certificates + tini(C-DEBIAN-SLIM) +├── Dockerfile # debian:bookworm-slim + tini + 只读诊断工具(C-DEBIAN-SLIM) ├── hoststack.yaml # 网关编排清单,非应用代码 ├── src/ │ ├── main.rs # tokio main:读 PORT/REDIS_URL/CONFIG_ENCRYPTION_KEY 后启动 webhook HTTP 入口(单入口,无 CLI 子命令) diff --git a/docs/retired.md b/docs/retired.md index 0a5c8b1..62552ca 100644 --- a/docs/retired.md +++ b/docs/retired.md @@ -23,7 +23,7 @@ | 多步对话 FSM(`Idle` / `AwaitClarify` / `AwaitConfirm` / `Analyzing` / `AwaitFallback`) | 目标设计,未落地 | 当前 AI 授权只需一个布尔+过期时间,多步态属过度设计 | Redis TTL 授权态,键与 TTL 见 `docs/reference.md` 的 AI 授权态一节 | | 把领域 / 渠道 / 通知 / util 各层拆成独立目录(子模块拆分方案) | 目标形态目录,未落地 | 代码量未到需要拆分的规模 | 实际结构见 `docs/design.md` 的工程结构一节:领域层是 `src/domain.rs` + `src/domain/jmap/`(仅 `client.rs`);LLM 在 `src/ai.rs`;通知在 `src/notify.rs`;加密与工具逻辑内联在 `src/state.rs` / `src/config.rs`,没有独立的 util 层,也没有集成测试目录 | | teloxide 风格的出站消息派发集成测试 | 目标形态测试,未落地 | 依附 teloxide 测试模式 | `src/channel.rs` 的单元测试(`#[test]`) | -| docker-compose `healthcheck` 示例(`message-weave health --addr ...`) | 已删除的示例 | 运行镜像 `debian:bookworm-slim` 内无 `curl`/`wget`,且 `src/main.rs` 无 CLI 子命令解析,示例照抄必失败 | 由平台 ingress 探测 `/ready`;说明见 `docs/deployment.md` 的就绪探测一节 | +| docker-compose `healthcheck` 示例(`message-weave health --addr ...`) | 已删除的示例 | `src/main.rs` 无 CLI 子命令解析,该子命令不存在,照抄必失败(早期还叠加了镜像内无 `curl` 的问题,现已内置 `curl`) | 由平台 ingress 探测 `/ready`;说明见 `docs/deployment.md` 的就绪探测一节 | | SPA 管理凭据 = `REDIS_URL` 的 Redis ACL 密码 | 未采用路线 | 混淆基础设施凭据与 UI 管理密码;Redis 无 ACL 密码(TLS-only 托管 Redis)时 `bootstrap_token` 的 `.is_empty()` 守卫让 SPA 永久 401 | 改用 `CONFIG_ENCRYPTION_KEY`(启动必填的 32 字节高熵 hex,常数时间比较) | diff --git a/hoststack.yaml b/hoststack.yaml index a62046f..2460244 100644 --- a/hoststack.yaml +++ b/hoststack.yaml @@ -1,15 +1,37 @@ # HostStack native Rust deployment for MessageWeave. +# # Set REDIS_URL and CONFIG_ENCRYPTION_KEY as encrypted HostStack dashboard # secrets; never put their values in this repository. +# +# Business config (JMAP URL + credentials, Telegram bot token, worker token, +# chat allowlist, LLM settings) is NOT read from environment variables at all. +# It is written to Redis via /api/bootstrap or admin PUT on /api/config. +# +# Container rootfs is read-only, so diagnostic tools must be installed at image +# build time (see the `runtime` stage of Dockerfile): curl, procps (ps/pgrep), +# iproute2 (ss/ip), jq, bind9-dnsutils (dig), netcat-openbsd (nc). services: web: type: web_service runtime: rust + install: + command: cargo fetch --locked build: command: cargo build --release --locked start: + # Binary is named after the Cargo package: `message-weave`, not `app`. + # Do NOT add --debug here for production. command: ./target/release/message-weave + # Temporary diagnosis only: change the line above to + # command: ./target/release/message-weave --debug + # and set the DEBUG_TOKEN secret. Then probe the debug surface from + # inside the container, never through the public domain: + # curl -s -i -H "Authorization: Bearer " http://127.0.0.1:8080/debug/config + # Debug routes are gated by BOTH factors (see docs/deployment.md §2.1). healthCheck: + # /healthz only, never /ready. /ready also probes redis, the JMAP session + # and Telegram getMe, so a transient upstream blip would restart the + # container. See docs/deployment.md §2.1. path: /healthz interval: 30 timeout: 5