Headless, live, bidirectional sync between an Obsidian vault on this server and
a remote CouchDB — powered by obsidian-livesync's
own CLI (self-hosted-livesync-cli), pinned and wrapped by this repo.
- Linux server with systemd, Node.js >= 22, git
- A CouchDB reachable over HTTPS (remote is fine)
- On your other devices: obsidian-livesync already configured with E2E encryption and Obfuscate Properties enabled
make install # interactive: prompts for secrets
# or fully scripted:
COUCHDB_URI=https://couch.example.com:5984 \
COUCHDB_DBNAME=obsidian-livesync \
COUCHDB_USER=... COUCHDB_PASSWORD=... \
E2E_PASSPHRASE=... OBFUSCATE_PASSPHRASE=... \
make install VAULT_DIR=/srv/vault
# Alternative: import the plugin's Setup URI (passphrase-protected).
# Prompts for the URI passphrase, then enforces the same sanity checks:
make import-uri URI='obsidian://setuplivesync?...'
make install # settings exist; preflight + service installation run
# Or first just inspect what a Setup URI carries (throwaway parse,
# nothing is applied, real vault untouched):
make check-uri URI='obsidian://setuplivesync?...'
make verify # healthcheck
| Path | What |
|---|---|
~/vault (default, configurable) |
the vault - actual markdown files |
~/vault/.livesync/settings.json |
secrets + config (0600, never committed) |
~/opt/obsidian-livesync |
pinned upstream clone + build tree |
~/.local/bin/livesync-cli |
installed CLI (upstream installer manages this) |
~/.config/systemd/user/livesync-cli.service |
systemd user unit |
Vault path resolution (highest wins): --vault flag, VAULT_DIR env,
config/env.local (copy from config/env.example), default ~/vault.
The E2E passphrase MUST equal the value your other devices use. The same
passphrase is used for Obfuscate Properties - upstream derives it from the E2E
passphrase; there is no separate setting (the legacy obfuscatePassphrase
settings key is ignored). The obfuscation is baked into every document ID in
the database - a mismatched passphrase does not error out, it silently desyncs.
It is stored only in the gitignored settings.json.
- Default: LiveSync mode - CouchDB
_changesfeed (sub-second remote -> local), chokidar file watching (instant local -> remote). - Behind a proxy that kills long-lived connections? Set
SYNC_INTERVAL=30(seconds) inconfig/env.localand re-runmake install.
Test the whole setup against your real CouchDB with a hard guarantee that nothing on the server can ever write to the remote database.
# 1. Create a NON-ADMIN CouchDB member user (one-off, in CouchDB):
# e.g. put the user in the database members, not in _admin
# 2. Turn the write guard ON (admin creds, prompted; blocks ALL non-admin writers):
make readonly-on
# 3. Install in read-only mode (uses config/env.local: READ_ONLY=1, SYNC_INTERVAL=60):
READ_ONLY=1 make install VAULT_DIR=/srv/vault-test
# 4. Pull once on demand, or let livesync-readonly.service keep it fresh:
make pull-once
# 5. When done testing:
make readonly-off
Notes:
- The guard is a CouchDB design doc (
_design/__livesync_readonly_guard) whose validator rejects every write from non-admin users. Reads and the _changes feed are unaffected. - While the guard is ON, ALL non-admin writers are blocked - including your other devices if they use non-admin credentials. Turn it off when done.
- Read-only sync is additive: it never deletes local files (upstream mirror semantics restore DB-only files).
- Read-only installs are updated with
READ_ONLY=1 make update;make verifychecks the livesync-readonly service.
make status # service status + last 50 journal lines
make verify # healthcheck (cron-friendly)
- Service down: systemd restarts on failure (
Restart=on-failure).make statusfirst; journal tail included. - Locked remote / rebuild needed:
livesync-cli ~/vault unlock-remote,mark-resolved,remote-status - Conflicted file (skipped by mirror, never destructively merged):
livesync-cli ~/vault info notes/foo.mdthenlivesync-cli ~/vault resolve notes/foo.md <rev> - Deleting files: deletions must go through
livesync-cli ~/vault rm notes/foo.md-mirrorintentionally restores DB-only files; removing a file on disk alone makes it come back. - Plaintext DB (no E2E): discouraged; if unavoidable,
ALLOW_PLAINTEXT=1 make verify.
# 1. Edit upstream.pin to the target tag (e.g. 1.1.0)
# 2. Reinstall and verify:
make update
Never automatic. Vault files and settings are untouched by updates.
make test # unit tests (no network)
make test-e2e-local # throwaway CouchDB via Docker; SKIPs without Docker