Files
gongxue-base/scripts/deploy/README.md

365 lines
16 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.

# tiku-supabase 云服务器部署说明
本文档用于把当前仓库部署到云服务器,并和已经解析好的域名打通。仓库内只保存安全模板,真实密钥、数据库密码、支付密钥、短信密钥、对象存储密钥和 Gitea 部署凭证必须放在服务器 `/etc/tiku-saas/` 下,不能提交到 Git。
## 域名规划
建议先按下面 6 个域名落地:
| 域名 | 用途 | 服务器转发 |
| --- | --- | --- |
| `api.tjszsb.com` | 自研业务 APITaro/H5/小程序统一调用 | `127.0.0.1:8787` |
| `app.tjszsb.com` | 学生 H5 题库端 | `/srv/tiku-saas/www/student` |
| `admin.tjszsb.com` | 租户后台 H5 | `/srv/tiku-saas/www/tenant-admin` |
| `console.tjszsb.com` | SaaS 平台后台 H5 | `/srv/tiku-saas/www/platform-admin` |
| `supabase.tjszsb.com` | Supabase API gateway/Auth/Storage/PostgREST | Supabase gateway通常是 `127.0.0.1:8000` |
| `studio.tjszsb.com` | Supabase Studio 运维后台 | 仅允许固定 IP/VPN/内网访问 |
`studio.tjszsb.com` 不建议裸露给公网。若必须临时开放,至少要加 Nginx IP 白名单、强密码、服务器防火墙和访问日志审计。
## 服务器目录
推荐使用固定目录,方便后续脚本和 AI 协作不漂移:
```text
/opt/tiku-saas/repo Git 工作副本
/opt/tiku-saas/bin 服务器本地执行脚本
/srv/tiku-saas/www/student 学生端 H5 静态文件
/srv/tiku-saas/www/tenant-admin 租户后台 H5 静态文件
/srv/tiku-saas/www/platform-admin 平台后台 H5 静态文件
/srv/tiku-saas/data 运行期数据
/srv/tiku-saas/backups 数据库和对象存储备份
/etc/tiku-saas/deploy.env 部署脚本配置,含 Gitea 只读部署凭证
/etc/tiku-saas/api.env API 生产环境变量
/etc/tiku-saas/worker.env worker 生产环境变量
/etc/tiku-saas/runtime-config/ 三套 H5 公开运行时配置
```
建议创建独立低权限用户:
```bash
sudo useradd --system --create-home --shell /bin/bash deploy
sudo mkdir -p /opt/tiku-saas/bin /srv/tiku-saas/www/student /srv/tiku-saas/www/tenant-admin /srv/tiku-saas/www/platform-admin /srv/tiku-saas/data /srv/tiku-saas/backups /etc/tiku-saas/runtime-config
sudo chown -R deploy:deploy /opt/tiku-saas /srv/tiku-saas
sudo chmod 750 /etc/tiku-saas
```
## 宝塔服务器实际落地记录
2026-07-01 首次上云使用的是 Alibaba Cloud Linux 3 + 宝塔面板环境。该服务器的 80/443 已由宝塔 Nginx 接管,主配置不在 `/etc/nginx`,而在:
```text
/www/server/nginx/conf/nginx.conf
/www/server/panel/vhost/nginx/*.conf
```
因此在这类服务器上不要执行 `systemctl start nginx`、不要写 `/etc/nginx/sites-available`,也不要覆盖宝塔生成的站点配置。宝塔 Nginx 的测试和重载命令是:
```bash
/www/server/nginx/sbin/nginx -t -c /www/server/nginx/conf/nginx.conf
/www/server/nginx/sbin/nginx -s reload
```
本次保留企业目录隔离方案:
```text
/opt/tiku-saas/repo Gitea 工作副本
/opt/tiku-saas/bin 服务器部署脚本
/srv/tiku-saas/www H5 发布产物
/srv/tiku-saas/data 运行数据
/srv/tiku-saas/backups 备份
/etc/tiku-saas 真实 env、Gitea token、运行时配置
```
宝塔新增站点时会拦截 `/srv` 作为网站根目录。不要因此把密钥、仓库或运行数据搬进 `/www`。只为 H5 静态站点创建 `/www/wwwroot` 下的软链接:
```bash
mkdir -p /www/wwwroot/tiku-saas
ln -sfn /srv/tiku-saas/www/student /www/wwwroot/tiku-saas/student
ln -sfn /srv/tiku-saas/www/tenant-admin /www/wwwroot/tiku-saas/tenant-admin
ln -sfn /srv/tiku-saas/www/platform-admin /www/wwwroot/tiku-saas/platform-admin
chown -h deploy:deploy /www/wwwroot/tiku-saas/student
chown -h deploy:deploy /www/wwwroot/tiku-saas/tenant-admin
chown -h deploy:deploy /www/wwwroot/tiku-saas/platform-admin
```
宝塔面板中新增三个纯静态站点:
| 域名 | 宝塔根目录 |
| --- | --- |
| `app.tjszsb.com` | `/www/wwwroot/tiku-saas/student` |
| `admin.tjszsb.com` | `/www/wwwroot/tiku-saas/tenant-admin` |
| `console.tjszsb.com` | `/www/wwwroot/tiku-saas/platform-admin` |
每个站点需要保留 H5 history fallback并禁止缓存公开运行时配置
```nginx
location / {
try_files $uri $uri/ /index.html;
}
location = /runtime-config.json {
add_header Cache-Control "no-store" always;
try_files $uri =404;
}
```
当前服务器已经验证过的基础环境:
```text
Node.js: v20.20.2,系统级安装在 /usr/bin/nodedeploy 用户可用
npm: 10.8.2deploy 用户可用
Docker: 26.1.3
Docker Compose: v2.27.0
Nginx: 宝塔 /www/server/nginx/sbin/nginx1.30.1
```
不要使用 root 的 nvm Node 路径作为生产运行时。若 `deploy` 用户看不到 Node/NPM应安装系统级 NodeSource Node.js 20
```bash
curl -fsSL https://rpm.nodesource.com/setup_20.x | bash -
dnf install -y nodejs
sudo -u deploy bash -lc 'command -v node; command -v npm; node -v; npm -v'
```
大陆服务器 `npm ci` 可能访问 npm 官方源超时。本次部署在 `/etc/tiku-saas/deploy.env` 中使用可配置 npm registry 和重试参数:
```bash
NPM_REGISTRY=https://registry.npmmirror.com
NPM_FETCH_RETRIES=5
NPM_FETCH_RETRY_MINTIMEOUT=20000
NPM_FETCH_RETRY_MAXTIMEOUT=120000
NPM_FETCH_TIMEOUT=300000
```
截至 2026-07-01 21:39`sudo -u deploy /opt/tiku-saas/bin/deploy.sh` 已完成:
- Gitea `main` 拉取到 `/opt/tiku-saas/repo`
- `npm ci` 安装依赖。
- `npm run security:repo`,结果 0 finding。
- `node scripts/production-launch-gate-test.js`,通过。
- API 和 worker 构建通过。
- 学生端、租户后台、平台后台三套 Taro H5 构建通过。
- H5 发布到 `/srv/tiku-saas/www/student``/srv/tiku-saas/www/tenant-admin``/srv/tiku-saas/www/platform-admin`
- 三个 `runtime-config.json` 已安装到各自 H5 根目录。
Taro H5 构建存在 webpack asset size warning这是前端包体优化事项不影响当前部署继续进行。后续可做拆包、按需加载和 KaTeX 字体裁剪。
## 服务器接管和故障恢复
2026-07-03 最新接管状态:
- Gitea `main` 已包含 PNVS 短信认证、后台登录修复、PNVS provider 配置脚本和旧题库视觉对齐版本,最新提交应至少是 `f8f8316`
- 服务器仓库仍在 `/opt/tiku-saas/repo`,归属用户应为 `deploy:deploy`
- 生产 API 已能启动,`https://api.tjszsb.com/api/tenant/resolve?host=app.tjszsb.com` 已返回 `master` 租户。
- Supabase self-hosted 运行在 `/opt/tiku-saas/supabase-project`Kong 通过 Nginx 暴露到 `https://supabase.tjszsb.com`
- 线上 H5 公开配置文件在 `/srv/tiku-saas/www/*/runtime-config.json`,密钥只允许放 `supabasePublishableKey` 这类公开 key。
- 短信登录推荐生产使用 `AUTH_SMS_PROVIDER=aliyun-pnvs`。阿里云 AccessKey/Secret 只写入 `app_private.tenant_secrets(secret_scope='sms', secret_key='aliyun-pnvs')`,不要写进 `/etc/tiku-saas/api.env` 或 H5 `runtime-config.json`
配置 PNVS provider 推荐用仓库脚本写入数据库,避免手写 SQL 时把密钥打进命令历史。生产环境建议临时关闭 shell history再用 `read -s` 输入 AccessKeySecret
```bash
cd /opt/tiku-saas/repo
set -a
source /etc/tiku-saas/api.env
set +a
set +o history
read -r -p 'Aliyun AccessKeyId: ' ALIYUN_ACCESS_KEY_ID
read -r -s -p 'Aliyun AccessKeySecret: ' ALIYUN_ACCESS_KEY_SECRET; echo
read -r -p 'PNVS SignName: ' ALIYUN_PNVS_SIGN_NAME
read -r -p 'PNVS TemplateCode: ' ALIYUN_PNVS_TEMPLATE_CODE
PNVS_TENANT_ID=00000000-0000-0000-0000-000000000001 \
ALIYUN_ACCESS_KEY_ID="$ALIYUN_ACCESS_KEY_ID" \
ALIYUN_ACCESS_KEY_SECRET="$ALIYUN_ACCESS_KEY_SECRET" \
ALIYUN_PNVS_SIGN_NAME="$ALIYUN_PNVS_SIGN_NAME" \
ALIYUN_PNVS_TEMPLATE_CODE="$ALIYUN_PNVS_TEMPLATE_CODE" \
npm run configure:aliyun-pnvs
unset ALIYUN_ACCESS_KEY_ID ALIYUN_ACCESS_KEY_SECRET ALIYUN_PNVS_SIGN_NAME ALIYUN_PNVS_TEMPLATE_CODE
set -o history
```
接管服务器时先做只读检查:
```bash
cd /opt/tiku-saas/repo
sudo -u deploy git status --short
sudo -u deploy git log -3 --oneline
sudo -u deploy git remote -v
systemctl status tiku-api --no-pager -l
systemctl status tiku-worker --no-pager -l
docker compose -f /opt/tiku-saas/supabase-project/docker-compose.yml ps
```
Gitea SSH 使用 `2222` 端口,推荐服务器 `deploy` 用户的 remote 使用完整 SSH URL
```bash
sudo -u deploy git -C /opt/tiku-saas/repo remote set-url origin ssh://git@git.gongxue100.com:2222/chenhaogxjy/tiku-supabase.git
sudo -u deploy ssh -o BatchMode=yes -T -p 2222 git@git.gongxue100.com
sudo -u deploy git -C /opt/tiku-saas/repo pull origin main
```
也可以写 `/home/deploy/.ssh/config`,但必须包含 `Port 2222`
```sshconfig
Host git.gongxue100.com
HostName git.gongxue100.com
User git
Port 2222
IdentityFile /home/deploy/.ssh/tiku_saas_deploy
IdentitiesOnly yes
```
如果 Taro 构建长时间没有输出,先判断它是真在编译还是已经卡死。构建中的 Taro/webpack 可能会有一段时间安静,但如果 `dist` 目录大小和最近修改时间 30 秒以上都不变,就按卡住处理:
```bash
ps -eo pid,ppid,user,stat,etime,%cpu,%mem,cmd | grep -E 'npm|node|taro|webpack' | grep -v grep
du -sh /opt/tiku-saas/repo/apps/taro/dist/h5-student
sleep 30
du -sh /opt/tiku-saas/repo/apps/taro/dist/h5-student
find /opt/tiku-saas/repo/apps/taro/dist/h5-student -type f -mmin -5 | head -20
```
确认卡住后,先在原终端 `Ctrl+C`。如果仍有残留 Taro 构建进程,再只结束这条构建链路,不要杀生产 API、Supabase 或其它 Node 服务:
```bash
ps -eo pid,ppid,user,stat,etime,%cpu,%mem,cmd | grep -E 'npm run build:taro|taro build --type h5|webpack' | grep -v grep
kill <pid>
sleep 3
kill -9 <pid>
```
然后清理单端产物并带 CI/内存参数重跑。先单独跑学生端,成功后再跑另外两端:
```bash
cd /opt/tiku-saas/repo
rm -rf apps/taro/dist/h5-student
sudo -u deploy env CI=1 NODE_OPTIONS="--max-old-space-size=4096" npm run build:taro:h5:student
rm -rf apps/taro/dist/h5-tenant-admin apps/taro/dist/h5-platform-admin
sudo -u deploy env CI=1 NODE_OPTIONS="--max-old-space-size=4096" npm run build:taro:h5:tenant
sudo -u deploy env CI=1 NODE_OPTIONS="--max-old-space-size=4096" npm run build:taro:h5:platform
```
三端构建成功后再发布:
```bash
sudo -u deploy /opt/tiku-saas/bin/deploy.sh
```
如果只是想接管代码开发,不要从服务器 `apps/taro/dist``/srv/tiku-saas/www` 拷贝产物;它们只是发布结果,源码以 Gitea `main` 为准。
## 首次安装
1. 安装基础组件Docker、Docker Compose、Node.js 20+、Nginx、Certbot、Git、rsync、flock。
2. 按 Supabase 官方 self-hosting Docker 文档部署 Supabase。生产必须启用 HTTPS 反向代理Supabase 官方也要求生产自托管部署使用 HTTPS。
3. 把本目录模板复制到服务器:
```bash
sudo mkdir -p /opt/tiku-saas/bin /etc/tiku-saas/runtime-config
sudo cp scripts/deploy/bin/deploy.sh /opt/tiku-saas/bin/deploy.sh
sudo cp scripts/deploy/env/deploy.env.example /etc/tiku-saas/deploy.env
sudo cp scripts/deploy/env/api.env.example /etc/tiku-saas/api.env
sudo cp scripts/deploy/env/worker.env.example /etc/tiku-saas/worker.env
sudo cp scripts/deploy/runtime-config/h5-student.runtime-config.example.json /etc/tiku-saas/runtime-config/h5-student.runtime-config.json
sudo cp scripts/deploy/runtime-config/h5-tenant-admin.runtime-config.example.json /etc/tiku-saas/runtime-config/h5-tenant-admin.runtime-config.json
sudo cp scripts/deploy/runtime-config/h5-platform-admin.runtime-config.example.json /etc/tiku-saas/runtime-config/h5-platform-admin.runtime-config.json
sudo chmod 700 /opt/tiku-saas/bin/deploy.sh
sudo chmod 600 /etc/tiku-saas/*.env /etc/tiku-saas/runtime-config/*.json
```
4. 编辑 `/etc/tiku-saas/*.env``/etc/tiku-saas/runtime-config/*.json`,填入真实生产配置。
5. 安装 systemd 服务:
```bash
sudo cp scripts/deploy/systemd/tiku-api.service /etc/systemd/system/tiku-api.service
sudo cp scripts/deploy/systemd/tiku-worker.service /etc/systemd/system/tiku-worker.service
sudo systemctl daemon-reload
sudo systemctl enable tiku-api tiku-worker
```
6. 安装 Nginx 配置:
```bash
sudo cp scripts/deploy/nginx/tjszsb.com.conf.example /etc/nginx/sites-available/tiku-saas.conf
sudo ln -s /etc/nginx/sites-available/tiku-saas.conf /etc/nginx/sites-enabled/tiku-saas.conf
sudo nginx -t
sudo systemctl reload nginx
```
7. 申请 HTTPS 证书:
```bash
sudo certbot --nginx -d api.tjszsb.com -d app.tjszsb.com -d admin.tjszsb.com -d console.tjszsb.com -d supabase.tjszsb.com -d studio.tjszsb.com
```
## Gitea 凭证
优先推荐 SSH deploy key。若暂时使用 Gitea token必须新建一个只读部署 token并写入 `/etc/tiku-saas/deploy.env`,不要把 token 写进脚本、Git remote、命令历史或 README。
已经在聊天、工单、截图里出现过的 token 都应当视为暴露,正式上云前请立即吊销并重新生成。
`deploy.sh` 会通过临时 `GIT_ASKPASS``git clone/fetch` 提供账号和 token避免 token 出现在 `git remote -v` 里。
## 更新发布
服务器上执行:
```bash
sudo -u deploy /opt/tiku-saas/bin/deploy.sh
```
脚本会执行:
1. 获取 `main` 最新代码。
2. `npm ci` 安装锁定依赖。
3. 运行仓库安全扫描和生产上线门禁测试。
4. 构建 API、worker、学生 H5、租户后台 H5、平台后台 H5。
5.`rsync --delete` 发布静态产物。
6. 复制服务器本地 `runtime-config.json` 到对应 Web 根目录。
7. 重启 `tiku-api``tiku-worker`
8. 输出当前发布的 Git commit。
## 上线前检查
每次正式放量前至少执行:
```bash
npm run security:repo
node scripts/production-launch-gate-test.js
node scripts/launch-persona-smoke-test.js
npm run readiness:production
```
接入真实生产配置后,还要在服务器上补跑:
```bash
npm run readiness:production:db
npm run smoke:auth:remote
SMS_SMOKE_API_BASE_URL=https://api.tjszsb.com SMS_SMOKE_TENANT_ID=00000000-0000-0000-0000-000000000001 SMS_SMOKE_PHONE=replace-with-real-phone SMS_SMOKE_ORIGIN=https://admin.tjszsb.com npm run smoke:sms-login:remote -- --json > docs/refactor/launch-artifacts/sms-pnvs-remote-smoke.json
npm run perf:api:local
```
如果要同时验证手机号绑定也走 PNVS provider verification准备一个未绑定测试手机号后执行
```bash
SMS_SMOKE_API_BASE_URL=https://api.tjszsb.com SMS_SMOKE_TENANT_ID=00000000-0000-0000-0000-000000000001 SMS_SMOKE_PHONE=replace-with-login-phone SMS_SMOKE_BIND_PHONE=replace-with-bind-phone SMS_SMOKE_ORIGIN=https://admin.tjszsb.com npm run smoke:sms-login:remote -- --json > docs/refactor/launch-artifacts/sms-pnvs-remote-smoke.json
```
压测必须在目标云服务器、目标数据库参数、目标对象存储和目标 Nginx 配置下重新计算,本地 Windows 压测数据只能作为开发参考。
## 关键安全要求
- 前端只保存 `supabasePublishableKey`,严禁出现 service role、数据库密码、短信密钥、支付私钥。
- 自研业务 API 默认只接受 Supabase JWT 或迁移期受控 app session不允许前端携带平台管理密钥。
- API、worker、Supabase、Nginx 日志要开启轮转,避免磁盘被日志打满。
- 数据库至少每日备份,正式放量前要完成一次恢复演练。
- 支付回调、短信回调、对象存储回调必须使用 HTTPS 域名,并在 API 层校验签名和租户归属。
- Supabase Studio 必须限制访问来源。
## 参考
- Supabase self-hosting Docker: https://supabase.com/docs/guides/self-hosting/docker
- Supabase reverse proxy and HTTPS: https://supabase.com/docs/guides/self-hosting/self-hosted-proxy-https
- Supabase Auth self-hosting config: https://supabase.com/docs/guides/self-hosting/auth/config
- Supabase self-hosted S3 storage: https://supabase.com/docs/guides/self-hosting/self-hosted-s3