Liu Bo190 downloadsLAN bidirectional vault sync — CRDT auto-merge, PSK auth, UDP discovery
Zero-cloud · Zero-conflict · Zero‑config — LAN bidirectional sync for Obsidian vaults
🌐 LAN | 🔒 WSS/TLS | 🤝 CRDT | 📡 UDP | 🔐 PSK
|
🔗 LAN Direct Sync Peer‑to‑peer WebSocket/WSS connection. Zero cloud dependency — your data stays on your network. 🤝 Zero‑Conflict CRDT Yjs CRDT automatically merges concurrent text edits. No manual conflict resolution. Ever. 🔒 TLS Encrypted WSS transport with auto‑generated ECDSA P‑256 certificates. Certificate PIN verification prevents MITM attacks. 📡 Auto Discovery UDP broadcast finds devices on the same LAN. No IP typing needed. |
🔐 Security First PSK challenge‑response authentication. Path traversal protection against rogue file writes. 🎯 Selective Sync
Exclude folders & file types.
Ignore 🖥️ Cross‑Platform
macOS (NSFileCoordinator aware)
Linux (writeFile + chmod) 🔌 Zero Config Install, set IP (or auto‑discover), and go. Certificates auto‑generate on first launch. |
📁 Plugin ID:
local-sync· Folder:your-vault/.obsidian/plugins/local-sync/
main.js + manifest.json + styles.css to your-vault/.obsidian/plugins/local-sync/Local Sync| Step | Action |
|---|---|
| 1️⃣ | Open ⚙️ **Settings** → **Local Sync** |
| 2️⃣ | Enter the **other device's IP** in "Target Address" |
| 3️⃣ | Click **Connect** |
| 4️⃣ | 🟢 Status bar turns green — files are syncing! |
💡 Pro tip: On the same subnet? Enable UDP discovery for zero‑config auto‑connect.
🔒 TLS is on by default with auto‑generated ECDSA P‑256 certificates.
┌──────────────────────────────────────────────────┐
│ UI Layer │
│ SettingTab / ConflictModal / StatusBar │
│ └─ TLS settings, fingerprint display, reset │
├──────────────────────────────────────────────────┤
│ Service Layer │
│ SyncEngine + CrdtEngine(Yjs) + ConflictDetector │
├──────────────────────────────────────────────────┤
│ Discovery Layer │
│ UDP broadcast + QR pairing (desktop↔mobile) │
├──────────────────────────────────────────────────┤
│ Security Layer │
│ TLS encryption + PSK auth + path validation │
│ └─ CertManager (ECDSA P‑256, auto‑generate) │
├──────────────────────────────────────────────────┤
│ Network & IO Layer │
│ WebSocket(WSS/WS) + chokidar + OsWriter │
│ └─ http/https dual‑protocol server │
└──────────────────────────────────────────────────┘
| Layer | Module | Responsibility |
|---|---|---|
| 🔒 Security | cert-manager.ts |
ECDSA P‑256 certificate lifecycle (generate/load/reset) |
| 🔒 Security | auth-handshake.ts |
PSK challenge‑response authentication |
| 🔒 Security | path-validator.ts |
Path traversal protection |
| 🌐 Network | connection-manager.ts |
WebSocket/WSS server/client/duplex + TLS fallback |
| 🌐 Network | protocol.ts |
Message serialization protocol |
| 🔄 Sync | sync-engine.ts ⭐ |
Sync orchestration & file state tracking |
| 🔄 Sync | crdt-engine.ts ⭐ |
Yjs CRDT auto‑merge for text files |
| 🔄 Sync | conflict-detector.ts |
Binary‑only conflict detection |
| 🔄 Sync | initial-sync.ts |
Two‑phase initial sync (manifest → transfer) |
| 📡 Discovery | discovery-manager.ts |
UDP broadcast device discovery |
| 📂 IO | file-watcher.ts |
Chokidar‑based file change detection |
| 📂 IO | os-writer.ts |
Platform‑aware file writing (macOS/Linux/Win) |
| 🖥️ UI | setting-tab.ts |
Settings panel with 6 configuration sections |
| 🖥️ UI | sync-status-bar.ts |
Status bar indicators |
| 🖥️ UI | conflict-resolver.ts |
Binary file conflict resolution dialog |
┌─────────────────────────────────────────────────────────────────────┐
│ Initial Sync │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Device A ── FILE_LIST_BATCH (manifest, 100 files/batch) ──→ Device B │
│ ←── FILE_LIST_ACK (missing + different) ──────────┤ │
│ ── FILE_RESPONSE (missing files, 10 concurrent) ──→ │
│ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Incremental Sync │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ File change detected │
│ ├── TEXT (.md/.txt/.canvas) → CRDT incremental update │
│ └── BINARY (.png/.pdf/.zip) → Full file transfer │
│ │
│ Both paths: protected by recentlyPushed(2s) + originDeviceId │
│ │
└─────────────────────────────────────────────────────────────────────┘
Client (WSS) Server (WSS)
│ │
│──── wss://connect ──────→│ TLS 1.2/1.3 handshake
│←── TLS established ─────│ Transport encrypted
│ │
│── cert-fingerprint ────→│ Exchange SHA‑256 fingerprints
│←── cert-fingerprint-ack─│ PIN verification
│ │
│── PSK challenge ───────→│ Inside encrypted channel
│←── authenticated ──────│
│ │
│── sync data (encrypted) →│ All traffic protected
| File Type | Strategy |
|---|---|
| 📝 .md / .txt / .canvas | Yjs CRDT auto‑merge — no user intervention needed |
| 🖼️ .png / .pdf / .zip | Detected → User prompted to keep local / remote / both |
| Mechanism | Purpose |
|---|---|
recentlyPushed (2s TTL) |
Prevents sync loop |
originDeviceId |
Ignores changes originated from self |
| Debounce (500ms) | Avoids redundant sync on rapid saves |
| UUID dedup | Prevents duplicate message processing |
| Heartbeat (120s ping/pong) | Detects dead connections |
| Exponential backoff (1s→60s) | Smart reconnection |
| Version tracking | Discards stale file versions |
| Pending queue | Buffers changes when offline, flushes on reconnect |
| Feature | Description |
|---|---|
| Protocol | WSS (WebSocket Secure) — TLS 1.2/1.3 |
| Certificate | ECDSA P‑256 self‑signed, auto‑generated on first launch |
| Storage | ~/.obsidian-sync/certs/cert.pem + key.pem |
| Validation | SHA‑256 fingerprint exchange + PIN code verification |
| Fallback | Automatic downgrade to plain WS (configurable) |
| Dependencies | Zero — all Node.js built‑ins (crypto, tls, http, https) |
No CA needed. No OpenSSL setup. No external dependencies. Just works — with or without TLS.
debugLog calls translated). Technical tokens (WS / WSS / TLS / UDP / CRDT / port / deviceId) and prefixes ([ObsSync] / [Obsidian Local Sync]) are preserved.diff-preview-modal.ts + diff-preview-service.ts.minAppVersion stays 1.6.6.This plugin intentionally uses a few APIs that trigger Obsidian's automated review warnings. Each is required for the plugin's core LAN‑sync function and is scoped as tightly as possible:
fs) — By design. A sync plugin must read and write the files it synchronizes, which live outside the Obsidian vault. Access is limited to (a) the user‑configured sync paths and (b) the plugin's own data directory ~/.obsidian-sync/ (logs, CRDT snapshots, TLS certs). It never touches unrelated files.os.networkInterfaces, os.homedir) — Used only for LAN peer discovery and to locate the plugin data dir. The plugin does not call os.hostname() or os.userInfo(), and no longer reads identity‑related environment variables (replaced process.env.HOME/USERPROFILE with os.homedir()). The device identifier is a randomly generated UUID, not derived from machine identity.document → activeDocument — Fixed. All DOM creation in the sync‑history view now uses Obsidian's activeDocument global for popout‑window compatibility.diff dependency advisory (GHSA‑73rr‑hh4g‑fpgx) — Not affected. The dependency is diff@^7.0.0 (resolved 7.0.0); npm audit reports 0 vulnerabilities. The advisory only affects earlier diff versions.| Metric | Value |
|---|---|
| 📁 Source Files | 26 .ts files, ~9,900 lines |
| 🧪 Tests | 239 passing (unit + integration + E2E) |
| ⏱️ Test Duration | ~1 second |
| 📦 Build Output | Single main.js (~620KB) |
| 🔗 External Deps | ws · chokidar · yjs (all bundled) |
| 🖥️ Platforms | macOS ✅ · Linux ✅ · Windows ✅ |
| 🔒 TLS Deps | Zero (all Node.js built‑in) |
| 🏷️ Latest | v1.2.0 — Sync history filters, log i18n, diff preview |
# Clone & build
git clone https://github.com/liuboacean/obsidian-local-sync-plugin.git
cd obsidian-local-sync-plugin
npm install
npm run build # Production build (main.js)
npm run dev # Watch mode (for Hot Reload plugin)
npm test # Run all 239 tests
# Quick local test with two vaults:
cp main.js manifest.json styles.css /path/to/vault-a/.obsidian/plugins/local-sync/
cp main.js manifest.json styles.css /path/to/vault-b/.obsidian/plugins/local-sync/
# Open both vaults in Obsidian, enable plugin, connect
| Version | Date | Highlights |
|---|---|---|
| 1.2.0 | Jul 7, 2026 | ✅ Sync history viewer with category filters, full DEBUG log i18n, diff preview, 239 tests |
| 1.1.0 | Jul 2, 2026 | ✅ TLS encryption (WSS), ECDSA P‑256 certs, 148 tests |
| 1.0.9 | Jul 2, 2026 | Obsidian community review fixes |
| 1.0.8 | Jul 2, 2026 | Release format fix |
| 1.0.7 | Jul 2, 2026 | Initial release, 131 tests |
PRs are welcome! Check the issues for areas to contribute.
MIT © 2026 Obsidian Local Sync Team
Made with ❤️ for the Obsidian community