doorbell: add ntfy token auth to listener poll + authenticated kuma canary
- doorbell-listener.py: poll private ntfy topics with auth= (Bearer token) to stop 403/429; add probe-tag routing to DoorbellProbe - scripts/kuma-heartbeat.sh: publish health canary to ntfy.sh with Authorization: Bearer (was anonymous -> rate limited -> false down in Kuma); read token from .secrets/ntfy-token
This commit is contained in:
@@ -26,13 +26,18 @@ Env vars (all optional):
|
||||
POLL_INTERVAL seconds between polls, default 30
|
||||
STATE_DIR default "/data/doorbell-listener"
|
||||
NTFY_TOPICS comma-separated override, default "ALERT_klubhaus_topic_test,ALERT_klubhaus_topic"
|
||||
NTFY_TOKEN ntfy access token (tk_...). Adds the auth= query param the
|
||||
ntfy.sh topics require; without it, polling the private
|
||||
topics gets 403/429 rate-limited by ntfy.sh.
|
||||
"""
|
||||
import base64
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import urllib.parse
|
||||
from pathlib import Path
|
||||
|
||||
import paho.mqtt.client as mqtt
|
||||
@@ -40,6 +45,7 @@ import requests
|
||||
|
||||
DEFAULT_TOPICS = "ALERT_klubhaus_topic_test,ALERT_klubhaus_topic"
|
||||
NTFY_TOPICS = [t.strip() for t in os.environ.get("NTFY_TOPICS", DEFAULT_TOPICS).split(",") if t.strip()]
|
||||
NTFY_TOKEN = os.environ.get("NTFY_TOKEN", "")
|
||||
|
||||
MQTT_HOST = os.environ.get("MQTT_HOST", "mosquitto")
|
||||
MQTT_PORT = int(os.environ.get("MQTT_PORT", "1883"))
|
||||
@@ -72,6 +78,14 @@ def log(msg: str) -> None:
|
||||
print(msg, flush=True)
|
||||
|
||||
|
||||
def auth_param() -> str:
|
||||
"""Raw base64 of 'Bearer <token>' with no trailing '=' — mirrors the
|
||||
doorbell frontend's buildAuthParam(). Empty string when no token set."""
|
||||
if not NTFY_TOKEN:
|
||||
return ""
|
||||
return base64.b64encode(f"Bearer {NTFY_TOKEN}".encode()).decode().rstrip("=")
|
||||
|
||||
|
||||
def last_id_path(topic: str) -> Path:
|
||||
safe = topic.replace("/", "_").replace(" ", "_")
|
||||
return STATE_DIR / f"last_id_{safe}"
|
||||
@@ -112,6 +126,8 @@ def start_flash(client: mqtt.Client, count: int, interval: float, topic: str) ->
|
||||
def poll_topic(client: mqtt.Client, topic: str, last_id: str) -> str:
|
||||
"""Poll one topic. Returns the latest message id seen (or last_id)."""
|
||||
url = f"https://ntfy.sh/{topic}/json?poll=1"
|
||||
if auth_param():
|
||||
url += f"&auth={urllib.parse.quote(auth_param())}"
|
||||
if last_id:
|
||||
url += f"&since={last_id}"
|
||||
log(f"[{topic}] polling {url}")
|
||||
|
||||
@@ -8,6 +8,7 @@ CONTAINER="${DB_CONTAINER:-doorbell-listener}"
|
||||
|
||||
[ -s "$TOKEN_FILE" ] || exit 1
|
||||
TOKEN="$(tr -d '[:space:]' < "$TOKEN_FILE")"
|
||||
NTFY_TOKEN="${NTFY_TOKEN:-$(tr -d '[:space:]' < /root/nr-flow-validator/infra/.secrets/ntfy-token 2>/dev/null || true)}"
|
||||
|
||||
reason=""
|
||||
|
||||
@@ -25,7 +26,7 @@ else
|
||||
# caused false "down" alerts. The subscribe/deliver leg is continuously
|
||||
# exercised by the production listener consuming real alerts.
|
||||
url="https://ntfy.sh/${CANARY_TOPIC}"
|
||||
if ! curl -fsS -m 10 -o /dev/null -X POST "$url" -d "canary-$(date +%s)" 2>/dev/null; then
|
||||
if ! curl -fsS -m 10 -o /dev/null -X POST "$url" -H "Authorization: Bearer ${NTFY_TOKEN}" -d "canary-$(date +%s)" 2>/dev/null; then
|
||||
reason="${reason}ntfy_publish_failed"
|
||||
fi
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user