# 如何升级自托管的 Specivo 实例

升级 Specivo，就是切换到更新的 `specivo/specivo` Docker 镜像。容器入口脚本会在启动时自动执行新的数据库迁移，所以整个流程很直接：备份、固定目标版本、拉取镜像、重启、验证。本文以从 0.1.9 升级到 0.1.10 为例，操作环境与安装指南相同，是一台 1 vCPU / 2 GB 内存的 Ubuntu 24.04 云主机。

全文的工作目录都是 `/opt/specivo`，安装指南把 Compose 文件、`.env` 和 `specivo-data/` 目录都放在这里。

## 你将完成的步骤

- 动手前先确认整个服务栈运行正常
- 导出数据库并打包数据目录
- 在 `.env` 中固定目标版本
- 拉取新镜像并重启服务栈
- 验证所有组件都已在新版本上正常运行

## 开始之前

你需要一套已安装好的 Specivo（按安装指南部署的环境）、root 的 shell 访问权限，以及几分钟时间。拉取任何东西之前，先阅读目标版本的发布说明：CHANGELOG 在代码仓库里，每个 GitHub Release 也都链接到它。先弄清变更内容，再执行升级。

## 第 1 步：确认当前运行的版本

SSH 登录服务器，进入安装目录：

```bash
cd /opt/specivo
```

确认所有容器都处于健康状态：

```bash
docker compose ps
```

你应该看到六个容器：`nginx`、`api`、`db`、`redis`、`celery-worker` 和 `celery-beat`，状态全部为 `Up (healthy)`。如果有容器在反复重启或不健康，先解决这个问题再继续。

查看当前的数据库 schema 版本：

```bash
docker compose exec -T api alembic current
```

记下输出。本文示例中是 `0020 (head)`。升级完成后还要再查一次。

检查磁盘空间。拉取会下载新的镜像层，备份归档在转移到其他主机之前也会一直占用磁盘：

```bash
df -h /
```

本文用的这台主机 48 GB 磁盘还剩 43 GB，足够容纳拉取的镜像和大约 280 MB 的备份归档。

## 第 2 步：先做备份

> **每次升级都必须执行这一步。**迁移可能会修改数据库 schema，而且不一定可逆。如果迁移已经执行、升级却出了问题，备份是唯一的退路。不要跳过。

如果还没有备份目录，先创建一个：

```bash
mkdir -p /opt/specivo-backups
```

导出数据库。必须加 `-T` 参数，这样才不会分配伪终端，输出才能正确通过管道写入文件：

```bash
docker compose exec -T db pg_dump -U specivo specivo \
  > /opt/specivo-backups/backup-$(date +%Y%m%d-%H%M%S).sql
```

本文导出的文件约 210 KB，是一份完整的纯 SQL 快照，包含每一个工作项（Issue）、维基（Wiki）页面、评论和历史记录。

打包数据目录。这会把附件、日志、主题、内置数据库文件以及磁盘上的嵌入模型一并打包：

```bash
tar czf /opt/specivo-backups/specivo-data-$(date +%Y%m%d-%H%M%S).tar.gz specivo-data
```

本文的归档约 279 MB，其中大部分是约 400 MB 的嵌入模型（在磁盘上本身已是压缩格式）和 PostgreSQL 数据文件。

继续之前，把这两个文件转移到主机之外：对象存储、外接硬盘或远程服务器都可以。只放在待升级这台机器上的备份，不算真正的备份。

## 第 3 步：固定目标版本

用编辑器打开 `.env`，把 `SPECIVO_VERSION` 从当前值改成你要升级到的版本：

```bash
SPECIVO_VERSION=0.1.10
```

如果你的 `.env` 还保留着初次安装时的 `SPECIVO_VERSION=latest`，现在正是改掉的好时机。固定到明确的标签，意味着每次升级的时机由你决定，而且你始终清楚运行的是哪个镜像。回滚也同样简单：重新固定到旧标签即可。

## 第 4 步：拉取新镜像

```bash
docker compose pull
```

Compose 只会下载有变化的镜像层。拉取结束时会输出类似这样的一行：

```
Image specivo/specivo:0.1.10 Pulled
```

`db`（pgvector）、`redis` 和 `nginx` 镜像不属于 Specivo 的发布周期，除非你改过它们固定的版本，否则这一步不会重新拉取。

如果你在 `.env` 中设置了 `SPECIVO_IMAGE=ghcr.io/specivo/specivo`（用于无法访问 Docker Hub 的服务器），同一镜像会从 GitHub Container Registry 拉取，输出行会显示为 `Image ghcr.io/specivo/specivo:<version> Pulled`。从 0.12.0 起，每个版本都会以相同的标签同时发布到两个镜像仓库。

