# 剧核工厂生产环境部署文档

本文档适用于在 Windows 本地构建项目，并一键部署到 Linux AMD64 服务器。
这里的“一键部署”是指服务器上的 `one_click_deployment.sh` 会一次完成环境初始化、数据库迁移、服务注册、Nginx 和 HTTPS 配置。
完整发布分为两个明确阶段：本地构建并上传、服务器执行一键部署。`push.bat` 只上传文件，不会自动修改数据库或重启线上服务。

## 1. 最短操作流程

首次部署，在 Windows 项目根目录执行：

```powershell
.\API\deploy\push.bat root@服务器IP with-env
ssh root@服务器IP
sudo bash /opt/juyou_ai/deploy/one_click_deployment.sh
```

日常更新，在 Windows 项目根目录执行：

```powershell
.\API\deploy\push.bat root@服务器IP
ssh root@服务器IP
sudo bash /opt/juyou_ai/deploy/one_click_deployment.sh
```

首次部署使用 `with-env`，将本地正式环境配置送到服务器；日常更新不使用 `with-env`，继续保留服务器已有的生产配置。
上传和服务器部署是两个独立步骤，便于在执行数据库迁移和重启前安排备份与维护窗口。

## 2. 固定路径与服务

| 项目 | 固定值 |
| --- | --- |
| 服务器项目目录 | `/opt/juyou_ai` |
| API 二进制 | `/opt/juyou_ai/bin/jcf-api` |
| 部署脚本 | `/opt/juyou_ai/deploy/one_click_deployment.sh` |
| WEB 静态目录 | `/opt/juyou_ai/static/web` |
| ADMIN 静态目录 | `/opt/juyou_ai/static/admin` |
| 数据库迁移目录 | `/opt/juyou_ai/migrations` |
| systemd 服务名 | `juyou_ai` |
| API 内部监听地址 | `127.0.0.1:8123` |
| 生产环境文件 | `/etc/juyou_ai/juyou_ai.env` |
| 临时环境文件 | `/tmp/juyou_ai.env` |
| Whisper 离线模型 | `/opt/juyou_ai/models/faster-whisper-small` |
| Whisper 运行缓存 | `/var/cache/juyou_ai/huggingface` |
| Nginx 配置 | `/etc/nginx/conf.d/juyou_ai.conf` |
| SSL 证书目录 | `/etc/nginx/ssl` |

API 仅监听服务器本机地址，公网请求统一通过 Nginx 进入。

## 3. 首次部署

### 3.1 准备本地环境

本地需要安装：

- Node.js 和 npm
- Go
- Python 3；服务器缺少离线模型时，本机还需要 `faster-whisper`
- tar
- OpenSSH Client（`ssh`、`scp`）

在终端确认命令可用：

```powershell
npm --version
go version
python --version
tar --version
ssh -V
```

首次在本机下载 Whisper 模型前安装 Python 依赖：

```powershell
python -m pip install faster-whisper==1.2.1
```

`push.bat` 不会自动安装前端依赖。首次构建或 `package-lock.json` 发生变化后，先执行：

```powershell
npm --prefix WEB ci
npm --prefix ADMIN ci
```

首次部署使用的正式环境配置保存在本地：

```text
API/.env
```

至少确认以下字段使用正式值，不能保留 `replace-with`、`change-me`、`example.com` 或开发环境默认值：

```env
DATABASE_NAME=juchuang_factory
DATABASE_USER=jcf
DATABASE_PASSWORD=数据库强密码

JWT_SECRET_KEY=随机长密钥
ADMIN_BOOTSTRAP_USERNAME=admin
ADMIN_BOOTSTRAP_PASSWORD=管理员初始强密码

CONFIG_ENCRYPTION_KEY_VERSION=v1
CONFIG_ENCRYPTION_KEY=正式加密密钥

COS_SECRET_ID=腾讯云子账号SecretId
COS_SECRET_KEY=腾讯云子账号SecretKey
COS_BUCKET=juyouai-1451538162
COS_REGION=ap-chengdu
COS_ENDPOINT=https://cos.ap-chengdu.myqcloud.com
COS_PUBLIC_BASE_URL=https://cos.youjuhui.xyz
```

COS 媒体使用独立域名时，还必须在腾讯云控制台进入 **对象存储 COS → 对应存储桶 → 安全管理 → 跨域访问 CORS 设置**，添加以下规则。将域名替换为实际访问 WEB 的域名；多个 WEB 域名需要全部列出。

```json
[
  {
    "AllowedOrigins": [
      "https://juhefactory.com",
      "https://www.juhefactory.com"
    ],
    "AllowedMethods": [
      "GET",
      "HEAD"
    ],
    "AllowedHeaders": [
      "*"
    ],
    "ExposeHeaders": [
      "Accept-Ranges",
      "Content-Length",
      "Content-Range",
      "Content-Type",
      "ETag"
    ],
    "MaxAgeSeconds": 3600
  }
]
```

