Problem Statement
当前自定义 ddv 备份格式难以维护:打包逻辑高度耦合,元数据依赖 Python pickle,分卷边界靠运行时状态推断,解包/查看归档信息时容易出现难以定位的问题;分卷路径返回不完整,格式也没有清晰的版本控制。结果是打包过程复杂、错误不易排查、后续扩展困难。
Solution
将现有 ddv 格式直接重写为一个显式、可测试、可扩展的新版本:
- 使用统一记录结构表达文件、目录、数据和归档索引
- 分卷规则固定在记录边界,不再依赖隐式状态
- 元数据使用结构化 JSON,而不是 Python 对象
- 加密使用带认证的 AEAD 算法
- 解包前校验路径安全性
- CLI 的
backup / unpack / info 使用方式保持直观
不兼容旧格式,旧 .ddv 文件视为废弃。
User Stories
- 作为备份用户,我希望用一条命令完成文件备份,以便不用理解内部实现就能可靠备份数据。
- 作为备份用户,我希望备份目录和文件都受支持,以便完整保存工作目录结构。
- 作为备份用户,我希望空目录也被记录,以便恢复时能还原完整目录树。
- 作为备份用户,我希望排除规则仍然生效,以便避免打包无关文件。
- 作为备份用户,我希望大备份能自动分卷,以便符合上传或存储限制。
- 作为备份用户,我希望分卷命名稳定可预测,以便上传、清理和恢复时容易识别。
- 作为备份用户,我希望打包结果返回所有分卷路径,以便后续上传和清理不会遗漏文件。
- 作为恢复用户,我希望解包后能还原文件和目录,以便快速恢复数据。
- 作为恢复用户,我希望归档内部使用相对路径,以便不会把机器路径结构复刻到目标目录。
- 作为恢复用户,我希望归档内路径被严格校验,以便恶意或损坏的归档不会造成路径穿越。
- 作为恢复用户,我希望损坏的归档能被明确识别,以便避免恢复出不可信数据。
- 作为恢复用户,我希望密码错误时得到明确失败,而不是得到看似成功的坏数据。
- 作为恢复用户,我希望文件校验失败时知道是哪个文件失败,以便判断恢复结果的可用范围。
- 作为恢复用户,我希望部分文件校验失败时整体退出码为非零,以便自动化流程能察觉异常。
- 作为运维用户,我希望能单独关闭压缩或加密,以便在性能和安全性之间灵活取舍。
- 作为运维用户,我希望压缩和加密顺序固定且明确,以便归档行为可预期。
- 作为运维用户,我希望定时备份流程不需要改动就能继续工作,以便现有部署不受干扰。
- 作为运维用户,我希望上传到云端的流程保持兼容,以便新格式能无缝接入现有备份链路。
- 作为运维用户,我希望可以在任意一个分卷上执行
info,以便不必总是记住第一卷位置。
- 作为运维用户,我希望缺失中间卷时直接失败,以便避免恢复出不完整数据。
- 作为运维用户,我希望卷序号不连续时直接失败,以便尽早发现归档不完整。
- 作为运维用户,我希望归档索引能展示文件数量、原始大小和打包后大小,以便快速了解备份规模。
- 作为运维用户,我希望归档版本清晰可见,以便未来排查兼容性问题时知道格式代次。
- 作为维护者,我希望打包逻辑拆成写器,以便分卷和记录写入规则集中且可测试。
- 作为维护者,我希望解包逻辑拆成读器,以便路径校验、索引解析和文件还原职责清晰。
- 作为维护者,我希望压缩和加密逻辑独立成处理层,以便未来替换算法时不影响归档结构。
- 作为维护者,我希望元数据不使用
pickle,以便归档不绑定 Python 对象结构和运行环境。
- 作为维护者,我希望归档格式不依赖 Python 类实例,以便跨语言或跨版本处理更容易。
- 作为维护者,我希望 SHA256 校验内建在归档索引中,以便数据完整性可验证。
- 作为维护者,我希望错误信息明确指向结构问题或校验问题,以便快速定位故障来源。
- 作为维护者,我希望测试通过 CLI 覆盖备份、解包和查看信息,以便外部行为保持稳定。
- 作为维护者,我希望旧格式代码被移除,以便仓库中只保留一条明确的格式路径。
- 作为维护者,我希望中文路径、长路径和空目录都有测试覆盖,以便常见真实场景不退化。
- 作为维护者,我希望压缩膨胀、分卷边界和缺失卷都有测试覆盖,以便极端情况不破坏归档。
- 作为维护者,我希望格式版本号写入卷头,以便后续演进时有明确的兼容判断依据。
- 作为维护者,我希望分卷记录不跨卷,以便恢复和校验逻辑简单可靠。
- 作为维护者,我希望归档索引只写入最后一卷,以便索引规则简单,不增加每卷维护成本。
- 作为维护者,我希望结构错误和数据校验错误分开处理,以便用户区分“归档坏了”和“某个文件坏了”。
Implementation Decisions
- 直接替换现有
ddv 格式,不保留旧格式兼容;旧 .ddv 文件视为废弃。
- 每个分卷都带显式卷头,包含魔数、格式版本、分卷序号、加密/压缩标志、盐值和 KDF 参数。
- 卷头中的分卷序号从 1 开始连续,读器必须校验连续性。
- 所有归档内容使用统一的记录结构:记录类型、流 ID、负载长度、负载数据。
- 记录类型至少包括文件/目录开始、数据块、文件/目录结束、归档索引和归档结束。
- 归档索引使用 JSON,而不是 Python
pickle。
- 归档索引放在最后一卷;
info 可从任意分卷路径出发定位最后一卷。
- 分卷统一命名为
package.N.ddv;单卷也使用 .1.ddv。
- 分卷只在记录边界发生;一条记录不允许跨卷。
- 打包流程返回所有分卷路径。
- 归档内部路径使用源文件的相对路径,不做打包时重命名。
- 归档内路径禁止绝对路径、
.. 和路径穿越;解包前必须校验。
- 空目录会记录并在解包时还原。
- symlink 默认跳过;硬链接按普通文件处理。
- 压缩使用 zstd,加密使用 ChaCha20-Poly1305。
- 处理顺序固定为:原始数据 -> zstd -> ChaCha20-Poly1305。
- 加密记录使用独立随机 nonce。
- 文件完整性使用 SHA256,并写入归档索引。
- 文件元数据包括路径、类型、原始大小、打包后大小、mtime、mode 和哈希。
- 现有 CLI 的
backup / unpack / info 入口和加密、压缩开关保持用户语义不变。
- 云端上传和定时备份行为不变。
- 旧的处理链、加密工具和旧元数据类删除。
- 结构错误(缺卷、卷序号错误、坏头、坏索引)直接失败;单文件校验失败收集后统一报错,并以非零退出码结束。
Testing Decisions
- 测试 seam 使用 CLI 端到端入口。
- 测试只关注外部行为:命令退出码、输出信息、生成的分卷文件、解包结果和目录结构。
- 不针对内部 codec、writer 或 reader 的私有实现做测试。
- 测试通过临时目录和最小 fixture 数据运行
backup、unpack 和 info。
- 覆盖:单文件、多文件、子目录、空目录、中文路径、多卷边界、压缩膨胀、加密加压缩、错误密码、缺失卷、卷序号不连续、TOC 损坏、路径穿越、
info 从任意卷可用。
- 本仓库当前没有既有测试,因此这些将作为第一批 CLI 级回归测试建立。
Out of Scope
- 不兼容旧
.ddv 格式。
- 不支持自定义分卷文件名前缀。
- 不支持打包时重命名归档内文件。
- 不保留 symlink、xattr、ACL 或特殊文件类型。
- 不改动
7z 或 zstd 备份格式分支。
- 不调整云端上传、回收站清理和定时任务逻辑。
- 不做性能优化、并发打包或增量备份。
Further Notes
- 新格式刻意避免绑定 Python 对象结构,以便未来更易维护和排查。
- 由于不兼容旧格式,实施时应在文档中明确提示旧备份文件需要另行处理。
- 本次以格式和打包流程重写为主,云端链路只要求不被破坏。
Problem Statement
当前自定义
ddv备份格式难以维护:打包逻辑高度耦合,元数据依赖 Pythonpickle,分卷边界靠运行时状态推断,解包/查看归档信息时容易出现难以定位的问题;分卷路径返回不完整,格式也没有清晰的版本控制。结果是打包过程复杂、错误不易排查、后续扩展困难。Solution
将现有
ddv格式直接重写为一个显式、可测试、可扩展的新版本:backup/unpack/info使用方式保持直观不兼容旧格式,旧
.ddv文件视为废弃。User Stories
info,以便不必总是记住第一卷位置。pickle,以便归档不绑定 Python 对象结构和运行环境。Implementation Decisions
ddv格式,不保留旧格式兼容;旧.ddv文件视为废弃。pickle。info可从任意分卷路径出发定位最后一卷。package.N.ddv;单卷也使用.1.ddv。..和路径穿越;解包前必须校验。backup/unpack/info入口和加密、压缩开关保持用户语义不变。Testing Decisions
backup、unpack和info。info从任意卷可用。Out of Scope
.ddv格式。7z或zstd备份格式分支。Further Notes