# 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: 1. Updates in-memory state (atomics) 2. POSTs `{"status": "open", "timestamp": ...}` to the cache service with a Bearer token 3. 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: ```nix boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; ``` ### 1. Flash the SD card ```sh 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 and is discoverable via mDNS as `noisebridge-pi.local`. ### 2. Find the Pi ```sh ping noisebridge-pi.local ``` ### 3. SSH host key Grab the key and add it to `secrets/secrets.nix`: ```sh ssh-keyscan noisebridge-pi.local 2>/dev/null | grep ed25519 ``` ```nix # 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 ]; } ``` ### 4. Create secrets ```sh 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 ``` ### 5. SSH access Add your public key to `configuration.nix`: ```nix users.users.root.openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAA..." ]; ``` ### 6. Deploy ```sh 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 `. **`GET /`** — door state ```json {"status": "open", "timestamp": 1710000000} ```