保存策略后，确认媒体响应包含与当前 WEB 域名一致的 `Access-Control-Allow-Origin`。否则视频可以播放，但浏览器会禁止导出静帧和通过 Blob 直接下载。

可以使用以下命令生成随机密钥：

```bash
openssl rand -hex 32
```

### 3.2 准备服务器

目标服务器需要满足：

- Linux AMD64/x86_64，使用 Debian 或 Ubuntu 最稳妥
- 使用 systemd
- SSH 登录用户具备 sudo 权限，推荐使用 root
- 云安全组及防火墙已放行 TCP 80、443
- 启用 HTTPS 时，域名已经解析到服务器公网 IP

脚本虽然包含 `apt-get`、`dnf` 和 `yum` 的安装分支，但服务账户固定使用 `www-data`。使用非 Debian/Ubuntu 发行版前，需要先确认系统存在 `www-data` 用户和用户组，并确认 PostgreSQL、Redis 的 systemd 服务名与脚本兼容。

### 3.3 本地构建并上传

在 Windows 项目根目录执行：

```powershell
.\API\deploy\push.bat root@服务器IP with-env
```

示例：

```powershell
.\API\deploy\push.bat root@192.168.1.1 with-env
```

命令完成并显示以下内容后，才进入服务器执行部署：

```text
Deployment push complete.
```

本地上传完成只表示文件已经同步到 `/opt/juyou_ai`，此时不会自动安装依赖、迁移数据库或重启服务。

### 3.4 登录服务器并执行首次部署

本地另行登录服务器：

```powershell
ssh root@服务器IP
```

服务器上执行：

```bash
bash /opt/juyou_ai/deploy/one_click_deployment.sh
```

如果当前不是 root 用户，执行：

```bash
sudo bash /opt/juyou_ai/deploy/one_click_deployment.sh
```

首次运行会询问两项配置。

域名：

```text
juhefactory.com
```

多个域名使用空格分隔：

```text
juhefactory.com www.juhefactory.com
```

不申请 SSL 时输入：

```text
_
```

然后输入 SSL 证书邮箱；不申请 SSL 时直接回车。域名和邮箱会保存到：

```text
/opt/juyou_ai/deploy/.deploy.conf
```

首次部署完成后，将该文件复制到本地：

```powershell
scp root@服务器IP:/opt/juyou_ai/deploy/.deploy.conf .\API\deploy\.deploy.conf
```

后续上传不会携带本地 `.deploy.conf`；服务器会继续使用 `/opt/juyou_ai/deploy/.deploy.conf`，不会被本地文件覆盖。

### 3.5 验证首次部署

查看服务状态：

```bash
systemctl status juyou_ai --no-pager
systemctl status nginx --no-pager
systemctl status postgresql --no-pager
systemctl status redis-server --no-pager
```

检查 API：

```bash
curl -i http://127.0.0.1:8123/health
```

启用 HTTPS 后检查公网入口：

```bash
curl -i https://你的域名/health
```

页面地址：

```text
https://你的域名/
https://你的域名/admin/
```

## 4. 更新部署

每次更新都分为本地上传和服务器部署两步，不能只执行其中一步。

### 4.1 本地重新构建并上传

在 Windows 项目根目录执行：

```powershell
.\API\deploy\push.bat root@服务器IP
```

等待出现：

```text
Deployment push complete.
```

### 4.2 服务器应用更新

登录服务器：

```powershell
ssh root@服务器IP
```

执行：

```bash
bash /opt/juyou_ai/deploy/one_click_deployment.sh
```

更新部署会：

- 继续使用服务器现有的 `/etc/juyou_ai/juyou_ai.env`
- 执行尚未执行的数据库迁移
- 更新 systemd 与 Nginx 配置
- 重启 systemd 服务 `juyou_ai`

### 4.3 只更新已上传文件但暂不部署

只执行本地 `push.bat` 即可。上传完成后不要在服务器运行 `one_click_deployment.sh`。

此时：

- `/opt/juyou_ai` 中已经是新文件
- `/etc/juyou_ai/juyou_ai.env` 保持不变
- 当前运行中的 API 进程仍可能使用旧二进制
- 数据库迁移、Nginx 更新和服务重启均未执行

需要正式应用更新时，再执行服务器部署命令。

## 5. 推送参数说明

| 命令 | 是否上传 `API/.env` | 是否自动执行服务器部署 | 使用场景 |
| --- | --- | --- | --- |
| `push.bat root@服务器IP` | 否 | 否 | 日常增量更新 |
| `push.bat root@服务器IP stage-only` | 否 | 否 | 与默认推送行为相同，用于明确表达“只暂存” |
| `push.bat root@服务器IP with-env` | 是 | 否 | 首次部署或明确替换整套生产环境配置 |

