133 lines
3.3 KiB
Markdown
133 lines
3.3 KiB
Markdown
# noisebell
|
|
|
|
Monitors a GPIO pin on a Raspberry Pi to detect door open/close events. State changes get POSTed to a webhook endpoint. Current state is available over HTTP.
|
|
|
|
Runs on NixOS with Tailscale for networking and agenix for secrets.
|
|
|
|
## Setup
|
|
|
|
### 1. Bootstrap
|
|
|
|
Build the SD image, flash it, and boot the Pi:
|
|
|
|
```sh
|
|
nix build .#nixosConfigurations.bootstrap.config.system.build.sdImage
|
|
dd if=result/sd-image/*.img of=/dev/sdX bs=4M status=progress
|
|
```
|
|
|
|
Insert the SD card into the Pi and power it on. It will connect to the Noisebridge WiFi network automatically.
|
|
|
|
### 2. Find the Pi
|
|
|
|
Once booted, find the Pi on the network:
|
|
|
|
```sh
|
|
# Scan the local subnet
|
|
nmap -sn 192.168.1.0/24
|
|
|
|
# Or check ARP table
|
|
arp -a
|
|
|
|
# Or check your router's DHCP leases
|
|
```
|
|
|
|
### 3. Get SSH host key
|
|
|
|
Grab the Pi's ed25519 host key and put it in `secrets/secrets.nix`:
|
|
|
|
```sh
|
|
ssh-keyscan <pi-ip> | grep ed25519
|
|
```
|
|
|
|
```nix
|
|
# secrets/secrets.nix
|
|
let
|
|
pi = "ssh-ed25519 AAAA..."; # paste the key here
|
|
in
|
|
{
|
|
"api-key.age".publicKeys = [ pi ];
|
|
"inbound-api-key.age".publicKeys = [ pi ];
|
|
"tailscale-auth-key.age".publicKeys = [ pi ];
|
|
}
|
|
```
|
|
|
|
### 4. Secrets
|
|
|
|
Create the encrypted secret files:
|
|
|
|
```sh
|
|
cd secrets
|
|
agenix -e api-key.age # paste API key for the cache endpoint
|
|
agenix -e inbound-api-key.age # paste API key that the cache uses to poll the Pi
|
|
agenix -e tailscale-auth-key.age # paste Tailscale auth key
|
|
```
|
|
|
|
### 5. Add SSH key
|
|
|
|
Add your SSH 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` | — | Webhook endpoint URL to POST state changes to |
|
|
| `apiKeyFile` | — | Path to file containing outbound API key (agenix secret) |
|
|
| `inboundApiKeyFile` | — | Path to file containing inbound API key for GET endpoint auth (agenix secret) |
|
|
| `gpioPin` | 17 | GPIO pin to monitor |
|
|
| `debounceSecs` | 5 | Debounce delay |
|
|
| `port` | 8080 | HTTP status port |
|
|
| `retryAttempts` | 3 | Webhook retry count |
|
|
| `retryBaseDelaySecs` | 1 | Base delay for exponential backoff |
|
|
| `httpTimeoutSecs` | 10 | Timeout for outbound webhook requests |
|
|
| `bindAddress` | `0.0.0.0` | Address to bind the HTTP server to |
|
|
| `activeLow` | `true` | Whether low GPIO level means open (depends on wiring) |
|
|
| `restartDelaySecs` | 5 | Seconds before systemd restarts on failure |
|
|
| `watchdogSecs` | 30 | Watchdog timeout — service is restarted if unresponsive |
|
|
|
|
## API
|
|
|
|
`GET /` — current door state:
|
|
|
|
```json
|
|
{"status": "open", "timestamp": 1710000000}
|
|
```
|
|
|
|
`GET /info` — system health and GPIO config:
|
|
|
|
```json
|
|
{
|
|
"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"
|
|
}
|
|
}
|
|
```
|
|
|
|
State changes (and initial state on startup) are POSTed to the configured endpoint in the same format as `GET /`, with an `Authorization: Bearer <api-key>` header.
|