把 Cloudflare 上的 NodeWarden 和 Olares 上的 Vaultwarden 两边保险库拉下来对比, 新增的互相补齐,不同的保留两份(冲突副本),不覆盖、不删除。 及时性要求不高时在 Win11 本地手动跑即可;逻辑成熟后再打包容器搬到服务器定时跑。
| 情况 | 动作 |
|---|---|
| 两边都有、内容相同 | 跳过 |
| 只在一边有 | 在另一边新建 |
| 两边都有、内容不同、且上次同步后又变过 | 两边不动原件,各把对方版本建一条「冲突副本」,人工裁决 |
| 两边都有、内容不同、但已保留过副本且没再改 | 跳过(不会每跑一次多一对副本) |
| 已配对的条目在一边消失 | 判为疑似删除:只报告、不删另一边、也不会把存活的那条当新增补回去(墓碑机制) |
| 同名同用户名重名多条、配不准 | 进「歧义」清单,人工处理,脚本不碰 |
- Python 3.8+(只用标准库)。Win11 自检:
python --version - 官方 Bitwarden CLI:到 bitwarden.com 下载 CLI 版(Windows),
把
bw.exe放到任意目录,并:- 要么把该目录加进 PATH,
- 要么在本目录
config.json的"bw"里写bw.exe的完整路径。 自检:bw --version
- 两边各有一个账号(建议同邮箱、同主密码,日常切换客户端时省事; 脚本本身不要求两边密码相同)。
copy config.example.json config.json
notepad config.json :: 填两边的 server 地址和邮箱
python bw_sync.py :: 只读预览:拉取+对比+出报告,不写任何数据
python bw_sync.py --apply :: 确认报告无误后,真正执行补齐/冲突副本,并保存 state运行时会用 getpass 分别提示输入两边主密码(不回显、不落盘)。
想免交互,可以先在当前终端里设置环境变量(名字见 config.json 的 password_env):
set BW_SYNC_NODE_PASSWORD=******
set BW_SYNC_VAULT_PASSWORD=******
python bw_sync.py --apply注意别把带密码的命令写进 .bat 文件存着。
bw_sync.py— 脚本本体config.json— 你的配置(从 example 复制;只有地址和邮箱,没有密码)bw-sync-state.json— 同步状态(配对表、内容哈希基线、墓碑),--apply后自动生成。 别随便删:删了等于失忆,脚本会重新靠「类型+名称+用户名」配对, 内容不同的同名条目会被当成新冲突再保留一轮副本。换电脑时把这个文件一起带走。bw-node/、bw-vault/— bw CLI 各自的独立配置目录 (脚本用BITWARDENCLI_APPDATA_DIR隔离两边登录态,不影响你自己日常用的 bw)。
- 附件:条目本身会同步,附件不搬,报告里会点名哪些条目带附件,自己手动搬或等 v2。
- passkey(FIDO2 凭证):条目会同步,但登录项里的 passkey 不带过去——passkey 本来就绑账号密钥,两边各存一份独立的才对。
- 密码历史、Send、组织/集合条目:不同步;组织条目会跳过(NodeWarden 本来也不支持组织)。
- 删除传播:永远不自动删。要真删一条,两边都手动删。
- 条目里 URI、自定义字段的顺序变了也会被判为「内容不同」而产生冲突副本,属已知误报来源。
- 脚本运行时内存里会同时有两边的主密码和解密后的保险库(经 bw CLI),
跑的时候别在不可信的机器上跑;跑完它会
bw lock两边。 - 报告只含条目名称和数量,不含密码;state 文件只存哈希和 id,不存明文。
python bw_sync.py --selftest用内置假数据把相同/新增/冲突/删除/重名/回收站/只读等分支都跑一遍,全 PASS 才算逻辑正常。
-
实测发现:真 bw 连一个不可达地址时自己会长时间重试(60 秒都不返回), 所以脚本给每条 bw 命令加了超时(一般命令 120 秒、sync/list 300 秒), 一边服务器宕机时脚本会明确报「超时——服务器可能不可达」并退出,不会挂死; 云上定时跑时这点尤其重要,别把超时去掉。
-
NodeWarden 对官方 bw CLI 的兼容程度:它宣称兼容官方客户端, 但 CLI 未见单独声明。第一次先跑只读模式,若 NodeWarden 那边
bw login/list报错, 把报错原文留着——可能要改走邮箱密码登录的具体参数或换它的 Web 导出兜底。 -
若某边开了登录 2FA 且 CLI 登录被拦,同样先保留报错再调整(API key 登录是备选路径)。
思路就一句话:bw 跟脚本一起打进镜像,不在运行时现装。
Dockerfile:基于python:3.12-slim,从 GitHub 官方 release 下载 bw 二进制 (当前钉死 2026.10.0,含 amd64/arm64 两份 sha256 校验,构建时校验不过就失败), 不用 npm 装 bw(避开 2026.4.0 那次 npm 投毒的同类风险),版本不浮动、要升手动改。- 运行时数据全靠 volume(见
docker-compose.yml):config.json、bw-sync-state.json、两边登录态目录bw-node/、bw-vault/挂在宿主机, 容器重建/升级都不丢。注意 bw 的登录态能持久化、但 session 不能——脚本每次跑都会 用主密码重新 unlock,所以无人值守时主密码必须从环境注入。 - 密钥只走环境变量:把
.env.example复制为.env填两边主密码(chmod 600、别进仓库), compose 用env_file注入;镜像和 config 里永远不出现密码。 - 无人值守登录:除了主密码,还可以给每边填个人 API key
(环境变量
BW_SYNC_NODE_CLIENTID/CLIENTSECRET、BW_SYNC_VAULT_*,Web 端 设置→安全→API 密钥里查),脚本检测到就优先用bw login --apikey登录、 再用主密码 unlock。第一次在云上跑仍建议先只读模式人工盯一遍, 若被新设备验证/2FA 拦下,先在本地同账号完成一次登录再回来。 - 部署位置:这是个跑完就退的一次性任务容器,宿主机 crontab 定时
docker compose run --rm bw-sync即可;放 Olares(当普通容器应用跑)或任意 VPS 都行。 它没法跑在 Cloudflare Workers 上(Workers 里没有能跑 bw 二进制的环境), 云端那份 NodeWarden 只是被同步的对象,不影响。
构建与首跑:
docker compose build
docker compose run --rm --entrypoint python3 bw-sync /app/bw_sync.py # 只读首跑
docker compose run --rm bw-sync # 正式同步注:这份 Dockerfile 在交付环境里没法实际 build(无 Docker),但 bw 2026.10.0 的 Linux 二进制已在同版本环境实测可运行、下载哈希已核对;首次 build 请留意 sha 校验输出。