chore: add safe production deploy script
This commit is contained in:
5
RULES.md
5
RULES.md
@@ -31,14 +31,15 @@
|
|||||||
- 文档 / 解析:`docs/source-analysis.html`(项目内独立文档,不公开挂主应用路由)
|
- 文档 / 解析:`docs/source-analysis.html`(项目内独立文档,不公开挂主应用路由)
|
||||||
- 管理后台:待定
|
- 管理后台:待定
|
||||||
- 服务器目录:`/opt/skg-marketing-studio`
|
- 服务器目录:`/opt/skg-marketing-studio`
|
||||||
- 生产启动:`docker compose -f docker-compose.prod.yml --env-file deploy/.env.production up -d --build`
|
- 生产部署唯一入口:`./scripts/deploy-prod-safe.sh`(先在服务器备份 `deploy/.env.production`、`data/jobs`、资源库和 `secrets`,再用受保护 rsync 同步代码,最后 Docker 重建并运行 `verify-prod-docker.sh`)
|
||||||
|
- 生产容器重建命令:`docker compose -f docker-compose.prod.yml --env-file deploy/.env.production up -d --build`;只允许脚本内部或明确只重启容器时使用,不允许再用裸 `rsync --delete` 手动同步。
|
||||||
- 生产架构:`web` 容器用 Nginx 承载 Next 静态导出;`/login/`、`/_next/`、`/assets/`、`/skg-logo-black.svg`、`/oasis-source/` 等登录页必需静态资源公开访问;未登录访问工作台跳转 `/login/`,`/api/` 通过 Nginx `auth_request` 校验 FastAPI 会话 Cookie 后反代到 `skg-marketing-api:4291`;Traefik 通过 `coolify` 外部网络接入 80/443
|
- 生产架构:`web` 容器用 Nginx 承载 Next 静态导出;`/login/`、`/_next/`、`/assets/`、`/skg-logo-black.svg`、`/oasis-source/` 等登录页必需静态资源公开访问;未登录访问工作台跳转 `/login/`,`/api/` 通过 Nginx `auth_request` 校验 FastAPI 会话 Cookie 后反代到 `skg-marketing-api:4291`;Traefik 通过 `coolify` 外部网络接入 80/443
|
||||||
- Web 验收必须以生产 Docker 形态为准:前端是 `next export` 静态产物 + Nginx,不是 `next dev` / `next start`。任何 Web 改动部署后必须运行 `./scripts/verify-prod-docker.sh`,确认 `/login/`、`/_next/`、`/api/health`、本地 API 地址泄漏和 API 镜像 `.env` 污染检查通过;不能只用本地 `npm run build` 作为上线依据。
|
- Web 验收必须以生产 Docker 形态为准:前端是 `next export` 静态产物 + Nginx,不是 `next dev` / `next start`。任何 Web 改动部署后必须运行 `./scripts/verify-prod-docker.sh`,确认 `/login/`、`/_next/`、`/api/health`、本地 API 地址泄漏和 API 镜像 `.env` 污染检查通过;不能只用本地 `npm run build` 作为上线依据。
|
||||||
- 当前音频解析:`https://ai.skg.com/azure/v1` 的 `gpt-4o-transcribe` 当前返回 `DeploymentNotFound`,且官方 Azure OpenAI transcription 路径探测也未返回可用部署;生产临时复制本地成功策略,直接使用容器内 `faster-whisper tiny.en` 真实转写,关闭 Gemini 多模态音频兜底。拿到真实 Azure ASR deployment 名后再恢复 `ASR_REMOTE_ENABLED=true`。
|
- 当前音频解析:`https://ai.skg.com/azure/v1` 的 `gpt-4o-transcribe` 当前返回 `DeploymentNotFound`,且官方 Azure OpenAI transcription 路径探测也未返回可用部署;生产临时复制本地成功策略,直接使用容器内 `faster-whisper tiny.en` 真实转写,关闭 Gemini 多模态音频兜底。拿到真实 Azure ASR deployment 名后再恢复 `ASR_REMOTE_ENABLED=true`。
|
||||||
- 持久化目录:服务器 `./data/jobs` 挂载到后端 `/data/jobs`;全局资源中心持久化在 `./data/asset_library`、`./data/prompt_library` 和 `./data/_trash`
|
- 持久化目录:服务器 `./data/jobs` 挂载到后端 `/data/jobs`;全局资源中心持久化在 `./data/asset_library`、`./data/prompt_library` 和 `./data/_trash`
|
||||||
- TikTok 下载登录态:公开视频默认不带 cookies 直接下载,生产环境变量必须显式保持 `YTDLP_COOKIES_FILE=`、`YTDLP_COOKIES_FROM_BROWSER=` 为空,防止容器读取不存在的浏览器 cookies。只有 TikTok 明确要求登录态时,才使用服务器私有 cookies 文件 `./secrets/tiktok_cookies.txt` 挂载到 API 容器 `/run/secrets/tiktok_cookies.txt` 并配置 `YTDLP_COOKIES_FILE=/run/secrets/tiktok_cookies.txt`;`yt-dlp` 会在任务结束时回写 cookies,因此不要把该挂载设为只读;不要使用云端浏览器读取方案,也不要把 cookies 入库。生产容器严禁使用 `YTDLP_COOKIES_FROM_BROWSER=chrome`。
|
- TikTok 下载登录态:公开视频默认不带 cookies 直接下载,生产环境变量必须显式保持 `YTDLP_COOKIES_FILE=`、`YTDLP_COOKIES_FROM_BROWSER=` 为空,防止容器读取不存在的浏览器 cookies。只有 TikTok 明确要求登录态时,才使用服务器私有 cookies 文件 `./secrets/tiktok_cookies.txt` 挂载到 API 容器 `/run/secrets/tiktok_cookies.txt` 并配置 `YTDLP_COOKIES_FILE=/run/secrets/tiktok_cookies.txt`;`yt-dlp` 会在任务结束时回写 cookies,因此不要把该挂载设为只读;不要使用云端浏览器读取方案,也不要把 cookies 入库。生产容器严禁使用 `YTDLP_COOKIES_FROM_BROWSER=chrome`。
|
||||||
- 登录凭证:用户名写下方快捷登录;密码明文备份只放服务器 `/root/skg-marketing-studio-login.txt`,生产环境变量 `WEB_AUTH_PASSWORD` / `WEB_AUTH_SESSION_SECRET` 只放服务器 `deploy/.env.production`
|
- 登录凭证:用户名写下方快捷登录;密码明文备份只放服务器 `/root/skg-marketing-studio-login.txt`,生产环境变量 `WEB_AUTH_PASSWORD` / `WEB_AUTH_SESSION_SECRET` 只放服务器 `deploy/.env.production`
|
||||||
- 手动 `rsync` 到服务器时必须排除本机开发文件和真实生产 env:`.git`、`.memory`、`.logs`、`.pids`、`data`、`jobs`、`secrets`、`api/.env`、`api/.env.local`、`api/.env.production`、`deploy/.env.production`、`web/node_modules`、`web/.next`、`web/out`。不要把本地 `api/.env` 或 `deploy/.env.production` 覆盖到 `/opt/skg-marketing-studio`,否则会把开发 cookies / API 配置烤进生产镜像或清空生产登录与模型配置。
|
- 禁止手动裸 `rsync --delete` 到服务器;必须使用 `./scripts/deploy-prod-safe.sh`。如遇极端情况必须手动同步,命令必须同时包含 protect/exclude:`.git`、`.memory`、`.logs`、`.pids`、`data`、`jobs`、`secrets`、`api/jobs`、`api/.env`、`api/.env.local`、`api/.env.production`、`deploy/.env.production`、`web/node_modules`、`web/.next`、`web/out`。不要把本地 `api/.env` 或 `deploy/.env.production` 覆盖到 `/opt/skg-marketing-studio`,也不要删除服务器 `data/jobs`,否则会清空案例、登录和模型配置。
|
||||||
|
|
||||||
## 快捷登录
|
## 快捷登录
|
||||||
- 登录地址:`https://marketing.skg.com/login/`
|
- 登录地址:`https://marketing.skg.com/login/`
|
||||||
|
|||||||
@@ -540,8 +540,8 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>生产部署</td>
|
<td>生产部署</td>
|
||||||
<td><code>docker compose -f docker-compose.prod.yml --env-file deploy/.env.production up -d --build</code></td>
|
<td><code>./scripts/deploy-prod-safe.sh</code></td>
|
||||||
<td>服务器目录为 <code>/opt/skg-marketing-studio</code>;后端任务文件挂载到 <code>./data/jobs</code>,全局资源中心挂载到 <code>./data/asset_library</code>、<code>./data/prompt_library</code> 和 <code>./data/_trash</code>,真实 Key 只放服务器 <code>deploy/.env.production</code>。Web 上线验收必须按 Docker 静态形态跑 <code>./scripts/verify-prod-docker.sh</code>,不能只用本地 <code>npm run build</code> 替代。</td>
|
<td>服务器目录为 <code>/opt/skg-marketing-studio</code>;后端任务文件挂载到 <code>./data/jobs</code>,全局资源中心挂载到 <code>./data/asset_library</code>、<code>./data/prompt_library</code> 和 <code>./data/_trash</code>,真实 Key 只放服务器 <code>deploy/.env.production</code>。生产部署唯一入口是 <code>deploy-prod-safe.sh</code>:先备份服务器 env、案例和资源库,再用 protect/exclude 保护 <code>data/</code>、<code>jobs/</code>、<code>secrets/</code>、<code>deploy/.env.production</code> 后同步代码,最后 Docker 重建并跑 <code>verify-prod-docker.sh</code>。禁止再用裸 <code>rsync --delete</code> 手动同步。</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>前端开发服务</td>
|
<td>前端开发服务</td>
|
||||||
@@ -1131,6 +1131,18 @@ ProductRefStateItem {
|
|||||||
<h2>变更记录</h2>
|
<h2>变更记录</h2>
|
||||||
<p>这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。</p>
|
<p>这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。</p>
|
||||||
<div class="changelog">
|
<div class="changelog">
|
||||||
|
<article class="change">
|
||||||
|
<header>
|
||||||
|
<h3>2026-05-20 · 生产部署增加数据保护脚本</h3>
|
||||||
|
<span class="tag amber">Deploy</span>
|
||||||
|
<span class="tag rose">Safety</span>
|
||||||
|
</header>
|
||||||
|
<div class="body">
|
||||||
|
<p><strong>问题:</strong>手动 <code>rsync --delete</code> 如果没有排除服务器 <code>data/jobs</code> 和真实 <code>deploy/.env.production</code>,会把生产案例、资源库或登录配置删掉。</p>
|
||||||
|
<p><strong>改动:</strong>新增 <code>scripts/deploy-prod-safe.sh</code> 作为生产部署唯一入口。脚本部署前会在服务器创建 <code>/opt/skg-marketing-studio-backups/skg-marketing-preserve-*.tgz</code>,备份真实 env、案例、资源库和 secrets;同步时用 <code>rsync --filter='P ...'</code> 和 exclude 双重保护 <code>data/</code>、<code>jobs/</code>、<code>secrets/</code>、<code>api/jobs</code>、<code>deploy/.env.production</code> 和本地开发文件。</p>
|
||||||
|
<p><strong>影响:</strong>后续发布不再手写裸 <code>rsync --delete</code>;脚本会自动 Docker 重建并调用 <code>verify-prod-docker.sh</code>。若误操作,先从最新 <code>skg-marketing-preserve-*.tgz</code> 恢复。</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
<article class="change">
|
<article class="change">
|
||||||
<header>
|
<header>
|
||||||
<h3>2026-05-20 · 转换层改为提示词确认后生成</h3>
|
<h3>2026-05-20 · 转换层改为提示词确认后生成</h3>
|
||||||
|
|||||||
71
scripts/deploy-prod-safe.sh
Executable file
71
scripts/deploy-prod-safe.sh
Executable file
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
HOST="${HOST:-root@76.13.31.179}"
|
||||||
|
APP_DIR="${APP_DIR:-/opt/skg-marketing-studio}"
|
||||||
|
BACKUP_DIR="${BACKUP_DIR:-/opt/skg-marketing-studio-backups}"
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "--no-build" ]]; then
|
||||||
|
BUILD_FLAG=""
|
||||||
|
else
|
||||||
|
BUILD_FLAG="--build"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> Preflight: creating remote data/env backup"
|
||||||
|
ssh "$HOST" "set -euo pipefail
|
||||||
|
cd '$APP_DIR'
|
||||||
|
mkdir -p '$BACKUP_DIR'
|
||||||
|
stamp=\$(date +%Y%m%d%H%M%S)
|
||||||
|
tar -czf '$BACKUP_DIR/skg-marketing-preserve-'\$stamp'.tgz' \
|
||||||
|
deploy/.env.production \
|
||||||
|
data/jobs \
|
||||||
|
data/asset_library \
|
||||||
|
data/prompt_library \
|
||||||
|
data/_trash \
|
||||||
|
secrets 2>/tmp/skg-backup-warnings.log || {
|
||||||
|
cat /tmp/skg-backup-warnings.log >&2 || true
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
find '$BACKUP_DIR' -name 'skg-marketing-preserve-*.tgz' -type f -printf '%T@ %p\n' | sort -nr | tail -n +8 | cut -d' ' -f2- | xargs -r rm -f
|
||||||
|
echo backup:\$(ls -t '$BACKUP_DIR'/skg-marketing-preserve-*.tgz | head -1)
|
||||||
|
"
|
||||||
|
|
||||||
|
echo "==> Syncing code with production data protected"
|
||||||
|
rsync -az --delete \
|
||||||
|
--filter='P /data/***' \
|
||||||
|
--filter='P /jobs/***' \
|
||||||
|
--filter='P /secrets/***' \
|
||||||
|
--filter='P /deploy/.env.production' \
|
||||||
|
--filter='P /api/jobs/***' \
|
||||||
|
--filter='P /api/.env' \
|
||||||
|
--filter='P /api/.env.local' \
|
||||||
|
--filter='P /api/.env.production' \
|
||||||
|
--exclude='/.git/' \
|
||||||
|
--exclude='/.memory/' \
|
||||||
|
--exclude='/.logs/' \
|
||||||
|
--exclude='/.pids/' \
|
||||||
|
--exclude='/data/' \
|
||||||
|
--exclude='/jobs/' \
|
||||||
|
--exclude='/secrets/' \
|
||||||
|
--exclude='/api/jobs/' \
|
||||||
|
--exclude='/api/.env' \
|
||||||
|
--exclude='/api/.env.local' \
|
||||||
|
--exclude='/api/.env.production' \
|
||||||
|
--exclude='/deploy/.env.production' \
|
||||||
|
--exclude='/web/node_modules/' \
|
||||||
|
--exclude='/web/.next/' \
|
||||||
|
--exclude='/web/out/' \
|
||||||
|
--exclude='/node_modules/' \
|
||||||
|
--exclude='内部分享-口播脚本.md' \
|
||||||
|
./ "$HOST:$APP_DIR/"
|
||||||
|
|
||||||
|
echo "==> Rebuilding production containers"
|
||||||
|
ssh "$HOST" "cd '$APP_DIR' && docker compose -f docker-compose.prod.yml --env-file deploy/.env.production up -d $BUILD_FLAG"
|
||||||
|
|
||||||
|
echo "==> Verifying production"
|
||||||
|
"$ROOT_DIR/scripts/verify-prod-docker.sh" "$HOST"
|
||||||
|
|
||||||
|
echo "==> Done"
|
||||||
Reference in New Issue
Block a user