From f99d7dc00f6db96d9bcb1b7626e0402b4dba5670 Mon Sep 17 00:00:00 2001 From: mugenrei Date: Wed, 18 Feb 2026 09:46:49 -0300 Subject: [PATCH] Add initial Docker setup and configuration for self-hosted LiveSync with CouchDB --- docker/.env.example | 52 ++++++++ docker/README.md | 213 +++++++++++++++++++++++++++++++++ docker/config/Caddyfile | 26 ++++ docker/config/cloudflared.yml | 31 +++++ docker/config/livesync.ini | 30 +++++ docker/config/ts-serve.json | 19 +++ docker/docker-compose.yml | 187 +++++++++++++++++++++++++++++ docker/scripts/couchdb-init.sh | 79 ++++++++++++ 8 files changed, 637 insertions(+) create mode 100644 docker/.env.example create mode 100644 docker/README.md create mode 100644 docker/config/Caddyfile create mode 100644 docker/config/cloudflared.yml create mode 100644 docker/config/livesync.ini create mode 100644 docker/config/ts-serve.json create mode 100644 docker/docker-compose.yml create mode 100644 docker/scripts/couchdb-init.sh diff --git a/docker/.env.example b/docker/.env.example new file mode 100644 index 0000000..816b146 --- /dev/null +++ b/docker/.env.example @@ -0,0 +1,52 @@ +# Self-hosted LiveSync — Environment Variables +# Copy this file to .env and fill in your values. +# NEVER commit .env to version control. + +# ============================================================================= +# REQUIRED — CouchDB credentials +# ============================================================================= + +# Admin username for CouchDB +COUCHDB_USER=admin + +# Admin password — use a strong random password (min 16 chars recommended) +COUCHDB_PASSWORD=change_me_use_a_strong_password + +# Name of the database the Obsidian plugin will use +COUCHDB_DATABASE=obsidiannotes + +# Host port CouchDB is exposed on (default: 5984) +# For tunnel-only deployments you can set this to 127.0.0.1:5984 to block external access +COUCHDB_PORT=5984 + +# ============================================================================= +# PROFILE: caddy (--profile caddy) +# ============================================================================= + +# Your public domain pointing to this server (A record) +# Example: couchdb.yourdomain.com +COUCHDB_DOMAIN=couchdb.yourdomain.com + +# Email for Let's Encrypt TLS certificate notifications +ACME_EMAIL=you@yourdomain.com + +# ============================================================================= +# PROFILE: tailscale (--profile tailscale) +# ============================================================================= + +# Tailscale OAuth key (not a regular auth key — must be OAuth for persistent use) +# Generate at: https://login.tailscale.com/admin/settings/oauth +# Scopes needed: devices:write +TS_AUTHKEY=tskey-auth-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +# Hostname this node will have on your tailnet (becomes ..ts.net) +TS_HOSTNAME=livesync + +# ============================================================================= +# PROFILE: cloudflare (--profile cloudflare) +# ============================================================================= + +# Tunnel token from Cloudflare Zero Trust dashboard +# Create at: https://one.dash.cloudflare.com/ → Networks → Tunnels → Create tunnel +# Copy the token from the "Install connector" step +CF_TUNNEL_TOKEN=eyJhIjoixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..5296495 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,213 @@ +# Self-hosted LiveSync — Docker Setup + +A fully self-hosted CouchDB stack for the [obsidian-livesync](https://github.com/vrtmrz/obsidian-livesync) plugin. +**No fly.io. No IBM Cloudant. No cloud accounts required for basic use.** + +> ✅ **Tested on Docker Desktop for Windows (Docker 29.2, Compose v5, WSL2 backend)** — full init, CORS, auth, and idempotent restart verified. + +--- + +## Architecture + +``` +Obsidian (desktop / iOS / Android) + │ CouchDB Replication Protocol (HTTPS) + ▼ +[ Reverse Proxy / Tunnel ] ◄── Choose ONE profile below + │ + ▼ +[ CouchDB container ] ◄── The only required service + │ initialized once by couchdb-init container + ▼ +[ Named Docker Volume ] ◄── All vault data stored here +``` + +--- + +## Quick Start + +### 1. Prerequisites + +- [Docker Desktop](https://docs.docker.com/desktop/) (Windows/Mac/Linux) or Docker Engine + Compose plugin +- A machine that Obsidian devices can reach over HTTPS (see profiles below) + +### 2. Configure + +```bash +cd docker/ +cp .env.example .env +# Edit .env — at minimum set COUCHDB_USER and a strong COUCHDB_PASSWORD +``` + +### 3. Launch + +```bash +# Default: CouchDB only (LAN / localhost, no TLS) +docker compose up -d + +# With Caddy (public domain + auto Let's Encrypt) +docker compose --profile caddy up -d + +# With Tailscale (no domain needed, private mesh or public Funnel) +docker compose --profile tailscale up -d + +# With Cloudflare Tunnel (Cloudflare account required) +docker compose --profile cloudflare up -d +``` + +### 4. Verify + +```bash +# Should return {"status":"ok"} +curl -u admin:yourpassword http://localhost:5984/_up + +# Check CORS headers +curl -v -H "Origin: app://obsidian.md" \ + -u admin:yourpassword \ + http://localhost:5984/ +``` + +### 5. Connect Obsidian + +In the Obsidian plugin settings (**Self-hosted LiveSync**): + +| Field | Value | +|---|---| +| URI | `https://your-domain-or-ts-hostname:5984` (or `http://localhost:5984` for LAN-only) | +| Username | value of `COUCHDB_USER` | +| Password | value of `COUCHDB_PASSWORD` | +| Database name | value of `COUCHDB_DATABASE` (default: `obsidiannotes`) | +| End-to-end passphrase | *your own chosen passphrase — never stored server-side* | + +--- + +## Profile Details + +### Default (no profile) — LAN / Localhost only + +CouchDB is exposed on `http://localhost:5984` (or LAN IP). +**Desktop Obsidian works over HTTP.** Mobile Obsidian requires HTTPS — use a tunnel profile. + +### `--profile caddy` — Public Domain + Auto TLS + +**Requires**: +- A domain with an A record pointing to this server's public IP +- Ports 80 and 443 open in your firewall/router + +**Set in `.env`**: +``` +COUCHDB_DOMAIN=couchdb.yourdomain.com +ACME_EMAIL=you@example.com +``` + +Caddy automatically issues a Let's Encrypt certificate. No manual cert management. + +### `--profile tailscale` — No Domain Required ✅ Recommended for privacy + +**Requires**: +- Free [Tailscale account](https://login.tailscale.com/) +- Install the Tailscale app on all your Obsidian devices +- Generate an **OAuth key** at: https://login.tailscale.com/admin/settings/oauth + (Scopes: `devices:write`) + +**Set in `.env`**: +``` +TS_AUTHKEY=tskey-auth-... +TS_HOSTNAME=livesync +``` + +**Two sub-modes**: +- **VPN mode** (default): CouchDB accessible only to devices on your Tailnet at + `https://livesync..ts.net` — completely private +- **Funnel mode**: public HTTPS at `https://livesync..ts.net` — no domain purchase + Enable in your [Tailscale ACL](https://login.tailscale.com/admin/acls): + ```json + "nodeAttrs": [{"target": ["tag:container"], "attr": ["funnel"]}] + ``` + +> **Note on Windows Docker Desktop**: If `/dev/net/tun` is unavailable, add `TS_USERSPACE=true` +> to the tailscale service environment in `docker-compose.yml`. + +### `--profile cloudflare` — Cloudflare Tunnel + +**Requires**: +- [Cloudflare account](https://www.cloudflare.com/) with a domain on Cloudflare DNS +- Tunnel token from Zero Trust → Networks → Tunnels → Create tunnel + +**Set in `.env`**: +``` +CF_TUNNEL_TOKEN=eyJhI... +COUCHDB_DOMAIN=couchdb.yourdomain.com +``` + +> ⚠️ **Known issue**: Cloudflare has a 100s proxy timeout that interrupts CouchDB's +> long-polling replication feed, causing `524` errors. +> **Fix**: In the Obsidian plugin settings enable: +> `"Use Request API to avoid inevitable CORS problem"` + +--- + +## Data & Backup + +All vault data lives in the `couchdb-data` Docker named volume. + +```bash +# Backup +docker run --rm -v obsidian-livesync_couchdb-data:/data \ + -v $(pwd)/backup:/backup alpine \ + tar czf /backup/couchdb-backup-$(date +%Y%m%d).tar.gz -C /data . + +# Restore +docker run --rm -v obsidian-livesync_couchdb-data:/data \ + -v $(pwd)/backup:/backup alpine \ + tar xzf /backup/couchdb-backup-20260218.tar.gz -C /data +``` + +--- + +## Security Notes + +- CouchDB requires authentication for **all** requests (configured by `livesync.ini`) +- Enable **End-to-End Encryption** passphrase in the Obsidian plugin — vault data is + encrypted before it ever leaves your device +- The init container runs once and exits — it has no persistent access +- Never expose CouchDB's admin interface (`/_utils`) to the public internet; + use a firewall rule or the path-based obfuscation trick from + [self-hosted-livesync-server](https://github.com/vrtmrz/self-hosted-livesync-server) + +--- + +## Useful Commands + +```bash +# View logs +docker compose logs -f couchdb +docker compose logs couchdb-init + +# Re-run init (e.g. after changing credentials) +docker compose restart couchdb-init + +# Stop without removing data +docker compose down + +# Stop AND remove all data volumes (DESTRUCTIVE) +docker compose down -v + +# Open CouchDB admin UI (Fauxton) in browser +open http://localhost:5984/_utils +``` + +--- + +## Troubleshooting + +| Problem | Solution | +|---|---| +| Init container keeps restarting | CouchDB not healthy yet — wait 30s, check `docker compose logs couchdb` | +| `curl: (52) Empty reply` | CouchDB not fully started — the healthcheck should gate this | +| Mobile can't connect | Needs HTTPS — use tailscale or caddy profile | +| 524 errors with Cloudflare | Enable "Use Request API" toggle in Obsidian plugin | +| `Permission denied` on volumes | Run `docker compose down -v` and retry — first-run volume ownership issue | +| CORS errors in browser | Confirm CouchDB headers: `curl -v -H "Origin: app://obsidian.md" http://localhost:5984/` | +| CouchDB exits immediately, zero logs (Windows) | **Do not add `:ro`** to the `livesync.ini` volume mount. CouchDB's entrypoint runs `chmod 0644` on all files in `/opt/couchdb/etc` — read-only bind mounts cause a silent EPERM crash on Docker Desktop for Windows (WSL2). The compose file is already correct; do not modify it. | +| Settings in `livesync.ini` seem ignored | Settings requiring restart (e.g. bind_address) load at start. Runtime-only settings (require_valid_user, enable_cors) are set by the init container via REST API and take effect immediately without restart. | diff --git a/docker/config/Caddyfile b/docker/config/Caddyfile new file mode 100644 index 0000000..27924db --- /dev/null +++ b/docker/config/Caddyfile @@ -0,0 +1,26 @@ +# Caddy config for Self-hosted LiveSync CouchDB +# ============================================================================= +# IMPORTANT: CouchDB handles CORS itself. +# Do NOT add CORS headers here — they will conflict with CouchDB's own headers. +# Do NOT intercept OPTIONS requests. +# ============================================================================= + +{ + # Email used for Let's Encrypt certificate notifications + email {$ACME_EMAIL} +} + +{$COUCHDB_DOMAIN} { + # Forward all traffic to CouchDB, preserving Host and forwarded-for headers + reverse_proxy couchdb:5984 { + header_up Host {host} + header_up X-Forwarded-For {remote_host} + header_up X-Forwarded-Proto {scheme} + } + + # Logging + log { + output stdout + level WARN + } +} diff --git a/docker/config/cloudflared.yml b/docker/config/cloudflared.yml new file mode 100644 index 0000000..3e18bcf --- /dev/null +++ b/docker/config/cloudflared.yml @@ -0,0 +1,31 @@ +# cloudflared tunnel configuration for Self-hosted LiveSync +# ============================================================================= +# +# Prerequisites: +# 1. Create a tunnel in Cloudflare Zero Trust → Networks → Tunnels +# 2. Copy the tunnel token to CF_TUNNEL_TOKEN in your .env +# 3. Add a public hostname in the tunnel config: +# Hostname : couchdb.yourdomain.com (or whatever you set COUCHDB_DOMAIN to) +# Service : http://couchdb:5984 +# +# Known issue: Cloudflare's 100-second proxy timeout can interrupt CouchDB's +# long-polling replication change feed, causing 524 errors. +# MITIGATION: In the Obsidian plugin settings, enable: +# "Use Request API to avoid inevitable CORS problem" +# This switches from long-poll to short-poll mode. +# +# ============================================================================= + +tunnel: ${CF_TUNNEL_ID} +credentials-file: /etc/cloudflared/credentials.json + +ingress: + - hostname: ${COUCHDB_DOMAIN} + service: http://couchdb:5984 + originRequest: + # Increase timeouts for CouchDB replication streams + connectTimeout: 30s + keepAliveTimeout: 90s + keepAliveConnections: 100 + noTLSVerify: false + - service: http_status:404 diff --git a/docker/config/livesync.ini b/docker/config/livesync.ini new file mode 100644 index 0000000..fd1ffb0 --- /dev/null +++ b/docker/config/livesync.ini @@ -0,0 +1,30 @@ +; CouchDB local configuration for Self-hosted LiveSync +; This file is volume-mounted into /opt/couchdb/etc/local.d/livesync.ini +; +; IMPORTANT: Do NOT set require_valid_user here. +; CouchDB needs to start without auth to complete its first-run cluster setup +; (_users, _replicator databases must be created first). +; The couchdb-init service applies auth lockdown via REST API after first-run. + +[couchdb] +; Max size per document (50MB). Large enough for binary attachments. +max_document_size = 50000000 + +[chttpd] +; Bind on all interfaces. +bind_address = 0.0.0.0 +port = 5984 +; 4 GB max request (handles very large vaults) +max_http_request_size = 4294967296 + +[httpd] +WWW-Authenticate = Basic realm="couchdb" + +[cors] +; These are the exact app origins Obsidian uses on desktop + mobile +credentials = true +origins = app://obsidian.md,capacitor://localhost,http://localhost + +[log] +; Reduce noise in Docker logs — set to "debug" if troubleshooting +level = warning diff --git a/docker/config/ts-serve.json b/docker/config/ts-serve.json new file mode 100644 index 0000000..55da774 --- /dev/null +++ b/docker/config/ts-serve.json @@ -0,0 +1,19 @@ +{ + "TCP": { + "443": { + "HTTPS": true + } + }, + "Web": { + "${TS_CERT_DOMAIN}:443": { + "Handlers": { + "/": { + "Proxy": "http://127.0.0.1:5984" + } + } + } + }, + "AllowFunnel": { + "${TS_CERT_DOMAIN}:443": true + } +} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..5f7350b --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,187 @@ +# Self-hosted LiveSync — Docker Compose +# ============================================================================= +# PROFILES +# -------- +# (default) CouchDB only — LAN/localhost access, no TLS +# Suitable for desktop-only use or testing. +# +# --profile caddy CouchDB + Caddy reverse proxy +# Auto TLS via Let's Encrypt. Needs public domain + ports 80/443. +# +# --profile tailscale CouchDB + Tailscale sidecar +# No domain required. HTTPS via *.ts.net PKI. +# Needs a Tailscale account (free tier works). +# +# --profile cloudflare CouchDB + cloudflared tunnel daemon +# Free public HTTPS via Cloudflare. Needs a CF account + tunnel token. +# NOTE: Enable "Use Request API" in the Obsidian plugin to avoid 524 timeouts. +# +# QUICK START (local test): +# cp .env.example .env && edit .env +# docker compose up -d +# curl -u admin:yourpassword http://localhost:5984/_up +# ============================================================================= + +name: obsidian-livesync + +services: + + # --------------------------------------------------------------------------- + # CouchDB — the only required service + # --------------------------------------------------------------------------- + couchdb: + image: couchdb:latest + container_name: livesync-couchdb + restart: unless-stopped + # NOTE: Do NOT set user: here — the CouchDB entrypoint starts as root to + # write docker.ini (from env vars), then drops to uid 5984 automatically. + environment: + COUCHDB_USER: ${COUCHDB_USER:?Set COUCHDB_USER in .env} + COUCHDB_PASSWORD: ${COUCHDB_PASSWORD:?Set COUCHDB_PASSWORD in .env} + volumes: + - couchdb-data:/opt/couchdb/data + # Mount to /opt/couchdb/etc/local.ini (NOT into local.d/). + # Do NOT use :ro — the CouchDB entrypoint runs chmod on this file at startup + # and will crash with EPERM if the file is read-only. The file is only read + # at startup; runtime changes go via the REST API into local.d/docker.ini. + - ./config/livesync.ini:/opt/couchdb/etc/local.ini + ports: + # Exposes CouchDB on the host for LAN/localhost access. + # The tunnel profiles (caddy/tailscale/cloudflare) provide HTTPS on top. + # You can remove this port mapping once a tunnel profile is in use. + - "${COUCHDB_PORT:-5984}:5984" + healthcheck: + # Test with admin credentials — ensures both CouchDB is up AND auth is ready. + # ${COUCHDB_USER} / ${COUCHDB_PASSWORD} are expanded by Docker Compose here. + test: + - "CMD-SHELL" + - "curl -sf -u ${COUCHDB_USER}:${COUCHDB_PASSWORD} http://localhost:5984/_session | grep -q ok || exit 1" + interval: 5s + timeout: 5s + retries: 24 + start_period: 20s + networks: + - livesync-net + + # --------------------------------------------------------------------------- + # One-shot init container — runs couchdb-init.sh after CouchDB is healthy. + # Sets single-node cluster, auth requirements, CORS, size limits, creates DB. + # Restarts on failure (e.g. race at first boot) but won't re-run if already done. + # --------------------------------------------------------------------------- + couchdb-init: + image: curlimages/curl:latest + container_name: livesync-init + restart: on-failure + depends_on: + couchdb: + condition: service_healthy + environment: + COUCHDB_INTERNAL_URL: http://couchdb:5984 + COUCHDB_USER: ${COUCHDB_USER} + COUCHDB_PASSWORD: ${COUCHDB_PASSWORD} + COUCHDB_DATABASE: ${COUCHDB_DATABASE:-obsidiannotes} + volumes: + - ./scripts/couchdb-init.sh:/couchdb-init.sh:ro + entrypoint: ["sh", "/couchdb-init.sh"] + networks: + - livesync-net + + # --------------------------------------------------------------------------- + # PROFILE: caddy — Caddy reverse proxy with automatic Let's Encrypt TLS + # Requirements: public domain, ports 80 + 443 open to internet + # Usage: docker compose --profile caddy up -d + # --------------------------------------------------------------------------- + caddy: + image: caddy:latest + container_name: livesync-caddy + profiles: [caddy] + restart: unless-stopped + depends_on: + couchdb: + condition: service_healthy + environment: + COUCHDB_DOMAIN: ${COUCHDB_DOMAIN:?Set COUCHDB_DOMAIN in .env for caddy profile} + ACME_EMAIL: ${ACME_EMAIL:?Set ACME_EMAIL in .env for caddy profile} + ports: + - "80:80" + - "443:443" + volumes: + - ./config/Caddyfile:/etc/caddy/Caddyfile:ro + - caddy-data:/data + - caddy-config:/config + networks: + - livesync-net + + # --------------------------------------------------------------------------- + # PROFILE: tailscale — Tailscale sidecar for mesh VPN + optional Funnel + # Requirements: Tailscale account (free), OAuth key, Funnel enabled in ACL + # Usage: docker compose --profile tailscale up -d + # The CouchDB port mapping above can be removed for tailscale-only deployments. + # --------------------------------------------------------------------------- + tailscale: + image: tailscale/tailscale:latest + container_name: livesync-tailscale + profiles: [tailscale] + restart: unless-stopped + hostname: ${TS_HOSTNAME:-livesync} + environment: + TS_AUTHKEY: ${TS_AUTHKEY:?Set TS_AUTHKEY in .env for tailscale profile} + TS_STATE_DIR: /var/lib/tailscale + TS_SERVE_CONFIG: /config/serve.json + TS_USERSPACE: "false" + TS_ACCEPT_DNS: "false" + TS_EXTRA_ARGS: "" + volumes: + - tailscale-state:/var/lib/tailscale + - ./config/ts-serve.json:/config/serve.json:ro + - /dev/net/tun:/dev/net/tun + cap_add: + - NET_ADMIN + - SYS_MODULE + # Share CouchDB's network namespace so Tailscale can reach it on localhost + network_mode: service:couchdb + depends_on: + - couchdb + + # --------------------------------------------------------------------------- + # PROFILE: cloudflare — cloudflared tunnel daemon + # Requirements: Cloudflare account, tunnel token from CF Zero Trust dashboard + # Usage: docker compose --profile cloudflare up -d + # NOTE: Enable "Use Request API" toggle in the Obsidian LiveSync plugin settings + # to avoid Cloudflare's 100-second proxy timeout (524 errors). + # --------------------------------------------------------------------------- + cloudflared: + image: cloudflare/cloudflared:latest + container_name: livesync-cloudflared + profiles: [cloudflare] + restart: unless-stopped + command: tunnel --no-autoupdate run + environment: + TUNNEL_TOKEN: ${CF_TUNNEL_TOKEN:?Set CF_TUNNEL_TOKEN in .env for cloudflare profile} + volumes: + - ./config/cloudflared.yml:/etc/cloudflared/config.yml:ro + depends_on: + couchdb: + condition: service_healthy + networks: + - livesync-net + +# ============================================================================= +# Volumes +# ============================================================================= +volumes: + couchdb-data: + driver: local + caddy-data: + driver: local + caddy-config: + driver: local + tailscale-state: + driver: local + +# ============================================================================= +# Networks +# ============================================================================= +networks: + livesync-net: + driver: bridge diff --git a/docker/scripts/couchdb-init.sh b/docker/scripts/couchdb-init.sh new file mode 100644 index 0000000..23b00ef --- /dev/null +++ b/docker/scripts/couchdb-init.sh @@ -0,0 +1,79 @@ +#!/bin/sh +# Self-hosted LiveSync — CouchDB Initialization Script +# Runs once on first startup via the couchdb-init service. +# Configures single-node cluster, auth, CORS, and size limits. + +set -e + +hostname="${COUCHDB_INTERNAL_URL:-http://couchdb:5984}" +username="${COUCHDB_USER:?COUCHDB_USER is required}" +password="${COUCHDB_PASSWORD:?COUCHDB_PASSWORD is required}" +node="${COUCHDB_NODE:-_local}" + +echo "==> Waiting for CouchDB at ${hostname} ..." +# _up is publicly accessible (no auth required) — safe pre-auth wait +until curl -sf "${hostname}/_up" 2>/dev/null | grep -q '"status":"ok"'; do + printf '.' + sleep 2 +done +echo "" +echo "==> CouchDB is up. Initializing..." + +# 1. Enable single-node cluster +curl -sf -X POST "${hostname}/_cluster_setup" \ + -H "Content-Type: application/json" \ + -d "{\"action\":\"enable_single_node\",\"username\":\"${username}\",\"password\":\"${password}\",\"bind_address\":\"0.0.0.0\",\"port\":5984,\"singlenode\":true}" \ + --user "${username}:${password}" && echo "[OK] cluster_setup" + +# 2. Require valid user on both http interfaces +curl -sf -X PUT "${hostname}/_node/${node}/_config/chttpd/require_valid_user" \ + -H "Content-Type: application/json" -d '"true"' --user "${username}:${password}" && echo "[OK] chttpd/require_valid_user" + +curl -sf -X PUT "${hostname}/_node/${node}/_config/chttpd_auth/require_valid_user" \ + -H "Content-Type: application/json" -d '"true"' --user "${username}:${password}" && echo "[OK] chttpd_auth/require_valid_user" + +# 3. HTTP auth challenge header +curl -sf -X PUT "${hostname}/_node/${node}/_config/httpd/WWW-Authenticate" \ + -H "Content-Type: application/json" -d '"Basic realm=\"couchdb\""' --user "${username}:${password}" && echo "[OK] httpd/WWW-Authenticate" + +# 4. Enable CORS on both http listeners +curl -sf -X PUT "${hostname}/_node/${node}/_config/httpd/enable_cors" \ + -H "Content-Type: application/json" -d '"true"' --user "${username}:${password}" && echo "[OK] httpd/enable_cors" + +curl -sf -X PUT "${hostname}/_node/${node}/_config/chttpd/enable_cors" \ + -H "Content-Type: application/json" -d '"true"' --user "${username}:${password}" && echo "[OK] chttpd/enable_cors" + +# 5. Increase size limits for large vaults +curl -sf -X PUT "${hostname}/_node/${node}/_config/chttpd/max_http_request_size" \ + -H "Content-Type: application/json" -d '"4294967296"' --user "${username}:${password}" && echo "[OK] chttpd/max_http_request_size" + +curl -sf -X PUT "${hostname}/_node/${node}/_config/couchdb/max_document_size" \ + -H "Content-Type: application/json" -d '"50000000"' --user "${username}:${password}" && echo "[OK] couchdb/max_document_size" + +# 6. CORS configuration — allow Obsidian app origins +curl -sf -X PUT "${hostname}/_node/${node}/_config/cors/credentials" \ + -H "Content-Type: application/json" -d '"true"' --user "${username}:${password}" && echo "[OK] cors/credentials" + +curl -sf -X PUT "${hostname}/_node/${node}/_config/cors/origins" \ + -H "Content-Type: application/json" \ + -d '"app://obsidian.md,capacitor://localhost,http://localhost"' \ + --user "${username}:${password}" && echo "[OK] cors/origins" + +# 7. Create the vault database if it doesn't exist +db="${COUCHDB_DATABASE:-obsidiannotes}" +set +e +status=$(curl -sf -o /dev/null -w "%{http_code}" --user "${username}:${password}" "${hostname}/${db}" 2>/dev/null) +curl_exit=$? +set -e + +if [ "$status" = "200" ]; then + echo "[OK] database '${db}' already exists" +else + curl -sf -X PUT "${hostname}/${db}" --user "${username}:${password}" && echo "[OK] database '${db}' created" || echo "[WARN] database creation returned non-200 — may already exist" +fi + +echo "" +echo "==> CouchDB initialization complete!" +echo " URL : ${hostname}" +echo " Database : ${db}" +echo " Username : ${username}"