bitspire/docs/raspberry-pi-setup.md
Padreug 91c6994dd4 feat(deploy): add aarch64 Raspberry Pi 4 target
The Pi 4 twin of the Pi 5 build: `rpi4-installed` (in-place rebuild target),
`rpi4-image`, and `packages.aarch64-linux.{sd-image-rpi4,atm-app-rpi4}`, all
through the board-keyed machinery of the previous commit. The shared runtime
is untouched; only the board pair is new.

deploy/nixos/hardware/raspberry-pi-4.nix mirrors raspberry-pi-5.nix line for
line except where the boards differ:
- KMS for the kiosk display is an opt-in on the Pi 4
  (`hardware.raspberry-pi."4".fkms-3d`), which also injects the CMA + vc4
  device-tree overlays; the Pi 5 gets it by default. Without it X falls back
  to the framebuffer and Electron renders in software.
- fkms-3d sets videoDrivers itself, so the module doesn't.
Everything else — extlinux boot, console pinned to tty0 so the GPIO UART is
free for a validator, no-suspend, the ttyValidator{0,1,2} udev symlinks — is
identical by design.

Evaluation-verified only: rpi4-installed/rpi4-image instantiate, and against
rpi5 they differ solely in the expected places (bcm2711 device tree, the two
fkms overlays, the rpiVersion=4 kernel, no clk-rp1 in initrd, machine model
in the env seed). Not yet booted on hardware; the doc says so.

docs/raspberry-pi-setup.md covers both boards — build, flash, first boot +
provisioning via the spire seed, in-place updates, peripherals — since #87
shipped the Pi 5 without one. It replaces a never-committed Pi 4 sketch
(parked on wip/rpi4-sketch) whose flake wiring didn't evaluate and whose
provisioning section predated the pairing seed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013A6683cCHnQxFUosx1krY4
2026-09-24 21:44:25 +02:00

6.6 KiB
Raw Permalink Blame History

Deploying bitSpire to a Raspberry Pi (4 or 5)

The DIY reference build: a Raspberry Pi, a bill validator on USB-serial, a touchscreen and a QR scanner. Both boards share one runtime in flake.nix (piBaseModules) and differ only in their hardware glue module:

Board nixosConfigurations Flashable image Hardware glue
Raspberry Pi 5 rpi5-installed, rpi5-image packages.aarch64-linux.sd-image-rpi5 deploy/nixos/hardware/raspberry-pi-5.nix
Raspberry Pi 4 rpi4-installed, rpi4-image packages.aarch64-linux.sd-image-rpi4 deploy/nixos/hardware/raspberry-pi-4.nix

<board>-image is what you flash; <board>-installed is what a running Pi rebuilds itself against afterwards. They share every module except the root filesystem declaration and the SD-image builder — see the comments above mkPiInstalled / mkPiImage in flake.nix for why they're split.

