forked from wangziqi/gongxue-base
365 lines
16 KiB
Markdown
365 lines
16 KiB
Markdown
# tiku-supabase 云服务器部署说明
|
||
|
||
本文档用于把当前仓库部署到云服务器,并和已经解析好的域名打通。仓库内只保存安全模板,真实密钥、数据库密码、支付密钥、短信密钥、对象存储密钥和 Gitea 部署凭证必须放在服务器 `/etc/tiku-saas/` 下,不能提交到 Git。
|
||
|
||
## 域名规划
|
||
|
||
建议先按下面 6 个域名落地:
|
||
|
||
| 域名 | 用途 | 服务器转发 |
|
||
| --- | --- | --- |
|
||
| `api.tjszsb.com` | 自研业务 API,Taro/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/node,deploy 用户可用
|
||
npm: 10.8.2,deploy 用户可用
|
||
Docker: 26.1.3
|
||
Docker Compose: v2.27.0
|
||
Nginx: 宝塔 /www/server/nginx/sbin/nginx,1.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
|