diff --git a/Dockerfile b/Dockerfile index 0aeb54e..e085b02 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,15 @@ +# ============================================================================ +# 仅本地开发 / 本地容器调试用。生产不执行本文件。 +# +# 线上部署走仓库根目录 hoststack.yaml + runtime: rust,由 HostStack 自己的 +# agent 在 rust:slim-trixie 里构建、拷进 debian:trixie-slim 的 runner 容器运行 +# ——完全不经过本 Dockerfile(连 Debian 版本都不同:bookworm vs trixie)。 +# 因此本文件里的诊断工具、ENTRYPOINT tini、EXPOSE 8080 在线上都不生效。 +# 判定当前跑的是哪条路径:容器内看 /proc/1/cmdline 与 /etc/os-release。 +# 生产启动命令的真源是 hoststack.yaml 的 start.command,见 docs/deployment.md +# §5.1;本文件只服务于 `docker run` / 本地起一个容器来验证行为。 +# ============================================================================ +# # C-DEBIAN-SLIM: Debian builder and runtime; no Alpine/musl dependencies. FROM rust:1-slim-bookworm AS builder WORKDIR /build diff --git a/README.md b/README.md index ed3a785..e58d293 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,12 @@ State is an architectural boundary here, not a preference. Requires a reachable Redis 7 instance and a JMAP session plus a Telegram bot token. +**This is the local/dev path.** The repo's `Dockerfile` is for local `docker run` only -- +production does not execute it. Live deploys run from `hoststack.yaml` with +`runtime: rust`, which HostStack builds in its own `rust:slim-trixie` image and runs in its +own `debian:trixie-slim` runner, so the Dockerfile's diagnostic tools, `ENTRYPOINT` and +`EXPOSE` do not apply there. See `docs/deployment.md` §3 and §5.1 for the boundary. + ```sh cp .env.example .env # fill REDIS_URL and CONFIG_ENCRYPTION_KEY docker build -t messageweave:latest . diff --git a/docs/deployment.md b/docs/deployment.md index aeaca77..f11ce40 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -96,6 +96,14 @@ ## 3. Rust 多阶段构建与运行 +> **边界:本章与仓库根目录的 `Dockerfile` 是「仅本地开发 / 本地容器调试」路径, +> 生产不执行它。** 线上走 §5.1 的 `hoststack.yaml` + `runtime: rust`,由 HostStack 自带 +> agent 在 `rust:slim-trixie` 构建、拷进 `debian:trixie-slim` runner 运行,完全不经过 +> 本 Dockerfile(连 Debian 版本都不同)。因此本章的 `ENTRYPOINT`(tini)、 +> `EXPOSE 8080`、内置诊断工具在生产容器中一律不生效。生产启动命令的真源是 +> `hoststack.yaml` 的 `start.command`。本地用 `docker build` + `docker run` 验证行为时 +> 才走本章。 + ### 3.1 构建阶段 (保留既有 rust:bookworm 构建 + 缓存分层约定;产物为单个静态编译二进制 `message-weave`) @@ -136,14 +144,31 @@ HostStack 使用仓库根目录 `hoststack.yaml`:`runtime: rust` 的 agent 在 或代码仓库。**本仓库的 Dockerfile 不由该路径执行**——runner 镜像是 HostStack 自带的 `debian:trixie-slim`,不是本文件的 `debian:bookworm-slim`,见 §3 与 §8.1 的说明。 -**该 YAML 是运行时配置的真源,且不能删除**:YAML 声明的字段**覆盖**控制台已存的同名 -配置,YAML 省略的字段**回落到运行时框架默认值**——Rust 的默认值是 `cargo fetch` / -`cargo build --release` / `./target/release/app`。最后那个默认值由 HostStack 自带、 -与本仓库包名无关:`Cargo.toml` 只声明一个 `[[bin]]`(名字 `message-weave`, -路径 `src/main.rs`),仓库里既没有 `app` 这个二进制目标也没有对应源文件。删掉本文件后 -Start Command 回落为 `./target/release/app`,进程在绑定端口前就以 `not found` 退出。 -线上实测印证了此行为:控制台 UI 显示的是默认值 `./target/release/app`,而实际 PID 1 是 -`./target/release/message-weave`——说明在生效的是 YAML,不是控制台。 +**配置优先级(三层,从 HostStack agent 源码实测确认)**:部署时 agent 先把控制台 +services 表里已存的值填进 payload,再用 `hoststack.yaml` 声明的值覆盖,最后才用运行时 +框架默认值补缺。源码判定条件是 `if (svcConfig.install?.command)` 才覆盖,且没有 +`&& !payload.x` 之类的短路——**YAML 声明了就以 YAML 为准、无条件覆盖控制台值;YAML +省略的字段则沿用控制台已存的那个值**。框架默认值只有在前两者都为空时才填,而 agent 的 +注释明确写着 *"trigger-payload always populates payload.x from the DB"*,所以对已建好的 +服务这条默认值分支实际上是**死代码**。这也意味着删掉本文件**不会**退回安全的默认值—— +它直接采用控制台现存的那个值。 + +**因此目前不能删**。控制台里现存的 Start Command 是 `./target/release/app`, +这是 HostStack 为 Rust runtime 自带的默认值、与本仓库包名无关:`Cargo.toml` 只声明 +一个 `[[bin]]`(名字 `message-weave`,路径 `src/main.rs`),仓库里既没有 `app` 这个 +二进制目标也没有对应源文件。直接删掉 `hoststack.yaml` 后,下一次部署会以 +`./target/release/app` 启动,进程在绑定端口前就以 `not found` 退出。 +线上实测印证:控制台 UI 显示 `./target/release/app`,而实际 PID 1 是 +`./target/release/message-weave`——说明现在生效的是 YAML,不是控制台。 + +> **决策记录**:最初倾向是"从控制台 UI 管理更方便,把 `hoststack.yaml` 拿掉"。 +> 但按上面的优先级,**这个前提不成立**——直接删 YAML 服务起不来。**当前决定:保留 +> `hoststack.yaml`。** 保留的代价是双写(在控制台改命令不会生效,必须改 YAML); +> 保留的理由是部署命令进入 git 历史、可 review 可回滚,而控制台值散在平台侧、改动无痕迹。 +> **若日后确要移除**,必须**先在控制台把三条命令改对**(install = `cargo fetch --locked`、 +> build = `cargo build --release --locked`、start = `./target/release/message-weave`), +> 再删本文件并重新部署一次验证;同时把本段连同该前提移到 `docs/retired.md` 并记下 +> 移除 commit。**不要**只删文件就以为控制台值会自动变对。 **校验是宽松的,不是拒绝式的**:agent 的部署路径不应用 zod schema 校验, `install` / `build` / `start` 等都是可选对象,**未知键会被静默丢弃而不是报错**, diff --git a/docs/design.md b/docs/design.md index 12d19b0..36906a5 100644 --- a/docs/design.md +++ b/docs/design.md @@ -304,8 +304,8 @@ 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 + tini + 只读诊断工具(C-DEBIAN-SLIM) -├── hoststack.yaml # 网关编排清单,非应用代码 +├── Dockerfile # 仅本地开发用;生产不执行(C-DEBIAN-SLIM,边界见 deployment.md §3) +├── hoststack.yaml # 生产部署真源(runtime/build/start/healthCheck),非网关清单,见 §5.1 ├── src/ │ ├── main.rs # tokio main:读 PORT/REDIS_URL/CONFIG_ENCRYPTION_KEY 后启动 webhook HTTP 入口(单入口,无 CLI 子命令) │ ├── config.rs # Redis 业务配置反序列化(serde → BusinessConfigWire → Config);CONFIG_ENCRYPTION_KEY 解析 diff --git a/hoststack.yaml b/hoststack.yaml index bf0645a..77e3025 100644 --- a/hoststack.yaml +++ b/hoststack.yaml @@ -7,16 +7,30 @@ # 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. # -# Precedence (verified in the HostStack agent source, deploy-executor.ts): -# a value declared here wins; a field omitted here falls back to the -# per-runtime framework default. For runtime: rust those defaults are -# `cargo fetch` / `cargo build --release` / `./target/release/app`. +# Precedence (verified in the HostStack agent source, deploy-executor.ts). +# Three layers, in order: +# 1. the values already stored on the dashboard services row -> payload +# 2. a field DECLARED here unconditionally overwrites that (guard is +# `if (svcConfig.start?.command)`, with no `&& !payload.x`), so this +# file wins over the dashboard for the fields it declares +# 3. a field OMITTED here keeps the dashboard value; the per-runtime +# framework default then fills in only if that is still empty, which +# for an existing service it never is -- the agent's own comment reads +# "trigger-payload always populates payload.x from the DB", so that +# branch is effectively dead code # -# That last default is why start.command MUST be declared below: `app` is not -# this package's binary name. Cargo.toml declares [[bin]] name = "message-weave" -# with path = "src/main.rs"; there is no `app` target and no src/app.rs. -# Deleting this file reverts the service to a start command that exits with -# `./target/release/app: not found` before it can bind the port. +# For runtime: rust those framework defaults are `cargo fetch` / +# `cargo build --release` / `./target/release/app`. +# +# Consequence: deleting this file does NOT fall back to safe defaults. It +# makes the dashboard's stored values take effect as-is, and that stored +# Start Command is HostStack's Rust default `./target/release/app` -- which +# is not this package's binary name. Cargo.toml declares +# [[bin]] name = "message-weave" with path = "src/main.rs"; there is no +# `app` target and no app.rs. The next deploy would exit with +# `./target/release/app: not found` before binding the port. +# To drop this file, first correct all three commands in the dashboard UI, +# then delete and redeploy to verify. See docs/deployment.md §5.1. # # Do NOT add --debug here for production. See the note above start.command. services: