This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
LNbitsBox is a bootable NixOS SD card image for Raspberry Pi 4 that runs LNbits with Lightspark Spark as a Lightning Network funding source. It includes a first-run configuration wizard, Tor hidden service, Wi-Fi config, admin dashboard, and OTA updates.
# Compressed image (for GitHub releases)
nix build .#sdImage -L
# Uncompressed image (faster for local testing)
nix build .#sdImageUncompressed -L
# Quick test build — builds uncompressed and copies .img to repo root
./build-test.sh
# Update a single flake input (e.g. lnbits)
nix flake lock --update-input lnbitsThe build requires: Nix with flakes enabled, extra-platforms = aarch64-linux, QEMU user emulation, and the nix-community/raspberry-pi-nix Cachix caches. See docs/development.md for full setup.
Configurator (first-run wizard):
cd nixos/configurator-app
python3 -m venv venv && source venv/bin/activate
pip install flask flask-wtf mnemonic
export DEV_MODE=true
python3 app.pyAdmin dashboard:
cd nixos/admin-app
python3 -m venv venv && source venv/bin/activate
pip install flask flask-wtf
export DEV_MODE=true
python3 app.pyDEV_MODE=true redirects all file paths to /tmp/lnbitspi-test/ instead of /var/lib/.
Marketing site (gh-pages/):
cd gh-pages
npm run dev # dev server
npm run build # static site generation (nuxt generate)
npm run preview # preview built siteThe gh-pages/ subdirectory has its own CLAUDE.md with design system details.
The marker file /var/lib/lnbits/.configured controls the entire system:
- Absent → Caddy routes all traffic to the configurator (port 8080). LNbits and admin services do not start.
- Present → Caddy routes traffic to LNbits (port 5000). Admin dashboard (port 8090) is available at
/box/. Configurator stops.
| Service | Port | NixOS file | Runs when |
|---|---|---|---|
| Caddy (reverse proxy) | 80/443 | caddy-proxy.nix |
Always |
| Configurator (wizard) | 8080 | configurator-service.nix |
Marker absent |
| LNbits | 5000 | lnbits-service.nix |
Marker present |
| Spark sidecar | 8765 | spark-sidecar-service.nix |
Marker present |
| Tunnel Me Out reverse tunnel | local 5000 (remote port assigned by provider) | reverse-tunnel-service.nix |
Tunnel configured + started |
| Admin dashboard | 8090 | admin-service.nix |
Marker present |
| Tor | — | tor-service.nix |
Always |
| Wi-Fi config | — | wifi-config.nix |
First boot |
LNbitsBox supports paid reverse tunnels via Tunnel Me Out (lnpro API). The admin app (nixos/admin-app/app.py) manages tunnel lifecycle, invoice generation/renewal, and service control.
- Provider API base:
LNBITSBOX_TUNNEL_API_BASE_URL(defaulthttps://lnbits.lnpro.xyz/reverse_proxy/api/v1) - Public product id:
LNBITSBOX_TUNNEL_PUBLIC_ID - Tunnel systemd service:
lnbitsbox-reverse-tunnel.service - Local forwarded target:
LNBITSBOX_TUNNEL_LOCAL_PORT(default5000)
Admin API endpoints:
GET /box/api/tunnel/status— current tunnel, pending invoice, service status, connect scriptPOST /box/api/tunnel/create-invoice— create first tunnel invoicePOST /box/api/tunnel/renew-invoice— add days to existing tunnelPOST /box/api/tunnel/poll— check payment status and sync pending invoice statePOST /box/api/tunnel/start— enable + restart reverse tunnel service (requires tunnel + key)POST /box/api/tunnel/stop— stop reverse tunnel service
UI notes (nixos/admin-app/templates/dashboard.html):
- Service row actions are status-driven:
Startwhen stopped,Stop+Restartwhen active. - Tunnel service row shows
Create Tunnelwhen no tunnel is configured. - Polling is adaptive (faster when invoice is pending, slower when stable, reduced when tab is hidden).
/var/lib/lnbits/.configured— marker file; creating it triggers switchover/var/lib/lnbits/database.sqlite3— LNbits database (never deleted by reset)/var/lib/spark-sidecar/mnemonic— BIP39 seed phrase/var/lib/spark-sidecar/api-key.env— Spark API key/etc/lnbits/lnbits.env— LNbits environment config/var/lib/caddy/ca-cert.pem— self-signed Root CA (persists across reboots)/var/lib/lnbitsbox-update/— OTA update state (status,log,target-version)/var/lib/lnbitsbox-tunnel/state.json— tunnel state (client_id,current_tunnel,pending_invoice)/var/lib/lnbitsbox-tunnel/reverse-proxy-key— private SSH key for tunnel connection/var/lib/lnbitsbox-tunnel/runtime.env— generated runtime env consumed by reverse tunnel service/etc/lnbitsbox-version— current version string (written fromflake.nixversion)
Caddy uses a self-signed Root CA generated at first boot. The CA persists; the server cert is regenerated each boot to include the current IP in the SAN. HTTP port 80 serves only a cert-trust page and the CA download (/cert/download). HTTPS routes: /box/* → admin, everything else → LNbits or configurator.
On tagged releases, CI:
- Builds
packages.x86_64-linux.toplevel(the NixOS system closure) - Pushes the closure to
lnbitsbox.cachix.orgviacachix push - Creates
manifest.jsoncontaining the Nix store path and version - Attaches
manifest.json, the.img.zst, andSHA256SUMS.txtto the GitHub release
On device, lnbitsbox-update <tag>:
- Downloads
manifest.jsonfrom the release - Runs
nix-store --realise <store-path>(fetches from all configured substituters:cache.nixos.org+lnbitsbox.cachix.org) - Sets the new system profile and activates it with
switch-to-configuration switch
Important: Use nix-store --realise (not nix copy --from <url>) so that common paths come from cache.nixos.org while LNbitsBox-specific paths come from Cachix.
The version string is set in flake.nix (version = "0.x.y"). Bump it before creating a release tag — it gets written to /etc/lnbitsbox-version on the device and embedded in manifest.json.
nixos/
configuration.nix # Root imports all modules; users, networking, kernel params
lnbits-service.nix # LNbits systemd service + env file
spark-sidecar-service.nix # Spark sidecar service
spark-sidecar-package.nix # Nix package wrapping spark-sidecar source
configurator-service.nix # First-run wizard service
configurator-package.nix # Nix package for the configurator Flask app
admin-service.nix # Admin dashboard service
admin-package.nix # Nix package for the admin Flask app
caddy-proxy.nix # TLS cert generation + Caddyfile generation
tor-service.nix # Tor hidden service
wifi-config.nix # wifi.txt reader on boot
welcome-screen.nix # MOTD and login screen
reset-configurator.nix # lnbitspi-reset script package
update-service.nix # OTA update script + polkit rules
configurator-app/app.py # Flask wizard (seed generation, SSH password)
admin-app/app.py # Flask admin (system stats, service control, OTA, Wi-Fi)
cert-trust-page/ # Static HTML page for CA cert installation
A private LNbits extension for paid SSH tunnel / reverse proxy management. Used with the TunnelMeOut extension. Not part of the NixOS image build — it's installed into LNbits separately.
GitHub Actions (.github/workflows/build-image.yml) triggers on version tags (v*) and workflow_dispatch. It builds on ubuntu-latest using QEMU for aarch64 cross-compilation, pushes the system closure to Cachix, and creates a GitHub release with the image, SHA256SUMS, and manifest.