# 从 Redmine 迁移到 Specivo，历史完整保留

从 Redmine 迁出，首先是数据问题，其次才是软件问题。工作项列表是最容易复制的部分，真正要紧的是历史：谁改了状态、日期为什么推迟、客户在第三条评论里说了什么、大家最终认可的是哪一版维基页面。Specivo 的迁移工具就是为了把这些历史连同人名一起完整带过去，并且明确告诉你哪些内容没有带过来。

## 适合谁

在自己的服务器上运行 Redmine、并希望继续保持这种模式的团队，规模不限。Specivo 是一个私有化部署的项目管理平台，集项目跟踪、知识库和自动化于一体。它运行在你自己的基础设施上，数据不出内网，AI 怎么用也由你决定：接云端服务、跑本地模型，或者在物理隔离网络里完全不用 AI。

## 能迁移哪些内容

导出工具直接读取 Redmine 的数据库，在一个一致的快照里完成读取，并生成一个导入包。Specivo 加载这个导入包时沿用原有标识。

- **项目与子项目**，包括父项目和已启用的模块。
- **追踪器、状态和优先级**，以及工时活动和角色。
- **用户与用户组**，包括默认邮箱、状态、语言和用户组成员。
- **项目成员关系**及其角色。只迁移直接授予的成员关系；通过用户组获得的访问权限会由用户组重新推导。
- **带完整历史的工作项**：评论、私有备注和字段变更按原顺序迁移，保留原作者和时间戳。
- **附件与内嵌图片**，从 Redmine 的 files 目录复制，相同文件只存一份。
- **关联与父任务**，包括指向其他项目、或指向另外单独导出的导入包中工作项的关联。
- **工时记录**，包括活动和用户。
- **版本与工作项类别。**
- **自定义字段**，包括字段值、可选项，以及字段适用的追踪器和项目。每个字段保留 Redmine 中的名称。
- **关注者**，包括工作项和维基页面上的关注者。
- **维基页面及其全部修订版本**，包括每个修订版本的作者、页面附件，以及页面重命名后留下的重定向。
- **工作项的预计工时**。
- **公开项目中非成员可读的内容**。

文本会从 Textile 转换为 Markdown。对其他工作项的引用（如 `#123`）会变成指向该工作项新编号的链接，前提是该工作项在本次导入包或之前的导入包中。

## 哪些内容不会迁移，以及如何查看

不会有任何内容被悄悄丢掉。导入包的清单文件列出所有被略过的内容，加载时导入报告会再次显示。例如：代码库、论坛等模块；按项目设置的优先级和工时活动；工作项以外对象上的自定义字段；版本、文档和论坛帖子上的附件；Specivo 没有的关联类型；找不到文件的附件；没有日期的工时记录。

运行导出前，还有几个行为值得了解：

- 如果引用的工作项要在之后才加载，引用会先保留为纯文本 `#N`，直到该工作项被加载。
- 历史记录里保存的新旧描述文本仍是 Redmine 的 Textile 格式。
- 如果某个值指向 Specivo 中不存在的对象（例如在 Redmine 中已删除的版本），会显示为空值；父工作项除外，它会保留为 `#123` 的形式。
- 在 Redmine 中已计划删除的项目会按活动项目导出。

## 迁移流程

1. **导出。**在能访问 Redmine 数据库的机器上，用一个只读的数据库账号运行工具。它会生成一个目录（或 `.zip` 文件），包含各类记录文件和附件。
2. **校验。**检查导入包是否有架构错误、失效引用、缺失或被改动的文件以及数量不符。
3. **试运行。**Specivo 在一个最终必定回滚的事务中加载整个导入包，并报告将使用的项目标识、冲突情况和会跳过的内容。
4. **加载。**正式加载同一个导入包。加载中断后可以续传。
5. **核对。**把 Specivo 中的数量和几个工作项与 Redmine 对照，确认无误后切换。

按发布标签安装工具：

