# Pi Rust service and deployment workflow for the Raspberry Pi at Noisebridge. The current recommended setup is: 1. run Raspberry Pi OS Lite on the Pi 2. keep the Pi itself free of Nix 3. build a static `aarch64` Noisebell binary on your laptop with Nix 4. 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 `noisebell` binary for `aarch64-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 ```sh 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: ```sh 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: ```sh 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: ```sh ssh-keyscan noisebell-pi.local 2>/dev/null | grep ed25519 ``` Add that key to `secrets/secrets.nix` for: - `pi-to-cache-key.age` - `cache-to-pi-key.age` - `tailscale-auth-key.age` Then refresh recipients if needed: ```sh cd secrets agenix -r ``` ## Edit secrets ```sh 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: ```sh scripts/deploy-pios-pi.sh pi@noisebell-pi.local ``` If Home Assistant is on a fixed LAN IP, set that explicitly during deploy: ```sh HOME_ASSISTANT_BASE_URL=http://10.21.0.43:8123 scripts/deploy-pios-pi.sh pi@100.66.45.36 ``` If you only know the IP: ```sh scripts/deploy-pios-pi.sh pi@10.21.x.x ``` That script: 1. builds `.#packages.aarch64-linux.noisebell-static` locally 2. builds `.#packages.aarch64-linux.noisebell-relay-static` locally 3. decrypts the Pi-facing secrets locally with `agenix` 4. uploads the binaries and secrets to the Pi 5. installs Tailscale and Avahi if needed 6. writes `/etc/noisebell/noisebell.env` 7. writes `/etc/noisebell/noisebell-relay.env` 8. installs `noisebell.service` and `noisebell-relay.service` 9. enables and starts both services 10. runs `tailscale up` with the decrypted auth key ## Files written on the Pi The deploy script creates: - `/opt/noisebell/releases//noisebell` - `/opt/noisebell/releases//noisebell-relay` - `/opt/noisebell/current` -> current release symlink - `/etc/noisebell/pi-to-cache-key` - `/etc/noisebell/cache-to-pi-key` - `/etc/noisebell/relay-webhook-secret` - `/etc/noisebell/homeassistant-webhook-id` - `/etc/noisebell/tailscale-auth-key` - `/etc/noisebell/noisebell.env` - `/etc/noisebell/noisebell-relay.env` - `/etc/systemd/system/noisebell.service` - `/etc/systemd/system/noisebell-relay.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: ```sh 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_MS` | `50` | Debounce delay in milliseconds | | `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 | ## Relay service configuration The optional relay service accepts authenticated webhooks from cache-service and forwards them to Home Assistant on the local network. | Variable | Default | Description | |---|---|---| | `NOISEBELL_RELAY_PORT` | `8090` | HTTP port for the relay webhook endpoint | | `NOISEBELL_RELAY_BIND_ADDRESS` | `0.0.0.0` | HTTP bind address | | `NOISEBELL_RELAY_TARGET_BASE_URL` | `http://10.21.0.43:8123` | Base URL for Home Assistant | | `NOISEBELL_RELAY_TARGET_WEBHOOK_ID` | required | Home Assistant webhook ID | | `NOISEBELL_RELAY_INBOUND_API_KEY` | required | Bearer token expected from cache-service | | `NOISEBELL_RELAY_RETRY_ATTEMPTS` | `3` | Forward retry count | | `NOISEBELL_RELAY_RETRY_BASE_DELAY_SECS` | `1` | Exponential backoff base delay | | `NOISEBELL_RELAY_HTTP_TIMEOUT_SECS` | `10` | Outbound request timeout | If `.local` resolution is reliable on your Pi, you can override the deploy default with `HOME_ASSISTANT_BASE_URL=http://homeassistant.local:8123`. Example cache target for the relay: ```nix { services.noisebell-cache.outboundWebhooks = [ { url = "http://noisebell-pi.local:8090/webhook"; secretFile = /run/agenix/noisebell-relay-webhook-secret; } ]; } ``` ## Home Assistant workflow The working Home Assistant path is: ```text Pi door sensor -> cache-service -> Pi relay -> Home Assistant webhook automation ``` This keeps cache-service as the fanout source while still letting Home Assistant stay LAN-only. Setup summary: 1. Pi still posts raw door events to cache via `NOISEBELL_ENDPOINT_URL` 2. cache-service fans out to `http://noisebell-pi:8090/webhook` using `relay-webhook-secret.age` 3. `noisebell-relay` forwards the payload to Home Assistant using `homeassistant-webhook-id.age` 4. Home Assistant automation triggers on the webhook and switches devices based on `trigger.json.status` Payload received by Home Assistant: ```json { "status": "open", "timestamp": 1774336193 } ``` Example Home Assistant automation: ```yaml alias: noisebell description: "" triggers: - trigger: webhook allowed_methods: - POST local_only: false webhook_id: "-roWWM0JVCWSispwyHXlcKtjI" conditions: [] actions: - if: - condition: template value_template: "{{ trigger.json.status == 'open' }}" then: - action: switch.turn_on target: entity_id: switch.mini_smart_plug_socket_1 else: - if: - condition: template value_template: "{{ trigger.json.status == 'closed' }}" then: - action: switch.turn_off target: entity_id: switch.mini_smart_plug_socket_1 mode: single ``` Important: Home Assistant webhook IDs are exact. If the automation shows a leading `-`, keep that same leading `-` in `homeassistant-webhook-id.age`. ## API All endpoints require `Authorization: Bearer `. **`GET /`** ```json {"status": "open", "timestamp": 1710000000} ```