From 6372d649da7af3a3c0fd4e0d18fa0e6656ecbb5d Mon Sep 17 00:00:00 2001 From: savsis Date: Sun, 4 Oct 2026 04:48:39 +0500 Subject: [PATCH] fix: node installer refuses a non-empty server before touching it and reports active only after xray stays up The install script now checks for a foreign proxy (xray directory, docker marzban/xray) and busy ports before adding the management key or writing anything, and tells the panel the node is active only after xray has stayed up with its port listening for three checks in a row. --- .github/workflows/ci.yml | 3 +++ nodeprov.py | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17a99bc..4bf5f92 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,6 +61,9 @@ jobs: } script = nodeprov.render_install_script(node) assert "PRIVKEY" in script + assert "PREFLIGHT_FAIL" in script and "443 2053 2087" in script, "node install must refuse a non-empty server before touching it" + assert script.index("PREFLIGHT_FAIL") < script.index("authorized_keys"), "preflight must run before the management key is added" + assert "OK=$((OK+1))" in script and "STATUS=failed" in script, "node must report active only after xray stays up" assert len(script) > 500 print("node install script rendered OK,", len(script), "bytes") PYEOF diff --git a/nodeprov.py b/nodeprov.py index 6eda908..1f2c94f 100644 --- a/nodeprov.py +++ b/nodeprov.py @@ -71,6 +71,28 @@ set -e echo "== MBS Panel node install ==" export DEBIAN_FRONTEND=noninteractive +PREFLIGHT_FAIL=0 +if ! {{ [ -f /usr/local/etc/xray/config.json ] && grep -q mbs-grpc /usr/local/etc/xray/config.json; }}; then + if [ -d /usr/local/bin/xray ]; then + echo "СТОП: /usr/local/bin/xray это каталог, на сервере уже стоит чужой прокси (Marzban-node и подобное)" + PREFLIGHT_FAIL=1 + fi + if command -v docker >/dev/null 2>&1 && docker ps --format '{{{{.Names}}}}' 2>/dev/null | grep -qiE 'marzban|xray|remnawave|v2ray|hysteria'; then + echo "СТОП: в Docker на этом сервере уже крутится прокси" + PREFLIGHT_FAIL=1 + fi + for p in {ports}; do + if ss -ltn 2>/dev/null | awk '{{print $4}}' | grep -qE "[:.]$p$"; then + echo "СТОП: порт $p уже занят другим процессом" + PREFLIGHT_FAIL=1 + fi + done +fi +if [ "$PREFLIGHT_FAIL" = "1" ]; then + echo "Нужен чистый сервер. Ничего не установлено и не изменено, ключ панели не добавлен." + exit 1 +fi + mkdir -p /root/.ssh chmod 700 /root/.ssh curl -Ls https://{panel}/mgmt-pubkey.txt >> /root/.ssh/authorized_keys @@ -91,9 +113,19 @@ XRAYCFG command -v ufw >/dev/null 2>&1 && {{ ufw allow 22/tcp || true; {ufw_rules} }} systemctl enable xray >/dev/null 2>&1 || true systemctl restart xray -sleep 1 -STATUS=$(systemctl is-active xray) +OK=0 +for i in 1 2 3 4 5 6 7 8; do + sleep 1 + if systemctl is-active --quiet xray && ss -ltn 2>/dev/null | awk '{{print $4}}' | grep -qE "[:.]{first_port}$"; then + OK=$((OK+1)) + else + OK=0 + fi + if [ "$OK" -ge 3 ]; then break; fi +done +if [ "$OK" -ge 3 ]; then STATUS=active; else STATUS=failed; fi echo "xray status: $STATUS" +if [ "$STATUS" != "active" ]; then journalctl -u xray -n 15 --no-pager 2>/dev/null || true; fi {hysteria_block} MY_IP=$(curl -s https://api.ipify.org || echo unknown) @@ -234,9 +266,11 @@ def render_install_script(node: dict) -> str: sni=node["sni"], ) + ports = " ".join(str(t["port"]) for t in transports) return SELF_INSTALL_SCRIPT.format( panel=PANEL_DOMAIN, token=node["provision_token"], config_json=config_json, certbot_block=certbot_block, hysteria_block=hysteria_block, ufw_rules=ufw_rules, + ports=ports, first_port=transports[0]["port"], )