95 lines
2.9 KiB
Python
Executable File
95 lines
2.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Thread keepalive timer entry point (runs on bl).
|
|
|
|
Reads keepalive-config.json, ticks every registered thread:
|
|
ensure reachable -> ping if idle past threshold -> recreate if dead.
|
|
|
|
Designed to run from a systemd timer (e.g. every 5 minutes). Each tick is
|
|
idempotent and cheap: threads that are alive and recently active are a
|
|
single navigate+verify (no ping sent).
|
|
|
|
Usage:
|
|
keepalive-timer.py [--config PATH] [--state PATH] [--dry-run] [--status]
|
|
|
|
--status print the registry with idle times and exit (no changes)
|
|
|
|
Exit codes: 0 = all ok, 1 = one or more threads failed, 2 = config error.
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from keepalive import Keepalive
|
|
|
|
NETVM_ROOT = os.environ.get("NETVM_ROOT", "/home/super/Projects/NetVM")
|
|
DEFAULT_CONFIG = os.path.join(NETVM_ROOT, "keepalive-config.json")
|
|
DEFAULT_STATE = os.path.join(NETVM_ROOT, "keepalive-threads.json")
|
|
|
|
|
|
def load_config(path):
|
|
try:
|
|
with open(path) as f:
|
|
cfg = json.load(f)
|
|
except FileNotFoundError:
|
|
print(f"keepalive: config not found: {path}", file=sys.stderr)
|
|
return None
|
|
except Exception as e:
|
|
print(f"keepalive: bad config {path}: {e}", file=sys.stderr)
|
|
return None
|
|
threads = cfg.get("threads", [])
|
|
if not isinstance(threads, list):
|
|
print("keepalive: config 'threads' must be a list", file=sys.stderr)
|
|
return None
|
|
return threads
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--config", default=DEFAULT_CONFIG)
|
|
ap.add_argument("--state", default=DEFAULT_STATE)
|
|
ap.add_argument("--dry-run", action="store_true")
|
|
ap.add_argument("--status", action="store_true")
|
|
args = ap.parse_args()
|
|
|
|
ka = Keepalive(state_path=args.state, dry_run=args.dry_run)
|
|
|
|
if args.status:
|
|
print(json.dumps(ka.status(), indent=2))
|
|
return 0
|
|
|
|
threads = load_config(args.config)
|
|
if threads is None:
|
|
return 2
|
|
|
|
results = {}
|
|
for t in threads:
|
|
key = t.get("key")
|
|
agent = t.get("agent", "opm")
|
|
if not key or not t.get("enabled", True):
|
|
continue
|
|
try:
|
|
status = ka.tick(
|
|
key,
|
|
agent,
|
|
idle_threshold_s=int(t.get("idle_threshold_s", 3600)),
|
|
max_retries=int(t.get("max_retries", 3)),
|
|
ping_message=t.get("ping_message"),
|
|
)
|
|
except Exception as e:
|
|
status = f"error: {e}"
|
|
ka.log_event("keepalive_tick_error",
|
|
{"key": key, "agent": agent, "error": str(e)[:200]})
|
|
results[key] = status
|
|
print(f"keepalive: {key}@{agent} -> {status}")
|
|
|
|
failed = [k for k, v in results.items()
|
|
if v in ("failed",) or v.startswith("error")]
|
|
return 1 if failed else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|