- docs/specs/2026-09-29-prod-write-side-design.md: minio-prod on 9001, api-prod STORAGE_S3_*/GAMES_BASE_DOMAIN/CORS env, POSTGRES_PASSWORD default removed chain-wide, nginx wildcard block templated + /api/ proxy (guarded repo-yaml.test.ts updated), TLS explicitly out of scope - docs/ROADMAP.md: P1 status -> spec written, doc index registered
9.3 KiB
P1 prod 写侧可用(本机自包含演练)设计 / Prod Write-Side Design
- 状态:设计已批准(2026-09-29,方案 A + 跳过 TLS),待 spec 审阅后转实现计划。
- 路线图:
docs/ROADMAP.mdP1(第一波)。 - 范围定性:纯 compose/nginx/配置编排改动;零后端代码改动、零前端应用代码改动(仅
deploy/资产与其守卫测试)。
1. 背景与意图
prod 栈的 api-prod 未配置任何 STORAGE_S3_*,server 启动时 storageEnabled=false,上传/投稿/审核/预览路由整体不注册——生产只读。本设计在本机 compose 内把 prod 栈补齐到与 dev 同等的写侧+游玩能力,作为日后迁真实服务器的定型演练。
已确认的两个上游决策(2026-09-29):
- 形态:本机自包含演练——MinIO 入 prod 栈,域名体系用
*.localhost。 - TLS:本轮跳过,全程
http://localhost:8080;README 记录迁真机时的 TLS 步骤。
成功标准(prod profile 起来后):注册→上传 bundle→投稿→审核上架→站内游玩(virtual 子域运行时 + external 外链)全链路可用;docker compose --profile prod up -d --build 后按 AGENTS.md 验证四步全绿。
2. 方案选型
| 方案 | 内容 | 取舍 |
|---|---|---|
| A. 镜像 dev 直连法(已选) | minio-prod(主机端口 9001),浏览器跨源直连 MinIO 拉密文/封面;nginx 通配块参数化 + 补 /api/ 反代 |
复用 dev 已验证的跨源模式(MINIO_API_CORS_ALLOW_ORIGIN + STORAGE_S3_PUBLIC_BASE_URL);改动最小 |
B. nginx 同源反代 /objects/ |
只暴露 8080,形态更近真 prod | 重写两个 server 块代理/改写 + MinIO 匿名策略变化,验证面大;上真服务器时再迁 |
| C. 只做写侧不验游玩 | 范围最小 | P1 明文含 GAMES_BASE_DOMAIN,游玩链路是"写侧可用"的完整验收,砍掉不闭合 |
3. 设计详述
3.1 prod 端口与命名基线
- prod MinIO 主机端口
${MINIO_PROD_PORT:-9001}(避开 dev 的 9000,误起两栈时冲突面最小化);容器名minio-prod,卷minio-data-prod(新卷,不碰任何既有卷名)。 - 桶名沿用
${MINIO_BUCKET:-crearte};路径风格STORAGE_S3_FORCE_PATH_STYLE=true;公开基址STORAGE_S3_PUBLIC_BASE_URL=http://localhost:${MINIO_PROD_PORT:-9001}/${MINIO_BUCKET:-crearte}。
3.2 compose 改动(crearte-deploy/)
api-prod 补环境变量(其余不动):
depends_on:
db-prod: { condition: service_healthy }
minio-prod: { condition: service_started }
environment:
<<: *api-env
DATABASE_URL: postgres://crearte:${POSTGRES_PASSWORD:?…}@db-prod:5432/crearte?sslmode=disable
STORAGE_S3_ENDPOINT: http://minio-prod:9000
STORAGE_S3_BUCKET: ${MINIO_BUCKET:-crearte}
STORAGE_S3_ACCESS_KEY_ID: ${MINIO_ROOT_USER:?…}
STORAGE_S3_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD:?…}
STORAGE_S3_FORCE_PATH_STYLE: "true"
STORAGE_S3_PUBLIC_BASE_URL: http://localhost:${MINIO_PROD_PORT:-9001}/${MINIO_BUCKET:-crearte}
GAMES_BASE_DOMAIN: localhost # server 默认即 localhost,显式写出以表意;真域名时改此值
CORS_ALLOWED_ORIGINS: ${HOST_ORIGIN:-http://localhost:8080}
minio-prod 服务:镜像 pgsty/minio:latest,command: ["server", "/data"],restart: unless-stopped,端口 ${MINIO_PROD_PORT:-9001}:9000,环境 MINIO_ROOT_USER/MINIO_ROOT_PASSWORD(同上 :?)+ MINIO_API_CORS_ALLOW_ORIGIN: "*",卷 minio-data-prod:/data。
web-prod 补两项:
build:
args:
VITE_API_BASE_URL: /
VITE_GAMES_BASE_DOMAIN: ${PUBLIC_GAMES_HOST:-localhost:8080} # host:port,含端口
VITE_HOST_ORIGIN: ${HOST_ORIGIN:-http://localhost:8080}
environment:
GAMES_SERVER_NAME: ${GAMES_SERVER_NAME:-*.localhost} # nginx 模板渲染变量
命名注意:
PUBLIC_GAMES_HOST(客户端用,localhost:8080带端口)与 server 的GAMES_BASE_DOMAIN(CORS 后缀匹配用,纯主机名localhost)是两个不同值,compose 变量必须分开,防止一处改动串坏另一处。
3.3 POSTGRES_PASSWORD 去默认值(全链)
x-postgres-env与两条DATABASE_URL中${POSTGRES_PASSWORD:-crearte}一律改${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}。- 数据风险与迁移:pgdata 卷内口令已固化;现存
.env没写该变量的用户必须显式补POSTGRES_PASSWORD=crearte才能保住旧卷,README「数据与配置」加一条迁移警告(dev/prod 同理)。 .env.example:POSTGRES_PASSWORD=crearte(dev 开箱即用)+ 注释"prod 务必改强口令";新增MINIO_ROOT_USER=/MINIO_ROOT_PASSWORD=空占位(注释说明 dev 可留默认 minioadmin 需显式写值、prod 必填)。注意.env.example值与 compose:?守卫同步。
3.4 nginx 模板化(crearte/deploy/)
现状两处死点:通配块 server_name *.games.example.com 硬编码;通配块不提供 /api/ 反代(子域运行时取 bundle-key 会 404——dev 靠 vite 顺带代理才没暴露)。
改法:
deploy/nginx.conf→deploy/nginx.conf.template,主块保持server_name _;,通配块改server_name ${GAMES_SERVER_NAME};。nginx 官方镜像 entrypoint(20-envsubst-on-templates.sh)按已定义环境变量渲染到/etc/nginx/conf.d/,$uri/$host等 nginx 内建变量不受影响(非环境变量)。- 通配块新增
location /api/,逐字照抄主块的 resolver +proxy_pass http://$api_upstream+Host/X-Forwarded-For写法;其余维持"三件套 +location / { return 404; }"骨架不变。子域页面因此同源取 API,bundle-key 链路不再依赖跨源。 - Dockerfile:
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf→COPY deploy/nginx.conf.template /etc/nginx/templates/default.conf.template。 - 守卫测试同步(
src/scripts/repo-yaml.test.ts):读取路径改deploy/nginx.conf.template;通配块查找键改server_name ${GAMES_SERVER_NAME};;新增断言通配块含location /api/且 proxy 指向上游写法;"no /data/ no /assets/" 断言保留。dev-game-runtime.ts头注里"对齐生产 nginx 通配 server block"的语义仍然成立,不动。
3.5 一次性桶初始化(prod)
与 dev 同型、端口不同,README「使用」prod 小节新增一行:
docker compose --profile prod exec minio-prod sh -c 'mc alias set local http://localhost:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" && mc mb --ignore-existing local/crearte && mc anonymous set download local/crearte'
单引号是刻意的:宿主机 shell 不展开 $MINIO_ROOT_USER/$MINIO_ROOT_PASSWORD(.env 不会自动 export 到用户 shell),由容器内 shell 用 compose 注入的同名环境变量完成展开——与 dev 版(写死 minioadmin)行为等价但不再复制凭据字面量。不引入自动初始化——保持"卷首建需手动一次性初始化"的既有约定。
4. 验证(AGENTS.md 四步,逐条执行)
docker compose config -q四个 profile 全过(dev/prod/debug/mock)。--profile prod up -d --build+ps健康全绿;跑 prod MinIO 桶初始化。- 冒烟链:
localhost:8080注册→docker compose exec api-prod /app/crearte-server user set-role <email> admin→登录→上传→投稿→管理端 approve→开<hash>.localhost:8080玩 virtual 作品(bootstrap→sw→bundle-key→解密→可玩);external 作品外链正常;匿名只读目录/详情 200。 - 回归:dev 栈
up -d --build再起一次确认 9001/9000 无端口冲突、写侧未受 POSTGRES_PASSWORD 改动影响(.env补值后);repo-yaml.test.ts等前端单测在容器内全绿。 down(绝不-v)。
5. 影响文件(预估)
| 仓库 | 文件 | 改动 |
|---|---|---|
| crearte-deploy | docker-compose.yml |
minio-prod 服务、api-prod 补 env、web-prod 补 args/env、POSTGRES_PASSWORD 去默认、卷 minio-data-prod |
| crearte-deploy | .env.example |
POSTGRES_PASSWORD、MINIO_ROOT_* 占位与注释 |
| crearte-deploy | README.md |
prod 写侧用法、桶初始化、迁移警告、真机 TLS/域名 checklist |
| crearte-deploy | docs/CHANGELOG.md |
0.5.0 条目 |
| crearte | deploy/nginx.conf.template(新,替代 nginx.conf) |
通配块 server_name 参数化 + /api/ 反代 |
| crearte | deploy/Dockerfile |
模板 COPY 改路径 |
| crearte | src/scripts/repo-yaml.test.ts |
读模板、断言更新 |
| crearte | docs/CHANGELOG.md |
0.15.0 条目 |
| wrapper | docs/ROADMAP.md |
P1 状态、文档索引登记本 spec |
6. 明确不做(YAGNI / 出圈)
- 不做 TLS、不装反代层(真机时:Caddy/certbot + 泛解析 + 通配证书,README 记步骤即可)。
- 不动 server/前端应用代码;不引 nginx
/objects/同源反代(方案 B,留待真服务器)。 - 不改任何卷名;不新增 compose 全局网络拓扑。
- P1 的"密码默认值"仅指
POSTGRES_PASSWORD链(S3 root 凭据入:?见 3.2/3.3);AUTH_TOKEN_SECRET/BUNDLE_KEK_k1已有守卫,不动。
7. 分支与提交
- crearte:
feat/prod-nginx-wildcard-api,完成后--no-ff合入 master 并删分支。 - crearte-deploy:
feat/prod-write-side,同上。 - wrapper:spec/ROADMAP/CHANGELOG 记录可直提 master。
- 提交顺序:crearte(模板+测试)→ crearte-deploy(compose+文档,验证依赖前者)→ wrapper 收尾。