# Noisebell Door status monitor for [Noisebridge](https://www.noisebridge.net). A Raspberry Pi watches a door sensor and reports open/closed state; remote services cache the data and fan it out to Discord and an Atom feed. ## Architecture ``` Pi (door sensor) ──webhook──▸ Cache Service ──webhook──▸ Discord Bot │ │ polls Pi ◂──┘ Atom feed ◂──┘ RSS Service ``` The **Pi** runs a small HTTP service that reads GPIO and exposes `GET /` (status) and `GET /info`. It also pushes state changes to the cache service via webhook. The **Cache Service** is the central hub. It polls the Pi for status and info, stores everything in SQLite, and forwards state changes to downstream webhooks (Discord, etc.). It exposes a read API for consumers. The **Discord Bot** receives webhooks from the cache service and posts embeds to a Discord channel. The **RSS Service** fetches history from the cache service and serves an Atom feed of door events from the last 7 days. ## Services | Service | Crate | Default Port | Description | |---------|-------|-------------|-------------| | Cache | `noisebell-cache` | 3000 | Polls Pi, caches state in SQLite, forwards webhooks | | Discord | `noisebell-discord` | 3001 | Posts door status embeds to Discord | | RSS | `noisebell-rss` | 3002 | Serves Atom feed of recent door events | ## Cache API | Method | Path | Auth | Description | |--------|------|------|-------------| | `GET` | `/status` | None | Current door status (`status`, `timestamp`, `last_seen`) | | `GET` | `/info` | None | Pi system info (JSON blob) | | `GET` | `/history` | None | Last 100 state changes | | `POST` | `/webhook` | Bearer token | Inbound webhook from Pi | | `GET` | `/health` | None | Returns `200 OK` | ## Building All remote services live in a Cargo workspace under `remote/`. ```sh cd remote cargo build --release ``` Or with Nix: ```sh cd remote nix build .#noisebell-cache nix build .#noisebell-discord nix build .#noisebell-rss ``` ## NixOS Configuration The flake at `remote/flake.nix` exports NixOS modules. Example configuration: ```nix { inputs.noisebell.url = "git+https://git.extremist.software/jet/noisebell?dir=remote"; outputs = { self, nixpkgs, noisebell, ... }: { nixosConfigurations.myhost = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ noisebell.nixosModules.default ({ ... }: { services.noisebell-cache = { enable = true; domain = "cache.noisebell.example.com"; piAddress = "http://noisebell-pi:80"; piApiKeyFile = "/run/secrets/noisebell-pi-api-key"; inboundApiKeyFile = "/run/secrets/noisebell-inbound-api-key"; outboundWebhooks = [ { url = "http://localhost:3001/webhook"; secretFile = "/run/secrets/noisebell-discord-webhook-secret"; } ]; }; services.noisebell-discord = { enable = true; domain = "discord.noisebell.example.com"; discordTokenFile = "/run/secrets/noisebell-discord-token"; channelId = "123456789012345678"; webhookSecretFile = "/run/secrets/noisebell-discord-webhook-secret"; }; services.noisebell-rss = { enable = true; domain = "rss.noisebell.example.com"; cacheUrl = "http://localhost:3000"; }; }) ]; }; }; } ``` ## Secrets | Secret | Used by | Description | |--------|---------|-------------| | `piApiKeyFile` | Cache | Bearer token for authenticating to Pi GET endpoints | | `inboundApiKeyFile` | Cache | Bearer token the Pi uses when POSTing to `/webhook` | | `outboundWebhooks[].secretFile` | Cache | Bearer token sent with outbound webhook POSTs | | `discordTokenFile` | Discord | Discord bot token | | `webhookSecretFile` | Discord | Must match the cache's outbound webhook secret |