Files
Genarrative/deploy/env/api-server.env.example
T
kdletters 1a7abfd059
Project CI / AI game creator shell Rust shard 4/4 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (push) Has been cancelled
AGC 项目定时快照上传(目标 OSS agc-dev) (#400)
## 交付内容

- 客户端新增 `project_snapshot` 模块:项目扫描与排除口径、增量索引、差异对比、上传编排、状态查询。
- 触发:工作区窗口存活期间的周期定时器 + 工作区窗口关闭(`CloseRequested`);应用退出只在有界预算内等待在途同步收尾,不重复发起。
- 服务端新增两条登录态路由 `POST /api/agc/project-snapshots/files` 与 `/manifest`,由 api-server 用服务端凭据写入私有前缀 `agc/project-snapshots/v1/{user}/{project}/`;客户端不持有 OSS 凭据、不直连 OSS。
- `platform-oss` 新增内部对象精确写入与探测、项目快照对象键构造;修复 `head_internal_object` 读 HEAD 响应长度恒为 0(会导致服务端"已存在即跳过"永不生效)。
- `shared-contracts` 新增 `agc_project_snapshots` DTO 与项目 ID、相对路径、摘要校验。
- 真实 OSS 存储层冒烟示例 + 客户端真实链路冒烟用例(`#[ignore]`,env 驱动)。
- 登记 `check-config.mjs` 的 native-only 命令白名单,恢复 `npm run agc` 可启动。

## 验证证据

- 存储层:`cargo run -p platform-oss --example agc_project_snapshot_live_smoke` 对真实 `agc-dev` 完成写入 → 读回(contentLength 32)→ 清单写入 → 探针清理。
- HTTP 层:登录态下 7 项校验(401 无 token、400 路径穿越/长度不一致/非法摘要/空正文/重复路径清单)全部符合预期且不写对象。
- 客户端链路(真实项目 gameagent-033b6cf3…):第一次 `synced uploaded=6 uploadedBytes=36694 remoteSkipped=6`,紧接着第二次 `no-op uploaded=0`。
- GUI 触发:`trigger=periodic ... uploaded=6`;把周期设为 600 秒排除干扰、改一个文件后关窗得到 `trigger=project-close ... revision=2 uploaded=1`,索引摘要由 `28d837cd84f8ab62` 推进到 `bff34e2e336d901f`(同步在进程退出前完成)。
- 门禁:AGC `project_snapshot` 14 passed(+1 ignored live)、api-server `project_snapshots` 3 passed、platform-oss 39 passed、shared-contracts 87 passed、`cargo fmt --check`(两处)、`check:encoding`、`check:doc-index`、`git diff --check`、`check-config.mjs` 全部通过。

## 已知未决(不阻塞本里程碑)

- 远端对象只增不减:没有删除路径,也没有 bucket 生命周期规则;要收口需要先定保留语义(清单引用 GC 还是 OSS 生命周期)。
- 部署环境需确认 `GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_*` 或回退的 `ALIYUN_OSS_*` 具备目标 bucket 私有前缀的 `PutObject` 权限;未配置时接口返回 503、客户端失败关闭(不推进索引)。
- 缺少按用户/项目的配额与限流(error_reports 有每小时提交上限的先例,本功能没有)。
- 没有界面入口与状态可见性:`read_local_project_snapshot_state` 已注册但未接 UI,失败只写本机日志。

## 不做

- 不做云端下载/恢复、跨设备合并、版本回滚。
- 不改 `/api/external/v1` 与 External OpenAPI,不新增 SpacetimeDB 表。

---------

Co-authored-by: kdletters <61648117+kdletters@users.noreply.github.com>
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/400
2026-09-17 18:34:51 +08:00

198 lines
9.9 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 复制到 /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_API_LISTEN_BACKLOG=1024
GENARRATIVE_API_WORKER_THREADS=4
# api 只监听 HTTP;外部生成 worker 用独立进程设置为 external-generation-worker 后横向扩缩。
GENARRATIVE_PROCESS_ROLE=api
# 默认 queue 进入 external_generation_job;本地/小流量同步排查可显式设 inline。
GENARRATIVE_EXTERNAL_GENERATION_MODE=queue
GENARRATIVE_EXTERNAL_GENERATION_WORKER_ID=
GENARRATIVE_EXTERNAL_GENERATION_WORKER_CONCURRENCY=2
GENARRATIVE_EXTERNAL_GENERATION_WORKER_POLL_INTERVAL_MS=2000
GENARRATIVE_EXTERNAL_GENERATION_WORKER_LEASE_SECONDS=600
GENARRATIVE_EXTERNAL_GENERATION_WORKER_JOB_TIMEOUT_SECONDS=900
GENARRATIVE_EXTERNAL_GENERATION_WORKER_LONG_JOB_TIMEOUT_SECONDS=1800
# 父流程只访问同机 BgFilter worker;内部 Token 只通过受保护文件共享,不写明文 env。
GENARRATIVE_BGFILTER_WORKER_BASE_URL=http://127.0.0.1:8083
GENARRATIVE_BGFILTER_INTERNAL_TOKEN_FILE=/etc/genarrative/secrets/bgfilter-worker.token
GENARRATIVE_BGFILTER_WORKER_CONNECT_TIMEOUT_MS=2000
GENARRATIVE_API_MAX_CONCURRENT_REQUESTS=512
GENARRATIVE_API_ADMIN_MAX_CONCURRENT_REQUESTS=16
GENARRATIVE_API_SHUTDOWN_OUTBOX_FLUSH_TIMEOUT_MS=5000
GENARRATIVE_TRACKING_OUTBOX_ENABLED=true
GENARRATIVE_TRACKING_OUTBOX_DIR=/var/lib/genarrative/tracking-outbox
GENARRATIVE_TRACKING_OUTBOX_BATCH_SIZE=500
GENARRATIVE_TRACKING_OUTBOX_FLUSH_INTERVAL_MS=1000
GENARRATIVE_TRACKING_OUTBOX_MAX_BYTES=268435456
GENARRATIVE_WALLET_REFUND_OUTBOX_ENABLED=true
GENARRATIVE_WALLET_REFUND_OUTBOX_DIR=/var/lib/genarrative/wallet-refund-outbox
GENARRATIVE_WALLET_REFUND_OUTBOX_BATCH_SIZE=100
GENARRATIVE_WALLET_REFUND_OUTBOX_FLUSH_INTERVAL_MS=1000
GENARRATIVE_WALLET_REFUND_OUTBOX_MAX_BYTES=67108864
# N 与单图估时:父子共同派生 attempt = N×est×2、callBudget = 2×attempt+1s 的公式输入;
# 单一来源放共享 env,专属 worker env 不重复定义。旧固定 REQUEST_TIMEOUT_MS 已删除。
GENARRATIVE_BGFILTER_WORKER_CONCURRENCY=16
GENARRATIVE_EDITOR_BGFILTER_SINGLE_IMAGE_ESTIMATE_MS=5000
GENARRATIVE_EDITOR_BGFILTER_BASE_URL=http://58.87.105.82/bgfilter
GENARRATIVE_EDITOR_BGFILTER_TOKEN=
# BgFilter 失败后的中间兜底:阿里云通用抠图(SegmentCommonImage)。AccessKey 留空则跳过该层,
# BgFilter 失败直接本地 editor_green_screen 去背;填入后恢复 BgFilter→阿里云→本地三级兜底。
# AccessKey 也可复用标准 SDK 命名 ALIBABA_CLOUD_ACCESS_KEY_ID / ALIBABA_CLOUD_ACCESS_KEY_SECRET。
GENARRATIVE_ALIYUN_MATTING_ENABLED=true
GENARRATIVE_ALIYUN_MATTING_ENDPOINT=imageseg.cn-shanghai.aliyuncs.com
GENARRATIVE_ALIYUN_MATTING_ACCESS_KEY_ID=
GENARRATIVE_ALIYUN_MATTING_ACCESS_KEY_SECRET=
GENARRATIVE_ALIYUN_MATTING_REQUEST_TIMEOUT_MS=30000
GENARRATIVE_OTEL_ENABLED=true
OTEL_SERVICE_NAME=genarrative-api
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.namespace=genarrative
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_DEV_PASSWORD_ENTRY_AUTO_REGISTER_ENABLED=false
GENARRATIVE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR=false
GENARRATIVE_SPACETIME_SERVER_URL=http://127.0.0.1:3101
GENARRATIVE_SPACETIME_DATABASE=genarrative-prod
GENARRATIVE_SPACETIME_TOKEN=
# 只保存受保护文件路径;不要把 bootstrap secret 明文写入生产 env。
GENARRATIVE_SPACETIME_RUNTIME_SERVICE_BOOTSTRAP_SECRET_FILE=/var/lib/genarrative/spacetime/runtime-service-bootstrap-secret.txt
# HTTP 角色使用 8 条无 read-model 订阅的调用连接,并额外创建 1 条共享缓存读连接。
GENARRATIVE_SPACETIME_POOL_SIZE=8
GENARRATIVE_SPACETIME_PROCEDURE_TIMEOUT_SECONDS=45
GENARRATIVE_LLM_PROVIDER=openai-compatible
GENARRATIVE_LLM_BASE_URL=https://api.tiantoken.com/v1
GENARRATIVE_LLM_API_KEY=
GENARRATIVE_LLM_MODEL=gpt-5.4-mini
# LLM Router 正式账号链路:production 固定使用官方地址/模型;管理员 Token 只读受保护文件。
GENARRATIVE_LLM_ROUTER_BASE_URL=https://router.genarrative.world/v1
GENARRATIVE_LLM_ROUTER_PROVISIONING_SECRET_FILE=/etc/genarrative/secrets/llm-router-provisioning.secret
GENARRATIVE_LLM_ROUTER_API_KEY_ENCRYPTION_SECRET_FILE=/etc/genarrative/secrets/llm-router-api-key-encryption.secret
GENARRATIVE_LLM_ROUTER_ADMIN_TOKEN_FILE=/etc/genarrative/secrets/llm-router-admin.token
TIANTOKEN_BASE_URL=https://api.tiantoken.com
TIANTOKEN_API_KEY=
TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS=1000000
VECTOR_ENGINE_BASE_URL=https://api.vectorengine.cn
VECTOR_ENGINE_API_KEY=
VECTOR_ENGINE_AUDIO_REQUEST_TIMEOUT_MS=180000
ELEVENLABS_BASE_URL=https://api.elevenlabs.io
ELEVENLABS_API_KEY=
ELEVENLABS_REQUEST_TIMEOUT_MS=180000
HYPER3D_BASE_URL=https://api.hyper3d.com/api/v2
HYPER3D_API_KEY=
HYPER3D_MODEL_REQUEST_TIMEOUT_MS=180000
VOLCENGINE_SPEECH_API_KEY=
VOLCENGINE_SPEECH_APP_ID=
VOLCENGINE_SPEECH_ACCESS_KEY=
VOLCENGINE_SPEECH_ASR_RESOURCE_ID=volc.seedasr.sauc.concurrent
VOLCENGINE_SPEECH_TTS_RESOURCE_ID=seed-tts-2.0
VOLCENGINE_SPEECH_REQUEST_TIMEOUT_MS=180000
VOLCENGINE_SPEECH_ASR_WS_URL=wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async
VOLCENGINE_SPEECH_TTS_BIDIRECTION_WS_URL=wss://openspeech.bytedance.com/api/v3/tts/bidirection
VOLCENGINE_SPEECH_TTS_SSE_URL=https://openspeech.bytedance.com/api/v3/tts/unidirectional/sse
ARK_CHARACTER_VIDEO_BASE_URL=
ARK_CHARACTER_VIDEO_API_KEY=
ARK_CHARACTER_VIDEO_MODEL=
ARK_CHARACTER_VIDEO_REQUEST_TIMEOUT_MS=420000
DASHSCOPE_BASE_URL=https://dashscope.aliyuncs.com/api/v1
DASHSCOPE_API_KEY=
DASHSCOPE_SCENE_IMAGE_MODEL=
DASHSCOPE_REFERENCE_IMAGE_MODEL=
DASHSCOPE_COVER_IMAGE_MODEL=
DASHSCOPE_IMAGE_REQUEST_TIMEOUT_MS=150000
DASHSCOPE_CHARACTER_VISUAL_MODEL=
DASHSCOPE_CHARACTER_IMAGE_SEQUENCE_MODEL=
DASHSCOPE_CHARACTER_REFERENCE_VIDEO_MODEL=
DASHSCOPE_CHARACTER_MOTION_TRANSFER_MODEL=
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=dysmsapi.aliyuncs.com
ALIYUN_SMS_SIGN_NAME=北京亓盒网络科技
ALIYUN_SMS_TEMPLATE_CODE=SMS_506245486
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
WECHAT_MINIPROGRAM_MESSAGE_TOKEN=
WECHAT_MINIPROGRAM_MESSAGE_ENCODING_AES_KEY=
# 真实微信支付必须开启退款主动查单与账单对账;生产发布脚本会复核该值。
WECHAT_PAY_REFUND_RECONCILIATION_ENABLED=true
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
# AGC 项目定时快照上传目标。对象只落在服务端私有前缀
# agc/project-snapshots/v1/{user}/{project}/ 下;AccessKey 为空时回退 ALIYUN_OSS_ACCESS_KEY_*
# 因此回退凭据必须对目标 bucket 具备该前缀的 PutObject/GetObject/DeleteObject 权限。
# bucket 未单独配置时默认 agc-dev,未配置凭据时 api-server 跳过该客户端,
# 接口返回 503 且客户端失败关闭(不写空对象、不推进本地索引)。
GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_BUCKET=agc-dev
GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_ENDPOINT=oss-rg-china-mainland.aliyuncs.com
GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_ACCESS_KEY_ID=
GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_ACCESS_KEY_SECRET=
# SpacetimeDB 数据目录 OSS 冷备份配置。可由 cron / Jenkins 调用发布包内 scripts/database-backup-to-oss.mjs。
GENARRATIVE_DATABASE_BACKUP_DATA_DIR=/stdb
GENARRATIVE_DATABASE_BACKUP_WORK_DIR=/var/lib/genarrative/database-backups
GENARRATIVE_DATABASE_BACKUP_OSS_BUCKET=
GENARRATIVE_DATABASE_BACKUP_OSS_ENDPOINT=oss-cn-shanghai.aliyuncs.com
GENARRATIVE_DATABASE_BACKUP_OSS_PREFIX=database-backups
GENARRATIVE_DATABASE_BACKUP_KEEP_LOCAL=false
# 可选:files 为逐文件 CAS + catalog,不生成 tar.gzarchive 保留旧全量压缩包兼容行为。
GENARRATIVE_DATABASE_BACKUP_STORAGE_FORMAT=archive
# files 模式并行 PUT/HEAD 数量,必须为 1-64;默认 16。
GENARRATIVE_DATABASE_BACKUP_FILES_CONCURRENCY=16
# 可选:仅限制备份进程上传总带宽,单位 bytes/s;为空或 0 表示不限速。
GENARRATIVE_DATABASE_BACKUP_UPLOAD_MAX_BYTES_PER_SECOND=
# 可选:显式要求备份工作目录所在文件系统至少保留的可用空间;为空时按数据目录大小 + 安全余量估算。
GENARRATIVE_DATABASE_BACKUP_MIN_FREE_BYTES=
# archive history 模式持久化已验真 full baseline 与追加批次;files 模式改用 work-dir 下的 files state。
GENARRATIVE_DATABASE_BACKUP_BASELINE_STATE=/var/lib/genarrative/database-backups/genarrative-prod-history-state.json
# 仅 archive history 首次从 uploadStatus=uploaded 的 full manifest 初始化 state 时设置;初始化后可留空。
GENARRATIVE_DATABASE_BACKUP_BASELINE_MANIFEST=
# 可选:定时 / publish 前备份使用独立最小权限 AccessKey;为空时回退 ALIYUN_OSS_ACCESS_KEY_*。
GENARRATIVE_DATABASE_BACKUP_OSS_ACCESS_KEY_ID=
GENARRATIVE_DATABASE_BACKUP_OSS_ACCESS_KEY_SECRET=
GENARRATIVE_DATABASE_BACKUP_STOP_SERVICE=spacetimedb.service