gekap/fast-copy
GitHub: gekap/fast-copy
一款跨平台的高速文件复制工具,通过 SSH 流式传输、内容去重和物理磁盘块顺序 I/O 技术,显著提升本地与远程大文件及海量小文件的拷贝速度。
Stars: 18 | Forks: 3
# fast-copy — 具备去重和 SSH 流式传输的高速文件复制器
一款快速、跨平台的命令行工具,以最大顺序磁盘速度复制文件和目录。专为 USB 驱动器、外接 HDD、NAS 备份和大型 SSH 传输而设计。
## 下载
适用于 Windows、macOS 和 Linux 的预编译 CLI 和 GUI 二进制文件可在 [发布页面](https://github.com/gekap/fast-copy/releases) 获取。
**核心功能:**
- **基于 Reflink 的 btrfs / XFS / APFS / ReFS 复制** — 仅元数据的 CoW 克隆(Linux 上使用 `FICLONE`,macOS 上使用 `clonefile(2)`)可使同一卷上 10 GB 的复制在几毫秒内完成
- **稀疏文件感知** *(v3.1.0+)* — VM 磁盘镜像和 Longhorn 副本通过 `SEEK_DATA` / `SEEK_HOLE` 复制,因此未分配的空洞永远不会传输到网络或目标磁盘上(在实际备份中:2.3 TB 逻辑大小 → 磁盘上仅 12 GB)
- **每个命令支持多个源** *(v3.1.0+)* — `fast-copy /var/lib/longhorn/replicas/pvc-* /mnt/backup` 接受 N 个源树,并像 `cp -r` 风格一样按 basename 保留每个源
- **`--use-sudo` 自动提权 + 防篡改审计日志** *(v3.1.0+)* — 针对仅限 root 访问的路径在 sudo 下重新执行,并写入包含谁运行了什么内容的 `chattr +i` JSONL 审计跟踪
- **安全强化的 sudo 流程** *(v3.1.1+)* — 处处使用 `O_NOFOLLOW`,审计文件位于 `~$SUDO_USER`,脚本权限预检,在 sudo 下拒绝执行 `--update`
- 按物理磁盘顺序读取文件(消除 HDD 上的随机寻道)
- **内容感知去重** — 仅复制一次每个唯一文件,对重复文件进行硬链接或 reflink
- **自动文件系统检测** — 检测目标上的 reflink/hardlink/symlink/none 能力,并选择最安全的去重策略
- **如实计算去重** — 在不支持链接的目标(FAT32、exFAT)上,将“节省的带宽”与磁盘使用量分开报告
- **跨运行去重数据库** — SQLite 缓存会记住跨运行的哈希值
- **显式哈希算法选择** — `--hash=auto|xxh128|sha256`,`auto` 优先使用更快的 `xxh128`(如果可用)
- **SSH 远程传输** — 通过 tar 管道流进行本地↔远程和远程↔远程传输
- **分块流式传输** — 100 MB 的 tar 批次,支持流式解压(无临时文件)
- **传输前空间检查** 和 **复制后验证**
- **兼容 Synology NAS** — 通过可移植的 tar stdin 处理支持 busybox / 非标准操作系统
- **单文件复制的文件路径目标** — `fast_copy host:file.tar.gz /local/renamed.tar.gz` 的作用类似于 `scp`/`cp`
- 可在 **Linux**、**macOS** 和 **Windows**(包括超过 260 个字符的长路径)上运行
## 为什么选择 fast-copy?
| 问题 | 解决方案 |
|---------|----------|
| 由于随机寻道,`cp -r` 在 HDD 上速度很慢 | 按物理磁盘顺序读取文件以实现顺序吞吐 |
| 复制成千上万个缓慢的小文件极其痛苦 | 将小文件打包成 tar 流批次 |
| 重复文件浪费空间和时间 | **内容感知去重** — 只复制一次,其余进行硬链接 |
| 复制中途失败时才提示没有空间检查 | 写入任何数据之前进行**预飞空间检查** |
| 廉价 USB 驱动器上的静默数据损坏 | **复制后验证**确认完整性 |
| 需要在多个操作系统上使用 | **跨平台** — Linux、macOS、Windows,具备原生 I/O 优化 |
| 在两台服务器之间复制非常痛苦 | 通过 SSH tar 管道流进行**远程到远程中继** |
| SFTP 速度慢(约 1-2 MB/s) | **原生 SSH tar 流**绕过 SFTP 开销(局域网上 5-10 MB/s) |
## 工作原理
### 本地到本地复制
文件复制分为 5 个阶段:
1. **扫描** — 遍历源树,记录每个文件及其大小
2. **去重** — 对文件进行哈希处理(xxHash-128 或 SHA-256)以找到相同的内容。每个唯一文件仅复制一次;重复文件变为硬链接
3. **空间检查** — 验证目标是否有足够的可用空间容纳去重后的数据
4. **物理布局** — 解析磁盘上的物理偏移量(Linux 上的 `FIEMAP`,macOS 上的 `fcntl`,Windows 上的 `FSCTL`)并按块顺序对文件进行排序
5. **块复制** — 大文件(≥1 MB)使用 64 MB 缓冲区进行复制。小文件通过 tar 管道(生产者→消费者,磁盘上无临时文件)进行流式传输。重复文件被重新创建为硬链接
复制完成后,将根据源哈希验证所有文件。
### SSH 远程传输
支持三种远程复制模式:
| 模式 | 工作原理 |
|------|-------------|
| **本地 → 远程** | 文件作为分块的 tar 批次通过 SSH 流式传输。远程端运行 `tar xf -` 实时解压 |
| **远程 → 本地** | 远程端运行 `tar cf -`,本地端进行流式解压 — 数据到达时即写入磁盘(无临时文件) |
| **远程 → 远程** | 数据通过您的本地机器中继:源 `tar cf` → SSH → 本地中继缓冲区 → SSH → 目标 `tar xf` |
**分块 tar 流式传输:** 文件被分割成约 100 MB 的批次。每个批次都是通过 SSH 传输的独立 tar 流。这提供了:
- 每个批次的进度更新
- 错误恢复(部分批次不会丢失已完成的工作)
- 无临时文件 — 流式解压直接将文件写入磁盘
- 大文件(≥1 MB)在解压期间获得分块进度更新
**远程源去重:** 文件哈希通过 `python3` 或 `sha256sum` 在远程服务器上通过 SSH 运行,分批处理,每批 5,000 个文件以避免超时。
**无 SFTP 操作:** 当远程服务器上可用 `tar` 时,所有传输均使用原生 SSH 通道而不是 SFTP。这避免了 SFTP 协议开销,甚至可以在禁用 SFTP 的服务器(例如 Synology NAS)上运行。清单通过 exec 命令读取/写入,并以 SFTP 作为后备。
### 缓冲区如何工作
缓冲区是一个固定大小的传输窗口。即使是 500 GB 的文件,一次也只在内存中占用 64 MB:
```
Source (500GB file) Buffer (64MB) Destination file
┌──────────────────┐ ┌─────────┐ ┌──────────────────┐
│ chunk 1 (64MB) │──read──│ 64MB │──write──▶ │ chunk 1 (64MB) │
│ chunk 2 (64MB) │──read──│ 64MB │──write──▶ │ chunk 2 (64MB) │
│ ... │ │ (reused)│ │ ... │
│ chunk 7813 │──read──│ 64MB │──write──▶ │ chunk 7813 │
└──────────────────┘ └─────────┘ └──────────────────┘
= 500GB complete
```
使用 `--buffer` 进行调整:低内存系统使用 `--buffer 8`,高速 SSD 使用 `--buffer 128`。
### 远程到远程如何工作
当源和目标都是远程 SSH 服务器时,数据通过您的本地机器进行中继:
```
┌─────────────┐ ┌───────────────┐ ┌─────────────┐
│ Source SSH │ tar │ Your machine │ tar │ Dest SSH │
│ server │ ─────▶ │ (relay) │ ─────▶ │ server │
└─────────────┘ cf - └───────────────┘ xf - └─────────────┘
```
这两台服务器不需要直接相互连接。数据以约 100 MB 的 tar 批次流式传输 — 您的机器永远不会存储完整的数据集。
### 文件系统检测和去重策略
在第 2 阶段之前,fast-copy 会检测目标文件系统并探测其实际能力(硬链接、符号链接、reflink CoW 克隆、区分大小写)。检测在缓存预热时约需 5 毫秒,并使用廉价的各操作系统专属 API(Linux 上的 `/proc/self/mountinfo`,macOS 上的 `statfs(2)`,Windows 上的 `GetVolumeInformationW`),仅对不明确的文件系统(XFS reflink、NTFS Dev Drive、网络挂载、FUSE)进行针对性探测。
检测到的策略会显示在横幅的 `Dedup:` 行旁边,并决定去重如何链接以及唯一文件如何被复制:
| 目标文件系统 | 策略 | 复制机制 | 去重链接机制 |
|---|---|---|---|
| btrfs, XFS (reflink=1), APFS, bcachefs | **reflink** | `FICLONE`/`clonefile`(仅元数据,瞬间完成) | reflinks(CoW;修改其中一个对等文件不会影响其他文件) |
| ext4, tmpfs, NTFS, HFS+, f2fs, NFS, SMB 及大多数其他 | **hardlink** | 带有大缓冲区的字节流复制 | `os.link()` 硬链接(共享 inode) |
| FAT32, exFAT, 部分 FUSE 挂载 | **none** | 字节流复制 | 完整复制(无法创建链接) |
### 基于 Reflink 的复制 (v3.1.0+)
在 btrfs / 开启 reflink 的 XFS / APFS / bcachefs 上,fast-copy 使用内核的 CoW 克隆原语,而不是读取和写入字节:
- **Linux**:在 btrfs、XFS(`reflink=1`)、bcachefs 上使用 `ioctl(FICLONE)`
- **macOS**:在 APFS 上使用 `clonefile(2)` — 与 `cp` 在 macOS Big Sur+ 内部使用的原语相同
- **Windows**:通过 `FSCTL_DUPLICATE_EXTENTS_TO_FILE` 实现 ReFS reflinks(推迟 — 未来版本发布)
这意味着:
- 在同一个 btrfs 卷上的 **10 GB 复制**可以在**毫秒级**完成,而不是几分钟
- 将 `/home` 备份到 `/mnt/btrfs/backup` 本质上是**零成本**的,直到您开始修改文件
- Synology DS720+ 用户(在 `/volume1` 上使用 btrfs)可获得近乎即时的本地备份
- macOS 用户可以获得与 `cp` 已经提供的相同速度 — fast-copy 以前在 APFS 上执行相同操作时较慢
当源和目标位于**不同的文件系统**上时(例如从 `/home` ext4 复制到 `/mnt/btrfs`),无法进行 reflink,fast-copy 会自动回退到字节流复制。通过 `st_dev` 进行的同一文件系统检查发生在任何系统调用之前。
**重要的架构特性**:Reflinks 是**写时复制** 的。如果您修改了两个 reflink 文件中的一个,内核只会为该文件分配新块 — 另一个对等文件保持不变。这对于任何增量更新工作流来说,从根本上**比硬链接更安全**:
```
Hardlinks: Reflinks:
fileA ┐ fileA → blocks 1-100
├→ inode 12345 fileB → blocks 1-100 (shared)
fileB ┘
After modifying fileB:
After modifying fileA: fileA → blocks 1-100 (unchanged)
fileA ┐ fileB → blocks 1-100 (CoW: new alloc only for changes)
├→ inode 12345 (NEW)
fileB ┘ ← also changed!
```
在支持 reflink 的目标上运行输出:
```
Phase 5 — Block copy
Strategy: reflink (CoW) for 5 files, 12.0 MB
Metadata-only clone — no data is read or written.
██████████████████████████████ 100% 12.0 MB in 0.1s avg 209.1 MB/s
Duplicate handling:
✓ Reflinks: 4 (CoW shared blocks; modifying one does not affect peers)
→ all reflinked (CoW; safe to modify peers)
```
在不支持链接的文件系统上(`strategy: none`),去重摘要会**如实报告发生的情况**:
```
Dedup complete:
Unique files: 44718
Total duplicates: 46951 (51.2% of files)
Bandwidth saved: 378.5 MB (transfer only)
Disk usage: 888.2 MB (full copies — FS does not support links)
```
并且第 3 阶段的空间检查使用完整的未去重大小,因此您永远不会因为误导性的去重计算而在复制中途遇到 `ENOSPC`。
要获取包含 FS 类型、能力矩阵和检测/探测时间的详细输出,请传入 `-v` / `--verbose`:
```
FS: xfs → reflink
hardlink=y symlink=y reflink=y case=sens
detect=4.3ms probe=1.1ms (4 probes)
```
### 针对 VM 镜像和 Longhorn 副本的批量备份工作流 (v3.1.0+)
v3.1.0 添加了一系列功能,这些功能共同使 fast-copy 适用于系统管理员风格的批量备份:从需要 root 权限的系统路径中复制许多稀疏 VM 磁盘或 Longhorn 副本,并具有防篡改的审计跟踪。
**每个命令支持多个源。** 传入任意数量的源路径,后跟目标 — 每个源都作为其自己的子树复制到目标下,并保留其 basename:
```
# Shell glob 展开为 N 个 source 路径
fast-copy /var/lib/longhorn/replicas/pvc-* /mnt/backup_pvc/
# 或者显式地列出它们
fast-copy /etc /var/log /home/operator /mnt/incident_snapshot/
```
现有的单源 `fast-copy SRC DST` 调用不受影响。
**稀疏文件感知(Linux/macOS)。** 当 `st_blocks * 512 < st_size` 时,文件会被自动检测为稀疏文件,并通过 `SEEK_DATA` / `SEEK_HOLE` 进行复制,因此未分配的空洞永远不会传输到网络或目标磁盘。第 3 阶段的空间检查在支持稀疏文件的目标上使用**已分配的**字节数,因此包含 12 GB 真实数据的 2.3 TB 稀疏树不再会被 900 GB 的目标拒绝。扫描输出会提前报告摘要:
```
Sparse: 346 sparse files — 2.3 TB logical, 12.2 GB on disk
Data to write: 12.2 GB (after sparse holes skipped 2.2 TB)
```
在不支持空洞的 Windows 和文件系统(FAT32、exFAT)上回退到密集复制。SSH 传输的网络格式仍然是密集的 — 支持稀疏感知的复制仅适用于本地→本地目标。
**`--use-sudo` 自动提权。** 在源或目标需要 root 权限(Longhorn 副本、容器卷系统路径)的常见情况下,省去了输入 `sudo python fast_copy.py …` 的麻烦。fast-copy 在 sudo 下重新执行自身,并让 sudo 像往常一样在终端上提示输入密码。仅限 Linux/macOS。
```
fast-copy --use-sudo /var/lib/longhorn/replicas/pvc-x123 /mnt/backup/
```
**防篡改审计日志。** 在 sudo 下运行时(通过 `$SUDO_USER` 检测),fast-copy 会将一个隐藏的 `.fast_copy_audit.jsonl` 写入 `~$SUDO_USER/` — 每次运行对应一条 JSON 记录,捕获提权前的用户名、完整命令、源/目标、每个文件的复制列表和运行摘要。每次写入后,该文件都会被设置 `chattr +i`(不可变),因此即使是 root 也无法编辑或删除它,除非先运行 `chattr -i`。下一次 sudo 运行会清除该标志,追加其记录,并重新设置为不可变。在不支持不可变属性的 tmpfs/FAT32/NFS 上会优雅降级(写入不受保护的记录,并伴有警告)。
检查:`sudo cat ~/.fast_copy_audit.jsonl`(读取不可变文件有效)。删除:`sudo chattr -i && sudo rm `。
### `--use-sudo` 的安全模型 (v3.1.1+)
该便捷标志会在 sudo 下重新执行工具,因此 fast-copy 在提权期间执行的任何操作都以 root 身份运行。v3.1.1 堵住了该流程中的七个本地权限提升路径,以防范同一主机上可以写入源树、目标树或脚本目录的非 root 攻击者:
- **在每次打开目标时使用 `O_NOFOLLOW`**(块流 / 稀疏 / 单个文件 / SFTP / tar 解压路径)。像 `/file -> /root/.bashrc` 这样的植入符号链接不再重定向具有 root 权限的写入。
- **审计文件移动到 `~$SUDO_USER`**,使用 `O_NOFOLLOW`、通过 fd 进行 `fchmod`,并拒绝 `st_nlink > 1`,因此预先植入的符号链接或审计路径上的硬链接无法欺骗 root 对敏感文件执行 `chattr +i` / `chmod 0600` / 追加操作。
- **在 POSIX 上源遍历使用 `followlinks=False`** 并对每个条目进行 `lstat` 检查:在 sudo 下,所有符号链接都会被拒绝并显示可见的“Skipped N symlinks”警告;在没有 sudo 的情况下,仅跳过其真实路径逸出源根目录的符号链接。
- **sudo 下 TOCTOU 安全的源读取。** 所有五个文件读取生产者都通过一个共享的 opener 路由,该 opener 在提权时会添加 `O_NOFOLLOW`,因此利用扫描→复制窗口进行竞态攻击的攻击者无法将常规文件替换为符号链接并窃取 `/etc/shadow`。
- **在 sudo 下拒绝执行 `--update`。** 受损的发布商不再能自动对 root 进行木马攻击 — 用户必须在更新后显式地重新提权。可选的 `--update-sha256 `(来自发布页面的 64 字符十六进制字符串)增加了带外完整性锁定。
- **对脚本和解释器进行 `--use-sudo` 预检。** 如果 `fast_copy.py`、其目录或 `sys.executable` 的所有者不是 root/调用者,或者可以被组/全局写入,则拒绝提权。堵住了“编辑脚本并等待”的木马攻击路径。
- **SSH `known_hosts` 路由到 `~$SUDO_USER`**,以便接受的 TOFU 密钥为人类操作者保留,而不是消失在 `/root/.ssh/` 中。
对于常规文件的非提权复制,CLI 没有任何变化。在 sudo 下,唯一的行为变化是跳过源中的符号链接(带有可见警告),而不是静默地跟随它们。
### 哈希算法选择
fast-copy 使用内容哈希来检测去重期间的重复项,并在复制后验证文件。使用 `--hash` 选择算法:
| 标志 | 算法 | 何时使用 |
|---|---|---|
| `--hash=auto` *(默认)* | 如果安装了 `xxhash` 包则为 `xxh128`,否则为 `sha256` | 通用 — 最快的可用选项 |
| `--hash=xxh128` | xxh128(128 位,速度快约 10 倍) | 强制使用快速的非加密哈希。如果缺少 `xxhash`,会报错并给出清晰的安装提示。 |
| `--hash=sha256` | SHA-256(加密) | 强制使用抗碰撞性哈希 — 推荐用于对抗性环境,或者当您希望对精心构造的碰撞提供强力保证时 |
所选算法会预先显示在横幅中,以便信任边界可见:
```
Hash: xxh128 (non-cryptographic; default)
```
或者
```
Hash: sha256 (cryptographic; forced)
```
### 重复文件处理摘要
在第 5 阶段之后,fast-copy 会按类型详细列出重复项在目标上的实际处理方式:
```
Duplicate handling:
✓ Hardlinks: 46951 (shared inode; zero extra disk)
→ all disk savings realized
```
在无法使用链接的 FAT32 上:
```
Duplicate handling:
✗ Full copies: 2 (FS does not support links — no disk savings)
→ no disk savings (bandwidth only)
```
混合情况(罕见 — 某些文件系统会回退到符号链接):
```
Duplicate handling:
✓ Hardlinks: 45 (shared inode; zero extra disk)
~ Symlinks: 3 (pointer to canonical; canonical must not be deleted)
✗ Full copies: 2 (FS does not support links — no disk savings)
→ 48/50 linked, 2 copied
```
## 平台要求
| 平台 | 最低版本 | 备注 |
|----------|----------------|-------|
| **Windows** | Windows 7 SP1 | 预编译二进制文件从 **v2.4.5+** 起兼容(使用 Python 3.8 构建)。v2.2.0–v2.4.4 版本需要 Windows 8.1+ |
| **macOS** | macOS 10.13 (High Sierra) | 提供 ARM64 (Apple Silicon) 和 Intel x86_64 二进制文件 |
| **Linux** | 任何具有 glibc 2.17+ 的版本 | x86_64 二进制文件;或在任何架构上运行 Python 脚本 |
在所有平台上直接运行 Python 脚本时,都需要 Python 3.8 或更高版本。
## 安装
```
# 直接使用 Python 3.8+ 运行
python fast_copy.py
# SSH 支持需要 paramiko
python -m pip install paramiko
# 可选:约 10 倍快的 hashing
python -m pip install xxhash
```
### 各平台专属的 xxHash 安装
| 平台 | 命令 |
|----------|---------|
| Debian/Ubuntu | `sudo apt install python3-xxhash` |
| Fedora/RHEL | `sudo dnf install python3-xxhash` |
| Arch | `sudo pacman -S python-xxhash` |
| macOS | `brew install python-xxhash` |
| Windows | `python -m pip install xxhash` |
如果未安装 xxHash,fast-copy 会静默回退到 SHA-256。
## 桌面 GUI
可选的原生桌面 GUI (`fast_copy_gui_qt.py`) 公开了**每一个** CLI 功能
— 所有四种传输模式(L2L / L2R / R2L / R2R)、去重、元数据保留、
SSH、排除模式和调优 — 都集成在一个美观的深色主题窗口中。它是一个
轻量级外壳:它构建命令行并作为子进程运行 `fast_copy.py`,因此
经过验证的复制引擎会保持不变地完成工作。
```
# 安装 GUI 依赖(engine 本身仍然保持 stdlib-only)
python -m pip install -r requirements-gui.txt # PySide6
# 启动
python fast_copy_gui_qt.py
```
功能:
- **多源支持** — 添加行以在同一个目标下并行复制多个源
(cp -r 风格)。当列出多个源时,SSH 源将被禁用,
这与引擎的设定一致。
- **实时进度** — 带有速度、ETA、文件数和字节数的实时进度条,
以及引擎输出的滚动日志。只读的**命令预览**显示
将要运行的确切命令。
- **试运行 / 开始 / 取消** — 取消会发送一个中断信号,以便引擎干净地停止
(与 CLI 上的 Ctrl-C 走相同的 `Interrupted.` 路径)。
### 从 GUI 使用 SSH
- **基于密钥的身份验证是推荐的路径**,并且可以毫无保留地使用:在
源或目标字段中输入 `user@host:/path`,展开 **SSH** 面板,并
将其指向您的私钥(或依赖您的 SSH 代理 / 默认密钥)。
- **密码身份验证** 通过环境变量将密码传递给
子进程来支持(绝不通过命令行,因此它永远不会出现在 `ps` 或
命令预览中)。这需要当前的引擎构建(`--ssh-src-password-env` /
`--ssh-dst-password-env`);GUI 会自动检测支持情况,否则会提示您
使用密钥身份验证。
### 从 GUI 以 root 身份运行
勾选 **Run as root** 以在 Linux 上通过 **pkexec**(图形化 PolicyKit 提示)提权。
在 pkexec 不可用的地方(macOS、最小化安装),该选项被禁用 —
请在终端中使用 `--use-sudo` 运行 CLI 以进行 root 复制。因为 pkexec 会清理
环境,SSH 密码无法与 **Run as root** 结合使用;在这种
情况下,请对远程端点使用密钥身份验证。
## 对象存储 — S3, Azure Blob, Google Cloud Storage (v3.6.0+)
云 URL 可以在**源和目标**上同时使用,支持各个方向:
```
# 安装你需要的 cloud SDK(全部可选,lazily imported)
python -m pip install -r requirements-cloud.txt
fast_copy.py /data s3://bucket/backups/ # upload
fast_copy.py s3://bucket/backups/ /restore/ # download
fast_copy.py s3://bucket/a/ s3://bucket/b/ # bucket-to-bucket (server-side)
fast_copy.py /data az://container/backups/ # Azure Blob
fast_copy.py /data gs://bucket/backups/ # Google Cloud Storage
```
支持的协议:**`s3://`**(AWS + 兼容 S3 的:MinIO, Cloudflare R2,
Wasabi, Backblaze B2),**`az://`**(Azure Blob),**`gs://`**(原生 GCS)。
亮点:
- **往返保真度** — 每个对象都存储了 fast-copy 元数据
(`fc_relpath`, `fc_mtime`, `fc_mode`, `fc_hash`, …);下载时会恢复
时间戳和模式,并重新哈希以验证完整性。
- **去重** — 在一次运行中,重复文件通过服务器端复制
(S3 CopyObject / Azure Copy Blob / GCS rewrite)完成,因此字节永远不会离开
云端;跨运行时,未更改的树会通过清单对象跳过。
节省的带宽与使用的存储量分开报告。
- **验证** — 上传会根据存储的哈希值进行 HEAD 采样;
下载会重新哈希并进行比较(不匹配将以非零状态退出)。
### 凭据
| 提供商 | 标志 | 环境 / 默认链 |
|----------|-------|------------------------------|
| S3 | `--endpoint-url`, `--s3-region`, `--s3-profile` | `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`, `~/.aws`, 实例配置文件 |
| Azure | `--az-connection-string`, `--az-account`, `--az-key` | `AZURE_STORAGE_CONNECTION_STRING` / `AZURE_STORAGE_ACCOUNT` + `AZURE_STORAGE_KEY` |
| GCS | `--gcs-project`, `--gcs-credentials` | 应用默认凭据 |
在 **GUI** 中,在源/目标字段中输入云 URL 并在
**Settings → Cloud credentials** 中填写;密钥通过
环境变量传递给引擎,绝不通过命令行。
#### 命名连接(多个账户 / S3 供应商)
对于多个 S3 端点(例如 Artesca, Qumulo, MinIO, AWS)外加 Azure 和
GCS,将每一个保存为**命名连接**,并在 URL 中通过
`scheme://name@bucket/key` 引用它:
```
# 交互式地创建/管理 connections(secret 输入时隐藏,文件权限为 0600)
fast_copy.py creds add artesca # type=s3, endpoint, key/secret …
fast_copy.py creds add aws
fast_copy.py creds list # names/types/endpoints, secrets masked
fast_copy.py creds test artesca # live connection check
# 然后为每个 endpoint 进行选择 — source 和 destination 可以使用不同的 vendors:
fast_copy.py s3://minio@data/ s3://aws@backups/
fast_copy.py s3://artesca@vol1/ az://azureprod@container/
```
连接位于**`fast_copy.py` 旁边的 `credentials.json`**(其自身
目录)中,引擎会自动加载它。这是可预测的,随
脚本一起移动,并避免了 Microsoft Store 版 Python 静默
虚拟化写入的 `%APPDATA%` 沙箱。使用 `FAST_COPY_CREDENTIALS` 环境变量、
传递给 `creds` 的显式路径参数或 `--credentials-file PATH` 进行覆盖。其结构是一个
`{"connections": {name: {type, …}}}` 映射(`type` 为 `s3`/`az`/`gs`)。GUI 的
**Cloud credentials** 面板通过其*已保存的
连接*下拉菜单读取/写入相同的文件。当未给出 `name@` 时,将使用名为 `default` 的连接(与 URL 协议匹配)。
#### 静态加密
该文件可以**加密**,因此密钥不会以明文形式存储:
```
fast_copy.py creds encrypt # AES-256-GCM; prompts for a passphrase
fast_copy.py creds decrypt # back to plaintext
fast_copy.py creds rekey # re-bind after updating fast_copy.py
fast_copy.py creds lock | unlock # set/clear OS immutability (needs root)
```
设计(及诚实的局限性):
- **机密性来自您的密码**(`scrypt` → AES-256-GCM),通过
隐藏提示或 `FAST_COPY_CREDS_PASSPHRASE` 提供。GUI 有一个匹配的
*Creds passphrase* 字段,通过环境变量传递给引擎。
- 该文件**绑定到此 `fast_copy.py`**(其 SHA-256 是加密器的
关联数据)以实现**防篡改** — 能够检测到被替换的二进制文件。因为
*密钥*是您的密码,正常的更新永远不会将您锁在外面;它只会警告
并且 `creds rekey` 会重新绑定。
- `creds lock` 是**防篡改,不是保密,也不是绝对的** — 设置它
需要 root 权限,而 root 总是可以逆转它。它在 Linux 之外不可用/较弱## 文档
每个 CLI 标志和 GUI 控件都记录在 **[DOCUMENTATION.md](DOCUMENTATION.md)** 中 — 每个选项的作用、默认值以及何时更改。相同的内容也会在 GUI 的 **Help → Documentation** 下显示。
## 用法
```
usage: fast_copy.py [-h] [--buffer BUFFER] [--threads THREADS] [--dry-run]
[-v] [--no-verify] [--no-dedup] [--hash {auto,xxh128,sha256}]
[--no-cache] [--force] [--overwrite] [--exclude EXCLUDE]
[--log-file LOG_FILE] [--use-sudo]
[--ssh-src-port PORT] [--ssh-src-key PATH] [--ssh-src-password]
[--ssh-dst-port PORT] [--ssh-dst-key PATH] [--ssh-dst-password]
[-z]
source [source ...] destination
positional arguments:
source One or more source folders, files, globs, or remote
(user@host:/path). When two or more positionals are given,
the last is the destination and earlier ones are sources.
destination Destination path or remote (user@host:/path)
options:
-h, --help Show help message and exit
--version, -V Show version and exit
--check-update Show available updates and release notes
--update [VERSION] Download and install latest (or a specific version).
Refused under sudo (v3.1.1+).
--update-sha256 HEX Verify the downloaded binary against a 64-char SHA-256
obtained out-of-band from the GitHub release page (v3.1.1+).
--buffer BUFFER Buffer size in MB (default: 64)
--threads THREADS Threads for hashing/layout (default: 4)
--dry-run Show copy plan without copying
-v, --verbose Verbose output (full FS detection details, etc.)
--no-verify Skip post-copy verification
--log-file LOG_FILE Write structured JSON log to file
--no-dedup Disable deduplication
--hash ALGO Hash algorithm: auto (default), xxh128, or sha256
auto = xxh128 if installed, else sha256
xxh128= 10× faster, non-cryptographic
sha256= cryptographic, collision-resistant
--no-cache Disable persistent hash cache (cross-run dedup database)
--force Skip space check, copy even if not enough space
--overwrite Overwrite all files, skip identical-file detection
--exclude EXCLUDE Exclude files/dirs by name (can use multiple times)
--use-sudo Re-exec self under sudo if not already root (v3.1.0+).
Useful when source or destination needs root, e.g.
/var/lib/longhorn/replicas. Linux/macOS only. Refuses
to elevate if the script or its directory is
group/world-writable (v3.1.1+).
SSH source options:
--ssh-src-port PORT SSH port for remote source (default: 22)
--ssh-src-key PATH Path to SSH private key for remote source
--ssh-src-password Prompt for SSH password for remote source
SSH destination options:
--ssh-dst-port PORT SSH port for remote destination (default: 22)
--ssh-dst-key PATH Path to SSH private key for remote destination
--ssh-dst-password Prompt for SSH password for remote destination
General SSH options:
-z, --compress Enable SSH compression (good for slow links)
```
## 示例
### 本地复制
```
# 将文件夹复制到 USB 驱动器
python fast_copy.py /home/kai/my-app /mnt/usb/my-app
# 复制单个文件
python fast_copy.py ~/Downloads/Rocky-10.0-x86_64-dvd1.iso /mnt/usb/
# Glob pattern
python fast_copy.py "~/Downloads/*.zip" /mnt/usb/zips/
# Windows
python fast_copy.py "C:\Projects\my-app" "E:\Backup\my-app"
```
### SSH 远程传输
```
# 本地到 remote
python fast_copy.py /data user@server:/backup/data --ssh-dst-password
# Remote 到本地
python fast_copy.py user@server:/data /local/backup --ssh-src-password
# Remote 到 remote(通过你的机器进行 relay)
python fast_copy.py user@src-host:/data admin@dst-host:/backup/data \
--ssh-src-password --ssh-dst-password
# 自定义端口和 key
python fast_copy.py user@host:/data /local \
--ssh-src-port 2222 --ssh-src-key ~/.ssh/id_ed25519
# Destination 位于非标准端口(例如,Synology NAS)
python fast_copy.py /local/data "user@nas:/volume1/Shared Folder/backup" \
--ssh-dst-port 2205 --ssh-dst-password
```
### 批量备份工作流 (v3.1.0+)
```
# 同时处理多个 source(cp -r 风格)
fast-copy /var/lib/longhorn/replicas/pvc-* /mnt/backup_pvc/
# Sparse VM 磁盘 — 仅读取和写入已分配的字节
fast-copy --use-sudo /var/lib/libvirt/images /mnt/backup/
# 在 sudo 下自动提权;将不可变的审计日志写入 ~/.fast_copy_audit.jsonl
fast-copy --use-sudo /etc /var/log /home/operator /mnt/incident_snapshot/
# 根据发布页面上的 hash 验证 self-update
fast-copy --update --update-sha256
```
### 其他选项
```
# Dry run(预览而不进行复制)
python fast_copy.py /data /mnt/usb/data --dry-run
# 带有完整 FS 检测详情的 verbose 输出
python fast_copy.py /data /mnt/usb/data -v
# 强制使用 SHA-256(加密的,抗碰撞)进行 dedup hashing
python fast_copy.py /data /mnt/usb/data --hash=sha256
# 强制使用 xxh128(最快)— 如果未安装 xxhash 则报错
python fast_copy.py /data /mnt/usb/data --hash=xxh128
# 将单个文件以新名称复制到 destination(类似 cp/scp)
python fast_copy.py user@host:/data/archive.tar.gz /backup/renamed.tar.gz
# 跳过 deduplication(对于已知唯一的文件速度更快)
python fast_copy.py /data /mnt/usb/data --no-dedup
# 按名称排除文件/目录
python fast_copy.py /project /mnt/usb/project --exclude node_modules --exclude .git
# 写入所有操作的结构化 JSON 日志
python fast_copy.py /data /mnt/usb/data --log-file copy.json
```
### 结构化 JSON 日志
`--log-file` 选项会写入包含以下内容的机器可读 JSON 日志:
- **摘要** — 源、目标、模式、复制/链接/跳过/出错的文件、写入的字节数、速度、去重节省量
- **每个文件的条目** — 操作(`copied`、`linked`、`skipped`、`error`)、路径、大小、方法、链接目标、错误消息
```
{
"timestamp": "2026-04-04T13:25:48.680170+00:00",
"summary": {
"source": "/data", "destination": "/mnt/usb/data",
"mode": "local_to_local", "total_files": 3,
"copied": 2, "linked": 1, "skipped": 0, "errors": 0,
"total_bytes": 18, "bytes_written": 12, "dedup_saved": 6,
"elapsed_sec": 0.03, "avg_speed_bps": 400, "hash_algo": "xxh128"
},
"files": [
{"action": "copied", "path": "data.bin", "size": 6, "method": "block_stream"},
{"action": "linked", "path": "data_copy.bin", "size": 6, "method": "hardlink", "link_target": "data.bin"}
]
}
```
## 真实基准测试
### 本地到本地:59,925 个文件(593 MB)到 HDD
```
Files: 59925 total (44454 unique + 15471 linked)
Data: 500.7 MB written (92.5 MB saved by dedup)
Time: 12.1s
Speed: 41.2 MB/s
```
去重检测到 15,471 个重复文件(25.8%),节省了 92.5 MB。文件按物理磁盘顺序读取,小文件被打包成块流。
### 远程到本地:100 Mbps 局域网上 91,669 个文件(888 MB)
```
Files: 91669 total (44718 copied + 46951 linked)
Data: 509.8 MB downloaded (378.5 MB saved by dedup)
Time: 14m 2s
Speed: 619.5 KB/s
```
去重发现 46,951 个重复文件(51.2%),节省了 378.5 MB 的传输量。文件以 6 个约 100 MB 的 tar 批次进行流式传输,并带有流式解压(无临时文件)。所有 91,669 个文件在复制后都进行了验证。
### 本地到远程:100 Mbps 局域网上 91,663 个文件(888 MB)
```
Files: 91663 total (44712 copied + 46951 linked)
Data: 509.8 MB uploaded
Time: 2m 7s
Speed: 4.0 MB/s
```
以 6 个 tar 批次上传。通过 SSH 上的批量 Python 脚本创建远程硬链接(每批 5,000 个)。比基于 SFTP 的传输快 3 倍。
### 远程到远程:3 个文件(1.7 GB)通过本地机器中继
```
Files: 3 total
Data: 1.7 GB relayed
Time: 5m 30s
Speed: 5.2 MB/s
```
数据通过 tar 管道在两台 SSH 服务器之间中继。源和目标不需要直接连接。传输后在目标上进行验证。
## 核心功能
- **稀疏文件感知** *(v3.1.0+)* — VM 磁盘镜像、Longhorn 副本和其他稀疏文件会被自动检测(`st_blocks * 512 < st_size`)并通过 `SEEK_DATA` / `SEEK_HOLE` 复制,因此未分配的空洞永远不会传输到网络或目标磁盘。第 3 阶段的空间检查在支持稀疏文件的目标上使用已分配的字节数。
- **每个命令支持多个源** *(v3.1.0+)* — `fast-copy SRC1 SRC2 … DST/` 接受 N 个源路径;每个源都作为其自己的子树复制到目标下,保留其 basename(cp -r 风格)。
- **`--use-sudo` 自动提权** *(v3.1.0+)* — 针对需要 root 权限的路径(Longhorn 副本、容器卷)在 sudo 下重新执行。仅限 Linux/macOS。
- **防篡改审计日志** *(v3.1.0+,在 v3.1.1+ 中强化)* — 在 sudo 下,将每次运行的不可变(`chattr +i`)JSONL 写入 `~$SUDO_USER/.fast_copy_audit.jsonl` — 捕获调用用户、命令、源/目标、每个文件的复制列表、摘要。即使是 root 也无法悄悄删除记录。
- **安全强化的 sudo 流程** *(v3.1.1+)* — 在 sudo 下的每次目标打开和源读取中都使用 `O_NOFOLLOW`;审计文件移出了攻击者可控的目标路径;在 sudo 下拒绝执行 `--update`(带有可选的 `--update-sha256` 完整性锁定);如果 `fast_copy.py` 或其目录可被组/全局写入,脚本权限预检将拒绝提权。
- **基于 Reflink 的复制** *(v3.1.0+)* — 在 btrfs / XFS reflink / APFS / bcachefs 上,文件通过 `FICLONE`/`clonefile`(仅元数据,瞬间完成)克隆,而不是逐字节复制。CoW 语义使修改后的对等文件保持独立。
- **块顺序读取** — 按物理磁盘顺序读取文件,消除随机寻道
- **内容去重** — xxHash-128 或 SHA-256 哈希;只复制一次,对重复文件进行硬链接或 reflink
- **自动文件系统检测** *(v3.0.0+)* — 检测目标 FS 类型(ext4, btrfs, XFS, APFS, NTFS, FAT32 等)并探测其能力(硬链接、符号链接、reflink、区分大小写),以选择最安全的去重策略
- **如实计算去重** *(v3.0.0+)* — 在 FAT32/exFAT 上,将“节省的带宽”与“磁盘使用量”分开报告,并为空间检查使用正确的完整大小
- **显式哈希算法选择** *(v3.0.0+)* — `--hash=auto|xxh128|sha256` 允许用户强制使用特定算法;选择显示在横幅中
- **各类型的重复文件处理摘要** *(v3.0.0+)* — 第 6 阶段确切显示目标上有多少重复文件变成了硬链接 / 符号链接 / 完整副本
- **单文件的文件路径目标** *(v2.4.8+)* — `fast_copy host:file.tar.gz /local/renamed.tar.gz` 适用于所有复制模式
- **跨运行去重数据库** — 位于驱动器根目录的 SQLite 缓存;重新运行时会跳过已复制的内容
- **流式 tar 管道** — 用于本地复制的生产者→消费者管道(无临时文件);SSH 采用分块的 100 MB 批次
- **无 SFTP 的 SSH 传输** — 使用带有 tar 的原生 SSH 通道;可在禁用 SFTP 的服务器上运行
- **兼容 Synology NAS** *(v3.0.0+)* — 使用可移植的 tar stdin 处理;已针对运行 DSM 7.x 的 DS720+ 进行测试
- **灵活的源** — 目录、单个文件或 glob 模式(`*.zip`, `*.iso`)
- **预飞空间检查** — 在写入前验证空间;为远程路径遍历父目录
- **复制后验证** — 根据源哈希验证每个文件
- **结构化 JSON 日志** — `--log-file` 记录每个操作(复制、链接、跳过、出错)以及摘要统计信息
- **权限保留** — 在本地和远程传输中复制文件权限
- **Windows 长路径支持** — 通过 `\\?\` 前缀处理超过 260 个字符的路径
- **交互式 SSH 主机密钥验证** — 带有 MD5 + SHA-256 指纹的 TOFU 提示;拒绝已更改的密钥(防御 MITM)
- **身份验证重试** — 身份验证失败时最多提示输入 3 次密码;优雅处理 Ctrl+C
- **跨平台** — Linux、macOS 和 Windows,具有原生 I/O 优化
- **自我更新** — `--check-update` 显示带有分类发行说明的可用版本;`--update [VERSION]` 安装最新版本或特定版本
- **独立二进制文件** — 使用 PyInstaller 构建为单文件可执行程序
## 致谢
- [@YoSiJo](https://github.com/YoSiJo) — 贡献了 `--index-existing` / `--dedup-existing` 针对现有文件的去重功能 ([#3](https://github.com/gekap/fast-copy/pull/3))。
## 许可证
Apache License 2.0 — 详见 [LICENSE](LICENSE)。
## 支持
fast-copy 是免费且开源的 — 支持它的最好方式是帮助它成长:
- ⭐ **为仓库加星** 并**传播出去** — 分享给任何需要移动大量数据的人。
- 🐛 通过 [issues](https://github.com/gekap/fast-copy/issues) 或 pull requests **报告 Bug 和想法**。
如果您想捐款,请[联系交流](https://fast-copy.dev/#contact)。
标签:Python, SSH, 内存分配, 去重, 数据备份, 文件传输, 无后门, 逆向工具