From 2edcfb1804038284f9454a124c7fb478239862d0 Mon Sep 17 00:00:00 2001 From: vybfi <52736701+vybfi@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:28:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=BA=94=E7=94=A8=20obsidian?= =?UTF-8?q?=20livesync=20(#927)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/obsidian_livesync/2.1/data.yml | 25 ++++ apps/obsidian_livesync/2.1/data/local.ini | 22 +++ apps/obsidian_livesync/2.1/docker-compose.yml | 20 +++ apps/obsidian_livesync/README.md | 139 ++++++++++++++++++ apps/obsidian_livesync/data.yml | 13 ++ apps/obsidian_livesync/logo.png | Bin 0 -> 14402 bytes 6 files changed, 219 insertions(+) create mode 100644 apps/obsidian_livesync/2.1/data.yml create mode 100644 apps/obsidian_livesync/2.1/data/local.ini create mode 100644 apps/obsidian_livesync/2.1/docker-compose.yml create mode 100644 apps/obsidian_livesync/README.md create mode 100644 apps/obsidian_livesync/data.yml create mode 100644 apps/obsidian_livesync/logo.png diff --git a/apps/obsidian_livesync/2.1/data.yml b/apps/obsidian_livesync/2.1/data.yml new file mode 100644 index 000000000..2dd86f5d9 --- /dev/null +++ b/apps/obsidian_livesync/2.1/data.yml @@ -0,0 +1,25 @@ +additionalProperties: + formFields: + - default: admin + envKey: OBSIDIAN_LIVESYNC_USER + labelEn: Username + labelZh: 用户名 + required: true + rule: paramCommon + type: text + - default: admin + envKey: OBSIDIAN_LIVESYNC_USER_PASSWORD + labelEn: Password + labelZh: 密码 + random: true + required: true + rule: paramComplexity + type: password + - default: 5984 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Port + labelZh: 端口 + required: true + rule: paramPort + type: number \ No newline at end of file diff --git a/apps/obsidian_livesync/2.1/data/local.ini b/apps/obsidian_livesync/2.1/data/local.ini new file mode 100644 index 000000000..8e5d78284 --- /dev/null +++ b/apps/obsidian_livesync/2.1/data/local.ini @@ -0,0 +1,22 @@ +[couchdb] +single_node=true +max_document_size = 50000000 + +[chttpd] +require_valid_user = true +max_http_request_size = 4294967296 + +[chttpd_auth] +require_valid_user = true +authentication_redirect = /_utils/session.html + +[httpd] +WWW-Authenticate = Basic realm="couchdb" +enable_cors = true + +[cors] +origins = app://obsidian.md,capacitor://localhost,http://localhost +credentials = true +headers = accept, authorization, content-type, origin, referer +methods = GET, PUT, POST, HEAD, DELETE +max_age = 3600 \ No newline at end of file diff --git a/apps/obsidian_livesync/2.1/docker-compose.yml b/apps/obsidian_livesync/2.1/docker-compose.yml new file mode 100644 index 000000000..cecc82e35 --- /dev/null +++ b/apps/obsidian_livesync/2.1/docker-compose.yml @@ -0,0 +1,20 @@ +version: "2.1" +services: + couchdb: + image: couchdb + container_name: ${CONTAINER_NAME} + environment: + - COUCHDB_USER=${OBSIDIAN_LIVESYNC_USER} + - COUCHDB_PASSWORD=${OBSIDIAN_LIVESYNC_USER_PASSWORD} + volumes: + - ./data/data:/opt/couchdb/data + - ./data/local.ini:/opt/couchdb/etc/local.ini + ports: + - ${PANEL_APP_PORT_HTTP}:5984 + restart: unless-stopped + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true \ No newline at end of file diff --git a/apps/obsidian_livesync/README.md b/apps/obsidian_livesync/README.md new file mode 100644 index 000000000..9def8e6ca --- /dev/null +++ b/apps/obsidian_livesync/README.md @@ -0,0 +1,139 @@ +# 创建数据库 +CouchDB 部署成功后, 需要手动创建一个数据库, 方便插件连接并同步. + +访问 http://localhost:5984/_utils, 输入帐号密码后进入管理页面 +点击 Create Database, 然后根据个人喜好创建数据库 +# 从移动设备访问 +如果你想要从移动设备访问 Self-hosted LiveSync,你需要一个合法的 SSL 证书。 + + +> 以下转发自官方 +# Self-hosted LiveSync + +Self-hosted LiveSync (自搭建在线同步) 是一个社区实现的在线同步插件。 +使用一个自搭建的或者购买的 CouchDB 作为中转服务器。兼容所有支持 Obsidian 的平台。 + +注意: 本插件与官方的 "Obsidian Sync" 服务不兼容。 + +![obsidian_live_sync_demo](https://user-images.githubusercontent.com/45774780/137355323-f57a8b09-abf2-4501-836c-8cb7d2ff24a3.gif) + +安装或升级 LiveSync 之前,请备份你的 vault。 + +## 功能 + +- 可视化的冲突解决器 +- 接近实时的多设备双向同步 +- 可使用 CouchDB 以及兼容的服务,如 IBM Cloudant +- 支持端到端加密 +- 插件同步 (Beta) +- 从 [obsidian-livesync-webclip](https://chrome.google.com/webstore/detail/obsidian-livesync-webclip/jfpaflmpckblieefkegjncjoceapakdf) 接收 WebClip (本功能不适用端到端加密) + +适用于出于安全原因需要将笔记完全自托管的研究人员、工程师或开发人员,以及任何喜欢笔记完全私密所带来的安全感的人。 + +## 重要提醒 + +- 请勿与其他同步解决方案(包括 iCloud、Obsidian Sync)一起使用。在启用此插件之前,请确保禁用所有其他同步方法以避免内容损坏或重复。如果要同步到多个服务,请一一进行,切勿同时启用两种同步方法。 + 这包括不能将您的保管库放在云同步文件夹中(例如 iCloud 文件夹或 Dropbox 文件夹) +- 这是一个同步插件,不是备份解决方案。不要依赖它进行备份。 +- 如果设备的存储空间耗尽,可能会发生数据库损坏。 +- 隐藏文件或任何其他不可见文件不会保存在数据库中,因此不会被同步。(**并且可能会被删除**) + +## 如何使用 + +### 准备好你的数据库 + +首先,准备好你的数据库。IBM Cloudant 是用于测试的首选。或者,您也可以在自己的服务器上安装 CouchDB。有关更多信息,请参阅以下内容: +1. [Setup IBM Cloudant](docs/setup_cloudant.md) +2. [Setup your CouchDB](docs/setup_own_server_cn.md) + +Note: 正在征集更多搭建方法!目前在讨论的有 [使用 fly.io](https://github.com/vrtmrz/obsidian-livesync/discussions/85)。 + +### 第一个设备 + +1. 在您的设备上安装插件。 +2. 配置远程数据库信息。 + 1. 将您的服务器信息填写到 `Remote Database configuration`(远程数据库配置)设置页中。 + 2. 建议启用 `End to End Encryption`(端到端加密)。输入密码后,单击“应用”。 + 3. 点击 `Test Database Connection` 并确保插件显示 `Connected to (你的数据库名称)`。 + 4. 单击 `Check database configuration`(检查数据库配置)并确保所有测试均已通过。 +3. 在 `Sync Settings`(同步设置)选项卡中配置何时进行同步。(您也可以稍后再设置) + 1. 如果要实时同步,请启用 `LiveSync`。 + 2. 或者,根据您的需要设置同步方式。默认情况下,不会启用任何自动同步,这意味着您需要手动触发同步过程。 + 3. 其他配置也在这里。建议启用 `Use Trash for deleted files`(删除文件到回收站),但您也可以保持所有配置不变。 +4. 配置杂项功能。 + 1. 启用 `Show staus inside editor` 会在编辑器右上角显示状态。(推荐开启) +5. 回到编辑器。等待初始扫描完成。 +6. 当状态不再变化并显示 ⏹️ 图标表示 COMPLETED(没有 ⏳ 和 🧩 图标)时,您就可以与服务器同步了。 +7. 按功能区上的复制图标或从命令面板运行 `Replicate now`(立刻复制)。这会将您的所有数据发送到服务器。 +8. 打开命令面板,运行 `Copy setup URI`(复制设置链接),并设置密码。这会将您的配置导出到剪贴板,作为您导入其他设备的链接。 + +**重要: 不要公开本链接,这个链接包含了你的所有认证信息!** (即使没有密码别人读不了) + +### 后续设备 + +注意:如果要与非空的 vault 进行同步,文件的修改日期和时间必须互相匹配。否则,可能会发生额外的传输或文件可能会损坏。 +为简单起见,我们强烈建议同步到一个全空的 vault。 + +1. 安装插件。 +2. 打开您从第一台设备导出的链接。 +3. 插件会询问您是否确定应用配置。 回答 `Yes`,然后按照以下说明进行操作: + 1. 对 `Keep local DB?` 回答 `Yes`。 + *注意:如果您希望保留本地现有 vault,则必须对此问题回答 `No`,并对 `Rebuild the database?` 回答 `No`。* + 2. 对 `Keep remote DB?` 回答 `Yes`。 + 3. 对 `Replicate once?` 回答 `Yes`。 + 完成后,您的所有设置将会从第一台设备成功导入。 +4. 你的笔记应该很快就会同步。 + +## 文件看起来有损坏... + +请再次打开配置链接并回答如下: +- 如果您的本地数据库看起来已损坏(当你的本地 Obsidian 文件看起来很奇怪) +- 对 `Keep local DB?` 回答 `No` +- 如果您的远程数据库看起来已损坏(当复制时发生中断) +- 对 `Keep remote DB?` 回答 `No` + +如果您对两者都回答“否”,您的数据库将根据您设备上的内容重建。并且远程数据库将锁定其他设备,您必须再次同步所有设备。(此时,几乎所有文件都会与时间戳同步。因此您可以安全地使用现有的 vault)。 + +## 测试服务器 + +设置 Cloudant 或本地 CouchDB 实例有点复杂,所以我搭建了一个 [self-hosted-livesync 尝鲜服务器](https://olstaste.vrtmrz.net/)。欢迎免费尝试! +注意:请仔细阅读“限制”条目。不要发送您的私人 vault。 + +## 状态栏信息 + +同步状态将显示在状态栏。 + +- 状态 + - ⏹️ 就绪 + - 💤 LiveSync 已启用,正在等待更改。 + - ⚡️ 同步中。 + - ⚠ 一个错误出现了。 +- ↑ 上传的 chunk 和元数据数量 +- ↓ 下载的 chunk 和元数据数量 +- ⏳ 等待的过程的数量 +- 🧩 正在等待 chunk 的文件数量 +如果你删除或更名了文件,请等待 ⏳ 图标消失。 + + +## 提示 + +- 如果文件夹在复制后变为空,则默认情况下该文件夹会被删除。您可以关闭此行为。检查 [设置](docs/settings.md)。 +- LiveSync 模式在移动设备上可能导致耗电量增加。建议使用定期同步 + 条件自动同步。 +- 移动平台上的 Obsidian 无法连接到非安全 (HTTP) 或本地签名的服务器,即使设备上安装了根证书。 +- 没有类似“exclude_folders”的配置。 +- 同步时,文件按修改时间进行比较,较旧的将被较新的文件覆盖。然后插件检查冲突,如果需要合并,将打开一个对话框。 +- 数据库中的文件在罕见情况下可能会损坏。当接收到的文件看起来已损坏时,插件不会将其写入本地存储。如果您的设备上有文件的本地版本,则可以通过编辑本地文件并进行同步来覆盖损坏的版本。但是,如果您的任何设备上都不存在该文件,则无法挽救该文件。在这种情况下,您可以从设置对话框中删除这些损坏的文件。 +- 要阻止插件的启动流程(例如,为了修复数据库问题),您可以在 vault 的根目录创建一个 "redflag.md" 文件。 +- 问:数据库在增长,我该如何缩小它? + 答:每个文档都保存了过去 100 次修订,用于检测和解决冲突。想象一台设备已经离线一段时间,然后再次上线。设备必须将其笔记与远程保存的笔记进行比较。如果存在曾经相同的历史修订,则可以安全地直接更新这个文件(和 git 的快进原理一样)。即使文件不在修订历史中,我们也只需检查两个设备上该文件的公有修订版本之后的差异。这就像 git 的冲突解决方法。所以,如果想从根本上解决数据库太大的问题,我们像构建一个扩大版的 git repo 一样去重新设计数据库。 +- 更多技术信息在 [技术信息](docs/tech_info.md) +- 如果你想在没有黑曜石的情况下同步文件,你可以使用[filesystem-livesync](https://github.com/vrtmrz/filesystem-livesync)。 +- WebClipper 也可在 Chrome Web Store 上使用:[obsidian-livesync-webclip](https://chrome.google.com/webstore/detail/obsidian-livesync-webclip/jfpaflmpckblieefkegjncjoceapakdf) + + +仓库地址:[obsidian-livesync-webclip](https://github.com/vrtmrz/obsidian-livesync-webclip) (文档施工中) + +## License + +The source code is licensed under the MIT License. +本源代码使用 MIT 协议授权。 \ No newline at end of file diff --git a/apps/obsidian_livesync/data.yml b/apps/obsidian_livesync/data.yml new file mode 100644 index 000000000..c41f68e80 --- /dev/null +++ b/apps/obsidian_livesync/data.yml @@ -0,0 +1,13 @@ +additionalProperties: + key: obsidian_livesync + name: obsidian自建同步服务器 + tags: + - Tool + shortDescZh: obsidian第三方自建服务器 + shortDescEn: obsidian self hosted livesync + type: tool + crossVersionUpdate: true + limit: 0 + website: https://github.com/vrtmrz/obsidian-livesync + github: https://github.com/vrtmrz/obsidian-livesync + document: https://github.com/vrtmrz/obsidian-livesync/blob/main/docs/setup_own_server_cn.md \ No newline at end of file diff --git a/apps/obsidian_livesync/logo.png b/apps/obsidian_livesync/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..321ff40b9fd2a147b8ae5b13e1f2045d0c38bdd2 GIT binary patch literal 14402 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 IM6N<$g1=z;7ytkO literal 0 KcmV+b0RR6000031