thermal+ha: don't lose boot-time pages; stop HA gracefully
van-thermal fires its first sample seconds after start, and the Pi often boots hot — order after network-online.target and retry network failures in the Pushover send path (3 attempts, 15s apart) so that page survives DNS not being up yet. Non-200 responses still don't retry. The Quadlet gets StopTimeout=120: podman's default 10s window SIGKILLed HA mid-flush and the recorder complained about an unclean sqlite shutdown on every start. Verified clean after a full stop/start cycle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
3add42a0cb
commit
6fe683000d
@@ -24,6 +24,9 @@ Environment=TZ=America/Toronto
|
||||
# recovery. HA is rootful + host-net anyway.
|
||||
PodmanArgs=--security-opt apparmor=unconfined
|
||||
AddCapability=NET_ADMIN NET_RAW
|
||||
# Podman's default 10s stop window SIGKILLs HA mid-flush and the recorder
|
||||
# complains about an unclean sqlite shutdown on the next start.
|
||||
StopTimeout=120
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
|
||||
+17
-10
@@ -73,7 +73,7 @@ def load_creds(cfg):
|
||||
return None
|
||||
|
||||
|
||||
def pushover(cfg, title, message, priority=0):
|
||||
def pushover(cfg, title, message, priority=0, attempts=3, retry_delay=15):
|
||||
creds = load_creds(cfg)
|
||||
if not creds:
|
||||
log(f"pushover skipped (no credentials): {title} — {message}", "warn")
|
||||
@@ -84,15 +84,22 @@ def pushover(cfg, title, message, priority=0):
|
||||
"message": message, "priority": priority,
|
||||
}).encode()
|
||||
req = urllib.request.Request("https://api.pushover.net/1/messages.json", data=data)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
ok = resp.status == 200
|
||||
if not ok:
|
||||
log(f"pushover HTTP {resp.status}", "warn")
|
||||
return ok
|
||||
except Exception as e:
|
||||
log(f"pushover send failed: {e}", "warn")
|
||||
return False
|
||||
# Retry network failures (DNS not up yet at boot, WAN flap) — they block the
|
||||
# sample loop briefly, which is fine at this cadence. A non-200 means Pushover
|
||||
# rejected the request (bad token etc.); retrying won't change that.
|
||||
for attempt in range(1, attempts + 1):
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
ok = resp.status == 200
|
||||
if not ok:
|
||||
log(f"pushover HTTP {resp.status}", "warn")
|
||||
return ok
|
||||
except Exception as e:
|
||||
last = attempt == attempts
|
||||
log(f"pushover send failed ({attempt}/{attempts}): {e}", "warn")
|
||||
if not last:
|
||||
time.sleep(retry_delay)
|
||||
return False
|
||||
|
||||
|
||||
def load_config():
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
[Unit]
|
||||
Description=Temperature monitor (CPU + NVMe) for the campervan router
|
||||
After=local-fs.target
|
||||
# Order after network-online so a boot-time threshold crossing (common: the Pi
|
||||
# boots hot) can actually reach Pushover — the very first sample fires within
|
||||
# seconds of start. If no WAN comes up, wait-online times out and we start anyway.
|
||||
After=local-fs.target network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
|
||||
Reference in New Issue
Block a user