diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..64bca66 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,23 @@ +.git +.gitignore +.memory +.logs +.pids +.playwright-mcp +.DS_Store +*.log + +node_modules +web/node_modules +web/.next +web/out + +api/.venv +api/jobs +jobs +data + +.env +.env.local +.env.production +deploy/.env.production diff --git a/.gitignore b/.gitignore index ffee6a0..721bf0c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ __pycache__/ *.log .logs/ .pids/ +deploy/.env.production # api api/.venv/ diff --git a/.memory/worklog.json b/.memory/worklog.json index 854786a..91eae08 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -1,18 +1,5 @@ { "entries": [ - { - "files_changed": 2, - "hash": "dd2e8c9", - "message": "auto-save 2026-05-13 23:17 (~2)", - "ts": "2026-05-13T23:18:31+08:00", - "type": "commit" - }, - { - "files_changed": 1, - "message": "Claude 会话活跃 · 最近命令:claude · 1 项未提交变更 · 最近提交:auto-save 2026-05-13 23:17 (~2)", - "ts": "2026-05-13T15:23:08Z", - "type": "session-heartbeat" - }, { "files_changed": 1, "hash": "38091d3", @@ -3251,6 +3238,19 @@ "message": "auto-save 2026-05-15 14:33 (~1)", "hash": "8090674", "files_changed": 1 + }, + { + "ts": "2026-05-15T14:42:29+08:00", + "type": "commit", + "message": "auto-save 2026-05-15 14:42 (~1)", + "hash": "ce04cf4", + "files_changed": 1 + }, + { + "ts": "2026-05-15T06:44:46Z", + "type": "session-heartbeat", + "message": "Codex 会话活跃 · 最近命令:codex · 5 项未提交变更 · 最近提交:auto-save 2026-05-15 14:42 (~1)", + "files_changed": 5 } ] } diff --git a/.project.json b/.project.json index fdc576c..f95bef8 100644 --- a/.project.json +++ b/.project.json @@ -2,16 +2,28 @@ "company" : "SKG", "created" : "2026-05-12", "credentials" : [ + { + "description" : "SKG AI 网关 API Key,生产只放服务器 deploy/.env.production 的 LLM_API_KEY,本地开发放 api/.env,不入库", + "name" : "LLM_API_KEY", + "storage" : "api/.env / deploy/.env.production", + "type" : "api_key" + }, { "description" : "MiniMax T2A 配音 API Key,本地开发只放 api/.env 的 MINIMAX_API_KEY,不入库", "name" : "MINIMAX_API_KEY", "storage" : "api/.env", "type" : "api_key" + }, + { + "description" : "SKG 豆包 / Seedance 视频生成 API Key,生产只放服务器 deploy/.env.production 的 VIDEO_API_KEY,本地开发放 api/.env,不入库", + "name" : "VIDEO_API_KEY", + "storage" : "api/.env / deploy/.env.production", + "type" : "api_key" } ], "description" : "SKG AI 素材生产管线第二条思路验证:TK 链接 → 拆轨 → 目标化关键帧 + ASR\/翻译 → 接 SKG 产品信息改写口播 → MiniMax 配音 → nano-banana-pro\/GPT Image 生图 → Seedance\/Kling\/Veo3 多模型生视频 → 合成带文案成品", "kind" : "app", - "name" : "SKG AI 素材管线 - TK 二创验证", + "name" : "SKG Marketing Studio / SKG 营销内容工作台", "ownership" : "company", "pin_order" : 1778664997, "pinned" : true, @@ -32,7 +44,21 @@ ], "status" : "active", "urls" : [ - + { + "label" : "production", + "type" : "app", + "url" : "https://marketing.skg.com" + }, + { + "label" : "production-api", + "type" : "backend", + "url" : "https://marketing.skg.com/api" + }, + { + "label" : "source-analysis", + "type" : "docs", + "url" : "docs/source-analysis.html" + } ], "worklog" : { "auto" : true, diff --git a/Dockerfile.api b/Dockerfile.api new file mode 100644 index 0000000..d6b6823 --- /dev/null +++ b/Dockerfile.api @@ -0,0 +1,22 @@ +FROM python:3.12-slim + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PIP_NO_CACHE_DIR=1 \ + JOBS_DIR=/data/jobs + +WORKDIR /app + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ffmpeg ca-certificates curl \ + && rm -rf /var/lib/apt/lists/* + +COPY api/requirements.txt /app/requirements.txt +RUN pip install --no-cache-dir -r /app/requirements.txt + +COPY api /app +RUN mkdir -p /data/jobs + +EXPOSE 4291 + +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "4291", "--proxy-headers"] diff --git a/Dockerfile.web b/Dockerfile.web new file mode 100644 index 0000000..df8ca58 --- /dev/null +++ b/Dockerfile.web @@ -0,0 +1,22 @@ +FROM node:22-bookworm-slim AS builder + +WORKDIR /app + +RUN corepack enable && corepack prepare pnpm@10.28.2 --activate + +COPY web/package.json web/pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile + +COPY web ./ + +ARG NEXT_PUBLIC_API_BASE=/api +ENV NEXT_PUBLIC_API_BASE=${NEXT_PUBLIC_API_BASE} + +RUN pnpm build + +FROM nginx:1.27-alpine + +COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=builder /app/out /usr/share/nginx/html + +EXPOSE 80 diff --git a/RULES.md b/RULES.md index 05aa0fe..2f7f730 100644 --- a/RULES.md +++ b/RULES.md @@ -13,12 +13,16 @@ - 第一冲刺:步骤 1-4(下载 / 拆轨 / 关键帧 / ASR+翻译) ## 部署事实 -- 平台:待定 -- 发布状态:未部署 -- 主站 / 前端:待定 -- API / 后端:待定 -- 文档 / 解析:待定 +- 平台:VPS `76.13.31.179`(Ubuntu 24.04 / Docker Compose / Coolify Traefik) +- 发布状态:生产部署配置已生成;公司域名 `marketing.skg.com` 已解析到 VPS,待执行 Compose 上线和 HTTPS 验证 +- 主站 / 前端:`https://marketing.skg.com` +- API / 后端:`https://marketing.skg.com/api` +- 文档 / 解析:`docs/source-analysis.html`(项目内独立文档,不公开挂主应用路由) - 管理后台:待定 +- 服务器目录:`/opt/skg-marketing-studio` +- 生产启动:`docker compose -f docker-compose.prod.yml --env-file deploy/.env.production up -d --build` +- 生产架构:`web` 容器用 Nginx 承载 Next 静态导出并反代 `/api/`,`api` 容器跑 FastAPI 4291;Traefik 通过 `coolify` 外部网络接入 80/443 +- 持久化目录:服务器 `./data/jobs` 挂载到后端 `/data/jobs` ## 快捷登录 - 登录地址:待补充 @@ -45,6 +49,7 @@ - `MINIMAX_TTS_BASE_URL` / `MINIMAX_TTS_MODEL` / `MINIMAX_TTS_VOICE_ID`:MiniMax 配音端点、模型和兜底音色配置 - `MINIMAX_TTS_VOICE_POOL`:MiniMax 英文随机音色池;当前默认男声 `English_magnetic_voiced_man`、女声 `English_Upbeat_Woman`、成熟声 `English_MaturePartner` - `POE_API_KEY` / `VIDEO_API_KEY`:视频生成通道 Key,只能放本地环境变量 +- 生产环境变量:服务器只使用 `deploy/.env.production`,模板为 `deploy/.env.production.example`;真实 Key 不入库 ## 规则 - 不允许编造不存在的部署域名、账号、密码 diff --git a/deploy/.env.production.example b/deploy/.env.production.example new file mode 100644 index 0000000..08f219b --- /dev/null +++ b/deploy/.env.production.example @@ -0,0 +1,43 @@ +# Copy this file to deploy/.env.production on the VPS. +# Keep real API keys out of git. + +# Runtime +JOBS_DIR=/data/jobs +KEYFRAME_COUNT=12 +CORS_ORIGINS=https://marketing.skg.com +API_PORT=4291 + +# SKG AI gateway, OpenAI-compatible +LLM_BASE_URL=https://ai.skg.com/ezlink/v1 +LLM_API_KEY= + +# Model routing +ASR_MODEL=whisper-1 +ASR_FALLBACK_MODEL=gemini-2.5-flash +TRANSLATE_MODEL=gemini-2.5-flash +REWRITE_MODEL=gemini-2.5-pro +IMAGE_MODEL=gemini-3-pro-image-preview + +# Audio rewrite and MiniMax TTS +AUDIO_REWRITE_MODEL=gemini-2.5-pro +AUDIO_PRODUCT_BRIEF="SKG smart massage products for daily neck, shoulder, back, eye, knee, and foot relaxation. Keep claims premium, clean, credible, and non-medical." +MINIMAX_API_KEY= +MINIMAX_TTS_BASE_URL=https://api.minimax.io +MINIMAX_TTS_MODEL=speech-2.8-turbo +MINIMAX_TTS_VOICE_ID=English_expressive_narrator +MINIMAX_TTS_VOICE_POOL=English_magnetic_voiced_man,English_Upbeat_Woman,English_MaturePartner + +# Video generation. Use SKG Doubao / Seedance gateway in production. +POE_API_BASE_URL=https://api.poe.com/v1 +POE_API_KEY= +VIDEO_API_BASE_URL=https://ai.skg.com/doubao +VIDEO_API_KEY= +VIDEO_MODEL=seedance +VIDEO_MODEL_SEEDANCE=doubao-seedance-2-0-fast-260128 +VIDEO_MODEL_KLING=kling-omni +VIDEO_MODEL_VEO3=veo-3.1-fast +VIDEO_CREATE_PATHS=/api/v3/contents/generations/tasks +VIDEO_STATUS_PATH=/api/v3/contents/generations/tasks/{id} +VIDEO_CONTENT_PATH=/api/v3/contents/generations/tasks/{id}/content +VIDEO_DURATION_FIELD=seconds +VIDEO_POLL_TIMEOUT_SECONDS=900 diff --git a/deploy/nginx.conf b/deploy/nginx.conf new file mode 100644 index 0000000..9e29505 --- /dev/null +++ b/deploy/nginx.conf @@ -0,0 +1,32 @@ +server { + listen 80; + server_name _; + + client_max_body_size 2g; + + gzip on; + gzip_types text/plain text/css application/json application/javascript application/xml image/svg+xml; + + location = /api { + return 308 /api/; + } + + location /api/ { + proxy_pass http://api:4291/; + proxy_http_version 1.1; + proxy_request_buffering off; + proxy_buffering off; + 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_read_timeout 1800s; + proxy_send_timeout 1800s; + proxy_connect_timeout 60s; + } + + location / { + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html; + } +} diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..791eb39 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,55 @@ +name: skg-marketing-studio + +services: + api: + build: + context: . + dockerfile: Dockerfile.api + container_name: skg-marketing-api + env_file: + - ./deploy/.env.production + environment: + JOBS_DIR: /data/jobs + CORS_ORIGINS: https://marketing.skg.com + volumes: + - ./data/jobs:/data/jobs + restart: unless-stopped + networks: + - skg-marketing-internal + + web: + build: + context: . + dockerfile: Dockerfile.web + args: + NEXT_PUBLIC_API_BASE: /api + container_name: skg-marketing-web + depends_on: + - api + restart: unless-stopped + networks: + - skg-marketing-internal + - coolify + labels: + - "traefik.enable=true" + - "traefik.docker.network=coolify" + - "traefik.http.middlewares.skg-marketing-gzip.compress=true" + - "traefik.http.middlewares.skg-marketing-redirect.redirectscheme.scheme=https" + - "traefik.http.routers.skg-marketing-http.entryPoints=http" + - "traefik.http.routers.skg-marketing-http.rule=Host(`marketing.skg.com`) && PathPrefix(`/`)" + - "traefik.http.routers.skg-marketing-http.middlewares=skg-marketing-redirect" + - "traefik.http.routers.skg-marketing-http.service=skg-marketing-http" + - "traefik.http.routers.skg-marketing-https.entryPoints=https" + - "traefik.http.routers.skg-marketing-https.rule=Host(`marketing.skg.com`) && PathPrefix(`/`)" + - "traefik.http.routers.skg-marketing-https.middlewares=skg-marketing-gzip" + - "traefik.http.routers.skg-marketing-https.tls=true" + - "traefik.http.routers.skg-marketing-https.tls.certresolver=letsencrypt" + - "traefik.http.routers.skg-marketing-https.service=skg-marketing-https" + - "traefik.http.services.skg-marketing-http.loadbalancer.server.port=80" + - "traefik.http.services.skg-marketing-https.loadbalancer.server.port=80" + +networks: + skg-marketing-internal: + name: skg-marketing-internal + coolify: + external: true diff --git a/docs/deploy-vps.md b/docs/deploy-vps.md new file mode 100644 index 0000000..a4745b5 --- /dev/null +++ b/docs/deploy-vps.md @@ -0,0 +1,63 @@ +# SKG Marketing Studio VPS Deployment + +Production domain: + +- App: `https://marketing.skg.com` +- API: `https://marketing.skg.com/api` + +Current VPS target: + +- Host: `76.13.31.179` +- OS: Ubuntu 24.04 +- Runtime: Docker Compose +- Public ingress: existing Coolify Traefik on ports 80/443 + +DNS: + +```text +marketing.skg.com A 76.13.31.179 +``` + +Do not run a host Nginx on ports 80/443. Those ports are already owned by Coolify / Traefik. This project publishes through Docker labels on the external `coolify` network. + +## First Deploy + +On the VPS: + +```bash +mkdir -p /opt/skg-marketing-studio +cd /opt/skg-marketing-studio +cp deploy/.env.production.example deploy/.env.production +``` + +Fill `deploy/.env.production` with the real production keys. Keep this file out of git. + +Then start: + +```bash +docker compose -f docker-compose.prod.yml --env-file deploy/.env.production up -d --build +``` + +Verify: + +```bash +curl -I https://marketing.skg.com +curl https://marketing.skg.com/api/health +docker compose -f docker-compose.prod.yml ps +``` + +## Update + +Sync the repo to `/opt/skg-marketing-studio`, then run: + +```bash +docker compose -f docker-compose.prod.yml --env-file deploy/.env.production up -d --build +``` + +## Runtime Notes + +- `web` is a static Next export served by Nginx. +- `web` proxies `/api/` to `api:4291`. +- `api` is only on the internal project network and stores jobs under `/data/jobs`. +- Server-side job files persist in `./data/jobs` on the VPS. +- Large uploads are allowed up to `2g` at the Nginx proxy layer. diff --git a/docs/source-analysis.html b/docs/source-analysis.html index e3aa7f9..0216075 100644 --- a/docs/source-analysis.html +++ b/docs/source-analysis.html @@ -533,6 +533,16 @@ ./scripts/stop-dev-background.sh.pids/ 里的 PID 停止后台前端 / 后端进程。 + + 生产站点 + https://marketing.skg.com + 公司域名已解析到 VPS 76.13.31.179。线上由既有 Coolify / Traefik 负责 HTTPS 入口,项目 web 容器用 Nginx 承载静态前端并把 /api/ 反代到 FastAPI。 + + + 生产部署 + docker compose -f docker-compose.prod.yml --env-file deploy/.env.production up -d --build + 服务器目录为 /opt/skg-marketing-studio;后端任务文件挂载到 ./data/jobs,真实 Key 只放服务器 deploy/.env.production。 + 前端开发服务 cd web && pnpm dev @@ -929,6 +939,17 @@ SubjectAsset {

变更记录

这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。

+
+
+

2026-05-15 · 公司域名生产部署配置

+ Runtime + Deploy +
+
+

改动:把生产入口确定为 https://marketing.skg.com,DNS 已解析到 VPS 76.13.31.179。新增 Docker Compose 生产配置:前端用 Next 静态导出 + Nginx,/api/ 反代到 FastAPI;后端任务目录持久化到服务器 ./data/jobs;Traefik 通过既有 coolify 外部网络接管 80/443。

+

影响:Dockerfile.webDockerfile.apidocker-compose.prod.ymldeploy/nginx.confdeploy/.env.production.exampledocs/deploy-vps.md.project.jsonRULES.mddocs/source-analysis.html

+
+

2026-05-15 · 本地启动改为后台不弹 Terminal