Add production Jenkins release pipelines

This commit is contained in:
2026-05-02 19:14:13 +08:00
parent 879a53bf8d
commit bdc3257003
38 changed files with 3315 additions and 982 deletions

76
deploy/env/api-server.env.example vendored Normal file
View File

@@ -0,0 +1,76 @@
# 复制到 /etc/genarrative/api-server.env 后再填入真实生产值。
# 该文件只能保存在生产服务器,不进入构建产物,不提交真实密钥。
GENARRATIVE_ENV=production
GENARRATIVE_API_HOST=127.0.0.1
GENARRATIVE_API_PORT=8082
GENARRATIVE_API_LOG=info,tower_http=info
GENARRATIVE_ADMIN_USERNAME=
GENARRATIVE_ADMIN_PASSWORD=
GENARRATIVE_ADMIN_TOKEN_TTL_SECONDS=14400
GENARRATIVE_INTERNAL_API_SECRET=CHANGE_ME_FOR_PRODUCTION
GENARRATIVE_JWT_ISSUER=genarrative-production
GENARRATIVE_JWT_SECRET=CHANGE_ME_FOR_PRODUCTION
GENARRATIVE_JWT_ACCESS_TOKEN_TTL_SECONDS=7200
AUTH_REFRESH_COOKIE_NAME=genarrative_refresh_session
AUTH_REFRESH_SESSION_TTL_DAYS=30
AUTH_REFRESH_COOKIE_PATH=/api/auth
AUTH_REFRESH_COOKIE_SAME_SITE=Lax
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_DATABASE=genarrative-prod
GENARRATIVE_SPACETIME_TOKEN=
GENARRATIVE_SPACETIME_POOL_SIZE=8
GENARRATIVE_SPACETIME_PROCEDURE_TIMEOUT_SECONDS=45
GENARRATIVE_LLM_PROVIDER=openai-compatible
GENARRATIVE_LLM_BASE_URL=
GENARRATIVE_LLM_API_KEY=
GENARRATIVE_LLM_MODEL=
GENARRATIVE_RPG_LLM_WEB_SEARCH_ENABLED=false
GENARRATIVE_CREATION_AGENT_LLM_WEB_SEARCH_ENABLED=false
DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/api/v1
DASHSCOPE_API_KEY=
DASHSCOPE_IMAGE_MODEL=wan2.7-image
DASHSCOPE_IMAGE_REQUEST_TIMEOUT_MS=150000
DASHSCOPE_CHARACTER_VISUAL_MODEL=wan2.7-image-pro
DASHSCOPE_CHARACTER_IMAGE_SEQUENCE_MODEL=wan2.7-image-pro
DASHSCOPE_CHARACTER_REFERENCE_VIDEO_MODEL=wan2.7-r2v
DASHSCOPE_CHARACTER_MOTION_TRANSFER_MODEL=wan2.2-animate-move
DASHSCOPE_CHARACTER_VIDEO_REQUEST_TIMEOUT_MS=420000
SMS_AUTH_ENABLED=false
SMS_AUTH_PROVIDER=aliyun
ALIYUN_SMS_ACCESS_KEY_ID=
ALIYUN_SMS_ACCESS_KEY_SECRET=
ALIYUN_SMS_ENDPOINT=dypnsapi.aliyuncs.com
ALIYUN_SMS_SIGN_NAME=
ALIYUN_SMS_TEMPLATE_CODE=
ALIYUN_SMS_TEMPLATE_PARAM_KEY=code
ALIYUN_SMS_COUNTRY_CODE=86
WECHAT_AUTH_ENABLED=false
WECHAT_AUTH_PROVIDER=real
WECHAT_APP_ID=
WECHAT_APP_SECRET=
WECHAT_CALLBACK_PATH=/api/auth/wechat/callback
WECHAT_REDIRECT_PATH=/
WECHAT_AUTHORIZE_ENDPOINT=https://open.weixin.qq.com/connect/qrconnect
WECHAT_ACCESS_TOKEN_ENDPOINT=https://api.weixin.qq.com/sns/oauth2/access_token
WECHAT_USER_INFO_ENDPOINT=https://api.weixin.qq.com/sns/userinfo
WECHAT_STATE_TTL_MINUTES=15
ALIYUN_OSS_BUCKET=
ALIYUN_OSS_ENDPOINT=oss-cn-shanghai.aliyuncs.com
ALIYUN_OSS_ACCESS_KEY_ID=
ALIYUN_OSS_ACCESS_KEY_SECRET=
ALIYUN_OSS_READ_EXPIRE_SECONDS=600
ALIYUN_OSS_POST_EXPIRE_SECONDS=600
ALIYUN_OSS_POST_MAX_SIZE_BYTES=20971520
ALIYUN_OSS_SUCCESS_ACTION_STATUS=200

