- Python 99.9%
- Dockerfile 0.1%
- Kategorie-Dropdown in Monatsdetail öffnet kein Detail-Popup mehr (click.stop) - CSV-Import ergänzt 'date' in tx_dict und re-evaluiert Filter nach dem Import - Wöchentliche Verträge: Buchungen werden anzahl- statt datumsbasiert zugeordnet - find_category_for_transaction scoped Filtergruppen über user_id (Multi-User) - Monatsübersicht/Dashboard: Connection-Reuse + Pot-Cache (Memoization) statt N+1-Zeitreise-Simulation pro Monat; O(M) statt O(M²) für Spartopf-Aggregation - Spartopf-Erstelldialog nutzt Icon-Picker statt Textfeld - Button "Filter rückwirkend anwenden" für Kategorien |
||
|---|---|---|
| data | ||
| docs/images | ||
| src | ||
| tests | ||
| .dockerignore | ||
| .gitignore | ||
| .gitlab-ci.yml | ||
| CLAUDE.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| main.py | ||
| pytest.ini | ||
| README.md | ||
| requirements-dev.txt | ||
| requirements.txt | ||
💰 FinanceUs
Persönliche Finanzverwaltung als Web-App — Einnahmen, Ausgaben, Abos, Spartöpfe und wiederkehrende Buchungen über mehrere Bankkonten hinweg.
Gebaut mit Python · NiceGUI (FastAPI + Vue.js) · SQLite · Pandas — UI auf Deutsch
Inhalt
Features
- 📊 Dashboard — KPIs und interaktive Charts (ECharts) zu Einnahmen, Ausgaben und Salden
- 🗓️ Monatsübersicht — Monatskarten mit Zusammenfassung, Drill-down in einzelne Transaktionen
- 🏦 Mehrere Konten — getrennte Verwaltung beliebig vieler Bankkonten
- 🏷️ Kategorien & Auto-Kategorisierung — regelbasierte Zuordnung über DNF-Filter (ODER von UNDs)
- 🔁 Wiederkehrende Buchungen — Verträge/Abos mit Soll-/Ist-Abgleich pro Periode
- 🐷 Spartöpfe — virtuelle Töpfe mit regelbasierten monatlichen Zuweisungen und Ledger-Historie
- 📥 CSV-Import — Volksbank-Format inkl. konfigurierbarer Import-Profile und Import-Verlauf
- 🔎 Globale Suche — über Transaktionen, Verträge, Spartöpfe und Kategorien, mit Bulk-Aktionen
- 👥 Mehrbenutzer — Admin-Panel mit Rollen (
admin/user) - 🔐 OIDC / SSO (optional) — Login via Authentik, Keycloak & Co. mit PKCE und JIT-Provisioning
- 🌗 Dark / Light Mode — persistent pro Nutzer
Screenshots
Dashboard
Monatsansicht
Kategorien
Schnellstart
Variante A — Docker Compose (empfohlen)
# Session-Secret erzeugen und in docker-compose.yml eintragen
openssl rand -base64 48
# Datenverzeichnis anlegen und für den Container-User schreibbar machen (siehe unten)
mkdir -p financeus_data
sudo chown -R 10001:10001 financeus_data
docker compose up -d
App läuft anschließend auf http://localhost:8080.
Datenbank, Secrets und Session-Storage werden über Volumes (./financeus_data, financeus_nicegui) persistiert.
Volume-Berechtigungen (wichtig)
Der Container läuft aus Sicherheitsgründen als Non-root-User (feste UID:GID = 10001:10001, im Dockerfile gepinnt). Ein per Bind-Mount eingebundenes Host-Verzeichnis (./financeus_data) gehört beim ersten Start aber root — der Container-User darf dann nicht hineinschreiben, und der Start bricht mit sqlite3.OperationalError: unable to open database file ab.
Deshalb muss das Datenverzeichnis dem Container-User gehören:
mkdir -p financeus_data
sudo chown -R 10001:10001 financeus_data
Die Datenbank (finanzus.db) wird danach beim ersten Start automatisch von SQLite angelegt — sie muss nicht manuell erstellt werden.
Alternative ohne
chown: Statt des Bind-Mounts ein Named Volume verwenden (financeus_data:/app/dataindocker-compose.yml). Docker übernimmt dann beim Anlegen automatisch die korrekte Ownership aus dem Image. Nachteil: kein direkter Host-Pfad-Zugriff auf die Daten (Backups viadocker cp/docker run -v).
Variante B — Lokal mit Python 3.14
pip install -r requirements.txt
python main.py
App läuft auf http://localhost:8080.
Standard-Login
| Benutzer | Passwort |
|---|---|
admin |
admin123 |
⚠️ Nach dem ersten Login das Admin-Passwort ändern (Admin-Panel unter
/admin).
Konfiguration
Umgebungsvariablen
| Variable | Pflicht | Zweck |
|---|---|---|
FINANCEUS_STORAGE_SECRET |
empfohlen | Secret für die Verschlüsselung des NiceGUI-Session-Storage. Fehlt es, wird ein zufälliges Secret in data/.storage_secret erzeugt — ohne stabiles Secret gehen Sessions bei jedem Neustart verloren. Generieren mit openssl rand -base64 48. |
FINANCEUS_PUBLIC_URL |
bei Reverse-Proxy | Öffentlich erreichbare Basis-URL der App (ohne trailing Slash), z.B. https://finance.deine-domain. Wird für die korrekte OIDC-Callback-URI benötigt — ohne diese Variable leitet die App die Callback-URI aus dem internen Request ab, was hinter einem Reverse-Proxy falsch sein kann. |
OIDC / SSO (optional)
Vollständig über die Admin-Einstellungsseite (/settings, nur Admin) konfigurierbar — kein Code- oder Env-Eingriff nötig:
- Issuer, Client-ID/Secret, Scopes, Admin-Gruppe
- Anzeigename des Providers — erscheint auf der Login-Seite als „Anmelden mit <Name>"
- Das Client-Secret wird Fernet-verschlüsselt in der DB gespeichert
- Vollständiger Authorization-Code-Flow mit PKCE, JIT-User-Provisioning und Backchannel-Logout
- Lokales Login deaktivieren — optionaler Schalter, der die Login-Seite auf den OIDC-Button reduziert; Notfall-Backdoor für Admins bleibt über
/login?local=1erreichbar
Die Redirect URI für den OIDC-Provider wird in der Einstellungsseite angezeigt und automatisch aus FINANCEUS_PUBLIC_URL (oder localhost:8080 als Fallback) zusammengesetzt. Format: <FINANCEUS_PUBLIC_URL>/auth/oidc/callback.
Es gibt keinen öffentlichen Registrierungsflow — Nutzer werden ausschließlich vom Admin oder via OIDC angelegt.
Architektur
Stack: NiceGUI 3.12.1 (FastAPI + Vue.js) · SQLite3 · Pandas · Authlib (OIDC) · Cryptography (Fernet)
Kernkonzepte
- SPA-Navigation — alle Seiten laufen unter
@ui.page('/')inpages/spa.py. Sidebar-Klicks rufennavigate(view, **params)auf: kein Browser-Reload, kein Flash; die URL wird viahistory.pushStateaktualisiert. Alle übrigen Routen leiten auf/um. - Content-Renderer — jede Seite stellt eine
render_X_content(user_id, navigate_fn=None)-Funktion bereit. - Auth-Guard —
check_auth()(core/auth.py) schützt die SPA-Shell;user_idliegt inapp.storage.user. - Refreshable UI —
@ui.refreshable-Komponenten rendern bei Datenänderungen neu. - DB-Zugriff — direkte
sqlite3-Verbindungen indatabase/, kein ORM. - Auto-Kategorisierung — DNF-Matching: Filtergruppen werden ge-OR-t, Bedingungen innerhalb einer Gruppe ge-AND-et (
database/automations.py).
Datenbank-Schema (Auszug)
- Kern:
users,accounts,categories,category_types,transactions,contracts - Filter:
filter_groups+filter_conditions(DNF-Regeln) - Spartöpfe:
virtual_pots+pot_ledger+pot_rules - OIDC:
app_settings,oidc_sessions
Projektstruktur
main.py → Thin Launcher: fügt src/ zum sys.path, importiert src/app.py
src/
app.py → Einstiegspunkt (NiceGUI-Config, DB-Init, Page-Imports, ui.run())
pages/ → SPA-Shell + Content-Renderer (eine Datei pro View)
spa.py → @ui.page('/') — einziger echter Einstiegspunkt
ui/ → Layout, wiederverwendbare Komponenten, Dialoge
components/
dialogs/ → dedizierte Dialog-Komponenten
core/ → Auth-Guard, CSV-Import, OIDC, Secret-Store, Konstanten
database/ → alle SQL-Queries und Schema (SQLite via sqlite3)
data/ → finanzus.db + Beispiel-CSVs (Laufzeitdaten, außerhalb src/)
tests/ → pytest-Suite (Import-Smoke, DB, Auth, Constants)
Dockerfile → Python 3.14-slim, Non-root, Healthcheck
docker-compose.yml → Service-Definition mit persistenten Volumes
.gitlab-ci.yml → CI/CD-Pipeline
Tests & CI/CD
Tests lokal ausführen
pip install -r requirements-dev.txt
pytest -q
Abgedeckt:
- Import-Smoke-Test — importiert jedes Modul unter
src/und fängt Syntax-/Import-Fehler ab - DB-Schema — idempotentes
init_db(), Kern-Tabellen vorhanden - Auth — PBKDF2-Passwort-Hashing (salted, timing-safe), User-CRUD,
verify_user() - Constants —
format_month_de(),get_chart_colors()
GitLab-Pipeline (.gitlab-ci.yml)
| Stage | Job | Was passiert |
|---|---|---|
lint |
compile |
Byte-Kompilierung aller Quellen (compileall) |
test |
pytest |
Installiert Dependencies, führt die Test-Suite aus (inkl. Import-Smoke-Test der gesamten App) |
build |
build-image |
Baut das Docker-Image mit Kaniko (daemonlos, kein dind). Push in die Container Registry nur auf Default-Branch / Tags (→ :latest bzw. Tag-Name); auf Feature-Branches nur Build-Validierung (--no-push) |
Runner-Voraussetzung: docker-Executor. Kein privileged nötig — Kaniko baut im Userspace ohne Docker-Daemon. Die Registry-Credentials liest Kaniko automatisch aus den eingebauten CI-Variablen ($CI_REGISTRY*).
Theming (Dark/Light Mode)
Das Theme-System basiert auf Tailwind CSS v4 (NiceGUI 3.12.1) mit einem statisch injizierten <style>-Block in ui/layout.py::_DARK_CSS, der !important-Regeln gegen die Quasar-/Tailwind-Layer durchsetzt. Die Präferenz liegt in app.storage.user['dark_mode'] (Default True); der Toggle löst bewusst einen window.location.reload() aus, damit Chart-Farben korrekt neu rendern.
Details und „goldene Regeln“ für Karten-Hintergründe stehen in
CLAUDE.md.


