一次
docker compose down误操作,导致 CowAgent AI 助手全部运行时数据(会话、记忆、技能、知识库)被销毁。本文记录根因分析、恢复方案选型、ext4magic 实战操作、以及最终的恢复效果和教训。
如果你正在面对同样的问题
刚误删了容器,数据没了?停下来,别做多余的磁盘操作:
# 1. 停掉容器,减少磁盘写入
docker compose stop
# 2. 确认文件系统是 ext3/ext4(不是就别往下看了)
df -T /
# 3. 装工具
sudo apt-get install -y ext4magic
然后跳到第四节,按步骤跑。越早开始,恢复得越多。
| 适用 | 不适用 |
|---|---|
| ext3 / ext4 | XFS / btrfs / ZFS |
| 容器刚被删(几小时内) | 几天前删的,磁盘上频繁有写入 |
| Docker overlayfs / containerd | Docker volumes(那玩意另有恢复方法) |
恢复结果跟你删了多久、之后写了多少数据直接相关。本文最终恢复了大部分会话和技能,但仍有少部分文件没捞回来——做好这个心理准备,别期望 100%。
一、事故背景
服务器上通过 Docker Compose 部署了 CowAgent(开源 AI 助理框架)。某次需要修改上下文配置以修复 LLM 回答丢失的问题,执行了以下命令:
docker compose down && docker compose up -d
重启后发现:所有会话记录、长期记忆、我安装的技能、知识库全部消失。
二、根因分析
2.1 直接原因
使用 docker compose down 而非 docker compose restart。
两者的区别:
| 命令 | 行为 |
|---|---|
docker compose restart |
重启容器,保留容器 ID 和可写层 |
docker compose down |
删除容器(等价于 docker rm),可写层被销毁 |
2.2 深层原因
CowAgent 的 docker-compose.yml 中,/home/agent/cow/ 目录没有被挂载到宿主机。所有运行时数据都在容器的可写层内:
容器内路径 是否持久化
/home/agent/cow/sessions/ ❌ 随容器销毁
/home/agent/cow/memory/ ❌ 随容器销毁
/home/agent/cow/skills/ ❌ 随容器销毁
/home/agent/cow/knowledge/ ❌ 随容器销毁
/app/config.json ✅ bind mount
/app/agent/memory/ (框架代码) ✅ bind mount
已挂载的是框架的 Python 源代码目录(如 cow_agent/memory/、cow_agent/skills/ 等),但这些只是代码,不是我运行时产生的数据。
三、恢复方案选型
| 方案 | 原理 | 可行性 | 结果 |
|---|---|---|---|
| Docker overlayfs 层恢复 | 查找 /var/lib/docker/overlay2/ 中旧容器的可写层 |
❌ | docker rm 立即清理了可写层 |
| Containerd 快照恢复 | 搜索 267 个 containerd overlayfs 快照 | ❌ | 旧容器快照已被 GC 回收 |
| Docker 备份目录 | 检查宿主机备份目录 | ⚠️ | 只有框架代码快照,无运行时数据 |
| VMware 快照回滚 | 回滚虚拟机到删除前状态 | ❌ | 未配置 VMware 快照 |
| LVM 快照回滚 | LVM 层面恢复 | ❌ | 文件系统为 ext4,无 LVM 快照 |
| debugfs 文件系统底层 | 扫描 ext4 已删除 inode | ❌ | 旧容器目录的 inode 已被回收 |
| ext4magic(日志恢复) | 从 ext4 日志重放历史文件系统状态 | ✅ | 成功恢复大量数据 |
| WAL 回放 | SQLite WAL 文件回放未提交事务 | ✅ | 恢复了 4 个额外会话和 200+ 条消息 |
| 文件指纹挖掘(File Carving) | 按内容特征从磁盘原始数据中提取文件 | ✅ | 恢复了技能 SKILL.md 和知识库文件 |
四、ext4magic 实战
4.1 环境检查
# 确认文件系统类型
df -T /
# /dev/mapper/ubuntu--vg-ubuntu--lv ext4
# 查看日志大小(关键!越大恢复越多)
sudo dumpe2fs /dev/mapper/ubuntu--vg-ubuntu--lv | grep -i journal
# Total journal size: 512M ← 512MB 日志是恢复成功的关键
踩坑 1:ext4magic 的输出目录必须在与源不同的分区,否则直接报错退出。
# ❌ 错误
sudo ext4magic /dev/sda -d /home/recovery ...
# ✅ 正确——用 /dev/shm (RAM disk)
sudo ext4magic /dev/sda -d /dev/shm/recovery ...
4.2 恢复操作
# 安装工具
sudo apt-get install -y ext4magic
# 恢复指定时间点之前的文件
# -a after-timestamp: 时间起点(Unix 时间戳)
# -b before-timestamp: 时间终点
# -r 文件名模式: 支持通配符
# -d 输出目录: 必须在不同分区!
sudo ext4magic /dev/mapper/ubuntu--vg-ubuntu--lv \
-r "*.db" \
-d /dev/shm/recovery \
-a $(date -d "2026-06-18 03:00:00 UTC" +%s) \
-b $(date -d "2026-06-18 03:52:00 UTC" +%s)
踩坑 2:-a 和 -b 参数中,-a(after)的时间必须早于 -b(before),不要搞反。-a 是时间范围起点,-b 是终点。
4.3 恢复结果分类
ext4magic 会产生两类恢复文件:
/dev/shm/recovery/
├── MAGIC-1/ # 按 inode 恢复,保留目录结构和文件名
│ └── <13111143>/ # SQLite 数据库 + WAL/SHM 文件
│ └── <13111268>/ # SKILL.md + manifest.json(安全分析技能)
│ └── <13111972>/ # Evolution 备份(0.bak, 1.bak, 2.bak)
│
└── MAGIC-2/ # 文件指纹挖掘(File Carving),无文件名
├── text/plain/ # 按 MIME 类型分类
│ ├── I_0033288956.txt # 47KB xx语言安全审计技能
│ ├── I_0033287573.txt # 8.7KB xx框架鉴权审计技能
│ ├── I_0033289140.txt # 7.8KB 每日记忆
│ ├── I_0033288994.txt # 知识库日志
│ └── ...
└── image/png/ # 图片碎片
踩坑 3:MAGIC-2 中的文件是按文件指纹(Magic Number)从磁盘原始数据中挖掘出来的,没有原始文件名。需要通过内容标题来识别是什么文件。例如 47KB 的文本文件,第一行是 language: xx-Vulnerability,才能确认它是xx语言安全审计技能的 SKILL.md。
踩坑 4:MAGIC-1 中可能存在大量无用的 .pyc 文件、空目录、Docker 容器配置碎片等。需要逐个检查 22 个 inode 目录才能找到有我的数据的 4 个。
五、SQLite WAL 回放
ext4magic 恢复的 index.db 旁边有 WAL(Write-Ahead Log)和 SHM 文件。直接打开数据库会自动回放:
# 把 db + wal + shm 放到一起
cp index.db /tmp/replay/index.db
cp index.db-wal /tmp/replay/index.db-wal
cp index.db-shm /tmp/replay/index.db-shm
# 用 sqlite3 打开会自动回放 WAL
python3 -c "
import sqlite3
conn = sqlite3.connect('/tmp/replay/index.db')
count = conn.execute('SELECT COUNT(*) FROM sessions').fetchone()[0]
print(f'Sessions: {count}')
"
# 输出: Sessions: 8(WAL 回放新增了 4 个会话!)
踩坑 5:WAL 文件必须和数据库文件在同一目录下,且文件名必须匹配(xxx.db + xxx.db-wal + xxx.db-shm),sqlite3 才会自动回放。
六、Knowledge Base 前端显示为空
CowAgent 的知识库服务有特殊要求:
# knowledge/service.py 的 list_tree() 逻辑
# 根目录下的 index.md 和 log.md 是特殊文件,不计入列表
# 只有 子目录中的 .md 文件才会被前端渲染
踩坑 6:恢复的知识文件直接放在 knowledge/ 根目录时,前端显示"暂无知识"。必须按分类放到子目录下:
knowledge/
├── index.md # 特殊文件,不在前端列表显示
├── log.md # 特殊文件,不在前端列表显示
├── xx文档/ # ✅ 前端显示
│ └── example.md
└── xx标准/ # ✅ 前端显示
└── example.md
七、恢复成果汇总
已恢复
| 类别 | 恢复量 | 恢复率 | 恢复手段 |
|---|---|---|---|
| 会话记录 | 8 个会话 / 522 条消息 | ~80% | ext4magic + WAL 回放 |
| Agent 人设 | AGENT.md(2.2KB) | 100% | Evolution 备份 |
| 长期记忆 | MEMORY.md(1.1KB)+ 多日记忆 | ~70% | 备份 + File Carving |
| 我的技能 (7个) | xx语言审计(47KB)、xx框架审计、路由映射等 | ~90% | MAGIC-1 + MAGIC-2 |
| 知识库 | 知识库日志 + 漏洞分级 + xx文档记录 | ~40% | File Carving |
未恢复内容
| 数据 | 原因 |
|---|---|
| web 会话数据库 (conversations.db) | ext4 日志中无记录,inode 已被回收 |
| xx文档完整正文 | 文件内容在容器内,inode 被覆盖 |
| 浏览器登录状态 | 浏览器 Profile 在容器内,未持久化 |
| 部分技能的 references 子文件 | 原始碎片不完整 |
八、核心教训
1. 只用 restart,别用 down
# ✅ 正确
docker compose restart
# ❌ 危险——会删除容器及其可写层
docker compose down && docker compose up -d
2. Bind Mount 一切运行时数据
volumes:
# 除了代码和配置,必须挂载运行时数据目录
- ./cow_home:/home/agent/cow # ← 这行是关键
3. 事故后立即停止写入
越早停止容器运行,ext4 日志中被覆盖的 inode 越少,恢复成功率越高。
4. ext4 日志大小直接决定恢复上限
512MB 日志能保留一定时间内的文件系统变更,最终恢复了大部分关键数据。如果日志只有默认的 32MB,恢复成功率会大幅下降。
5. File Carving 是最后的救命稻草
即使文件系统元数据(inode)已被回收,文件内容本身可能仍残留在磁盘上。通过文件指纹(Magic Number)挖掘可以恢复部分内容,缺点是丢失了文件名和目录结构。
6. 上线前自检清单
# 1. 确认所有运行时路径已 bind mount
docker inspect <容器名> --format='{
{range .Mounts}}{
{.Destination}} ← {
{.Source}}{
{"\n"}}{
{end}}'
# 2. 模拟 down+up,验证数据仍在
docker compose down && docker compose up -d
docker exec <容器名> ls /home/agent/cow/AGENT.md # 应返回文件路径
# 3. 定时备份已配置且测试通过
crontab -l | grep backup
ls -la ./backups/auto/ | tail -3
# 4. 团队统一使用 restart,禁用 down
alias dc-restart='docker compose restart'