noisebell/pi
2026-03-18 17:44:15 -07:00
..
pi-service feat: reorganize to one flake one rust project 2026-03-18 17:44:15 -07:00
.envrc feat: update readmes and keys and fixes 2026-03-17 02:57:47 -07:00
bootstrap.nix feat: update readmes and keys and fixes 2026-03-17 02:57:47 -07:00
configuration.nix feat: update age configuration to be inside 2026-03-17 03:51:09 -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: expose configurations, add retry, make stable 2026-03-09 17:11:22 -07:00
module.nix feat: update age configuration to be inside 2026-03-17 03:51:09 -07:00
README.md feat: update readmes and keys and fixes 2026-03-17 02:57:47 -07:00
result feat: update readmes and keys and fixes 2026-03-17 02:57:47 -07:00

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:

boot.binfmt.emulatedSystems = [ "aarch64-linux" ];

1. Flash the SD card

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

ping noisebridge-pi.local

3. 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 ];
}

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

5. SSH access

Add your public key to configuration.nix:

users.users.root.openssh.authorizedKeys.keys = [
  "ssh-ed25519 AAAA..."
];

6. Deploy

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}

GET /info — system health + GPIO config

{
  "uptime_secs": 3600,
  "started_at": 1710000000,
  "cpu_temp_celsius": 42.3,
  "memory_available_kb": 350000,
  "memory_total_kb": 512000,
  "disk_total_bytes": 16000000000,
  "disk_available_bytes": 12000000000,
  "load_average": [0.01, 0.05, 0.10],
  "nixos_version": "24.11.20240308.9dcb002",
  "commit": "c6e726c",
  "gpio": {
    "pin": 17,
    "active_low": true,
    "pull": "up",
    "open_level": "low",
    "current_raw_level": "low"
  }
}