Skip to content

Repository files navigation

PisoPilot

PisoPilot is a privacy-first, mobile-first personal finance PWA for self-hosted use. It combines double-entry-style ledger mechanics, zero-based budgeting, recurring bill management, goals, reporting, offline entry, imports, and confirm-before-save AI assistance in one installable application.

This repository is published as a portfolio project. It demonstrates production-oriented full-stack engineering across financial data integrity, offline synchronization, responsive product design, container hardening, database migrations, and encrypted operations.

Product capabilities

  • Cash, bank, e-wallet, credit-card, loan, investment, property, and other asset accounts
  • Exact minor-unit money handling with PHP as the default base currency and explicit exchange-rate snapshots
  • Income, expense, refund, transfer, split, pending, cleared, and reconciled transactions
  • Safe transaction editing with linked bill-payment integrity and optimistic concurrency
  • Zero-based monthly envelopes, rollover, ready-to-assign calculations, and card-payment envelopes
  • Optional 50/30/20 planning and actual-spending view with editable ratios, six-month trends, reviewed allocation, and safe guided rebalancing
  • Complete bill management: partial payments, reminders, skip/restore, and recurring-series scopes
  • Recurring-series pause, resume, and end lifecycle while preserving paid history
  • Savings goals, debt payoff planning, and loan, savings, and retirement calculators
  • Spending, cash-flow, net-worth, account, and budget reports
  • CSV preview, mapping, deduplication, plus JSON and CSV exports
  • Receipt upload and vision extraction into an editable proposal
  • Local AI assistance whose suggestions always require explicit user confirmation
  • IndexedDB caching, queued offline mutations, idempotent sync, and expiring offline grants
  • Six persistent visual themes and an installable phone-first PWA experience

PisoPilot deliberately does not include bank credential storage, automatic bill payment, public account registration, autonomous AI writes, or shared-household access.

Engineering highlights

  • Monetary values are stored as integer minor units; floating-point arithmetic is never used for balances.
  • 50/30/20 targets use deterministic largest-remainder rounding, so every cent of planned income is assigned exactly once.
  • Needs, wants, savings, debt minimums, and excess debt payments are classified explicitly; uncertain transfers remain visible as setup warnings rather than being silently guessed.
  • Balances and reports are derived from signed ledger postings rather than mutable cached totals.
  • Bill payments remain linked to ledger transactions, so edits and deletions recalculate bill status safely.
  • Fully paid bill occurrences are immutable; corrections happen through their payment transactions.
  • Recurring edits support occurrence-only and this-and-future scopes while retaining paid or partially paid history.
  • Offline writes use client mutation IDs, durable outbox storage, optimistic versions, and server conflict records.
  • AI output is treated as an untrusted proposal and never performs arithmetic or writes autonomously.
  • Production containers run without root privileges, with dropped capabilities, a read-only filesystem, and loopback-only publishing.

Architecture

flowchart LR
    Phone[Phone / browser PWA] -->|HTTPS over private network| Edge[Tailscale Serve]
    Edge --> API[Fastify API]
    API --> DB[(PostgreSQL)]
    API --> Files[Receipt volume]
    API --> AI[Optional local AI gateway]
    Worker[pg-boss worker] --> DB
    Worker --> Files
    Backup[Encrypted backup job] --> DB
    Backup --> Files
Loading
Area Technology Responsibility
apps/web React, Vite, TanStack Query, Dexie, Wouter PWA interface, local cache, and offline outbox
apps/api Fastify, Drizzle ORM, pg-boss API, validation, ledger rules, sync, and background work
packages/shared TypeScript, Zod Shared contracts and exact money helpers
PostgreSQL PostgreSQL 17 Durable financial and application state
Deployment Docker Compose, Tailscale, systemd, age Private ingress, services, and encrypted backups

Local development

Requirements: Node.js 22+, npm, and PostgreSQL.

cp .env.example .env
# Replace the example database password and generate a random offline-grant secret.
npm ci
npm run db:migrate
npm run db:seed
npm run dev

The development configuration disables Tailscale header enforcement and uses a placeholder local identity. Never reuse development secrets in production.

Quality checks:

npm run typecheck
npm test
npm run lint
npm run build
npm audit --omit=dev

Self-hosted deployment

The included deployment profile targets an unprivileged Debian 12 Proxmox container with Docker, Tailscale, and a host-mounted backup destination. Treat the provided container ID, storage sizes, and network settings as examples and review them for your own host.

On the Proxmox host:

deploy/scripts/provision-lxc.sh

After copying the repository to /opt/pisopilot inside the container:

cd /opt/pisopilot
cp deploy/.env.production.example deploy/.env
# Edit deploy/.env with your identity, timezone, and optional AI endpoint.

install -d -m 0700 /etc/pisopilot/secrets
openssl rand -hex 32 > /etc/pisopilot/secrets/postgres_password
openssl rand -hex 48 > /etc/pisopilot/secrets/offline_grant_secret
printf '%s' 'YOUR_DEDICATED_AI_GATEWAY_KEY' > /etc/pisopilot/secrets/openwebui_api_key
chgrp 10001 /etc/pisopilot/secrets/*
chmod 0640 /etc/pisopilot/secrets/*

deploy/scripts/deploy.sh
tailscale up --hostname=pisopilot
tailscale serve --bg http://127.0.0.1:3000

The API accepts only the exact identity configured by TAILSCALE_OWNER_LOGIN. Docker publishes the application on loopback; Tailscale Serve is the intended production ingress. Do not expose port 3000 directly to an untrusted network because identity is supplied through the trusted proxy header.

AI support is optional. Set AI_ENABLED=false when no local gateway is configured; all other functionality remains available.

Operations and recovery

docker compose -f /opt/pisopilot/deploy/compose.yml ps
docker compose -f /opt/pisopilot/deploy/compose.yml logs -f --tail=200 app worker
/opt/pisopilot/deploy/scripts/deploy.sh
systemctl start pisopilot-backup.service

The systemd timer creates nightly PostgreSQL and receipt archives, verifies checksums, encrypts the archive with age, and applies a 90-day retention policy. Store the matching age private key outside the application host; backups cannot be recovered without it.

For a restore, decrypt into a temporary directory, verify SHA256SUMS, restore PostgreSQL with pg_restore, recover the receipt volume, and verify /healthz before reopening access.

Security model

  • Private-network identity enforcement; no password form or public registration flow
  • Docker file secrets kept outside images and source control
  • Content Security Policy, HSTS in production, frame blocking, rate limits, and sensitive-header redaction
  • Upload type and 10 MB request limits for receipt attachments
  • Short-lived offline grants with explicit local-data clearing
  • Optimistic concurrency and audit/sync records for mutable financial entities
  • Confirm-before-save AI proposals with expiration

See SECURITY.md for responsible disclosure and deployment assumptions.

Portfolio scope and license

This is a working personal project, not regulated financial software and not a hosted financial service. Use test data when evaluating it.

No open-source license is currently granted. Public visibility is provided for portfolio review and technical evaluation; copyright remains with the repository owner.

About

Privacy-first self-hosted finance PWA with exact ledger accounting, zero-based and 50/30/20 budgeting, offline sync, bills, and local AI

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages