@npm_xiyuan/encrypted-notes-sync
v1.0.0
Published
End-to-end encrypted, bidirectional sync for local plaintext notes across your own Git repositories. Self-hosted, AES-256, multi-repo load balancing, crash-safe recovery.
Maintainers
Readme
加密笔记同步
将本地明文笔记加密后同步到你自己的一组 Git 仓库的双向同步工具。自托管、无第三方服务器、AES-256 + Brotli 压缩、多仓库负载均衡、崩溃安全恢复。
本地明文笔记 <-> ens sync <-> 加密清单 + 加密 Git 对象特性
- 端到端加密 - AES-256(AEAD V2 GCM,兼容旧版 ECB)+ Brotli 压缩。明文从不离开你的机器。
- 多仓库分发 - 一主 + N 从;新文件按当前仓库体积负载均衡分配。
- 双向合并 - 文本走三方行级合并,图片按时间戳取新,无法自动合并时写入冲突标记。
- 自托管 - 运行在你自己的 Git 仓库上(Gitee、GitHub、GitLab,任意 Git 托管)。无账号、无云锁定。
- 崩溃安全恢复 - 发布日志、原子写入、预提交恢复,中断后保持清单与文件一致。
- 删除安全 - 高风险删除在任何写入前暂停,等待显式确认。
- 双语 CLI - 面向人的输出支持
zh-CN/en;--json输出稳定、与语言无关。
安装
要求:Node.js 20+、Git、自有仓库访问权限。
全局安装(推荐):
npm install -g @npm_xiyuan/encrypted-notes-sync免安装运行:
npx @npm_xiyuan/encrypted-notes-sync sync从源码(开发):
git clone https://gitee.com/xiyuan/distributed_notes.git
cd distributed_notes
npm install全局命令为 ens(也可用 encrypted-notes-sync)。下文使用 npm run <脚本>;全局安装后 ens <命令> 与之等价,见 ens help。
快速开始
在项目根目录创建
config.json5。可从模板复制:cp config.example.json5 config.json5最小可用配置:
{ "notesDir": "./notes", "secret": "替换为长随机密钥", "gitRepos": { "master": { "url": "https://your-git-host.example.invalid/you/notes-manifest.git", "branch": "main" } } }设置一个长随机
secret并安全备份——丢失后无法解密。CLI 不会强制它非空或达到最低强度。完整字段见配置。运行同步:
npm run syncsync会先确认所有配置的远程仓库已存在,然后准备缺失的本地 clone、目录、分支和manifest.json,并拉取加密笔记。它绝不会创建远程仓库。如果远程仓库还不存在,先运行 setup:
npm run setupsetup可以创建远程仓库、clone、初始化分支和存储目录。运行前请检查配置。
就这样。用任意编辑器在 notesDir 下编辑笔记,然后 npm run sync 同步。
全局安装后,
ens <命令>等价于npm run <命令>(例如ens sync)。完整映射见ens help。
使用方式
日常同步
npm run sync输出显示简洁的阶段进度(预检查、扫描、远程更新、计划)与结果摘要(推送、拉取、合并、无变更)。除非有冲突需要处理,否则不显示笔记路径。终端输出语言随 language 配置(默认中文),--json 输出始终为语言无关的结构化文档。
恢复新设备
npm install
npm run sync当所有远程仓库已存在时,sync 会自动准备缺失的本地状态并拉取所有加密笔记。除非远程仓库不存在,否则不需要 setup。
确认高风险删除
当删除计划触发以下任一阈值时,sync 会在任何写入之前停止:
notesDir为空但 manifest 仍有条目- 将删除 ≥ 10 个文件
- 删除量 ≥ manifest 条目的 20%
- 将删除全部条目
交互终端——输入提示显示的精确值:
DELETE <数量>非交互 / JSON 模式——使用标志重跑:
npm run sync -- --confirm-delete
npm run sync -- --json --confirm-delete该标志仅确认已展示的删除计划,不会绕过任何其他安全检查。确认前务必检查 notesDir 和实际文件。
自动化与帮助
npm run sync -- --help
npm run sync -- --json帮助无副作用。JSON 同步成功时向 stdout 写入一个结构化结果,失败时向 stderr 写入一个结构化失败结果。
何时使用 setup
npm run setup仅当你需要创建远程仓库、初始化远程分支或修复配置时使用。对于已有部署和远程已存在的新设备,直接用 sync。
工作方式
三个由工具管理的部分:
| 部分 | 位置 | 是否加密 |
| --- | --- | --- |
| 明文笔记 | notesDir | 否 |
| 本地清单(manifest.json) | 项目根目录 | 否(本地状态) |
| 远程清单(manifest.config) | 主仓库 | 是 |
| 加密笔记对象 | 主仓库 + 内容仓库 | 是 |
数据流:本地明文笔记 <-> sync <-> 加密清单 + 加密 Git 对象。
- sync 扫描
notesDir,与本地manifest.json比较。 - 解密远程
manifest.config,比较远程状态。 - 选择:无变更、推送、拉取、合并或恢复。
- 新内容按仓库体积分配;manifest 记录其位置。
- 内容仓库先于主仓库发布。本地清单最后写入。
不要手动创建、编辑、重命名或删除 manifest.json 或 manifest.config。
{
"schemaVersion": 2,
"generation": 3,
"parentDigest": "0000000000000000000000000000000000000000000000000000000000000000",
"files": {
"projects/example.md": {
"filePath": "projects/example.md",
"fileGitPath": "projects/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"gitId": "git.example.invalid/example/notes-content@main",
"size": "128",
"md5": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"contentHash": {
"algorithm": "sha256",
"value": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
}
}
}
}配置
真实配置只放在仅运行时克隆中,不要放在开发工作树里。不要提交真实的密钥、令牌或私有仓库地址。优先使用 SSH,让 token 字段保持为空。
{
language: "zh-CN",
notesDir: "./notes",
defaultBranch: "main",
defaultNoteRoot: "./encrypted-notes",
defaultToken: "",
secret: "replace-with-a-long-random-secret",
saveBuffer: true,
crypto: {
writeFormat: "aead-v2",
maxPlaintextBytes: 1073741824,
},
gitRepos: {
master: {
url: "https://git.example.invalid/example/notes-manifest.git",
branch: "main",
token: "",
noteRoot: "./encrypted-notes",
description: "Encrypted manifest and note content",
isPrivate: true,
},
subList: [
{
url: "https://git.example.invalid/example/notes-content.git",
branch: "main",
token: "",
noteRoot: "./encrypted-notes",
description: "Additional encrypted note content",
isPrivate: true,
},
],
},
}| 字段 | 默认值 | 用途 |
| --- | --- | --- |
| language | zh-CN | 控制所有 CLI 命令面向人的终端输出语言。仅接受 zh-CN 和 en;省略时默认值为 zh-CN。--json 不受语言设置影响,保持稳定的机器可读 schema 和字符串。 |
| notesDir | notes | 本地明文笔记目录(相对于项目根目录)。 |
| defaultBranch | master | 仓库未指定 branch 时使用的分支。 |
| defaultNoteRoot | notes | 仓库未指定 noteRoot 时使用的加密存储根目录。 |
| defaultToken | null | 仓库未指定 token 时使用的 HTTPS 令牌。优先用 SSH。 |
| secret | 空 | 加密密钥。设为长随机值并安全备份。CLI 不强制强度;空或弱不安全。 |
| saveBuffer | true | true:二进制封装。false:文本表示。 |
| crypto.writeFormat | aead-v2 | 新写入格式。保持 aead-v2 以使用认证加密。 |
| crypto.maxPlaintextBytes | 1073741824 | 笔记加密/解密/迁移的最大明文大小。清单解密有单独固定的 64 MiB 限制,不使用此设置。 |
| crypto.migrationConcurrency | 2 | 规范化到 1–4。迁移目前是串行的,无运行时效果。 |
| gitRepos.master | 必填 | 主仓库:存储加密 manifest.config,可选存储笔记对象。 |
| gitRepos.subList | [] | 可选的额外加密笔记对象仓库。 |
仓库条目字段(master 和每个 subList 项):
| 字段 | 用途 |
| --- | --- |
| url | 必填。Git SSH 或 HTTPS URL。用 branch 字段,不要在 URL 中编码分支。 |
| branch | 可选,覆盖 defaultBranch。 |
| token | 可选,覆盖 defaultToken。使用 SSH 时通常不需要。 |
| noteRoot | 可选,覆盖 defaultNoteRoot。 |
| description | 托管平台创建仓库时使用的描述。 |
| isPrivate | 创建时请求私有可见性。请使用私有仓库。 |
| instanceUrl | 支持的自托管实例端点。 |
故障排除
文本冲突
当文本合并无法自动解决时,sync 仍会发布带 Git 冲突标记的结果。退出码为 0(发布已成功),但结果需要你处理。
JSON 结果:
{
"ok": true,
"mode": "merge",
"requiresAttention": true,
"conflicts": ["projects/example.md"]
}在继续编辑或同步前,解决每个列出文件中的标记:
并发修改文本的不同区域会自动合并;只有修改同一位置时才保留冲突标记。
<<<<<<< <local-device> 本地内容 ======= 远程内容 >>>>>>> <remote-device>
移除所有 <<<<<<<、======= 和 >>>>>>> 标记。<local-device> 和 <remote-device> 是设备名称占位符。两台设备同时编辑可能产生冲突——不会像协作文档那样自动合并。
恢复与错误
发布中断时,保留日志、锁和仓库状态。运行 npm run sync 进行验证和继续,不要手动回滚。
| 错误或状态 | 安全操作 |
| --- | --- |
| sync 时 SETUP_REQUIRED | 远程或本地状态无法安全准备。检查配置;当远程已存在时 sync 会自动准备本地状态。仅在需要创建远程或修复时运行 npm run setup。 |
| DELETE_CONFIRMATION_REQUIRED | sync 在高风险删除阈值处停止。检查 notesDir 和文件。用 DELETE <数量> 或 npm run sync -- --confirm-delete 确认。 |
| PENDING_PUBLICATION | 运行 npm run sync 进行验证的向前恢复。若仍停止,保留日志。 |
| CRYPTO_MIGRATION_PENDING | 运行 npm run crypto:migrate:status,再运行 npm run crypto:migrate 继续。 |
| SYNC_LOCKED、ACTIVE_LOCK | 确认没有其他同步在运行,运行 npm run doctor,然后重试。不要手动删除锁。 |
| 仓库领先或分叉 | 用 git fetch、git status --short --branch、git log --oneline --left-right HEAD...@{upstream} 检查。不要 reset 或 force-push。 |
| 清单 generation 冲突 | 停止并运行 npm run doctor。保留两边历史和清单以供诊断。 |
| 解密 / AEAD 认证失败 | 确认正确的 secret、完整的密文、兼容的版本。不要用猜测替换。 |
| 认证 / fetch / push 失败 | 检查 URL、分支、权限、令牌或 SSH。日志中脱敏密钥。 |
安全性
新写入默认使用 AEAD V2:AES-256-GCM、scrypt 派生密钥、每个封装随机 salt 和 nonce。仅在 Brotli 压缩能减小时才压缩。AEAD V2 提供保密性和认证性——解密失败必须视为不安全数据。
AES-256-ECB 仍可读取旧版密文,也可通过 crypto.writeFormat: "legacy-ecb-v1" 显式写入。旧版写入未认证,不建议使用。
本工具不是密钥管理器、实时协作系统或语义冲突解决器。不要因为笔记对象已加密就存入凭据或恢复密钥。
- 使用私有仓库。 加密是纵深防御,不意味着可以公开仓库元数据。
- 保留备份:明文笔记、
secret和仓库数据。丢失密钥后加密数据不可恢复。 - 不要提交令牌、密钥、带凭据的 URL 或笔记内容到 Git、日志或 issue。
- Git 历史保留旧的加密对象。今天删除文件不会移除历史密文。密钥轮换或历史清除是独立项目。
- 在首台设备发布 AEAD V2 清单前升级所有设备。V2 发布后不要降级。
- 不要并发同步。 工具有本地锁和远程检查,但设备排序是你的责任。
命令与副作用
日常
| 命令 | 说明 |
| --- | --- |
| npm run sync | 同步。确认远程已存在,准备缺失的本地状态,然后拉取/合并/加密/推送。绝不创建远程。 |
| npm run sync:prepare | setup + sync。有 setup 的副作用。兼容命令。 |
同步选项
| 命令 | 说明 |
| --- | --- |
| npm run sync -- --help | 输出用法。无副作用。 |
| npm run sync -- --json | 成功时 stdout 输出一个 JSON,失败时 stderr 输出一个 JSON。 |
| npm run sync:prepare -- --help | 输出用法。无副作用。 |
| npm run sync:prepare -- --json | setup + sync,结构化 JSON 输出。 |
| npm run sync -- --confirm-delete | 在非交互模式中确认已审核的风险删除计划。 |
| npm run sync -- --json --confirm-delete | JSON + 删除确认。 |
准备与诊断
| 命令 | 说明 |
| --- | --- |
| npm run setup | 创建远程、clone、初始化分支和存储。仅用于创建远程或修复。 |
| npm run doctor | 只读人类可读诊断。 |
| npm run doctor:json | 同样的检查,以 JSON 输出。 |
迁移
| 命令 | 说明 |
| --- | --- |
| npm run crypto:migrate | 将符合条件的旧版密文重写为 AEAD V2 并发布。 |
| npm run crypto:migrate:status | 读取迁移状态,不重写。 |
笔记治理
| 命令 | 说明 |
| --- | --- |
| npm run notes:audit | 清点源笔记树。只读。 |
| npm run notes:plan | 生成迁移规划报告。只读。 |
开发
| 命令 | 说明 |
| --- | --- |
| npm test | 运行 Node.js 测试套件。不是同步健康检查。 |
| npm run lint | 对 bin/、script/ 和 test/ 运行 ESLint。 |
| npm run lint:fix | 自动修复安全的 ESLint 问题。 |
进阶文档
贡献
Node.js ESM CLI。提交变更前安装依赖并运行测试:
npm install
npm test保持 setup、sync-first 同步、诊断、迁移和笔记治理之间的边界。行为变更应包含测试。不要在报告中包含真实凭据或私有笔记。
许可证
源代码采用 GNU General Public License v2.0(GPL-2.0-only)许可证。