Status: the Pi 4 target is evaluation-verified (it instantiates, and its configuration differs from the Pi 5's only in the expected board-specific places) but has not yet been booted on hardware. Treat the first bring-up as exactly that.

What's different from the x86 fleet

  • aarch64. Any Pi build needs an aarch64 builder — a Pi itself, an ARM box, or boot.binfmt.emulatedSystems = [ "aarch64-linux" ] on an x86 host. The Electron app closure will not build on plain x86.
  • Boot. Raspberry Pi firmware + U-Boot + extlinux (from nixos-hardware), not systemd-boot. The image is an SD-card image, not a GPT disk image.
  • No determinate, no atm-tui in the Pi runtime yet (neither ships an aarch64 package). Add them when they do.
  • Cachix is pre-wired (aiolabs.cachix.org in nix.settings), so an in-place rebuild substitutes the heavy closure rather than compiling on the Pi — provided the closure was pushed there first.

Board differences that matter

Pi 5 Pi 4
SoC / device tree BCM2712 (bcm2712*-rpi-*.dtb) BCM2711 (bcm2711-rpi-4*.dtb)
Kiosk GPU / KMS vc4/v3d on by default opt-in via hardware.raspberry-pi."4".fkms-3d (the glue module enables it; it also injects the CMA + vc4 overlays)
RAM 4–16 GB 4 GB is the floor for Electron; 8 GB is comfortable
Root storage SD, USB, or NVMe over PCIe SD or USB only
Power 5 V / 5 A USB-C PD 5 V / 3 A; Electron startup is the peak

Everything else — validator serial symlinks, console=tty0 keeping the GPIO UART free, no-suspend, the bitspire service — is identical between the two glue modules by design. Keep it that way: a change to one almost certainly belongs in the other.

1. Build the image

On (or via) an aarch64 builder:

nix build .#packages.aarch64-linux.sd-image-rpi4     # or sd-image-rpi5
ls result/sd-image/
# → nixos-image-sd-card-<version>-aarch64-linux.img.zst

2. Flash

lsblk -f                      # identify the SD card — NOT your main disk
zstd -dc result/sd-image/*.img.zst | sudo dd of=/dev/sdX bs=4M status=progress conv=fsync

The image carries two labelled partitions the installed config expects: FIRMWARE (vfat, Pi firmware + U-Boot) and NIXOS_SD (ext4 root). The root partition grows to fill the card on first boot.

3. First boot

Insert the card, connect Ethernet and power. The Pi boots into the kiosk with no pairing, so the screen shows the pairing wizard — with a camera it waits for a QR; without one it says so and tells you to provision VITE_SPIRE_SEED instead. It also comes up with sshd and password auth enabled, same as the x86 installed configs — this is the provisioning window.

Provision exactly as for a Sintra — from the dev box, with the spire seed minted by spirekeeper:

SPIRE_SEED='spire-seed:v1:…' bash deploy/nixos/provision-atm.sh <pi-ip> 22

That writes /var/lib/bitspire/.env and restarts the service; the seed carries the relay and the LNbits transport pubkey, so nothing else is needed. Alternatively, show the seed's QR to the machine's camera and let the on-screen wizard do the same thing. Verify with:

ssh bitspire@<pi-ip> 'journalctl -u bitspire -n 50 --no-pager | grep "\["'
# expect [Signer] Pairing to bunker … then [Lightning] LNbits client initialized

The dev-only VITE_ATM_PRIVATE_KEY fallback works here too for a bench setup without a bunker — see provision-atm.sh's header for the variables.

4. Updating in place

Once flashed, never re-flash for a software update. The -installed target is the aarch64 twin of the fleet's rebuild ritual:

sudo nixos-rebuild switch --flake \
  "git+ssh://forgejo@git.atitlan.io/aiolabs/bitspire.git?ref=dev#rpi4-installed"

(Pi 5: #rpi5-installed.) With the closure on cachix this is a download, not a build. If it starts compiling Electron on the Pi, the closure wasn't pushed — stop, build on the aarch64 builder, cachix push aiolabs, retry.

5. Wiring the peripherals

  • Bill validator — over a USB-serial adapter. The glue modules give stable symlinks so .env never depends on enumeration order: FTDI → /dev/ttyValidator0, CP210x → /dev/ttyValidator1, CH340 → /dev/ttyValidator2. Two adapters of the same chip need disambiguating by KERNELS/serial in the udev rule — do that at bring-up. A validator wired straight to the GPIO UART (pins 14/15) also works: the kernel console is pinned to tty0 precisely so that UART stays free.
  • QR scanner — USB HID keyboard-emulation, no configuration.
  • Touchscreen — DSI or HDMI. X runs on the Pi's KMS driver.
  • Serial console for debugging — there isn't one by default (see above). Use SSH, or temporarily add console=ttyAMA0,115200 to boot.kernelParams in the glue module.

Hardware notes for 24/7 operation

  • Storage. SD cards have finite write endurance; for anything beyond a bench build put root on a USB-SATA SSD (both boards) or NVMe (Pi 5). state.db and the journal are the writers.
  • Thermal. Both boards throttle under sustained load without cooling. Heatsinks at minimum; the official active cooler for the Pi 5.
  • Power. Brown-outs on Electron startup look like random reboots. Use the official supply or one rated above the board's peak, never a hub.
  • Swap. The runtime provisions a 2 GB swapfile so memory pressure degrades instead of hard-freezing. On a 4 GB Pi 4 that is not optional.
  • deploy/nixos/README.md — the x86 fleet pipeline this mirrors
  • docs/machine-installation.md — why images rather than nixos-install
  • deploy/nixos/hardware/raspberry-pi-{4,5}.nix — the per-board glue
  • flake.nix — piBoards, piBaseModules, mkPiInstalled, mkPiImage