2 Commits

5 changed files with 66 additions and 21 deletions

View File

@@ -77,9 +77,16 @@ bash <(curl -fsSL https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main
| `OBF_PORTS` | `2083,5443,993` | Порты обфускатора |
| `TG_TOKEN`/`TG_CHAT` | пусто | Telegram-уведомления |
| `ALLOW_PLAIN_WG` | пусто | `=1` открыть порт 51820 для iOS WireGuard (без обфускации) |
| `CHANNEL` | `stable` | канал: `stable` (ветка main) или `beta` |
После установки панель напечатает адрес, логин, пароль и **API key** — сохрани их.
> **Канал:** по умолчанию ставится **стабильная** версия (ветка `main`). Бета — по желанию:
> ```bash
> CHANNEL=beta bash <(curl -fsSL https://raw.githubusercontent.com/andrey271192/PCA_Phobos/main/install.sh)
> ```
> Переключение после установки: `phobos-update beta` / `phobos-update main`.
### Вторичный сервер (для failover / балансировки)
```bash

View File

@@ -1 +1 @@
1.2.2
1.2.4

52
app.py
View File

@@ -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"🔴 <b>Server {ip}</b> DOWN!")
elif was_up == "down" and now_status == "ok":
elif was_up == "down" and eff == "ok":
tg_send(f"🟢 <b>Server {ip}</b> 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():
<p>{tr("Установленная версия","Installed version")}: <b style="color:#a5b4fc">{panel_version()}</b></p>
<form method="post" class="form-row">
<input type="hidden" name="action" value="update">
<input type="text" name="target" placeholder="main / v1.1.0" style="width:150px">
<input type="text" name="target" placeholder="main / beta / v1.2.3" style="width:150px">
<button class="btn btn-primary" type="submit" onclick="return confirm('{tr("Обновить панель и скрипты?","Update panel and scripts?")}')">{tr("Обновить","Update")}</button>
</form>
<form method="post" style="display:inline">
<input type="hidden" name="action" value="check">
<button class="btn btn-sm" style="background:#0ea5e9" type="submit">{tr("Проверить обновления","Check for updates")}</button>
</form>
<form method="post" style="margin-top:8px">
<input type="hidden" name="action" value="rollback">
<button class="btn btn-danger btn-sm" type="submit" onclick="return confirm('{tr("Откатить на предыдущую версию?","Roll back to the previous version?")}')">{tr("Откатить на предыдущую","Rollback")}</button>

View File

@@ -25,7 +25,11 @@ PANEL_PASS="${PANEL_PASS:-OcAdmin2026!}"
TG_TOKEN="${TG_TOKEN:-}"
TG_CHAT="${TG_CHAT:-}"
OBF_PORTS="${OBF_PORTS:-2083,5443,993}"
PCA_BRANCH="${PCA_BRANCH:-main}"
# Канал: stable по умолчанию (ветка main). Бета — по желанию: CHANNEL=beta (или PCA_BRANCH=beta).
CHANNEL="${CHANNEL:-stable}"
if [ -z "${PCA_BRANCH:-}" ]; then
case "$CHANNEL" in beta) PCA_BRANCH="beta";; *) PCA_BRANCH="main";; esac
fi
GH_TOKEN="${GH_TOKEN:-}" # set for private-repo installs; empty for public
PHOBOS_DIR="/opt/Phobos"
PANEL_DIR="/opt/phobos-panel"

View File

@@ -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"