4.4 KiB
Pi
Rust service and deployment workflow for the Raspberry Pi at Noisebridge.
The current recommended setup is:
- run Raspberry Pi OS Lite on the Pi
- keep the Pi itself free of Nix
- build a static
aarch64Noisebell binary on your laptop with Nix - copy the binary, secrets, and systemd service to the Pi over SSH
This avoids the Raspberry Pi Zero 2 W NixOS boot issues while still keeping the application build reproducible.
What stays on Raspberry Pi OS
- bootloader
- kernel
- firmware
- Wi-Fi and local networking
- SSH base access
- Tailscale package/runtime
- Avahi package/runtime
What Nix manages
- building a static
noisebellbinary foraarch64-linux - the exact app binary you deploy
- encrypted secrets in the repo
- repeatable deployment from your laptop
Initial Pi OS setup
1. Flash Raspberry Pi OS Lite
curl -L "https://downloads.raspberrypi.org/raspios_lite_arm64_latest" | xz -d -c | sudo dd of=/dev/sdb bs=16M conv=fsync status=progress && sync
2. Configure the flashed SD card
Configure it for:
- Wi-Fi on
Noisebridge - SSH enabled
- serial enabled if you want a recovery console
The helper script is:
sudo scripts/configure-pios-sd.sh /run/media/jet/bootfs /run/media/jet/rootfs
This setup expects SSH key login for user pi; it does not configure a password.
3. Boot the Pi and verify SSH
After boot, verify SSH works:
ssh pi@noisebell-pi.local
Add the Pi host key to age recipients
The deploy flow decrypts secrets locally on your laptop, but the Pi host key should still be a recipient for the Pi-facing secrets so the repo stays accurate.
Grab the Pi host key:
ssh-keyscan noisebell-pi.local 2>/dev/null | grep ed25519
Add that key to secrets/secrets.nix for:
pi-to-cache-key.agecache-to-pi-key.agetailscale-auth-key.age
Then refresh recipients if needed:
cd secrets
agenix -r
Edit secrets
cd secrets
agenix -e pi-to-cache-key.age
agenix -e cache-to-pi-key.age
agenix -e tailscale-auth-key.age
These stay encrypted in git. The deploy script decrypts them locally on your laptop and copies the plaintext files to the Pi as root-only files.
Deploy to Raspberry Pi OS
From your laptop:
scripts/deploy-pios-pi.sh pi@noisebell-pi.local
If you only know the IP:
scripts/deploy-pios-pi.sh pi@10.21.x.x
That script:
- builds
.#packages.aarch64-linux.noisebell-staticlocally - decrypts the Pi-facing secrets locally with
agenix - uploads the binary and secrets to the Pi
- installs Tailscale and Avahi if needed
- writes
/etc/noisebell/noisebell.env - installs
noisebell.service - enables and starts the service
- runs
tailscale upwith the decrypted auth key
Files written on the Pi
The deploy script creates:
/opt/noisebell/releases/<timestamp>/noisebell/opt/noisebell/current-> current release symlink/etc/noisebell/pi-to-cache-key/etc/noisebell/cache-to-pi-key/etc/noisebell/tailscale-auth-key/etc/noisebell/noisebell.env/etc/systemd/system/noisebell.service
All secret files are root-only.
Tailscale
Tailscale is kept on Raspberry Pi OS rather than NixOS.
The deploy script:
- installs the Tailscale package if missing
- enables
tailscaled - runs
tailscale up --auth-key=... --hostname=noisebell-pi
So Tailscale stays part of the base OS, while its auth key is still managed as an encrypted age secret in this repo.
Later updates
Normal iteration is just rerunning the deploy script:
scripts/deploy-pios-pi.sh pi@noisebell-pi.local
That rebuilds the binary locally, uploads a new release, refreshes secrets, and restarts the service.
Service configuration
The deployed service uses these environment variables:
| Variable | Default | Description |
|---|---|---|
NOISEBELL_GPIO_PIN |
17 |
GPIO pin number |
NOISEBELL_DEBOUNCE_SECS |
5 |
Debounce delay in seconds |
NOISEBELL_PORT |
80 |
HTTP server port |
NOISEBELL_ENDPOINT_URL |
required | Webhook URL to POST state changes to |
NOISEBELL_RETRY_ATTEMPTS |
3 |
Webhook retry count |
NOISEBELL_RETRY_BASE_DELAY_SECS |
1 |
Exponential backoff base delay |
NOISEBELL_HTTP_TIMEOUT_SECS |
10 |
Outbound request timeout |
NOISEBELL_BIND_ADDRESS |
0.0.0.0 |
HTTP bind address |
NOISEBELL_ACTIVE_LOW |
true |
Low GPIO = door open |
API
All endpoints require Authorization: Bearer <token>.
GET /
{"status": "open", "timestamp": 1710000000}