# Server B Web 运行时 Runbook 本文记录 S05 在 Server B 上建立的 Node.js、pnpm、Nginx、systemd 与开发 TLS 基线。当前环境是本地 KVM 开发环境,不是阿里云生产环境。 ## 1. 固定版本 | 组件 | 版本 | 管理方式 | | --- | --- | --- | | Node.js | `v22.22.2` / Debian 包 `22.22.2-1nodesource1` | NodeSource 22.x,`apt-mark hold` | | Corepack | `0.34.6` | Node 全局工具 | | pnpm | `10.33.0` | 全局固定版本;项目应再用 `packageManager` 固定 | | Nginx | `1.24.0-2ubuntu7.12` | Ubuntu 24.04 security/updates,`apt-mark hold` | Node.js 22 当前属于 LTS 线。固定包在升级前必须先核对 Node/Nginx 安全公告、NestJS/Prisma 兼容性并完成回归测试。 版本记录:`/etc/lisglosips/web-runtime-versions.env`。 ## 2. 目录与发布模型 ```text /opt/lisglosips/ ├── releases/ │ └── s05-placeholder-20260620/ │ ├── public/index.html │ └── server.mjs └── current -> /opt/lisglosips/releases/s05-placeholder-20260620 ``` - 发布目录由 `root:lisglosips` 持有,程序只读。 - `current` 必须是指向完整 release 的符号链接。 - 正式发布先写入新 release,完成校验后再原子切换 `current`。 - 不允许直接在 `current` 指向的 release 内在线修改代码。 - systemd 可写状态目录为 `/var/lib/lisglosips`,运行目录为 `/run/lisglosips`。 ## 3. systemd 服务模板 模板:`/etc/systemd/system/lisglosips@.service`。 实例环境文件:`/etc/lisglosips/<实例名>.env`,权限必须为 `0640 root:lisglosips`。当前实例: ```text lisglosips@api.service -> /etc/lisglosips/api.env ``` 模板默认使用 `lisglosips` 系统用户,启用只读系统、私有临时目录、空 capability、设备隔离和内核保护。后续 Worker 如需写录音目录,应使用实例 drop-in 精确增加 `ReadWritePaths`,不要放宽整个模板。 常用命令: ```bash sudo systemctl status lisglosips@api sudo systemctl restart lisglosips@api sudo journalctl -u lisglosips@api -n 100 --no-pager sudo systemd-analyze security lisglosips@api.service ``` ## 4. Nginx 路由 | 路径 | 行为 | | --- | --- | | `/` | `/opt/lisglosips/current/public` 静态站点,支持 SPA fallback | | `/healthz` | 反向代理到 `127.0.0.1:3000` | | `/api/` | API 代理,单 IP `20 r/s`、burst 40 | | `/api/auth/login` | 登录专用限制,单 IP `5 r/min`、burst 3 | | `/_recordings/` | 仅供 `X-Accel-Redirect` 内部访问,外部请求返回 404 | 关键文件: ```text /etc/nginx/conf.d/lisglosips-global.conf /etc/nginx/sites-available/lisglosips.conf /etc/nginx/snippets/lisglosips-proxy.conf /etc/nginx/snippets/lisglosips-security-headers.conf /etc/nginx/snippets/lisglosips-tls.conf ``` 应用仅监听 `127.0.0.1:3000`。Nginx 监听 80/443,但 nftables 仅允许已登记管理端访问 443;开发阶段没有对外开放 80。 配置变更: ```bash sudo nginx -t sudo systemctl reload nginx ``` ## 5. 开发 TLS 开发 CA 和证书位于: ```text /etc/lisglosips/pki/ca/lisglosips-dev-ca.crt /etc/lisglosips/pki/ca/lisglosips-dev-ca.key /etc/lisglosips/pki/certs/server.crt /etc/lisglosips/pki/private/server.key ``` CA 私钥为 `0600 root:root`,服务器私钥为 `0640 root:www-data`。证书 SAN 包含 `lisglosips.local`、`yanzi`、`100.90.90.91` 和 `127.0.0.1`。 重新签发开发证书: ```bash sudo /usr/local/sbin/lisglosips-issue-dev-cert sudo nginx -t sudo systemctl reload nginx ``` 本机公有 CA 副本保存在 `.codex-private/tls/lisglosips-dev-ca.crt`,不得把 CA 私钥复制出 Server B。Windows 使用私有 CA 测试时,因开发 CA 不发布 CRL,curl 需附加 `--ssl-no-revoke`;这不会关闭证书链、主机名或签名校验。 迁移阿里云后必须替换为正式域名证书,并完成: 1. 把证书和私钥放入受控 Secret 路径。 2. 更新 `lisglosips-tls.conf`。 3. 将 HSTS 从开发值 `300` 调整为生产值,建议先观察再提升至 `31536000`。 4. 验证自动续期、续期失败告警和回滚证书。 5. 开放 80 仅用于 ACME/跳转,或使用 DNS-01 后继续关闭 80。 ## 6. 健康检查 ```bash curl --fail http://127.0.0.1:3000/healthz curl --fail --cacert /etc/lisglosips/pki/ca/lisglosips-dev-ca.crt \ https://127.0.0.1/healthz sudo ss -lntp | grep -E ':(80|443|3000)\b' sudo systemctl --failed ``` 预期: - `/healthz` 返回 `status=ok`。 - 3000 仅出现在 `127.0.0.1`。 - 443 由 Nginx 监听。 - 首页响应包含 HSTS、CSP、X-Frame-Options、X-Content-Type-Options 和 Permissions-Policy。 - TLS 1.2/1.3 可用,TLS 1.1 被拒绝。 ## 7. 新版本发布 ```bash release="2026xxxxTxxxxxxZ" sudo install -d -o root -g lisglosips -m 0750 "/opt/lisglosips/releases/$release" # 将已经构建和校验的产物放入新 release。 sudo chown -R root:lisglosips "/opt/lisglosips/releases/$release" sudo find "/opt/lisglosips/releases/$release" -type d -exec chmod 0750 {} + sudo find "/opt/lisglosips/releases/$release" -type f -exec chmod 0640 {} + sudo ln -sfn "/opt/lisglosips/releases/$release" /opt/lisglosips/current sudo systemctl restart lisglosips@api sudo nginx -t && sudo systemctl reload nginx ``` 发布后必须检查内部健康、HTTPS 健康、日志、应用端口监听和前端静态文件。数据库迁移从 S08 起必须在切换 `current` 前按迁移 Runbook 执行。 ## 8. 回滚 应用回滚:把 `current` 指回上一 release,重启应用并检查 HTTPS 健康。 ```bash sudo ln -sfn /opt/lisglosips/releases/ /opt/lisglosips/current sudo systemctl restart lisglosips@api ``` S05 变更前备份: ```text /var/backups/lisglosips-s05/20260620T123432Z ``` 其中 `pre-change.tar.gz` 包含变更前应用与配置,`SHA256SUMS` 用于恢复前校验。恢复 Nginx/systemd 配置后必须执行 `nginx -t`、`systemctl daemon-reload`,再重启对应服务。卸载 Nginx或解除包固定不是常规回滚动作,只有确认恢复目标需要时才执行。 ## 9. 已知事项 - NodeSource 仓库在本地代理链路上偶尔出现 TLS 握手中断;现有索引和已安装包可用。升级前必须先确认仓库恢复并校验候选版本。 - 当前证书仅供开发,不能用于阿里云生产。 - 80 已监听但被 nftables 拒绝;生产是否开放取决于证书签发方案。 - 当前 API 是 S05 无依赖占位服务,S07 后端骨架完成后替换。