在服务器 / NAS 上用命令行同步(obsync-cli)
这是什么
obsync 是「手机电脑同步」的命令行客户端,让没有桌面环境的机器也能加入你的同步库:
- 云服务器、NAS、家里的小主机,长期在线地把笔记库和手机、电脑保持一致;
- 不需要安装 Obsidian,也不需要图形界面;
- 同步行为与桌面端插件一致(同一套同步逻辑),冲突处理、删除保护等机制都沿用。
适合场景:给一台常开的机器放一份笔记副本、做备份、让脚本/自动化读写笔记库。
1. 环境要求
| 项目 | 要求 |
|---|---|
| 系统 | Linux,x86_64 或 arm64(需要 glibc;Alpine 请换 glibc 基础镜像) |
| Node.js | 22.5 或更高(需带内置 SQLite,使用 nodejs.org 官方 Node 22 即可) |
| 其他 | 需要能联网访问同步服务;系统带 flock(util-linux,绝大多数发行版自带);本机有可写的磁盘目录 |
| 账号 | 一个「手机电脑同步」账号(微信登录),并已有或将创建一个同步库 |
检查 Node 版本:
node --version # 应显示 v22.5.0 或更高
2. 安装
npm install -g obsync-cli
国内网络访问 npm 官方源较慢时,可以改用镜像源:
npm install -g obsync-cli --registry=https://registry.npmmirror.com
如果 npm 源都连不上,也可以直接从本站下载安装包:
npm install -g https://shoujidiannao.bijitongbu.site/downloads/obsync-cli-latest.tgz
安装后验证:
obsync --help # 显示命令列表
obsync install # 自检运行环境(系统、CPU、Node、SQLite),不会下载任何东西
obsync install 成功时返回 ok:true;失败会指出缺什么(例如 Node 版本过低)。
升级:先停掉正在运行的 obsync run,执行 npm install -g obsync-cli@latest,再执行一次 obsync update(见 7.3)把新版同步插件应用到你的笔记库。
卸载:npm uninstall -g obsync-cli。卸载不会删除你的笔记和同步状态目录(见第 3 节)。
3. 两个目录
每条命令都要告诉 obsync 两个位置:
| 参数 | 含义 |
|---|---|
--vault <目录> | 你的笔记库目录,必须已存在(空目录也行,同步后会被填充) |
--state-dir <目录> | obsync 的私有状态目录,存放登录信息、同步状态、日志。不写时默认 ~/.local/share/obsync |
注意:
- 状态目录必须在笔记库之外,否则会报错。
- 状态目录里有账号凭据和同步密钥,权限是 0700,不要共享、不要放进会被同步的目录、不要提交到 git。
- 一个笔记库对应一份状态。换笔记库路径或换状态目录,会被视为一个全新的设备,需要重新登录、重新加入。
- 同一个笔记库同一时间只能有一个
obsync run,再启第二个会返回「已被占用」(退出码 7)。
为了少打字,下面的示例统一定义一个函数(放进你的 shell 配置里更方便):
mkdir -p ~/notes ~/.local/share/obsync
obs() { obsync "$@" --vault ~/notes --state-dir ~/.local/share/obsync --json; }
以后文中的 obs xxx 都等价于带上这两个目录和 --json 的 obsync xxx。--json 让输出变成单行 JSON,便于脚本处理;人工查看时可以去掉。
4. 快速开始(三步)
# 1) 准备本地环境(安装随包携带的同步插件并校验)
obs init
# 2) 在一个终端里保持同步程序运行(占住当前终端是正常的)
obs run
# 3) 在另一个终端里登录、加入同步库、开始同步(详见第 5、6 节)
obs login start
...
要点:除了 install、init、run、update 之外,所有命令都是在向「正在运行的 obsync run」发指令。 所以必须先让 run 保持运行,再在另一个终端发命令。没有在运行时,这些命令会提示「尚未就绪,请先运行 obsync run」(退出码 5)。
长期使用请参考第 7 节,把 run 交给 systemd 托管。
5. 登录
5.1 用微信登录(推荐)
命令行没有二维码,登录靠一个 6 位登录码:
obs login start
# {"ok":true,"code":0,"message":"操作成功。",
# "sessionId":"<一串本地会话ID>","loginCode":"123456","expiresIn":300}
- 记下
loginCode(6 位数字)和sessionId。登录码默认 5 分钟内有效,以返回的expiresIn(秒)为准。 - 打开微信,进入「笔记同步助手」服务号的对话,把这 6 位数字单独作为一条消息发给它。如果还没关注,请先在桌面端插件的登录引导里找到关注二维码,或在微信里搜索服务号关注。
- 回到终端查询结果,每次执行只查一次,间隔约 2 秒再查:
obs login poll --session '<上面的 sessionId>'
# 还没处理完:{"ok":true,"code":0,...,"phase":"pending"}
# 登录成功: {"ok":true,"code":0,...,"phase":"ok"}
phase | 含义 | 你要做什么 |
|---|---|---|
pending | 服务号还没收到或还没处理完这个码 | 确认已发送,约 2 秒后再 poll |
ok | 登录成功,凭据已保存在状态目录 | 不要再 poll,继续下一步 |
expired | 登录码已过期 | 重新 login start,用新的码 |
常见误区:
--session填的是sessionId,不是loginCode。- 每次
login start只保留最近一次会话,旧码作废。 - 命令返回
phase:"expired"时退出码仍是 0,写脚本请读phase,别只看退出码。 - 已经登录的设备再
login start会被拒绝(「此设备已登录」,退出码 5)。
5.2 用已有的登录凭据(适合自动化)
如果你已经有账号凭据文件(原始文本,不是 JSON),可以直接导入:
obs login token --token-file /secure/path/account-token.txt
# 或从标准输入读取,避免落盘:
obs login token --token-file - < /secure/path/account-token.txt
所有秘密(登录凭据、恢复密钥、设置文件)只能通过 --token-file、--key-file、--input-file 或标准输入传入,不接受直接写在命令行里。
5.3 查看登录状态与退出登录
obs status # loggedIn:true 表示本机已保存登录信息
obs logout # 清除本机的库绑定和登录信息
logout 只清本机,不删除远端的笔记库,也不会让服务端凭据失效。
6. 加入同步库
登录之后,这台机器还没有绑定任何同步库。有三种方式加入,任选其一。 先看看账号下有哪些库:
obs vault list
# vaults:[{vaultId,name,createdAt,fileCount,sizeBytes,deviceCount,connectedCount,lastActiveAt,...}]
如果账号下还没有任何同步库,可以在这台机器上直接新建一个,新建后本机自动绑定到它:
obs vault create --name '我的笔记'
obs status # bound:true
新建的库是空的,本机笔记库里已有的笔记会在开始同步后上传。之后其它设备用配对码或恢复密钥加入这个库即可;新建后请尽快用 export-key --output 保存恢复密钥(见 6.2)。
加入或新建之后,为安全起见同步会先处于暂停状态,你要先确认同步范围,再手动 resume(见 6.4)。
6.1 方式一:输入配对码(旁边有一台已在同步的设备)
适合:手机或电脑上已经装好并在同步,你想把这台机器加进去。
- 在已经在同步的设备(桌面端插件)里打开「配对」功能,生成一个 6 位配对码。
- 在这台机器上:
obs pair start --code 123456
# {"ok":true,...,"sessionId":"...","phase":"claiming"}
obs pair status
# {"ok":true,...,"phase":"sas_pending","sas":{"digits":"482913","emojiIndices":[17,203]}}
- 人工核对:对照旧设备界面上显示的核对数字/表情,确认两边完全一致。不一致说明配对对象不对,请取消。
- 一致后在两端各自确认(旧设备在它的界面点确认;这台机器执行下面命令):
obs pair confirm --session '<pair start 返回的 sessionId>'
obs pair status # 依次出现 decrypting → ready
obs status # 确认 bound:true,库名/库 ID 是你要的那个
phase | 含义 |
|---|---|
claiming | 正在提交配对码、交换信息 |
sas_pending | 等你人工核对数字并确认 |
decrypting | 已确认,正在取得密钥并完成绑定 |
ready | 绑定完成 |
error | 失败(含配对码过期)。pair cancel 后让旧设备重新出码再来 |
提示:
- 配对码是通过命令行参数
--code传入的,会出现在 shell 历史里。它只有短时有效,用完即作废;介意的话事后清理历史。 pair start失败有时是退出码 0 加phase:"error",脚本里要读phase。- 放弃配对:
obs pair cancel --session '<sessionId>'。
6.2 方式二:用恢复密钥加入(没有别的设备在线时)
适合:旧设备不在身边或不在线,但你保存了这个同步库的恢复密钥。
恢复密钥可以来自桌面端插件导出的文件,也可以是另一台 obsync 机器上导出的:
# 在已绑定的旧机器上(可选,用于导出)
obsync export-key --output /secure/path/recovery-key.txt --vault <旧库> --state-dir <旧状态目录>
在这台机器上加入:
obs vault list
obs vault join-key --vault-id '<vault list 里选定的 vaultId>' \
--key-file /secure/path/recovery-key.txt
# {"ok":true,...,"vaultId":"...","vaultName":"..."}
obs status # bound:true
- 恢复密钥只对你指定的那个库有效,拿别的库的密钥会失败(退出码 6)。
- 密钥文件可以是纯密钥文本,也可以是桌面端导出的文件(会自动忽略注释行)。
- 恢复密钥等同于库的钥匙,务必妥善保管,不要发到聊天或工单里。
export-key不加--output会把密钥明文打印在终端,请只在安全环境使用。
6.3 方式三:向在线设备发起加入申请
适合:已有设备在线,但你不方便在它上面生成配对码。
在这台新机器上:
obs settings set --input-file - <<'JSON'
{"deviceName":"我的服务器"}
JSON
obs join-request start --vault-id '<目标 vaultId>'
# {"ok":true,...,"sessionId":"...","phase":"waiting","notifiedCount":1}
notifiedCount 是已通知到的在线设备数。为 0 说明没有设备在线能审批,需要先让旧设备上线。
在已绑定且在线的旧设备上审批(桌面端会弹出审批提示;若旧设备也是 obsync 命令行,则用下面命令):
obsync approvals list --vault <旧库> --state-dir <旧状态目录> --json
# requests:[{requestId,requesterLabel,vaultName,expiresAt,phase}]
obsync approvals approve --request '<requestId>' ... # 同意
# obsync approvals reject --request '<requestId>' ... # 拒绝
同意之后双方进入核对阶段:
# 新机器
obs join-request status # phase:"sas_pending",带 sas 核对数字
# 人工核对两边数字完全一致后,两端各自确认
obs join-request confirm --session '<sessionId>'
# 旧设备若是命令行: obsync approvals confirm --request '<requestId>' ...
obs join-request status # joining → done
obs status # bound:true
放弃:新机器 obs join-request cancel --session '<sessionId>',或旧设备 approvals reject。阶段含义:waiting 等审批,sas_pending 等人工核对,joining 正在加入,done 完成,rejected/expired/error 需要重新发起。
6.4 加入后:确认范围,再开始同步
任何一种方式加入后,同步都先暂停。先看当前同步范围:
obs settings get
可调整的项(只有这几项):
| 设置项 | 含义 |
|---|---|
excludedFolders | 不同步的文件夹/文件(字符串数组) |
excludedFileRules | 不同步的文件规则(每条还必须同时出现在 excludedFolders 中) |
includedFolders | 只同步的文件夹(配合 onlySyncIncluded) |
onlySyncIncluded | true 表示只同步 includedFolders |
conflictAction | 冲突处理:merge(合并)或 conflict(保留冲突副本) |
deviceName | 本设备在同步库里显示的名字 |
pause | 是否暂停(false 同时解除「加入后先暂停」) |
例如只同步 notes 目录:
obs settings set --input-file - <<'JSON'
{"includedFolders":["notes"],"onlySyncIncluded":true}
JSON
确认无误后开始同步:
obs resume
obs sync --timeout 85 # 触发一轮并等待收敛,最长 85 秒
obs status
如果 status 里出现 identityConfirmation(状态 adopt-pending),说明需要你确认「这个目录采用该同步库的身份」,确认无误后按它给出的值执行:
obs identity-confirm --vault-id '<identityConfirmation.vaultId>' --generation '<identityConfirmation.generation>'
7. 长期运行
7.1 systemd(推荐)
先用将要运行服务的同一用户完成 obs init 和登录、加入。新建 /etc/systemd/system/obsync.service(把用户和路径换成你自己的):
[Unit]
Description=obsync headless vault
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=youruser
UMask=0077
ExecStart=/usr/bin/env obsync run --vault /home/youruser/notes --state-dir /home/youruser/.local/share/obsync --json
Restart=on-failure
RestartSec=5
KillSignal=SIGTERM
TimeoutStopSec=90
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now obsync.service
sudo journalctl -u obsync.service -n 50
ExecStart里的obsync要能在服务的 PATH 里找到。用 nvm 装的 Node 请写obsync的绝对路径(which obsync),并确保该路径下的node是 22.5 以上。
之后在同一用户下用 obs status、obs sync 等命令控制它。换用户或换目录会连不上。
关掉发命令的终端不会停掉 run。停止同步请停进程(sudo systemctl stop obsync.service,前台运行时按 Ctrl-C),不要用 logout 或 unbind 代替。
7.2 只同步一次就退出(适合 cron / 脚本)
obsync run --once --timeout 300 --vault ~/notes --state-dir ~/.local/share/obsync --json
启动、同步到收敛、退出。判断成功请同时检查退出码为 0 且输出里 convergence.stable 为 true;被信号打断时可能也返回 0,但没有收敛信息。--once 与常驻的 run 互斥。
7.3 更新同步插件
同步逻辑由一个插件提供,插件随命令行程序一起打包,不需要另外联网下载。升级命令行程序(见第 2 节)后,插件并不会自动换新,需要执行:
# 先停掉正在运行的 run,然后:
obsync update --vault ~/notes --state-dir ~/.local/share/obsync
# 再启动 run
update 会校验随包插件的大小与 SHA-256 后安装到你的状态目录;失败会自动恢复旧版本,已有的账号和同步状态不受影响。没有新版命令行程序时执行 update 不会有变化。
8. 日常命令速查
| 命令 | 作用 |
|---|---|
obs status | 查看登录、绑定、同步状态与各项阻塞(没登录也能查,退出码 0) |
obs sync [--timeout 秒] | 触发一轮并等待收敛,最长 85 秒;超时返回 5,可重复执行 |
obs pause / obs resume | 暂停 / 继续同步(进程仍在运行) |
obs settings get / settings set --input-file … | 查看 / 修改同步范围等设置 |
obs forks list | 列出需要你决定的冲突副本 |
obs forks resolve --path <文件> --since <时间戳> --choice keep-local|use-cloud | 处理冲突:保留本机 / 采用云端(--since 用 list 返回的原值) |
obs failures list / failures retry --uid <标识> | 查看 / 重试同步失败的文件 |
obs mass-delete list / mass-delete confirm --fired-at <时间戳> | 出现「批量删除待确认」时查看并确认(见下) |
obs diagnostics [--output <新文件>] | 导出脱敏的诊断信息,反馈问题时附上 |
obs unbind | 解除本机与同步库的绑定(保留登录、本地笔记和云端库) |
obs export-key [--output <新文件>] | 导出恢复密钥(敏感,见 6.2) |
批量删除保护:当检测到大量文件将被一次性删除时,同步会暂停并让命令返回 8,等你确认。先 obs mass-delete list 看清要删什么,确认是你的本意再 mass-delete confirm --fired-at <list 里的时间戳>。不是本意就不要确认,先排查原因(比如目录被误清空)。
解绑或退出前,如果这台机器上的是库的唯一一份密钥,请先 export-key --output 保存好恢复密钥。
9. 退出码
| 码 | 含义 | 常见处理 |
|---|---|---|
| 0 | 成功 | 但 login poll、pair、join-request 的失败可能仍是 0,要读输出里的 phase |
| 1 | 同步进程异常退出(看门狗、收尾失败) | 查日志;systemd 会按配置自动重启 |
| 3 | 未登录 | 先登录(第 5 节) |
| 4 | 未绑定同步库 | 先加入同步库(第 6 节) |
| 5 | 暂时不可用 | 常见:run 没启动、网络问题、会话已过期、同步超时。多数可重试 |
| 6 | 参数错误或安装失败 | 按提示修正参数、检查 Node 版本 |
| 7 | 目录已被占用 | 同一笔记库已有一个 run,不要重复启动 |
| 8 | 批量删除待确认 | 见第 8 节 |
10. 常见问题
| 现象 | 原因与处理 |
|---|---|
| 提示「尚未就绪,请先运行 obsync run」 | 没有在运行的同步进程,或用了不同的 --vault / --state-dir,或换了用户。保持 run 运行,并使用完全相同的路径和用户 |
| 提示「已被占用」(7) | 同一笔记库已有一个 run。不要启第二个;也不要手动删除锁文件 |
login poll 一直 pending | 确认登录码已作为单独一条消息发给了服务号,且未超过 5 分钟 |
| 提示「待确认内容已经变化」(5) | 会话已失效(重启过、已成功、已过期、被新会话覆盖)。重新发起 |
刚加入,sync 返回 5 | 加入后默认暂停。按 6.4 先确认范围再 resume |
status 退出 0,但同步没完成 | status 只是查询,退出 0 不代表已同步完。看 blockers 和 pending,用 sync 判断 |
| 配对一直不成功 | 配对码已过期,或用错了码(登录码不是配对码)。pair cancel 后让旧设备重新生成 |
pending.persistence 一直为 true,sync 总不收敛 | 已知问题:某些死信恢复后标志残留,导致「假未收敛」。导出 diagnostics 反馈给我们,不要自行改动状态文件 |
屏幕上出现 ExperimentalWarning: SQLite is an experimental feature | 这是 Node 自身的提示,写在错误输出里,不影响命令结果和 JSON 输出,可以忽略 |
| 想看日志 | 状态目录下 vaults/<库标识>/profile/runtime.log 与 host-stderr.log;systemd 另看 journalctl |
11. 安全说明
- 凭据与密钥保存在状态目录,只有当前用户可读写(0700/0600)。备份状态目录等同于备份你的账号凭据,请按敏感数据对待。
- 命令行不接受秘密参数(登录凭据、恢复密钥、设置文件只能走文件或标准输入),避免被其他用户通过进程列表看到。唯一例外是配对码
--code,它短时有效、用完作废。 - 加入同步库需要库的密钥:通过配对、审批或恢复密钥取得。请自行保管好恢复密钥,它是加入这个库的凭证。
- 同步插件随命令行程序一起分发,安装时会校验文件大小与 SHA-256,校验失败会拒绝安装并保留原版本。
- 同步使用的插件、状态都放在你指定的状态目录里,笔记只读写你指定的笔记库目录。
12. 许可协议
obsync-cli 以 CC BY-NC-SA 4.0(署名-非商业性使用-相同方式共享)授权:可以免费个人使用、学习和修改;不得用于商业目的;修改后再分发须保留版权声明并以同一协议发布。完整条款见安装包里的 LICENSE 文件。商业使用请联系我们另行授权。
13. 获取帮助
反馈问题时请附上:obsync --help 里看到的命令、出错命令的完整输出、obs status 的输出,以及 obs diagnostics --output /tmp/diag.json 导出的文件(已脱敏)。不要贴出恢复密钥、登录凭据或配对码。