# 更新与回滚操作文档

本文档说明生产服务器上如何更新代码、更新镜像、以及出现问题时如何回滚。
适用服务器：`lsmserver`（Ubuntu，Docker Compose + systemd 部署）。

## 0. 环境速览

| 服务 | 源码/配置位置 | 运行方式 | 容器名 | 数据位置 |
| --- | --- | --- | --- | --- |
| API | `/home/ubuntu/mediaplayer`（git 仓库） | `docker compose up -d --build` | `sentence_api-oral-trainer-api-1` | `/home/ubuntu/mediaplayer/sentence_api/data` |
| Whisper | 部署副本 `/opt/whisper`（来自仓库 `whisper/`） | systemd：`whisper-transcribe.service` | `whisper-transcribe` | 模型缓存在 Docker 卷 `hf-hub-cache` |
| Nginx | `/etc/nginx/sites-available/oral-trainer` | systemd：`nginx` | — | — |

关键事实：

- API 的代码是**构建进镜像**的（Dockerfile 里 `COPY sentence_api /app/sentence_api`），
  只 `git pull` 不会让运行中的容器生效，必须重新构建并重建容器。
- 只有 `./data` 被挂载进容器，重建容器不影响视频和数据库。
- Whisper 的 `/opt/whisper` 是仓库 `whisper/` 目录的副本，`git pull` 不会自动同步到
  `/opt/whisper`，需要手动复制。
- Whisper 的 systemd 模板 `ExecStop` 是 `docker compose down`（若未改过），
  `systemctl stop whisper-transcribe` 会删除容器；日常请用 `systemctl restart`。

## 1. 更新前准备

### 1.1 确认当前状态正常

```bash
docker ps                    # 两个容器都在运行
curl http://127.0.0.1:8000/healthz          # API 正常
curl http://127.0.0.1:9000/v1/models        # Whisper 正常
```

### 1.2 记录当前版本，便于回滚

```bash
cd /home/ubuntu/mediaplayer
git rev-parse HEAD           # 记录当前 commit
git status                   # 确认没有未提交的本地改动
docker images | grep sentence_api   # 记录当前镜像
```

### 1.3 备份数据（强烈建议每次更新前执行）

```bash
cd /home/ubuntu/mediaplayer/sentence_api

# 停 API 保证 SQLite 备份一致
docker compose stop oral-trainer-api

mkdir -p /home/ubuntu/backups
sudo tar -czf "/home/ubuntu/backups/oral-trainer-$(date +%F-%H%M).tar.gz" data

# 恢复启动
docker compose up -d
```

备份文件包含全部视频、录音和数据库，放在 `/home/ubuntu/backups/`。

## 2. 更新 API（最常见）

```bash
cd /home/ubuntu/mediaplayer
git pull                     # 拉取最新代码

cd sentence_api
docker compose up -d --build # 重新构建镜像并重建容器
```

验证：

```bash
docker compose ps            # 状态应为 healthy
docker compose logs -f oral-trainer-api   # 看启动日志
curl http://127.0.0.1:8000/healthz
```

确认新代码已生效：

```bash
docker images | grep sentence_api   # 看镜像创建时间是否为刚才
```

### 2.1 只改了 `.env`（无需重新构建）

```bash
cd /home/ubuntu/mediaplayer/sentence_api
docker compose up -d         # 检测到配置变化会自动重建容器
```

### 2.2 只改了 Nginx 配置

```bash
sudo cp sentence_api/nginx.conf.example /etc/nginx/sites-available/oral-trainer
sudo nginx -t
sudo systemctl reload nginx
curl -H 'Host: videoservice.d1kt.cn' http://127.0.0.1/healthz
```

## 3. 更新 Whisper（不常需要）

Whisper 的代码不常变；以下情况才需要更新：

- 改了 `/opt/whisper` 里的配置或 compose 文件；
- 想升级镜像（`ghcr.io/speaches-ai/speaches:latest-cuda` 是 latest 标签，
  不会自动拉新版本）。

```bash
# 1. 拉代码并同步到 /opt/whisper（保留服务器上的 .env，不覆盖）
cd /home/ubuntu/mediaplayer
git pull
rsync -a --exclude .env whisper/ /opt/whisper/

# 2. 如果 compose 或 systemd 单元文件有改动，先重载
sudo systemctl daemon-reload

# 3. 如果需要升级镜像
cd /opt/whisper
docker compose pull

# 4. 重启服务（会自动重建容器）
sudo systemctl restart whisper-transcribe
```

验证：

```bash
systemctl status whisper-transcribe
curl http://127.0.0.1:9000/v1/models
```

注意：模型缓存在 Docker 卷 `hf-hub-cache` 里，重启不会重新下载模型。

## 4. 回滚

### 4.1 回滚 API 代码

```bash
cd /home/ubuntu/mediaplayer
git log --oneline -5         # 找到要回退到的 commit

# 方式一：回退到指定版本（会生成反向提交，保留 git 历史）
git revert <有问题的commit>

# 方式二：直接切回旧 commit（分离头指针，之后记得切回分支）
git checkout <上一版commit>

# 重新构建并重建容器
cd sentence_api
docker compose up -d --build
```

验证回滚是否生效：`curl http://127.0.0.1:8000/healthz`，并观察日志。

### 4.2 回滚数据

```bash
cd /home/ubuntu/mediaplayer/sentence_api
docker compose stop oral-trainer-api

# 用更新前备份恢复（会覆盖当前数据，请确认）
sudo tar -xzf /home/ubuntu/backups/oral-trainer-<备份时间>.tar.gz -C .

docker compose up -d
```

### 4.3 回滚 Whisper

```bash
# 从仓库历史中取出旧版 whisper/ 文件，重新同步
cd /home/ubuntu/mediaplayer
git checkout <上一版commit> -- whisper/

rsync -a --exclude .env whisper/ /opt/whisper/
sudo systemctl restart whisper-transcribe
```

## 5. 常见问题排查

| 现象 | 原因 | 处理 |
| --- | --- | --- |
| 更新后接口行为没变 | 忘了 `--build`，容器还在跑旧镜像 | 执行 `docker compose up -d --build` |
| 构建时 pip 安装超时 | 服务器访问 PyPI 受限 | 给 Dockerfile 的 pip install 加 `-i https://pypi.tuna.tsinghua.edu.cn/simple` 后重新构建 |
| 构建时拉基础镜像超时 | Docker Hub 网络问题 | 检查 `/etc/docker/daemon.json` 的 `registry-mirrors`（已配置 DaoCloud） |
| `healthz` 里 `moss_configured: false` | `.env` 里 `MOSS_TRANSCRIBE_URL` 为空或改了未重建 | 改 `.env` 后执行 `docker compose up -d` |
| Whisper 9000 端口连不上 | 服务未运行或容器被删 | `systemctl status whisper-transcribe`；容器被删时用 `systemctl start` 重建 |
| API 重启后视频卡在 `processing` | 重启中断了处理流程 | 后台点“重新处理”即可 |
| `git pull` 失败 | 本地有未提交改动或认证问题 | `git status` 检查；`git stash` 暂存改动；确认 `git remote -v` 和凭据 |
