KassenKnoten

KassenKnoten

Two incomes, one household, one clear plan.

The household planner for people who share costs unevenly — and want the maths to be right. Rent by income, electricity down the middle, the gym only one of you uses. Every item gets its own rule, and both of you see what it costs while you type.

Exact to the cent — no float ever touches money Linux, macOS, Windows — amd64 and arm64 No telemetry, no external calls MIT licence
The overview: income, fixed costs, variable costs, savings rate and free cash as headline figures, with the per-person breakdown and costs by category beneath.
The current month at a glance — and any other month, because every entry knows which months it applies to.

The problem

Every tool gets this half right.

Splitting apps assume one rule for everything. Spreadsheets can express it — until one cell slips and the formula is quietly wrong for months. KassenKnoten makes the split a deliberate choice per item, computes it to the cent, and shows you the result while you type.

Every cost its own rule

Fixed quota or proportional to income — chosen per item, never assumed. The household default only pre-fills the form.

Exact to the cent

Amounts are whole cents, shares are basis points. 39,99 € down the middle becomes 20,00 € and 19,99 € — never 39,98 €. The calculation engine is a pure, tested module.

The past stays honest

Every entry carries the months it applies to. A raise in September leaves August reporting August — and the app asks whether a change is new from now on or was always true.

Variable costs, two ways

Groceries, fuel, going out: either the plan counts — or every receipt with its date, and the plan becomes the budget you measure against.

Scan a receipt

Photograph it and the total, the date and the shop are filled in. Read on your own server — the photo is never uploaded anywhere and never stored.

Month by month

Twelve months in one chart. Drag along it and each month's income, costs, savings and free cash follow — no navigation, no page load.

Every rhythm normalised

A 132,00 € yearly insurance sits next to everything else as 11,00 € a month. Nobody divides by twelve in their head again.

Save on purpose

Savings pots with a monthly rate, a balance and an optional target — owned by one person or by the household.

Calm warnings

Negative free cash, a savings rate above income, an overspent budget — as a calm banner, not a modal. Deleting offers “Undo” instead of asking “are you sure?”.

Sign in your way

A household password with an optional one-time code, Authentik or any other OpenID Connect provider — or both, switched in the settings.

Installable as an app

Own icon on the home screen, opens without an address bar. Browsers that cannot install a web app are told where the option lives in their own menu.

English or German

Chosen when you set up and changed whenever you like. The language belongs to the household, not to a browser — the tablet in the kitchen speaks the same one as the laptop.

Extend it with your own code

Upload an extension and it contributes cards to the overview. It runs on your server with full access — install only what you have read, or what comes from someone you trust.

Backed up every day

One SQLite file on a volume you control. The server keeps the last fourteen states of your data beside it — download one, restore it in one step.

Shared fixed costs

Who pays what — and why.

Every shared item shows both shares in euros and percent, and which rule produced them. The split is previewed as you type, by the same function that later saves it — a preview that calculates a second way is a preview that can disagree with the result.

  • Fixed quota or by income, decided per item
  • Largest remainder: the shares always add back up to the total
  • Private and shared kept cleanly apart
Shared fixed costs: every row shows both people's shares in euros and percent, and the split rule that produced them.

Month by month

The past, one drag away.

Every entry knows the months it applies to, so every month can be shown as it really was. The overview draws the last twelve — drag along the chart, or use the arrow keys, and that month's figures follow.

  • Income, fixed and variable costs, savings rate and free cash
  • A raise in April changes April on, not the months before
  • Every figure also listed underneath, for reading without a chart
The month-by-month chart: five lines over twelve months, with the selected month's figures above it.

Variable costs

A plan, or every receipt.

Anything that is not the same every month gets a budget — and you decide per budget how it is kept. Plan counts the figure you set and asks nothing more. Detailed counts what was actually booked, every expense with its date.

  • The mode is a badge on the card, not a setting in a dialog
  • Booked against planned, always side by side
  • Split between you exactly like a fixed cost
Variable costs: a shared budget in detailed mode with a progress bar, both people's shares, and the individual expenses with their dates.

Receipts

Photograph it. Check it. Save.

Scan receipt opens the camera, and the total, the date and the shop come back filled in. The one question left is which budget it belongs to — the budget carries the split, so that stays your decision.

  • Read by an OCR engine inside the container, in German and English
  • No key, no cloud service; the photo is dropped once it is read
  • What it could not read stays empty and says so
