Files
dingMCP/docs/deployment.md
T

167 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 部署与分发
## 服务器形态
统一网关是一个小型 Node.js 服务,监听 `127.0.0.1:3010`,由 Nginx 提供 HTTPS。它只调用钉钉与薪福通 API,不运行浏览器,通常 1 核 CPU、512 MB–1 GB 内存即可。
需要:
- Node.js 22.13+;
- Nginx、Certbot 和固定域名;
- 一个专用钉钉企业内部应用;
- 薪福通正式 OpenAPI 应用。
钉钉应用 OAuth 回调:
```text
https://approval.justconn.top/oauth/dingtalk/callback
```
钉钉应用需要用户登录/身份、通讯录个人信息读取(用于实时取得当前用户所属部门)、OA 审批待办与已处理工作记录、本人发起实例 ID、实例详情、模板 Schema、审批处理和发起实例所需权限。
薪福通除已有的用户映射、本人待办、单据详情、审批历史和 pass/reject/back 权限外,发起审批还需要一次性开通:
- `xft-member/openapi/xft-member/member/get/by-id`(查询企业成员详情,用于由当前登录身份安全取得本人花名册员工号);
- `itrip-gateway/v1/form/query-event-list`;
- `itrip-gateway/v1/form/customFields/eventSeqId/{eventSeqId}`;
- `itrip-gateway/v1/dataSource/findMultilevelSourcePage`(查询企业费用类型数据源);
- `itrip-gateway/v1/dataSource/getMultiLevelDetail/{sourceSeqId}`(读取费用类型名称与正式提交编码);
- `itrip-gateway/v1/approve/recall`(单据撤回);
- `itrip-gateway/v1/bills/apply/travel/create`;
- `itrip-gateway/v1/bills/apply/expense/create`;
- `itrip-gateway/v1/bills/apply/entertainment/create`;
- `itrip-gateway/v1/bills/apply/common/create`;
- `itrip-gateway/v1/reimburse/bill/create`;
- `itrip-gateway/v1/reimburse/bill/travel/create`。
统一历史查询还会使用已开通的 `itrip-gateway/v1/bills/queryBillByCondition`(本人发起单据)和 `itrip-gateway/v1/bills/bill-list`(精简列表信息)。两者必须保留权限校验;`empId` 只能由网关从当前登录用户的成员档案派生。
创建接口必须保持权限校验开启。网关统一提交 `APPRV_PEND`,并在写后实时读取本人单据状态:进入待审批时提供撤回引用;按企业流程规则即时通过时返回 `completed`,不提供撤回引用且禁止重复提交。
`1.1.0-rc.15` 中薪福通撤回仍标记为实验能力:权限和调用契约已接通,但当前企业真实请求返回 `XFTSYS9999`,尚未取得成功样本。网关会写后复核并禁止自动重试。面向试用用户时必须保留这一说明;若业务要求撤回必须稳定可用,应等待薪福通定位后再升级为正式能力。
## 首次凭证配置
把钉钉 AppKey/AppSecret 保存为权限 `0600` 的 `.secrets/dingtalk.json`,把薪福通 AuthoritySecret 保存为权限 `0600` 的密钥文件。然后运行一次:
```bash
npm ci
npm run build
node dist/src/admin.js service configure \
--public-base-url https://approval.justconn.top \
--corp-id YOUR_CORP_ID \
--credentials-file .secrets/dingtalk.json \
--xft-open-api-app-id YOUR_XFT_APP_ID \
--xft-open-api-secret-file .secrets/xft-open-api.secret \
--xft-open-api-enterprise-id YOUR_XFT_ENTERPRISE_ID \
--port 3010
```
不要把这些企业凭证发给终端用户。
`MCP_SESSION_SECRET_FILE` 与 `MCP_APPROVAL_REF_SECRET_FILE` 由管理命令生成,内容至少 32 字节且权限必须为 `0600`(或更严格)。手工配置环境变量时同样强制至少 32 字节;缺失时网关拒绝启动,不再临时生成会导致重启后引用失效的随机密钥。`MCP_PUBLIC_BASE_URL` 必须是无内嵌凭证、无额外路径/query/hash 的 HTTPS origin,端口范围为 1–65535。
## 一键安装和更新
首次发布还需要生成一对 Ed25519 发布密钥:
```bash
npm run release:keygen
```
私钥保存在 `.secrets/release-signing-private.pem`(`0600`),必须离线备份且禁止提交、复制到 Web 目录或提供给网关进程;公钥位于 `distribution/release-signing-public.pem` 并随客户端发布。密钥已经存在时脚本会拒绝覆盖,轮换必须另行设计交叉签名迁移,不能直接替换。
首次配置完成后,每次安装、升级或 Bug 修复统一执行:
```bash
cd /path/to/dingMCP
./deploy/install.sh
```
脚本自动测试、构建、用 Ed25519 私钥签署 schema v3 清单、重启服务、原子发布静态文件、安装 Nginx 配置并验证公网健康检查、OAuth 元数据和安装链接。首次签证书时,临时 HTTP 配置只开放 ACME challenge,其余请求返回 403;不会在明文 HTTP 上代理 OAuth、Token 或 MCP。证书通过 Certbot webroot 模式签发,正式配置启用 HTTPS 跳转与不带 `includeSubDomains` 的 HSTS。已有证书会自动复用;签名私钥缺失、权限不是 `0600` 或与固定公钥不匹配时发行会失败。
发行物先复制到独立 staging 目录,再通过符号链接原子切换;客户端不会看到新清单配旧 artifact 的中间状态。服务器保留当前版本和最近两个旧版本用于排障,较老版本自动清理。`install.md`、`compat-install.md`、清单、签名、安装器、Skill、校验文件和测试包均以 `no-cache` 提供;内容寻址的客户端 tgz 才使用 immutable 缓存。签名清单的 `createdAt` 超过 180 天时兼容客户端拒绝安装,避免旧签名版本冻结攻击。
卸载:
```bash
./deploy/uninstall.sh
```
这是服务器卸载命令,不是终端用户插件卸载。它默认保留凭证、OAuth 数据库和 Let's Encrypt 证书,重新安装无需重新录入企业密钥。自动化环境可使用 `./deploy/uninstall.sh --yes`。
## 用户分发
部署脚本会生成:
```text
release/
├── install.md
├── compat-install.md
├── manifest.json
├── manifest.sig
├── SHA256SUMS
├── dingtalk-approval-installer.mjs
├── dingtalk-approval-bootstrap.ps1
├── dingtalk-approval-bootstrap.sh
├── release-signing-public.pem
├── dingtalk-approval-client-*.tgz
├── dingtalk-approval-SKILL.md
├── dingmcp-test-bundle.tar.gz
└── agent-test-install.md
```
终端用户只需要把下面链接发给自己的 Agent:
```text
https://approval.justconn.top/downloads/dingmcp/install.md
```
公开 `install.md` 是按回调拓扑选择路径的短安装说明。同机浏览器能够完成宿主回调时,Agent 应按宿主官方方式注册名为 `dingtalk-approval` 的远程 Streamable HTTP MCP,URL 为 `https://approval.justconn.top/mcp`,并由宿主使用标准 OAuth 元数据、动态客户端注册和 PKCE 登录。该路径不下载 Node/npm、Bridge 或 Skill;网关 MCP `instructions` 已提供完整业务规则。登录后只读调用 `approval_list_todos(source=all, limit=1)`,钉钉 OA 与薪福通都为 `ok` 才算验收通过。
宿主缺少远程 MCP/标准 OAuth,或 loopback 回调与用户打开授权页的设备不一致时,`install.md` 引导到 `compat-install.md`,无需先尝试一次必然失败的远程登录。兼容页保留签名客户端、平台引导、Skill、stdio MCP、可跨设备的 device-code 登录、更新、卸载和故障恢复步骤。完整 Skill 必须同时包含 `references/create-approval.md` 和 `references/local-drafts.md`。Windows PowerShell 产物必须带 UTF-8 BOM,Node 探测使用外层双引号/内层单引号,避免 PowerShell 5.1 吞掉传给原生程序的双引号。
兼容 Windows 下载优先使用 `curl.exe`,从系统代理变量继承代理,并设置 10 秒连接超时、20 秒低速判定和 90 秒单源总时限;没有 curl 才以 `Invoke-WebRequest -TimeoutSec 90` 兜底。Node 固定版本制品仍按阿里云、npmmirror、官方源顺序尝试并强制校验官方 SHA-256。项目 artifact 由 Ed25519 清单和 SHA-256 保护;来源失败可换源,签名或哈希不符必须立即停止。
正式部署会把同一批不可变兼容安装文件上传到公开 Gitea Release,并把自建 HTTPS 作为固定备用。远程默认路径不依赖这些客户端文件,只依赖网关健康、OAuth 元数据和 `/mcp`。同一版本重跑复用 Release Tag 的提交时间和归一化测试包,保持可重入。
## 运维检查
网关会话上限默认为每个已认证用户 32 个、全局 1000 个,同时计入尚未完成初始化的连接。
运维人员可在服务端环境配置中设置 `MCP_SESSION_MAX_PER_USER=64` 等正整数(范围 1–1000),
并通过上述部署流程更新网关后生效;无需修改或更新客户端。未配置时使用 32,非法值拒绝启动。
该阈值用于限制一个账号占用的连接和内存,不是审批条数、同时执行审批的配额或上游平台限制。
现有 Bridge 客户端退出或刷新登录时,即使没有发送 MCP DELETE,网关也会在观察到
Streamable HTTP GET 长连接断开后保留 60 秒重连宽限期,再回收遗留会话;后台每 15 秒
检查一次,新连接进入时也会检查,因此通常在断开后的 60–75 秒内释放名额。
宽限期内恢复长连接或继续发送请求会保留原会话;仅使用 POST 的客户端仍使用 2 小时
闲置期限。正常连接中的长会话不按闲置期限清理,最长存活时间仍为 24 小时。
实际请求处理结束前不会因为断线、取消或超时回收会话,也不会重试任何审批写入。
回收状态只保存在内存中,不记录审批参数、正文、Token 或用户身份到日志。
如果会话确实仍在连接并达到配置上限,网关仍返回 HTTP 429 和 `Retry-After: 60`,需要关闭不用的
Agent 连接再重试。旧客户端可能仍将 429 显示为“审批网关暂时无法连接”;此次服务端
修复无需用户更新客户端,但不会改变客户端内置的错误文案。
```bash
systemctl --user status dingtalk-approval-mcp.service
curl --fail http://127.0.0.1:3010/healthz
curl --fail https://approval.justconn.top/healthz
curl --fail https://approval.justconn.top/downloads/dingmcp/manifest.json
curl --fail https://approval.justconn.top/downloads/dingmcp/manifest.sig
```
备份时必须把 `.runtime/auth.sqlite`、同路径派生的 `.runtime/auth.sqlite.invoice-dedup.sqlite` 与 `.secrets/oauth-session.key` 一并迁移。发票查重库只含不可逆 HMAC 和最小状态元数据;审批正文不落库,不需要备份。本机提单草稿不在服务器上,也不属于服务器备份;用户如需迁移,必须把 Agent 所在机器的当前用户私有草稿目录作为敏感业务数据单独处理。
每个候选版和正式版发布前执行 [发布检查清单](release-checklist.md)。
只读 E2E 脚本没有生产默认地址,也不会自动挑选第一张审批:
```bash
DINGTALK_APPROVAL_MCP_URL=https://staging.example.com/mcp \
node scripts/e2e-approval-mcp.mjs dingtalk
```
读取指定单据时加 `--business-no EXACT`。只有已取得真实写入授权时,才可再加 `--execute-real-write`;脚本要求精确业务编号,并在写后重新列待办复核。任何 `outcome_uncertain` 均不得重试。