README file from
Github🔄 Local Sync
Zero-cloud · Zero-conflict · Zero‑config — LAN bidirectional sync for Obsidian vaults
✨ Features
🔗 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 .trash, .tmp, node_modules — your choice.
🖥️ Cross‑Platform
macOS (NSFileCoordinator aware)
Linux (writeFile + chmod)
Windows (write + lock‑retry)
🔌 Zero Config Install, set IP (or auto‑discover), and go. Certificates auto‑generate on first launch.
🚀 Quick Start
Installation
📁 Plugin ID:
local-sync· Folder:your-vault/.obsidian/plugins/local-sync/
- Download the latest release from the Releases page
- Copy
main.js+manifest.json+styles.csstoyour-vault/.obsidian/plugins/local-sync/ - Open Obsidian → ⚙️ Settings → Community plugins → Enable
Local Sync
Usage
💡 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.
🏗️ Architecture
┌──────────────────────────────────────────────────┐
│ 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 │
└──────────────────────────────────────────────────┘
Module Overview
| 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 |
🔄 How It Works
Sync Flow
┌─────────────────────────────────────────────────────────────────────┐
│ 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 │
│ │
└─────────────────────────────────────────────────────────────────────┘
TLS Handshake
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
Conflict Resolution
| 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 |
Synchronization Safeguards
| 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 |
🔒 TLS Encryption (v1.1.0)
| 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.
📊 Project Stats
| Metric | Value |
|---|---|
| 📁 Source Files | 20 .ts files, ~7,800 lines |
| 🧪 Tests | 148 passing (unit + integration + E2E) |
| ⏱️ Test Duration | ~1 second |
| 📦 Build Output | Single main.js (~552KB) |
| 🔗 External Deps | ws · chokidar · yjs (all bundled) |
| 🖥️ Platforms | macOS ✅ · Linux ✅ · Windows ✅ |
| 🔒 TLS Deps | Zero (all Node.js built‑in) |
| 🏷️ Latest | v1.1.0 — TLS encryption |
🛠️ Development
# 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 148 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
📋 Changelog
| Version | Date | Highlights |
|---|---|---|
| 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 |
🗺️ Roadmap
- Yjs CRDT auto‑merge
- PSK auth + path security
- UDP auto‑discovery
- Cross‑platform file writer
- TLS encryption (WSS) ← v1.1.0
- Mobile client support (P2)
- Sync history viewer (P2)
- Diff preview before sync (P2)
🤝 Contributing
PRs are welcome! Check the issues for areas to contribute.
📄 License
MIT © 2026 Obsidian Local Sync Team
Made with ❤️ for the Obsidian community