# Cache Service The central hub. Sits between the Pi and Discord. It does two things: polls the Pi on a timer to keep a local copy of the door state, and receives push webhooks from the Pi when the state actually changes. Either way, updates get written to SQLite and forwarded to downstream services via outbound webhooks. If the Pi stops responding to polls (configurable threshold, default 3 misses), the cache marks it as offline and notifies downstream. ## API | Method | Path | Auth | Description | |--------|------|------|-------------| | `GET` | `/status` | — | Current door status (`status`, `since`, `last_checked`) | | `GET` | `/badge.svg` | — | Live README badge with Noisebridge logo | | `GET` | `/metrics` | — | Prometheus metrics, scraped locally by the DO Prometheus | | `POST` | `/webhook` | Bearer | Inbound webhook from the Pi | | `GET` | `/health` | — | Health check | `since` is the Pi-reported time when the current state began. `last_checked` is when the cache most recently attempted a poll. The public Caddy vhost returns `404` for `/metrics` and `/webhook`; Prometheus scrapes the cache directly on localhost, and the Pi posts webhooks over Tailscale. Metrics include the configured Pi target, poll interval, offline threshold, last poll result, last HTTP status, last poll duration, last poll attempt/success/failure timestamps, and failure counters split into HTTP, timeout, connect, request-other, and parse failures. Regular timer-driven poll data should be debugged from Prometheus and Grafana, not by scanning logs. The cache logs sparse events instead: state changes applied from the Pi, offline/online transitions, first or changed poll failures in a failure streak, stale events, auth/rate-limit rejections, outbound webhook deliveries, retries, and final failures. Successful unchanged polls, badge/image/status reads, and metrics scrapes are intentionally quiet at `INFO`. ## Badge `/badge.svg` serves a classic shields.io-style SVG badge with the Noisebridge logo and the current cache status (`open`, `closed`, or `offline`). Use it in a GitHub README like this: ```md [![Space status](https://your-cache-domain.example.com/badge.svg)](https://your-cache-domain.example.com/status) ``` That keeps the badge clickable and sends readers to the live `/status` endpoint. ## Configuration NixOS options under `services.noisebell-cache`: | Option | Default | Description | |--------|---------|-------------| | `domain` | required | Caddy virtual host domain | | `piAddress` | required | Pi URL (e.g. `http://noisebell:80`) | | `piApiKeyFile` | required | Key file for authenticating to the Pi | | `inboundApiKeyFile` | required | Key file for validating inbound webhooks | | `port` | `3000` | Listen port | | `statusPollIntervalSecs` | `60` | How often to poll `GET /` on the Pi | | `offlineThreshold` | `3` | Consecutive failed polls before marking offline | | `retryAttempts` | `3` | Outbound webhook retry count | | `retryBaseDelaySecs` | `1` | Exponential backoff base delay | | `httpTimeoutSecs` | `10` | HTTP request timeout | | `dataDir` | `/var/lib/noisebell-cache` | SQLite database location | | `outboundWebhooks` | `[]` | List of `{url, secretFile}` for downstream services | ## Secrets | Secret | What it's for | |--------|---------------| | `piApiKeyFile` | Bearer token to poll the Pi's GET endpoints | | `inboundApiKeyFile` | Bearer token the Pi sends when POSTing to `/webhook` | | `outboundWebhooks[].secretFile` | Bearer token sent with outbound webhook POSTs | ## Home Assistant Home Assistant works well as another `outboundWebhooks` target. - For fast automations, point a webhook target at Home Assistant's `/api/webhook/` endpoint. - For current-state display and restart recovery, have Home Assistant also poll `GET /status`. Example NixOS config: ```nix { services.noisebell-cache.outboundWebhooks = [ { url = "https://homeassistant.example.com/api/webhook/noisebell-door"; secretFile = null; } ]; } ``` The webhook payload sent by cache-service is: ```json { "status": "open", "timestamp": 1710000000 } ``` `secretFile = null` is usually fine for Home Assistant webhook triggers because the webhook ID itself acts as the secret. If you place Home Assistant behind an extra auth layer that expects a bearer token, set `secretFile` to a file containing that token.