150 lines
9.2 KiB
Markdown
150 lines
9.2 KiB
Markdown
# 部署与分发
|
||
|
||
## 服务器形态
|
||
|
||
统一网关是一个小型 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/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/bills/apply/use-car/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 的提交时间和归一化测试包,保持可重入。
|
||
|
||
## 运维检查
|
||
|
||
```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` 均不得重试。
|