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.
|
# recovery. HA is rootful + host-net anyway.
|
||||||
PodmanArgs=--security-opt apparmor=unconfined
|
PodmanArgs=--security-opt apparmor=unconfined
|
||||||
AddCapability=NET_ADMIN NET_RAW
|
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]
|
[Service]
|
||||||
Restart=always
|
Restart=always
|
||||||
|
|||||||
+9
-2
@@ -73,7 +73,7 @@ def load_creds(cfg):
|
|||||||
return None
|
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)
|
creds = load_creds(cfg)
|
||||||
if not creds:
|
if not creds:
|
||||||
log(f"pushover skipped (no credentials): {title} — {message}", "warn")
|
log(f"pushover skipped (no credentials): {title} — {message}", "warn")
|
||||||
@@ -84,6 +84,10 @@ def pushover(cfg, title, message, priority=0):
|
|||||||
"message": message, "priority": priority,
|
"message": message, "priority": priority,
|
||||||
}).encode()
|
}).encode()
|
||||||
req = urllib.request.Request("https://api.pushover.net/1/messages.json", data=data)
|
req = urllib.request.Request("https://api.pushover.net/1/messages.json", data=data)
|
||||||
|
# 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:
|
try:
|
||||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||||
ok = resp.status == 200
|
ok = resp.status == 200
|
||||||
@@ -91,7 +95,10 @@ def pushover(cfg, title, message, priority=0):
|
|||||||
log(f"pushover HTTP {resp.status}", "warn")
|
log(f"pushover HTTP {resp.status}", "warn")
|
||||||
return ok
|
return ok
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f"pushover send failed: {e}", "warn")
|
last = attempt == attempts
|
||||||
|
log(f"pushover send failed ({attempt}/{attempts}): {e}", "warn")
|
||||||
|
if not last:
|
||||||
|
time.sleep(retry_delay)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Temperature monitor (CPU + NVMe) for the campervan router
|
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]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
|
|||||||
Reference in New Issue
Block a user