A scanned receipt on a phone: amount, date and shop filled in, the budget chosen, ready to save.

Savings

Saving you can see.

Pots with a rate, a balance and an optional target. Progress is a bar rather than mental arithmetic — and when a pot passes its target, the overview says so kindly.

Savings pots with a monthly rate, current balance, target and a progress bar.

Settings

Everything else, sorted.

Language and appearance, the default split and categories, sign-in, backups and extensions — each on its own page, all listed on one overview. On a wide screen the list stays beside you.

  • Switch sign-in methods and keep the allowlist without a restart
  • Download any of the daily backups, restore in one step
  • Settings that could lock you out refuse to
Settings on a wide screen: the categories beside the sign-in page, with the household password and Authentik switched on and the allowlist below.

On a phone

Designed at 375 px, not shrunk to it.

A household plan gets opened in the kitchen, not at a desk. Same features, navigation within thumb reach, an amount field that brings up the number pad, and a design that follows the system theme.

  • Light and dark, following the system
  • Installable as an app, with its own icon
  • Offline it says so, instead of showing yesterday's figures
The overview on a phone in dark mode. The sign-in screen: a button for the identity provider, and the household password with the field for the one-time code.

Security

They are your figures. They are treated that way.

Password and a second factor

One household password, hashed with argon2id and read from the environment — a copy of the database is not a copy of the password. Optionally a code from any authenticator app (TOTP, RFC 6238). A code that has been used is refused.

Your identity provider

OpenID Connect with PKCE, the ID token checked against the provider's keys, and only addresses on an allowlist kept in the app get in. Taking someone off the list ends their session on the next click.

Encrypted session

The session cookie is encrypted, not merely signed (JWE, A256GCM), httpOnly, SameSite=Lax and Secure over HTTPS.

Everything closed but what must be open

Every route but the login and the health check requires a session — deny by default, so a new page cannot accidentally be public. Server actions check again themselves.

Nothing from outside

A Content-Security-Policy with a per-request nonce, self-hosted fonts, no CDNs, no telemetry. The container runs as an unprivileged user.

Checked before it ships

No image is published before the tests pass and a real browser has signed in, set up a household and checked a split on the phone layout — against the very server the image runs.

Self-hosting

Docker and two minutes.

No checkout, no Node, no build. The image is published for amd64 and arm64 and runs wherever Docker runs Linux containers: a Linux server or NAS, a Raspberry Pi 4 or 5 with a 64-bit OS, a Mac, or Windows with Docker Desktop. Both images are started and tested in a browser before every release. Download two files, generate the secrets — the scripts for that run inside the image — and start.

Linux and macOS

mkdir kassenknoten && cd kassenknoten
curl -LO https://github.com/jankln/KassenKnoten/releases/latest/download/docker-compose.yml
curl -L -o .env https://github.com/jankln/KassenKnoten/releases/latest/download/env.example

# a session secret, appended to .env
docker run --rm ghcr.io/jankln/kassenknoten:latest \
  node scripts/session-secret.ts >> .env

# your household password, hashed — paste the printed line into .env
docker run -it --rm ghcr.io/jankln/kassenknoten:latest \
  node scripts/hash-password.ts

# optional: a second factor, prints a QR code into your terminal
docker run -it --rm ghcr.io/jankln/kassenknoten:latest \
  node scripts/totp-secret.ts

# optional: sign in with Authentik — fill in the OIDC_* lines of .env

docker compose up -d

Windows · PowerShell

mkdir kassenknoten; cd kassenknoten
curl.exe -LO https://github.com/jankln/KassenKnoten/releases/latest/download/docker-compose.yml
curl.exe -L -o .env https://github.com/jankln/KassenKnoten/releases/latest/download/env.example

# a session secret, appended to .env
docker run --rm ghcr.io/jankln/kassenknoten:latest `
  node scripts/session-secret.ts | Add-Content .env

# your household password, hashed — paste the printed line into .env (notepad .env)
docker run -it --rm ghcr.io/jankln/kassenknoten:latest `
  node scripts/hash-password.ts

docker compose up -d

Windows needs Docker Desktop with WSL 2. Then open http://127.0.0.1:3000 — a three-step wizard sets up the household. For access from outside the machine, put a reverse proxy with TLS in front.