From cc9b5526848fc834d03318b598133c4591991e7b Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 1 Jul 2026 20:09:02 +0800 Subject: [PATCH] chore: add cloud deployment templates --- README.md | 4 + docs/refactor/README.md | 2 + scripts/deploy/README.md | 153 ++++++++++++++++++ scripts/deploy/bin/deploy.sh | 140 ++++++++++++++++ scripts/deploy/env/api.env.example | 57 +++++++ scripts/deploy/env/deploy.env.example | 18 +++ scripts/deploy/env/worker.env.example | 32 ++++ scripts/deploy/nginx/tjszsb.com.conf.example | 149 +++++++++++++++++ ...platform-admin.runtime-config.example.json | 7 + .../h5-student.runtime-config.example.json | 7 + ...5-tenant-admin.runtime-config.example.json | 7 + scripts/deploy/systemd/tiku-api.service | 28 ++++ scripts/deploy/systemd/tiku-worker.service | 28 ++++ 13 files changed, 632 insertions(+) create mode 100644 scripts/deploy/README.md create mode 100644 scripts/deploy/bin/deploy.sh create mode 100644 scripts/deploy/env/api.env.example create mode 100644 scripts/deploy/env/deploy.env.example create mode 100644 scripts/deploy/env/worker.env.example create mode 100644 scripts/deploy/nginx/tjszsb.com.conf.example create mode 100644 scripts/deploy/runtime-config/h5-platform-admin.runtime-config.example.json create mode 100644 scripts/deploy/runtime-config/h5-student.runtime-config.example.json create mode 100644 scripts/deploy/runtime-config/h5-tenant-admin.runtime-config.example.json create mode 100644 scripts/deploy/systemd/tiku-api.service create mode 100644 scripts/deploy/systemd/tiku-worker.service diff --git a/README.md b/README.md index 432a6871..b9059e87 100644 --- a/README.md +++ b/README.md @@ -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`。 diff --git a/docs/refactor/README.md b/docs/refactor/README.md index 3ee56c06..262b5169 100644 --- a/docs/refactor/README.md +++ b/docs/refactor/README.md @@ -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`:题目、单词、知识手册导入契约,明确后端校验、旧格式转换和前端职责。 diff --git a/scripts/deploy/README.md b/scripts/deploy/README.md new file mode 100644 index 00000000..b71e4d14 --- /dev/null +++ b/scripts/deploy/README.md @@ -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 diff --git a/scripts/deploy/bin/deploy.sh b/scripts/deploy/bin/deploy.sh new file mode 100644 index 00000000..dcc8d8a0 --- /dev/null +++ b/scripts/deploy/bin/deploy.sh @@ -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" diff --git a/scripts/deploy/env/api.env.example b/scripts/deploy/env/api.env.example new file mode 100644 index 00000000..3fee4de3 --- /dev/null +++ b/scripts/deploy/env/api.env.example @@ -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 diff --git a/scripts/deploy/env/deploy.env.example b/scripts/deploy/env/deploy.env.example new file mode 100644 index 00000000..302e0497 --- /dev/null +++ b/scripts/deploy/env/deploy.env.example @@ -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 diff --git a/scripts/deploy/env/worker.env.example b/scripts/deploy/env/worker.env.example new file mode 100644 index 00000000..3092b5d0 --- /dev/null +++ b/scripts/deploy/env/worker.env.example @@ -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 diff --git a/scripts/deploy/nginx/tjszsb.com.conf.example b/scripts/deploy/nginx/tjszsb.com.conf.example new file mode 100644 index 00000000..21d47d9f --- /dev/null +++ b/scripts/deploy/nginx/tjszsb.com.conf.example @@ -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; + } +} diff --git a/scripts/deploy/runtime-config/h5-platform-admin.runtime-config.example.json b/scripts/deploy/runtime-config/h5-platform-admin.runtime-config.example.json new file mode 100644 index 00000000..3467a8bf --- /dev/null +++ b/scripts/deploy/runtime-config/h5-platform-admin.runtime-config.example.json @@ -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" +} diff --git a/scripts/deploy/runtime-config/h5-student.runtime-config.example.json b/scripts/deploy/runtime-config/h5-student.runtime-config.example.json new file mode 100644 index 00000000..91d66dc5 --- /dev/null +++ b/scripts/deploy/runtime-config/h5-student.runtime-config.example.json @@ -0,0 +1,7 @@ +{ + "portal": "student", + "apiBaseUrl": "https://api.tjszsb.com", + "supabaseUrl": "https://supabase.tjszsb.com", + "supabasePublishableKey": "replace-with-supabase-publishable-key", + "tenantCode": "master" +} diff --git a/scripts/deploy/runtime-config/h5-tenant-admin.runtime-config.example.json b/scripts/deploy/runtime-config/h5-tenant-admin.runtime-config.example.json new file mode 100644 index 00000000..b33210ba --- /dev/null +++ b/scripts/deploy/runtime-config/h5-tenant-admin.runtime-config.example.json @@ -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" +} diff --git a/scripts/deploy/systemd/tiku-api.service b/scripts/deploy/systemd/tiku-api.service new file mode 100644 index 00000000..5aa95598 --- /dev/null +++ b/scripts/deploy/systemd/tiku-api.service @@ -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 diff --git a/scripts/deploy/systemd/tiku-worker.service b/scripts/deploy/systemd/tiku-worker.service new file mode 100644 index 00000000..ab520920 --- /dev/null +++ b/scripts/deploy/systemd/tiku-worker.service @@ -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