Keep /alive unchanged and serve a dashboard at / with status cards, latency graphs, an event timeline, and a Wi-Fi section. Background monitors on configurable intervals: TCP reachability, DNS resolution, public IP change detection, host reboot detection, Wi-Fi link quality, and Wi-Fi scan of networks in range via iw. The Wi-Fi interface is selected with WIFI_INTERFACE; the page lists visible interfaces and reports when none is available. Scan results stay in memory, other metrics go to SQLite on a mounted volume. Optional MQTT publishing with Home Assistant discovery groups all sensors under one device per host. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TdvJYoY8kc8bmBck89U2i6
114 lines
4.5 KiB
Markdown
114 lines
4.5 KiB
Markdown
# healthcheck-container
|
|
|
|
Small Flask service for remote devices (Raspberry Pi in Docker) that answers
|
|
`/alive` for container monitoring and, on top of that, watches the network the
|
|
device sits on: internet reachability, DNS, public IP changes, Wi-Fi link
|
|
quality, Wi-Fi networks in range, and host reboots. Metrics go to SQLite and
|
|
are shown on a dashboard at `/`. Optionally the same values are published to an
|
|
MQTT broker with Home Assistant discovery, so each device shows up in Home
|
|
Assistant as a device with sensors and no manual configuration.
|
|
|
|
## Endpoints
|
|
|
|
| Path | Purpose |
|
|
| --- | --- |
|
|
| `/alive` | Unchanged health check, returns `{"alive": true}` |
|
|
| `/` | Dashboard: status, Wi-Fi, graphs, timeline |
|
|
| `/api/status` | Latest state of every monitor as JSON |
|
|
| `/api/wifi` | Interface list, link details, last scan results |
|
|
| `/api/events?limit=200` | Timeline events, newest first |
|
|
| `/api/samples?prefix=reach.&range=3600` | Bucketed samples for graphs |
|
|
|
|
## Running
|
|
|
|
```sh
|
|
docker compose up -d --build
|
|
```
|
|
|
|
Then open `http://<device>:9999/`.
|
|
|
|
Wi-Fi needs two things from Docker:
|
|
|
|
- `network_mode: host`, otherwise the container has no wireless interface at all.
|
|
- `cap_add: NET_ADMIN`, otherwise link and interface info work but scanning fails
|
|
with a permission error that is shown on the dashboard.
|
|
|
|
Without `WIFI_INTERFACE` the dashboard lists the interfaces it can see and
|
|
nothing is scanned. Set the variable to one of the listed wireless interfaces
|
|
and restart the container.
|
|
|
|
## Storage
|
|
|
|
Samples and events are stored in SQLite at `DB_PATH` (default
|
|
`/data/healthcheck.db`). The compose file mounts a named volume at `/data`, so
|
|
the database survives `docker compose down`, image rebuilds and `up -d`. Only
|
|
`docker compose down -v` or `docker volume rm` deletes it. A bind mount works
|
|
as well, for example `- /opt/healthcheck:/data`.
|
|
|
|
Samples older than `SAMPLE_RETENTION_DAYS` (default 7) are deleted hourly.
|
|
Events are kept. Wi-Fi scan results are held in memory only and never written.
|
|
|
|
## Configuration
|
|
|
|
All settings are environment variables. Intervals are in seconds.
|
|
|
|
| Variable | Default | Meaning |
|
|
| --- | --- | --- |
|
|
| `PORT` | `9999` | HTTP port |
|
|
| `DB_PATH` | `/data/healthcheck.db` | SQLite file |
|
|
| `SAMPLE_RETENTION_DAYS` | `7` | How long graph samples are kept |
|
|
| `DEVICE_NAME` | hostname | Shown on the dashboard and in Home Assistant |
|
|
| `DEVICE_ID` | slug of `DEVICE_NAME` | Used in MQTT topics and unique ids |
|
|
| `REACH_TARGETS` | `1.1.1.1:443,8.8.8.8:443,9.9.9.9:443` | TCP connect targets, `host:port` |
|
|
| `REACH_INTERVAL` | `30` | |
|
|
| `REACH_TIMEOUT` | `3` | Connect timeout per target |
|
|
| `DNS_NAME` | `cloudflare.com` | Name resolved through the system resolver |
|
|
| `DNS_INTERVAL` | `60` | |
|
|
| `PUBLIC_IP_URLS` | ipify, ifconfig.me, icanhazip | Tried in order, first answer wins |
|
|
| `PUBLIC_IP_INTERVAL` | `300` | |
|
|
| `PUBLIC_IP_TIMEOUT` | `5` | |
|
|
| `UPTIME_INTERVAL` | `60` | Reboot detection via `/proc/uptime` |
|
|
| `WIFI_INTERFACE` | empty | Wireless interface to use, empty disables Wi-Fi |
|
|
| `WIFI_LINK_INTERVAL` | `30` | Signal, bitrate, retries of the current link |
|
|
| `WIFI_SCAN_INTERVAL` | `60` | Scan for networks in range |
|
|
| `MQTT_HOST` | empty | Broker host, empty disables MQTT |
|
|
| `MQTT_PORT` | `1883` | |
|
|
| `MQTT_USERNAME` | empty | |
|
|
| `MQTT_PASSWORD` | empty | |
|
|
| `MQTT_DISCOVERY_PREFIX` | `homeassistant` | Must match the MQTT integration setting |
|
|
| `LOG_LEVEL` | `INFO` | |
|
|
|
|
## Events on the timeline
|
|
|
|
- Internet unreachable / reachable again (all targets failed, then any succeeded)
|
|
- DNS resolution failed / working again
|
|
- Public IP is X, Public IP changed from X to Y
|
|
- Wi-Fi connected, disconnected, roamed to another BSSID
|
|
- Host rebooted
|
|
- Health check service started
|
|
|
|
## Home Assistant
|
|
|
|
Set `MQTT_HOST` (and credentials if the broker needs them). On connect the
|
|
service publishes retained discovery messages under
|
|
`homeassistant/<component>/healthcheck_<device_id>/<key>/config`, all pointing
|
|
to one device, then publishes state to `healthcheck/<device_id>/state` and
|
|
availability to `healthcheck/<device_id>/availability`. When Home Assistant
|
|
restarts it announces itself on `homeassistant/status` and the service
|
|
re-publishes discovery.
|
|
|
|
Entities per device:
|
|
|
|
- Internet, DNS, Wi-Fi (binary sensors, `connectivity` class)
|
|
- Internet latency, DNS latency (ms)
|
|
- Public IP
|
|
- Host uptime
|
|
- Wi-Fi SSID, BSSID, channel, signal (dBm), TX bitrate (Mbit/s), networks in range
|
|
|
|
## Development
|
|
|
|
```sh
|
|
python3 -m unittest discover -s tests
|
|
DB_PATH=./data/hc.db python3 app.py
|
|
```
|