三种命令都只负责构建、打包和上传。`stage-only` 当前是语义标记，执行行为与不传第二个参数相同。只有随后在服务器运行 `one_click_deployment.sh`，才会执行迁移、更新服务配置并重启应用。

## 6. 常用维护命令

服务管理：

```bash
systemctl restart juyou_ai
systemctl stop juyou_ai
systemctl start juyou_ai
systemctl reload nginx
nginx -t
```

查看 API 日志：

```bash
journalctl -u juyou_ai -f
journalctl -u juyou_ai -n 200 --no-pager
```

修改域名或证书邮箱：

```bash
nano /opt/juyou_ai/deploy/.deploy.conf
```

也可以删除配置，让下次部署重新询问：

```bash
rm /opt/juyou_ai/deploy/.deploy.conf
```

更新 acme.sh：

```bash
bash /opt/juyou_ai/deploy/update_acme.sh
```

## 7. 备份与故障排查

正式执行数据库迁移前应先备份数据库。以下示例中的数据库名必须替换为生产环境 `DATABASE_NAME` 的实际值：

```bash
mkdir -p /opt/juyou_ai/backups
sudo -u postgres pg_dump -Fc juchuang_factory | tee /opt/juyou_ai/backups/juchuang_factory.dump >/dev/null
```

PostgreSQL：

```bash
systemctl status postgresql --no-pager
journalctl -u postgresql -n 200 --no-pager
```

Redis：

```bash
systemctl status redis-server --no-pager
redis-cli ping
```

Nginx：

```bash
nginx -t
journalctl -u nginx -n 200 --no-pager
```

API：

```bash
systemctl status juyou_ai --no-pager
journalctl -u juyou_ai -n 200 --no-pager
ls -l /etc/juyou_ai/juyou_ai.env
ls -l /opt/juyou_ai/bin/jcf-api
```

如果部署提示环境字段缺失、格式错误或仍为示例值，修改本地 `API/.env`，重新执行 `push.bat`，然后再次在服务器运行部署脚本。

如果数据库迁移失败，应先核对数据库实际结构和 `jcf_schema_migrations`，不得通过手工插入迁移记录直接绕过错误。数据库迁移不会自动回滚。

## 8. 自动化部署流程说明

本节用于解释脚本内部做了什么。日常操作只需要按“首次部署”或“更新部署”章节执行命令。

### 8.1 `push.bat` 的本地流程

1. 检查目标 SSH 地址；仅在使用 `with-env` 时检查本地 `API/.env` 是否存在。
2. 通过 SSH 检查服务器是否已有完整的 Whisper `small` 模型。缺少时，本机运行 `download_whisper_model.py` 下载模型，并把 `models/` 加入本次上传包。
3. 执行 `npm --prefix WEB run build`，类型检查通过后将 WEB 输出到 `API/static/web`。
4. 执行 `npm --prefix ADMIN run build`，类型检查通过后将 ADMIN 输出到 `API/static/admin`。
5. 使用 `GOOS=linux`、`GOARCH=amd64`、`-trimpath` 和裁剪调试信息的链接参数，编译 `API/bin/jcf-api`。
6. 创建本机临时压缩包，包含：

```text
bin/
static/
deploy/
migrations/
acme/
workers/
models/       # 仅服务器缺少离线模型时包含
.env          # 仅使用 with-env 时包含
```

7. 使用 SCP 将压缩包上传到 `/tmp/juyou-deploy.tar.gz`。
8. 使用 SSH 在服务器的 `/tmp/juyou-deploy-work` 临时目录解包。
9. 默认删除可能残留的 `/tmp/juyou_ai.env`；使用 `with-env` 时才将 `.env` 安装为权限 `600` 的 `/tmp/juyou_ai.env`，并从项目文件中删除 `.env`。
10. 服务器存在 `rsync` 时，以 `--delete` 将发布文件同步到 `/opt/juyou_ai`；不存在时使用 `cp -a` 覆盖复制。
11. 删除服务器临时解包目录和压缩包。
12. 删除本机临时压缩包。

同步时保留以下服务器内容：

```text
/opt/juyou_ai/media/
/opt/juyou_ai/backups/
/opt/juyou_ai/models/
```

`models/` 默认不会被 `rsync --delete` 清理；仅当服务器缺少完整模型且本次压缩包含有模型时，才会单独同步模型内容。

`/opt/juyou_ai/deploy/.deploy.conf` 仅在服务器首次运行部署脚本时生成；上传包会排除本地 `.deploy.conf`，后续部署不会覆盖服务器配置。

