diff --git a/.env.example b/.env.example index 083dea0..2bf952e 100644 --- a/.env.example +++ b/.env.example @@ -9,3 +9,7 @@ MYSQL_ROOT_PASSWORD=root_password COLLECT_INTERVAL_SECONDS=900 APP_TIMEZONE=Europe/London FLASK_SECRET_KEY=replace-with-a-random-string + +# Optional: healthcheck.io-style heartbeat URL +# On success: GET | On failure: POST /fail +HEALTHCHECK_URL=https://health.pattinson.org/ping/2009942a-9877-411e-89b4-3d17382c8286 diff --git a/app/collector.py b/app/collector.py index ab73d36..531de54 100644 --- a/app/collector.py +++ b/app/collector.py @@ -2,6 +2,8 @@ from __future__ import annotations import logging import time +import urllib.error +import urllib.request from datetime import datetime, timezone from typing import Any @@ -73,6 +75,27 @@ def save_reading(device: Device, status: dict[str, Any]) -> None: ) +def _ping_healthcheck(success: bool, detail: str | None = None) -> None: + url = config.healthcheck_url + if not url: + logger.debug("Healthcheck ping skipped: HEALTHCHECK_URL not configured") + return + try: + if success: + logger.debug("Pinging healthcheck (success): %s", url) + urllib.request.urlopen(url, timeout=10) # noqa: S310 + logger.info("Healthcheck ping sent successfully") + else: + fail_url = f"{url}/fail" + logger.debug("Pinging healthcheck (fail): %s — %s", fail_url, detail) + payload = (detail or "").encode() + req = urllib.request.Request(fail_url, data=payload, method="POST") + urllib.request.urlopen(req, timeout=10) # noqa: S310 + logger.info("Healthcheck fail ping sent: %s", detail) + except Exception as exc: # noqa: BLE001 + logger.warning("Healthcheck ping failed: %s", exc) + + def collect_once(client: SwitchBotClient) -> None: logger.info("Starting collection cycle") devices = sync_devices(client) @@ -106,8 +129,10 @@ def main() -> int: started = time.monotonic() try: collect_once(client) - except Exception: + _ping_healthcheck(True) + except Exception as exc: logger.exception("Collector cycle failed") + _ping_healthcheck(False, str(exc)) elapsed = time.monotonic() - started time.sleep(max(1, interval - elapsed)) diff --git a/app/config.py b/app/config.py index f27289c..b14f263 100644 --- a/app/config.py +++ b/app/config.py @@ -20,6 +20,7 @@ class Config: collect_interval_seconds: int = int(os.getenv("COLLECT_INTERVAL_SECONDS", "900")) app_timezone: str = os.getenv("APP_TIMEZONE", "Europe/London") flask_secret_key: str = os.getenv("FLASK_SECRET_KEY", "dev-only-secret") + healthcheck_url: str | None = os.getenv("HEALTHCHECK_URL") config = Config() diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index 95735dd..d059587 100644 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -6,8 +6,7 @@

Today so far

-

Temperature dashboard

-

Local timezone: {{ timezone }}. Collector interval: {{ collect_interval_seconds // 60 }} min.

+

Vet Temperatures

Make report
diff --git a/docker-compose.yml b/docker-compose.yml index 81899c6..401a0cb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,6 +40,7 @@ services: SWITCHBOT_SECRET: ${SWITCHBOT_SECRET:-} COLLECT_INTERVAL_SECONDS: ${COLLECT_INTERVAL_SECONDS:-900} APP_TIMEZONE: ${APP_TIMEZONE:-Europe/London} + HEALTHCHECK_URL: ${HEALTHCHECK_URL:-} depends_on: db: condition: service_healthy