Zero-knowledge bookkeeping.
An open-source, privacy-first personal finance system built on plain-text accounting principles. ldgr combines the rigor of double-entry bookkeeping (hledger-compatible) with AES-256-GCM envelope encryption — no server, sync transport, or third party ever sees your plaintext financial data.
- Zero-knowledge encryption — client-side AES-256-GCM with Argon2id key derivation. The vault key hierarchy ensures data is encrypted before it leaves your device.
- Double-entry bookkeeping — proper accounting with hierarchical accounts, balance assertions, and multi-currency support.
- Recovery key — 24-word Crockford Base32 emergency key generated at vault creation. Lost password + lost recovery key = unrecoverable (by design).
- Local-first — SQLite-backed storage with versioned rows. Works fully offline. Sync is optional.
- Self-hosted sync server — optional Axum-based relay server with SRP-6a zero-knowledge authentication. The server stores only encrypted blobs — it never sees plaintext financial data. Docker image included.
- Multi-platform — Rust core library with CLI (clap + ratatui), iOS/iPadOS (SwiftUI via UniFFI), and web (Next.js + WASM) frontends.
- hledger-compatible — import from and export to hledger journal format. Use
hledgerfor reporting if you prefer. - Investment tracking — value holdings at market prices for net worth calculations. Market data from Yahoo Finance, CoinGecko (crypto), and ECB (forex) — all free, no API keys required. See the Market Data guide for providers, caching, privacy, and configuration.
Note: ldgr is a net worth tracker, not a trading platform. Market data is used to value your investment holdings as part of the overall financial picture. For investment decisions, use specialized tools (your brokerage platform, Bloomberg, etc.).
# Install from source
cargo install --path crates/ldgr-cli
# Create a new vault
ldgr init
# → Prompts for master password
# → Displays recovery key (write it down!)
# → Creates vault at ~/.ldgr/vault.ldgr
# Unlock the vault
ldgr unlock
# Add accounts and transactions
ldgr accounts add Assets:Checking:Chase
ldgr accounts add Expenses:Food
ldgr add --date 2024-01-15 --description "Groceries" \
--posting "Expenses:Food 42.50 USD" \
--posting "Assets:Checking:Chase -42.50 USD"
# View reports
ldgr balance
ldgr register
ldgr incomestatement
ldgr balancesheet
# Import from bank exports
ldgr import statement.csv --profile chase
ldgr import statement.ofx
# Export for hledger
ldgr export --format hledger | hledger balance
# Generate a styled PDF report
ldgr export --format pdf --report balancesheet --output balance-sheet.pdf
ldgr export --format pdf --report incomestatement --output income.pdf date:2024
ldgr export --format pdf --report networth --output net-worth.pdf
# Lock when done
ldgr lock| Command | Description |
|---|---|
ldgr init |
Create a new encrypted vault |
ldgr unlock |
Unlock vault with master password |
ldgr lock |
Lock vault (clear session) |
ldgr status |
Show vault path, version, and lock state |
ldgr accounts |
List all accounts |
ldgr accounts add <name> |
Create account (auto-detects type from name) |
ldgr accounts rename <old> <new> |
Rename an account |
ldgr add |
Add a transaction (interactive or with flags) |
ldgr delete <id> |
Soft-delete a transaction |
ldgr balance [query] |
Hierarchical account balances |
ldgr register [query] |
Chronological register with running balance |
ldgr incomestatement [query] |
Income statement (Revenue - Expenses) |
ldgr balancesheet [query] |
Balance sheet (Assets - Liabilities = Equity) |
ldgr import <file> |
Import CSV or OFX/QFX bank exports |
ldgr export --format <fmt> |
Export to hledger, CSV, or JSON |
ldgr export --format pdf --report <name> --output <file> |
PDF report (balancesheet, incomestatement, networth) |
ldgr validate <file> |
Check journal importability |
ldgr reconcile <account> |
Interactive reconciliation |
ldgr rules |
Manage import auto-categorization rules |
The optional sync server (crates/ldgr-server/) is an encrypted blob relay — it
stores and serves encrypted blobs but never decrypts them.
Self-host in one command (Immich-style docker-compose bundle at the repo root):
cp .env.example .env # review settings (registration policy, admin email)
docker compose up -d # pulls the published multi-arch GHCR imageThis gives you a running server with a persistent named volume and a passing
/health healthcheck. See the Self-Hosting guide for the
full end-to-end walkthrough — deploy, TLS (Caddy), upgrades, backup/restore,
first-run admin onboarding, adding users, and the 1Password-style two-secret
account model (master password + Account Secret Key + Emergency Kit).
Published images live at ghcr.io/kafkade/ldgr-server (multi-arch amd64/arm64,
versioned + latest).
Other ways to run it (single container or from source):
# Single container from the published image
docker run -p 8080:8080 -v ldgr-data:/data ghcr.io/kafkade/ldgr-server:latest
# Build the image locally
docker build -t ldgr-server -f crates/ldgr-server/Dockerfile .
docker run -p 8080:8080 -v ldgr-data:/data ldgr-server
# Or run directly from source
cargo run -p ldgr-serverConfiguration (environment variables):
| Variable | Default | Description |
|---|---|---|
LDGR_BIND_ADDR |
127.0.0.1:8080 |
Listen address (Docker image overrides to 0.0.0.0:8080) |
LDGR_DB_PATH |
ldgr-server.db |
SQLite database path (Docker image overrides to /data/ldgr-server.db) |
LDGR_REGISTRATION |
invite-only |
Who may register: open, invite-only, or admin-only |
LDGR_ADMIN_EMAIL |
(unset) | Seeds the first admin; that account bypasses the registration policy |
LDGR_SESSION_TTL_HOURS |
720 |
Session lifetime (30 days) |
LDGR_RELAY_TTL_MINUTES |
10 |
Key exchange relay offer TTL |
LDGR_MAX_BLOB_BYTES |
52428800 |
Max blob size (50 MB) |
LDGR_SRP_HANDSHAKE_TTL_SECS |
120 |
Login handshake lifetime (seconds) |
LDGR_DEFAULT_QUOTA_BYTES |
1073741824 |
Default per-user storage quota (1 GiB) |
LDGR_SERVER_NAME |
ldgr-server |
Cosmetic server name advertised by the discovery endpoint |
New to sync? The default registration policy is
invite-only— for a personal instance you'll usually wantLDGR_REGISTRATION=open. See the full walkthrough below.
API endpoints: Register, login (SRP-6a), vault management, encrypted batch and snapshot CRUD, device registration, and key exchange relay. See the architecture doc for details.
Setting it up across devices? The Cross-Client Sync Setup guide is the authoritative, step-by-step walkthrough — deploy the server, create an account, register each device (CLI / iOS / macOS / web), sync a transaction between two clients, review conflicts, and understand the threat model.
ldgr is a monorepo with a shared Rust core and platform-specific frontends:
crates/ldgr-core/ Shared Rust library (crypto, accounting, storage, sync)
crates/ldgr-cli/ CLI binary (clap + ratatui TUI)
crates/ldgr-server/ Sync server (Axum, AGPL-3.0 licensed)
bindings/swift/ UniFFI-generated Swift bindings
apps/ios/ SwiftUI app
apps/web/ Next.js + WASM app
Key design constraint: ldgr-core has zero I/O dependencies. No networking, no file system access, no platform APIs. All I/O happens in platform-specific code. This keeps the core testable, deterministic, and compilable to WASM.
Password → Argon2id → Master Key → HKDF → Master Encryption Key → wraps Vault Key → wraps Item Keys
→ Recovery Key (alternate path to Vault Key)
All key types implement Zeroize and ZeroizeOnDrop. Debug implementations redact secret values.
The vault uses a custom binary format (LDGR magic bytes) with:
- Argon2id KDF parameters in the header (upgradeable on password change)
- Vault key wrapped by both the password-derived MEK and the recovery key
- Per-item envelope encryption with size-bucket padding (512 B / 2 KB / 8 KB / 32 KB)
- Domain-separated AAD tags for each wrapping operation
# Prerequisites: Rust 1.85+
cargo build --workspace
# Run tests
cargo test --workspace
# Run clippy
cargo clippy --workspace --all-targets
# Check formatting
cargo fmt --checkSee CONTRIBUTING.md for development workflow details.
- How is my data protected? — plain-language overview of ldgr's encryption, no technical background needed
- Market Data — providers, caching, privacy, proxy configuration, and self-hosting
- Architecture & Roadmap — full system design, ADRs, data model
- Roadmap — phased development plan
- ADRs — architecture decision records, including ADR-008: Self-Hosting + Two-Secret Account Auth
All components are licensed under Apache-2.0, except the sync server (crates/ldgr-server/) which is licensed under AGPL-3.0.
See ADR-006 for the licensing rationale.
Contributions are welcome! Please read CONTRIBUTING.md before submitting a pull request. All contributions require a DCO (Developer Certificate of Origin) sign-off.