Files
LibreLedger/README.md
T
LibrePortal 0759c5dbfd License LibreLedger under AGPL-3.0-or-later
The README now says AGPL-3.0-or-later, and the image label changes from
AGPL-3.0-only to match. The published 1.0.0 image keeps the old label; the
new one ships with the next image build (1.0.1). The vendored noble
libraries stay MIT.

Assisted-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 01:49:27 +01:00

6.5 KiB

LibreLedger

An encrypted personal money ledger: your data is encrypted in your browser; the server never sees it.

One screen, one continuous running balance:

  • Balances at the top: your accounts and their balances, with a grand total. That total is where the ledger opens.
  • Monthly ledger below: one block per month, a table of Date · Type · Description · In · Out · Balance. The balance runs on from your account balances and carries from each month into the next.
    • Type comes from the row: money in shows a green Money badge, money out an amber Cost badge.
    • Each row has a category (emoji and colour) for tagging entries at a glance.
    • Insert or remove rows anywhere, and add or remove whole months.
  • Recurring bills and income (rent, phone, salary…), each with an amount, a category and a schedule:
    • Monthly on a chosen day, every N weeks from an anchor date, or yearly on a month and day.
    • 🔁 Add recurring drops every matching occurrence into a month as real, editable rows; + Add month moves to the next calendar month and pre-fills it.
  • Totals, Savings and Housing affordability summaries, several budgets in one ledger, CSV and PDF export.
  • Dark / light theme, dark by default.

Zero-knowledge encryption

  • Everything is encrypted in the browser with AES-256-GCM. The key is derived from your passphrase with PBKDF2-HMAC-SHA256, 250,000 iterations and a random 16-byte salt; each save uses a fresh random 12-byte IV.
  • The server (server.py) only stores and returns the resulting blob, { "v": 1, "salt": …, "iv": …, "ct": … } (base64). It never receives the passphrase, the key or any figure from the ledger.
  • There is no account and no recovery. Lose the passphrase and the data is gone; that is the point.
  • The page loads nothing from anywhere else. The server sends a strict Content-Security-Policy ('self' only), nosniff, frame denial and no-referrer, and serves only the app's own files.

Plain http on your LAN or VPN

Browsers only give pages their built-in encryption (Web Crypto) in a secure context: HTTPS or http://localhost. Opened as http://192.168.x.x:port or over a VPN address, LibreLedger switches to its built-in fallback: the same PBKDF2-SHA256 and AES-256-GCM from the audited @noble/hashes and @noble/ciphers libraries, vendored and pinned in vendor/. The files it writes are identical in format, so a ledger saved one way opens the other way.

The lock screen says when the fallback is in use. Unlocking takes a few seconds longer, and the key sits in page memory rather than in the browser's key store, so prefer HTTPS whenever you have it. Plain http also means anyone on the network path can see (encrypted) traffic and could tamper with the page itself, so only use it on a network you trust.

Self-hosting

Docker

docker run -d --name libreledger -p 127.0.0.1:8080:8080 \
  -v libreledger-data:/data \
  git.libreportal.org/libreportal/libreledger:1.0.0
# open http://localhost:8080

Or build it yourself: docker build -t libreledger .

The image runs server.py on port 8080 as an unprivileged user, keeps its data in the /data volume and has a health check on /api/health. Put HTTPS in front of it (Caddy, Traefik, nginx…) when it is reachable from anywhere but your own machine.

Without Docker

python3 server.py                          # http://127.0.0.1:8080, data in ./data
python3 server.py --host 0.0.0.0 --port 8080 --data-dir /srv/libreledger

Python 3.9 or newer, standard library only.

Option Environment Default
--host LIBRELEDGER_HOST 127.0.0.1
--port LIBRELEDGER_PORT 8080
--data-dir LIBRELEDGER_DATA_DIR ./data
--backup-interval LIBRELEDGER_BACKUP_INTERVAL 600 seconds (0: every save)
--backup-keep-recent LIBRELEDGER_BACKUP_KEEP_RECENT 10
--backup-keep-daily LIBRELEDGER_BACKUP_KEEP_DAILY 30

Your data

  • <data-dir>/ledger.enc is the ledger, written atomically on every change.
  • <data-dir>/backups/ledger-<UTC time>.enc are earlier versions: the newest 10, plus the last one of each of the most recent 30 days. At most one is taken per backup interval.
  • All of them are encrypted with the passphrase that was current when they were written. To restore one, stop the server and copy it over ledger.enc, or use Restore in the app.
  • To move a ledger to another server, copy ledger.enc into its data folder and unlock it with your passphrase. Files from the earlier "Money Ledger" version of this app work as they are.

The server has no login of its own: anyone who can reach it can fetch the encrypted file or overwrite it (the backups cover the latter). Keep it on a private network, a VPN, or behind an authenticating proxy.

Install on LibrePortal

LibreLedger is in the LibrePortal app catalog: libreportal app install libreledger, or Apps → LibreLedger in the WebUI. It installs private (reachable only from your trusted places), with its data included in LibrePortal's backups, and on https://ledger.<your domain> when a domain is set up.

In the app

  • 💾 Saved to disk: the encrypted ledger is saving to the server.
  • 🔗 Link file (Chrome/Edge desktop, when opened without the server): auto-save to an encrypted file you choose.
  • Export CSV / PDF: plaintext exports of the decrypted data. Handle them with care.
  • Backup / Restore: download or load an encrypted copy.
  • Change passphrase: re-encrypts with a new passphrase. Older backups keep the old one.
  • Lock: forgets the key and the data; the passphrase is needed again.

Development

python3 tests/test_server.py                 # server: allowlist, headers, checks, backups
node tests/crypto-interop.test.mjs           # Web Crypto <-> fallback interop (Node 22.12+)

Files: index.html (markup and lock screen), styles.css, app.js (state, rendering and the crypto layer), theme.js, crypto-fallback.js, server.py, banks/ (logos, see banks/NOTICE.txt), vendor/.

Licence

GNU AGPL-3.0-or-later: version 3 of the GNU Affero General Public License, or (at your option) any later version. The vendored noble libraries are MIT-licensed (their LICENSE files are next to them); bank logos are trademarks of their owners (banks/NOTICE.txt).