真正的冷备份需定期下载官方 packages.json.gz 快照,校验哈希与时间戳,按日期归档并建立索引,禁用自动删除,且每次存档后验证包名、版本及废弃状态。

Composer 元数据冷备份不能只靠 composer install 或 composer update 时顺带缓存——那些是临时、可被清理、无版本锚点的运行时产物,不是归档。
为什么 vendor 目录和 composer.lock 不算冷备份
很多人误以为把整个 vendor 目录打包、或保留一份 composer.lock 就完成了元数据归档,实际这存在三个致命断层:
-
composer.lock只记录当前解析出的包版本与哈希,不包含 packagist.org 的原始元数据(如废弃状态、替换关系、历史版本标签、require-dev 的条件依赖树) - packagist.org 本身不保证长期可用,2023 年就发生过全站 4 小时不可访问;2025 年起其 API 已开始对未认证请求限流,且明确声明“不提供 SLA”
-
vendor目录里的代码是构建产物,不含composer.json中的 autoload 配置变更、scripts 定义、或 license 文本等归档审计必需字段
真正可用的冷备份:抓取 Packagist 元数据快照
核心动作是定期拉取 Packagist 的完整元数据镜像,而非单个项目依赖。官方提供两种接口,但只有后者适合归档:
- 用
https://packagist.org/packages/list.json获取所有包名列表(约 40 万条),再逐个请求https://packagist.org/p/{vendor/name}.json—— 效率低、易被限流、无时间戳,不适合批量归档 - 直接下载官方发布的压缩快照:
https://api.packagist.org/packages.json.gz(含全部包元数据的 JSON Lines 格式),每天更新一次,文件带Last-Modified响应头,可做增量比对 - 备份脚本必须校验
Content-MD5或用sha256sum存档哈希,并记录下载时间戳(如2026-11-04T08:54:00Z),这是后续回溯依赖变更的唯一锚点
归档存储结构怎么设计才可查、可验证
平铺一堆 .json.gz 文件会迅速失控。推荐按时间切片 + 索引文件组织:
- 目录结构固定为:
/archive/packagist/YYYY/MM/DD/packages.json.gz+ 同级index.json -
index.json记录当天快照的etag、content-length、downloaded_at和前一日快照的 diff 统计(如新增包数、deprecated 包数) - 不要用对象存储的“自动生命周期”删除旧文件——有些项目需要追溯 3 年前某次安全公告涉及的包是否当时已被标记为 abandoned
- 如果使用 S3/COS,开启版本控制并禁用删除操作(
object-lock模式),防止人为误删
如何验证一次归档是否真正可用
冷备份的价值只在能还原时体现。每次写入新快照后,必须跑轻量验证:
- 解压
packages.json.gz,用jq -r '.name' | head -n 100确认前 100 个包名可读,排除 gzip 损坏 - 随机选 5 个已知活跃包(如
monolog/monolog、symfony/console),检查其versions字段是否存在,且至少含一个非 dev 版本 - 用
grep -c '"abandoned":true'统计当日废弃包数量,若突变为 0,大概率是快照生成异常(真实数据每日总有 20–50 个新废弃包)
真正的难点不在下载,而在把“某天某刻的 Packagist 全貌”变成可审计、可定位、抗误操作的资产。很多团队卡在第二步——没给每个快照打上不可篡改的时间戳和哈希,结果三年后发现两份备份内容一致却无法确认哪份更早,只能重下。


















