diff --git a/docs/deployment.md b/docs/deployment.md index dba45e2..257ef51 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -71,12 +71,14 @@ section in the configuration page. Enter the public HTTPS origin that receives c the Worker URL when the gateway is enabled, otherwise the backend origin. The page registers `/webhook/tg` with Telegram and `/push/jmap` with Stalwart. Credentials remain in the backend; the browser sends only the callback URLs. The protected registration handlers are -`src/notify.rs:1439` for Telegram and `src/notify.rs:1315` for Stalwart. +`src/notify.rs:1619` for Telegram and `src/notify.rs:1464` for Stalwart. Telegram's secret token and `allowed_updates: ["message"]` are taken from the saved business configuration. Stalwart verification and verification-code writeback happen automatically -after registration. Registration can be repeated safely; changing the callback origin updates -Telegram and creates a Stalwart subscription for the new URL. +after registration. Registration can be repeated safely; registering the same origin is +idempotent. Changing the callback origin updates Telegram (atomic replacement) and, for +Stalwart, destroys the previous subscription before creating the new one under the registration +lock, so the old origin stops receiving pushes. To remove a subscription, call `POST /api/push/disable` with the same `callback_url`. diff --git a/docs/design.md b/docs/design.md index 571d111..0584e26 100644 --- a/docs/design.md +++ b/docs/design.md @@ -420,7 +420,7 @@ Testing: **No integrated test directory**, and no stand-alone util tool module. ### 7.3.1 Configuration Management API - `GET /` provides a SPA embedded in the Rust binary; `/assets/config.js` and `/assets/styles.css` provide page resources. The service does not read or write local files while running (`C-NO-LOCAL-WRITE`). -- `GET /api/status` publicly returns `{ "ready": boolean, "mode": "configured" | "configuration-setup", "missing": string[], "version": string }`, which only lists the missing environment variable names. When `REDIS_URL` or `CONFIG_ENCRYPTION_KEY` is missing, SPA only displays the configuration boot status and missing variables; the management session authorization area is displayed only after the service status is confirmed to be ready=true. `version` is the `BUILD_VERSION` (`+`) baked by `build.rs` during compilation. The SPA footer uses this to confirm whether the deployment is actually implemented. +- `GET /api/status` publicly returns `{ "ready": boolean, "mode": "configured" | "configuration-setup", "missing": string[], "version": string }`, which only lists the missing environment variable names. When `REDIS_URL` or `CONFIG_ENCRYPTION_KEY` is missing, SPA only displays the configuration boot status and missing variables; the management session authorization area is displayed only after the service status is confirmed to be ready=true. `version` is the `BUILD_VERSION` (`+`) baked by `build.rs` during compilation. The SPA no longer renders it, so the API is the only way to confirm a deployment landed. - `POST /api/admin/session` accepts `Authorization: Bearer ` and returns `{ "session": "", "expires_in": 1800 }`; the admin session only saves the digest in Redis and expires after 1,800 seconds. `POST /api/admin/session/revoke` revokes the current session and returns `204` successfully. - The admin page only saves the session in JavaScript memory. Request settings `credentials: omit`, `cache: no-store`, without using cookies, localStorage or sessionStorage. The admin API accepts a valid admin session; the compatibility path also accepts `WORKER_TOKEN`. Worker transparently transmits the authentication header (`SAF-LB-PASSTHRU`) as it is. - `GET /api/config` and `PUT /api/config` only read and write non-sensitive running parameters: diff --git a/docs/design.zh-CN.md b/docs/design.zh-CN.md index 1c0d056..bb63c2c 100644 --- a/docs/design.zh-CN.md +++ b/docs/design.zh-CN.md @@ -420,7 +420,7 @@ message-weave/ ### 7.3.1 配置管理 API - `GET /` 提供嵌入 Rust 二进制的 SPA;`/assets/config.js` 与 `/assets/styles.css` 提供页面资源。服务不在运行时读取或写入本地文件(`C-NO-LOCAL-WRITE`)。 -- `GET /api/status` 公开返回 `{ "ready": boolean, "mode": "configured" | "configuration-setup", "missing": string[], "version": string }`,只列缺少的环境变量名称。缺少 `REDIS_URL` 或 `CONFIG_ENCRYPTION_KEY` 时,SPA 只显示配置引导状态与缺失变量;服务状态确认 ready=true 后才显示管理会话授权区。`version` 是 `build.rs` 在编译期烘焙的 `BUILD_VERSION`(`+`),SPA 页脚据此确认部署是否真正落地。 +- `GET /api/status` 公开返回 `{ "ready": boolean, "mode": "configured" | "configuration-setup", "missing": string[], "version": string }`,只列缺少的环境变量名称。缺少 `REDIS_URL` 或 `CONFIG_ENCRYPTION_KEY` 时,SPA 只显示配置引导状态与缺失变量;服务状态确认 ready=true 后才显示管理会话授权区。`version` 是 `build.rs` 在编译期烘焙的 `BUILD_VERSION`(`+`)。SPA 已不再渲染它,确认部署落地只能查这个接口。 - `POST /api/admin/session` 接受 `Authorization: Bearer `,返回 `{ "session": "", "expires_in": 1800 }`;Redis 中保存的是会话令牌的摘要,30 分钟后过期。`POST /api/admin/session/revoke` 可立即撤销会话,成功返回 `204`。 - 管理页面仅在 JavaScript 内存中保存 session。请求设置 `credentials: omit`、`cache: no-store`,不使用 Cookie、localStorage 或 sessionStorage。管理 API 接受有效 admin session;兼容路径也接受 `WORKER_TOKEN`。Worker 原样透传鉴权头(`SAF-LB-PASSTHRU`)。 - `GET /api/config` 与 `PUT /api/config` 只读取和写入非敏感运行参数: diff --git a/docs/reference.md b/docs/reference.md index 09c065d..9671c4f 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -272,7 +272,8 @@ Response conventions: `ready` is `false` and `missing` lists the absent required keys (`REDIS_URL`, `CONFIG_ENCRYPTION_KEY`) when a variable is absent — the route itself never errors, so it is safe to poll. `version` is the `BUILD_VERSION` string baked in by `build.rs` - (`+`) and is how the SPA footer proves a deploy landed. + (`+`). The SPA no longer renders it — the API is now the + only way to confirm a deploy landed. - `/debug/*` returns **503** in exactly one place: `POST /debug/notify` when the business config is not loaded or there is no outbound client — `service_unavailable` with `Retry-After: 30`. The three probe endpoints instead report failure @@ -703,7 +704,9 @@ the secret in that response. Re-run `setWebhook` after rotating the secret. Register Stalwart push with `POST /api/push/register` and an HTTPS `callback_url`. The backend creates the subscription and completes Stalwart's verification callback. Repeating the same -callback URL is idempotent. Use `POST /api/push/disable` with that URL to remove it. +callback URL is idempotent. Registering with a different callback URL destroys the previous +subscription first, then creates the new one and repoints the current-pointer mapping, so the old +origin stops receiving pushes. Use `POST /api/push/disable` with that URL to remove it. The backend has no internal scheduler. [`scripts/cron-drain.sh`](../scripts/cron-drain.sh) calls `/reconcile` (when `MW_RECONCILE_TOKEN` is set) and then `/worker`. Configure diff --git a/docs/reference.zh-CN.md b/docs/reference.zh-CN.md index 39c705a..574e4b2 100644 --- a/docs/reference.zh-CN.md +++ b/docs/reference.zh-CN.md @@ -271,7 +271,7 @@ env var — 并且“DEBUG_TOKEN”非空**(“SAF-DEBUG-GATE”;门被读 “ready”为“false”,“missing”列出了缺少的必需密钥(“REDIS_URL”、 `CONFIG_ENCRYPTION_KEY`)当变量不存在时 - 路由本身永远不会出错,所以它是 可以安全进行民意调查。 `version` 是由 `build.rs` 烘焙的 `BUILD_VERSION` 字符串 - (`+`),这就是 SPA 页脚证明部署已落地的方式。 + (`+`)。SPA 已不再渲染它,确认部署落地只能查这个接口。 - `/debug/*` 在一个地方返回 **503**:业务执行时`POST /debug/notify` 配置未加载或没有出站客户端 — `service_unavailable` `重试时间:30`。三个探测端点反而报告失败 @@ -694,7 +694,8 @@ Telegram、JMAP、allowlist、worker、reconcile 和 LLM 业务设置驻留在 R 使用 `getWebhookInfo` 检查已注册的 URL;Telegram 不会在响应中回显 Webhook 密钥。更换密钥后,需重新调用 `setWebhook`。 通过 `POST /api/push/register` 和 HTTPS `callback_url` 注册 Stalwart Push。后端会创建订阅并完成 Stalwart 的验证回调; -重复注册相同 URL 是幂等的。调用 `POST /api/push/disable` 并传入该 URL 可删除订阅。 +重复注册相同 URL 是幂等的。用一个不同的回调 URL 注册时,后端会先销毁上一个订阅,再创建新订阅并重指向当前指针映射, +因此旧 origin 不再收到推送。调用 `POST /api/push/disable` 并传入该 URL 可删除订阅。 后端没有内置调度器。外部调度器运行 [`scripts/cron-drain.sh`](../scripts/cron-drain.sh):设置了 `MW_RECONCILE_TOKEN` 时, 脚本先调用 `/reconcile`,再调用 `/worker`。在调度器的密钥管理器中配置 `MW_APP_URL`、`MW_WORKER_TOKEN`,