forked from wangziqi/gongxue-base
chore: add cloud deployment templates
This commit is contained in:
@@ -104,6 +104,7 @@ supabase/seed.sql 最小租户 seed
|
||||
scripts/import-pocketbase/ PocketBase schema/数据导入器和校验器
|
||||
scripts/smoke-seed.js 本地集成测试 seed 数据
|
||||
scripts/api-integration-test.js
|
||||
scripts/deploy/ 云服务器部署模板:Nginx、systemd、环境变量示例、发布脚本
|
||||
docs/refactor/ 重构架构、进度、治理文档
|
||||
docker-compose.api.yml API 容器化运行配置
|
||||
```
|
||||
@@ -180,6 +181,7 @@ apps/taro/deploy/h5-platform-admin.runtime-config.example.json
|
||||
|
||||
```text
|
||||
docs/refactor/taro-h5-deployment.md
|
||||
scripts/deploy/README.md
|
||||
```
|
||||
|
||||
学生端当前页面:
|
||||
@@ -431,6 +433,8 @@ npm run test:rls
|
||||
|
||||
接下来在新云服务器上建议按下面顺序推进。不要跳过证据门禁;它是后续给合作商交付 SaaS 时的安全底线。
|
||||
|
||||
当前 `tjszsb.com` 已建议按 6 个生产入口使用:`api.tjszsb.com` 反代业务 API,`app.tjszsb.com` 承载学生 H5,`admin.tjszsb.com` 承载租户后台,`console.tjszsb.com` 承载平台后台,`supabase.tjszsb.com` 反代 Supabase gateway/Auth/Storage/PostgREST,`studio.tjszsb.com` 仅限固定 IP/VPN 访问 Supabase Studio。可提交到仓库的部署模板在 `scripts/deploy/`;真实服务器文件建议放在 `/opt/tiku-saas/repo`、`/srv/tiku-saas/www/*` 和 `/etc/tiku-saas/*.env`。Gitea token、数据库密码、支付私钥、短信密钥、对象存储密钥都只允许放服务器本地,不允许写入 Git、前端运行时配置或部署脚本。
|
||||
|
||||
1. 准备服务器基础环境:安装 Docker、Node.js 20+、Supabase CLI、Nginx/Caddy、进程管理或容器编排工具;拉取本仓库 `main`,以 Gitea 最新提交为准。
|
||||
2. 启动 Supabase/PostgreSQL,执行全部 migrations 和最小 seed;确认 `DATABASE_URL` 指向云端数据库。
|
||||
3. 按 `docs/refactor/postgresql-4c16g-tuning.md` 应用 4 核 16G `shared-host` 起步参数,启用 `pg_stat_statements`,重启 PostgreSQL 后跑 `PG_TUNING_PROFILE=shared-host npm run perf:postgres:evidence -- --strict --json`。
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
- `packages/config`、`packages/db`、`packages/domain`:新系统共享基础包。
|
||||
- `scripts/import-pocketbase`:PocketBase schema/数据导入工具。
|
||||
- `docker-compose.api.yml`、`docker-compose.api.benchmark.yml`、`apps/api/Dockerfile`:本地 Docker API 和受限资源压测入口。
|
||||
- `scripts/deploy/README.md`:云服务器部署 runbook,覆盖 `tjszsb.com` 六域名规划、服务器目录、Gitea 安全部署、Nginx、systemd 和更新脚本。
|
||||
- `scripts/deploy/bin/deploy.sh`:服务器端发布脚本模板,负责拉取 Gitea、构建 API/worker/Taro H5、发布静态文件和重启服务;真实密钥只从 `/etc/tiku-saas/*.env` 读取。
|
||||
- `docs/refactor/architecture.md`:新重构目录边界和工程规范。
|
||||
- `docs/refactor/ai-development-guardrails.md`:后续 AI/开发者必须遵守的 Supabase-first 架构和安全守则。
|
||||
- `docs/refactor/content-import-contract.md`:题目、单词、知识手册导入契约,明确后端校验、旧格式转换和前端职责。
|
||||
|
||||
153
scripts/deploy/README.md
Normal file
153
scripts/deploy/README.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# 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
|
||||
```
|
||||
|
||||
## 首次安装
|
||||
|
||||
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
|
||||
npm run perf:api:local
|
||||
```
|
||||
|
||||
压测必须在目标云服务器、目标数据库参数、目标对象存储和目标 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
|
||||
140
scripts/deploy/bin/deploy.sh
Normal file
140
scripts/deploy/bin/deploy.sh
Normal file
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
CONFIG_FILE="${CONFIG_FILE:-/etc/tiku-saas/deploy.env}"
|
||||
|
||||
if [[ ! -r "$CONFIG_FILE" ]]; then
|
||||
echo "Missing deploy config: $CONFIG_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
: "${GIT_REPO:?GIT_REPO is required}"
|
||||
: "${GIT_BRANCH:=main}"
|
||||
: "${APP_ROOT:=/opt/tiku-saas}"
|
||||
: "${REPO_DIR:=$APP_ROOT/repo}"
|
||||
: "${WWW_ROOT:=/srv/tiku-saas/www}"
|
||||
: "${RUNTIME_CONFIG_DIR:=/etc/tiku-saas/runtime-config}"
|
||||
: "${RUN_SECURITY_CHECKS:=true}"
|
||||
: "${RUN_LAUNCH_GATE:=true}"
|
||||
: "${RESTART_SERVICES:=true}"
|
||||
|
||||
LOCK_FILE="${LOCK_FILE:-/tmp/tiku-saas-deploy.lock}"
|
||||
mkdir -p "$APP_ROOT" "$WWW_ROOT/student" "$WWW_ROOT/tenant-admin" "$WWW_ROOT/platform-admin"
|
||||
|
||||
exec 9>"$LOCK_FILE"
|
||||
if ! flock -n 9; then
|
||||
echo "Another deployment is already running." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log() {
|
||||
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "${ASKPASS_FILE:-}" && -f "$ASKPASS_FILE" ]]; then
|
||||
rm -f "$ASKPASS_FILE"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
prepare_git_auth() {
|
||||
if [[ -n "${GIT_SSH_COMMAND:-}" ]]; then
|
||||
export GIT_SSH_COMMAND
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -z "${GIT_USERNAME:-}" || -z "${GITEA_TOKEN:-}" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
ASKPASS_FILE="$(mktemp)"
|
||||
chmod 700 "$ASKPASS_FILE"
|
||||
cat > "$ASKPASS_FILE" <<'ASKPASS'
|
||||
#!/usr/bin/env bash
|
||||
case "$1" in
|
||||
*Username*) printf '%s\n' "$GIT_USERNAME" ;;
|
||||
*Password*) printf '%s\n' "$GITEA_TOKEN" ;;
|
||||
*) printf '\n' ;;
|
||||
esac
|
||||
ASKPASS
|
||||
export GIT_ASKPASS="$ASKPASS_FILE"
|
||||
export GIT_TERMINAL_PROMPT=0
|
||||
}
|
||||
|
||||
prepare_git_auth
|
||||
|
||||
if [[ ! -d "$REPO_DIR/.git" ]]; then
|
||||
log "Cloning repository..."
|
||||
git clone --branch "$GIT_BRANCH" "$GIT_REPO" "$REPO_DIR"
|
||||
fi
|
||||
|
||||
cd "$REPO_DIR"
|
||||
|
||||
log "Fetching $GIT_BRANCH..."
|
||||
git fetch origin "$GIT_BRANCH" --prune
|
||||
git checkout "$GIT_BRANCH"
|
||||
|
||||
if ! git diff --quiet || ! git diff --cached --quiet; then
|
||||
echo "Repository has local changes. Refusing to deploy until the server checkout is clean." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git merge --ff-only "origin/$GIT_BRANCH"
|
||||
|
||||
CURRENT_SHA="$(git rev-parse --short=12 HEAD)"
|
||||
log "Deploying commit $CURRENT_SHA"
|
||||
|
||||
log "Installing dependencies with npm ci..."
|
||||
npm ci
|
||||
|
||||
if [[ "$RUN_SECURITY_CHECKS" == "true" ]]; then
|
||||
log "Running repository security scan..."
|
||||
npm run security:repo
|
||||
fi
|
||||
|
||||
if [[ "$RUN_LAUNCH_GATE" == "true" ]]; then
|
||||
log "Running production launch gate test..."
|
||||
node scripts/production-launch-gate-test.js
|
||||
fi
|
||||
|
||||
log "Building API and worker..."
|
||||
npm run build:api
|
||||
npm run build:worker
|
||||
|
||||
log "Building H5 portals..."
|
||||
npm run build:taro:h5:student
|
||||
npm run build:taro:h5:tenant
|
||||
npm run build:taro:h5:platform
|
||||
|
||||
log "Publishing H5 static assets..."
|
||||
rsync -a --delete apps/taro/dist/h5-student/ "$WWW_ROOT/student/"
|
||||
rsync -a --delete apps/taro/dist/h5-tenant-admin/ "$WWW_ROOT/tenant-admin/"
|
||||
rsync -a --delete apps/taro/dist/h5-platform-admin/ "$WWW_ROOT/platform-admin/"
|
||||
|
||||
install_runtime_config() {
|
||||
local source_file="$1"
|
||||
local target_dir="$2"
|
||||
if [[ ! -r "$source_file" ]]; then
|
||||
echo "Missing runtime config: $source_file" >&2
|
||||
exit 1
|
||||
fi
|
||||
install -m 0644 "$source_file" "$target_dir/runtime-config.json"
|
||||
}
|
||||
|
||||
log "Installing H5 runtime config files..."
|
||||
install_runtime_config "$RUNTIME_CONFIG_DIR/h5-student.runtime-config.json" "$WWW_ROOT/student"
|
||||
install_runtime_config "$RUNTIME_CONFIG_DIR/h5-tenant-admin.runtime-config.json" "$WWW_ROOT/tenant-admin"
|
||||
install_runtime_config "$RUNTIME_CONFIG_DIR/h5-platform-admin.runtime-config.json" "$WWW_ROOT/platform-admin"
|
||||
|
||||
if [[ "$RESTART_SERVICES" == "true" ]]; then
|
||||
log "Restarting systemd services..."
|
||||
systemctl restart tiku-api.service
|
||||
systemctl restart tiku-worker.service
|
||||
systemctl --no-pager --full status tiku-api.service tiku-worker.service >/dev/null
|
||||
fi
|
||||
|
||||
log "Deployment finished: $CURRENT_SHA"
|
||||
57
scripts/deploy/env/api.env.example
vendored
Normal file
57
scripts/deploy/env/api.env.example
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
# Copy to /etc/tiku-saas/api.env and chmod 600.
|
||||
# This file is read by systemd. Do not commit the real file.
|
||||
|
||||
NODE_ENV=production
|
||||
PORT=8787
|
||||
|
||||
DATABASE_URL=postgresql://tiku_app:replace-with-password@127.0.0.1:5432/postgres
|
||||
DEFAULT_TENANT_SLUG=master
|
||||
CORS_ORIGIN=https://app.tjszsb.com,https://admin.tjszsb.com,https://console.tjszsb.com
|
||||
|
||||
ALLOW_LEGACY_AUTH_HEADERS=false
|
||||
ALLOW_PLATFORM_ADMIN_KEY=false
|
||||
|
||||
SUPABASE_URL=https://supabase.tjszsb.com
|
||||
SUPABASE_JWT_ISSUER=https://supabase.tjszsb.com/auth/v1
|
||||
AUTH_JWT_JWKS_URL=https://supabase.tjszsb.com/auth/v1/.well-known/jwks.json
|
||||
AUTH_SESSION_SECRET=replace-with-strong-random-session-secret
|
||||
AUTH_CODE_PEPPER=replace-with-strong-random-code-pepper
|
||||
|
||||
AUTH_SMS_PROVIDER=aliyun
|
||||
ALIYUN_SMS_ACCESS_KEY_ID=replace-with-access-key-id
|
||||
ALIYUN_SMS_ACCESS_KEY_SECRET=replace-with-access-key-secret
|
||||
ALIYUN_SMS_SIGN_NAME=replace-with-sms-sign
|
||||
ALIYUN_SMS_TEMPLATE_LOGIN=replace-with-template-code
|
||||
|
||||
WECHAT_MINIAPP_APP_ID=replace-with-miniapp-app-id
|
||||
WECHAT_MINIAPP_APP_SECRET=replace-with-miniapp-app-secret
|
||||
WECHAT_WEB_APP_ID=replace-with-web-app-id
|
||||
WECHAT_WEB_APP_SECRET=replace-with-web-app-secret
|
||||
QQ_CONNECT_APP_ID=replace-with-qq-app-id
|
||||
QQ_CONNECT_APP_KEY=replace-with-qq-app-key
|
||||
|
||||
PAYMENT_WECHAT_ENABLED=true
|
||||
WECHAT_PAY_MCH_ID=replace-with-merchant-id
|
||||
WECHAT_PAY_APP_ID=replace-with-pay-app-id
|
||||
WECHAT_PAY_API_V3_KEY=replace-with-api-v3-key
|
||||
WECHAT_PAY_PRIVATE_KEY=replace-with-private-key-path-or-kms-id
|
||||
WECHAT_PAY_NOTIFY_URL=https://api.tjszsb.com/api/commerce/webhooks/wechat-pay
|
||||
|
||||
PAYMENT_ALIPAY_ENABLED=true
|
||||
ALIPAY_APP_ID=replace-with-alipay-app-id
|
||||
ALIPAY_APP_PRIVATE_KEY=replace-with-private-key-path-or-kms-id
|
||||
ALIPAY_PUBLIC_KEY=replace-with-alipay-public-key
|
||||
ALIPAY_NOTIFY_URL=https://api.tjszsb.com/api/commerce/webhooks/alipay
|
||||
|
||||
STORAGE_DEFAULT_PROVIDER=aliyun_oss
|
||||
ALIYUN_OSS_REGION=oss-cn-beijing
|
||||
ALIYUN_OSS_ENDPOINT=https://oss-cn-beijing.aliyuncs.com
|
||||
ALIYUN_OSS_BUCKET=replace-with-bucket
|
||||
ALIYUN_OSS_ACCESS_KEY_ID=replace-with-access-key-id
|
||||
ALIYUN_OSS_ACCESS_KEY_SECRET=replace-with-access-key-secret
|
||||
ASSET_SIGNING_SECRET=replace-with-strong-random-asset-secret
|
||||
|
||||
WORKER_ASSET_SECURITY_SCANNER=metadata_rules,http
|
||||
WORKER_ASSET_SECURITY_SCAN_FAIL_OPEN=false
|
||||
ASSET_SECURITY_SCAN_ENDPOINT=https://replace-with-security-scanner.example.com/scan
|
||||
ASSET_SECURITY_SCAN_TOKEN=replace-with-scanner-token
|
||||
18
scripts/deploy/env/deploy.env.example
vendored
Normal file
18
scripts/deploy/env/deploy.env.example
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# Copy to /etc/tiku-saas/deploy.env and chmod 600.
|
||||
# Keep real credentials on the server only.
|
||||
|
||||
GIT_REPO=https://git.gongxue100.com/chenhaogxjy/tiku-supabase.git
|
||||
GIT_BRANCH=main
|
||||
|
||||
# Prefer SSH deploy key. If using HTTPS token, create a rotated read-only deploy token.
|
||||
GIT_USERNAME=replace-with-readonly-deploy-user
|
||||
GITEA_TOKEN=replace-with-rotated-readonly-token
|
||||
|
||||
APP_ROOT=/opt/tiku-saas
|
||||
REPO_DIR=/opt/tiku-saas/repo
|
||||
WWW_ROOT=/srv/tiku-saas/www
|
||||
RUNTIME_CONFIG_DIR=/etc/tiku-saas/runtime-config
|
||||
|
||||
RUN_SECURITY_CHECKS=true
|
||||
RUN_LAUNCH_GATE=true
|
||||
RESTART_SERVICES=true
|
||||
32
scripts/deploy/env/worker.env.example
vendored
Normal file
32
scripts/deploy/env/worker.env.example
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# Copy to /etc/tiku-saas/worker.env and chmod 600.
|
||||
# This file is read by systemd. Do not commit the real file.
|
||||
|
||||
NODE_ENV=production
|
||||
DATABASE_URL=postgresql://tiku_app:replace-with-password@127.0.0.1:5432/postgres
|
||||
|
||||
SUPABASE_URL=https://supabase.tjszsb.com
|
||||
|
||||
STORAGE_DEFAULT_PROVIDER=aliyun_oss
|
||||
ALIYUN_OSS_REGION=oss-cn-beijing
|
||||
ALIYUN_OSS_ENDPOINT=https://oss-cn-beijing.aliyuncs.com
|
||||
ALIYUN_OSS_BUCKET=replace-with-bucket
|
||||
ALIYUN_OSS_ACCESS_KEY_ID=replace-with-access-key-id
|
||||
ALIYUN_OSS_ACCESS_KEY_SECRET=replace-with-access-key-secret
|
||||
ASSET_SIGNING_SECRET=replace-with-strong-random-asset-secret
|
||||
|
||||
WORKER_ASSET_SECURITY_SCANNER=metadata_rules,http
|
||||
WORKER_ASSET_SECURITY_SCAN_FAIL_OPEN=false
|
||||
ASSET_SECURITY_SCAN_ENDPOINT=https://replace-with-security-scanner.example.com/scan
|
||||
ASSET_SECURITY_SCAN_TOKEN=replace-with-scanner-token
|
||||
|
||||
WECHAT_PAY_MCH_ID=replace-with-merchant-id
|
||||
WECHAT_PAY_APP_ID=replace-with-pay-app-id
|
||||
WECHAT_PAY_API_V3_KEY=replace-with-api-v3-key
|
||||
WECHAT_PAY_PRIVATE_KEY=replace-with-private-key-path-or-kms-id
|
||||
|
||||
ALIPAY_APP_ID=replace-with-alipay-app-id
|
||||
ALIPAY_APP_PRIVATE_KEY=replace-with-private-key-path-or-kms-id
|
||||
ALIPAY_PUBLIC_KEY=replace-with-alipay-public-key
|
||||
|
||||
CRM_WEBHOOK_TIMEOUT_MS=5000
|
||||
WORKER_POLL_INTERVAL_MS=5000
|
||||
149
scripts/deploy/nginx/tjszsb.com.conf.example
Normal file
149
scripts/deploy/nginx/tjszsb.com.conf.example
Normal file
@@ -0,0 +1,149 @@
|
||||
# Copy to /etc/nginx/sites-available/tiku-saas.conf, review, then symlink into sites-enabled.
|
||||
# Certbot can inject ssl_certificate directives after the HTTP blocks are reachable.
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name app.tjszsb.com;
|
||||
root /srv/tiku-saas/www/student;
|
||||
index index.html;
|
||||
|
||||
add_header X-Frame-Options SAMEORIGIN always;
|
||||
add_header X-Content-Type-Options nosniff always;
|
||||
add_header Referrer-Policy strict-origin-when-cross-origin always;
|
||||
|
||||
location = /runtime-config.json {
|
||||
add_header Cache-Control "no-store" always;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location ~* \.(?:js|css|png|jpg|jpeg|gif|webp|svg|ico|woff2?)$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name admin.tjszsb.com;
|
||||
root /srv/tiku-saas/www/tenant-admin;
|
||||
index index.html;
|
||||
|
||||
add_header X-Frame-Options SAMEORIGIN always;
|
||||
add_header X-Content-Type-Options nosniff always;
|
||||
add_header Referrer-Policy strict-origin-when-cross-origin always;
|
||||
|
||||
location = /runtime-config.json {
|
||||
add_header Cache-Control "no-store" always;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location ~* \.(?:js|css|png|jpg|jpeg|gif|webp|svg|ico|woff2?)$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name console.tjszsb.com;
|
||||
root /srv/tiku-saas/www/platform-admin;
|
||||
index index.html;
|
||||
|
||||
add_header X-Frame-Options SAMEORIGIN always;
|
||||
add_header X-Content-Type-Options nosniff always;
|
||||
add_header Referrer-Policy strict-origin-when-cross-origin always;
|
||||
|
||||
location = /runtime-config.json {
|
||||
add_header Cache-Control "no-store" always;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location ~* \.(?:js|css|png|jpg|jpeg|gif|webp|svg|ico|woff2?)$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name api.tjszsb.com;
|
||||
|
||||
client_max_body_size 50m;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8787;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_read_timeout 120s;
|
||||
proxy_send_timeout 120s;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name supabase.tjszsb.com;
|
||||
|
||||
client_max_body_size 100m;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name studio.tjszsb.com;
|
||||
|
||||
# Replace these placeholders with office/VPN/server bastion IPs before enabling.
|
||||
allow 127.0.0.1;
|
||||
# allow 203.0.113.10;
|
||||
deny all;
|
||||
|
||||
location / {
|
||||
# Supabase self-hosted Studio port depends on your compose file. Common values are 3000 or 54323.
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"portal": "platform-admin",
|
||||
"apiBaseUrl": "https://api.tjszsb.com",
|
||||
"supabaseUrl": "https://supabase.tjszsb.com",
|
||||
"supabasePublishableKey": "replace-with-supabase-publishable-key",
|
||||
"tenantCode": "master"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"portal": "student",
|
||||
"apiBaseUrl": "https://api.tjszsb.com",
|
||||
"supabaseUrl": "https://supabase.tjszsb.com",
|
||||
"supabasePublishableKey": "replace-with-supabase-publishable-key",
|
||||
"tenantCode": "master"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"portal": "tenant-admin",
|
||||
"apiBaseUrl": "https://api.tjszsb.com",
|
||||
"supabaseUrl": "https://supabase.tjszsb.com",
|
||||
"supabasePublishableKey": "replace-with-supabase-publishable-key",
|
||||
"tenantCode": "master"
|
||||
}
|
||||
28
scripts/deploy/systemd/tiku-api.service
Normal file
28
scripts/deploy/systemd/tiku-api.service
Normal file
@@ -0,0 +1,28 @@
|
||||
[Unit]
|
||||
Description=tiku-supabase API
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=deploy
|
||||
Group=deploy
|
||||
WorkingDirectory=/opt/tiku-saas/repo
|
||||
EnvironmentFile=/etc/tiku-saas/api.env
|
||||
ExecStart=/usr/bin/node /opt/tiku-saas/repo/apps/api/dist/apps/api/src/server.js
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
KillSignal=SIGTERM
|
||||
TimeoutStopSec=30
|
||||
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
ReadWritePaths=/srv/tiku-saas /opt/tiku-saas/repo
|
||||
CapabilityBoundingSet=
|
||||
AmbientCapabilities=
|
||||
LockPersonality=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
28
scripts/deploy/systemd/tiku-worker.service
Normal file
28
scripts/deploy/systemd/tiku-worker.service
Normal file
@@ -0,0 +1,28 @@
|
||||
[Unit]
|
||||
Description=tiku-supabase worker
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=deploy
|
||||
Group=deploy
|
||||
WorkingDirectory=/opt/tiku-saas/repo
|
||||
EnvironmentFile=/etc/tiku-saas/worker.env
|
||||
ExecStart=/usr/bin/node /opt/tiku-saas/repo/apps/worker/dist/apps/worker/src/index.js --loop
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
KillSignal=SIGTERM
|
||||
TimeoutStopSec=60
|
||||
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
ReadWritePaths=/srv/tiku-saas /opt/tiku-saas/repo
|
||||
CapabilityBoundingSet=
|
||||
AmbientCapabilities=
|
||||
LockPersonality=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user