```bash
uv tool install git+https://github.com/specivo/redmine-migration-tool@v0.1.0
```

然后依次导出、校验、加载：

```bash
export REDMINE_DB_URL='postgresql://redmine_reader:PASSWORD@db.example.com/redmine'
specivo-redmine-migration \
  --files-dir /var/lib/redmine/files \
  --instance tracker.example.com \
  --out redmine-export \
  --validate

python -m specivo.cli.import_data --source bundle --path redmine-export --validate
python -m specivo.cli.import_data --source bundle --path redmine-export --dry-run
python -m specivo.cli.import_data --source bundle --path redmine-export
```

`--instance` 名称用于标识你的 Redmine 实例，这样之后再对同一个实例导出并加载时，只会新增新记录。

## 逐个项目迁移

用 `--project` 每次只导出一个项目，然后按顺序加载各个导入包。第一次之后的每次加载都加上 `--continue-import`。共享的链接文件（`--links-file`）记录那些等待尚未加载项目的引用，等对应项目加载后自动解析。

## 支持的版本与数据库

工具已在 PostgreSQL 16 和 MySQL 8.4 上针对 Redmine 7.0.1 测试通过。需要 Python 3.12 或更高版本。

## 前提条件与安全性

- **只在你自己的服务器上运行。**导出和加载都在你控制的基础设施上进行。工具不向任何地方发送数据。
- **对 Redmine 只读。**给工具一个只能 `SELECT` 的数据库账号。
- **每次加载前先试运行。**
- **Specivo 0.11.0 或更高版本**才能加载导入包。内置的 Redmine 导入器已在该版本移除。

## 常见问题

**迁移期间 Redmine 可以继续运行吗？**可以。导出只做读取。对于大型实例，建议在只读副本上或业务低峰时段运行。临近切换时，用同一个实例名称再导出一次，并加上 `--continue-import` 加载。新增的记录会被补进来；已经导入过的记录会跳过而不是更新。

**可以重复导入吗？**可以。带 `--continue-import` 再次加载同一个导入包，不会重复创建已存在的内容。如果某次运行中途停止，用 `--resume` 可以从停止处继续。

**用户和密码怎么处理？**默认情况下，每个导入的账号首次登录时都必须设置新密码，管理员也会作为普通账号导入。如果希望大家沿用 Redmine 密码，导出和加载时都加上 `--import-passwords`，并且目标 Specivo 需要启用对应的密码哈希插件。管理员始终需要设置新密码。通过 LDAP 等外部目录登录的账号在 Redmine 中没有本地密码，导入包里自然也没有，报告中会列出这些账号。包含密码的导入包要当作敏感数据妥善保护，导入完成后立即删除。

**插件怎么办？**工具读取的是 Redmine 的核心表和 files 目录。代码库和论坛等模块会被标记为未迁移。插件存放在自有表中的数据不会被读取，所以请先查看导出报告和清单文件。

**必须一次性全部迁移吗？**不必。可以按项目导出，再按顺序加载各个导入包，方法见上文。

## 从哪里开始

1. 先搭一个 Specivo 实例。按照 [Docker 安装指南](/zh-cn/self-host-specivo-docker/) 大约十分钟就能完成，不会碰你的 Redmine。
2. 用 `--project` 导出一个小项目，对它做校验和试运行。
3. 阅读报告，调整你不认可的地方，再来一遍。
4. 准备好之后，执行完整的导出和加载。

迁移工具是开源的，托管在 [github.com/specivo/redmine-migration-tool](https://github.com/specivo/redmine-migration-tool)。Specivo 文档见 [specivo.io/docs/specivo](https://specivo.io/docs/specivo/zh-cn/)；如果你的数据必须留在无法访问互联网的网络中，[离线部署指南](https://specivo.io/docs/specivo/zh-cn/install/air-gapped/) 介绍了相应的做法。

Redmine 多年来功不可没，对很多团队来说它还能再用上几年。等到该迁移的时候，历史应该跟你一起走。