mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-06-20 09:08:57 +03:00
Compare commits
16 Commits
main
...
0.25.77-cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
becff6eba9 | ||
|
|
6c07f0ca64 | ||
|
|
18f1fce3e8 | ||
|
|
efb6a0a814 | ||
|
|
874164ecf5 | ||
|
|
42954fcf68 | ||
|
|
b2c6916ac7 | ||
|
|
21f47cf48d | ||
|
|
21796b6651 | ||
|
|
f468758c75 | ||
|
|
2ee6a2c09f | ||
|
|
463c0c0bc8 | ||
|
|
641488de1f | ||
|
|
5cb76bba72 | ||
|
|
866a49204c | ||
|
|
fb93511ae7 |
23
.gitattributes
vendored
23
.gitattributes
vendored
@@ -1,24 +1 @@
|
||||
# Always checkout shell scripts with LF line endings (never CRLF)
|
||||
*.sh text eol=lf
|
||||
|
||||
# Standard text files — auto normalize on checkout
|
||||
*.md text eol=lf
|
||||
*.yml text eol=lf
|
||||
*.yaml text eol=lf
|
||||
*.ini text eol=lf
|
||||
*.env text eol=lf
|
||||
*.json text eol=lf
|
||||
*.ts text eol=lf
|
||||
*.js text eol=lf
|
||||
*.mjs text eol=lf
|
||||
*.css text eol=lf
|
||||
|
||||
# Binary files — no line ending conversion
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.woff2 binary
|
||||
*.woff binary
|
||||
*.sh text eol=lf
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# Code of Conduct
|
||||
|
||||
We wish to maintain an open, welcoming, and collaborative environment for all contributors.
|
||||
|
||||
## Our Standard
|
||||
|
||||
Our core principle is mutual respect. We encourage open discussion, diverse perspectives, and constructive feedback.
|
||||
|
||||
## The Limit of Tolerance
|
||||
|
||||
To preserve a tolerant and open community, we do not tolerate intolerance. Actions that aim to harass, exclude, or silence others are not welcome. Specifically, we do not accept personal attacks, breaches of privacy, or sustained disruption of discussions. We prioritise protecting the community's capacity for open, peaceful collaboration.
|
||||
|
||||
## Resolution
|
||||
|
||||
If any issue arises, the project maintainers will resolve it in a fair, minimal, and constructive manner, aiming to restore a cooperative environment. Depending on the nature of the behaviour, actions may range from a simple warning to temporary or permanent suspension of repository access.
|
||||
|
||||
## Contact
|
||||
|
||||
You can contact the project maintainer via email at `vrtmrz@proton.me` or via Nostr at `npub1azzj0dzw8evwtgyjeucyfz5cs8k0eg7rd0x4qvggcg3s7lx0dmaqv9sfka`.
|
||||
|
||||
## Criticism of the Maintainer
|
||||
|
||||
To ensure open and transparent governance, criticism of the maintainer will not be deleted as long as it is clearly framed as a constructive objection. However, spamming duplicate issues on the same topic or resorting to personal attacks will result in closure or removal.
|
||||
|
||||
## Revisions
|
||||
|
||||
This Code of Conduct is maintained by the project maintainers and may be updated to address new challenges. While the final decision rests with the maintainers, we welcome constructive suggestions and feedback through issues or pull requests.
|
||||
@@ -1,70 +0,0 @@
|
||||
# Contributing to Self-hosted LiveSync
|
||||
|
||||
Thank you for your interest in contributing to Self-hosted LiveSync! We welcome all contributions, including bug reports, feature requests, documentation improvements, translations, and pull requests.
|
||||
|
||||
## Getting Started
|
||||
|
||||
To set up the development environment, please follow these steps:
|
||||
|
||||
1. Clone the repository recursively to ensure all Git submodules are loaded:
|
||||
```bash
|
||||
git clone --recursive https://github.com/vrtmrz/obsidian-livesync
|
||||
```
|
||||
If you have already cloned the repository without submodules, run the following command:
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
2. Install the package dependencies:
|
||||
```bash
|
||||
npm ci
|
||||
```
|
||||
|
||||
3. Build the plug-in:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
For a more comprehensive guide on development workflows, testing configurations, and subrepos, please refer to [devs.md](devs.md).
|
||||
|
||||
## Guidelines for Contributions
|
||||
|
||||
### 1. Code Style and Verification
|
||||
|
||||
Before submitting a pull request, you must run verification scripts locally to ensure that there are no syntax, type, or linting errors:
|
||||
|
||||
- Run type checking and linting:
|
||||
```bash
|
||||
npm run check
|
||||
```
|
||||
- Run unit tests:
|
||||
```bash
|
||||
npm run test:unit
|
||||
```
|
||||
|
||||
If you have the capability and a suitable environment (such as Linux and Docker), running the CLI End-to-End (E2E) tests is also highly appreciated. Instructions are detailed in [devs.md](devs.md). If you cannot run E2E tests locally, please explicitly ask to run the tests on the CI by stating 'Please run CI tests' in your pull request description.
|
||||
|
||||
### 2. Documentation and UI Text Style
|
||||
|
||||
To maintain consistency across the project, we ask that you follow the established writing style and conventions of the codebase when contributing documentation or user-facing messages:
|
||||
|
||||
- **Spelling**: Prioritise region-independent, neutral spelling if a suitable word exists. If there is no such word, please use British English spelling to align with the codebase's style (for example: preferring '-ise' and '-isation' suffixes over '-ize' and '-ization'). However, we do not treat alternative spellings as errors.
|
||||
- **Oxford Comma**: Use the serial (Oxford) comma to separate items in lists of three or more (for example: 'settings, snippets, and themes').
|
||||
- **Logical Punctuation**: Place punctuation marks outside quotation marks unless they are part of the quoted text itself (for example: write 'dialogue', not 'dialogue,').
|
||||
- **No Contractions**: Avoid using contractions in general text or documentation (for example: write "do not" instead of "don't", and "cannot" instead of "can't").
|
||||
- **Affirmative Phrasing**: Avoid asking questions using negative forms in user-facing dialogue. Use affirmative questions to prevent translation and interpretation discrepancies.
|
||||
- **Specific Words**: Use 'dialogue' for documentation and user-facing messages (use 'dialog' only inside source code). Use the hyphenated form 'plug-in' in user-facing text (use 'plugin' only in configuration settings or technical contexts).
|
||||
|
||||
For a detailed list of vocabulary conventions and terms, please refer to [docs/terms.md](docs/terms.md).
|
||||
|
||||
### 3. Translations
|
||||
|
||||
To add or update translations, please refer to [docs/adding_translations.md](docs/adding_translations.md) for detailed instructions.
|
||||
|
||||
### 4. Git Submodules
|
||||
|
||||
The `src/lib` directory is a Git submodule pointing to the shared library `livesync-commonlib`. If you wish to propose changes to the shared library, do not modify `src/lib` directly. Instead, please submit a separate pull request to the [livesync-commonlib repository](https://github.com/vrtmrz/livesync-commonlib).
|
||||
|
||||
## License
|
||||
|
||||
By contributing, you agree that your contributions will be licensed under the MIT License.
|
||||
20
devs.md
20
devs.md
@@ -76,26 +76,6 @@ To facilitate development and testing, the build process can automatically copy
|
||||
- `test/unit/` - Unit tests (via vitest, as harness is browser-based)
|
||||
- `test/harness/` - Mock implementations (e.g., `obsidian-mock.ts`)
|
||||
|
||||
### Import Path Normalisation
|
||||
|
||||
The codebase uses `@/` and `@lib/` path aliases to keep import structures clean. To normalise imports and exports across files, use the following utility script:
|
||||
```bash
|
||||
npm run pretty:importpath
|
||||
```
|
||||
Under the hood, this runs Deno with the script [utilsdeno/normalise-imports.ts](file:///p:/plant25/obsidian/projects/obsidian-livesync/utilsdeno/normalise-imports.ts). You can pass additional flags to this script if required (by running it via Deno directly from the `utilsdeno` directory):
|
||||
- `--run`: Applies the changes (the script runs in dry-run mode by default).
|
||||
- `--all-alias`: Normalises sibling/child relative imports starting with `./` to use aliases.
|
||||
|
||||
### Type Generation
|
||||
|
||||
To generate fallback type definitions for the shared library and add appropriate Deno ignore comments (which suppresses Deno compilation warnings and linting warnings inside the `_types` directory), run:
|
||||
```bash
|
||||
npm run build:lib:types
|
||||
```
|
||||
This script executes:
|
||||
1. TypeScript compilation (`tsconfig.types.json`) to output definitions to the `_types` directory.
|
||||
2. The Deno script [utilsdeno/types-add-ignore.ts](file:///p:/plant25/obsidian/projects/obsidian-livesync/utilsdeno/types-add-ignore.ts) to prepend Deno ignore comments to the generated type files.
|
||||
|
||||
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
# 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 <hostname>.<tailnet>.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
|
||||
349
docker/README.md
349
docker/README.md
@@ -1,349 +0,0 @@
|
||||
# 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.<tailnet>.ts.net` — completely private
|
||||
- **Funnel mode**: public HTTPS at `https://livesync.<tailnet>.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**:
|
||||
- Free [Cloudflare account](https://www.cloudflare.com/)
|
||||
- A domain managed by Cloudflare DNS (can transfer existing domain for free)
|
||||
- Cloudflare Zero Trust account (free)
|
||||
|
||||
#### Step 1: Create a Cloudflare Tunnel
|
||||
|
||||
1. Log in to [Cloudflare Zero Trust](https://one.dash.cloudflare.com/)
|
||||
2. Navigate to **Networks → Tunnels**
|
||||
3. Click **Create a tunnel**
|
||||
4. Choose **Cloudflared** as tunnel type
|
||||
5. Name your tunnel (e.g., `obsidian-livesync`)
|
||||
6. Click **Save tunnel**
|
||||
7. **Copy the tunnel token** — it looks like `eyJhIjoiZX...` (very long, ~400 characters)
|
||||
|
||||
#### Step 2: Configure Environment
|
||||
|
||||
Edit `docker/.env`:
|
||||
```env
|
||||
CF_TUNNEL_TOKEN=eyJhIjoiZX... # Paste the full token from Step 1
|
||||
COUCHDB_DOMAIN=sync.yourdomain.com # Must be a domain managed by Cloudflare
|
||||
```
|
||||
|
||||
#### Step 3: Add Public Hostname Route
|
||||
|
||||
🚨 **CRITICAL**: Token-based tunnels ignore the local `cloudflared.yml` config file. All routing is controlled from the dashboard.
|
||||
|
||||
Back in the Zero Trust dashboard, **in the same tunnel creation flow** (or edit your tunnel later):
|
||||
|
||||
1. Go to the **Public Hostname** tab
|
||||
2. Click **Add a public hostname**
|
||||
3. Configure:
|
||||
- **Subdomain**: `sync` (or your preferred subdomain)
|
||||
- **Domain**: Select your Cloudflare domain from dropdown
|
||||
- **Type**: `HTTP`
|
||||
- **URL**: `couchdb:5984` ← **Do NOT use `localhost`!**
|
||||
|
||||
**Why `couchdb:5984` not `localhost:5984`?**
|
||||
- The `cloudflared` container runs inside Docker on the same network as `couchdb`
|
||||
- Docker's internal DNS resolves `couchdb` to the correct container
|
||||
- Using `localhost` would look inside the `cloudflared` container (nothing there)
|
||||
|
||||
4. Under **Additional application settings** (expand):
|
||||
- **No TLS Verify**: Leave **OFF** (CouchDB uses plain HTTP internally, that's fine)
|
||||
- Leave other settings at defaults
|
||||
5. Click **Save hostname**
|
||||
|
||||
#### Step 4: Start the Stack
|
||||
|
||||
```bash
|
||||
cd docker/
|
||||
docker compose --profile cloudflare up -d
|
||||
```
|
||||
|
||||
Verify containers are running:
|
||||
```bash
|
||||
docker ps --filter "name=livesync"
|
||||
```
|
||||
|
||||
You should see:
|
||||
- `livesync-couchdb` — Status: Up (healthy)
|
||||
- `livesync-cloudflared` — Status: Up
|
||||
- `livesync-init` — Status: Exited (0)
|
||||
|
||||
#### Step 5: Test the Connection
|
||||
|
||||
```bash
|
||||
# Should return 401 Unauthorized (proves CouchDB auth is working)
|
||||
curl -I https://sync.yourdomain.com
|
||||
|
||||
# Should return {"couchdb":"Welcome",...}
|
||||
curl -u admin:yourpassword https://sync.yourdomain.com
|
||||
```
|
||||
|
||||
If you get **404**, see Troubleshooting below.
|
||||
|
||||
#### Step 6: Configure Obsidian Plugin
|
||||
|
||||
In Obsidian → Settings → **Self-hosted LiveSync**:
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| URI | `https://sync.yourdomain.com` |
|
||||
| Username | value of `COUCHDB_USER` from `.env` |
|
||||
| Password | value of `COUCHDB_PASSWORD` from `.env` |
|
||||
| Database name | value of `COUCHDB_DATABASE` from `.env` (default: `obsidiannotes`) |
|
||||
| End-to-end passphrase | *Choose your own* — never stored server-side |
|
||||
|
||||
Under **Remote Database Configuration → Advanced**:
|
||||
- Enable: ✅ **Use Request API to avoid inevitable CORS problem**
|
||||
(See "Known Issue" below for why this is critical)
|
||||
|
||||
---
|
||||
|
||||
#### 🔧 Troubleshooting Cloudflare Tunnel
|
||||
|
||||
**Problem: 404 Error / Cloud flare Generic Error Page**
|
||||
|
||||
**Diagnosis**:
|
||||
```bash
|
||||
# Check if cloudflared is running
|
||||
docker logs livesync-cloudflared --tail 20
|
||||
|
||||
# Look for: "Registered tunnel connection"
|
||||
# If you see the connector ID, the tunnel is connected but routing is wrong
|
||||
```
|
||||
|
||||
**Fix**: The public hostname rule is missing or incorrect.
|
||||
|
||||
1. Go to Zero Trust → Networks → Tunnels → your tunnel → **Edit**
|
||||
2. Click **Public Hostname** tab
|
||||
3. Verify a hostname exists with:
|
||||
- Service Type: `HTTP`
|
||||
- URL: `couchdb:5984` (NOT `localhost:5984`)
|
||||
4. If no hostname exists, add it (see Step 3 above)
|
||||
5. Wait 30 seconds for changes to propagate, then test again
|
||||
|
||||
**Problem: Connection immediately closes / 502 Bad Gateway**
|
||||
|
||||
**Diagnosis**: CouchDB is not healthy or not on the same Docker network as cloudflared.
|
||||
|
||||
```bash
|
||||
docker ps --filter "name=livesync-couchdb"
|
||||
# Status should be: Up (healthy)
|
||||
|
||||
docker inspect livesync-couchdb -f '{{.NetworkSettings.Networks}}'
|
||||
# Should show: livesync-net
|
||||
|
||||
docker inspect livesync-cloudflared -f '{{.NetworkSettings.Networks}}'
|
||||
# Should also show: livesync-net
|
||||
```
|
||||
|
||||
**Fix**: If CouchDB is unhealthy, check logs:
|
||||
```bash
|
||||
docker logs livesync-couchdb --tail 50
|
||||
```
|
||||
|
||||
**Problem: 524 Timeout Errors During Sync**
|
||||
|
||||
**Root cause**: Cloudflare's proxy has a **100-second idle timeout**. CouchDB's replication protocol uses long-polling on the `_changes` feed, which can idle for longer during quiet periods.
|
||||
|
||||
**Fix**: Switch to short-polling mode in the Obsidian plugin:
|
||||
1. Obsidian → Settings → Self-hosted LiveSync
|
||||
2. **Remote Database Configuration → Advanced**
|
||||
3. Enable: ✅ **Use Request API to avoid inevitable CORS problem**
|
||||
4. Save and restart sync
|
||||
|
||||
This keeps all requests under 100 seconds.
|
||||
|
||||
**Alternative**: Use Tailscale or Caddy profiles instead — neither has aggressive timeouts.
|
||||
|
||||
---
|
||||
|
||||
## 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. |
|
||||
@@ -1,26 +0,0 @@
|
||||
# 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
# 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
|
||||
@@ -1,30 +0,0 @@
|
||||
; 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
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"TCP": {
|
||||
"443": {
|
||||
"HTTPS": true
|
||||
}
|
||||
},
|
||||
"Web": {
|
||||
"${TS_CERT_DOMAIN}:443": {
|
||||
"Handlers": {
|
||||
"/": {
|
||||
"Proxy": "http://127.0.0.1:5984"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"AllowFunnel": {
|
||||
"${TS_CERT_DOMAIN}:443": true
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
# 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
|
||||
@@ -1,79 +0,0 @@
|
||||
#!/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}"
|
||||
@@ -515,7 +515,7 @@ Saving will be performed forcefully after this number of seconds.
|
||||
#### Use the trash bin
|
||||
|
||||
Setting key: trashInsteadDelete
|
||||
Move remotely deleted files to the trash, instead of deleting. On Obsidian v1.7.2 or newer, file deletion respects the user's deletion preferences (by utilising the `FileManager.trashFile` API), regardless of this setting.
|
||||
Move remotely deleted files to the trash, instead of deleting.
|
||||
|
||||
#### Keep empty folder
|
||||
|
||||
@@ -557,10 +557,9 @@ Setting key: notifyAllSettingSyncFile
|
||||
|
||||
### 7. Hidden Files (Advanced)
|
||||
|
||||
#### Enable Hidden files sync
|
||||
#### Hidden file synchronisation
|
||||
|
||||
Setting key: syncInternalFiles
|
||||
Enable the synchronisation of hidden files and folders (e.g. settings files, templates, snippets, and themes under `.obsidian`).
|
||||
#### Enable Hidden files sync
|
||||
|
||||
#### Scan for hidden files before replication
|
||||
|
||||
|
||||
Reference in New Issue
Block a user