Files
gongxue-base/scripts/deploy/README.md
2026-07-12 19:26:57 +08:00

471 lines
19 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.

# SaaS 题库服务器部署手册
本手册覆盖两类场景:
1. 全新测试/预生产服务器的隔离 bootstrap。
2. 现有云服务器从旧原地覆盖脚本升级到版本化原子发布。
生产发布器默认 fail closed。缺少真实数据库 readiness、三端 runtime config、生产 evidence、浏览器烟测环境或服务重启权限时发布必须失败不能通过关闭门禁或伪造 evidence 绕过。
## 先选择入口
| 场景 | 入口 | 配置模型 | 状态 |
| --- | --- | --- | --- |
| 全新生产服务器 | 仓库根目录 `deploy.sh` | `/opt/tiku-saas/shared/` | 推荐 |
| 现有云服务器 | `scripts/deploy/bin/deploy.sh` 安装到 `/opt/tiku-saas/bin/deploy.sh` | `/etc/tiku-saas/` | 兼容升级入口 |
| 全新测试/预生产服务器 | 本手册的 staging bootstrap | 完全独立目录、数据库、域名和凭据 | 当前无一键 staging profile |
两套生产脚本的变量名不同,不能混用 env 模板:
- 根脚本:`REPO_URL``BRANCH``DEPLOY_ROOT`,模板为根目录 `deploy.env.example`
- 兼容脚本:`GIT_REPO``GIT_BRANCH``APP_ROOT`,模板为 `scripts/deploy/env/deploy.env.example`
服务器长期更新命令可以继续保持:
```bash
sudo -u deploy /opt/tiku-saas/bin/deploy.sh
```
但首次发布当前基线前,必须先升级 `/opt/tiku-saas/bin/deploy.sh`、部署配置、API/Worker env 和 systemd units。发布脚本不会自动更新自己也不会自动安装 Nginx 或 systemd 模板。
## 发布能力与边界
新版生产部署器会:
1. 获取固定分支的最新 commit并在独立候选 release 安装锁定依赖。
2. 运行 TypeScript、仓库安全扫描、runtime audit 和 Taro 供应链审计。
3. 构建 API、Worker、学生 H5、租户后台 H5、平台后台 H5。
4. 注入三端公开 runtime config运行严格 guard、manifest、25 项静态 smoke 和 33 项真实浏览器交互 smoke。
5. 运行生产 env readiness可选执行 migration随后运行数据库 readiness。
6. 使用与 commit、artifact 和 SHA-256 绑定的真实 production launch evidence。
7. 原子切换应用与 Web release重启服务验证 API `/health` 和线上 H5 hash。
8. 失败时恢复上一份代码和 Web release。
脚本不会:
- 创建数据库或对象存储备份。
- 回滚已经提交的 migration。
- 自动更新 `/etc/systemd/system`、宝塔 Nginx 或 `/etc/tiku-saas/*.env`
- 逐一确认所有 Worker backlog、Provider、外部告警和业务数据正确性。
- 自动创建首个平台超管。
因此 migration 必须先备份并演练向后兼容。代码/Web 回滚不能被当作数据库回滚。
## 域名与目录
参考域名:
```text
api.tjszsb.com Node.js API
app.tjszsb.com 学生 H5
admin.tjszsb.com 租户后台 H5
console.tjszsb.com 平台后台 H5
supabase.tjszsb.com Supabase Gateway/Auth/Data API
studio.tjszsb.com Supabase Studio必须限制来源
```
现有生产兼容布局:
```text
/opt/tiku-saas/source Gitea 源码副本,只用于 fetch/build
/opt/tiku-saas/releases 版本化候选应用
/opt/tiku-saas/current 当前应用 release 软链接
/opt/tiku-saas/repo systemd 当前运行副本
/opt/tiku-saas/bin/deploy.sh 服务器外置兼容部署器
/srv/tiku-saas/www 当前三端 Web release 软链接
/srv/tiku-saas/www-releases 版本化 Web release
/etc/tiku-saas/deploy.env 部署配置和只读 Gitea 凭据
/etc/tiku-saas/api.env API 生产 env
/etc/tiku-saas/worker.env Worker 生产 env
/etc/tiku-saas/runtime-config/ 三端公开 runtime config
/etc/tiku-saas/production-launch-evidence.json
/etc/tiku-saas/launch-artifacts/ 与 evidence 配套的证据文件
```
根部署器使用 `/opt/tiku-saas/shared/` 保存 deploy env、readiness env、runtime config 和 evidence。systemd 模板目前仍从 `/etc/tiku-saas/api.env``/etc/tiku-saas/worker.env` 读取运行配置,因此使用根脚本时也必须维护这两个文件;`shared/.env` 只用于候选 release 的 readiness关键 API 配置必须与 `/etc/tiku-saas/api.env` 保持一致,避免双配置漂移。现有服务器优先使用兼容入口,减少这项差异。
## 凭据
- 已经出现在聊天、工单、截图或日志里的 token 必须吊销。
- 服务器优先使用只读 SSH deploy keyGitea SSH 端口为 `2222`
- 使用 HTTPS 时token 只放权限为 `600/640` 的服务器 env由临时 `GIT_ASKPASS` 注入。
- token 不得写入 Git remote、脚本、README 或命令历史。
- `DATABASE_ADMIN_URL``DATABASE_MIGRATION_URL` 只从密码管理器临时注入,不长期写入 deploy/API/Worker env。
SSH remote
```text
ssh://git@git.gongxue100.com:2222/chenhaogxjy/tiku-supabase.git
```
## 新测试服务器
测试服务器必须与生产完全隔离:
```text
/opt/tiku-saas-staging
/srv/tiku-saas-staging
/etc/tiku-saas-staging
staging-api.example.com
staging-app.example.com
staging-admin.example.com
staging-console.example.com
独立 PostgreSQL/Supabase、Auth、bucket 和 Provider 测试账号
```
数据库安全标记必须是:
```text
environment=staging
allow_destructive_tests=false
```
API 与 Worker 应继续使用 `NODE_ENV=production`,这样能验证生产配置 fail-fast。staging 不能使用 production 数据库、bucket、支付/短信密钥或真实用户流量,也不能运行 `supabase db reset``db:smoke-seed:test``test:api``test:rls` 和会写入集成夹具的 Worker 测试。
当前仓库没有经过验证的一键 staging profile。两套 deploy 脚本在 production 模式下都会强制真实 launch gatelaunch evidence 也不能用本地 mock 伪造。首次测试服务器采用下面的分阶段 bootstrap在 staging profile 被单独实现和验证前,不要直接套用生产 `/opt/tiku-saas``/srv/tiku-saas``/etc/tiku-saas` 路径。
### 1. 固定候选 commit
```bash
sudo install -d -o deploy -g deploy /opt/tiku-saas-staging/source
sudo -u deploy git clone \
ssh://git@git.gongxue100.com:2222/chenhaogxjy/tiku-supabase.git \
/opt/tiku-saas-staging/source
sudo -u deploy git -C /opt/tiku-saas-staging/source checkout <reviewed-commit-sha>
```
不要在验证期间继续移动候选分支。
### 2. 安装与构建预检
```bash
cd /opt/tiku-saas-staging/source
sudo -u deploy npm ci --workspaces --include-workspace-root --include=dev
sudo -u deploy npm run check:api
sudo -u deploy npm run check:worker
sudo -u deploy npm run check:taro
sudo -u deploy npm run security:repo
sudo -u deploy npm run audit:runtime
sudo -u deploy npm run audit:taro:supply-chain
sudo -u deploy npm run build:api
sudo -u deploy npm run build:worker
sudo -u deploy npm run build:taro:h5:student
sudo -u deploy npm run build:taro:h5:tenant
sudo -u deploy npm run build:taro:h5:platform
```
服务器必须安装 Chrome/Chromium或设置 `TARO_H5_SMOKE_BROWSER` 指向受支持浏览器。随后运行:
```bash
sudo -u deploy npm run smoke:taro:h5
sudo -u deploy env TARO_H5_INTERACTION_OUTPUT_DIR=/tmp/tiku-h5-staging \
npm run smoke:taro:h5:interaction
```
这一步只证明候选产物和本地 mock 旅程,不是生产 evidence。
### 3. 数据库 bootstrap 与 migration
先创建数据库和对象存储快照,再 dry-run runtime role 计划:
```bash
cd /opt/tiku-saas-staging/source
npm run bootstrap:db-runtime-roles
```
由真正 PostgreSQL superuser 应用一次:
```bash
DATABASE_ADMIN_URL='<secret-managed-superuser-url>' \
npm run bootstrap:db-runtime-roles -- \
--apply --confirm=BOOTSTRAP_BACKEND_RUNTIME_ROLES
```
bootstrap 不设置角色密码。用密码管理器为 `tiku_api``tiku_worker` 设置独立强密码,再把相应连接分别写入 staging API/Worker env。
全新数据库在 migrations 后没有业务租户数据,生产 readiness 会因没有 active tenant 而阻断。使用受控管理流程创建 staging 的 master tenant、active domain、branding/settings 和必要 Provider 配置;不要执行 `supabase/seed.sql`,它是本地开发 seed会把数据库标为 `local` 并写入 mock 数据。
由标准 migration role 应用迁移:
```bash
DATABASE_MIGRATION_URL='<secret-managed-migration-url>' \
supabase db push --db-url "$DATABASE_MIGRATION_URL"
```
然后使用 API 运行角色验证:
```bash
set -a
source /etc/tiku-saas-staging/api.env
set +a
npm run readiness:production
npm run readiness:production:db
```
migration 是前向操作。失败时按备份恢复方案处理,不依赖 deploy symlink 回滚。
### 4. runtime config 与受控激活
`scripts/deploy/runtime-config/*.example.json` 创建 staging 文件。浏览器 H5 使用 Origin 解析租户时,`tenantCode` 保持空字符串;只允许公开 HTTPS URL 和 Supabase publishable key。
在尚未具备 staging 原子发布 profile 时,先由运维在隔离路径安装 systemd/Nginx明确每一个 WorkingDirectory、EnvironmentFile、端口、域名和 Web root 都指向 `*-staging`。不得直接复制当前生产 unit 后仍保留 `/opt/tiku-saas/repo``/etc/tiku-saas`
完成真实 staging Auth/CORS/Provider/三类 persona、Worker 和线上 H5 检查后,将报告保存到 staging 自己的 evidence bundle。production launch evidence 仍只能由最终生产候选生成,不能从 staging 复制冒充。
## 现有服务器一次性升级
### 1. 冻结与备份
1. 将完整基线合并到 Gitea记录待部署 SHA。
2. 备份 PostgreSQL、对象存储、`/etc/tiku-saas`、旧部署脚本、旧运行目录和宝塔 Nginx 配置。
3. 验证备份可读,并记录数据库恢复与代码/Web 回滚步骤。
4. 首次 migration 推荐独立执行“备份 -> migration -> DB readiness -> evidence”正式应用发布时恢复 `RUN_DB_MIGRATIONS=false`
### 2. 准备 source checkout
从一个不依赖旧部署脚本的临时目录获取新代码:
```bash
sudo install -d -o deploy -g deploy /opt/tiku-saas/source
sudo -u deploy git clone \
ssh://git@git.gongxue100.com:2222/chenhaogxjy/tiku-supabase.git \
/opt/tiku-saas/source
sudo -u deploy git -C /opt/tiku-saas/source checkout <reviewed-commit-sha>
```
若目录已存在,只允许 clean checkout
```bash
sudo -u deploy git -C /opt/tiku-saas/source status --short
sudo -u deploy git -C /opt/tiku-saas/source fetch origin main --prune
sudo -u deploy git -C /opt/tiku-saas/source checkout main
sudo -u deploy git -C /opt/tiku-saas/source merge --ff-only origin/main
```
### 3. 升级外置部署脚本
先备份,再安装兼容入口:
```bash
sudo cp -a /opt/tiku-saas/bin/deploy.sh \
/opt/tiku-saas/bin/deploy.sh.backup-$(date +%Y%m%d%H%M%S)
sudo install -o root -g deploy -m 0750 \
/opt/tiku-saas/source/scripts/deploy/bin/deploy.sh \
/opt/tiku-saas/bin/deploy.sh
```
不要期待仓库 pull 自动更新 `/opt/tiku-saas/bin/deploy.sh`
### 4. 更新配置
备份 `/etc/tiku-saas/deploy.env``api.env``worker.env`,再逐项 diff 模板,不要覆盖真实 secrets
```bash
diff -u /etc/tiku-saas/deploy.env \
/opt/tiku-saas/source/scripts/deploy/env/deploy.env.example || true
diff -u /etc/tiku-saas/api.env \
/opt/tiku-saas/source/scripts/deploy/env/api.env.example || true
diff -u /etc/tiku-saas/worker.env \
/opt/tiku-saas/source/scripts/deploy/env/worker.env.example || true
```
必须确认:
```text
SOURCE_REPO_DIR=/opt/tiku-saas/source
REPO_DIR=/opt/tiku-saas/repo
RELEASES_DIR=/opt/tiku-saas/releases
WWW_ROOT=/srv/tiku-saas/www
WWW_RELEASES_DIR=/srv/tiku-saas/www-releases
RUN_TARO_SUPPLY_CHAIN_AUDIT=true
RUN_DB_READINESS=true
RUN_LAUNCH_GATE=true
SYSTEMD_UNITS="tiku-api.service tiku-workers.target"
HEALTHCHECK_URL=http://127.0.0.1:8787/health
```
API `DATABASE_URL` 必须使用 `tiku_api`Worker 必须使用 `tiku_worker`。真实 env 的权限推荐为 `root:deploy 0640`
Provider 密钥不要仅凭 env 模板判断“已经配置完成”。PNVS、OAuth、支付和租户级密钥以 `app_private.tenant_secrets` 及对应 Provider 配置为真实来源,必须通过仓库配置/诊断脚本和 production readiness 验证。对象存储及 Worker scanner 等进程级配置才由 API/Worker env 提供。
连接池要按进程总量预算:默认 9 个常驻 Worker 若每个 `DB_POOL_MAX=5`,仅 Worker 上限约 45 个连接;再加 API、timer/oneshot、Supabase 内部服务和运维连接。正式启用前应结合 PostgreSQL `max_connections` 和 PgBouncer 配额调整,而不是逐个进程孤立设置。
### 5. 升级 Worker 调度
不要使用通配复制后遗留旧 unit。显式安装当前清单
```bash
sudo systemctl stop tiku-worker.service 2>/dev/null || true
sudo systemctl disable tiku-worker.service 2>/dev/null || true
sudo rm -f /etc/systemd/system/tiku-worker.service
for unit in \
tiku-api.service \
tiku-worker@.service \
tiku-worker-job@.service \
tiku-worker-monthly-usage.service \
tiku-worker-monthly-usage.timer \
tiku-worker-platform-audit-alerts.timer \
tiku-worker-platform-billing.timer \
tiku-worker-platform-dunning.timer \
tiku-worker-platform-usage.timer \
tiku-worker-student-supervision.timer \
tiku-workers.target
do
sudo install -o root -g root -m 0644 \
"/opt/tiku-saas/source/scripts/deploy/systemd/$unit" \
"/etc/systemd/system/$unit"
done
sudo systemctl daemon-reload
sudo systemctl enable tiku-api.service tiku-workers.target
```
不要在新的应用 release 尚未构建并同步到 `/opt/tiku-saas/repo` 前启动 `tiku-workers.target`
连续 Worker 为 CRM、commerce、provider bills、催缴通知、审计通知、assets、imports、public banks 和 exports定时任务负责计费、用量、月结超额、催缴、审计告警和学习督导。
### 6. systemd 权限
部署器以 `deploy` 用户运行,但需要重启 `tiku-api.service``tiku-workers.target`。首次升级前检查:
```bash
sudo -u deploy systemctl is-active tiku-api.service
sudo -u deploy systemctl restart tiku-api.service
```
第二条如果要求交互认证,部署会在切换阶段失败。不要给 `deploy` `NOPASSWD: ALL`。选择其一:
- 用受审 root wrapper 只允许 restart/is-active 这两个顶层 unit并把 `RESTART_COMMAND` 指向 wrapper。
- 配置精确的 PolicyKit 规则,只允许 deploy 管理本项目 unit。
- 由 root 执行受控部署器,同时确保 clone/npm/release 文件所有权仍为 deploy并重新验证脚本权限模型。
完成最小权限方案后,必须在非交互会话中验证。
### 7. 数据库、runtime config 与 evidence
按“新测试服务器”中的数据库步骤完成:
1. superuser runtime role/extension bootstrap。
2.`tiku_api/tiku_worker` 设置独立密码。
3. 标准 migration role 应用 migration。
4. `readiness:production``readiness:production:db`
三端 runtime config 路径:
```text
/etc/tiku-saas/runtime-config/h5-student.runtime-config.json
/etc/tiku-saas/runtime-config/h5-tenant-admin.runtime-config.json
/etc/tiku-saas/runtime-config/h5-platform-admin.runtime-config.json
```
`tenantCode` 默认留空,由三个生产 Origin 解析租户;只有明确的固定租户预览/小程序模式才填写。
production evidence 必须绑定待发布 commit。兼容脚本默认读取
```text
/etc/tiku-saas/production-launch-evidence.json
```
相对 artifact 路径从 evidence 所在目录解析,因此完整 bundle 应为:
```text
/etc/tiku-saas/production-launch-evidence.json
/etc/tiku-saas/launch-artifacts/*
```
只复制 evidence JSON 而不复制 artifacts 会 fail closed。staging、本地 mock 或旧 commit 的 evidence 不能复用。
### 8. 宝塔/Nginx
宝塔服务器继续保留它管理的站点文件,不要直接套用普通 `/etc/nginx/sites-available` 命令。人工对照 `scripts/deploy/nginx/tjszsb.com.conf.example` 同步:
- `/srv/tiku-saas/www/{student,tenant-admin,platform-admin}` 三端路径。
- SPA history fallback。
- `runtime-config.json` `no-store`
- 带 hash 静态资源长期缓存。
- API forwarded headers、body limit、超时和必要限流。
- Supabase Gateway/Studio HTTPS 与 Studio 来源限制。
若宝塔站点根目录必须位于 `/www/wwwroot`,用受控软链接指向 `/srv/tiku-saas/www/*`,不要复制第二份静态文件形成漂移。
### 9. 正式执行
服务器需有 Node.js 20、npm、Git、rsync、curl、Supabase CLI以及 Chrome/Chromium。浏览器不在标准路径时设置 `TARO_H5_SMOKE_BROWSER`
先确认 source clean、evidence commit 和待发布 SHA 一致,再运行:
```bash
sudo -u deploy /opt/tiku-saas/bin/deploy.sh
```
如果本次数据库已独立迁移完成,保持:
```text
RUN_DB_MIGRATIONS=false
```
部署器在任何激活前完成候选构建和门禁;应用/Web 切换、服务重启、API health 或线上 H5 hash 失败会触发代码/Web回滚。
## 发布后验收
脚本只检查顶层 target active 和 API `/health`,发布后还必须人工运行:
```bash
systemctl --failed --no-pager
systemctl is-active tiku-api.service tiku-workers.target
systemctl list-dependencies tiku-workers.target --no-pager
systemctl list-timers 'tiku-worker-*' --all --no-pager
systemctl status 'tiku-worker@*.service' --no-pager
journalctl -u tiku-api.service -n 100 --no-pager
journalctl -u 'tiku-worker@*.service' -n 200 --no-pager
curl -fsS http://127.0.0.1:8787/health
```
随后验证:
- 三个生产域名的 `index.html`、runtime config、登录和主入口。
- `/api/tenant/resolve` 的 Origin/CORS。
- 真实 Auth/JWKS、PNVS 短信、支付/退款回调、对象存储签名与扫描。
- Worker backlog、失败重试、timer 下次执行时间和外部告警渠道。
- 错误率、P95/P99、数据库连接池、慢 SQL、锁等待、磁盘和日志轮转。
## 回滚
自动回滚只覆盖应用代码和三端 Web
```text
/opt/tiku-saas/current
/opt/tiku-saas/repo
/srv/tiku-saas/www
```
数据库 migration、对象存储写入、外部 Provider 状态和已产生业务事件不会自动回滚。数据库恢复必须使用发布前已验证的快照/备份,并由负责人单独决策。
手工代码/Web 回滚前,先记录失败 release 和日志,再将软链接切回上一 release、同步运行目录、重启服务并复核 API/H5。不要使用 `git reset --hard` 处理服务器运行目录。
## 安全检查清单
- [ ] 暴露 token 已吊销,服务器只读凭据已轮换。
- [ ] 待发布 commit 已冻结source checkout clean。
- [ ] PostgreSQL、对象存储和 `/etc/tiku-saas` 已备份并验证可读。
- [ ] `tiku_api/tiku_worker` bootstrap、独立密码和 migration role 已完成。
- [ ] 三端 runtime config 只有公开字段,`tenantCode` 策略正确。
- [ ]`tiku-worker.service` 已停止、禁用并删除。
- [ ] 新 Worker units/timers/target 已安装deploy 重启权限最小化。
- [ ] Chrome/Chromium 可用于 33 项 H5 交互 smoke。
- [ ] evidence commit、artifacts、hash 和人工 attestation 完整。
- [ ] 宝塔/Nginx history fallback、缓存、CORS、CSP 和 forwarded headers 已复核。
- [ ] 首个平台超管通过 Auth-bound bootstrap 创建并留有审计。
- [ ] 发布后逐 Worker、timer、Provider、日志和监控验收完成。
## 参考
- [根 README](../../README.md)
- [生产地基基线](../../docs/refactor/production-foundation-baseline-20260712.md)
- [Taro H5 部署](../../docs/refactor/taro-h5-deployment.md)
- [上线 evidence 模板](../../docs/refactor/production-launch-evidence.template.json)
- [Supabase self-hosting](https://supabase.com/docs/guides/self-hosting/docker)
- [Supabase HTTPS reverse proxy](https://supabase.com/docs/guides/self-hosting/self-hosted-proxy-https)