From e0ea54e83aa56ca6dc376828ec834f23f19dc4c0 Mon Sep 17 00:00:00 2001 From: phobos Date: Sat, 30 May 2026 23:52:47 +0300 Subject: [PATCH] v1.2.3: debounce server DOWN alerts (3 cycles + retry) fix flapping; update.sh aborts on missing version + --check/--versions + beta channel; panel Check button --- VERSION | 2 +- app.py | 52 ++++++++++++++++++++++++++++++++++++---------------- update.sh | 20 +++++++++++++++++--- 3 files changed, 54 insertions(+), 20 deletions(-) diff --git a/VERSION b/VERSION index 23aa839..0495c4a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.2 +1.2.3 diff --git a/app.py b/app.py index 3147129..f522c08 100644 --- a/app.py +++ b/app.py @@ -479,6 +479,7 @@ def kick_client_by_id(client_id): prev_session_keys = None prev_server_status = {} +_down_streak = {} _last_fanout = 0 server_stats_cache = {} server_handshakes_cache = {} @@ -503,12 +504,19 @@ def get_local_stats(): def get_remote_stats(server): + import urllib.request + url = f"http://{server['ip']}:8444/api/health" + req = urllib.request.Request(url, headers={"X-API-Key": server.get("api_key", "")}) + data = None + for _attempt in range(2): # retry once: smooths transient timeouts (1-worker agent) + try: + data = json.loads(urllib.request.urlopen(req, timeout=6).read()) + break + except Exception: + data = None try: - import urllib.request - url = f"http://{server['ip']}:8444/api/health" - req = urllib.request.Request(url, headers={"X-API-Key": server.get("api_key", "")}) - resp = urllib.request.urlopen(req, timeout=3) - data = json.loads(resp.read()) + if data is None: + raise Exception("no response") # cache handshakes so page renders never block on remote HTTP server_handshakes_cache[server.get("ip", "")] = data.get("handshakes", {}) or {} # Filter peers: only count known client public keys @@ -777,7 +785,7 @@ def push_config_to_router(client_id): def session_monitor(): - global prev_session_keys, prev_server_status, server_stats_cache, _last_fanout + global prev_session_keys, prev_server_status, server_stats_cache, _last_fanout, _down_streak while True: try: s = load_settings() @@ -814,19 +822,24 @@ def session_monitor(): local_stats = get_local_stats() server_stats_cache[SERVER_IP] = local_stats + DOWN_CONFIRM = 3 # consecutive failed cycles before declaring a server DOWN for srv in servers: ip = srv["ip"] stats = get_remote_stats(srv) - server_stats_cache[ip] = stats + raw = stats.get("status", "down") + streak = _down_streak.get(ip, 0) + streak = streak + 1 if raw == "down" else 0 + _down_streak[ip] = streak + # update cache on OK, or only once a DOWN is confirmed (keep last-good on transient) + if raw == "ok" or streak >= DOWN_CONFIRM: + server_stats_cache[ip] = stats + eff = "down" if streak >= DOWN_CONFIRM else "ok" was_up = prev_server_status.get(ip, "ok") - now_status = stats["status"] - - if was_up == "ok" and now_status == "down": + if was_up == "ok" and eff == "down": tg_send(f"🔴 Server {ip} DOWN!") - elif was_up == "down" and now_status == "ok": + elif was_up == "down" and eff == "ok": tg_send(f"🟢 Server {ip} back ONLINE") - - prev_server_status[ip] = now_status + prev_server_status[ip] = eff # periodic config fan-out to secondaries (covers CLI-created clients # and config drift; gated ~5 min, skips down servers) @@ -1371,9 +1384,12 @@ def labels_page(): def settings_page(): s = load_settings() msg = "" - if request.method == "POST" and request.form.get("action") in ("update", "rollback"): - if request.form.get("action") == "update": + if request.method == "POST" and request.form.get("action") in ("update", "rollback", "check"): + _act = request.form.get("action") + if _act == "update": _out = run_update(request.form.get("target", "").strip() or "main") + elif _act == "check": + _out = run_update("--check") else: _out = run_update("--rollback") s = load_settings() @@ -1414,9 +1430,13 @@ def settings_page():

{tr("Установленная версия","Installed version")}: {panel_version()}

- +
+
+ + +
diff --git a/update.sh b/update.sh index 17a2bbe..ed533c0 100755 --- a/update.sh +++ b/update.sh @@ -60,10 +60,19 @@ apply_ref() { done } +ref_exists() { curl -fsSL -m12 "$RAW/$1/app.py" -o /dev/null 2>/dev/null; } + case "${1:-}" in - --version|-v) - echo "Установлено: $CUR" - echo "Доступно: $(latest_version)" + --version|-v|--check) + echo "Установлено: $CUR" + echo "Стабильная: $(latest_version)" + beta=$(curl -fsSL -m8 "$RAW/beta/VERSION" 2>/dev/null | tr -d ' \r\n') + [ -n "$beta" ] && echo "Бета (beta): $beta" + ;; + --versions|--tags) + echo "Доступные версии (git tags):" + curl -fsSL -m10 "https://api.github.com/repos/$REPO/tags?per_page=100" 2>/dev/null \ + | grep -oE '"name": *"[^"]+"' | sed 's/.*"name": *"\(.*\)"/ \1/' || echo " (нет данных)" ;; --list) echo "Бэкапы (для отката):" @@ -83,6 +92,11 @@ case "${1:-}" in ;; *) ref="${1:-main}"; [ "$ref" = "latest" ] && ref="main" + if ! ref_exists "$ref"; then + echo "Версия/ветка '$ref' не найдена на GitHub. Обновление отменено (текущая $CUR не тронута)." + echo "Доступные: phobos-update --versions · стабильная: $(latest_version)" + exit 1 + fi target=$(curl -fsSL -m10 "$RAW/$ref/VERSION" 2>/dev/null | tr -d ' \r\n'); [ -z "$target" ] && target="$ref" echo "Текущая: $CUR -> целевая: $target ($ref)" b=$(do_backup); echo "Бэкап: $b"