A headless backup tool for Obsidian LiveSync. Extracts your notes directly from CouchDB, decrypts them, and creates daily zip archives - no Obsidian installation required.
LiveSync stores your notes in CouchDB with end-to-end encryption. While this is great for syncing between devices, it means your backups are just encrypted blobs. This tool:
- Extracts notes directly from CouchDB
- Decrypts E2EE content (supports both encrypted and plaintext chunks)
- Creates timestamped zip archives
- Automatically prunes old backups
- Runs in Docker with minimal resources (~100MB RAM)
# Clone the repo
git clone https://github.com/edleeman17/livesync-backup.git
cd livesync-backup
# Configure
cp .env.example .env
# Edit .env with your values (see Configuration below)
# Build and run
docker compose build
docker compose run --rm livesync-backupCopy .env.example to .env and fill in your values:
| Variable | Description | Where to find it |
|---|---|---|
E2EE_PASSPHRASE |
Your LiveSync encryption passphrase | Obsidian → Settings → LiveSync → Encryption |
COUCHDB_URI |
CouchDB server URL | Your server address, e.g., https://192.168.1.100:5984 |
COUCHDB_USER |
CouchDB username | Your CouchDB admin credentials |
COUCHDB_PASSWORD |
CouchDB password | Your CouchDB admin credentials |
COUCHDB_DATABASE |
Database name | Check Fauxton at http://your-server:5984/_utils |
NAS_PATH |
Where to save backups | Any directory path, e.g., /mnt/nas/backups |
- Open Fauxton (CouchDB admin) at
http://your-server:5984/_utils - Look for a database starting with
obsidian - It's usually just
obsidianorobsidian-{suffix}
Alternatively, check your LiveSync plugin's data.json file:
- If
additionalSuffixOfDatabaseNameis empty → database isobsidian - If it has a value like
5987ba08b1ec3c27→ database isobsidian-5987ba08b1ec3c27
| Variable | Default | Description |
|---|---|---|
RETENTION_DAYS |
30 |
Days to keep old backups |
DRY_RUN |
false |
Set to true to preview without making changes |
UPTIME_KUMA_PUSH_URL |
- | Uptime Kuma push URL for monitoring |
CA_CERT_HOST |
- | Path to CA certificate on host (for self-signed certs) |
CA_CERT |
- | Path where cert is mounted in container (e.g., /app/certs/ca.pem) |
CONFIG_PASSPHRASE |
- | For decrypting credentials from data.json (advanced) |
Before running a real backup, you can preview what would happen:
DRY_RUN=true docker compose run --rm livesync-backupThis will:
- Connect to CouchDB and verify credentials
- List all files that would be backed up
- Show what backup file would be created
- Show what old backups would be pruned
No files are written or deleted in dry run mode.
Optionally notify Uptime Kuma when backups complete or fail:
- In Uptime Kuma, add a new monitor with type "Push"
- Copy the Push URL
- Add to your
.env:
UPTIME_KUMA_PUSH_URL=https://your-uptime-kuma/api/push/xxxxxxxxThe tool will:
- Send
status=upwith "Backup completed" on success - Send
status=downwith error message on failure
Add a cron job to run backups automatically:
crontab -e# Run backup daily at 2 AM
0 2 * * * cd /path/to/livesync-backup && docker compose run --rm livesync-backup >> ./backup.log 2>&1If your CouchDB uses a self-signed SSL certificate, you have two options:
Add these to your .env:
CA_CERT_HOST=/path/to/your/ca-certificate.pem
CA_CERT=/app/certs/ca.pemThe certificate will be mounted into the container and used for proper TLS verification.
If no CA_CERT is configured, the tool automatically skips TLS verification. This is less secure but works out of the box.
- Connects to CouchDB and fetches all file entries
- Retrieves content chunks (LiveSync splits files into chunks for efficient syncing)
- Decrypts chunks using your E2EE passphrase (or passes through plaintext for unencrypted files)
- Reassembles files and writes to a temp directory
- Creates a timestamped zip archive (e.g.,
obsidian-2024-01-15.zip) - Moves archive to your backup destination
- Deletes backups older than retention period
This tool is designed with safety as a top priority:
- Delete your notes - Read-only access to CouchDB; no write or delete operations
- Write outside backup directory - Path traversal attacks are blocked
- Delete non-backup files - Only files matching
obsidian-YYYY-MM-DD.zipcan be pruned - Delete directories - Only regular files are considered for pruning
- Path validation - All file paths are validated before writing
- Strict filename pattern - Backup pruning uses exact regex matching
- Dry run mode - Preview all operations before running
- Comprehensive test suite - 35+ tests verify safety guarantees
Run the test suite to verify safety guarantees:
# Run tests in Docker
docker compose run --rm --user root --entrypoint deno livesync-backup test --allow-read --allow-write --allow-env src/tests/
# Or locally with Deno
deno test --allow-read --allow-write --allow-env src/tests/Tests cover:
- Path traversal prevention
- Absolute path rejection
- Null byte injection prevention
- Backup filename pattern validation
- Retention period enforcement
- Subdirectory traversal prevention
livesync-backup/
├── src/
│ ├── main.ts # Entry point and orchestration
│ ├── config.ts # Configuration loading
│ ├── crypto.ts # AES-256-GCM decryption (V2 format)
│ ├── couchdb.ts # CouchDB client
│ ├── extractor.ts # Chunk reassembly and file writing
│ ├── backup.ts # Zip creation and pruning
│ ├── path_safety.ts # Path validation utilities
│ └── tests/ # Test suite
├── Dockerfile
├── docker-compose.yml
├── entrypoint.sh # Handles CA cert vs insecure TLS
├── deno.json
└── .env.example
LiveSync uses AES-256-GCM encryption with multiple formats:
HKDF format (%= prefix) - used for chunked files:
- Binary structure:
iv[12] + hkdfSalt[32] + ciphertext + tag - Key derivation: PBKDF2(passphrase, globalSalt, 310000) → HKDF expansion
- All base64 encoded after prefix
V2 legacy format (% prefix) - used for some inline files:
- Format:
%+ hex(iv[16]) + hex(salt[16]) + base64(ciphertext + tag) - Key derivation: PBKDF2(SHA256(passphrase), salt, 100000)
The tool handles both formats and plaintext (for files created before E2EE was enabled).
- File entries:
_idis the file path, containschildren[]array pointing to chunks - Leaf chunks:
_idish:{content-hash}, contains encrypteddatafield
Your CouchDB is using HTTPS but you specified http://. Change to https://.
Self-signed certificate issue. The Docker image handles this automatically, but if running locally, use:
deno run --unsafely-ignore-certificate-errors ...These are files created before you enabled E2EE, or files that weren't encrypted. Update to the latest version which handles plaintext chunks.
Files stored with inline data (not chunked) may use different encryption parameters. This can happen with:
- Files imported from other apps (e.g., Bear)
- Files created during a migration
- Files encrypted with different settings
Workaround: In Obsidian, make a small edit to the affected file and save it. This will re-encrypt it with the current settings and store it as a chunked file, which can be backed up.
Check your COUCHDB_DATABASE value matches the actual database name in Fauxton.
MIT