From c64be0ab02f7968309fcf49fa8ea2902c6879841 Mon Sep 17 00:00:00 2001 From: Simon Milvert Date: Sun, 18 Oct 2026 23:55:30 +0200 Subject: [PATCH] Add CouchDB and livesync-bridge for Obsidian LiveSync CouchDB is served at livesync.${DOMAIN} via Traefik without a middleware chain, since CouchDB handles CORS itself. livesync-bridge mirrors the vault to ~/repo/notes, and notes-autocommit.sh pushes it from cron. Co-Authored-By: Claude Opus 5.5 --- docker-compose.yml | 54 +++++++++++++++++++++++++ obsidian-livesync/couchdb-entrypoint.sh | 9 +++++ obsidian-livesync/livesync.ini | 26 ++++++++++++ obsidian-livesync/notes-autocommit.sh | 15 +++++++ 4 files changed, 104 insertions(+) create mode 100644 obsidian-livesync/couchdb-entrypoint.sh create mode 100644 obsidian-livesync/livesync.ini create mode 100755 obsidian-livesync/notes-autocommit.sh diff --git a/docker-compose.yml b/docker-compose.yml index 42db6d4..c8b6dab 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,6 +30,10 @@ secrets: file: $SECRETSDIR/carconnect_grafana_pw home_assistant_token_spoolman: file: $SECRETSDIR/home_assistant_token_spoolman + couchdb_password: + file: $SECRETSDIR/couchdb_password + livesync_bridge_config: + file: $SECRETSDIR/livesync_bridge_config.json include: @@ -909,6 +913,56 @@ services: - "traefik.http.routers.carconnect-grafana.tls.certresolver=milvert_dns" - "traefik.http.routers.carconnect-grafana.tls=true" + couchdb: + image: couchdb:3.5 + container_name: couchdb + restart: unless-stopped + entrypoint: ["/bin/sh", "/couchdb-entrypoint.sh"] + networks: + - backend + environment: + - TZ=${TZ} + - COUCHDB_USER=obsidian + secrets: + - couchdb_password + volumes: + - ${DIR_LOCAL}/couchdb/data:/opt/couchdb/data + - ./obsidian-livesync/couchdb-entrypoint.sh:/couchdb-entrypoint.sh:ro + - ./obsidian-livesync/livesync.ini:/livesync.ini:ro + labels: + - diun.enable=true + # No middleware chain: CouchDB handles CORS itself, and chain-no-auth's + # secure-headers (GET/OPTIONS/PUT only) and rate limit break LiveSync. + - "traefik.enable=true" + - "traefik.http.services.livesync-service.loadbalancer.server.port=5984" + - "traefik.http.routers.livesync-secure.entrypoints=web-secure" + - "traefik.http.routers.livesync-secure.rule=Host(`livesync.${DOMAIN}`)" + - "traefik.http.routers.livesync-secure.tls.certresolver=milvert_dns" + - "traefik.http.routers.livesync-secure.tls=true" + + # Mirrors the CouchDB vault to ~/repo/notes as markdown. + # No published image, so it is built from a pinned commit. + livesync-bridge: + build: https://github.com/vrtmrz/livesync-bridge.git#fc0e028d33b3a12ddee1322951375e05107edc55 + image: livesync-bridge:fc0e028 + container_name: livesync-bridge + restart: unless-stopped + user: "${UID}:${GID}" + depends_on: + - couchdb + networks: + - backend + environment: + - TZ=${TZ} + - LSB_CONFIG=/run/secrets/livesync_bridge_config + secrets: + - livesync_bridge_config + volumes: + - /home/simon/repo/notes:/app/data/vault + - ${DIR_LOCAL}/livesync-bridge:/deno-dir/location_data + labels: + - "traefik.enable=false" + networks: frontend: external: true diff --git a/obsidian-livesync/couchdb-entrypoint.sh b/obsidian-livesync/couchdb-entrypoint.sh new file mode 100644 index 0000000..64ebc91 --- /dev/null +++ b/obsidian-livesync/couchdb-entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# Reads the admin password from the Docker secret and installs the LiveSync +# settings before handing over to the image's own entrypoint. +set -e + +export COUCHDB_PASSWORD="$(cat /run/secrets/couchdb_password)" +cp /livesync.ini /opt/couchdb/etc/local.d/10-livesync.ini + +exec tini -- /docker-entrypoint.sh /opt/couchdb/bin/couchdb diff --git a/obsidian-livesync/livesync.ini b/obsidian-livesync/livesync.ini new file mode 100644 index 0000000..349570a --- /dev/null +++ b/obsidian-livesync/livesync.ini @@ -0,0 +1,26 @@ +; CouchDB settings required by Obsidian Self-hosted LiveSync. +; Values follow obsidian-livesync docker/scripts/couchdb-init.sh. + +[couchdb] +single_node = true +max_document_size = 50000000 + +[chttpd] +require_valid_user = true +max_http_request_size = 4294967296 +enable_cors = true + +[chttpd_auth] +require_valid_user = true +authentication_redirect = /_utils/session.html + +[httpd] +WWW-Authenticate = Basic realm="couchdb" +enable_cors = true + +[cors] +origins = app://obsidian.md,capacitor://localhost,http://localhost +credentials = true +headers = accept, authorization, content-type, origin, referer +methods = GET, PUT, POST, HEAD, DELETE +max_age = 3600 diff --git a/obsidian-livesync/notes-autocommit.sh b/obsidian-livesync/notes-autocommit.sh new file mode 100755 index 0000000..ca2977b --- /dev/null +++ b/obsidian-livesync/notes-autocommit.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# Commits and pushes the LiveSync-mirrored notes repo. Run from cron. +set -eu + +REPO=/home/simon/repo/notes + +# Skip this run if the previous one is still going. +exec 9>/tmp/notes-autocommit.lock +flock -n 9 || exit 0 + +cd "$REPO" +git add -A +git diff --cached --quiet && exit 0 +git commit -q -m "Autocommit $(date '+%Y-%m-%d %H:%M')" +git push -q origin main