utils/omniroute_offload/omniroute_offload/_gw_notify.py
Krzysztof kuhy Rudnicki 7af6afe51a
Some checks failed
dependency freshness / dependencies on newest stable (push) Has been cancelled
file length / file-length (push) Has been cancelled
md-naming / markdown naming convention (push) Has been cancelled
omniroute_offload pre-commit / pre-commit (push) Has been cancelled
palette drift / palette (push) Has been cancelled
repo-contract / repo contract (push) Has been cancelled
shared gate tests / gate unit tests (push) Has been cancelled
omniroute_offload: always-on gateway ops
omniroute-gateway start|health|update|backup|combo keeps the local
OmniRoute gateway up. systemd --user timers: health every 10 min
(real free-best chat; restart <=1/30 min, reactivate providers whose
cooldown ended, notify once per outage), update weekly Sun 12:00
(backup, newest stable -web tag, rollback of image + volume on a
failed health check).

Failure modes: health/update fail loudly (exit 1 + desktop + Signal);
all providers cooling = degraded (exit 0, one notice/day); a missing
signal-bot .env logs and exits non-zero on alert paths; health skips
while update holds the lock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWZSdJmNjGUvLnHJ4WG
2026-11-05 13:37:35 +01:00

74 lines
2.5 KiB
Python

"""Tell kuhy: one replaceable desktop popup plus a Signal DM from the bot account."""
from __future__ import annotations
import subprocess
import sys
from typing import TYPE_CHECKING
from omniroute_offload import _gw_api as api
if TYPE_CHECKING:
from pathlib import Path
from omniroute_offload._gw_config import Config
class NotifyError(RuntimeError):
"""A channel could not be used."""
def _read_env(path: Path) -> dict[str, str]:
pairs = (
line.split("=", 1)
for line in path.read_text().splitlines()
if "=" in line and not line.lstrip().startswith("#")
)
return {k.strip(): v.strip() for k, v in pairs}
def signal(cfg: Config, text: str) -> None:
"""DM the first TRUSTED_INVITERS number from the signal-bot account."""
try:
env = _read_env(cfg.signal_env)
owner = env["TRUSTED_INVITERS"].split(",")[0].strip()
body = {"number": env["PHONE_NUMBER"], "recipients": [owner], "message": text}
api.call(f"{env['API_BASE_URL']}/v2/send", body, timeout=20)
except (OSError, KeyError, api.ApiError) as err:
msg = f"signal: {err!r}"
raise NotifyError(msg) from err
def desktop(cfg: Config, text: str, urgency: str) -> None:
"""notify-send, replacing this instance's previous popup instead of stacking."""
id_file = cfg.state_dir / "notify_id"
previous = id_file.read_text().strip() if id_file.exists() else "0"
proc = subprocess.run(
[
"notify-send", "--app-name=omniroute", "--print-id",
f"--replace-id={previous}", f"--urgency={urgency}",
f"OmniRoute ({cfg.name})", text,
],
capture_output=True, text=True, check=False, timeout=10,
) # fmt: skip
if proc.returncode:
msg = f"notify-send: {proc.stderr.strip()}"
raise NotifyError(msg)
id_file.write_text(proc.stdout.strip())
def notify(cfg: Config, text: str, *, urgency: str = "normal") -> bool:
"""Send on both channels, log each failure; True only when Signal got it."""
print(f"notify: {text}", file=sys.stderr)
cfg.state_dir.mkdir(parents=True, exist_ok=True)
try:
desktop(cfg, text, urgency)
except (NotifyError, OSError, subprocess.SubprocessError) as err:
print(f"desktop notify failed: {err}", file=sys.stderr)
try:
signal(cfg, f"OmniRoute ({cfg.name}): {text}")
except NotifyError as err:
print(f"signal notify failed: {err}", file=sys.stderr)
return False
return True