diff --git a/CHANGELOG.md b/CHANGELOG.md index da0a7a5..1dfcb28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## Unreleased +- Панель «Серверы»: сверху блок «Как добавить второй сервер» (по-русски, команда secondary, предупреждение про независимые панели и подсети); ручное добавление свёрнуто. install.sh: предупреждение перед вопросом о подсети; подсеть `10.25.11.0/16` → `10.25.11.0/24` вместо молчаливого `10.25.0.0/16`. + # Changelog — PCA Phobos Каналы: **stable** (публичный, без токена) · **beta** / **dev** (приватный repo, токен подписчика). diff --git a/app.py b/app.py index 64a4405..05c0ae4 100644 --- a/app.py +++ b/app.py @@ -4479,6 +4479,26 @@ def servers_page(): {nav('servers')} {msg} +
+

➕ {tr("Как добавить второй сервер", "How to add a second server")}

+

{tr("Сервер управления один — эта панель. Второй VPS ставится как «дополнительный» (secondary) одной командой.", "There is one management server — this panel. The second VPS is installed as a secondary with one command.")}

+
    +
  1. {tr("Возьмите чистый VPS (Ubuntu/Debian). Если на нём уже стоял Phobos целиком — сначала удалите его (uninstall.sh).", "Take a clean VPS (Ubuntu/Debian). If a full Phobos was installed there, uninstall it first (uninstall.sh).")}
  2. +
  3. {tr("Зайдите на него по SSH под root и выполните команду ниже.", "SSH to it as root and run the command below.")}
  4. +
  5. {tr("На вопрос «Подсеть VPN-клиентов» просто нажмите Enter", "At the “VPN clients subnet” prompt just press Enter")} — {tr("подсеть должна быть такой же, как у этой панели (не вводите другую!).", "the subnet must be the same as on this panel (do not enter a different one!).")}
  6. +
  7. {tr("Сервер сам появится в списке ниже, клиенты скопируются на него автоматически. Кнопка «Добавить сервер» не нужна.", "The server appears in the list below by itself and clients are copied to it automatically. The “Add Server” button is not needed.")}
  8. +
  9. {tr("Назначить клиенту сервер: Клиенты → «Редактировать» → «Сервер». Наши приложения (роутер, iOS, Android, Windows 1.4.5+) переключатся сами; другие клиенты — только повторным импортом ссылки.", "Assign a server to a client: Clients → Edit → Server. Our apps (router, iOS, Android, Windows 1.4.5+) switch by themselves; other clients need the link re-imported.")}
  10. +
+
+ MAIN_PROTO={panel_scheme()} MAIN_SERVER={SERVER_IP} MAIN_PORT={panel_port()} MAIN_API_KEY={api_key} bash <(curl -kfsSL {panel_base_url()}/server/secondary-setup.sh) + +
+
+ ⚠ {tr("Не ставьте второй сервер полным install.sh!", "Do not install the second server with the full install.sh!")}
+ {tr("Полный install.sh создаёт ОТДЕЛЬНУЮ панель со своими клиентами. Подсеть там придётся указывать свою (например 10.25.11.0/24, иначе у клиентов будут одинаковые IP и второй туннель на роутере не поднимется), а смена сервера у клиента между двумя панелями НЕ работает — это разные клиенты. Нужна смена и назначение серверов — ставьте только командой выше, подсеть по умолчанию (Enter).", "The full install.sh creates a SEPARATE panel with its own clients. You would have to enter your own subnet there (e.g. 10.25.11.0/24, otherwise clients get identical IPs and the second router tunnel will not come up), and switching a client's server between two panels does NOT work — they are different clients. If you need server assignment and switching, use only the command above with the default subnet (Enter).")} +
+
+

{tr("Панель серверов", "Server Dashboard")} {hlp("Живая статистика всех VPN-серверов. Peers = активные WG-подключения. Assigned = клиенты, у которых этот сервер основной. Фоновый монитор опрашивает каждые " + str(s.get('monitor_interval',30)) + " сек и шлёт оповещения в Telegram при падении/восстановлении сервера.", "Live stats for all VPN servers. Peers = active WG connections. Assigned = clients whose primary is this server. Background monitor polls every " + str(s.get('monitor_interval',30)) + "s and sends Telegram alerts on server up/down.")}

{stats_cards}
@@ -4498,6 +4518,7 @@ def servers_page(): {hlp("Принудительно отправить конфиг на роутеры с прямым SSH. Роутеры за NAT это игнорируют и сами подтягивают конфиг за ~15 сек — обычно эта кнопка не нужна.", "Force-push config to routers reachable by SSH. NAT routers ignore it and auto-pull within ~15s — you usually do not need this.")}
+
{tr("Сервер уже поставлен, но не появился в списке? Добавить вручную", "Server installed but not in the list? Add manually")}
@@ -4508,6 +4529,7 @@ def servers_page(): {hlp("Добавить новый VPN-сервер в пул резервирования. Если secondary ставился командой ниже, API key можно оставить пустым — будет использован главный ключ панели. 8444 — это порт mini-API, не ключ.", "Add a new VPN server to the failover pool. If the secondary was installed with the command below, API key may be left blank — the panel main key will be used. 8444 is the mini-API port, not the key.")}
+
@@ -4527,7 +4549,7 @@ def servers_page():
-

