Skip to content

Spec: 重写 ddv 备份格式 #4

Description

@ddv12138

Problem Statement

当前自定义 ddv 备份格式难以维护:打包逻辑高度耦合,元数据依赖 Python pickle,分卷边界靠运行时状态推断,解包/查看归档信息时容易出现难以定位的问题;分卷路径返回不完整,格式也没有清晰的版本控制。结果是打包过程复杂、错误不易排查、后续扩展困难。

Solution

将现有 ddv 格式直接重写为一个显式、可测试、可扩展的新版本:

  • 使用统一记录结构表达文件、目录、数据和归档索引
  • 分卷规则固定在记录边界,不再依赖隐式状态
  • 元数据使用结构化 JSON,而不是 Python 对象
  • 加密使用带认证的 AEAD 算法
  • 解包前校验路径安全性
  • CLI 的 backup / unpack / info 使用方式保持直观

不兼容旧格式,旧 .ddv 文件视为废弃。

User Stories

  1. 作为备份用户,我希望用一条命令完成文件备份,以便不用理解内部实现就能可靠备份数据。
  2. 作为备份用户,我希望备份目录和文件都受支持,以便完整保存工作目录结构。
  3. 作为备份用户,我希望空目录也被记录,以便恢复时能还原完整目录树。
  4. 作为备份用户,我希望排除规则仍然生效,以便避免打包无关文件。
  5. 作为备份用户,我希望大备份能自动分卷,以便符合上传或存储限制。
  6. 作为备份用户,我希望分卷命名稳定可预测,以便上传、清理和恢复时容易识别。
  7. 作为备份用户,我希望打包结果返回所有分卷路径,以便后续上传和清理不会遗漏文件。
  8. 作为恢复用户,我希望解包后能还原文件和目录,以便快速恢复数据。
  9. 作为恢复用户,我希望归档内部使用相对路径,以便不会把机器路径结构复刻到目标目录。
  10. 作为恢复用户,我希望归档内路径被严格校验,以便恶意或损坏的归档不会造成路径穿越。
  11. 作为恢复用户,我希望损坏的归档能被明确识别,以便避免恢复出不可信数据。
  12. 作为恢复用户,我希望密码错误时得到明确失败,而不是得到看似成功的坏数据。
  13. 作为恢复用户,我希望文件校验失败时知道是哪个文件失败,以便判断恢复结果的可用范围。
  14. 作为恢复用户,我希望部分文件校验失败时整体退出码为非零,以便自动化流程能察觉异常。
  15. 作为运维用户,我希望能单独关闭压缩或加密,以便在性能和安全性之间灵活取舍。
  16. 作为运维用户,我希望压缩和加密顺序固定且明确,以便归档行为可预期。
  17. 作为运维用户,我希望定时备份流程不需要改动就能继续工作,以便现有部署不受干扰。
  18. 作为运维用户,我希望上传到云端的流程保持兼容,以便新格式能无缝接入现有备份链路。
  19. 作为运维用户,我希望可以在任意一个分卷上执行 info,以便不必总是记住第一卷位置。
  20. 作为运维用户,我希望缺失中间卷时直接失败,以便避免恢复出不完整数据。
  21. 作为运维用户,我希望卷序号不连续时直接失败,以便尽早发现归档不完整。
  22. 作为运维用户,我希望归档索引能展示文件数量、原始大小和打包后大小,以便快速了解备份规模。
  23. 作为运维用户,我希望归档版本清晰可见,以便未来排查兼容性问题时知道格式代次。
  24. 作为维护者,我希望打包逻辑拆成写器,以便分卷和记录写入规则集中且可测试。
  25. 作为维护者,我希望解包逻辑拆成读器,以便路径校验、索引解析和文件还原职责清晰。
  26. 作为维护者,我希望压缩和加密逻辑独立成处理层,以便未来替换算法时不影响归档结构。
  27. 作为维护者,我希望元数据不使用 pickle,以便归档不绑定 Python 对象结构和运行环境。
  28. 作为维护者,我希望归档格式不依赖 Python 类实例,以便跨语言或跨版本处理更容易。
  29. 作为维护者,我希望 SHA256 校验内建在归档索引中,以便数据完整性可验证。
  30. 作为维护者,我希望错误信息明确指向结构问题或校验问题,以便快速定位故障来源。
  31. 作为维护者,我希望测试通过 CLI 覆盖备份、解包和查看信息,以便外部行为保持稳定。
  32. 作为维护者,我希望旧格式代码被移除,以便仓库中只保留一条明确的格式路径。
  33. 作为维护者,我希望中文路径、长路径和空目录都有测试覆盖,以便常见真实场景不退化。
  34. 作为维护者,我希望压缩膨胀、分卷边界和缺失卷都有测试覆盖,以便极端情况不破坏归档。
  35. 作为维护者,我希望格式版本号写入卷头,以便后续演进时有明确的兼容判断依据。
  36. 作为维护者,我希望分卷记录不跨卷,以便恢复和校验逻辑简单可靠。
  37. 作为维护者,我希望归档索引只写入最后一卷,以便索引规则简单,不增加每卷维护成本。
  38. 作为维护者,我希望结构错误和数据校验错误分开处理,以便用户区分“归档坏了”和“某个文件坏了”。

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 数据运行 backupunpackinfo
  • 覆盖:单文件、多文件、子目录、空目录、中文路径、多卷边界、压缩膨胀、加密加压缩、错误密码、缺失卷、卷序号不连续、TOC 损坏、路径穿越、info 从任意卷可用。
  • 本仓库当前没有既有测试,因此这些将作为第一批 CLI 级回归测试建立。

Out of Scope

  • 不兼容旧 .ddv 格式。
  • 不支持自定义分卷文件名前缀。
  • 不支持打包时重命名归档内文件。
  • 不保留 symlink、xattr、ACL 或特殊文件类型。
  • 不改动 7zzstd 备份格式分支。
  • 不调整云端上传、回收站清理和定时任务逻辑。
  • 不做性能优化、并发打包或增量备份。

Further Notes

  • 新格式刻意避免绑定 Python 对象结构,以便未来更易维护和排查。
  • 由于不兼容旧格式,实施时应在文档中明确提示旧备份文件需要另行处理。
  • 本次以格式和打包流程重写为主,云端链路只要求不被破坏。

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agent描述已充分明确,可交给 AFK agent

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions