From 6791596deeb282399e6788933f3c82f54b0c5d91 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 --- obsidian_livesync/2.1/data.yml | 25 ++++ obsidian_livesync/2.1/data/local.ini | 22 ++++ obsidian_livesync/2.1/docker-compose.yml | 20 ++++ obsidian_livesync/README.md | 139 +++++++++++++++++++++++ obsidian_livesync/data.yml | 13 +++ obsidian_livesync/logo.png | Bin 0 -> 14402 bytes 6 files changed, 219 insertions(+) create mode 100644 obsidian_livesync/2.1/data.yml create mode 100644 obsidian_livesync/2.1/data/local.ini create mode 100644 obsidian_livesync/2.1/docker-compose.yml create mode 100644 obsidian_livesync/README.md create mode 100644 obsidian_livesync/data.yml create mode 100644 obsidian_livesync/logo.png diff --git a/obsidian_livesync/2.1/data.yml b/obsidian_livesync/2.1/data.yml new file mode 100644 index 000000000..2dd86f5d9 --- /dev/null +++ b/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/obsidian_livesync/2.1/data/local.ini b/obsidian_livesync/2.1/data/local.ini new file mode 100644 index 000000000..8e5d78284 --- /dev/null +++ b/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/obsidian_livesync/2.1/docker-compose.yml b/obsidian_livesync/2.1/docker-compose.yml new file mode 100644 index 000000000..cecc82e35 --- /dev/null +++ b/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/obsidian_livesync/README.md b/obsidian_livesync/README.md new file mode 100644 index 000000000..9def8e6ca --- /dev/null +++ b/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/obsidian_livesync/data.yml b/obsidian_livesync/data.yml new file mode 100644 index 000000000..c41f68e80 --- /dev/null +++ b/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/obsidian_livesync/logo.png b/obsidian_livesync/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..321ff40b9fd2a147b8ae5b13e1f2045d0c38bdd2 GIT binary patch literal 14402 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 X4i<_eobLT=m7lcu7qM~?1ONX8zhL?p literal 0 HcmV?d00001