pi-webdav-backup
pi 配置与插件数据自动备份到 WebDAV(坚果云 / Nextcloud / 群晖 / 自建):归档与快照两种模式、AES-256-GCM 加密、DPAPI 密钥管理、敏感字段脱敏、agentDir 之外的额外数据(extraScope)、插件实现代码随备份(includePlugins)实现换机一键还原、退出自动备份与每日定时备份。
Package details
Install pi-webdav-backup from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-webdav-backup- Package
pi-webdav-backup- Version
1.4.0- Published
- Oct 13, 2026
- Downloads
- 557/mo · 53/wk
- Author
- ddldxiacan
- License
- MIT
- Types
- extension
- Size
- 407.4 KB
- Dependencies
- 0 dependencies · 1 peer
Pi manifest JSON
{
"extensions": [
"./extensions/webdav-backup.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi WebDAV 备份扩展
把 pi 的插件、扩展、模型配置自动备份到任意 WebDAV 服务(坚果云 / Nextcloud / 群晖 / 自建 rclone)。
零第三方依赖,只用 Node 内置模块。密钥默认不明文落盘(DPAPI / 环境变量 / 命令)。
安装
一行命令(pi 官方包机制):
pi install git:github.com/ddldxiacan/pi-webdav-backup@v1 # 永远装最新的 1.x 正式版
@v1 是移动主版本 tag,始终指向最新的 1.x 正式版。以后升级不用改命令:
pi update --extensions # 自动升到最新的 v1.x(然后 /reload)
只想试一下、不安装(仅本次运行生效):
pi -e git:github.com/ddldxiacan/pi-webdav-backup
其他方式 / 更细的版本控制:
pi install git:github.com/ddldxiacan/pi-webdav-backup@v1.1.0 # 钉死某个版本(不自动升级)
pi install git:github.com/ddldxiacan/pi-webdav-backup # 不钉 ref,直接跟 main 分支
pi install npm:pi-webdav-backup # 已发布到 npm 时
pi install /path/to/pi-webdav-backup # 本地目录(下载 ZIP 解压后)
@<ref>可以是移动 tag(@v1)、具体版本 tag(@v1.1.0)、分支或 commit SHA:
@v1→pi update --extensions会跟到最新的 1.x,推荐@v1.1.0/ commit SHA → 钉死不动;升级需重新pi install ...@<新版本>- ref 必须真实存在,写错(如不存在的 tag)会 clone/checkout 失败
手动安装:把 extensions/webdav-backup.ts 与 extensions/webdav-backup/ 拷到 ~/.pi/agent/extensions/。
装好后在 pi 里运行 /reload 加载。
本包带
pi-package关键字,会出现在 pi.dev 包画廊。
运行时产生的文件:
~/.pi/agent/webdav-backup.json ← 备份配置(/backup-setup 生成)
~/.pi/agent/secrets.dpapi.json ← DPAPI 加密的密钥(自动创建,0600)
快速开始
/backup-setup # 向导:填地址、账号、密码(默认 DPAPI 加密保存),自动测连接
/backup # 立即备份
/backup verify # 体检:密钥来源 / 明文告警 / 连接性
/backup check # 单独测连接
/backup list # 看远端已有备份
/backup restore # 选一个备份恢复到临时目录
配置写入 ~/.pi/agent/webdav-backup.json。
命令一览
| 命令 | 作用 |
|---|---|
/backup |
立即备份(同步执行,完成后通知) |
/backup check |
测试 WebDAV 连通性与认证 |
/backup list |
列出远端备份文件 |
/backup dry |
试运行:只扫描统计,不上传 |
/backup status |
查看上次备份时间 |
/backup prune |
清理旧备份(保留 keepVersions 个) |
/backup restore |
交互选择备份并恢复;完成后询问是否写回当前 pi(换机一键还原);如含 extra/ 数据会询问是否归位;未写回时自动体检恢复目录并询问补装插件 |
/backup restore-apply <目录> |
把恢复目录写回当前 pi 的 agent 目录(覆盖前自动备份) |
/backup apply-extras <目录> |
把恢复目录里 agentDir 之外的额外数据(extra/)归位到真实路径 |
/backup verify |
配置体检:密钥来源、明文告警、连接性 |
/backup plugins |
插件体检:已声明的插件是否装齐(含逐个依赖查缺失) |
/backup repair-plugins |
补装缺失的插件 / 依赖(npm install / git clone) |
/backup keys list |
列出已加密保存的密钥 |
/backup keys set <名称> |
用 DPAPI 加密保存密钥 |
/backup keys rm <名称> |
删除密钥 |
/backup keys migrate |
把配置里的明文密钥迁成加密引用 |
/backup-setup |
配置向导(默认走 DPAPI 加密) |
/backup-cron [HH:MM] |
安装每日定时备份(Windows 计划任务) |
/backup-cron remove |
移除定时任务 |
/backup-log |
查看最近 20 行日志 |
命令行也可直接用:
node ~/.pi/agent/extensions/webdav-backup/cli.mjs backup --json
node ~/.pi/agent/extensions/webdav-backup/cli.mjs doctor --json
node ~/.pi/agent/extensions/webdav-backup/cli.mjs migrate --method dpapi
node ~/.pi/agent/extensions/webdav-backup/cli.mjs restore --list
node ~/.pi/agent/extensions/webdav-backup/cli.mjs restore --file pi-agent-20260924-120000.tar.gz --to D:\restore
node ~/.pi/agent/extensions/webdav-backup/cli.mjs plugins --json [--dir D:\pi-restore]
node ~/.pi/agent/extensions/webdav-backup/cli.mjs repair-plugins --dry-run [--dir D:\pi-restore]
node ~/.pi/agent/extensions/webdav-backup/cli.mjs apply-extras --dir D:\pi-restore [--dry-run] [--force]
node ~/.pi/agent/extensions/webdav-backup/cli.mjs restore-apply --dir D:\pi-restore [--dry-run] [--force]
node ~/.pi/agent/extensions/webdav-backup/cli.mjs detect-legacy --json
输出末行固定是 JSON 汇总(--json 时只输出该行,人类模式则前面还有日志行),脚本可直接解析;退出码 0 成功 / 1 失败。
密钥不明文落盘
remote.password 与 encryptKey 不直接写明文,而是写引用:
| 语法 | 说明 | 适用 |
|---|---|---|
dpapi:名称 |
Windows DPAPI 用户级加密存入 secrets.dpapi.json,本机本用户可解,无需主密码 |
仅 Windows |
$ENV_VAR / ${ENV_VAR} |
环境变量(也支持 $A-$B 拼接,$$ 转义) |
跨平台 |
file:路径 |
从文件读取(自动去换行;相对路径基于 ~/.pi/agent) |
跨平台 |
!命令 |
执行命令取 stdout(可对接密码管理器 CLI) | 跨平台 |
plain:值 |
显式声明明文(会告警,仅调试用) | — |
| 其他值 | 视为明文(会告警) | — |
该语法与 pi 自身的
resolve-config-value约定一致,auth.json的key字段也支持$ENV_VAR与!命令。⚠️ 要跨 Windows / Linux / macOS 多机同步,用
file:或$ENV,不要用dpapi:——同 DPAPI 密文只能在本机本用户解密。 详见下文「跨平台密钥(多机同步)」。
推荐做法
{
"remote": {
"url": "https://dav.jianguoyun.com/dav/",
"username": "you@example.com",
"password": "dpapi:webdav-password" // 不是明文
},
"encrypt": true,
"encryptKey": "dpapi:backup-key" // 不是明文
}
密钥本身这样存:
/backup keys set webdav-password # 提示输入,DPAPI 加密入库
/backup keys set backup-key
/backup keys list # 只看名称与时间,不显示明文
已有明文配置?一条命令迁走(自动备份原文件):
/backup keys migrate
非 Windows 或偏好环境变量:
node cli.mjs migrate --method env # 写入用户环境变量(setx)
node cli.mjs migrate --method file # 写入 0600 权限的 secrets/ 目录
体检
/backup verify
输出密钥来源、是否明文、DPAPI 是否可用、连接是否通过。有明文会直接标 ⚠️。
DPAPI 的边界:防御的是「配置文件/备份被拷走或误传到云端」。不防御已经能以你的身份运行代码的本机恶意程序。若需更强隔离,用
!命令对接外部密码管理器。
环境变量方式也支持
不想改配置文件时,仍可用环境变量(优先级低于配置里的值):
setx PI_WEBDAV_BACKUP_PASSWORD "你的应用密码"
setx PI_WEBDAV_BACKUP_KEY "你的加密口令"
跨平台密钥(多机同步)
dpapi: 只能在 Windows 本机本用户解密,同一份配置在 Linux 上会解析失败。要在 Windows/Linux/macOS 多机共用一套配置,改用 file:(推荐,无需改 shell)或 $ENV。
方案:file: 相对引用(配置里写相对 agentDir 的路径,各平台写法一致)
{
"remote": { "password": "file:secrets/webdav-password.txt" },
"encrypt": true,
"encryptKey": "file:secrets/backup-key.txt"
}
每台机器各创建一次密钥文件(内容相同):
mkdir -p ~/.pi/agent/secrets
printf '%s' '你的加密口令' > ~/.pi/agent/secrets/backup-key.txt
printf '%s' '你的应用密码' > ~/.pi/agent/secrets/webdav-password.txt
chmod 600 ~/.pi/agent/secrets/*.txt # Windows 可忽略
secrets/**默认被排除,密钥不会随备份上传。所以新机器必须手动做一次这步(这是唯一的手动环节)。 读取时会自动去掉行尾换行,echo/printf都可以。
方案:$ENV
配置里写 "encryptKey": "$PI_WEBDAV_BACKUP_KEY",然后各机器设同名环境变量(值相同):
# Linux / macOS:~/.bashrc 或 ~/.zshrc
export PI_WEBDAV_BACKUP_KEY="你的加密口令"
# Windows PowerShell(需新开终端生效)
setx PI_WEBDAV_BACKUP_KEY "你的加密口令"
从现有 dpapi: 迁移:node cli.mjs migrate --method file(写入 secrets/)或 --method env(写入用户环境变量)。
备份什么
默认备份 ~/.pi/agent/ 下除以下内容外的所有文件:
| 排除项 | 原因 |
|---|---|
sessions/** |
会话历史,通常很大(可用 includeSessions 打开) |
tmp/**、web-search-cache/** |
缓存 |
**/node_modules/** |
可重装(但开 includePlugins 后会一并备份,见下文) |
bin/** |
pi 自动管理的 fd/rg 二进制,换机后会重新下载(约 8MB) |
**/*.log |
日志 |
包含:extensions/(你写的扩展)、models.json、models-store.json、settings.json、trust.json、auth.json、npm/package.json 等。
想换机一键还原? 打开
includePlugins: true,把插件实现代码(node_modules)也备份进去, 新电脑恢复后插件即可直接用,无需联网重装。详见下文「换机一键还原」。
插件数据写在 ~/.pi/agent 之外怎么办(extraScope)
有些插件把配置/数据写在 agent 目录之外,默认 scope: ["."] 够不到:
| 路径 | 谁写在这里 |
|---|---|
~/.pi/settings.json |
pi 旧版本遗留的包声明(~/.pi/npm/ 同理) |
~/.pi/web-search.json |
pi-web-access 的回退位置 |
~/.config/rpiv-*/config.json |
@juicesharp/rpiv-* 系列(XDG 配置) |
用 extraScope 把这些**绝对路径(或 ~/...)**一并备份;归档内统一放在 extra/ 前缀下:
{
"extraScope": [
"~/.pi/settings.json",
"~/.pi/web-search.json",
"~/.config/rpiv-todo"
]
}
- 备份时:
extra/内的 JSON/配置会按字段名(key/token/password/apiKey…)脱敏 - 恢复时:默认只解到恢复目录;
/backup restore会询问是否归位到真实路径,也可手动node cli.mjs apply-extras --dir <恢复目录> - 跨机可移植:备份时会把每个文件的归属记成“锚点 + 尾巴”(
home/config(XDG) /appdata), 恢复时按当前机器的锚点重算目标。换用户名、换盘符、Linux↔Windows 都能正确归位:- 旧机
/home/alice/.pi/settings.json→ 新机/home/bob/.pi/settings.json - 旧机
C:/Users/alice/.pi/settings.json→ 新机D:/Users/carol/.pi/settings.json - 不在任何锚点下的绝对路径(如
/etc/foo)保持原样,并在结果里标为unmapped以便你核对
- 旧机
- 安全:若本地已存在“真实值”、而归档里是
__REDACTED__,归位会跳过该文件(除非加--force), 以免把真密钥覆盖成占位符;覆盖前会把原文件存为<name>.bak-extras-<时间戳> - 探测遗留配置:
/backup-setup会自动检测并询问是否纳入;也可node cli.mjs detect-legacy
归档里的
extra-manifest.json记录了源机器的platform/hostname/home与各锚点, 归位时会打印出来,便于确认“这次恢复来自哪台机器”。
插件(install 装的包)会怎样
插件是否连同**实现代码(node_modules)**一起备份,由 includePlugins 决定:
includePlugins |
备份里有什么 | 恢复后 |
|---|---|---|
false(默认,瘦备份) |
声明 + git: 源码;npm 包只有 package.json/lockfile |
插件实现不在,需联网 /backup repair-plugins 补装 |
true(推荐,换机一键还原) |
连同 node_modules 一起 |
插件直接可用,无需联网 |
开启 includePlugins: true 时,会保留 npm/node_modules/**、git/**/node_modules/**,仅排除其中运行时用不到、
体积大的内部数据(**/.git/**、node_modules/.cache、.vite、.turbo、.bin)。
取舍:
true能实现真正的一键还原,代价是体积变大(有原生依赖时可能几百 MB,且同一份备份在 不同平台间可能带入不兼容的二进制)。false体积小,但恢复后必须联网补装。无论哪种模式,
/backup restore都不再强制联网:开启includePlugins后,恢复流程会检测到 备份里已含插件代码并跳过补装提示。
敏感文件脱敏(重要)
备份时(加密与否都会),auth.json、webdav-backup.json 以及 extraScope 下的 JSON/配置会先脱敏再上传:
- 按字段名匹配:
key/token/secret/password/credential/cookie/apiKey/private… - 未加密时还额外按值的形状匹配:
sk-…、ghp_…、AIza…、JWT、32 位以上随机串 - 命中替换为
__REDACTED__,结构保留;密钥引用($ENV/dpapi:/file:)不会被抹 - 解析失败时整文件替换为占位符,绝不原样上传
因为加密备份同样会脱敏,用明文写的 API key / 密码无法从备份恢复(会变成
__REDACTED__)。 建议一律用$ENV/dpapi:引用(引用会被保留,恢复后仍可用)。 脱敏是「尽力而为」的防泄漏措施,真正的安全做法是开启encrypt。
配置项
{
"enabled": true,
"remote": {
"url": "https://dav.jianguoyun.com/dav/", // 见下方各服务填法
"username": "you@example.com",
"password": "dpapi:webdav-password", // 密钥引用,见上文;留空则读 $PI_WEBDAV_BACKUP_PASSWORD
"remoteDir": "pi-backup", // 远端目录
"remoteName": "pi-agent" // 文件名前缀
},
"scope": ["."], // 相对 ~/.pi/agent 的路径,可写 ["extensions", "models.json"]
"exclude": [], // 额外 glob 排除规则
"extraScope": [], // agentDir 之外的额外备份路径(绝对路径或 ~/...),见上文「插件数据」
"includePlugins": false, // 是否把插件实现(node_modules)一起备份(换机一键还原的关键),见上文
"includeSessions": false, // 是否备份会话历史
"snapshot": false, // 见下文「两种模式」
"encrypt": false, // AES-256-GCM 加密
"encryptKey": "dpapi:backup-key", // 密钥引用(≥16 位):dpapi:名称 / $ENV_VAR / file:路径;见上文「密钥」
"backupOnExit": true, // 退出 pi 时自动备份
"backupOnExitMinIntervalMinutes": 30, // 两次自动备份最小间隔
"backupOnExitTimeoutMs": 600000, // 自动备份等待上限
"keepVersions": 10, // 保留最近 N 个归档(0 = 不清理)
"timeoutMs": 60000,
"insecureTls": false // 自签名证书时才设 true
}
各服务 URL 填法
| 服务 | url | username | password |
|---|---|---|---|
| 坚果云 | https://dav.jianguoyun.com/dav/ |
注册邮箱 | 应用密码(账户信息 → 安全选项 → 添加应用密码) |
| Nextcloud | https://云地址/remote.php/dav/files/<用户名>/ |
用户名 | 登录密码或应用密码 |
| 群晖 WebDAV | https://NAS地址:5006/<共享文件夹> |
DSM 账号 | DSM 密码 |
| 自建 rclone serve | http://主机:端口/ |
--user 指定的值 |
--pass 指定的值 |
⚠️ 密码建议用引用而非明文(多机同步请用
file:/$ENV,不要用dpapi:):/backup keys set webdav-password # Windows DPAPI # 跨平台:"password": "file:secrets/webdav-password.txt"也可用环境变量:
setx PI_WEBDAV_BACKUP_PASSWORD "你的应用密码"
两种备份模式
归档模式(默认,snapshot: false)
每次打包成一个 pi-agent-<时间戳>.tar.gz(可选 .pibak 加密)上传。
- ✅ 单个文件,下载/归档方便
- ✅ 天然有版本历史
- ⚠️ 每次全量上传
快照模式(snapshot: true)
逐文件上传到 pi-backup/pi-agent/,用远端 manifest.json 做增量比对。
- ✅ 只传变化的文件,第二次起非常快
- ✅ 能单独恢复某一个文件
- ⚠️ 只保留最新状态,无历史版本
建议:日常用快照模式(省流量),配合每周一次归档(可另配一个 remoteName)。
退出自动备份怎么工作
session_shutdown(reason: quit)触发时:
- 检查
enabled/backupOnExit - 距上次自动备份不足
backupOnExitMinIntervalMinutes→ 跳过(避免反复开关 pi 重复上传) - 否则 spawn 一个分离子进程执行上传,立刻返回
用分离进程的原因:pi 退出时若被 SIGTERM/关窗打断,留在主进程的上传会被中断;分离进程不受影响,且不会卡住退出。
日志写在 ~/.pi/agent/webdav-backup.log。
每日定时备份
# 在 pi 里:/backup-cron 03:00 或命令行:
node "$env:USERPROFILE\.pi\agent\extensions\webdav-backup\cli.mjs" install-cron --time 03:00
需要管理员权限(创建计划任务)。移除:/backup-cron remove。
恢复
/backup restore # 选择备份 → 恢复 → 询问是否写回当前 pi
恢复分两步:先解到临时目录(不会直接动你的现有配置),再由你确认后才写回 ~/.pi/agent。
写回前每个被覆盖的旧文件都会另存为 <名字>.bak-apply-<时间戳>。
换机一键还原(推荐):新电脑装好 pi → /backup-setup 配好同一 WebDAV → /backup restore -y:
/backup restore
├─ 选备份(`-y` 直接取最新)→ 解压到临时目录
├─ 本机已装插件时选【合并 / 替换】(见下)
├─ 询问是否【写回当前 pi】(restore-apply)
│ ├─ 是:把配置/扩展/插件实现写回 ~/.pi/agent(覆盖前自动备份)
│ └─ 否:只留在临时目录,你自己拷
├─ 如含 extra/ 额外数据(~/.pi/settings.json 等)→ 询问是否归位
└─ 若备份已含插件代码 → 完成;否则体检并询问联网补装
/reload # 重启 pi 后插件即可用
前置条件:备份时开启了 includePlugins: true,插件实现才会在备份里;否则写回后仍需联网补装。
本机已经装了插件时,写回(合并 vs 替换)
新电脑往往已经装过一些插件——可能和备份里同名但版本不同,也可能多装了备份里没有的。 直接覆盖会把同名插件降级、把本机多装的声明抹掉、留下版本混合的“缝合”目录。所以写回有两种模式:
| 模式 | settings.json 的 packages | npm/package.json 的 dependencies | 本机已装的插件实现 | 本机多装的插件 |
|---|---|---|---|---|
| 合并(默认,推荐) | 本机声明优先,追加备份里本机没有的 | 本机依赖优先,追加备份里本机没有的 | 保留本机版本,不覆盖 | 保留(声明与代码都留) |
| 替换(旧机原样) | 完全用备份版 | 完全用备份版 | 被备份覆盖(同名降级) | 声明被移除(代码不删) |
为何要合并
npm/package.json:pi 启动时会拿它与node_modules做协调(npm install)。 若直接覆盖成备份版,本机独有的插件会被 npm 当成多余包【卸载】。所以合并模式会保留本机已有依赖。
- 交互式
/backup restore:本机有插件声明时会先让你选(默认合并) - 命令行:
--mode merge(默认)/--mode replace
“合并”对已安装的插件只保声明与代码、不升级也不降级;备份里本机没有的插件则连代码一起补入。 想完全回到旧机器状态(含同名降级)用“替换”。两种模式都不会删除本机多装的插件代码, 只会(在替换时)从其
settings.json声明里移除。
手动写回某个已恢复目录(不重新下载):
/backup restore-apply <目录>
# 或命令行:
node cli.mjs restore-apply --dir D:\pi-restore [--dry-run] [--force] [--mode merge|replace]
写回的安全规则:
- 只写回 agent 目录内的内容;
extra/(agentDir 之外)由apply-extras单独处理 - 自动跳过备份/恢复产生的元数据(manifest、state、log)
- 目标已存在且内容不同时,先备份旧文件再覆盖(
.bak-apply-*) - 归档里是
__REDACTED__而本地有真实值时跳过(除非--force),避免把真密钥抹掉 - 合并模式下,本机已装插件的目录整体保留,不写入备份里的同名文件(避免版本混合)
若只想解到指定目录、不写回:
node cli.mjs restore --file pi-agent-20260924-120000.tar.gz --to D:\pi-restore
恢复后把插件装回来(仅瘦备份需要)
如果备份时没有开启 includePlugins,插件实现代码 node_modules 不在备份里,需要联网补装。
恢复后跑(--dir 指向刚恢复出来的目录,不加则看当前 ~/.pi/agent):
/backup plugins # 看哪些插件没装好
/backup repair-plugins # 补装
node cli.mjs plugins --json --dir D:\pi-restore # 命令行同理
node cli.mjs repair-plugins --dir D:\pi-restore
会做的事(安装命令与 pi 自身的安装逻辑保持一致):
| 情况 | 动作 |
|---|---|
npm 包装在 settings.json 里、但 npm/node_modules 里没有 |
npm install <包>[@版本] --legacy-peer-deps(在 <目录>/npm) |
| git 包目录不存在 | git clone --depth 1 [--branch <ref>];ref 不是分支/标签(如 commit SHA)时自动回退为完整 clone + git checkout |
| git 包目录在、但缺依赖(逐个依赖名检测,拷了一半也能查出来) | 在该包目录内 npm install --omit=dev |
--dry-run 只列将执行的命令,不落盘;单个包失败不影响其余,结果里会分开报 repaired / failed。
来源解析与 pi 的
parseGitUrl完全对齐:支持git:简写、https://…URL、git@host:pathscp 形式, ref 可含/(如feature/x),.git后缀自动去掉(否则会装到 pi 找不到的目录)。
/backup restore在未写回时会自动体检刚恢复出来的目录并询问是否立即补装(体检的是临时目录, 而不是当前~/.pi/agent,因为声明还在临时目录里);若已写回当前目录,则看点目录。/backup restore -y只略过「恢复哪个」的询问,写回与补装仍会各自询问一次。
加密备份的恢复
需要 encryptKey 一致(配置文件或 $PI_WEBDAV_BACKUP_KEY)。密钥不对会明确报错,不会写出损坏文件。
解包过程会拒绝绝对路径与 .. 穿越,防止恶意归档写出到目标目录之外。
多机同步(Windows ↔ Linux)
⚠️ 先认清本质:这是"备份/恢复",不是实时双向同步。
| 命令 | 方向 |
|---|---|
/backup |
本机 → 云端(上传) |
/backup restore |
云端 → 本机(下载,默认合并) |
两台机器各自 /backup 会互相覆盖,建议以一台为主机,其他机器只做 /backup restore。
一、旧机(主机):先备份
/backup dry # 确认扫描结果(开了 includePlugins 会含几百 MB 插件代码)
/backup # 正式备份
二、新机:安装 + 恢复
# 1. 装插件(GitHub / npm 二选一)
pi install git:github.com/ddldxiacan/pi-webdav-backup@v1
# 然后重启 pi
# 2. 创建密钥文件(内容与旧机一致;secrets/ 不入备份,故需手动一次)
mkdir -p ~/.pi/agent/secrets
printf '%s' '你的加密口令' > ~/.pi/agent/secrets/backup-key.txt
printf '%s' '你的应用密码' > ~/.pi/agent/secrets/webdav-password.txt
chmod 600 ~/.pi/agent/secrets/*.txt
# 3. 写配置 ~/.pi/agent/webdav-backup.json(与旧机相同,密钥用 file:)
配置文件模板(Windows/Linux 通用):
{
"enabled": true,
"remote": {
"url": "https://你的.webdav/dav",
"username": "you@example.com",
"password": "file:secrets/webdav-password.txt",
"remoteDir": "pi-backup",
"remoteName": "pi"
},
"scope": ["."],
"includePlugins": true,
"extraScope": ["~/.pi/settings.json"],
"encrypt": true,
"encryptKey": "file:secrets/backup-key.txt",
"includeSessions": false
}
# 4. 测连接 + 恢复
/backup check
/backup restore -y # 选备份 → 解密解压 → 选合并/替换 → 写回 → 归位 extra → 完成
/reload
三、关键约束
| 项 | 要求 |
|---|---|
encryptKey |
两台机器值必须一致,否则解不开加密备份 |
| 密钥引用 | 多机一律用 file: 或 $ENV,不要用 dpapi:(只能本机解密) |
remote.url / remoteDir / remoteName |
两台机器必须完全一致,否则找不到备份 |
| 插件还原 | 旧机需开 includePlugins: true;否则新机恢复后要联网 /backup repair-plugins |
| 合并/替换 | 新机已装插件时,默认"合并"(保留本机已装的,只补缺失) |
四、常见坑
| 症状 | 处理 |
|---|---|
| 新机报"加密密钥解析失败" | 用了 dpapi: 但非 Windows;改用 file: / $ENV |
| 解密失败 | backup-key 与旧机不一致(或旧备份用了更早的口令) |
| 恢复后插件不生效 | 旧机未开 includePlugins → /backup repair-plugins 联网补装 |
| 恢复后缺插件声明 | 插件声明在 ~/.pi/settings.json(非 agent)→ 加入 extraScope |
| 连接 401 | WebDAV 密码错(坚果云要用应用密码) |
测试
cd pi-webdav-backup
npm test
# 等价于:node extensions/webdav-backup/run-tests.mjs
扩展契约校验(模拟 pi 加载器导入入口文件):
node extensions/webdav-backup/verify-load.mjs
11 个测试文件、450+ 项断言,用内置的内存 WebDAV 服务端做真实 HTTP 往返:
| 文件 | 覆盖 |
|---|---|
test.mjs |
收集/排除、归档、加密往返、快照增量、prune |
test-cli.mjs |
真实子进程调用 CLI、退出备份、DPAPI 端到端、doctor、日志、状态 |
test-restore.mjs |
归档/加密/快照恢复、目录穿越防护、302 重定向(对象存储)恢复 |
test-plugins.mjs |
插件声明解析(与 pi 官方 parseGitUrl 对齐:.git 后缀、ref 含 /、URL/scp 来源)、恢复后体检(缺安装/逐个依赖查缺依赖)、补装、SHA ref 回退、dry-run、真实恢复往返 |
test-apply.mjs |
换机一键还原:includePlugins 排除组装与收集、含插件归档往返恢复、restore-apply 写回/覆盖前备份/幂等/dry-run/防止 __REDACTED__ 覆盖真值、跳过 extra/ 与元数据 |
test-extras.mjs |
agentDir 之外的额外数据(extraScope):路径映射互逆、跨机锚点重映射(换用户名/盘符/平台)、收集、脱敏、恢复归位、幂等、--force 安全防护、遗留配置探测 |
test-redact.mjs |
脱敏(含「字段名无关但值是密钥」的回归用例) |
test-json.mjs |
CLI 输出 JSON 解析容错(回归:空输出曾抛裸 JSON 错误吞掉 stderr) |
test-empty-cmd.mjs |
空参数/子命令解析、输出契约(回归:/backup 不带参数曾报「未知命令:」、完成提醒拿不到 JSON 汇总) |
test-secrets.mjs |
dpapi/$ENV/file/!命令/plain 各分支、明文告警、三种迁移方式 |
test-auth-migrate.mjs |
auth.json 明文密钥审计与迁移(迁移前自动备份、可回滚) |
版本历史
| 版本 | 内容 |
|---|---|
| v1.4.0 | 跨平台健壮性:① 合并/恢复时过滤旧机器专属的绝对路径本地源(E:\...、/home/...、UNC)——pi install /绝对/路径 会把路径写进 settings.json,跨机恢复时无效,现在不再带入目标机;② WebDAV 客户端对 423 Locked / 429 / 5xx 与网络抖动自动重试(指数退避,尊重 Retry-After,上限 4 次),修复实测中两个实例并发上传导致的 423 直接失败;新增 test-webdav.mjs(故障注入)与跨平台路径回归用例 |
| v1.3.3 | 修复合并模式会卸载本机插件:restore-apply 合并模式此前只合并 settings.json,却用备份版覆盖 npm/package.json——pi 下次启动的 npm 协调会把本机独有插件当成多余包卸载(实测于 Windows→Linux 同步)。现改为同时合并 npm/package.json 的 dependencies(本机优先),结果里标 npm-dependencies。另:文档补充「多机同步」与「跨平台密钥」流程 |
| v1.3.2 | 跨平台多机同步:依赖体检修正(npm 提升到父级的依赖不再误报为缺失,按 Node 模块解析算法沿祖先 node_modules 查找);文档新增「跨平台密钥(多机同步)」与「多机同步(Windows ↔ Linux)」流程,明确多机用 file: / $ENV 而非 dpapi:,并给出常见坑 |
| v1.3.1 | 依赖体检误报修正(npm 提升的依赖被当成缺失) |
| v1.3.0 | 换机一键还原:新增 includePlugins(把插件实现 node_modules 一并备份,重机恢复即可直接用,无需联网重装);新增 restore-apply / /backup restore-apply(把恢复结果写回当前 ~/.pi/agent,覆盖前备份、幂等、防止 __REDACTED__ 覆盖真值);写回默认【合并模式】:本机已装插件保留不被覆盖、settings.json 只合并 packages(避免删掉/降级本机插件),可选 --mode replace 完全还原;/backup restore 默认询问是否写回当前 pi,已含插件时不再提示联网补装;/backup-setup 向导新增插件代码选项;脱敏审计跳过第三方插件代码(避免误报);排除规则重构(includePlugins 开关控制 node_modules) |
| v1.2.0 | 插件数据跨机同步补齐:新增 extraScope(备份 agentDir 之外的路径,如 ~/.pi/settings.json、~/.config/rpiv-*,归档内放 extra/ 前缀);归档写入 extra-manifest.json,按 home/config(XDG)/appdata 锚点记录归属,恢复时按当前机器重算目标(换用户名/盘符/平台均可);/backup apply-extras 归位(幂等、覆盖前备份、防止 __REDACTED__ 覆盖真密钥、支持 --force、未映射路径单独提醒);新增 detect-legacy 探测遗留配置;/backup-setup 引导纳入;默认排除新增 bin/**;README 澄清加密备份同样会脱敏 |
| v1.1.0 | 插件备份/恢复修好:恢复后自动体检「恢复出来的目录」并补装(此前看错目录,补装提示永不出现);plugins/repair-plugins 新增 --dir;git 来源解析与 pi 完全对齐(.git 后缀、ref 含 /、https:///scp 形式);commit SHA ref 自动回退为完整 clone + checkout;缺依赖逐个检测;安装参数对齐 pi(--legacy-peer-deps / --omit=dev);Windows 路径含空格不再报错。另含 v1.0.2 之后的修复:恢复遇 302 重定向到对象存储自动跟随(不外泄凭据)、完成通知拿不到汇总的回归 |
| v1.0.2 | 初始发布:归档/快照两种备份模式、AES-256-GCM 加密、DPAPI/环境变量/命令密钥、明文脱敏、退出自动备份与每日定时备份、恢复到临时目录 |
发新版时:打新 tag(如
v1.1.1)后把移动 tagv1一并指过去,让pi install …@v1始终装最新:git tag -f v1 v1.1.1 && git push -f origin v1
故障排查
| 现象 | 处理 |
|---|---|
| 401 认证失败 | 坚果云必须用应用密码;确认 username 是邮箱 |
| 404 路径不存在 | 检查 url 结尾斜杠与路径;Nextcloud 要带 /remote.php/dav/files/<用户名>/ |
| 403 拒绝访问 | 远端目录权限不足;坚果云需先在网页版建好目录 |
| TLS 证书错误 | 自签名证书才设 insecureTls: true(有中间人风险) |
| 备份很慢 | 改用 snapshot: true;或把 includeSessions 关掉 |
| 退出时没备份 | 看 webdav-backup.log;确认 backupOnExit: true 且距上次已过间隔 |
| 想立刻看到日志 | /backup-log |
| DPAPI 解密失败 | 密文只能在本机本用户下解;换机器/换用户需重新 /backup keys set |
| 提示「环境变量未设置」 | /backup keys set 改用 dpapi,或先设好该环境变量 |
恢复报 HTTP 423 |
网盘并发锁;v1.4.0+ 会自动重试(指数退避,尊重 Retry-After)。若持续失败,请确认没有安装两份 backup 插件(退出时会同时备份),可用 /backup list + pi list 检查 |
恢复报 HTTP 302 |
Cloudreve/群晖等把下载重定向到对象存储(腾讯云 COS 等);客户端已自动跟随跨主机 302(不携带凭据),请升级到 v1.1.0+ |
恢复后 settings.json 多了无效路径 |
v1.4.0+ 已过滤旧机器的绝对路径本地源;旧版残留可手动删除 |
| 恢复后插件不生效 | 若备份时未开启 includePlugins,插件代码(node_modules)不在备份里,需联网补装:/backup repair-plugins(可带 --dir)。推荐下次备份开启 includePlugins: true,下次即可一键还原 |
| 写回时提示跳过某些文件 | 归档里是 __REDACTED__ 而本地已有真实密钥时会被跳过(避免把真密钥抹掉);确认要覆盖可加 --force |
补装报 git clone 失败 |
确认能访问 github.com 等源;ref 是 commit SHA 时会自动回退为完整 clone + checkout,仍失败则看日志里的 git 输出 |
| 插件体检一直报「缺少安装」 | 多半是 .git 后缀 / ref 形式的声明被装到了 pi 找不到的目录;v1.1.0+ 已与 pi 的解析对齐,升级后跑一次 /backup repair-plugins |
| 配置体检 | /backup verify 一次看全部状态 |