A single-owner, self-hosted evidence ledger for work locations, business travel, expenses, receipts, tax-relevant facts, and employer reimbursement.
Important
Experimental self-hosted software. WorkLedger preserves evidence and records deterministic derivations; it does not provide tax, legal, accounting, or employment advice. Review exported facts and amounts against the rules and evidence applicable to the relevant person and filing period.
Start locally · Read the architecture · Download the latest release
WorkLedger targets one owner on one trusted host, accessed through a mobile or desktop browser. It combines fast daily entry with an audit-oriented storage model:
- one-tap work-from-home records and structured journey, expense, receipt, and external-activity flows;
- append-only event revisions linked by a globally checked SHA-256 audit chain;
- PostgreSQL constraints that guard revision immutability, event identity, and audit-link continuity;
- original-preserving receipt storage with derived previews and thumbnails;
- distinct tax-relevance and employer-reimbursement tracks;
- explicit incomplete, unresolved, derived, confirmed, and overridden states;
- deterministic CSV, JSON, XLSX, SQLite, ZIP, and employer-package exports;
- checksum-verified database-and-file backups with a gated restore path; and
- optional Deutsche Bahn timetable and OpenRouteService lookups with manual fallbacks.
The supported deployment path uses Docker with Compose v2. Allow approximately 2 GiB of free space before uploaded evidence and backups.
macOS or Linux:
./start.shWindows PowerShell:
./start.ps1The start script creates a project-local .env with randomly generated
application and database secrets when needed. Open http://127.0.0.1:8787,
create the owner PIN, and configure residences, employers, workplaces, and
optional rail passes.
Common operations:
./start.sh
./stop.sh
docker compose ps
docker compose logs --tail=200 web worker postgres redis./stop.sh --volumes removes the database volume. Create and test a backup
before using that option.
flowchart TD
B[Mobile or desktop browser] --> W[Django web application]
W --> P[(PostgreSQL)]
W --> F[Evidence files]
W --> R[(Redis)]
R --> C[Celery worker and scheduler]
C --> P
C --> F
P --> X[Deterministic exports and backups]
F --> X
The application is a server-rendered Django monolith. Domain boundaries live
under apps/: accounts, ledger, travel, evidence, expenses, taxes, and
exports. Redis carries task-queue work; PostgreSQL stores the ledger and audit
state; evidence files retain original uploads. The normal Compose configuration
binds the web service to 127.0.0.1.
Each event has a stable identity and an ordered revision history. Editing adds a revision rather than replacing the previous representation. Database guards reject deletion, mutation of protected revision fields, broken predecessor links, and event-identity changes. Exports include normalized records and hashes so a reviewer can trace a derived amount back to its evidence and revision.
These controls provide application-level auditability. Host administrators and database owners remain inside the trust boundary, and backup integrity still depends on tested restore procedures and protected media.
WorkLedger data can contain personal, employment, location, tax, and financial information. Use a dedicated host or account, a long owner PIN, encrypted storage where appropriate, and exact allowed-host/CSRF settings. The default service is loopback-only.
compose.review.yaml can expose a temporary trusted-LAN review port without
TLS. For remote access, keep the application behind an authenticated private
network or a correctly configured TLS reverse proxy. The project has not
received an external security audit.
Runtime data belongs outside version control:
.env
workledger-data/
workledger-backups/
*.db *.sqlite *.zip *.xlsx *.csv *.pdf
Live train choices use project-local credentials:
DB_TIMETABLES_CLIENT_ID=
DB_TIMETABLES_API_KEY=
Road-distance candidates use:
OPENROUTESERVICE_API_KEY=
Provider output enters the ledger as an unconfirmed fact until reviewed. Manual train entry and manually confirmed distance remain available without provider access.
Create a database-and-file backup with a SHA-256 manifest:
./backup.sh
./backup.sh /mounted/offline-disk/workledgerRestore is explicit and verifies the manifest before replacement:
./restore.sh --yes /path/to/workledger-YYYYMMDDTHHMMSSZThe previous evidence directory is retained as a timestamped sibling. Validate the procedure on disposable infrastructure and keep at least one encrypted, offline copy.
docs/TAX_RULES_2026.md records the dated German rule
inputs implemented by this release and links each official statutory source.
The ledger stores the rule version used for a derivation so later review can
distinguish contemporaneous facts from subsequent rule changes.
Use test-only infrastructure. Never aim setup helpers, seed commands, or mutating probes at a live personal database.
uv sync --all-groups --frozen
npm ci
npm run build
uv run ruff check .
uv run mypy apps config tests
uv run pytest -q
npm run test:jsPostgreSQL constraint tests require a disposable database:
WORKLEDGER_TEST_DATABASE_URL=postgresql://... \
uv run pytest -q --ds=config.settings.postgres_testGitHub Actions runs the locked Python/SQLite suite, static analysis, JavaScript tests, and shell-syntax checks. Browser end-to-end tests additionally require Playwright.
apps/ Django domain applications and migrations
config/ Django, Celery, ASGI/WSGI, and environment settings
templates/ server-rendered mobile interface
assets/ Tailwind source
static/ built CSS, application JavaScript, and PWA shell
tests/ unit, integration, property, UI, and optional E2E tests
docker/ entrypoint and database-role initialization
docs/ architecture and dated tax-rule provenance
*.sh, *.ps1 start, stop, backup, restore, and scheduling tools
Release metadata is in CITATION.cff. Author:
Maarten Linus Herrmann, ORCID
0009-0005-8721-6588.
WorkLedger is licensed under GPL-3.0-or-later. Vendored Alpine.js and
HTMX distributions and CSS generated with Tailwind retain their own terms; see
THIRD_PARTY_NOTICES.md. The reciprocal license keeps
distributed variants of the application available under the same terms. Other
dependencies remain subject to the licenses recorded by their distributions
and lockfiles.