Files
agento3/scripts/daemon.py
akiba 085cc7a140 AO3 Mirror v4 — initial commit
Cookie-aware proxy pool with CF challenge solving
- Tiered proxy pool (fast 50 + main 676)
- Per-proxy cf_clearance cookie persistence
- CF challenge detection + user-browser solving
- Safari + Chrome TLS fingerprint rotation
- Async FastAPI backend with LRU cache
- Passive daemon with systemd supervision
- Stats dashboard + Prometheus metrics
2026-06-23 08:04:04 +00:00

147 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
AO3 Mirror - 被动守护进程 (Daemon v2)
不再主动 e2e 检查或重启 healthy worker。
职责:
1. 每 30s 检查 worker 是否活着,只有连续 3 轮都挂掉才重启
2. 不做 e2e不做 caddy 检查(让 systemd 管)
3. 每 5min 写状态文件
"""
import json
import os
import subprocess
import sys
import time
import logging
import signal
BASE_DIR = "/home/ubuntu/ao3-mirror"
SHM_DIR = "/dev/shm/ao3"
WORKER_PORTS = [8081, 8082]
CHECK_INTERVAL = 30
STATUS_FILE = f"{SHM_DIR}/status.json"
os.makedirs(SHM_DIR, exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [DAEMON] %(message)s",
handlers=[logging.StreamHandler()],
)
logger = logging.getLogger("ao3-daemon")
state = {
"started_at": time.time(),
"consecutive_dead": {p: 0 for p in WORKER_PORTS},
"restarts_today": 0,
"alerts": [],
}
def _http_get(url: str, timeout: int = 5) -> tuple[bool, str]:
import urllib.request
try:
req = urllib.request.Request(url)
resp = urllib.request.urlopen(req, timeout=timeout)
return (resp.status == 200, resp.read().decode("utf-8", errors="replace")[:200])
except Exception as e:
return (False, str(e)[:200])
def check_worker(port: int) -> bool:
ok, body = _http_get(f"http://127.0.0.1:{port}/health", timeout=4)
if ok:
try:
return json.loads(body).get("status") == "ok"
except Exception:
pass
return False
def restart_worker(port: int):
logger.warning(f"Restarting worker on port {port}")
state["restarts_today"] += 1
subprocess.run(["fuser", "-k", f"{port}/tcp"], capture_output=True, timeout=10)
time.sleep(1)
python_bin = "/home/ubuntu/.hermes/hermes-agent/venv/bin/python3"
i = WORKER_PORTS.index(port)
log_file = f"{BASE_DIR}/worker-{i}.log"
proc = subprocess.Popen(
["taskset", "-c", str(i), python_bin, "-m", "uvicorn", "app:app",
"--host", "127.0.0.1", "--port", str(port), "--workers", "1",
"--loop", "uvloop", "--http", "httptools",
"--log-level", "warning", "--timeout-keep-alive", "30"],
cwd=BASE_DIR, stdout=open(log_file, "a"), stderr=open(log_file, "a"),
)
pid_file = f"{SHM_DIR}/worker-{i}.pid"
with open(pid_file, "w") as f:
f.write(str(proc.pid))
logger.info(f"Worker {port} restarted (PID: {proc.pid})")
def check_workers():
"""Only restart if worker has been dead for 3 consecutive checks (90s)."""
for port in WORKER_PORTS:
if check_worker(port):
state["consecutive_dead"][port] = 0
else:
state["consecutive_dead"][port] += 1
if state["consecutive_dead"][port] >= 3:
restart_worker(port)
state["consecutive_dead"][port] = 0
dead_count = sum(1 for p in WORKER_PORTS if state["consecutive_dead"][p] > 0)
alive = len(WORKER_PORTS) - dead_count
if dead_count > 0:
logger.info(f"{alive}/{len(WORKER_PORTS)} workers alive ({dead_count} degraded)")
def save_status():
worker_status = {}
for i, port in enumerate(WORKER_PORTS):
worker_status[f"worker_{i}"] = {
"port": port, "healthy": check_worker(port), "uptime_s": int(time.time() - state["started_at"]),
}
data = {
"timestamp": time.time(), "uptime": int(time.time() - state["started_at"]),
"workers": worker_status, "restarts_today": state["restarts_today"],
}
with open(STATUS_FILE + ".tmp", "w") as f:
json.dump(data, f)
os.rename(STATUS_FILE + ".tmp", STATUS_FILE)
def handle_signal(signum, frame):
logger.info(f"Signal {signum}, exiting")
sys.exit(0)
def main():
signal.signal(signal.SIGTERM, handle_signal)
signal.signal(signal.SIGINT, handle_signal)
logger.info("Daemon v2 started (passive mode)")
logger.info(f"Watching {WORKER_PORTS}, check every {CHECK_INTERVAL}s, restart after 3 missed checks")
# Initial startup: start all workers immediately
for port in WORKER_PORTS:
if not check_worker(port):
restart_worker(port)
time.sleep(2)
state["consecutive_dead"][port] = 0
time.sleep(5) # Let them settle
last_status = 0
while True:
try:
check_workers()
now = time.time()
if now - last_status >= 300:
save_status()
last_status = now
except Exception as e:
logger.error(f"Loop error: {e}")
time.sleep(CHECK_INTERVAL)
if __name__ == "__main__":
main()