A minimal self-hosted sync plugin for Obsidian. Always-on live sync via your own CouchDB instance, accessible over Tailscale.
Forked from the architecture of obsidian-livesync, stripped to the essentials.
Includes sync stability safeguards and the editor-style side-by-side diff viewer. See release notes. Grey-screen crash causality remains unconfirmed; use the controlled test plan before enabling Sink in a live vault. Development checks require Node.js 22.22.2+ (Node 22 LTS recommended).
- Live sync — changes replicate instantly across all devices
- Self-hosted — CouchDB on your own server via Docker, no third-party cloud
- End-to-end encryption — AES-256-GCM with PBKDF2 key derivation (optional)
- Automatic conflict resolution — 3-way merge via diff-match-patch, manual fallback
- Side-by-side diff viewer — aligned local/remote lines, red/green and inline highlights, synchronized scrolling, collapsible context, and change navigation
- Mobile support — works on iOS and Android via Obsidian mobile
- Setup URI — share connection settings to new devices in one paste
- 8 settings total — no configuration sprawl
Server:
- Linux machine (Ubuntu, Debian, etc.) with Docker and Docker Compose
- Tailscale installed (or accessible on your LAN)
Client (each device):
- Obsidian 1.4.0+
- Tailscale connected to the same network as the server
cd ~/docker
git clone https://github.com/aandrx/sink.git && cd sink
mv server/* . && rm -rf server/ src/ docs/ *.ts *.mjs *.json *.css main.ts
cp .env.example .env
nano .env # set COUCHDB_PASSWORD
docker compose up -d
bash setup.shYour CouchDB is now running at http://localhost:5985. Get your Tailscale IP:
tailscale ip -4git clone https://github.com/aandrx/sink.git ~/sink
cd ~/sink && npm install && npm run build
# Note: npm install is required before building — it fetches the `events`
# polyfill that makes the plugin load on mobile (iOS/Android).
VAULT="$HOME/Documents/MyVault"
mkdir -p "$VAULT/.obsidian/plugins/sink"
cp main.js manifest.json styles.css "$VAULT/.obsidian/plugins/sink/"Open Obsidian → Settings → Community Plugins → enable Sink → follow the Setup Wizard.
Enter your Tailscale IP as the server URL: http://<tailscale-ip>:5985
Choose "Push vault to server" on your first device.
- On your configured device: Settings → Sink → Copy URI
- Send the URI to your other device via a secure channel (Signal, AirDrop, etc.)
- Open the URI on the new device — Obsidian opens the wizard with credentials pre-filled
- Choose "Pull from server"
For full instructions including mobile setup and giving a friend access, see docs/setup.md.
Status bar (bottom of Obsidian window):
| Text | Meaning |
|---|---|
Sink |
Connected, live sync active |
Sink ↑↓ |
Actively syncing |
Sink ✗ |
Error — check credentials |
Sink ○ |
Disconnected |
Hover the status bar item for a tooltip with details.
Floating overlay (top-right of editor):
| Text | Meaning |
|---|---|
Full |
Connected and idle |
Flowing |
Sync in progress |
Filling filename.md |
Receiving file from server |
Draining filename.md |
Sending file to server |
Blocked |
Sync error |
Empty |
Disconnected |
| Setting | Default | Description |
|---|---|---|
| Server URL | — | CouchDB address, e.g. http://100.64.1.23:5985 |
| Username | — | CouchDB username |
| Password | — | CouchDB password |
| Database name | sink |
Database to replicate |
| Encryption passphrase | — | AES-256-GCM passphrase; leave empty to disable |
| Sync delay | 1000ms |
Debounce time after last edit before pushing |
| Auto-resolve conflicts | true |
3-way merge; false = prompt manually |
| Sync config folder | false |
Allow config-folder sync; workspace state, plugin-enable lists, and Sink itself are always excluded |
- The Setup URI encodes credentials as base64 — it is not encrypted. Only share it via a secure, private channel.
- The
.envfile on the server contains your CouchDB password — it is gitignored and must never be committed. - The
server/data/directory contains your database — also gitignored, back it up regularly. - All devices share one CouchDB account. There is no per-user access control.
- With an encryption passphrase set, note content is encrypted before leaving the device. Without one, content is stored in plaintext in CouchDB.
Obsidian Vault
│
▼
SyncEngine (SyncEngine.ts)
├── FileSerializer — vault file ↔ CouchDB document conversion
├── CryptoHelper — AES-256-GCM encrypt/decrypt
├── LocalDB — PouchDB (IndexedDB) local cache
├── RemoteDB — PouchDB HTTP adapter → CouchDB
├── Replicator — continuous bidirectional replication
└── ConflictResolver — 3-way merge via diff-match-patch
Transport: PouchDB replication over HTTP to CouchDB 3, tunnelled over Tailscale.
Compact the database (run on server, prevents unbounded growth):
cd ~/docker/sink && source .env
curl -X POST "http://${COUCHDB_USER}:${COUCHDB_PASSWORD}@localhost:5985/sink/_compact" \
-H "Content-Type: application/json"Backup:
tar czf ~/backups/sink-db-$(date +%Y%m%d).tar.gz -C ~/docker/sink data/Update plugin:
cd ~/sink && git pull && npm install && npm run build
cp main.js manifest.json styles.css "/path/to/vault/.obsidian/plugins/sink/"For suspected grey-screen crashes, keep Sink disabled in the live vault until a controlled test succeeds. See docs/STABILITY.md for findings, safety limits, and a staged verification procedure. The new config default does not override an existing saved setting.
| Problem | Fix |
|---|---|
| "Connection failed" | Verify Tailscale is running; curl http://<ip>:5985/ should return JSON |
| "Authentication failed" | Credentials don't match .env |
| Status shows "Blocked" | Auth error — re-enter password in Settings → Sink |
| Plugin not appearing | All 3 files must be in .obsidian/plugins/sink/; reload plugins |
| Mobile can't reach server | Open Tailscale on phone; try the server URL in a browser first |
| CORS errors | Restart the container: docker compose restart |
| Sync loop / thrashing | Check that .obsidian/plugins/sink/data.json is excluded (it is by default) |
MIT