3.9 KiB
Pi
Rust service and NixOS config for the Raspberry Pi at Noisebridge. Reads a magnetic door sensor via GPIO, serves the current state over HTTP, and pushes changes to the cache service.
Runs NixOS with Tailscale for remote access and agenix for secrets.
How it works
The service watches a GPIO pin for rising/falling edges with a configurable debounce. When the door state changes, it:
- Updates in-memory state (atomics)
- POSTs
{"status": "open", "timestamp": ...}to the cache service with a Bearer token - Retries with exponential backoff on failure
On startup it also syncs the initial state.
Setup
Prerequisites
If you're building on an x86_64 machine, you need binfmt emulation for aarch64. On NixOS, add this to your system config and rebuild:
boot.binfmt.emulatedSystems = [ "aarch64-linux" ];
1. Flash the SD card
Preferred: one command builds the bootstrap image, writes it to the SD card, and installs the bootstrap agenix identity onto the boot partition so the full Pi system can come up on first boot:
nix run .#flash-pi-sd -- /dev/sdX
This bootstrap image already includes the normal Noisebell service, Tailscale, and the Pi config.
Manual build if you need it:
nix build .#nixosConfigurations.bootstrap.config.system.build.sdImage
dd if=result/sd-image/*.img of=/dev/sdX bs=4M status=progress
Boot the Pi. It connects to the Noisebridge WiFi automatically.
2. SSH host key
Grab the key and add it to secrets/secrets.nix:
ssh-keyscan noisebridge-pi.local 2>/dev/null | grep ed25519
# secrets/secrets.nix
let
pi = "ssh-ed25519 AAAA...";
in
{
"api-key.age".publicKeys = [ pi ];
"inbound-api-key.age".publicKeys = [ pi ];
"tailscale-auth-key.age".publicKeys = [ pi ];
}
3. Create secrets
cd secrets
agenix -e api-key.age # key for POSTing to the cache
agenix -e inbound-api-key.age # key the cache uses to poll us
agenix -e tailscale-auth-key.age # tailscale auth key
4. Bootstrap agenix identity
The Pi uses a dedicated bootstrap age identity stored at /boot/noisebell-bootstrap.agekey to
decrypt its runtime secrets, so first boot does not depend on the machine's freshly generated SSH
host key.
To refresh recipients after changing secrets/secrets.nix:
cd secrets
agenix -r
If you use nix run .#flash-pi-sd -- /dev/sdX, this file is installed automatically.
To install the bootstrap identity manually onto a flashed card before first boot:
cd secrets
agenix -d bootstrap-identity.age > /boot/noisebell-bootstrap.agekey
chmod 600 /boot/noisebell-bootstrap.agekey
5. SSH access
Add your public key to configuration.nix:
users.users.root.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAA..."
];
6. Deploy
After first boot, the Pi should already be running the normal service stack from the flashed image. Use this only for later updates:
nixos-rebuild switch --flake .#pi --target-host root@noisebell
Configuration
Options under services.noisebell in flake.nix:
| Option | Default | Description |
|---|---|---|
endpointUrl |
required | Webhook URL to POST state changes to |
apiKeyFile |
required | Outbound API key file (agenix secret) |
inboundApiKeyFile |
required | Inbound API key file for GET auth |
gpioPin |
17 |
GPIO pin number |
debounceSecs |
5 |
Debounce delay in seconds |
port |
8080 |
HTTP server port |
retryAttempts |
3 |
Webhook retry count |
retryBaseDelaySecs |
1 |
Exponential backoff base delay |
httpTimeoutSecs |
10 |
Outbound request timeout |
bindAddress |
0.0.0.0 |
HTTP bind address |
activeLow |
true |
Low GPIO = door open (depends on wiring) |
restartDelaySecs |
5 |
systemd restart delay on failure |
watchdogSecs |
30 |
systemd watchdog timeout |
API
All endpoints require Authorization: Bearer <token>.
GET / — door state
{"status": "open", "timestamp": 1710000000}