diff --git a/deploy/env/api-server.env.example b/deploy/env/api-server.env.example index 25016c80..bd69bd54 100644 --- a/deploy/env/api-server.env.example +++ b/deploy/env/api-server.env.example @@ -22,7 +22,7 @@ AUTH_REFRESH_COOKIE_SECURE=true GENARRATIVE_AUTH_STORE_PATH=/var/lib/genarrative/auth/auth-store.json GENARRATIVE_DEV_PASSWORD_ENTRY_AUTO_REGISTER_ENABLED=false -GENARRATIVE_SPACETIME_SERVER_URL=http://127.0.0.1:3000 +GENARRATIVE_SPACETIME_SERVER_URL=http://127.0.0.1:3101 GENARRATIVE_SPACETIME_DATABASE=genarrative-prod GENARRATIVE_SPACETIME_TOKEN= GENARRATIVE_SPACETIME_POOL_SIZE=8 diff --git a/deploy/nginx/genarrative-dev-http.conf b/deploy/nginx/genarrative-dev-http.conf index dd7e27ed..340ebcba 100644 --- a/deploy/nginx/genarrative-dev-http.conf +++ b/deploy/nginx/genarrative-dev-http.conf @@ -55,7 +55,7 @@ server { # 仅开放前端 SpacetimeDB SDK 运行所需的最小公网路由。 location ~ ^/v1/database/[^/]+/subscribe$ { - proxy_pass http://127.0.0.1:3000; + proxy_pass http://127.0.0.1:3101; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; @@ -64,7 +64,7 @@ server { } location ^~ /v1/identity { - proxy_pass http://127.0.0.1:3000; + proxy_pass http://127.0.0.1:3101; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; diff --git a/deploy/nginx/genarrative.conf b/deploy/nginx/genarrative.conf index ca2b388e..828339e5 100644 --- a/deploy/nginx/genarrative.conf +++ b/deploy/nginx/genarrative.conf @@ -69,7 +69,7 @@ server { # SpacetimeDB 只开放 TypeScript SDK 运行所需的最小公网路由。 location ~ ^/v1/database/[^/]+/subscribe$ { - proxy_pass http://127.0.0.1:3000; + proxy_pass http://127.0.0.1:3101; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; @@ -78,7 +78,7 @@ server { } location ^~ /v1/identity { - proxy_pass http://127.0.0.1:3000; + proxy_pass http://127.0.0.1:3101; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; diff --git a/deploy/systemd/spacetimedb.service b/deploy/systemd/spacetimedb.service index 16b236f9..93fdbede 100644 --- a/deploy/systemd/spacetimedb.service +++ b/deploy/systemd/spacetimedb.service @@ -8,7 +8,7 @@ Type=simple User=spacetimedb Group=spacetimedb WorkingDirectory=/stdb -ExecStart=/stdb/spacetime --root-dir=/stdb start --listen-addr=127.0.0.1:3000 +ExecStart=/stdb/spacetime --root-dir=/stdb start --listen-addr=127.0.0.1:3101 Restart=always RestartSec=5 LimitNOFILE=1048576 diff --git a/docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md b/docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md index aeb13536..c468db7d 100644 --- a/docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md +++ b/docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md @@ -44,7 +44,7 @@ ## 生产架构 - Nginx 作为唯一公网入口,负责 HTTPS、静态站点、后台静态页面、维护页与 `/admin/api/` 反向代理。 -- SpacetimeDB 作为系统服务运行,监听 `127.0.0.1:3000`,数据根目录为 `/stdb`。 +- SpacetimeDB 作为系统服务运行,监听 `127.0.0.1:3101`,数据根目录为 `/stdb`。`3000` 保留给部署机本机 Git/Web 服务,禁止再让 SpacetimeDB 占用该端口。 - Rust `api-server` 作为系统服务运行,监听 `127.0.0.1:8082`,只被 Nginx 的 `/admin/api/` 访问。 - 主站与后台前端构建为静态文件,发布到服务器固定目录,不放入 Jenkins 目录,也不跟随 Docker 镜像。 - 除网站静态发布外,`api-server` 发布、SpacetimeDB 模块发布、数据库导入、服务器配置变更都必须先进入维护模式。 @@ -77,7 +77,7 @@ - 服务名:`spacetimedb.service` - 运行用户:`spacetimedb` - 工作目录:`/stdb` -- 启动命令:`/stdb/spacetime --root-dir=/stdb start --listen-addr=127.0.0.1:3000` +- 启动命令:`/stdb/spacetime --root-dir=/stdb start --listen-addr=127.0.0.1:3101` - 对外暴露:默认不直接暴露公网端口。 该方案与 SpacetimeDB 官方自托管文档一致:使用 Ubuntu、专用用户、`/stdb` 根目录、systemd 服务和 Nginx。 @@ -210,6 +210,7 @@ Jenkins controller 与 Linux agent 看到的 Git 服务地址不同,必须拆 - Jenkins Job 的 `Pipeline script from SCM` 由 controller 执行,SCM URL 使用 controller 可访问的公网地址:`http://82.157.175.59:3000/GenarrativeAI/Genarrative.git`。 - Jenkinsfile 内部的源码、脚本 checkout 在 Linux agent 上执行,`GIT_REMOTE_URL` 使用 agent 本机可访问地址:`http://127.0.0.1:3000/GenarrativeAI/Genarrative.git`。 +- 这里的 `3000` 是 Git/Web 服务端口,不是 SpacetimeDB 端口;生产 SpacetimeDB 固定使用 `http://127.0.0.1:3101`,避免流水线部署时与本机 Git 服务抢端口。 因此生产 Jenkinsfile 不使用 `checkout scm` 作为构建源码入口,而是显式 `checkout([$class: 'GitSCM', userRemoteConfigs: [[url: "${GIT_REMOTE_URL}"]], ...])`。后续 `scripts/jenkins-checkout-source.sh` 会继续把 `origin` 设置为 `GIT_REMOTE_URL`,并按 `SOURCE_BRANCH` / `COMMIT_HASH` 拉取和校验目标提交。 @@ -474,7 +475,7 @@ WASM_SOURCE="${CARGO_TARGET_DIR}/wasm32-unknown-unknown/release/spacetime_module - 通过 `DEPLOY_TARGET` 选择逻辑导出目标;`development` 映射到 `linux && genarrative-build`,`release` 映射到 `linux && genarrative-release-deploy`。 - `release` 导出必须勾选 `CONFIRM_RELEASE_DEPLOY_AGENT`,避免当前开发/构建/开发部署 agent 冒充 release 部署机。 - 进入维护模式,避免导出期间继续写入。 -- 从目标机器本机 SpacetimeDB 导出指定数据库数据,默认连接 `SPACETIME_SERVER=local`,自托管 `root-dir` 默认 `/stdb`。 +- 从目标机器本机 SpacetimeDB 导出指定数据库数据,默认连接 `SPACETIME_SERVER_URL=http://127.0.0.1:3101`,自托管 `root-dir` 默认 `/stdb`。 - 产物归档到 Jenkins,并可额外保存到 `SERVER_BACKUP_DIRECTORY`。 - 敏感 token 与 bootstrap secret 只通过 Jenkins Secret Text 凭据 ID 注入,不作为明文 Job 参数。 - 成功后解除维护模式。 diff --git a/jenkins/Jenkinsfile.production-database-export b/jenkins/Jenkinsfile.production-database-export index cda064f7..3abdef58 100644 --- a/jenkins/Jenkinsfile.production-database-export +++ b/jenkins/Jenkinsfile.production-database-export @@ -19,7 +19,7 @@ pipeline { string(name: 'NOTIFICATION_EMAILS', defaultValue: '', description: '本次运行追加通知邮箱;会与 Jenkins Secret Text 凭据 genarrative-notification-emails 合并发送') string(name: 'DATABASE', defaultValue: 'genarrative-prod', description: 'SpacetimeDB database') string(name: 'SPACETIME_SERVER', defaultValue: 'local', description: 'SpacetimeDB server alias') - string(name: 'SPACETIME_SERVER_URL', defaultValue: '', description: '显式 SpacetimeDB server URL,填写后优先于 SPACETIME_SERVER') + string(name: 'SPACETIME_SERVER_URL', defaultValue: 'http://127.0.0.1:3101', description: '显式 SpacetimeDB server URL,填写后优先于 SPACETIME_SERVER') string(name: 'SPACETIME_ROOT_DIR', defaultValue: '/stdb', description: 'spacetime CLI root-dir;release 自托管默认 /stdb') string(name: 'INCLUDE_TABLES', defaultValue: '', description: '可选,逗号分隔的表名白名单') string(name: 'WORKSPACE_EXPORT_DIRECTORY', defaultValue: 'database-exports', description: 'Jenkins workspace 内的导出目录,用于归档') diff --git a/jenkins/Jenkinsfile.production-database-import b/jenkins/Jenkinsfile.production-database-import index d866f5ec..6c7e8d9d 100644 --- a/jenkins/Jenkinsfile.production-database-import +++ b/jenkins/Jenkinsfile.production-database-import @@ -19,7 +19,7 @@ pipeline { string(name: 'NOTIFICATION_EMAILS', defaultValue: '', description: '本次运行追加通知邮箱;会与 Jenkins Secret Text 凭据 genarrative-notification-emails 合并发送') string(name: 'DATABASE', defaultValue: 'genarrative-prod', description: 'SpacetimeDB database') string(name: 'SPACETIME_SERVER', defaultValue: 'local', description: 'SpacetimeDB server alias') - string(name: 'SPACETIME_SERVER_URL', defaultValue: '', description: '显式 SpacetimeDB server URL,填写后优先于 SPACETIME_SERVER') + string(name: 'SPACETIME_SERVER_URL', defaultValue: 'http://127.0.0.1:3101', description: '显式 SpacetimeDB server URL,填写后优先于 SPACETIME_SERVER') string(name: 'SPACETIME_ROOT_DIR', defaultValue: '/stdb', description: 'spacetime CLI root-dir;release 自托管默认 /stdb') choice(name: 'INPUT_SOURCE', choices: ['pipeline_archive', 'manual_upload'], description: '导入数据源;pipeline_archive 从导出流水线归档获取,manual_upload 使用本次构建手动上传文件') string(name: 'INPUT_FILE', defaultValue: '', description: 'pipeline_archive 模式可选;留空时使用导出流水线默认归档路径 database-exports/spacetime-migration-<导出构建号>.json') diff --git a/jenkins/Jenkinsfile.production-full-build-and-deploy b/jenkins/Jenkinsfile.production-full-build-and-deploy index a8139dc4..1a6d7ccd 100644 --- a/jenkins/Jenkinsfile.production-full-build-and-deploy +++ b/jenkins/Jenkinsfile.production-full-build-and-deploy @@ -30,6 +30,7 @@ pipeline { choice(name: 'DEPLOY_TARGET', choices: ['development', 'release'], description: '逻辑部署目标;development 使用当前 Linux 开发/构建/开发部署 agent') booleanParam(name: 'CONFIRM_RELEASE_DEPLOY_AGENT', defaultValue: false, description: '确认 release 目标已有独立 release 部署 agent;当前 Linux 开发/构建/开发部署 agent 不可冒充 release 部署机') string(name: 'DATABASE', defaultValue: 'genarrative-prod', description: '生产 SpacetimeDB database') + string(name: 'SPACETIME_SERVER_URL', defaultValue: 'http://127.0.0.1:3101', description: 'Stdb 发布目标 URL;默认避开本机 Git/Web 使用的 3000 端口') } stages { @@ -134,6 +135,7 @@ pipeline { string(name: 'BUILD_VERSION', value: env.EFFECTIVE_BUILD_VERSION), string(name: 'NOTIFICATION_EMAILS', value: params.NOTIFICATION_EMAILS ?: ''), string(name: 'DATABASE', value: params.DATABASE), + string(name: 'SPACETIME_SERVER_URL', value: params.SPACETIME_SERVER_URL ?: ''), string(name: 'DEPLOY_TARGET', value: params.DEPLOY_TARGET), booleanParam(name: 'CONFIRM_RELEASE_DEPLOY_AGENT', value: params.CONFIRM_RELEASE_DEPLOY_AGENT), string(name: 'BUILD_JOB_NAME', value: params.STDB_BUILD_JOB_NAME), diff --git a/jenkins/Jenkinsfile.production-server-provision b/jenkins/Jenkinsfile.production-server-provision index 1ea7c214..94d7cfb7 100644 --- a/jenkins/Jenkinsfile.production-server-provision +++ b/jenkins/Jenkinsfile.production-server-provision @@ -185,7 +185,7 @@ pipeline { render_api_env_example() { sed \ -e "s|^GENARRATIVE_API_PORT=.*|GENARRATIVE_API_PORT=${API_PORT}|" \ - -e "s|^GENARRATIVE_SPACETIME_SERVER_URL=.*|GENARRATIVE_SPACETIME_SERVER_URL=http://127.0.0.1:3000|" \ + -e "s|^GENARRATIVE_SPACETIME_SERVER_URL=.*|GENARRATIVE_SPACETIME_SERVER_URL=http://127.0.0.1:3101|" \ deploy/env/api-server.env.example } diff --git a/jenkins/Jenkinsfile.production-stdb-module-publish b/jenkins/Jenkinsfile.production-stdb-module-publish index da51c059..bd00bb5f 100644 --- a/jenkins/Jenkinsfile.production-stdb-module-publish +++ b/jenkins/Jenkinsfile.production-stdb-module-publish @@ -22,6 +22,7 @@ pipeline { string(name: 'BUILD_NUMBER_TO_DEPLOY', defaultValue: '', description: '要复制归档产物的上游构建号') string(name: 'DATABASE', defaultValue: 'genarrative-prod', description: '生产 SpacetimeDB database') string(name: 'SPACETIME_SERVER', defaultValue: 'local', description: 'SpacetimeDB server alias') + string(name: 'SPACETIME_SERVER_URL', defaultValue: 'http://127.0.0.1:3101', description: '显式 SpacetimeDB server URL,填写后优先于 SPACETIME_SERVER') booleanParam(name: 'CLEAR_DATABASE', defaultValue: false, description: '是否清空数据库后发布') } @@ -47,6 +48,17 @@ pipeline { if (!params.DATABASE?.trim()) { error('DATABASE 不能为空。') } + if (!params.SPACETIME_SERVER?.trim() && !params.SPACETIME_SERVER_URL?.trim()) { + error('SPACETIME_SERVER 与 SPACETIME_SERVER_URL 不能同时为空。') + } + def spacetimeServerUrl = params.SPACETIME_SERVER_URL?.trim() + if (spacetimeServerUrl && !(spacetimeServerUrl ==~ /^https?:\/\/[A-Za-z0-9._:-]+$/)) { + error("SPACETIME_SERVER_URL 只能是 http(s) URL,且不能包含路径或 shell 特殊字符: ${spacetimeServerUrl}") + } + def spacetimeServer = params.SPACETIME_SERVER?.trim() + if (!spacetimeServerUrl && spacetimeServer && !(spacetimeServer ==~ /^[A-Za-z0-9._:-]+$/)) { + error("SPACETIME_SERVER 只能包含字母、数字、点、下划线、冒号和短横线: ${spacetimeServer}") + } } } } @@ -99,6 +111,9 @@ pipeline { steps { script { def clearArg = params.CLEAR_DATABASE ? '--clear-database' : '' + def serverArg = params.SPACETIME_SERVER_URL?.trim() + ? "--server-url \"${params.SPACETIME_SERVER_URL.trim()}\"" + : "--server \"${params.SPACETIME_SERVER}\"" sh """ bash -lc ' set -euo pipefail @@ -106,7 +121,7 @@ pipeline { scripts/deploy/production-stdb-publish.sh \\ --source-dir "build/${params.BUILD_VERSION}" \\ --database "${params.DATABASE}" \\ - --server "${params.SPACETIME_SERVER}" \\ + ${serverArg} \\ ${clearArg} ' """ diff --git a/scripts/deploy/production-stdb-publish.sh b/scripts/deploy/production-stdb-publish.sh index 0929fa96..8ecb797e 100644 --- a/scripts/deploy/production-stdb-publish.sh +++ b/scripts/deploy/production-stdb-publish.sh @@ -5,10 +5,11 @@ set -euo pipefail usage() { cat <<'EOF' 用法: - ./scripts/deploy/production-stdb-publish.sh --source-dir build/ --database [--server local] [--clear-database] + ./scripts/deploy/production-stdb-publish.sh --source-dir build/ --database [--server-url http://127.0.0.1:3101] [--server local] [--clear-database] 说明: 进入维护模式,校验 spacetime_module.wasm.sha256,并在生产实例本机执行 spacetime publish。 + 默认使用 http://127.0.0.1:3101,避免与部署机本机 Git/Web 服务的 3000 端口冲突。 失败时保留维护模式。 EOF } @@ -36,6 +37,7 @@ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" SOURCE_DIR="" DATABASE="" SERVER_ALIAS="local" +SERVER_URL="http://127.0.0.1:3101" CLEAR_DATABASE=0 DEPLOY_COMPLETED=0 @@ -55,6 +57,11 @@ while [[ $# -gt 0 ]]; do ;; --server) SERVER_ALIAS="${2:?缺少 --server 的值}" + SERVER_URL="" + shift 2 + ;; + --server-url) + SERVER_URL="${2:?缺少 --server-url 的值}" shift 2 ;; --clear-database) @@ -106,16 +113,25 @@ echo "[production-stdb-publish] 校验 wasm" PUBLISH_ARGS=( publish "${DATABASE}" - --server "${SERVER_ALIAS}" --bin-path "${SOURCE_DIR}/spacetime_module.wasm" --yes ) +if [[ -n "${SERVER_URL}" ]]; then + PUBLISH_ARGS+=(--server "${SERVER_URL}") +else + PUBLISH_ARGS+=(--server "${SERVER_ALIAS}") +fi + if [[ "${CLEAR_DATABASE}" -eq 1 ]]; then PUBLISH_ARGS+=(--clear-database) fi -echo "[production-stdb-publish] 发布 SpacetimeDB module: ${DATABASE} -> ${SERVER_ALIAS}" +if [[ -n "${SERVER_URL}" ]]; then + echo "[production-stdb-publish] 发布 SpacetimeDB module: ${DATABASE} -> ${SERVER_URL}" +else + echo "[production-stdb-publish] 发布 SpacetimeDB module: ${DATABASE} -> ${SERVER_ALIAS}" +fi spacetime "${PUBLISH_ARGS[@]}" "${SCRIPT_DIR}/maintenance-off.sh"