Server SSH Access

+

{tr("SSH-доступ к серверам","Server SSH Access")}

SSH credentials for secondary servers. Used by panel to add/remove WG peers when switching client servers. Main server uses local commands.

@@ -4546,7 +4568,7 @@ def servers_page():
-

API Key

+

{tr("API-ключ","API Key")}

@@ -4554,11 +4576,6 @@ def servers_page():
-
-

Deploy Secondary Server

-

One command deploys WG + obfuscator + mini-API on a new VPS. Auto-registers in panel. Run via SSH on new VPS:

- MAIN_PROTO={panel_scheme()} MAIN_SERVER={SERVER_IP} MAIN_PORT={panel_port()} MAIN_API_KEY={api_key} bash <(curl -kfsSL {panel_base_url()}/server/secondary-setup.sh) -
""" return render(html) if __name__ == "__main__": diff --git a/install.sh b/install.sh index 560281c..605ee26 100755 --- a/install.sh +++ b/install.sh @@ -42,6 +42,7 @@ PY fi # Подсеть клиентов: env SERVER_WG_IPV4_NETWORK или вопрос в терминале (Enter = 10.25.0.0/16) if [ -z "${SERVER_WG_IPV4_NETWORK:-}" ] && [ -r /dev/tty ] && [ -t 1 ]; then + printf '\n!!! Это ВТОРОЙ сервер к уже работающей панели? Тогда прервите (Ctrl+C) и ставьте командой из панели\n «Серверы -> Как добавить второй сервер» (secondary-setup.sh), подсеть там по умолчанию.\n Своя подсеть нужна только для НЕЗАВИСИМОЙ панели (например 10.25.11.0/24, не пересекается с первой);\n смена сервера у клиентов между независимыми панелями не работает.\n\n' > /dev/tty printf 'Подсеть VPN-клиентов [10.25.0.0/16]: ' > /dev/tty read -r -t 60 SERVER_WG_IPV4_NETWORK < /dev/tty || true fi @@ -49,9 +50,16 @@ SERVER_WG_IPV4_NETWORK="${SERVER_WG_IPV4_NETWORK:-10.25.0.0/16}" SERVER_WG_IPV4_NETWORK="$(python3 - "$SERVER_WG_IPV4_NETWORK" <<'PY' import ipaddress, sys try: - net = ipaddress.ip_network(sys.argv[1].strip(), strict=False) + net = ipaddress.ip_network(sys.argv[1].strip(), strict=True) except ValueError: - sys.exit("ERROR: bad subnet " + sys.argv[1]) + # «10.25.11.0/16» — биты хоста заданы: раньше молча превращалось в 10.25.0.0/16 + # (две установки с одинаковой подсетью → одинаковые IP клиентов, конфликт на роутере). + # Считаем, что нужна подсеть этого адреса, и берём /24. + try: + net = ipaddress.ip_network(sys.argv[1].strip().split("/")[0] + "/24", strict=False) + sys.stderr.write("NOTICE: subnet %s has host bits set, using %s\n" % (sys.argv[1], net)) + except ValueError: + sys.exit("ERROR: bad subnet " + sys.argv[1]) if net.version != 4 or not net.is_private or net.prefixlen > 29: sys.exit("ERROR: subnet must be private IPv4, /8../29 (e.g. 10.25.1.0/24)") print(net) diff --git a/server/secondary-setup.sh b/server/secondary-setup.sh index 7d04a51..b882e29 100755 --- a/server/secondary-setup.sh +++ b/server/secondary-setup.sh @@ -27,9 +27,16 @@ SERVER_WG_IPV4_NETWORK="${SERVER_WG_IPV4_NETWORK:-10.25.0.0/16}" SERVER_WG_IPV4_NETWORK="$(python3 - "$SERVER_WG_IPV4_NETWORK" <<'PY' import ipaddress, sys try: - net = ipaddress.ip_network(sys.argv[1].strip(), strict=False) + net = ipaddress.ip_network(sys.argv[1].strip(), strict=True) except ValueError: - sys.exit("ERROR: bad subnet " + sys.argv[1]) + # «10.25.11.0/16» — биты хоста заданы: раньше молча превращалось в 10.25.0.0/16 + # (две установки с одинаковой подсетью → одинаковые IP клиентов, конфликт на роутере). + # Считаем, что нужна подсеть этого адреса, и берём /24. + try: + net = ipaddress.ip_network(sys.argv[1].strip().split("/")[0] + "/24", strict=False) + sys.stderr.write("NOTICE: subnet %s has host bits set, using %s\n" % (sys.argv[1], net)) + except ValueError: + sys.exit("ERROR: bad subnet " + sys.argv[1]) if net.version != 4 or not net.is_private or net.prefixlen > 29: sys.exit("ERROR: subnet must be private IPv4, /8../29 (e.g. 10.25.1.0/24)") print(net)