`.go` 源码、测试代码、`cmd`、`internal`、`tools`、`media` 和 `backups` 不会进入上传包；运行时需要的 `workers` 会进入上传包。

### 8.2 `one_click_deployment.sh` 的服务器流程

1. 首次运行时只从 JuYouAI 自己的 Nginx 配置恢复域名；没有该配置时询问域名。仅复用输入域名对应的证书和私钥，不扫描其他项目的证书，并保存 `.deploy.conf`。
2. 使用 `with-env` 时检测 `/tmp/juyou_ai.env` 并将其作为本次环境配置来源，否则继续使用服务器生产环境文件。
3. 调用 `deploy_with_ssl.sh` 执行正式部署。

### 8.3 `deploy_with_ssl.sh` 和 `prepare_server.sh` 的服务器流程

1. 本机执行 `push.bat` 时检查服务器是否已有 Whisper 模型；首次部署会在本机下载并上传到 `/opt/juyou_ai/models/faster-whisper-small`。服务器不访问 Hugging Face。
2. 安装 sudo、Nginx、PostgreSQL、Redis、FFmpeg、curl、socat、ca-certificates、openssl、Python 3 和 pip；缺少时安装 `faster-whisper 1.2.1`。
3. 启用并启动 PostgreSQL、Redis 和 Nginx。
4. 存在 `/tmp/juyou_ai.env` 时将其安装到 `/etc/juyou_ai/juyou_ai.env`；否则保留现有生产环境文件，权限保持 `root:root`、`600`。
5. 将生产环境标准化为：

```env
APP_ENV=production
DEBUG=false
SERVER_ADDRESS=127.0.0.1:8123
DATABASE_HOST=127.0.0.1
DATABASE_PORT=5432
DATABASE_URL=
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
CORS_ORIGINS=根据部署域名生成
FFMPEG_PATH=ffmpeg
FFPROBE_PATH=ffprobe
PYTHON_PATH=python3
ASR_SCRIPT_PATH=/opt/juyou_ai/workers/transcribe.py
JCF_ASR_CACHE_DIR=/var/cache/juyou_ai
HF_HOME=/var/cache/juyou_ai/huggingface
FASTER_WHISPER_MODEL_PATH=/opt/juyou_ai/models/faster-whisper-small
```

6. 校验离线 Whisper 模型、数据库、JWT、管理员、配置加密和 COS 等必填配置。
7. 创建或更新 PostgreSQL 用户和数据库，配置本机认证。
8. 按文件名顺序执行 `/opt/juyou_ai/migrations/*.up.sql`，并记录到 `jcf_schema_migrations`。
9. 创建或更新 `/etc/systemd/system/juyou_ai.service`：

```ini
[Service]
User=www-data
Group=www-data
WorkingDirectory=/opt/juyou_ai
EnvironmentFile=/etc/juyou_ai/juyou_ai.env
CacheDirectory=juyou_ai
ExecStart=/opt/juyou_ai/bin/jcf-api
Restart=always
RestartSec=3
```

10. 配置 Nginx：`/api/` 和健康检查路径代理到 `127.0.0.1:8123`，`/admin/` 提供 ADMIN 静态文件，其他路径提供 WEB 静态文件，上传体积上限为 2 GiB。
11. 配置域名且已有该域名的证书时直接复用；没有证书但填写邮箱时，先创建 HTTP 验证站点，再使用 acme.sh 通过 HTTP-01 申请并安装 HTTPS 证书；域名填 `_`，或没有证书且未填写邮箱时使用 HTTP。
12. 启用 HTTPS 时把 80 端口请求重定向到 HTTPS，并允许 TLS 1.2、TLS 1.3。
13. 执行 `nginx -t`，配置检查成功后重载 Nginx。
14. 重启 systemd 服务 `juyou_ai`。
15. 部署成功后删除 `/tmp/juyou_ai.env`。

如果部署中途失败，临时环境文件可能保留，以便排查并重新执行部署；问题解决并部署成功后会自动清理。

### 8.4 重复执行与失败边界

- 脚本使用 `set -euo pipefail`，关键命令失败时会立即退出。
- 已有 PostgreSQL 用户会更新密码，已有数据库不会重复创建。
- 已记录在 `jcf_schema_migrations` 的迁移会跳过，未记录迁移按文件名顺序执行。
- systemd 和 Nginx 配置每次都会按当前发布内容重新生成。
- 已保存的域名、证书邮箱和匹配域名的现有证书会复用。
- 部署脚本不会自动恢复旧二进制，也不会自动回滚已经成功执行的数据库迁移。发布前必须自行完成数据库备份；需要应用级回滚时，应重新上传兼容当前数据库结构的旧版本并再次部署。
