obsidian: self-hosted vault sync via CouchDB on jupiter

Adds services/dev/obsidian-livesync.nix — CouchDB 3 from the native
nixpkgs module, tuned as the backend for the Self-hosted LiveSync plugin
— and publishes it as notes.mgaction.town through neptun.

It goes out over the public reverse proxy rather than staying on the LAN
because Obsidian's mobile apps refuse cleartext HTTP and *.jupiter.sol
cannot hold a publicly trusted cert. That makes the hardening load-bearing
rather than decorative:

  - require_valid_user in both [chttpd] and [chttpd_auth], so nothing
    answers unauthenticated on the open internet;
  - neptun's vhost matches on CouchDB's own naming rule (system endpoints
    all begin with `_`, user databases never can), so Fauxton, /_all_dbs
    and /_node/_local/_config — which rewrites the server config given
    admin credentials — 404 at the proxy while any number of per-vault
    databases pass. Verified against both sets of paths with caddy run
    against a stub backend;
  - the plugin's own E2EE carries the actual confidentiality: jupiter only
    ever stores ciphertext. Its passphrase is deliberately NOT in sops —
    it never leaves the clients, and pairing it with the server credential
    would defeat the point.

flush_interval -1 is required, not tuning: replication rides a continuous
_changes feed that caddy would otherwise buffer into a stall.

Storage sits on the array with RequiresMountsFor, since a CouchDB that
starts without /mnt/data would create an empty database on the eMMC and
LiveSync would replicate that emptiness back to every client. Logs go to
journald rather than the unrotated /var/log/couchdb.log, for the same
29G-eMMC reasons as the rest of jupiter.

The admin password reaches CouchDB as an [admins] ini fragment via
extraConfigFiles; services.couchdb.adminPass would have rendered it into
the world-readable store.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TLN5nkLBtCciD3ZnUwtw2b
This commit is contained in:
2026-08-25 23:25:37 +02:00
co-authored by Claude Opus 5
parent 94061bd80a
commit 15ae1cf608
5 changed files with 205 additions and 2 deletions
+17
View File
@@ -69,4 +69,21 @@
sops.secrets.sabnzbd_eweka_username.owner = "sabnzbd";
sops.secrets.sabnzbd_eweka_password.owner = "sabnzbd";
# CouchDB admin account for Obsidian LiveSync
# (services/dev/obsidian-livesync.nix). Rendered into an [admins] ini
# fragment rather than passed as services.couchdb.adminPass, which would put
# the plaintext in the world-readable store.
#
# owner = couchdb on BOTH: couchdb re-reads its ini chain as its own
# User=/Group= after systemd drops privileges, and sops defaults to
# root:root 0400 — without this it comes up with no admin configured, which
# under require_valid_user means every request 401s.
sops.secrets.couchdb_admin_password.owner = "couchdb";
sops.templates."couchdb-admins.ini" = {
owner = "couchdb";
content = ''
[admins]
obsidian = ${config.sops.placeholder.couchdb_admin_password}
'';
};
}