noisebell/pi
2026-03-24 00:51:37 -07:00
..
pi-relay feat: add home assistant capability with pi-relay 2026-03-24 00:24:12 -07:00
pi-service feat: add basic rss feat support 2026-03-23 13:56:00 -07:00
.envrc feat: update readmes and keys and fixes 2026-03-17 02:57:47 -07:00
configuration.nix feat: update ot synchronous gpio and rotate keys 2026-03-23 01:49:01 -07:00
flake.lock feat: expose configurations, add retry, make stable 2026-03-09 17:11:22 -07:00
flake.nix feat: update age configuration to be inside 2026-03-17 03:51:09 -07:00
hardware-configuration.nix feat!: make declarative version 2026-03-22 17:40:48 -07:00
module.nix fix: active poll instead of wait for interupt 2026-03-23 13:22:55 -07:00
README.md feat: add documentation for pi relay and home automation 2026-03-24 00:51:37 -07:00

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

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.age
  • cache-to-pi-key.age
  • tailscale-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 Home Assistant is on a fixed LAN IP, set that explicitly during deploy:

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:

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/<timestamp>/noisebell
  • /opt/noisebell/releases/<timestamp>/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:

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:

{
  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:

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:

{
  "status": "open",
  "timestamp": 1774336193
}

Example Home Assistant automation:

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 <token>.

GET /

{"status": "open", "timestamp": 1710000000}