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
145 lines
6.6 KiB
Markdown
145 lines
6.6 KiB
Markdown
# 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:
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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.
|
||
|
||
## Related
|
||
|
||
- `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`
|