From a9f9a6765b0f22bd6f413571ae0dbd040e0d0cd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Thu, 20 Aug 2026 20:54:55 +0200 Subject: [PATCH] feat(obsidian): add CouchDB stack for Obsidian LiveSync sync Self-hosted sync backend for the Obsidian LiveSync plugin, following https://blog.admin-intelligence.de/selbsthosting-von-obsidian-mit-couchdb-kostenlose-synchronisation-einrichten/. local.ini.example carries the required chttpd/cors config; local.ini itself is gitignored since CouchDB rewrites it in place (hashed admin password, node uuid) on first start, so a tracked copy would never stay clean. The config also has to be mounted read-write, not :ro: CouchDB's entrypoint chowns everything under /opt/couchdb and aborts under set -e if that fails. Co-Authored-By: Claude Sonnet 5 --- README.md | 1 + obsidian/.env.example | 4 ++++ obsidian/.gitignore | 1 + obsidian/compose.yml | 36 ++++++++++++++++++++++++++++++++++++ obsidian/local.ini.example | 35 +++++++++++++++++++++++++++++++++++ 5 files changed, 77 insertions(+) create mode 100644 obsidian/.env.example create mode 100644 obsidian/.gitignore create mode 100644 obsidian/compose.yml create mode 100644 obsidian/local.ini.example diff --git a/README.md b/README.md index 0e3983d..6df5fb5 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Each stack is independently deployable with its own `compose.yml` and `.env`. Al | `passbolt` | Password manager (GPG-encrypted, team sharing) | passbolt, db | | `code` | Browser-based VS Code IDE with Anthropic API access | code | | `headscale` | Self-hosted Tailscale control server (WireGuard mesh VPN) | headscale | +| `obsidian` | CouchDB sync backend for the Obsidian LiveSync plugin | obsidian | | `pulsenode` | Infrastructure dashboard - monitors all the stacks above | pulsenode | ## Config files diff --git a/obsidian/.env.example b/obsidian/.env.example new file mode 100644 index 0000000..b7f7968 --- /dev/null +++ b/obsidian/.env.example @@ -0,0 +1,4 @@ +TRAEFIK_HOST=obsidian.example.com +NETWORK_NAME=falcon_network +COUCHDB_USER=obsidian +COUCHDB_PASSWORD=change_me diff --git a/obsidian/.gitignore b/obsidian/.gitignore new file mode 100644 index 0000000..81ab0a9 --- /dev/null +++ b/obsidian/.gitignore @@ -0,0 +1 @@ +local.ini diff --git a/obsidian/compose.yml b/obsidian/compose.yml new file mode 100644 index 0000000..7bebd18 --- /dev/null +++ b/obsidian/compose.yml @@ -0,0 +1,36 @@ +services: + obsidian: + image: couchdb:latest + container_name: obsidian + environment: + TZ: ${TIMEZONE:-Europe/Amsterdam} + COUCHDB_USER: ${COUCHDB_USER} + COUCHDB_PASSWORD: ${COUCHDB_PASSWORD} + volumes: + - ../.data/obsidian:/opt/couchdb/data + - ./local.ini:/opt/couchdb/etc/local.d/local.ini + restart: always + healthcheck: + test: ["CMD-SHELL", "curl -sf -u \"$$COUCHDB_USER:$$COUCHDB_PASSWORD\" http://localhost:5984/_up || exit 1"] + interval: 5s + timeout: 5s + retries: 5 + labels: + - "traefik.enable=true" + - "traefik.http.middlewares.obsidian-redirect-web-secure.redirectscheme.scheme=https" + - "traefik.http.routers.obsidian-web.middlewares=obsidian-redirect-web-secure" + - "traefik.http.routers.obsidian-web.rule=Host(`${TRAEFIK_HOST}`)" + - "traefik.http.routers.obsidian-web.entrypoints=web" + - "traefik.http.routers.obsidian-web-secure.rule=Host(`${TRAEFIK_HOST}`)" + - "traefik.http.routers.obsidian-web-secure.tls.certresolver=resolver" + - "traefik.http.routers.obsidian-web-secure.entrypoints=web-secure" + - "traefik.http.routers.obsidian-web-secure.middlewares=security-headers@file,no-index@file" + - "traefik.http.services.obsidian-web-secure.loadbalancer.server.port=5984" + - "traefik.docker.network=${NETWORK_NAME}" + networks: + - compose_network + +networks: + compose_network: + name: ${NETWORK_NAME} + external: true diff --git a/obsidian/local.ini.example b/obsidian/local.ini.example new file mode 100644 index 0000000..633b0f1 --- /dev/null +++ b/obsidian/local.ini.example @@ -0,0 +1,35 @@ +# CouchDB settings required by the Obsidian LiveSync plugin. +# Mounted into /opt/couchdb/etc/local.d/ (files here override the image's +# default.ini and are picked up on container start, no admin-UI setup needed). +# +# Copy this to local.ini before first start: +# cp local.ini.example local.ini +# local.ini is gitignored: CouchDB rewrites it at runtime (hashed admin +# password, generated node uuid), so it can't be a tracked static file. +# +# Mounted read-write on purpose: the entrypoint chowns everything under +# /opt/couchdb to the couchdb user on start and aborts if that fails, so a +# :ro bind mount here breaks the container. It'll take ownership of this +# file on the host after first start; that's expected. +# +# Reference: https://blog.admin-intelligence.de/selbsthosting-von-obsidian-mit-couchdb-kostenlose-synchronisation-einrichten/ + +[couchdb] +single_node = true +max_document_size = 50000000 + +[chttpd] +require_valid_user = true +enable_cors = true +max_http_request_size = 4294967296 + +[chttpd_auth] +require_valid_user = true + +[httpd] +WWW-Authenticate = Basic realm="couchdb" +enable_cors = true + +[cors] +credentials = true +origins = app://obsidian.md,capacitor://localhost,http://localhost