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
6.6 KiB
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, noatm-tuiin the Pi runtime yet (neither ships an aarch64 package). Add them when they do. - Cachix is pre-wired (
aiolabs.cachix.orginnix.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
.envnever depends on enumeration order: FTDI →/dev/ttyValidator0, CP210x →/dev/ttyValidator1, CH340 →/dev/ttyValidator2. Two adapters of the same chip need disambiguating byKERNELS/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 totty0precisely 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,115200toboot.kernelParamsin 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.dband 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.
Related
deploy/nixos/README.md— the x86 fleet pipeline this mirrorsdocs/machine-installation.md— why images rather thannixos-installdeploy/nixos/hardware/raspberry-pi-{4,5}.nix— the per-board glueflake.nix—piBoards,piBaseModules,mkPiInstalled,mkPiImage