# volundr A small, secure, single-user web app for logging bike service and repair "events" (type, date, mileage, cost, notes) and getting a clear **service-due** hint per bike, plus **MOT / insurance renewal** tracking. Built with FastAPI + SQLite + Jinja2 + HTMX. No build step. Server-rendered pages. ## Features - Log events for a bike: type (service, fuel, tyre, chain, brake, repair, mot, insurance, other), date, optional mileage + cost, free-text notes. - Per-bike **service status** derived from your newest mileage and the most recent *service* event: `ok` (still under the interval), `due-soon` (within the lead time — book it soon), or `overdue` (past it — book a service now). - **MOT / insurance renewals**: the most recent `mot` / `insurance` event is tracked and flagged `ok` / `due-soon` / `overdue` by date (default: valid for 12 months, "due soon" within the final 60). - Set a **service interval** in miles per bike (e.g. 5000). - Dashboard "action needed" strip surfaces any overdue bike. - Cost and event totals, newest-first history. - **Secure login** (Argon2id hashing, server-side session, CSRF, login throttling, hardened security headers, strict CSP). Designed to run behind a reverse proxy on the open web. ## Quick start ```bash # 1. Install dependencies uv sync --all-extras # 2. Configure credentials (required) export BIKE_ADMIN_USERNAME=admin export BIKE_ADMIN_PASSWORD= export BIKE_ADMIN_SECRET_KEY=$(python -c "import secrets;print(secrets.token_urlsafe(32))") # 3. Run uv run uvicorn app.main:app --host 127.0.0.1 --port 8000 # open http://127.0.0.1:8000 ``` Or copy `.env.example` to `.env` and fill in the values (env vars and the local `.env` file are both read automatically). ## Running behind a reverse proxy (production) TLS terminated at the proxy (Caddy/nginx), app on a private host behind it. `deploy/bike_admin.service` is the systemd unit used in production; `deploy/env.production.example` is the production `.env` template. - Set `BIKE_ADMIN_IS_PRODUCTION=true` so the session cookie is `Secure`. - Bind as needed for the proxy to reach you: - proxy on the **same** host → `--host 127.0.0.1` - proxy on **another** host (e.g. a single fronting Caddy) → `--host 0.0.0.0` or bind to a specific LAN interface; never the public interface directly, since there is no app-level TLS. - Proxy forwards to the app and sets `X-Forwarded-For` / `X-Forwarded-Proto`; the app trusts these by default (`BIKE_ADMIN_TRUST_PROXY`). uvicorn's `--forwarded-allow-ips 192.168.0.0/16 --proxy-headers` accepts them (adjust to your LAN range). ### systemd (production recipe) ```bash # one-time: deps + service account [on Arch: uv ships as python-uv] sudo pacman -S --needed git python-uv sudo useradd --system bike # one-time: app sudo chown -R bike:bike /opt/bike_admin sudo -u bike uv sync --all-extras --project /opt/bike_admin sudo install -d -o bike -g bike /opt/bike_admin/data # systemd needs it to exist # one-time: .env (see deploy/env.production.example) sudo chown bike:bike /opt/bike_admin/.env sudo chmod 640 /opt/bike_admin/.env # owner rw, group r, others none sudo cp deploy/bike_admin.service /etc/systemd/system/bike-admin.service sudo systemctl daemon-reload sudo systemctl enable --now bike-admin ``` Caddy block for the fronting proxy: ``` volundr.local.jakes-asgard.uk { reverse_proxy 192.168.0.52:8000 } ``` Back up your data by copying the single file in the data dir — `/bike_admin.db` (SQLite, WAL; `data/bike_admin.db` by default). ## Tests / lint ```bash uv run pytest -q uv run ruff check app tests uv run ruff format --check app tests ```