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.
The service watches a GPIO pin for rising/falling edges with a configurable debounce. When the door state changes, it:
{"status": "open", "timestamp": ...} to the cache service with a Bearer tokenOn startup it also syncs the initial state.
| 1 | nix build .#nixosConfigurations.bootstrap.config.system.build.sdImage |
| 2 | dd if=result/sd-image/*.img of=/dev/sdX bs=4M status=progress |
Boot the Pi. It connects to the Noisebridge WiFi automatically.
| 1 | nmap -sn 192.168.1.0/24 |
| 2 | # or |
| 3 | arp -a |
Grab the key and add it to secrets/secrets.nix:
| 1 | ssh-keyscan <pi-ip> | grep ed25519 |
| 1 | # secrets/secrets.nix |
| 2 | let |
| 3 | pi = "ssh-ed25519 AAAA..."; |
| 4 | in |
| 5 | { |
| 6 | "api-key.age".publicKeys = [ pi ]; |
| 7 | "inbound-api-key.age".publicKeys = [ pi ]; |
| 8 | "tailscale-auth-key.age".publicKeys = [ pi ]; |
| 9 | } |
| 1 | cd secrets |
| 2 | agenix -e api-key.age # key for POSTing to the cache |
| 3 | agenix -e inbound-api-key.age # key the cache uses to poll us |
| 4 | agenix -e tailscale-auth-key.age # tailscale auth key |
Add your public key to configuration.nix:
| 1 | users.users.root.openssh.authorizedKeys.keys = [ |
| 2 | "ssh-ed25519 AAAA..." |
| 3 | ]; |
| 1 | nixos-rebuild switch --flake .#pi --target-host root@noisebell |
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 |
All endpoints require Authorization: Bearer <token>.
GET / — door state
| 1 | {"status": "open", "timestamp": 1710000000} |
GET /info — system health + GPIO config
| 1 | { |
| 2 | "uptime_secs": 3600, |
| 3 | "started_at": 1710000000, |
| 4 | "cpu_temp_celsius": 42.3, |
| 5 | "memory_available_kb": 350000, |
| 6 | "memory_total_kb": 512000, |
| 7 | "disk_total_bytes": 16000000000, |
| 8 | "disk_available_bytes": 12000000000, |
| 9 | "load_average": [0.01, 0.05, 0.10], |
| 10 | "nixos_version": "24.11.20240308.9dcb002", |
| 11 | "commit": "c6e726c", |
| 12 | "gpio": { |
| 13 | "pin": 17, |
| 14 | "active_low": true, |
| 15 | "pull": "up", |
| 16 | "open_level": "low", |
| 17 | "current_raw_level": "low" |
| 18 | } |
| 19 | } |