Add network monitoring, dashboard, and Home Assistant MQTT publishing

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
This commit is contained in:
Milan PandurovandClaude Fable 5.1 committed 2026-09-17 15:42:09 +02:00
1 parent aace768660
commit 7206e0c4db
13 files changed
+1509 -6

No files matched your search

+88
View File
@@ -0,0 +1,88 @@
import os
import re
import socket
def _int(name, default):
raw = os.environ.get(name)
if raw is None or raw.strip() == "":
return default
try:
return int(raw)
except ValueError:
return default
def _float(name, default):
raw = os.environ.get(name)
if raw is None or raw.strip() == "":
return default
try:
return float(raw)
except ValueError:
return default
def _list(name, default):
raw = os.environ.get(name, default)
return [item.strip() for item in raw.split(",") if item.strip()]
def _slug(value):
return re.sub(r"[^a-z0-9_-]+", "_", value.lower()).strip("_") or "device"
class Config:
def __init__(self):
env = os.environ
hostname = socket.gethostname()
self.port = _int("PORT", 9999)
self.log_level = env.get("LOG_LEVEL", "INFO")
self.db_path = env.get("DB_PATH", "/data/healthcheck.db")
self.sample_retention_days = _int("SAMPLE_RETENTION_DAYS", 7)
self.device_name = env.get("DEVICE_NAME", hostname)
self.device_id = _slug(env.get("DEVICE_ID", self.device_name))
self.reach_targets = _list("REACH_TARGETS", "1.1.1.1:443,8.8.8.8:443,9.9.9.9:443")
self.reach_interval = _int("REACH_INTERVAL", 30)
self.reach_timeout = _float("REACH_TIMEOUT", 3.0)
self.dns_name = env.get("DNS_NAME", "cloudflare.com")
self.dns_interval = _int("DNS_INTERVAL", 60)
self.public_ip_urls = _list(
"PUBLIC_IP_URLS",
"https://api.ipify.org,https://ifconfig.me/ip,https://icanhazip.com",
)
self.public_ip_interval = _int("PUBLIC_IP_INTERVAL", 300)
self.public_ip_timeout = _float("PUBLIC_IP_TIMEOUT", 5.0)
self.uptime_interval = _int("UPTIME_INTERVAL", 60)
self.wifi_interface = env.get("WIFI_INTERFACE", "").strip()
self.wifi_link_interval = _int("WIFI_LINK_INTERVAL", 30)
self.wifi_scan_interval = _int("WIFI_SCAN_INTERVAL", 60)
self.mqtt_host = env.get("MQTT_HOST", "").strip()
self.mqtt_port = _int("MQTT_PORT", 1883)
self.mqtt_username = env.get("MQTT_USERNAME", "")
self.mqtt_password = env.get("MQTT_PASSWORD", "")
self.mqtt_discovery_prefix = env.get("MQTT_DISCOVERY_PREFIX", "homeassistant")
def public(self):
return {
"device_name": self.device_name,
"device_id": self.device_id,
"reach_targets": self.reach_targets,
"reach_interval": self.reach_interval,
"dns_name": self.dns_name,
"dns_interval": self.dns_interval,
"public_ip_interval": self.public_ip_interval,
"wifi_interface": self.wifi_interface,
"wifi_link_interval": self.wifi_link_interval,
"wifi_scan_interval": self.wifi_scan_interval,
"sample_retention_days": self.sample_retention_days,
"mqtt_enabled": bool(self.mqtt_host),
}