573c1ec7dff8f046c0e35450acf62453dfb8a710
After the Harbor push, install cosign and sign each image's digest with a key stored in COSIGN_PRIVATE_KEY / COSIGN_PASSWORD secrets. Cosign resolves the SHA tag to the underlying digest, so a single signature covers every tag (version, sha, latest) pointing at the same image. Harbor looks up signatures by digest and will display "signed" status once the signature artifact lands alongside the image. Cosign is curl-installed at v2.4.1 and uses the existing docker login for registry auth — no extra credentials needed beyond the cosign key. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TehRiehlBudget
A self-hosted personal finance application for tracking spending, balances, and net worth across all of your accounts — checking, savings, credit, loans, and investments — with manual transaction entry, receipt uploads, and a conversational AI advisor running entirely on your own hardware.
Deployed at budget.tehriehldeal.com.
Features
- Accounts — track checking, savings, cash, credit cards, loans, stocks, brokerage, and retirement accounts. Drag-to-reorder, per-account history, and a balance integrity check that reverses counter-party transfers when an account is deleted.
- Transactions — record income, expenses, and transfers between accounts. Filter by account, category, type, and date. Attach receipt images. Edit, delete, and bulk-export to CSV.
- Activity history — every create / update / delete is recorded with a snapshot, so deleted transactions and accounts remain auditable for as long as you need them. Filter by entity, action, account, or date range.
- CSV export — pull a date-ranged transaction set out as CSV from the global list or scoped to a single account, with quick-range presets (last 30/60/90 days, this year, all time) plus custom start/end pickers.
- Confirmation guards — every destructive action prompts before running, with light/dark mode parity.
- AI advisor — a "financial buddy" that summarizes your month against last month and answers follow-ups. Runs against a local Ollama model — no transaction data leaves your network.
- Dashboards — net worth and total debt at a glance, spending-by-category pie, monthly cash flow bar chart, and per-account balance history with overlaid valuations.
- Receipts — store receipt images on your own filesystem, never on a third-party blob store.
- Field-level encryption — sensitive columns (account numbers, transaction notes) are AES-256-GCM encrypted at rest.
Tech Stack
- Frontend — React 19 + TypeScript, Vite, TailwindCSS + ShadCN UI, Zustand, Recharts, React Router
- Backend — NestJS (TypeScript), REST, Prisma ORM
- Database — PostgreSQL (Dockerized for local dev)
- Auth — Supabase (email/password + OAuth, JWT-validated on the backend)
- AI — Ollama (self-hosted; defaults to
llama3) - File storage — local filesystem on the self-hosted server
- Package manager — pnpm workspaces
Project Structure
tehriehlbudget/
├── tehriehlbudget-frontend/ # React + Vite app
├── tehriehlbudget-backend/ # NestJS API + Prisma schema
├── docker-compose.yml # Postgres for local dev
├── pnpm-workspace.yaml
└── package.json # Workspace-level scripts
Prerequisites
- Node.js 20+
- pnpm 9+
- Docker (for local Postgres)
- A Supabase project (for auth) — free tier is fine
- An Ollama server reachable from the backend (local or LAN) for the AI advisor
Quick Start
# Clone and install
git clone <your-fork-url>
cd tehriehlbudget
pnpm install
# Start local Postgres
docker-compose up -d
# Configure the backend
cp tehriehlbudget-backend/.env.example tehriehlbudget-backend/.env
# Edit .env to set DATABASE_URL, SUPABASE_*, ENCRYPTION_KEY, OLLAMA_URL, OLLAMA_MODEL
# Apply database migrations
pnpm --filter tehriehlbudget-backend prisma migrate deploy
pnpm --filter tehriehlbudget-backend prisma generate
# Configure the frontend
cp tehriehlbudget-frontend/.env.example tehriehlbudget-frontend/.env
# Edit .env to set VITE_API_URL and VITE_SUPABASE_*
# Run both servers (in separate terminals)
pnpm dev:backend
pnpm dev:frontend
The frontend is served at http://localhost:5173 and the API at http://localhost:3000.
Required environment variables
Backend (tehriehlbudget-backend/.env)
| Variable | Description |
|---|---|
DATABASE_URL |
Postgres connection string |
SUPABASE_URL |
Your Supabase project URL |
SUPABASE_SERVICE_ROLE_KEY |
Service-role key (used to validate JWTs) |
ENCRYPTION_KEY |
32-byte hex key for AES-256-GCM field encryption |
OLLAMA_URL |
URL of your Ollama server (e.g. http://localhost:11434) |
OLLAMA_MODEL |
Ollama model id (e.g. llama3.2:latest) |
Frontend (tehriehlbudget-frontend/.env)
| Variable | Description |
|---|---|
VITE_API_URL |
Backend URL (e.g. http://localhost:3000) |
VITE_SUPABASE_URL |
Supabase project URL |
VITE_SUPABASE_ANON_KEY |
Supabase anon (publishable) key |
Common Commands
# Run dev servers
pnpm dev:frontend
pnpm dev:backend
# Build
pnpm build:frontend
pnpm build:backend
# Tests
pnpm test # both packages
pnpm test:backend # Jest
pnpm test:frontend # Vitest
pnpm test:coverage # both, with coverage
# Lint & format
pnpm lint
pnpm format
# Prisma
pnpm --filter tehriehlbudget-backend prisma migrate dev
pnpm --filter tehriehlbudget-backend prisma studio
Architecture Notes
- Field-level encryption. A NestJS interceptor encrypts sensitive columns on write and decrypts on read using AES-256-GCM. The plaintext only ever exists in memory inside the request handler.
- Receipt storage. Images are stored on the server's local filesystem. The backend issues access-controlled URLs for upload and retrieval — there is no S3 / external blob store.
- Balance integrity on delete. When an account is deleted, related transfers' counter-party balances are reversed inside the same Prisma
$transactionbefore the cascade fires, so a surviving account never reflects a transfer that no longer exists. - Audit log.
ActivityLogrows capture create / update / delete actions for transactions, accounts, and account valuations. For account deletions the per-transaction snapshots are written before the cascade so the trail outlives the data. - AI advisor. The advisor builds a per-request snapshot of standing balances, monthly flow numbers, and top spending categories, strips PII, and sends it to Ollama. The prompt explicitly separates point-in-time balances from monthly flow and instructs the model to never invent equations or derive new figures — your transaction data does not leave your network.
- Auth. Supabase issues JWTs; protected routes on both ends validate them. Sessions are persisted client-side via Supabase's SDK.
License
Languages
TypeScript
97.4%
CSS
1.1%
Dockerfile
0.7%
JavaScript
0.6%
HTML
0.1%