createrepo_rs 0.1.9

🦀 Pure Rust RPM repository metadata generator — creates repodata for dnf/yum repos. Zero FFI, single static binary. Drop-in replacement for createrepo_c and createrepo.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
# createrepo_rs 🦀

[![Rust](https://img.shields.io/badge/Rust-1.76%2B-orange.svg)](https://www.rust-lang.org)
[![License](https://img.shields.io/badge/license-GPL--2.0-blue.svg)](LICENSE)
[![CI](https://github.com/jamesarch/createrepo_rs/actions/workflows/ci.yml/badge.svg)](https://github.com/jamesarch/createrepo_rs/actions)
[![crates.io](https://img.shields.io/crates/v/createrepo_rs.svg)](https://crates.io/crates/createrepo_rs)

**纯 Rust 编写的 RPM 仓库元数据生成器** — 生成与 dnf / yum 兼容的 repodata(primary.xml、filelists.xml、other.xml、repomd.xml)。单一静态二进制,零 FFI 依赖,可直接替代 `createrepo_c`。

[English](README.md)

> ##### 已在 Zabbix 7.2 生产环境验证(Debian 13,80 核)。

## 🎯 为什么选择 createrepo_rs?

| | createrepo_c (C) | createrepo_rs (Rust) |
|---|---|---|
| 输出兼容性 || ✅ dnf 兼容 |
| CLI 参数 | 55 | 53 |
| 依赖 | librpm, libxml2, glib2, zchunk... | **** FFI — 纯 Rust 生态 |
| 二进制大小 | ~200KB + 动态库 | **3.5MB 静态链接** (musl) |
| 内存安全 | ❌ 手动 malloc/free | ✅ 借⽤检查器 |
| 交叉编译 | 困难 | `cargo zigbuild` 一键搞定 |
| 线程安全 | ⚠️ 容易出现竞态 | ✅ 全局 `Send + Sync` |
| I/O 超时保护 ||`--timeout` 看门狗 |
| `dnf` 兼容 ||**已验证** |
| 包清单扫描 ||`--dump-manifest` 0.08s |
| 签名检测 | ❌ 需借助 `rpm -K` | ✅ 内置 |
| 内存级 SQLite || ✅ 内存构建 + VACUUM INTO 落盘 |

## 🚀 快速开始

```bash
# 从 crates.io 安装
cargo install createrepo_rs

# 或从 git 安装
cargo install --git https://github.com/jamesarch/createrepo_rs createrepo_rs

# --- 发行版软件包 ---

# Fedora / RHEL / CentOS (COPR)
dnf copr enable jamesarch/createrepo-rs
dnf install createrepo-rs

# Arch Linux (AUR)
yay -S createrepo-rs
# 或:paru -S createrepo-rs

# RHEL / CentOS (.rpm 直接下载)
curl -LO https://github.com/jamesarch/createrepo_rs/releases/download/v0.1.9/createrepo-rs-0.1.9-1.el9.x86_64.rpm
dnf install createrepo-rs-0.1.9-1.el9.x86_64.rpm

# Debian / Ubuntu (.deb)
curl -LO https://github.com/jamesarch/createrepo_rs/releases/download/v0.1.9/createrepo-rs_0.1.9_amd64.deb
dpkg -i createrepo-rs_0.1.9_amd64.deb

# 为 RPM 目录生成元数据
createrepo_rs /path/to/rpms/

# 生产环境示例(Zabbix 镜像站)
createrepo_rs /srv/repo/ \
  --baseurl=https://mirrors.example.com/repo \
  --compress-type=zstd \
  --timeout=300 \
  --verbose
```

### Docker

```bash
# Docker Hub
docker run --rm -v /path/to/rpms:/data jamesarch/createrepo-rs /data

# GitHub Container Registry
docker run --rm -v /path/to/rpms:/data ghcr.io/jamesarch/createrepo-rs /data

# 完整选项示例
docker run --rm -v /srv/repo:/data jamesarch/createrepo-rs /data \
  --baseurl=https://mirrors.example.com/repo \
  --compress-type=zstd \
  --verbose

# 指定版本
docker run --rm -v /path/to/rpms:/data jamesarch/createrepo-rs:0.1.9 /data
```

### CI/CD 集成

CI 里可以选择 GHCR 或 Docker Hub。生产流水线建议固定版本号;如果希望始终使用最新发布版,可以用 `latest`。

```yaml
# GitHub Actions — GHCR 镜像
- name: Generate RPM repository metadata (GHCR)
  uses: docker://ghcr.io/jamesarch/createrepo-rs:0.1.9
  with:
    args: ./rpms --baseurl=https://repo.example.com --compress-type=zstd
```

```yaml
# GitHub Actions — Docker Hub 镜像
- name: Generate RPM repository metadata (Docker Hub)
  uses: docker://jamesarch/createrepo-rs:0.1.9
  with:
    args: ./rpms --baseurl=https://repo.example.com --compress-type=zstd
```

```yaml
# GitLab CI — GHCR 镜像
generate-repodata-ghcr:
  image: ghcr.io/jamesarch/createrepo-rs:0.1.9
  script:
    - createrepo_rs ./rpms --baseurl=https://repo.example.com --compress-type=zstd
```

```yaml
# GitLab CI — Docker Hub 镜像
generate-repodata-dockerhub:
  image: jamesarch/createrepo-rs:0.1.9
  script:
    - createrepo_rs ./rpms --baseurl=https://repo.example.com --compress-type=zstd
```

已发布的容器标签:

- `0.1.9` — 精确发布版本
- `0.1` — 当前 minor 线的最新 patch
- `latest` — 最新发布版
- `sha-<short-sha>` — 对应源码提交,便于追溯

## 📊 性能

基于 Zabbix 生产服务器实测(Debian 13,80 核,254 个 RPM 包):

### 完整生成(254 个包,zstd 压缩)

| 工具 | 耗时 | CPU | 输出 |
|------|------|-----|--------|
| createrepo_c | 2.15s | 454% | 232K |
| **createrepo_rs** | **1.87s** | 1724% | 14M¹ |

> ¹ 14M 包含 SQLite 数据库。使用 `--no-database` 后输出约 200K。

### 增量更新(热缓存,`--update --skip-stat`

| 工具 | 耗时 | CPU | 备注 |
|------|------|-----|-------|
| createrepo_c | 0.85s | 214% | 基于 mtime |
| createrepo_rs | 1.4s | 100% | 含 SQLite 重建 |

### `--dump-manifest` — 包清单扫描

| 范围 | 方法 | 耗时 | 线程数 |
|-------|--------|------|---------|
| 254 包 | `rpm -K` 循环 (bash) | 10s+ | 1 |
| 254 包 | **`--dump-manifest`** | **0.078s** | 80 |
| 5 包 | 受限范围扫描 | 0.2s | 80 |

### 80 核环境下的线程缩放

| 线程数 | 完整生成 | 清单扫描 |
|---------|---------------|---------------|
| 1 | 10.5s | 10.5s |
| 4 | 3.6s | 0.5s |
| **80** (自动) | **1.87s** | **0.078s**|

> createrepo_rs 默认自动使用全部 CPU。C 版本最多仅支持 5 个线程。

## 📦 功能特性

### 核心功能
- ✅ primary.xml、filelists.xml、other.xml 生成 — dnf 兼容
- ✅ repomd.xml 支持多哈希校验(sha256/sha512)
- ✅ 内存级 SQLite — 内存速度写入,完成时一次性落盘
-`--no-database` 完全跳过 SQLite 生成
- ✅ 多线程 RPM 解析(自动检测 CPU 数量)
-`--timeout=N` I/O 卡死看门狗(网络挂载、坏盘等)
-`--dump-manifest` — 并行 JSON-lines 包清单 + 签名检测
- ✅ 优雅的 Ctrl+C 处理,线程 panic 恢复(`catch_unwind`-`--update` 增量模式,使用 Arc\<Package\> 缓存

### 依赖提取(从 RPM 头)
- ✅ Provides / Requires / Conflicts / Obsoletes
- ✅ Suggests / Enhances / Recommends / Supplements
- ✅ 完整 EVR(Epoch:Version-Release)解析
- ✅ 依赖标志(EQ、LT、GT、LE、GE)

### 元数据覆盖
- ✅ Summary、description、packager、URL、license、vendor、group
- ✅ Build host、source RPM、build time、file time
- ✅ Changelog 提取,支持 `--changelog-limit`
- ✅ 文件类型检测(目录、符号链接、常规文件)
- ✅ 文件摘要(从 RPM header 读取)

### 压缩
- ✅ gzip(默认)— 最大兼容性
- ✅ zstd — 最佳速度/体积平衡
- ✅ xz — 最小体积
- ✅ bzip2 — 兼容旧系统
- ✅ XML 与元数据文件独立压缩类型(`--general-compress-type`
### CLI(53 个参数)

```bash
createrepo_rs --help
```

主要参数:

| 参数 | 说明 |
|------|-------------|
| `--workers=N` | 并行线程数(默认:全部 CPU) |
| `--timeout=N` | 全局超时时间,秒(I/O 卡死保护) |
| `--dump-manifest` | JSON-lines 包清单 + 签名检查 |
| `--compress-type=zstd` | 压缩算法 |
| `--no-database` | 跳过 SQLite 生成 |
| `--checksum=sha512` | 元数据哈希算法 |
| `--revision=12345` | 自定义仓库版本号 |
| `--baseurl=https://...` | 仓库基础 URL |
| `--simple-md-filenames` | 简洁文件名,不含哈希前缀 |
| `--cut-dirs=2` | 移除 location_href 中的 N 层目录 |
| `--update` | 增量模式(跳过未变更的包) |
| `--retain-old-md-by-age=30d` | 自动清理旧元数据 |
| `-q / -v` | 静默 / 详细输出 |
| `--compatibility` | 最大兼容模式(gzip + 简洁文件名) |
| `--update-md-path=PATH` | 从自定义路径加载已有元数据 |
| `--duplicated-nevra=error` | 重复包时报错 |
| `--location-prefix=PREFIX` | location href 前缀 |

完整 53 参数解析器见 `cli/mod.rs`(52 个选项 + PATH)。

## 🧠 架构亮点

### 内存级 SQLite(v0.1.6)

SQLite 数据库完全在内存中构建,仅完成时落盘:

```
insert_package() ──► RAM (RefCell<Connection>) ──► VACUUM INTO repomd.sqlite
                           ▲                              ▲
                    每次 INSERT O(1)           finish() 时 O(1)
```

传统做法中每个包独立事务 + 磁盘 fsync。我们的做法:单一内存连接,所有表共用一个数据库,结束时原子性刷入磁盘。

### `--dump-manifest`(v0.1.7)

二进制内置的轻量级并行包清单扫描器:

- 仅读取 RPM 签名头 + 名称/版本/架构 — **跳过文件列表、依赖、changelog**
- 通过 crossbeam + `std::thread::scope` 并行,自动适配全部 CPU
- JSON lines 格式 — 每行一个包对象,可直接被 Python/Shell 消费
- 签名检测通过 RPM 签名头标签(PGP/RSA/DSA)

80 核上扫描 254 个包仅需 **0.078 秒**。可替代 CI/CD 中的 `rpm -K` + `rpm -qp` 循环。

### I/O 韧性(v0.1.5)

仓库目录在 NFS、CIFS、FUSE 等网络挂载上可能无限期卡死。应对措施:

- `--timeout=N` 生成看门狗线程,超时强制退出
- 结果收集使用 `recv_timeout(300s)` 代替阻塞 `recv()`
- 任务提交使用 `send_timeout(30s)` 检测卡死线程
- 线程 panic 通过 `catch_unwind` 捕获 — 单个坏包不会拖垮整个线程池

### 构建信息嵌入(v0.1.5)

每个二进制自带溯源信息:

```
$ createrepo_rs --version
createrepo_rs 0.1.8
revision  a5afd72
built     2026-05-27T15:59:41
```

Git 哈希 + 编译时间戳通过 `build.rs` 在构建时注入。无运行时依赖,无需配置文件。

## 💪 性能哲学

| 原则 | 实现 |
|-----------|---------------|
| **尽量零拷贝** | `&str` 优先于 `String``Arc<Package>` 用于更新缓存 |
| **批量 I/O** | SQLite 全部在内存写入,单次磁盘刷出 |
| **默认并行** | `num_cpus::get()` 线程数,无需手动调优 |
| **轻量读取** | `read_manifest_entry()` 跳过 80% RPM 头解析 |
| **Release 配置** | LTO + opt-level=3 + codegen-units=1 + panic=abort |
| **HEAP 优先** | 64KB 读缓冲区放在堆上而非栈上 |

## 🔨 构建

```bash
# 开发构建
cargo build

# Release 构建(优化)
cargo build --release

# Linux 静态二进制(musl)— 需安装 Zig
# 安装:brew install zig (macOS) / apt install zig (Linux)
# 然后:cargo install cargo-zigbuild
cargo zigbuild --release --target x86_64-unknown-linux-musl

# ARM64 交叉编译
cargo zigbuild --release --target aarch64-unknown-linux-musl

# 交叉编译 Linux(macOS ARM → Linux x86_64)
cargo zigbuild --release --target x86_64-unknown-linux-gnu
```

## 🏗️ 项目结构

```
createrepo_rs/
├── build.rs          # 构建信息嵌入(git hash、时间戳)
├── lib.rs            # 库入口 + prelude 重导出
├── Cargo.toml        # v0.1.8, Rust 1.76+
├── src/main.rs       # 二进制入口,CLI 编排,--dump-manifest
├── cli/mod.rs        # Clap 参数解析器(53 个参数)
├── pool/mod.rs       # 并行线程池(crossbeam + catch_unwind)
├── rpm/mod.rs        # RPM 头解析(基于 `rpm` crate)
├── types/mod.rs      # 核心类型:Package、Dependency、RepomdRecord
├── compression/      # gzip、bzip2、zstd、xz
├── db/mod.rs         # 内存 SQLite + VACUUM INTO 落盘
├── xml/
│   ├── error.rs      # XML 错误类型
│   ├── mod.rs        # XML 辅助函数
│   ├── parse.rs      # XML 解析(用于 --update 缓存)
│   └── repomd.rs     # repomd.xml 生成
└── walk/mod.rs       # 目录遍历 + glob 排除
```

## 🐳 Docker 测试

```bash
cd docker-test
docker compose up -d
docker logs -f createrepo_client
```

输出:
```
✅✅✅ Success! createrepo_rs generated metadata recognized and downloaded by dnf!
```

## 📚 作为库使用

```toml
[dependencies]
createrepo_rs = "0.1"
```

```rust
use std::path::Path;
use createrepo_rs::prelude::*;

// 解析 RPM 包
let mut reader = RpmReader::open(Path::new("my-package.rpm")).unwrap();
let pkg = reader.read_package().unwrap();
println!("{} {}-{}", pkg.name, pkg.version, pkg.release);

// 检查签名(轻量 — 仅读取头部)
println!("signed: {}", reader.is_signed());

// 轻量清单扫描
let entry = reader.read_manifest_entry().unwrap();
println!("{} {} {} signed={}", entry.name, entry.version, entry.arch, entry.signed);

// 解析 EVR 依赖版本
let (epoch, ver, rel) = parse_dep_version("0:1.2.3-4");
assert_eq!(epoch, Some(0));
assert_eq!(ver.as_deref(), Some("1.2.3"));
assert_eq!(rel.as_deref(), Some("4"));

// 批量处理的线程池
let (pool, receiver) = WorkerPool::new(8);
pool.submit(Job::ProcessPackage(PathBuf::from("pkg.rpm")));
```

[`prelude`] 模块重导出所有常用类型:
- 压缩:`gzip_compress``gzip_decompress``zstd_compress``zstd_decompress`- 类型:`Package``Dependency``ChecksumType``CompressionType``ChangelogEntry`
- RPM:`RpmReader``parse_dep_version``ManifestEntry`
- DB:`RepomdDb``DbError`
- 线程池:`WorkerPool``Job``ProcessingResult`
- XML:`XmlError`
- 遍历:`DirectoryWalker``WalkError`

## 📝 更新日志

### v0.1.8 — 并行清单
- 并行 `--dump-manifest``std::thread::scope` + crossbeam)
- 轻量 `read_manifest_entry()` — 仅读头部,跳过文件/依赖/changelog
- 254 个包:10.5s → 0.078s(130 倍加速)

### v0.1.7 — 清单 + 签名检测
- `--dump-manifest` 参数 — JSON-lines 包清单输出
- `is_signed()` — 通过 RPM 签名头检测 PGP/RSA/DSA
- `ManifestEntry` 结构体:name、version、arch、signed

### v0.1.6 — 内存级 SQLite
- SQLite 完全在内存中构建,完成时通过 `VACUUM INTO` 一次性落盘
- 三张表共用一个连接(primary、filelists、other)
- 移除约 120 行冗余样板代码

### v0.1.5 — I/O 超时保护
- `--timeout` 看门狗线程强制退出
- `recv_timeout(300s)` 收集结果(原阻塞 `recv()`- `send_timeout(30s)` 提交任务(原阻塞 `send()`- `catch_unwind` 线程 panic 恢复
- 构建信息嵌入(`--version` 显示 git hash + 时间戳)
- SQLite `transaction()` 自动回滚

### v0.1.4 — 首次公开发布
- 52/55 CLI 参数
- `dnf` 兼容(Docker 集成测试验证)
- dnf 兼容的 XML 输出

## 📝 许可证

GPL-2.0-or-later — 与 createrepo_c 相同。

## 🙏 致谢

原始 C 实现:[rpm-software-management/createrepo_c](https://github.com/rpm-software-management/createrepo_c)

构建依赖:
- [rpm-rs/rpm]https://github.com/rpm-rs/rpm — 纯 Rust RPM 解析器
- [dralley/rpmrepo_metadata]https://github.com/dralley/rpmrepo_metadata — EVR 解析参考(作者 [@dralley]https://github.com/dralley,Red Hat)
- [quick-xml]https://github.com/tafia/quick-xml — 快速 XML 写入
- [rusqlite]https://github.com/rusqlite/rusqlite — SQLite 绑定
- [crossbeam]https://github.com/crossbeam-rs/crossbeam — MPMC 通道