# 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 key;Gitea 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 gate,launch 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 ``` 不要在验证期间继续移动候选分支。 ### 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='' \ 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='' \ 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 ``` 若目录已存在,只允许 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)