npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pi-webdav-backup

v1.4.0

Published

pi 配置与插件数据自动备份到 WebDAV(坚果云 / Nextcloud / 群晖 / 自建):归档与快照两种模式、AES-256-GCM 加密、DPAPI 密钥管理、敏感字段脱敏、agentDir 之外的额外数据(extraScope)、插件实现代码随备份(includePlugins)实现换机一键还原、退出自动备份与每日定时备份。

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/[email protected]   # 钉死某个版本(不自动升级)
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": "[email protected]",
    "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": "[email protected]",
    "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)触发时:

  1. 检查 enabled / backupOnExit
  2. 距上次自动备份不足 backupOnExitMinIntervalMinutes → 跳过(避免反复开关 pi 重复上传)
  3. 否则 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:path scp 形式, 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": "[email protected]",
    "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)后把移动 tag v1 一并指过去,让 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 一次看全部状态 |