## 第 5 步：重启服务栈

```bash
docker compose up -d
```

Compose 会把运行中的容器与 Compose 文件对比，只重建镜像发生变化的容器。本次升级重建了三个：`specivo-api-1`、`specivo-celery-worker-1` 和 `specivo-celery-beat-1`。`db`、`redis` 和 `nginx` 的镜像没有变化，容器原样保留。

Compose 会等 `db` 和 `redis` 报告健康后再启动 `api`，启动顺序不需要你自己管理。

**迁移会自动执行。**入口脚本每次启动都会运行 `alembic upgrade head`。你会在 API 日志中看到类似这样的输出：

```
Running database migrations...
INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
```

如果某个版本确实包含 schema 变更，这些行之间还会出现 `Running upgrade <rev> -> <rev>`，也就是 alembic 按顺序逐个执行新迁移。

你永远不需要手动运行迁移命令。盯着日志，确认启动过程没有报错：

```bash
docker compose logs -f api
```

看到 `Application startup complete.` 后按 `Ctrl-C` 停止跟踪日志。

有一点需要说明：这次从 0.1.9 到 0.1.10 的升级没有新增 schema 迁移，`alembic current` 在升级前后都是 `0020 (head)`。入口脚本依然执行了迁移步骤，这一步是幂等的。其他版本之间的升级可能包含 schema 迁移，正因如此，无论你是否预期有 schema 变更，升级前的备份每次都是必做项。

## 第 6 步：验证

确认所有容器都已使用新镜像：

```bash
docker ps
```

`api`、`celery-worker` 和 `celery-beat` 三行都应显示 `specivo/specivo:0.1.10`。`api` 容器应报告 `Up (healthy)`。

检查登录页能否响应：

```bash
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:9933/
```

返回 `302` 并重定向到登录页，就是正常的健康响应。

再次确认 schema 版本一致：

```bash
docker compose exec -T api alembic current
```

最后在浏览器中打开应用，登录，打开一个工作项，再执行一次搜索。三项都正常，升级就完成了。

## 回滚

如果新版本出现异常，在 `.env` 中重新固定到之前的标签即可回滚：

```bash
SPECIVO_VERSION=0.1.9
```

然后拉取并重启：

```bash
docker compose pull
docker compose up -d
```

> **如果失败的升级已经执行了 schema 迁移，仅重新固定标签是不够的。**数据库 schema 可能已经领先于旧镜像，旧镜像会拒绝启动或运行异常。这种情况下，你必须恢复升级前的数据库导出文件和 `specivo-data/` 归档，再启动旧镜像。先恢复数据目录，再把 SQL 导出文件载入数据库：
>
> ```bash
> docker compose down
> tar xzf /opt/specivo-backups/specivo-data-<timestamp>.tar.gz
> docker compose up -d db
> docker compose exec -T db psql -U specivo specivo \
>   < /opt/specivo-backups/backup-<timestamp>.sql
> docker compose up -d
> ```
>
> 这正是升级前备份每次都必须执行的原因。

## 0.1.10 的新变化

升级之前，一定要先阅读目标版本的 CHANGELOG。0.1.10 的主要变更如下：

- **多语言界面**。现已支持俄语、中文、法语、西班牙语和泰语。管理员可以设置工作区级别的默认语言，每个用户也可以在个人资料设置中自行覆盖。
- **Markdown 编辑器**。工作项描述和维基页面现在使用 EasyMDE，带有工具栏和实时预览。编辑器内的预览渲染由新增的预览端点提供支持。
- **主题与描述统一编辑**。一个按钮即可同时打开这两个字段，取代了之前分开编辑的流程。
- **新增 MCP 工具**。元数据架构管理工具现已通过 MCP 提供。`list_issues` 工具支持 `metadata key=value` 筛选条件，AI 助手可以据此按结构化元数据查询工作项。
- **缺陷修复**。CSS 和 JS 文件名加入内容哈希，升级后不再受浏览器旧缓存影响。修复了 Celery 任务注册问题。提升了 Markdown 编辑器的稳定性。

## 好习惯

- 每次只升级一个小版本，不要一次跨越多个版本。迁移序列是按逐步递增的方式设计的。
- 拉取每个版本之前，都先阅读它的发布说明。
- 每次都备份，哪怕看起来只是补丁版本。
- 在 `.env` 中始终把 `SPECIVO_VERSION` 固定到明确的标签。生产实例绝不要跑在 `latest` 上。
- 每次升级后做一次快速冒烟测试：登录、打开一个工作项、执行一次搜索。