View File

@@ -0,0 +1,101 @@
# 生产域名需要在部署前替换为真实域名,并由 certbot 或等价流程写入 HTTPS 证书配置。
server {
listen 80;
server_name genarrative.example.com;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name genarrative.example.com;
ssl_certificate /etc/letsencrypt/live/genarrative.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/genarrative.example.com/privkey.pem;
root /srv/genarrative/web;
index index.html;
include /etc/nginx/snippets/genarrative-maintenance.conf;
location ^~ /admin/api/ {
default_type application/json;
if ($genarrative_maintenance) {
return 503 '{"ok":false,"error":{"code":"MAINTENANCE","message":"服务维护中"}}';
}
proxy_pass http://127.0.0.1:8082/admin/api/;
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 X-Request-Id $request_id;
}
location = /admin {
return 301 /admin/;
}
location ^~ /admin/assets/ {
try_files $uri =404;
}
location ^~ /admin/ {
error_page 503 /maintenance.html;
if ($genarrative_maintenance) {
return 503;
}
try_files $uri $uri/ /admin/index.html;
}
location ^~ /assets/ {
try_files $uri =404;
}
# 生产公网不再暴露旧一体化 API、生成资源代理和健康检查入口。
location ~ ^/(api|generated-|healthz) {
return 404;
}
# SpacetimeDB 只开放 TypeScript SDK 运行所需的最小公网路由。
location ~ ^/v1/database/[^/]+/subscribe$ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s;
}
location ^~ /v1/identity {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
location ^~ /v1/ {
return 404;
}
location / {
error_page 503 /maintenance.html;
if ($genarrative_maintenance) {
return 503;
}
try_files $uri $uri/ /index.html;
}
}

View File

@@ -0,0 +1,12 @@
# 维护模式由发布脚本或人工运维通过固定文件控制。
# 文件存在时,普通页面展示维护页,管理 API 返回 503。
set $genarrative_maintenance 0;
if (-f /var/lib/genarrative/maintenance/enabled) {
set $genarrative_maintenance 1;
}
location = /maintenance.html {
root /srv/genarrative/web;
add_header Cache-Control "no-store";
internal;
}

View File

@@ -0,0 +1,26 @@
[Unit]
Description=Genarrative Rust API Server
After=network-online.target spacetimedb.service
Wants=network-online.target
Requires=spacetimedb.service
[Service]
Type=simple
User=genarrative
Group=genarrative
WorkingDirectory=/opt/genarrative/current
EnvironmentFile=/etc/genarrative/api-server.env
ExecStart=/opt/genarrative/current/api-server
Restart=always
RestartSec=5
KillSignal=SIGINT
TimeoutStopSec=30
# api-server 只读发布目录,运行态写入必须显式落到环境变量指定的服务端私有目录。
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ReadWritePaths=/opt/genarrative /var/lib/genarrative
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,23 @@
[Unit]
Description=SpacetimeDB Server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=spacetimedb
Group=spacetimedb
WorkingDirectory=/stdb
ExecStart=/stdb/spacetime --root-dir=/stdb start --listen-addr=127.0.0.1:3000
Restart=always
RestartSec=5
LimitNOFILE=1048576
# 生产库只监听本机端口,由 Nginx 暴露最小客户端路由。
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ReadWritePaths=/stdb
[Install]
WantedBy=multi-user.target