feat(hybrid-gateway): integrate muse-cli with Cloudflare netns isolation, symmetric sidechat routing, and 646-pip sync unblock

This commit is contained in:
operator
2026-10-04 22:54:01 +00:00
parent 3d5fbe5aeb
commit 1a271b1bbd
60 changed files with 5068 additions and 55 deletions
+94
View File
@@ -0,0 +1,94 @@
#!/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())