From 9e64088e467b6afbbfe522b19a7a7cc428be1161 Mon Sep 17 00:00:00 2001 From: vitaly Date: Tue, 30 Jun 2026 21:50:50 +0300 Subject: [PATCH] =?UTF-8?q?chore:=20=D1=83=D0=B1=D1=80=D0=B0=D1=82=D1=8C?= =?UTF-8?q?=20VLESS-=D0=BF=D1=80=D0=BE=D0=BA=D1=81=D0=B8,=20proxychains=20?= =?UTF-8?q?=D0=B8=20fuck-rkn.sh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Пользователь перешёл на Amnezia VPN — встроенный VLESS-прокси (xray + proxychains4) и отдельный server-installer fuck-rkn.sh (telemt + xray) больше не нужны. Убран весь код в ai-setup.sh/setup.sh, конфиги с VPN-кредами, неактуальный тест и упоминания в документации. Co-Authored-By: Claude Sonnet 5 --- QUICK_START.md | 2 +- README.md | 24 +- .../claude/skills/android-cli/SKILL.md | 1 - .../proxychains/proxychains-xray.conf | 9 - home-configs/vless/servers.conf | 6 - home-configs/xray/config.json | 51 - scripts/ai-setup.sh | 438 ---- scripts/fuck-rkn.sh | 1909 ----------------- setup.sh | 22 +- tests/test_fixes.sh | 9 - 10 files changed, 7 insertions(+), 2464 deletions(-) delete mode 100644 home-configs/proxychains/proxychains-xray.conf delete mode 100644 home-configs/vless/servers.conf delete mode 100755 home-configs/xray/config.json delete mode 100644 scripts/fuck-rkn.sh diff --git a/QUICK_START.md b/QUICK_START.md index 51fc6a2..ff7b4c2 100644 --- a/QUICK_START.md +++ b/QUICK_START.md @@ -6,7 +6,7 @@ bash setup.sh ``` -Спросит про VLESS-прокси (по умолчанию `Y`; если не нужен, ответь `n`), затем установит лаунчеры +Установит лаунчеры `ai-claude`, `ai-gpt`, `ai-deepseek`, `ai-kimi`, `ai-glm`, `ai-openrouter`, `ai-opencode`, `ai-gemini`, `ai-cursor` в `~/bin`, если он уже есть в `PATH`, иначе в `~/.local/bin/`, и запишет API-ключи. diff --git a/README.md b/README.md index 9a650e1..adadfa3 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ setup.sh # мастер-скрипт с меню scripts/ ├── ai-setup.sh # устанавливает AI-инструменты в выбранный BIN_DIR -├── fuck-rkn.sh # серверный установщик nginx + telemt + xray ├── ru-bypass.sh # .ru трафик напрямую, kill switch для остального ├── ks-off.sh # временно отключить kill switch └── ks-on.sh # восстановить kill switch @@ -20,12 +19,8 @@ home-configs/ │ ├── hooks/ # хуки аккаунтов Claude Code │ ├── skills/ # кастомные скиллы для Claude Code (и Gemini) │ └── statusline-command.sh # статусная строка Claude Code -├── network/ -│ └── README.md # подробная документация по сетевой настройке -├── vless/ -│ └── servers.conf # список VLESS-серверов для прокси -└── proxychains/ - └── proxychains-xray.conf # конфиг proxychains (SOCKS5 через xray) +└── network/ + └── README.md # подробная документация по сетевой настройке tests/ ├── test_fixes.sh # юнит-тесты структуры ai-setup.sh └── test_network.sh # тесты маршрутизации @@ -210,20 +205,6 @@ android init - Android CLI находится в preview, API/команды могут меняться. - На Windows в v0.7 отключена команда `android emulator`. - Если уже установлен Android Studio, Android CLI может использовать тот же SDK или создать новый в `~/.android/sdk`. Проверяй активный путь через `android info`. -- Android CLI - нативный бинарь, который спавнит дочерние процессы (emulator, gradle, adb), поэтому он не оборачивается в `proxychains4`. - -## VLESS / Xray (опционально) - -При запуске `scripts/ai-setup.sh` спрашивает, нужен ли VLESS-прокси для AI API запросов. - -Если выбрать **Y**: -- Читает список серверов из `home-configs/vless/servers.conf` -- Проверяет каждый сервер реальным curl'ом через SOCKS5 -- Устанавливает Xray, генерирует конфиг, создаёт systemd сервис -- Все лаунчеры оборачиваются в `proxychains4` - -Если выбрать **n** - VLESS отключается, xray останавливается, системный/Firefox прокси -сбрасываются в direct. IPv6 включается обратно только если UFW kill switch не активен. ## Ключи и конфиги @@ -265,7 +246,6 @@ GPT/claude-code-proxy backend; текущие лаунчеры его напря - `bash`, `curl`, `python3` - Node.js/npm (для ai-gpt через Codex CLI и ai-gemini через agy) - `ipset` (устанавливается автоматически при запуске ru-bypass.sh) -- (опционально) `proxychains-ng` - для VLESS-режима - (опционально) `gsettings` - для автонастройки системного прокси GNOME ## Тесты diff --git a/home-configs/claude/skills/android-cli/SKILL.md b/home-configs/claude/skills/android-cli/SKILL.md index f6f6eac..021ebb6 100644 --- a/home-configs/claude/skills/android-cli/SKILL.md +++ b/home-configs/claude/skills/android-cli/SKILL.md @@ -80,7 +80,6 @@ android update - **Preview-версия**: API и команды могут меняться. Обновляй через `android update`. - **Windows**: `android emulator` отключён в v0.7. - **Существующий SDK**: если уже установлен Android Studio или `sdkmanager`, Android CLI может использовать тот же SDK или создать новый в `~/.android/sdk`. Проверяй активный путь через `android info`. -- **Прокси**: Android CLI - нативный бинарь, который спавнит дочерние процессы (emulator, gradle, adb). Не оборачивай его в `proxychains4`, это может сломать child-процессы. ## Полезные ссылки diff --git a/home-configs/proxychains/proxychains-xray.conf b/home-configs/proxychains/proxychains-xray.conf deleted file mode 100644 index 060bc2c..0000000 --- a/home-configs/proxychains/proxychains-xray.conf +++ /dev/null @@ -1,9 +0,0 @@ -# ProxyChains-NG config for xray SOCKS5 proxy -strict_chain -proxy_dns -remote_dns_subnet 224 -tcp_read_time_out 15000 -tcp_connect_time_out 8000 - -[ProxyList] -socks5 127.0.0.1 1080 diff --git a/home-configs/vless/servers.conf b/home-configs/vless/servers.conf deleted file mode 100644 index 14f93eb..0000000 --- a/home-configs/vless/servers.conf +++ /dev/null @@ -1,6 +0,0 @@ -# VLESS servers — один URL на строку -# Имя после # используется для отображения в меню выбора -vless://495ac855-d718-4581-8344-f1e62105a904@92.118.8.29:443?encryption=mlkem768x25519plus.native.0rtt.gNGR8kO_UDWjoeer_TenoRbA09ajfrSfahqkS4q0dTeCFutCHGJjIdhSl8Fv8HyYUyWB_2GqMrSIzLS9l1BiidClOnliyUmZylQwQoUFl6CsLsZxdrrDz3w2isuIhtXPfZw8lANOErk7p7mm4QN0H2ZKCOQQaxkL3XA8EcxqNuGhK4XLqlFBDommPJUQfPuP7LV3V7s8YTcwq1EUr-Nyv0CZRdcF9WCNCWq26TVYXFwHSkeknBBFKYEJiytB8DpIvlmTvREQfEVnTziwOdYKikQXeOYdbcuKSWGb2okDZjFzYckULaWhavOwRWnOquZqdXiatrNPmCWnG2mfcSxA0UdLu8ALiOtuPjBGDUqqaXhZLLuH1zhdsUE4PMqqkNnMvsRwmvAYgWwSKWioz3wTQ1sKVfW1jTh8LyouZaVkuqRTunwWBcVCRDYEFUh4TBB_-WOpu7eh4ROmj9w0mhWBrtRO-uin8ZwqhkeScQhwoFUEvlwCsAl8AevGmAm1qpdOzfXNUgBTtVoXG9OTMUk9ZORiTzO0tutVTlKjaiBb1vOqgiFqiPuG1csvaPt63LQ1e9ywvEek7NkLVnrJopmCnTiu9UhE7GwZbxI8FBFYrBmPpIUszLiWLQZqvySbqBrIFTC5lIw9WymdW5wKONs5DZmYtFZOHUVEFtVFJvcZqwnEJARKI_M-rvkS7FWPPCkKHpEVe_IJsgOgoUzI9yQI5kNlS5XAzUG7ZMGWtzYjOZNZ1FYP5NbBTTy9I8kpLRQv3yp7PYBGdBQapaYX82Qjc4orJdghG5UOTQwsokojPVaAzSWG4qk5hoNugvogx9KHdsitKXCDlhdksQS5Obc8uMg1TxO2c0AnR2nNNVqgb_prVUlaNHcP4wARfCqrMwdgYQSSoLZluah9a2eZxGcC6CMxPBsThCEwNzIJUZiuPIvEQVFyJpiCHeUErGZFLpmaE0MK2cEzD9N7pRVgEExvwIMnO3jIGmCz80FFuAmEjDG5iyMkzQpsEnBHwAaihjkSwgCsZzTL26OtIDUUYMZ1N8ciRRqTvSGzn_K6EUp7JaWygZGw3QCBRocO7SEWpuGvakJc0QV32vpty3c8UeIcJZlgGKgtcNcsVYvLpEkkYviNCbpq6jnECRYUuXGp_oQv1mwqx2WMqGic7LVZNtYCJ3fHAsGi4yh5nfU0dNSCC0cgeyakNXvL3eyqHIB_e8RrEiMywUg7_MGP2oE9ZOqayLRapVTGQ-YATgcnZ_Eg5bKHNcRVqBpo0heElCtMPoEdQYE_HZSOm1cf9nFIYqRJ6_RPJNKGDOBwy1wJWJel54y-CeY3S2vAWRibOFiwVGW01tHNSCEQBaiLEzQ2Xml6IkSAHNm9eifPrdxuBChibzco4dUFpumbxrRkJYAuqmQfWgi36ziojeEDLlUd55ZDeCOVn4MoHMcD4yzAkpdUelyBafeUoQEs6hCDf2p7CvCquWKoEzcwvuccR0GcN5un2sqQAMVKw3Gs3fsRwkgcdsK69oE4KfUKVjrBGkIk9ChYCJMYGZJ-pGkWFp1qLua2QnNmPVJlpPjurRJVoiAiOVE&security=reality&sni=www.amd.com&fp=chrome&pbk=AMoGtXyztJYD6GRP6m8K82Mi-5vthrNrppJkEmconTU&sid=9cd28fd258f8b99c&type=xhttp&path=%2F&mode=auto#RF-VPS1 -vless://91749001-1f60-4afb-b7b5-25d1480de980@194.87.213.143:443?encryption=mlkem768x25519plus.native.0rtt.D4eIFKFhlMVUsInLgmx5lWuTpBJ8B3lEWnA4gCKdZgw1rjlvHsWd38rC35BAQeKSeBmbZ_sIk6w1gzCCAcYKlJN8rMZyr4TEecnADumEI0e05fRcYCXH70BdHnJxkhklzUIwAKyZHmwlk7euozhYinwq-piRuEBvl0NS-tITgOaje8jLuMyrBTnBKBtzwDR43aMsDqZhzRi-qIaiy0GXjEgHmypFIFB3EOdmCukbvgyJuBAbttTHqdCedQCeJTNfs3tzmzqFKuJXVGRvAvipWGqC_4hzlTWaVXBiuysAmGMC0oqERTyUS9qKqytOr_oyu-jPzjCdIUTPAjdpw4kzXEiuetAuZNuwdbRAHAB_DadVIAsqyVapNIVP0UlN-PJE95pAS6Yfpru2uIBWcJZhV7LE2FKBtAcC31lHeVKy12pG_NlZopYltcmdacVeXBRd1wURrrDFNNTCKaG5wAFm3HFYrzY6qZfNanebgQeYIQm3rbbGGCLBYLyHxcmKeuWVIMTJlISicHdnNyok7KJ2gWKTWtREkfQMXrZKF8BeiSp-9FQX_UCwx0LMd0axB8hqCVtpGspbH8ugKamFr4olFyKrUdQuLhEM1_xzNgZT3dWhvCxDw1VOp3qMd6G4X4YhnABioLOPl2JEyjOLMdAFRlRrdXnNpJdTA0esrJFw5Mte2gNYDAgWEFoq5uupAZlYwniB69BCeEouQiU0GYdWkxwAqwy8xtCEs9Wi4RGPBmdZnhOb5LMUBACwvFm_haF5zgwDrTmUZWdKhXMDXOMOdrcQaDvMf3uk64Q6obe47hkn1LFQS5mKOYYAjblg1RV0y1ovWtFSVBuuCsckyNUPSjETKthQQ6OUgZmpN-pMlBgzGzi3rowqdjNzrMh894ufrptX2GBqEANIM3h5HkR2ObIlJdOv6rQRnmu09EVKp7BHAVLKJcJff9HJYWIqUueArllu5IivueGPG5gPMDIp5SoHVvk_eCMUY2lLtxEE1PCjbgt8ahKTQLcFCXmJf7h945F4jOsDLCxiBSSxaMZdK5AiGGcQm-mcQ4aXBLgiHLCfD3SU7UzIF9key8u35bBm2OVjyTWei-AOssdVV2qFMppuF7Myv4GaEMpQ1JGaLpxT9zSSdVqbO6aJNBkkFxJhcnWqm8CMWVkaGubHkSeDInS65_KFe6I8IrZVm9lBThN1mvdqWkCvZ_ooRiChX7hNKTAXH9cgW-OaOTY1eissDkmW2FEGcPU5iRcDY5kgubZYgTSaHKFAIqg-6NIQX1l3d6lx41EkwyKUCWU-zfjBGsdgszjHXykOrcNQ75o_M3k_-_IBdkOpaESrhSsj2bdNUVcx7fx1S2d6STiLIrW--WeoeBhb6BcytgkbnlRlmvfNDZbOTJYzXQLJtCRZv3xJyfcEB0UqvKFIaCtN4ubHTpyRZ5PHtYHDh9lmIASIItMqeXQlazktz-cajiOFX8RCIkcDFkZ-0KpmYiBToVpi-zSjdLCPPVMf14V5kqAP6EA1IiTJPwFuroWluOJ5j8mx4c0i8fbd1n_mTH_7Ff8Ognu-LFLBU9r9yWcVhBkm_JQ&security=reality&sni=vkvideo.ru&fp=chrome&pbk=BWwAjxuB6QbC2ta23L8_U4QjjkUs17NDi79Mm2RjQHQ&sid=17c863e8490260b6&type=xhttp&path=%2F&mode=packet-up#RF-Bridge-PQ -vless://f3f3d573-5440-425c-a13c-203b34a9279f@202.148.54.122:443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=www.samsung.com&fp=chrome&pbk=aTBwueWs3WYoxA0M051KZn3hr2EogEULd3GOdvl7x0Y&sid=5a0048d376dc1c00#AmneziaVPN -vless://40ee74ff-9865-498a-b464-12fa6a3772fe@45.43.91.139:853?encryption=mlkem768x25519plus.native.0rtt.TZBtUoTBrKNMXKtza2TElwtqc9W_zgAJEDcxH1fBEwQgbZCAXHgK9edzewlVFSBK3zK2p4IZ6hsESffCSOesy4Mll_CKPiY0kFGCV-yNl6w45eav7RUhDOuiPBrHR3hC-bgH4xoClLajDfG7nHu2ecppcsEedkt7MLSacYI3EsJ-rKa-vbN3vgI5w8ZtunFF-PYVRJuYrbw711RwbcO_AWfPt1J25ZhVcxQ5CWufWXG1XLtaZLdk-2cmKsiVIgcr6tOIYIuh8XULoCVh5rsjwlcH4slu4cYGGgxwNIg7C_wYs8zLyvKWgBUL0UwctTyNbxChWktTuWaGRnS27PJbdbJ6T9gTzMsWh3dbVnlGZbIjbJMe3NAvVOJUhGOQ7NpEP9x9wgVzO-dUJQkOwKAoDfAtvbRIAaRg_5w20ZRy0sutSwUCoKtUfxCxB4CwcSmFgqWRrsvHNTy2U7We6GibAwy90iiC3sGCVSM69XcN1xirfVVGRbAMrfk0DehWNAvQFHFNkThvi0qh-1Q3p4YK6EWhgswTqmMbM2iOTQA_KaxUQKwfPnIfCBMIuTBdFDYOMyyvZayFq4Ul62vEMvUOCQCHL7WtdzhVR5cdquggX3C4Zzutlug_n4q2jLt2ldYvAwEMErPOlSq2DKmIcWFE5YWSjUhYxvg76rOEK_WSSGNrtrO5n6eRFtiIyaxXPxEhjsFdoLMekDSdhZWyhwa3-9Vqf_xDgrs2CMlOkpQbIvqe92QkVdWzhWeulBu4XGsnOVCivvvLrUBfGDs9FrCYGLEtOLtMlHcny1tXjdjEVkejrvOcOTCvxKoidTzJVsVi2Oxn9VGfO6TOURvP4KwsDDVZbgUgAYqfBbkrnpNhaaZFuoYw-7GuZawl8LyOy8SU9NQesDt4YUZ3MrWcHwALrKqCD3OSf0wzq0N-BDY6frAQf8W3KSO8ivKfxMQhN5xPfSuTlRivWuq1lpEjFiqi-vZF-ZkVA9qRWmyXRfaUYcKqqQFoO5Ks0FuainJmxPocseUIxVF3qCINQLsaxVgWITAcWOOZV4m021Yy2LxnwcubRUkDHPGvIocQE8JF7fpAWVqjAxwT7oCHiHRf6WtzEHhCWZx7y9tR-AV4_XgZNEwRj7DM-kdztDl78Wg9E7Ze9lnK3UCXPxQf9THOd_wqQ0aF5bkwf_qqj0klX9WphfBvPzVuI0lmVeWPDKyqVDqCw4An8oFB7bxt_lPDzIOInvB5U8hXUuMjsQS6iEcSSHcfqed8adcKvzTCK6JcjLCTyImVS3hsxMOC9qFMtDpmsxcNdFphlZc9ODjGgZtVLYLIYka-JKQWPgC2R-s8H6EU0ymjIhsmdMe93KyHAcC3anpUKTu9oSk5sthVFhB0l2RCpSKlWOhFG8RkAspKWYlu-DGC3ROvnbJojjpGkpDFEWoVnzWQ8VK5hcxSeFQ17auDWMKBkDuhR1ciVURnCBBVO1FEGvGGVyAbYoUri8moPeZ8jNBvztFZiahBeWQBsxQcA9C78Yus34S2aOwCfHVQ9hN2slx5tPY5abR_EwhyS4NhAZ7how7k0Fds42glbOE&security=reality&sni=www.amd.com&fp=chrome&pbk=3PdVmi-ZFgQpvTD8Tj4ubL1kicezogKf7-qkMLfjxxw&sid=846683ae0dde8bd1&type=xhttp&path=%2F&mode=auto#EURO-Reality-PQ diff --git a/home-configs/xray/config.json b/home-configs/xray/config.json deleted file mode 100755 index db47183..0000000 --- a/home-configs/xray/config.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "inbounds": [ - { - "port": 1080, - "listen": "127.0.0.1", - "protocol": "socks", - "settings": { - "udp": true - } - }, - { - "port": 2080, - "listen": "127.0.0.1", - "protocol": "http" - } - ], - "outbounds": [ - { - "protocol": "vless", - "settings": { - "vnext": [ - { - "address": "92.118.8.29", - "port": 443, - "users": [ - { - "id": "495ac855-d718-4581-8344-f1e62105a904", - "encryption": "mlkem768x25519plus.native.0rtt.gNGR8kO_UDWjoeer_TenoRbA09ajfrSfahqkS4q0dTeCFutCHGJjIdhSl8Fv8HyYUyWB_2GqMrSIzLS9l1BiidClOnliyUmZylQwQoUFl6CsLsZxdrrDz3w2isuIhtXPfZw8lANOErk7p7mm4QN0H2ZKCOQQaxkL3XA8EcxqNuGhK4XLqlFBDommPJUQfPuP7LV3V7s8YTcwq1EUr-Nyv0CZRdcF9WCNCWq26TVYXFwHSkeknBBFKYEJiytB8DpIvlmTvREQfEVnTziwOdYKikQXeOYdbcuKSWGb2okDZjFzYckULaWhavOwRWnOquZqdXiatrNPmCWnG2mfcSxA0UdLu8ALiOtuPjBGDUqqaXhZLLuH1zhdsUE4PMqqkNnMvsRwmvAYgWwSKWioz3wTQ1sKVfW1jTh8LyouZaVkuqRTunwWBcVCRDYEFUh4TBB_-WOpu7eh4ROmj9w0mhWBrtRO-uin8ZwqhkeScQhwoFUEvlwCsAl8AevGmAm1qpdOzfXNUgBTtVoXG9OTMUk9ZORiTzO0tutVTlKjaiBb1vOqgiFqiPuG1csvaPt63LQ1e9ywvEek7NkLVnrJopmCnTiu9UhE7GwZbxI8FBFYrBmPpIUszLiWLQZqvySbqBrIFTC5lIw9WymdW5wKONs5DZmYtFZOHUVEFtVFJvcZqwnEJARKI_M-rvkS7FWPPCkKHpEVe_IJsgOgoUzI9yQI5kNlS5XAzUG7ZMGWtzYjOZNZ1FYP5NbBTTy9I8kpLRQv3yp7PYBGdBQapaYX82Qjc4orJdghG5UOTQwsokojPVaAzSWG4qk5hoNugvogx9KHdsitKXCDlhdksQS5Obc8uMg1TxO2c0AnR2nNNVqgb_prVUlaNHcP4wARfCqrMwdgYQSSoLZluah9a2eZxGcC6CMxPBsThCEwNzIJUZiuPIvEQVFyJpiCHeUErGZFLpmaE0MK2cEzD9N7pRVgEExvwIMnO3jIGmCz80FFuAmEjDG5iyMkzQpsEnBHwAaihjkSwgCsZzTL26OtIDUUYMZ1N8ciRRqTvSGzn_K6EUp7JaWygZGw3QCBRocO7SEWpuGvakJc0QV32vpty3c8UeIcJZlgGKgtcNcsVYvLpEkkYviNCbpq6jnECRYUuXGp_oQv1mwqx2WMqGic7LVZNtYCJ3fHAsGi4yh5nfU0dNSCC0cgeyakNXvL3eyqHIB_e8RrEiMywUg7_MGP2oE9ZOqayLRapVTGQ-YATgcnZ_Eg5bKHNcRVqBpo0heElCtMPoEdQYE_HZSOm1cf9nFIYqRJ6_RPJNKGDOBwy1wJWJel54y-CeY3S2vAWRibOFiwVGW01tHNSCEQBaiLEzQ2Xml6IkSAHNm9eifPrdxuBChibzco4dUFpumbxrRkJYAuqmQfWgi36ziojeEDLlUd55ZDeCOVn4MoHMcD4yzAkpdUelyBafeUoQEs6hCDf2p7CvCquWKoEzcwvuccR0GcN5un2sqQAMVKw3Gs3fsRwkgcdsK69oE4KfUKVjrBGkIk9ChYCJMYGZJ-pGkWFp1qLua2QnNmPVJlpPjurRJVoiAiOVE", - "flow": "" - } - ] - } - ] - }, - "streamSettings": { - "network": "xhttp", - "security": "reality", - "realitySettings": { - "serverName": "www.amd.com", - "fingerprint": "chrome", - "publicKey": "AMoGtXyztJYD6GRP6m8K82Mi-5vthrNrppJkEmconTU", - "shortId": "9cd28fd258f8b99c" - }, - "xhttpSettings": { - "path": "/", - "mode": "auto" - } - } - } - ] -} diff --git a/scripts/ai-setup.sh b/scripts/ai-setup.sh index cde1349..37810d5 100755 --- a/scripts/ai-setup.sh +++ b/scripts/ai-setup.sh @@ -53,260 +53,6 @@ else warn "jq не найден. statusline-command.sh и некоторые функции могут не работать." fi -# ── VLESS URL parser ─────────────────────────────────────────── -# Принимает vless:// URL, устанавливает переменные VL_* -parse_vless_url() { - local url="$1" - eval "$(python3 -c " -import urllib.parse, sys - -url = sys.argv[1] -rest = url[8:] # strip 'vless://' - -at_pos = rest.index('@') -uuid = rest[:at_pos] -rest = rest[at_pos+1:] - -colon_pos = rest.index(':') -q_pos = rest.index('?') -host = rest[:colon_pos] -port = rest[colon_pos+1:q_pos] - -rest = rest[q_pos+1:] -hash_pos = rest.index('#') if '#' in rest else len(rest) -qs = rest[:hash_pos] -name = rest[hash_pos+1:] if '#' in rest else '' - -params = urllib.parse.parse_qs(qs) -def get(p, default=''): - vals = params.get(p, [default]) - return vals[0] if vals else default - -flow = get('flow') -vtype = get('type') -if not vtype: - vtype = 'xhttp' if not flow else 'tcp' -spx_default = '' if vtype == 'tcp' else '/' - -print(f'VL_UUID={uuid}') -print(f'VL_ADDRESS={host}') -print(f'VL_PORT={port}') -print(f'VL_ENCRYPTION={get(\"encryption\")}') -print(f'VL_FLOW={flow}') -print(f'VL_SECURITY={get(\"security\")}') -print(f'VL_SNI={get(\"sni\")}') -print(f'VL_FP={get(\"fp\", \"chrome\")}') -print(f'VL_PBK={get(\"pbk\")}') -print(f'VL_SID={get(\"sid\")}') -print(f'VL_TYPE={vtype}') -print(f'VL_PATH={urllib.parse.unquote(get(\"path\", \"/\"))}') -print(f'VL_MODE={get(\"mode\", \"auto\")}') -print(f'VL_SPX={get(\"spx\", spx_default)}') -print(f'VL_NAME={name}') -" "$url")" -} - -# ── VLESS connectivity test ──────────────────────────────────── -# Запускает xray с тестовым конфигом и проверяет curl'ом -# Возвращает: "ok" или "fail" -test_vless_server() { - local url="$1" test_port="$2" - - parse_vless_url "$url" - - # Генерируем временный конфиг - _TMP_CONF="/tmp/xray_test_${test_port}.json" - _VL_XHTTP="" - if [ "$VL_TYPE" = "xhttp" ]; then - _VL_XHTTP=$(printf ',\n "xhttpSettings": {\n "path": "%s",\n "mode": "%s"\n }' "$VL_PATH" "$VL_MODE") - fi - - cat > "$_TMP_CONF" << XRAYEOF -{ - "log": { "loglevel": "none" }, - "inbounds": [ - { - "port": $test_port, - "listen": "127.0.0.1", - "protocol": "socks", - "settings": { "udp": true } - } - ], - "outbounds": [ - { - "protocol": "vless", - "settings": { - "vnext": [ - { - "address": "$VL_ADDRESS", - "port": $VL_PORT, - "users": [ - { - "id": "$VL_UUID", - "encryption": "$VL_ENCRYPTION", - "flow": "$VL_FLOW" - } - ] - } - ] - }, - "streamSettings": { - "network": "$VL_TYPE", - "security": "$VL_SECURITY", - "realitySettings": { - "serverName": "$VL_SNI", - "fingerprint": "$VL_FP", - "publicKey": "$VL_PBK", - "shortId": "$VL_SID", - "spiderX": "$VL_SPX" - }$_VL_XHTTP - } - } - ] -} -XRAYEOF - - # Запускаем xray - /usr/local/bin/xray run -c "$_TMP_CONF" >/dev/null 2>&1 & - local xpid=$! - sleep 2 - - # Тест через SOCKS5 - local result="fail" - if curl --socks5 "127.0.0.1:${test_port}" --max-time 5 -s https://ifconfig.co/ >/dev/null 2>&1; then - result="ok" - fi - - kill $xpid 2>/dev/null; wait $xpid 2>/dev/null - rm -f "$_TMP_CONF" - echo "$result" -} - -# ── 0. Выбор режима работы VLESS (on / off / skip) ───────────── -# AI_SETUP_VLESS: -# 1 / on — включить VLESS (установка xray + proxychains + обёртки лаунчеров) -# 0 / off — отключить VLESS (откат: stop xray, сброс прокси, IPv6 on) -# skip — НЕ трогать сеть (умолчание). Лаунчеры синхронизируются -# с текущим состоянием (xray активен?). -# Устанавливается из меню setup.sh. По умолчанию skip, чтобы прямой запуск -# ai-setup.sh и пункты 1/7/8 не меняли сетевые настройки. -DO_VLESS_INSTALL=0 -case "${AI_SETUP_VLESS:-skip}" in - 1|on) - USE_VLESS=1 - DO_VLESS_INSTALL=1 - - # Читаем список серверов - _VL_URLS=() - _VL_LABELS=() - _SERVERS_FILE="$SCRIPT_DIR/home-configs/vless/servers.conf" - - if [ ! -f "$_SERVERS_FILE" ]; then - err "Файл servers.conf не найден: $_SERVERS_FILE" - fi - - while IFS= read -r line || [[ -n "$line" ]]; do - [[ "$line" =~ ^[[:space:]]*# ]] && continue - [[ -z "$line" ]] && continue - _vl_rest="${line#vless://}" - _vl_rest="${_vl_rest#*@}" - _vl_ip="${_vl_rest%%:*}" - _vl_name="${line##*#}" - [[ "$_vl_name" == "$line" ]] && _vl_name="" - _VL_URLS+=("$line") - _VL_LABELS+=("$_vl_ip ($_vl_name)") - done < "$_SERVERS_FILE" - - if [ "${#_VL_URLS[@]}" -eq 0 ]; then - err "Нет VLESS серверов в $_SERVERS_FILE" - fi - - # Реальная проверка VLESS (запускаем xray для каждого сервера) - echo "" - info "Проверяю VLESS-доступность серверов..." - _VL_STATUS=() - for i in "${!_VL_URLS[@]}"; do - _label="${_VL_LABELS[$i]}" - _test_port=$((10980 + i)) - printf " %-45s" "$((i+1))) $_label" - _status=$(test_vless_server "${_VL_URLS[$i]}" "$_test_port") - if [ "$_status" = "ok" ]; then - echo -e "${GREEN}✓ работает${NC}" - _VL_STATUS+=("ok") - else - echo -e "${RED}✗ не работает${NC}" - _VL_STATUS+=("fail") - fi - done - echo "" - - read -r -p "Выбери сервер [1-${#_VL_URLS[@]}]: " _vl_choice - _vl_choice="${_vl_choice:-1}" - if ! [[ "$_vl_choice" =~ ^[0-9]+$ ]] || [ "$_vl_choice" -lt 1 ] || [ "$_vl_choice" -gt "${#_VL_URLS[@]}" ]; then - err "Неверный выбор: $_vl_choice" - fi - - _VL_SELECTED="${_VL_URLS[$((_vl_choice-1))]}" - parse_vless_url "$_VL_SELECTED" - info "Выбран: $VL_ADDRESS ($VL_NAME)" - ;; - 0|off) - USE_VLESS=0 - DO_VLESS_INSTALL=0 - info "Отключение VLESS (откат настроек)" - - # ── Откат всех настроек vless ────────────────────────────── - info "Откатываю настройки прокси..." - - # Останавливаем и отключаем xray - sudo systemctl stop xray 2>/dev/null || true - sudo systemctl disable xray 2>/dev/null || true - success "xray остановлен и отключён" - - # Системный прокси → none - if command -v gsettings &>/dev/null; then - gsettings set org.gnome.system.proxy mode 'none' 2>/dev/null || true - success "Системный прокси отключён" - fi - - # Firefox → direct (type=0) - FIREFOX_PROFILE="" - if [ -d "$HOME/snap/firefox/common/.mozilla/firefox" ]; then - FIREFOX_PROFILE=$(find "$HOME/snap/firefox/common/.mozilla/firefox" -name "*.default*" -type d | head -1) - elif [ -d "$HOME/.mozilla/firefox" ]; then - FIREFOX_PROFILE=$(find "$HOME/.mozilla/firefox" -name "*.default*" -type d | head -1) - fi - if [ -n "$FIREFOX_PROFILE" ]; then - cat > "$FIREFOX_PROFILE/user.js" << 'FJSEOF' -user_pref("network.proxy.type", 0); -FJSEOF - success "Firefox переключён на прямой доступ" - fi - - # Включаем IPv6 обратно (только если kill switch не активен) - if ufw status | grep -qE "активен|active"; then - info "UFW kill switch активен — оставляю IPv6 отключённым" - else - sudo rm -f /etc/sysctl.d/99-disable-ipv6.conf - sudo sysctl -w net.ipv6.conf.all.disable_ipv6=0 2>/dev/null || true - sudo sysctl -w net.ipv6.conf.default.disable_ipv6=0 2>/dev/null || true - sudo systemctl restart systemd-resolved 2>/dev/null || true - success "IPv6 восстановлен" - fi - ;; - *) - # skip — сеть не трогаем; синхронизируем лаунчеры с текущим состоянием xray - if systemctl is-active --quiet xray 2>/dev/null; then - USE_VLESS=1 - info "Сеть не трогаю. VLESS активен — лаунчеры будут с proxychains4." - else - USE_VLESS=0 - info "Сеть не трогаю. VLESS не активен — прямые лаунчеры." - fi - info "Управление VLESS: пункт 9 (включить / отключить)." - ;; -esac - # ── 1. npm prefix в домашнюю папку ────────────────────────── info "Настраиваю npm prefix..." mkdir -p "$NPM_GLOBAL" @@ -334,175 +80,6 @@ else warn "Node.js не найден. Некоторые функции могут не работать." fi -# ── 2.5. proxychains-ng + xray (только при включении VLESS) ── -if [ "$DO_VLESS_INSTALL" -eq 1 ]; then - info "Проверяю proxychains-ng..." - if ! command -v proxychains4 &>/dev/null; then - info "Устанавливаю proxychains-ng (нужен sudo)..." - if command -v apt-get &>/dev/null; then - sudo apt-get install -y proxychains-ng - elif command -v dnf &>/dev/null; then - sudo dnf install -y proxychains-ng - else - warn "Не удалось установить proxychains-ng автоматически. Установите вручную." - fi - fi - if command -v proxychains4 &>/dev/null; then - success "proxychains4 найден" - else - warn "proxychains4 не найден. Продолжаю без проксирования." - fi - - info "Устанавливаю xray..." - - # Останавливаем старый процесс (мог остаться от предыдущей установки) - sudo systemctl stop xray 2>/dev/null || true - - XRAY_VERSION="26.3.27" - XRAY_ARCH="64" - XRAY_URL="https://github.com/XTLS/Xray-core/releases/download/v${XRAY_VERSION}/Xray-linux-${XRAY_ARCH}.zip" - - TMPDIR=$(mktemp -d) - curl -fsSL "$XRAY_URL" -o "$TMPDIR/xray.zip" - unzip -q "$TMPDIR/xray.zip" -d "$TMPDIR" - sudo install -m 755 "$TMPDIR/xray" /usr/local/bin/xray - rm -rf "$TMPDIR" - - sudo mkdir -p /etc/xray - - # xhttpSettings — только для xhttp-транспорта - _VL_XHTTP="" - if [ "$VL_TYPE" = "xhttp" ]; then - _VL_XHTTP=$(printf ',\n "xhttpSettings": {\n "path": "%s",\n "mode": "%s"\n }' "$VL_PATH" "$VL_MODE") - fi - - sudo tee /etc/xray/config.json > /dev/null << XRAYEOF -{ - "log": { "loglevel": "warning" }, - "inbounds": [ - { - "port": 1080, - "listen": "127.0.0.1", - "protocol": "socks", - "settings": { "udp": true } - }, - { - "port": 2080, - "listen": "127.0.0.1", - "protocol": "http" - } - ], - "outbounds": [ - { - "protocol": "vless", - "settings": { - "vnext": [ - { - "address": "$VL_ADDRESS", - "port": $VL_PORT, - "users": [ - { - "id": "$VL_UUID", - "encryption": "$VL_ENCRYPTION", - "flow": "$VL_FLOW" - } - ] - } - ] - }, - "streamSettings": { - "network": "$VL_TYPE", - "security": "$VL_SECURITY", - "realitySettings": { - "serverName": "$VL_SNI", - "fingerprint": "$VL_FP", - "publicKey": "$VL_PBK", - "shortId": "$VL_SID", - "spiderX": "$VL_SPX" - }$_VL_XHTTP - } - } - ] -} -XRAYEOF - sudo chmod 644 /etc/xray/config.json - - sudo tee /etc/systemd/system/xray.service > /dev/null << 'SVCEOF' -[Unit] -Description=Xray Service -After=network.target - -[Service] -ExecStart=/usr/local/bin/xray run -c /etc/xray/config.json -Restart=on-failure - -[Install] -WantedBy=multi-user.target -SVCEOF - - # Удаляем чужие drop-in оверрайды (могут переопределять ExecStart на старый конфиг) - sudo rm -rf /etc/systemd/system/xray.service.d/ - # Удаляем старый дефолтный конфиг xray из других путей - sudo rm -rf /usr/local/etc/xray/ - - sudo systemctl daemon-reload - sudo systemctl enable --now xray - success "xray установлен и запущен" - - # ── Отключение IPv6 (VLESS не тянет IPv6, браузеры зависают) ── - info "Отключаю IPv6 на уровне системы..." - sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1 - sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1 - sudo tee /etc/sysctl.d/99-disable-ipv6.conf > /dev/null << 'SYSCTEOF' -# Отключение IPv6 — требуется для стабильной работы VLESS/xray -net.ipv6.conf.all.disable_ipv6=1 -net.ipv6.conf.default.disable_ipv6=1 -SYSCTEOF - sudo systemctl restart systemd-resolved - success "IPv6 отключён, DNS-кэш очищен" - - cp "$SCRIPT_DIR/home-configs/proxychains/proxychains-xray.conf" "$HOME/.proxychains-xray.conf" - success "Proxychains конфиг обновлён" - - # ── Настройка Firefox на SOCKS5 + remote DNS ──────────────── - info "Настраиваю Firefox на SOCKS5 прокси..." - FIREFOX_PROFILE="" - if [ -d "$HOME/snap/firefox/common/.mozilla/firefox" ]; then - FIREFOX_PROFILE=$(find "$HOME/snap/firefox/common/.mozilla/firefox" -name "*.default*" -type d | head -1) - elif [ -d "$HOME/.mozilla/firefox" ]; then - FIREFOX_PROFILE=$(find "$HOME/.mozilla/firefox" -name "*.default*" -type d | head -1) - fi - - if [ -n "$FIREFOX_PROFILE" ]; then - cat > "$FIREFOX_PROFILE/user.js" << 'FJSEOF' -user_pref("network.proxy.type", 1); -user_pref("network.proxy.socks", "127.0.0.1"); -user_pref("network.proxy.socks_port", 1080); -user_pref("network.proxy.socks_remote_dns", true); -user_pref("network.proxy.http", ""); -user_pref("network.proxy.http_port", 0); -user_pref("network.proxy.ssl", ""); -user_pref("network.proxy.ssl_port", 0); -FJSEOF - success "Firefox настроен на SOCKS5 (профиль: $FIREFOX_PROFILE)" - else - warn "Firefox не найден, пропускаю настройку прокси" - fi - - # ── Настройка системного прокси (для Chrome/Chromium) ─────── - info "Настраиваю системный прокси..." - if command -v gsettings &>/dev/null; then - gsettings set org.gnome.system.proxy mode 'manual' 2>/dev/null || true - gsettings set org.gnome.system.proxy.http host '127.0.0.1' 2>/dev/null || true - gsettings set org.gnome.system.proxy.http port 2080 2>/dev/null || true - gsettings set org.gnome.system.proxy.socks host '127.0.0.1' 2>/dev/null || true - gsettings set org.gnome.system.proxy.socks port 1080 2>/dev/null || true - success "Системный прокси настроен (HTTP 2080 + SOCKS 1080)" - else - warn "gsettings не найден, пропускаю настройку системного прокси" - fi -fi - # ── 3. Claude Code ─────────────────────────────────────────── info "Проверяю Claude Code..." if ! command -v claude &>/dev/null; then @@ -2320,21 +1897,6 @@ claude --dangerously-skip-permissions --system-prompt-file "$_PROMPT_FILE" "$@" OPENCODEEOF chmod +x "$BIN_DIR/ai-opencode" -# ── 8.5. Proxychains инъекция (только в режиме vless) ──────── -if [ "$USE_VLESS" -eq 1 ]; then - info "Включаю proxychains4 в ai-лаунчеры..." - sed -i 's/^exec "\$codex_bin"/exec proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" "\$codex_bin"/' "$BIN_DIR/ai-gpt" - sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-deepseek" - sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-kimi" - sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-glm" - sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-openrouter" - sed -i 's|^\([[:space:]]*\)"\$agy_bin" --dangerously-skip-permissions "\$@"|\1proxychains4 -f "\$HOME/.proxychains-xray.conf" "\$agy_bin" --dangerously-skip-permissions "\$@"|' "$BIN_DIR/ai-gemini" - sed -i 's|^\([[:space:]]*\)"\$cursor_bin" --force "\$@"|\1proxychains4 -f "\$HOME/.proxychains-xray.conf" "\$cursor_bin" --force "\$@"|' "$BIN_DIR/ai-cursor" - sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-claude" - sed -i 's/^claude --dangerously-skip-permissions/proxychains4 -f "\$HOME\/\.proxychains-xray\.conf" claude --dangerously-skip-permissions/' "$BIN_DIR/ai-opencode" - success "proxychains4 интегрирован" -fi - # Подготавливаем изолированные config dir'ы для Claude Code-лаунчеров, # чтобы hooks (/switch-account, /add-account) и settings.json были на месте # ещё до первого запуска пользователем. diff --git a/scripts/fuck-rkn.sh b/scripts/fuck-rkn.sh deleted file mode 100644 index 8d4a897..0000000 --- a/scripts/fuck-rkn.sh +++ /dev/null @@ -1,1909 +0,0 @@ -#!/bin/bash -# Copyright (c) 2026 Виталий Никитенко. MIT License. -# ============================================================================= -# setup.sh — Единый установщик: -# • Telegram MTProto прокси (nginx stream + telemt + fail2ban) -# • VPN обход блокировок (nginx stream + xray VLESS+Reality) -# -# Схема: клиент → nginx:443 (SNI-роутинг) → telemt | xray (127.0.0.1) -# -# Адаптировано под «сибирскую» схему DPI (июнь 2026, разбор @hyperion_cs): -# заморозка включается при совпадении трёх сигналов (логическое И): -# 1) подозрительная подсеть сервера — рвём выбором провайдера/CDN (вне скрипта); -# 2) массовый TLS-фингерпринт — random_fp() даёт только «лояльные» -# пресеты uTLS (firefox/edge/360/qq/android/random), без chrome/safari/ios -# и без синтетического randomized; -# 3) лавина TLS-соединений к одному SNI — лечим XHTTP + XMUX (mux в outbound). -# Достаточно стабильно нарушать ЛЮБОЕ одно звено — правило не срабатывает. -# -# Использование: -# bash setup.sh — интерактивное меню -# bash setup.sh telemt — telemt direct: 3 инстанса (443/5223/8530) + UFW rate-limit -# bash setup.sh telemt-rf — telemt РФ-нода: те же инстансы, но выход через SOCKS5 -# (бэкенд systemd|docker спрашивается, или env TELEMT_BACKEND) -# bash setup.sh update [mss] — обновить telemt и задать client_mss во всех инстансах -# (mss: tspu|extreme-low|2in8|88..4096|"", по умолч. tspu) -# bash setup.sh xray-euro — установить xray евро-ноду (Reality direct, порт 443→853→8443) -# bash setup.sh xray-rf — установить xray РФ-ноду (Reality direct, порт 443→853→8443, мост → евро) -# bash setup.sh status — статус всех сервисов -# bash setup.sh purge — полное удаление всего -# ============================================================================= -set -euo pipefail - -# ══════════════════════════════════════════════════════════════════════════════ -# КОНСТАНТЫ -# ══════════════════════════════════════════════════════════════════════════════ -NGINX_CONF_MAIN="/etc/nginx/nginx.conf" -NGINX_STREAM_DIR="/etc/nginx/stream.d" -NGINX_STREAM_LOG="/var/log/nginx/stream-access.log" -TELEMT_STREAM_CONF="${NGINX_STREAM_DIR}/telemt.conf" -XRAY_STREAM_CONF="${NGINX_STREAM_DIR}/xray.conf" -F2B_FILTER="/etc/fail2ban/filter.d/nginx-stream-empty-sni.conf" -F2B_JAIL="/etc/fail2ban/jail.d/nginx-stream-empty-sni.local" -XRAY_DIR="/usr/local/etc/xray" -XRAY_BIN="/usr/local/bin/xray" -XRAY_STATE_FILE="/root/euro-node.conf" -NGINX_EXT_PORT=443 # внешний порт nginx (всегда 443) - -# ══════════════════════════════════════════════════════════════════════════════ -# ЦВЕТА И ВЫВОД -# ══════════════════════════════════════════════════════════════════════════════ -RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m' -CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m' - -info() { echo -e "${CYAN}[INFO]${NC} $*"; } -ok() { echo -e "${GREEN}[OK]${NC} $*"; } -warn() { echo -e "${YELLOW}[WARN]${NC} $*" >&2; } -die() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; } -section() { echo -e "\n${BOLD}${CYAN}══════════ $* ══════════${NC}"; } -banner() { - echo -e "${CYAN}${BOLD}" - echo "╔══════════════════════════════════════════════════════════╗" - echo "║ nginx + telemt + xray — Установщик ║" - echo "╚══════════════════════════════════════════════════════════╝" - echo -e "${NC}" -} - -# ══════════════════════════════════════════════════════════════════════════════ -# ПРАВА И ОС -# ══════════════════════════════════════════════════════════════════════════════ -[[ $EUID -eq 0 ]] || die "Запустите от root или через sudo" - -detect_os() { - OS_FAMILY="" - if [[ -f /etc/os-release ]]; then - source /etc/os-release - case "${ID:-}" in - debian|ubuntu|linuxmint|raspbian) OS_FAMILY="debian" ;; - rhel|centos|fedora|rocky|almalinux) OS_FAMILY="rhel" ;; - alpine) OS_FAMILY="alpine" ;; - *) - case "${ID_LIKE:-}" in - *debian*) OS_FAMILY="debian" ;; - *rhel*|*fedora*) OS_FAMILY="rhel" ;; - esac ;; - esac - fi - [[ -f /etc/alpine-release ]] && OS_FAMILY="alpine" - [[ -z "$OS_FAMILY" ]] && die "Неподдерживаемый дистрибутив" - ok "ОС: ${OS_FAMILY} (${ID:-?} ${VERSION_ID:-})" -} - -pkg_update() { - case "$OS_FAMILY" in - debian) DEBIAN_FRONTEND=noninteractive apt-get update -qq ;; - rhel) dnf makecache -q 2>/dev/null || yum makecache -q ;; - alpine) apk update -q ;; - esac -} - -pkg_install() { - case "$OS_FAMILY" in - debian) DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "$@" ;; - rhel) dnf install -y -q "$@" 2>/dev/null || yum install -y -q "$@" ;; - alpine) apk add --no-cache "$@" ;; - esac -} - -svc_enable() { systemctl enable "$1" 2>/dev/null || rc-update add "$1" default 2>/dev/null || true; } -svc_start() { systemctl start "$1" 2>/dev/null || rc-service "$1" start 2>/dev/null || true; } -svc_restart() { systemctl restart "$1" 2>/dev/null || rc-service "$1" restart 2>/dev/null || true; } -svc_stop() { systemctl stop "$1" 2>/dev/null || rc-service "$1" stop 2>/dev/null || true; } -svc_disable() { systemctl disable "$1" 2>/dev/null || rc-update del "$1" 2>/dev/null || true; } -svc_active() { systemctl is-active --quiet "$1" 2>/dev/null; } - -get_svc_mgr() { - if command -v systemctl &>/dev/null && [[ -d /run/systemd/system ]]; then echo "systemd" - elif command -v rc-service &>/dev/null; then echo "openrc" - else echo "none"; fi -} - -# ══════════════════════════════════════════════════════════════════════════════ -# ПОРТЫ -# ══════════════════════════════════════════════════════════════════════════════ -port_in_use() { - local p="$1" - ss -tlnH 2>/dev/null | awk '{print $4}' | grep -qE ":${p}$" && return 0 - netstat -tlnH 2>/dev/null | awk '{print $4}' | grep -qE ":${p}$" && return 0 - local hex; hex="$(printf '%04X' "$p")" - grep -qi ":${hex} " /proc/net/tcp /proc/net/tcp6 2>/dev/null && return 0 - return 1 -} - -find_free_port() { - local candidates=("$@") - for p in "${candidates[@]}"; do - port_in_use "$p" || { echo "$p"; return; } - done - echo "${candidates[-1]}" -} - -show_ports() { - section "Занятые порты" - local watch="22 80 443 8080 8443 9090 9091 18443 19443 28443" - local raw; raw="$(ss -tlnpH 2>/dev/null || netstat -tlnpH 2>/dev/null || true)" - printf " ${BOLD}%-8s %-22s %-20s %s${NC}\n" "ПОРТ" "АДРЕС" "СЕРВИС" "ПРОЦЕСС" - printf " %s\n" "──────────────────────────────────────────────────────────" - local found=0 - for p in $watch; do - local line; line="$(echo "$raw" | awk -v pt=":${p}" '$4 ~ pt"$" {print; exit}')" - [[ -z "$line" ]] && continue - found=1 - local addr proc svc col - addr="$(echo "$line" | awk '{print $4}')" - proc="$(echo "$line" | grep -oP 'users:\(\("([^"]+)"' | head -1 | grep -oP '"[^"]+"' | tr -d '"' || echo "?")" - case "$p" in - 22) svc="SSH"; col="$GREEN" ;; - 443|8443) svc="HTTPS"; col="$CYAN" ;; - 9091) svc="telemt API"; col="$CYAN" ;; - 18443|19443|28443) svc="внутр.порт"; col="$CYAN" ;; - *) svc="—"; col="$YELLOW" ;; - esac - printf " ${col}%-8s %-22s %-20s %s${NC}\n" "$p" "$addr" "$svc" "${proc:-?}" - done - [[ $found -eq 0 ]] && echo " Ни один из отслеживаемых портов не занят" - printf " %s\n\n" "──────────────────────────────────────────────────────────" -} - -get_public_ip() { - local ip="" - ip="$(curl -s4 -m 5 ifconfig.me 2>/dev/null || curl -s4 -m 5 api.ipify.org 2>/dev/null || true)" - echo "${ip:-}" -} - -# ══════════════════════════════════════════════════════════════════════════════ -# NGINX — общие функции (используются обоими модулями) -# ══════════════════════════════════════════════════════════════════════════════ - -# Проверка: установлен ли nginx с поддержкой stream -nginx_stream_ok() { - command -v nginx &>/dev/null || return 1 - local tmpd; tmpd="$(mktemp -d)" - cat > "$tmpd/nginx.conf" << 'NCEOF' -include /etc/nginx/modules-enabled/*.conf; -events {} -stream {} -NCEOF - local out; out="$(nginx -t -c "$tmpd/nginx.conf" 2>&1)" - rm -rf "$tmpd" - echo "$out" | grep -q 'unknown directive "stream"' && return 1 - return 0 -} - -# Устанавливаем nginx + stream модуль если нужно -nginx_ensure() { - section "nginx" - if nginx_stream_ok; then - ok "nginx со stream модулем уже установлен" - return - fi - - info "Устанавливаю nginx..." - case "$OS_FAMILY" in - debian) - pkg_install nginx - pkg_install libnginx-mod-stream 2>/dev/null || pkg_install nginx-full 2>/dev/null || true ;; - rhel) - pkg_install nginx 2>/dev/null || true - pkg_install nginx-mod-stream 2>/dev/null || true ;; - alpine) - pkg_install nginx nginx-mod-stream ;; - esac - - nginx_stream_ok || die "nginx stream модуль недоступен после установки. Попробуйте вручную: apt install libnginx-mod-stream" - ok "nginx установлен" -} - -# Восстанавливаем nginx.conf если он повреждён (нет events{} или http{}) -nginx_restore_if_broken() { - # Считаем повреждённым если нет блока events{} - if grep -q 'events[[:space:]]*{' "$NGINX_CONF_MAIN" 2>/dev/null; then - return 0 # файл в порядке - fi - - warn "nginx.conf повреждён (отсутствует блок events{}) — восстанавливаю..." - cat > "$NGINX_CONF_MAIN" << 'NGINX_DEFAULT' -user www-data; -worker_processes auto; -pid /run/nginx.pid; -include /etc/nginx/modules-enabled/*.conf; - -events { - worker_connections 768; -} - -http { - sendfile on; - tcp_nopush on; - types_hash_max_size 2048; - include /etc/nginx/mime.types; - default_type application/octet-stream; - ssl_protocols TLSv1.2 TLSv1.3; - ssl_prefer_server_ciphers on; - access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log; - gzip on; - include /etc/nginx/conf.d/*.conf; - include /etc/nginx/sites-enabled/*; -} -NGINX_DEFAULT - ok "nginx.conf восстановлен" -} - -# Добавляем блок stream{} в nginx.conf (идемпотентно) -nginx_ensure_stream_block() { - # Сначала проверяем целостность файла - nginx_restore_if_broken - - # Проверяем через grep -E (Extended regex) — поддерживает | - if grep -qE 'proxy-stream-block|telemt-stream-block|xray-stream-block' "$NGINX_CONF_MAIN" 2>/dev/null; then - ok "Блок stream{} уже есть в nginx.conf" - return - fi - # Дополнительно — вдруг stream{} добавлен вручную - if grep -q 'stream[[:space:]]*{' "$NGINX_CONF_MAIN" 2>/dev/null; then - ok "Блок stream{} уже есть в nginx.conf" - return - fi - - info "Добавляю блок stream{} в конец nginx.conf..." - mkdir -p "$NGINX_STREAM_DIR" - printf '\n# proxy-stream-block\nstream {\n include /etc/nginx/stream.d/*.conf;\n}\n# /proxy-stream-block\n' \ - >> "$NGINX_CONF_MAIN" -} - -# Перезапуск nginx с валидацией -nginx_reload() { - info "Проверяю конфигурацию nginx..." - if ! nginx -t 2>&1; then - die "Конфигурация nginx невалидна — см. вывод выше" - fi - - # Если внешний порт уже занят НЕ nginx (частый случай — легаси telemt, - # держащий 443 напрямую), nginx упадёт с 'Address already in use'. - # Ловим это заранее и даём понятную диагностику вместо туманной ошибки. - if port_in_use "$NGINX_EXT_PORT"; then - local holder - holder="$(ss -tlnpH "( sport = :${NGINX_EXT_PORT} )" 2>/dev/null | grep -oE 'users:\(\("[^"]+"' | grep -oE '"[^"]+"' | tr -d '"' | head -1 || true)" - if [[ -n "$holder" && "$holder" != "nginx" ]]; then - warn "Порт ${NGINX_EXT_PORT} уже занят процессом '${holder}' — nginx не сможет его забиндить." - if [[ "$holder" == telemt* ]]; then - warn "Похоже, это telemt в direct-режиме (он сам слушает ${NGINX_EXT_PORT})." - warn "На этой ноде xray ходит через nginx:${NGINX_EXT_PORT} — нельзя держать оба на одном порту." - warn "Останови/удали telemt: systemctl stop telemt* ; либо запусти 'bash $0 purge' для очистки легаси." - fi - die "Освободи порт ${NGINX_EXT_PORT} и повтори (см. предупреждения выше)." - fi - fi - - svc_enable nginx - svc_restart nginx - ok "nginx перезапущен, слушает :${NGINX_EXT_PORT}" -} - -# Проверяем доступность сервиса через nginx -nginx_check_port() { - local sni="$1" - info "Проверяю доступность ${sni}:${NGINX_EXT_PORT}..." - local code - code="$(curl -sk -o /dev/null -w '%{http_code}' \ - --connect-timeout 5 \ - --resolve "${sni}:${NGINX_EXT_PORT}:127.0.0.1" \ - "https://${sni}:${NGINX_EXT_PORT}" 2>/dev/null || true)" - if [[ "$code" =~ ^[0-9]+$ ]]; then - ok "nginx отвечает на :${NGINX_EXT_PORT} (HTTP ${code}) ✓" - else - warn "nginx не ответил на :${NGINX_EXT_PORT} — проверьте: ufw status, systemctl status nginx" - fi -} - -# Открываем внешний порт в файрволе -firewall_open() { - local port="$1" label="${2:-proxy}" - if command -v ufw &>/dev/null; then - ufw allow "${port}/tcp" comment "$label" > /dev/null 2>&1 || true - ok "ufw: порт ${port}/tcp открыт" - elif command -v firewall-cmd &>/dev/null; then - firewall-cmd --permanent --add-port="${port}/tcp" > /dev/null 2>&1 || true - firewall-cmd --reload > /dev/null 2>&1 || true - ok "firewalld: порт ${port}/tcp открыт" - elif command -v iptables &>/dev/null; then - iptables -C INPUT -p tcp --dport "$port" -j ACCEPT 2>/dev/null || \ - iptables -A INPUT -p tcp --dport "$port" -j ACCEPT 2>/dev/null || true - ok "iptables: порт ${port}/tcp открыт" - else - warn "Файрвол не найден — откройте порт ${port} вручную" - fi -} - -# Блокируем внутренний порт снаружи (только lo) -firewall_block_internal() { - local port="$1" - if command -v iptables &>/dev/null; then - iptables -C INPUT -p tcp --dport "$port" -i lo -j ACCEPT 2>/dev/null || \ - iptables -I INPUT 1 -p tcp --dport "$port" -i lo -j ACCEPT 2>/dev/null || true - iptables -C INPUT -p tcp --dport "$port" -j DROP 2>/dev/null || \ - iptables -A INPUT -p tcp --dport "$port" -j DROP 2>/dev/null || true - ok "Внутренний порт ${port} доступен только через lo" - fi -} - -# Записываем stream-конфиг с map по SNI (один server на порту 443) -# Принимает: файл назначения, лог-файл, массив пар "sni:backend" -nginx_write_stream_conf() { - local dest="$1" logfile="$2" - shift 2 - local pairs=("$@") # "sni1:backend1" "sni2:backend2" ... - - mkdir -p "$NGINX_STREAM_DIR" "$(dirname "$logfile")" - - { - cat << 'HEADER' -# nginx stream: SNI-роутинг (авто-генерация setup.sh) -# Редактируйте /etc/nginx/stream.d/*.conf или перезапустите setup.sh - -log_format proxy_stream '$remote_addr [$time_local] ' - '$protocol $status $bytes_sent $bytes_received ' - '$session_time "$ssl_preread_server_name" -> $upstream_addr'; -HEADER - echo "access_log ${logfile} proxy_stream;" - echo "" - echo "map \$ssl_preread_server_name \$proxy_backend {" - echo ' default "";' - for pair in "${pairs[@]}"; do - local sni="${pair%%:*}" - local backend="${pair#*:}" - printf ' "%s" "%s";\n' "${sni}" "${backend}" - done - echo "}" - echo "" - cat << 'SERVER' -server { - listen 443; - ssl_preread on; - proxy_connect_timeout 5s; - proxy_timeout 600s; - proxy_pass $proxy_backend; -} -SERVER - } > "$dest" - - ok "Stream конфиг записан → ${dest}" -} - -# Добавляем или обновляем одну запись в существующем map -nginx_upsert_map_entry() { - local conf="$1" sni="$2" backend="$3" - - if grep -qF "\"${sni}\"" "$conf" 2>/dev/null; then - # Обновляем существующую - sed -i "s|\"${sni}\".*|\"${sni}\" \"${backend}\";|" "$conf" - info "Обновлена запись SNI '${sni}' → ${backend}" - else - # Вставляем новую после строки с default - sed -i "/default.*\"\";/a\\ \"${sni}\" \"${backend}\";" "$conf" - ok "Добавлена запись SNI '${sni}' → ${backend}" - fi -} - -# ══════════════════════════════════════════════════════════════════════════════ -# FAIL2BAN (для telemt — баним пустой/неверный SNI) -# ══════════════════════════════════════════════════════════════════════════════ -configure_fail2ban() { - section "fail2ban" - if ! command -v fail2ban-client &>/dev/null; then - info "Устанавливаю fail2ban..." - case "$OS_FAMILY" in - debian) pkg_install fail2ban ;; - rhel) - pkg_install fail2ban 2>/dev/null || { - pkg_install epel-release 2>/dev/null || true - pkg_install fail2ban 2>/dev/null || { warn "fail2ban недоступен"; return; } - } ;; - alpine) pkg_install fail2ban 2>/dev/null || { warn "fail2ban недоступен"; return; } ;; - esac - fi - - mkdir -p "$(dirname "$F2B_FILTER")" "$(dirname "$F2B_JAIL")" - - tee "$F2B_FILTER" > /dev/null << 'EOF' -[Definition] -failregex = ^ \[.*\] \S+ \S+ \S+ \S+ \S+ ".*" -> -$ -ignoreregex = -datepattern = \[%%d/%%b/%%Y:%%H:%%M:%%S %%z\] -EOF - - cat > "$F2B_JAIL" << EOF -[nginx-stream-empty-sni] -enabled = true -filter = nginx-stream-empty-sni -logpath = ${NGINX_STREAM_LOG} -backend = auto -findtime = 300 -maxretry = 10 -bantime = 21600 -action = iptables-multiport[name=nginx-stream-empty-sni, port="${NGINX_EXT_PORT}", protocol=tcp] -EOF - - svc_enable fail2ban - svc_restart fail2ban - ok "fail2ban: 10 попыток / 5мин → бан 6ч" -} - -# ══════════════════════════════════════════════════════════════════════════════ -# МОДУЛЬ TELEMT -# ══════════════════════════════════════════════════════════════════════════════ -# client_mss: "" (не трогать MSS) | пресеты extreme-low|tspu|2in8 | число 88..4096 -# Дефолт "tspu" (=92) — быстрый фикс под агрессивный профиль ТСПУ. -# Требует telemt >= 3.4.15. -# В direct-схеме (без nginx) MSS реально доходит до клиента — здесь он работает. -TELEMT_CLIENT_MSS="${TELEMT_CLIENT_MSS:-tspu}" -TELEMT_MIN_MSS_VERSION="3.4.15" - -# ── Direct-инстансы (без nginx): порт|домен|api_порт ── -# Каждая пара порт+домен подобрана под повседневный фоновый трафик ОС/сервисов: -# 443 + cloudflare → обычный HTTPS/CDN -# 5223 + apple → Apple Push Notification Service (APNs) -# 8530 + microsoft → Windows Update / WSUS -# Несколько инстансов = запас на точечную блокировку пары порт+домен. -# Формат строки: "ИМЯ:ПОРТ:ДОМЕН:API_ПОРТ" -TELEMT_DIRECT_INSTANCES=( - "telemt1:443:www.cloudflare.com:9091" - "telemt2:5223:www.apple.com:9092" - "telemt3:8530:www.microsoft.com:9093" -) - -# Карта «основной порт → запасной», если основной занят (конфликт bind). -# 443 → 853: оба не шейпятся, 853 = DNS-over-TLS. Для остальных запасного нет -# (5223/8530 редко заняты). Это про доступность порта, не про обход DPI. -declare -A TELEMT_PORT_FALLBACK=( - [443]=853 -) - -# Фактические порты инстансов после возможной подмены (заполняется при установке). -TELEMT_DIRECT_PORTS=() - -# ── Бэкенд запуска: "systemd" (нативный бинарь + юниты) или "docker" ── -# Можно задать заранее через переменную окружения, иначе спросим в меню. -TELEMT_BACKEND="${TELEMT_BACKEND:-}" -# Официальный образ telemt (можно переопределить в env). -TELEMT_IMAGE="${TELEMT_IMAGE:-ghcr.io/telemt/telemt:latest}" -# docker-compose файл для 3 инстансов. -TELEMT_COMPOSE="/etc/telemt/docker-compose.yml" - -# ── SOCKS5 upstream (РФ-нода) ── -# Если заданы — telemt отправляет исходящий трафик к Telegram через SOCKS5 -# (xray/sing-box/ssh -D на localhost). По FAQ telemt для этого режима ОБЯЗАТЕЛЕН -# use_middle_proxy=false (он у нас и так false). Пусто = direct (выход прямой). -TELEMT_SOCKS5_HOST="" -TELEMT_SOCKS5_PORT="" -TELEMT_SOCKS5_USER="" -TELEMT_SOCKS5_PASS="" - -# Валидация значения client_mss. Печатает нормализованное значение, либо die. -telemt_validate_mss() { - local v="$1" - case "$v" in - ""|extreme-low|tspu|2in8) printf '%s' "$v"; return 0 ;; - *) - if [[ "$v" =~ ^[0-9]+$ ]] && (( v >= 88 && v <= 4096 )); then - printf '%s' "$v"; return 0 - fi - die "Недопустимое значение client_mss: '${v}' (ожидается пусто|extreme-low|tspu|2in8|число 88..4096)" - ;; - esac -} - -# Сравнение версий: возвращает 0, если $1 >= $2 (semver x.y.z). -telemt_version_ge() { - local a="$1" b="$2" - [[ "$(printf '%s\n%s\n' "$a" "$b" | sort -V | head -1)" == "$b" ]] -} - -telemt_detect_arch() { - local m; m="$(uname -m)" - case "$m" in - x86_64|amd64) - if grep -qE 'avx2.*bmi2|bmi2.*avx2' /proc/cpuinfo 2>/dev/null; then echo "x86_64-v3" - else echo "x86_64"; fi ;; - aarch64|arm64) echo "aarch64" ;; - *) die "Неподдерживаемая архитектура: $m" ;; - esac -} - -telemt_detect_libc() { - for f in /lib/ld-musl-*.so.* /lib64/ld-musl-*.so.*; do [[ -e "$f" ]] && { echo musl; return; }; done - grep -qE '^ID="?alpine"?' /etc/os-release 2>/dev/null && { echo musl; return; } - echo gnu -} - -telemt_gen_secret() { - local s="" - s="$(openssl rand -hex 16 2>/dev/null || true)" - [[ ${#s} -eq 32 ]] && { echo "$s"; return; } - s="$(dd if=/dev/urandom bs=16 count=1 2>/dev/null | od -An -tx1 | tr -d ' \n')" - [[ ${#s} -eq 32 ]] && { echo "$s"; return; } - die "Не удалось сгенерировать секрет" -} - -telemt_ensure_user() { - mkdir -p /opt/telemt /etc/telemt - - if [[ "$TELEMT_BACKEND" == "docker" ]]; then - # Контейнер бежит от root (user: "0:0"); системный юзер telemt на хосте - # не нужен. telemt-API пишет config.toml через write-temp → rename в той - # же директории, поэтому каталог должен быть доступен на запись владельцу. - chown root:root /opt/telemt /etc/telemt - chmod 755 /opt/telemt /etc/telemt - return - fi - - # systemd: telemt бежит под непривилегированным юзером telemt - local nologin; nologin="$(command -v nologin 2>/dev/null || echo /bin/false)" - getent group telemt &>/dev/null || grep -q '^telemt:' /etc/group 2>/dev/null || \ - groupadd -r telemt 2>/dev/null || addgroup -S telemt 2>/dev/null || true - getent passwd telemt &>/dev/null || grep -q '^telemt:' /etc/passwd 2>/dev/null || \ - useradd -r -g telemt -d /opt/telemt -s "$nologin" -c "Telemt" telemt 2>/dev/null || \ - adduser -S -D -H -h /opt/telemt -s "$nologin" -G telemt telemt 2>/dev/null || true - - chown telemt:telemt /opt/telemt && chmod 750 /opt/telemt - chown telemt:telemt /etc/telemt && chmod 750 /etc/telemt -} - -# Генерация конфигов всех инстансов (общее для systemd и docker). -# Заполняет TELEMT_DIRECT_PORTS и TELEMT_DIRECT_SECRETS, пишет /etc/telemt/telemtN.toml. -TELEMT_DIRECT_SECRETS=() -TELEMT_INSTALLED_VERSION="" -_telemt_generate_configs() { - telemt_ensure_user - - local mss; mss="$(telemt_validate_mss "$TELEMT_CLIENT_MSS")" - local client_mss_line="" - [[ -n "$mss" ]] && client_mss_line="client_mss = \"${mss}\"" - - # Блок upstream: SOCKS5 (РФ-нода) либо direct (прямой выход). - # type="socks5" требует use_middle_proxy=false (у нас уже так). - local upstream_block - if [[ -n "$TELEMT_SOCKS5_HOST" && -n "$TELEMT_SOCKS5_PORT" ]]; then - upstream_block=$'\n[[upstreams]]\ntype = "socks5"\naddress = "'"${TELEMT_SOCKS5_HOST}:${TELEMT_SOCKS5_PORT}"$'"' - if [[ -n "$TELEMT_SOCKS5_USER" && -n "$TELEMT_SOCKS5_PASS" ]]; then - upstream_block+=$'\nusername = "'"${TELEMT_SOCKS5_USER}"$'"\npassword = "'"${TELEMT_SOCKS5_PASS}"$'"' - fi - upstream_block+=$'\nweight = 1\nenabled = true' - else - upstream_block=$'\n[[upstreams]]\ntype = "direct"\nweight = 1\nenabled = true' - fi - - local idx=0 - TELEMT_DIRECT_SECRETS=() - - local entry name port domain api secret escaped_domain - for entry in "${TELEMT_DIRECT_INSTANCES[@]}"; do - IFS=':' read -r name port domain api <<< "$entry" - - # Если основной порт занят (напр. nginx/apache/панель уже на 443) — - # переключаемся на запасной из TELEMT_PORT_FALLBACK. Это решает конфликт - # bind, а НЕ проблему DPI: по замерам мая-2026 853/8443 на федеральных - # операторах блокируются наравне с 443. - if port_in_use "$port"; then - local fb="${TELEMT_PORT_FALLBACK[$port]:-}" - if [[ -n "$fb" ]] && ! port_in_use "$fb"; then - warn "Порт ${port} занят — переключаю ${name} на запасной ${fb}" - port="$fb" - elif [[ -n "$fb" ]]; then - warn "Порт ${port} и запасной ${fb} оба заняты — ${name} может не запуститься (bind error)" - else - warn "Порт ${port} занят, запасного нет — ${name} может не запуститься" - fi - fi - TELEMT_DIRECT_PORTS[idx]="$port" - - secret="$(telemt_gen_secret)" - TELEMT_DIRECT_SECRETS[idx]="$secret" - escaped_domain="$(printf '%s' "$domain" | sed 's/\\/\\\\/g; s/"/\\"/g')" - - cat > "/etc/telemt/${name}.toml" << EOF -[general] -fast_mode = true -use_middle_proxy = false - -[general.modes] -classic = false -secure = false -tls = true - -[network] -ipv4 = true -ipv6 = false -prefer = 4 - -[server] -port = ${port} -listen_addr_ipv4 = "0.0.0.0" -${client_mss_line} - -[server.api] -enabled = true -listen = "127.0.0.1:${api}" -whitelist = ["127.0.0.1/32"] - -[censorship] -tls_domain = "${escaped_domain}" -mask = true -mask_port = 443 -tls_emulation = true -unknown_sni_action = "reject_handshake" -fake_cert_len = 2048 - -[access] -replay_check_len = 65536 -ignore_time_skew = false - -[access.users] -${name} = "${secret}" -${upstream_block} -EOF - # Права на конфиг зависят от бэкенда: - # - systemd: telemt бежит под юзером telemt → 640 root:telemt (узкие права); - # - docker: внутри контейнера группы telemt нет, образ может бежать под - # другим UID → даём 644 root:root, чтобы конфиг гарантированно читался. - if [[ "$TELEMT_BACKEND" == "docker" ]]; then - chown root:root "/etc/telemt/${name}.toml" - chmod 644 "/etc/telemt/${name}.toml" - else - chown root:telemt "/etc/telemt/${name}.toml" - chmod 640 "/etc/telemt/${name}.toml" - fi - ok "${name}: порт ${port}, ${domain}, api ${api}" - idx=$((idx + 1)) - done -} - -# ── Бэкенд systemd: нативный бинарь + юнит на инстанс ── -install_telemt_systemd() { - section "Установка telemt (systemd, ${#TELEMT_DIRECT_INSTANCES[@]} инстанса)" - - TELEMT_INSTALLED_VERSION="$(telemt_download_binary)" - [[ -n "$TELEMT_INSTALLED_VERSION" ]] || die "Не удалось установить бинарь telemt" - - if [[ -n "$TELEMT_CLIENT_MSS" ]] && ! telemt_version_ge "$TELEMT_INSTALLED_VERSION" "$TELEMT_MIN_MSS_VERSION"; then - warn "telemt ${TELEMT_INSTALLED_VERSION} < ${TELEMT_MIN_MSS_VERSION}: client_mss будет проигнорирован ядром telemt." - fi - - _telemt_generate_configs - - local svcmgr; svcmgr="$(get_svc_mgr)" - local entry name port domain api - for entry in "${TELEMT_DIRECT_INSTANCES[@]}"; do - IFS=':' read -r name port domain api <<< "$entry" - if [[ "$svcmgr" == "systemd" ]]; then - cat > "/etc/systemd/system/${name}.service" << EOF -[Unit] -Description=Telemt MTProto Proxy (${domain}) -After=network-online.target -Wants=network-online.target - -[Service] -Type=simple -User=telemt -Group=telemt -WorkingDirectory=/opt/telemt -ExecStart=/bin/telemt /etc/telemt/${name}.toml -Restart=on-failure -RestartSec=5 -LimitNOFILE=65536 -AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE -CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE -NoNewPrivileges=true - -[Install] -WantedBy=multi-user.target -EOF - elif [[ "$svcmgr" == "openrc" ]]; then - cat > "/etc/init.d/${name}" << EOF -#!/sbin/openrc-run -name="${name}" -command="/bin/telemt" -command_args="/etc/telemt/${name}.toml" -command_background=true -command_user="telemt:telemt" -pidfile="/run/${name}.pid" -directory="/opt/telemt" -rc_ulimit="-n 65536" -depend() { need net; } -EOF - chmod 0755 "/etc/init.d/${name}" - fi - done - - [[ "$svcmgr" == "systemd" ]] && systemctl daemon-reload - - for entry in "${TELEMT_DIRECT_INSTANCES[@]}"; do - IFS=':' read -r name _ _ _ <<< "$entry" - svc_enable "$name" - svc_restart "$name" - done - - ok "telemt ${TELEMT_INSTALLED_VERSION} установлен (systemd)" -} - -# ── Бэкенд docker: официальный образ + 3 сервиса в одном compose ── -# network_mode: host — порты контейнеров видны на хосте напрямую (как systemd), -# что сохраняет работу UFW per-port rate-limit и client_mss (нужен NET_ADMIN). -install_telemt_docker() { - section "Установка telemt (docker, ${#TELEMT_DIRECT_INSTANCES[@]} инстанса)" - - telemt_ensure_docker - _telemt_generate_configs - - info "Тяну образ ${TELEMT_IMAGE}..." - docker pull "$TELEMT_IMAGE" || warn "Не удалось обновить образ (использую локальный, если есть)" - - # Собираем docker-compose с 3 сервисами. - # client_mss требует CAP_NET_ADMIN; бинд привилегированных портов — NET_BIND_SERVICE. - # Конфиг монтируем как директорию /etc/telemt (требование API telemt для - # атомарного rename), каждому сервису передаём свой файл аргументом. - { - echo "services:" - local entry name port domain api - for entry in "${TELEMT_DIRECT_INSTANCES[@]}"; do - IFS=':' read -r name port domain api <<< "$entry" - cat << EOF - ${name}: - image: ${TELEMT_IMAGE} - container_name: ${name} - restart: unless-stopped - network_mode: host - user: "0:0" - cap_add: - - NET_BIND_SERVICE - - NET_ADMIN - volumes: - - /etc/telemt:/etc/telemt - command: ["/etc/telemt/${name}.toml"] - ulimits: - nofile: - soft: 65536 - hard: 65536 - logging: - driver: json-file - options: - max-size: "10m" - max-file: "3" -EOF - done - } > "$TELEMT_COMPOSE" - chmod 640 "$TELEMT_COMPOSE" - - info "Запускаю контейнеры..." - (cd /etc/telemt && docker compose up -d) || die "Не удалось запустить контейнеры telemt" - - TELEMT_INSTALLED_VERSION="$(docker run --rm "$TELEMT_IMAGE" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "?")" - ok "telemt ${TELEMT_INSTALLED_VERSION} установлен (docker)" -} - -# Установка Docker по официальному скрипту, если его нет. -telemt_ensure_docker() { - if command -v docker &>/dev/null && docker compose version &>/dev/null; then - return 0 - fi - info "Устанавливаю Docker (get.docker.com)..." - curl -fsSL https://get.docker.com | sh || die "Не удалось установить Docker" - svc_enable docker; svc_start docker - command -v docker &>/dev/null || die "Docker не установлен" - docker compose version &>/dev/null || die "docker compose недоступен (нужен Compose v2)" -} - -# Диспетчер бэкенда: вызывает нужный установщик. -install_telemt_direct() { - case "$TELEMT_BACKEND" in - docker) install_telemt_docker ;; - *) install_telemt_systemd ;; - esac -} - -# Определить установленный бэкенд по артефактам на диске. -# Печатает "docker" | "systemd" | "" (не установлен). -telemt_detect_backend() { - [[ -f "$TELEMT_COMPOSE" ]] && { echo "docker"; return; } - ls /etc/telemt/telemt*.toml &>/dev/null && { echo "systemd"; return; } - echo "" -} - -# Активен ли инстанс $1 — независимо от бэкенда. -telemt_instance_active() { - local name="$1" - if [[ "$(telemt_detect_backend)" == "docker" ]]; then - command -v docker &>/dev/null && docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "$name" - else - svc_active "$name" 2>/dev/null - fi -} - -# Перезапустить все инстансы — независимо от бэкенда. -telemt_restart_all() { - if [[ "$(telemt_detect_backend)" == "docker" ]]; then - (cd /etc/telemt && docker compose restart) 2>/dev/null || true - else - local entry name - for entry in "${TELEMT_DIRECT_INSTANCES[@]}"; do - IFS=':' read -r name _ _ _ <<< "$entry" - svc_restart "$name" - done - fi -} - -# Скачать и установить актуальный бинарь telemt поверх существующего. -# Печатает установленную версию в stdout (последней строкой). -telemt_download_binary() { - local arch libc latest fn url tmpd - arch="$(telemt_detect_arch)"; libc="$(telemt_detect_libc)" - info "Архитектура: ${arch}, libc: ${libc}" >&2 - - latest="$(curl -fsI 'https://github.com/telemt/telemt/releases/latest' 2>/dev/null \ - | grep -i '^location:' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true)" - [[ -z "$latest" ]] && die "Не удалось определить версию telemt" - info "Версия: ${latest}" >&2 - - fn="telemt-${arch}-linux-${libc}.tar.gz" - url="https://github.com/telemt/telemt/releases/download/${latest}/${fn}" - tmpd="$(mktemp -d)" - - info "Скачиваю ${fn}..." >&2 - if ! curl -fsSL "$url" -o "${tmpd}/${fn}" 2>/dev/null; then - if [[ "$arch" == "x86_64-v3" ]]; then - warn "x86_64-v3 недоступен, откат к x86_64..." - arch="x86_64"; fn="telemt-${arch}-linux-${libc}.tar.gz" - url="https://github.com/telemt/telemt/releases/download/${latest}/${fn}" - curl -fsSL "$url" -o "${tmpd}/${fn}" || die "Не удалось скачать telemt" - else - die "Не удалось скачать telemt: $url" - fi - fi - - gzip -dc "${tmpd}/${fn}" | tar -xf - -C "$tmpd" 2>/dev/null || die "Ошибка распаковки" - local bin; bin="$(find "$tmpd" -type f -name telemt | head -1)" - [[ -n "$bin" ]] || die "Бинарник telemt не найден в архиве" - - install -m 0755 "$bin" /bin/telemt - command -v setcap &>/dev/null && \ - setcap cap_net_bind_service,cap_net_admin=+ep /bin/telemt 2>/dev/null || true - rm -rf "$tmpd" - - printf '%s' "$latest" -} - -# Идемпотентно записать client_mss в секцию [server] всех direct-конфигов. -# $1 = значение MSS (уже валидированное; пусто = удалить параметр). -telemt_set_mss_in_config() { - local mss="$1" cfg found=0 - for cfg in /etc/telemt/telemt*.toml; do - [[ -f "$cfg" ]] || continue - found=1 - # Удалить любые существующие строки client_mss - sed -i -E '/^[[:space:]]*client_mss[[:space:]]*=/d' "$cfg" - if [[ -n "$mss" ]]; then - if grep -qE '^\[server\][[:space:]]*$' "$cfg"; then - sed -i -E "/^\[server\][[:space:]]*$/a client_mss = \"${mss}\"" "$cfg" - else - warn "Секция [server] не найдена в $cfg — пропуск" - fi - fi - done - [[ "$found" -eq 1 ]] || die "Конфиги telemt не найдены в /etc/telemt/" -} - -# Список имён установленных direct-инстансов (по конфигам на диске). -telemt_installed_instances() { - local cfg name - for cfg in /etc/telemt/telemt*.toml; do - [[ -f "$cfg" ]] || continue - name="$(basename "$cfg" .toml)" - echo "$name" - done -} - -# Обновление telemt + настройка client_mss одной командой (по всем инстансам). -# Использование: update_telemt [MSS] -# MSS: extreme-low|tspu|2in8|число 88..4096|"" (снять параметр). По умолчанию tspu. -update_telemt() { - section "Обновление telemt + client_mss" - - local insts; insts="$(telemt_installed_instances)" - [[ -n "$insts" ]] || \ - die "telemt не установлен (нет /etc/telemt/telemt*.toml). Сначала: bash $0 telemt" - - detect_os - - local backend; backend="$(telemt_detect_backend)" - [[ -n "$backend" ]] || backend="systemd" - info "Бэкенд: ${backend}" - - local mss; mss="$(telemt_validate_mss "${1:-$TELEMT_CLIENT_MSS}")" - - # 1. Обновить telemt: образ (docker) или бинарь (systemd) - local latest - if [[ "$backend" == "docker" ]]; then - telemt_ensure_docker - info "Обновляю образ ${TELEMT_IMAGE}..." - docker pull "$TELEMT_IMAGE" || warn "Не удалось обновить образ" - latest="$(docker run --rm "$TELEMT_IMAGE" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "?")" - else - latest="$(telemt_download_binary)" - fi - - # 2. Проверка поддержки client_mss - if [[ -n "$mss" && "$latest" != "?" ]] && ! telemt_version_ge "$latest" "$TELEMT_MIN_MSS_VERSION"; then - warn "telemt ${latest} < ${TELEMT_MIN_MSS_VERSION}: client_mss не поддерживается этой версией." - fi - - # 3. Прописать параметр во все конфиги (с бэкапом) - local cfg - for cfg in /etc/telemt/telemt*.toml; do - [[ -f "$cfg" ]] && cp -a "$cfg" "${cfg}.bak.$(date +%s)" 2>/dev/null || true - done - telemt_set_mss_in_config "$mss" - if [[ "$backend" == "docker" ]]; then - chown root:root /etc/telemt/telemt*.toml 2>/dev/null || true - chmod 644 /etc/telemt/telemt*.toml 2>/dev/null || true - else - chown root:telemt /etc/telemt/telemt*.toml 2>/dev/null || true - chmod 640 /etc/telemt/telemt*.toml 2>/dev/null || true - fi - - # 4. Применить: docker — пересоздать контейнеры с новым образом; systemd — рестарт - if [[ "$backend" == "docker" ]]; then - (cd /etc/telemt && docker compose up -d) || warn "docker compose up -d не выполнен" - else - telemt_restart_all - fi - sleep 1 - - ok "telemt обновлён до ${latest} (${backend})" - if [[ -n "$mss" ]]; then - ok "client_mss = \"${mss}\" применён ко всем инстансам" - else - ok "client_mss снят — MSS определяет ядро" - fi - info "Проверка: grep client_mss /etc/telemt/telemt*.toml" -} - -# ── UFW: открыть порты инстансов + per-port rate-limit (анти-зондирование) ── -# По гайду: не больше 1 нового SYN/сек с одного IP на каждый порт. -# Раздельные recent-списки на порт (mtp443, mtp5223…) — иначе Telegram при -# переключении между прокси шлёт SYN на все порты разом и лимит рвёт соединение. -telemt_ufw_ratelimit() { - section "UFW: порты + rate-limit" - - if ! command -v ufw &>/dev/null; then - warn "ufw не установлен — пропускаю rate-limit. Открой порты вручную." - return - fi - - # Порты инстансов — фактические (после возможной подмены 443→853). - # Если массив пуст (вызов вне install), читаем порт из конфигов на диске. - local ports=() - local entry name port - if [[ ${#TELEMT_DIRECT_PORTS[@]} -gt 0 ]]; then - ports=("${TELEMT_DIRECT_PORTS[@]}") - else - for entry in "${TELEMT_DIRECT_INSTANCES[@]}"; do - IFS=':' read -r name port _ _ <<< "$entry" - if [[ -f "/etc/telemt/${name}.toml" ]]; then - port="$(awk -F'=' '/^port[[:space:]]*=/{gsub(/[[:space:]]/,"",$2); print $2; exit}' "/etc/telemt/${name}.toml")" - fi - ports+=("$port") - done - fi - - # SSH первым — критично, чтобы не отрезать себя - local ssh_port; ssh_port="$(ss -tlnpH 2>/dev/null | grep -i sshd | grep -oE ':[0-9]+' | head -1 | tr -d ':' || true)" - ssh_port="${ssh_port:-22}" - info "Разрешаю SSH-порт ${ssh_port} перед включением UFW" - ufw allow "${ssh_port}/tcp" >/dev/null 2>&1 || true - - local p - for p in "${ports[@]}"; do - ufw allow "${p}/tcp" >/dev/null 2>&1 || true - done - ufw --force enable >/dev/null 2>&1 || true - - # Модуль xt_recent — без него UFW молча выбросит правила -m recent - modprobe xt_recent 2>/dev/null || true - echo xt_recent > /etc/modules-load.d/xt_recent.conf 2>/dev/null || true - if ! lsmod 2>/dev/null | grep -q xt_recent; then - warn "Модуль xt_recent не загрузился — rate-limit не будет применён." - warn "Проверь вручную: modprobe xt_recent && lsmod | grep xt_recent" - ok "Порты открыты (без rate-limit)" - return - fi - - # Бэкап и вставка правил в ufw-before-input (после established) - local rules="/etc/ufw/before.rules" - if [[ -f "$rules" ]]; then - cp "$rules" "${rules}.bak.$(date +%s)" 2>/dev/null || true - TELEMT_RL_PORTS="${ports[*]}" python3 - "$rules" << 'PYEOF' -import os, sys -path = sys.argv[1] -ports = os.environ["TELEMT_RL_PORTS"].split() -lines = open(path).readlines() -if any("MTProto rate-limit" in l for l in lines): - print(" правила rate-limit уже есть, пропуск"); raise SystemExit -idx = None -for i, l in enumerate(lines): - if "ufw-before-input -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT" in l: - idx = i + 1; break -if idx is None: - # запасная точка: вставка после первого ufw-before-input ACCEPT lo - for i, l in enumerate(lines): - if "ufw-before-input -i lo -j ACCEPT" in l: - idx = i + 1; break -if idx is None: - print(" ОШИБКА: точка вставки в before.rules не найдена"); raise SystemExit -block = ["\n# === MTProto rate-limit (1 SYN/сек на IP per-port) ===\n"] -for p in ports: - block.append(f"-A ufw-before-input -p tcp --dport {p} --syn -m recent --name mtp{p} --rcheck --seconds 1 -j DROP\n") - block.append(f"-A ufw-before-input -p tcp --dport {p} --syn -m recent --name mtp{p} --set -j ACCEPT\n") -block.append("# === конец MTProto rate-limit ===\n") -lines[idx:idx] = block -open(path, "w").writelines(lines) -print(f" rate-limit вставлен для портов: {', '.join(ports)}") -PYEOF - ufw reload >/dev/null 2>&1 || true - ok "UFW rate-limit активен (1 SYN/сек/IP на порт)" - else - warn "$rules не найден — rate-limit пропущен, порты открыты" - fi -} - -# Получить tls-ссылку инстанса из его локального API. $1 = api-порт. -# Печатает ссылку или пусто. Делает несколько попыток (API поднимается не мгновенно). -telemt_link_from_api() { - local api="$1" i link - for i in 1 2 3 4 5; do - link="$(curl -s --max-time 3 "http://127.0.0.1:${api}/v1/users" 2>/dev/null \ - | python3 -c "import sys,json -try: - d=json.load(sys.stdin) - print(d['data'][0]['links']['tls'][0]) -except Exception: - pass" 2>/dev/null)" - [[ -n "$link" ]] && { printf '%s' "$link"; return 0; } - sleep 1 - done - return 1 -} - -# Общая установка telemt (3 инстанса) + UFW + вывод ссылок. -# $1 = роль ("direct" | "rf") — влияет только на заголовок и подсказки. -# Режим выхода определяется переменными TELEMT_SOCKS5_* (заданы до вызова). -_telemt_install_and_report() { - local role="$1" - - detect_os - show_ports - - # Выбор бэкенда запуска, если не задан заранее (env TELEMT_BACKEND). - if [[ -z "$TELEMT_BACKEND" ]]; then - echo "" - echo -e "${BOLD} Бэкенд запуска telemt:${NC}" - echo -e " ${GREEN}1)${NC} systemd (нативный бинарь + юниты)" - echo -e " ${GREEN}2)${NC} docker (официальный образ ${TELEMT_IMAGE}, 3 сервиса)" - echo -n " Выбор [1]: " - read -r _be - case "${_be:-1}" in - 2) TELEMT_BACKEND="docker" ;; - *) TELEMT_BACKEND="systemd" ;; - esac - fi - info "Бэкенд: ${TELEMT_BACKEND}" - - # Зависимости. python3/jq/ufw нужны на хосте в обоих режимах (API-ссылки, - # UFW rate-limit). Для systemd дополнительно нужны tar/gzip/openssl (бинарь); - # для docker — сам docker (ставится в install_telemt_docker). - pkg_update - if [[ "$TELEMT_BACKEND" == "docker" ]]; then - pkg_install curl jq python3 iptables ufw - else - pkg_install curl wget tar gzip openssl jq python3 iptables ufw - fi - - # Установка (диспетчер выберет systemd или docker) - install_telemt_direct - - # UFW: открыть порты инстансов + per-port rate-limit - telemt_ufw_ratelimit - - sleep 2 - - local server_ip; server_ip="$(get_public_ip)" - - echo "" - echo -e "${BOLD}${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}" - if [[ "$role" == "rf" ]]; then - echo -e "${BOLD}${GREEN}║ TELEGRAM ПРОКСИ РФ (telemt → SOCKS5) НАСТРОЕН ${NC}" - else - echo -e "${BOLD}${GREEN}║ TELEGRAM ПРОКСИ (telemt direct) НАСТРОЕН ${NC}" - fi - echo -e "${BOLD}${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}" - echo "" - if [[ "$role" == "rf" ]]; then - echo -e "${BOLD}Схема:${NC} Клиент → telemt (FakeTLS) → SOCKS5 ${TELEMT_SOCKS5_HOST}:${TELEMT_SOCKS5_PORT} → туннель → Telegram" - else - echo -e "${BOLD}Схема:${NC} Клиент → telemt (FakeTLS, порт=домен) → Telegram (прямой выход)" - fi - [[ -n "$TELEMT_CLIENT_MSS" ]] && \ - echo -e " client_mss: ${TELEMT_CLIENT_MSS} — фрагментация TCP клиента (доходит до клиента: nginx нет)" - echo "" - - local entry name port domain api link status - local i=0 - for entry in "${TELEMT_DIRECT_INSTANCES[@]}"; do - IFS=':' read -r name port domain api <<< "$entry" - # Фактический порт (после возможной подмены 443→853) - port="${TELEMT_DIRECT_PORTS[$i]:-$port}" - if telemt_instance_active "$name" 2>/dev/null; then status="${GREEN}active${NC}"; else status="${RED}не запущен${NC}"; fi - echo -e "${BOLD}${CYAN}── ${name}: порт ${port}, домен ${domain} [${status}${CYAN}]${NC}" - - link="$(telemt_link_from_api "$api" || true)" - if [[ -n "$link" ]]; then - link="$(printf '%s' "$link" | sed -E "s/server=[^&]*/server=${server_ip}/")" - echo -e " ${BOLD}${CYAN}${link}${NC}" - else - local hex_domain client_secret secret - secret="${TELEMT_DIRECT_SECRETS[$i]:-}" - hex_domain="$(printf '%s' "$domain" | xxd -p 2>/dev/null | tr -d '\n' || \ - printf '%s' "$domain" | od -A n -t x1 | tr -d ' \n')" - client_secret="ee${secret}${hex_domain}" - echo -e " ${YELLOW}(API не ответил, ссылка собрана вручную)${NC}" - echo -e " ${BOLD}${CYAN}tg://proxy?server=${server_ip}&port=${port}&secret=${client_secret}${NC}" - fi - echo "" - i=$((i + 1)) - done - - echo -e "${BOLD}Команды:${NC}" - if [[ "$TELEMT_BACKEND" == "docker" ]]; then - echo -e " Логи: docker logs -f telemt1 (telemt2 / telemt3)" - echo -e " Рестарт: cd /etc/telemt && docker compose restart" - echo -e " Обновить: cd /etc/telemt && docker compose pull && docker compose up -d" - else - echo -e " Логи: journalctl -u telemt1 -f (telemt2 / telemt3)" - echo -e " Рестарт: systemctl restart telemt1 telemt2 telemt3" - fi - echo -e " Статус: bash $0 status" - echo -e " Ссылки: for p in 9091 9092 9093; do curl -s http://127.0.0.1:\$p/v1/users | jq -r '.data[0].links.tls[0]'; done" - echo -e " Баны UFW: iptables -L ufw-before-input -n | grep recent" - echo "" - if [[ "$role" == "rf" ]]; then - echo -e "${YELLOW} ⚠️ SOCKS5 ${TELEMT_SOCKS5_HOST}:${TELEMT_SOCKS5_PORT} должен слушать ДО трафика (xray/sing-box).${NC}" - echo -e "${YELLOW} Проверка: ss -tlnp | grep ${TELEMT_SOCKS5_PORT} | логи telemt при ошибке выхода.${NC}" - fi - echo -e "${YELLOW} ⚠️ Порт 5223 (APNs) и 8530 (WSUS) должны быть открыты у провайдера/хостера.${NC}" - echo "" -} - -setup_telemt() { - section "Telegram MTProto Proxy (telemt, direct — без nginx)" - # Direct: SOCKS5 не задаём - TELEMT_SOCKS5_HOST=""; TELEMT_SOCKS5_PORT="" - TELEMT_SOCKS5_USER=""; TELEMT_SOCKS5_PASS="" - _telemt_install_and_report "direct" -} - -setup_telemt_rf() { - section "Telegram MTProto Proxy РФ (telemt → SOCKS5)" - - detect_os - - # SOCKS5 upstream (туннель на localhost: xray/sing-box/ssh -D) - local def_sh="${MT_SOCKS5_HOST:-127.0.0.1}" - local def_sp="${MT_SOCKS5_PORT:-1080}" - echo -n " SOCKS5 host [${def_sh}]: " - read -r TELEMT_SOCKS5_HOST; TELEMT_SOCKS5_HOST="${TELEMT_SOCKS5_HOST:-$def_sh}" - echo -n " SOCKS5 port [${def_sp}]: " - read -r TELEMT_SOCKS5_PORT; TELEMT_SOCKS5_PORT="${TELEMT_SOCKS5_PORT:-$def_sp}" - echo -n " SOCKS5 username (пусто = без авторизации): " - read -r TELEMT_SOCKS5_USER - if [[ -n "$TELEMT_SOCKS5_USER" ]]; then - echo -n " SOCKS5 password: " - read -r TELEMT_SOCKS5_PASS - else - TELEMT_SOCKS5_PASS="" - fi - - # Предупреждаем, если на SOCKS5-порту никто не слушает (не фатально) - if command -v ss &>/dev/null && [[ "$TELEMT_SOCKS5_HOST" == "127.0.0.1" || "$TELEMT_SOCKS5_HOST" == "localhost" ]]; then - if ! ss -tlnH "( sport = :${TELEMT_SOCKS5_PORT} )" 2>/dev/null | grep -q ":${TELEMT_SOCKS5_PORT}"; then - warn "На ${TELEMT_SOCKS5_HOST}:${TELEMT_SOCKS5_PORT} сейчас никто не слушает." - warn "Подними туннель (xray/sing-box) до старта трафика, иначе выход к Telegram не заработает." - fi - fi - - _telemt_install_and_report "rf" -} - -# ══════════════════════════════════════════════════════════════════════════════ -# МОДУЛЬ XRAY (direct, без nginx — Reality сам делает TLS-камуфляж) -# ══════════════════════════════════════════════════════════════════════════════ -# Внешний порт прослушивания xray выбирается из цепочки кандидатов: берём первый -# свободный. 443 предпочтителен (сливается с HTTPS-фоном); 853/8443 — запас на -# случай, если 443 занят (напр. telemt уже сидит на нём на той же машине). -# Это про доступность порта, а НЕ про обход DPI: Reality маскирует трафик -# независимо от порта, а 853/8443 на федеральных операторах режутся как 443. -# Можно переопределить: XRAY_PORT=<число> жёстко задаёт порт (без перебора). -XRAY_PORT_CANDIDATES=(443 853 8443) -XRAY_PORT="${XRAY_PORT:-}" # пусто = выбрать из цепочки; число = жёстко -XRAY_SNI="" - -# Выбрать порт для xray: если XRAY_PORT задан жёстко (env) — проверить его; -# иначе перебрать XRAY_PORT_CANDIDATES и взять первый свободный. -# Результат пишется в XRAY_PORT. die, если ничего не свободно. -xray_resolve_port() { - if [[ -n "$XRAY_PORT" ]]; then - if port_in_use "$XRAY_PORT"; then - warn "Порт ${XRAY_PORT} (задан жёстко) занят." - warn "Освободи порт: ss -tlnp '( sport = :${XRAY_PORT} )'" - die "Порт ${XRAY_PORT} занят." - fi - info "Xray порт: ${XRAY_PORT} (задан явно)" - return - fi - local p - for p in "${XRAY_PORT_CANDIDATES[@]}"; do - if ! port_in_use "$p"; then - XRAY_PORT="$p" - info "Xray порт: ${XRAY_PORT} (первый свободный из: ${XRAY_PORT_CANDIDATES[*]})" - return - fi - warn "Порт ${p} занят — пробую следующий..." - done - die "Все порты-кандидаты заняты (${XRAY_PORT_CANDIDATES[*]}). Освободи один или задай XRAY_PORT=<порт>." -} - -install_xray_bin() { - section "Установка Xray-core" - local ver tmpd - ver="$(curl -sI 'https://github.com/XTLS/Xray-core/releases/latest' \ - | grep -i '^location:' | grep -oP 'v[\d.]+')" - [[ -z "$ver" ]] && ver="v26.2.6" - info "Версия: ${ver}" - - tmpd="$(mktemp -d)" - wget -q -O "${tmpd}/xray.zip" \ - "https://github.com/XTLS/Xray-core/releases/download/${ver}/Xray-linux-64.zip" - unzip -q "${tmpd}/xray.zip" -d "${tmpd}/xray" - install -m 755 "${tmpd}/xray/xray" "$XRAY_BIN" - mkdir -p "$XRAY_DIR" /usr/local/share/xray /var/log/xray - - info "Скачиваю geo-базы..." - wget -q -O /usr/local/share/xray/geoip.dat \ - 'https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat' - wget -q -O /usr/local/share/xray/geosite.dat \ - 'https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat' - rm -rf "$tmpd" - - cat > /etc/systemd/system/xray.service << 'UNIT' -[Unit] -Description=Xray Service -After=network.target nss-lookup.target - -[Service] -User=root -ExecStart=/usr/local/bin/xray run -config /usr/local/etc/xray/config.json -Restart=on-failure -LimitNOFILE=1000000 - -[Install] -WantedBy=multi-user.target -UNIT - systemctl daemon-reload - ok "Xray установлен: $("$XRAY_BIN" version | head -1)" -} - - -# Возвращает «лояльный» TLS fingerprint для Reality (uTLS). -# По разбору «сибирской» схемы (июнь 2026) Сигнал 2 ловит МАССОВЫЕ фингерпринты, -# под которые чаще всего маскируются прокси: chrome, safari, ios — они исключены. -# randomized тоже исключён: это СИНТЕТИЧЕСКАЯ генерация (может дать комбинацию -# расширений/шифров, которой нет ни у одного реального браузера, — сама по себе -# аномалия). Оставляем редкие валидные пресеты + безопасный random -# (random берёт один из РЕАЛЬНЫХ пресетов браузера, почерк всегда валидный). -random_fp() { - local fps=("firefox" "edge" "android" "360" "qq" "random") - local idx=$(( RANDOM % ${#fps[@]} )) - echo "${fps[$idx]}" -} - - -setup_xray_euro() { - section "Xray Евро-нода (VLESS+Reality, direct — порт из ${XRAY_PORT_CANDIDATES[*]})" - - detect_os - show_ports - - # SNI-домен - local default_sni="www.amd.com" - echo -n " SNI-домен для Reality [${default_sni}]: " - read -r XRAY_SNI - XRAY_SNI="${XRAY_SNI:-$default_sni}" - - # Выбор внешнего порта из цепочки кандидатов (443→853→8443) - xray_resolve_port - info "Xray будет слушать 0.0.0.0:${XRAY_PORT} напрямую (без nginx)" - - pkg_update - pkg_install curl wget unzip uuid-runtime openssl python3 - - install_xray_bin - - # Ключи - info "Генерирую ключи Reality..." - local keys; keys="$("$XRAY_BIN" x25519)" - local priv_key pub_key - priv_key="$(echo "$keys" | grep -i 'private' | awk '{print $NF}')" - pub_key="$(echo "$keys" | grep -i 'public' | awk '{print $NF}')" - [[ -z "$priv_key" || -z "$pub_key" ]] && die "Ошибка генерации ключей: $keys" - - local uuid short_id fp - uuid="$(uuidgen)" - short_id="$(openssl rand -hex 8)" - fp="$(random_fp)" - info "TLS fingerprint: ${fp}" - - # Конфиг xray — слушает напрямую на 0.0.0.0:XRAY_PORT - cat > "${XRAY_DIR}/config.json" << EOF -{ - "log": { "loglevel": "warning" }, - "dns": { "servers": ["1.1.1.1", "8.8.8.8"] }, - "inbounds": [{ - "listen": "0.0.0.0", - "port": ${XRAY_PORT}, - "protocol": "vless", - "settings": { - "clients": [{ "id": "${uuid}" }], - "decryption": "none" - }, - "streamSettings": { - "network": "xhttp", - "security": "reality", - "realitySettings": { - "show": false, - "dest": "${XRAY_SNI}:443", - "xver": 0, - "serverNames": ["${XRAY_SNI}"], - "privateKey": "${priv_key}", - "shortIds": ["${short_id}"] - }, - "xhttpSettings": { "path": "/", "mode": "auto" } - } - }], - "outbounds": [{ - "protocol": "freedom", - "tag": "direct", - "settings": { "domainStrategy": "UseIPv4v6" } - }] -} -EOF - - # PQ шифрование (если поддерживается) - local pq_enc="none" - pq_enc="$(XRAY_UUID="$uuid" XRAY_DIR="$XRAY_DIR" python3 - << 'PYEOF' -import subprocess, json, os -try: - out = subprocess.check_output(["/usr/local/bin/xray", "vlessenc"]).decode() - enc = [l for l in out.split("\n") if '"encryption"' in l] - pq = enc[1].split('"encryption": "')[1].rstrip('"') if len(enc) > 1 else "none" - if pq != "none": - dec = [l for l in out.split("\n") if '"decryption"' in l] - pq_dec = dec[1].split('"decryption": "')[1].rstrip('"') if len(dec) > 1 else "none" - with open(os.environ["XRAY_DIR"] + "/config.json") as f: - cfg = json.load(f) - cfg["inbounds"][0]["settings"]["decryption"] = pq_dec - with open(os.environ["XRAY_DIR"] + "/config.json", "w") as f: - json.dump(cfg, f, indent=2) - print(pq) -except: - print("none") -PYEOF -)" || pq_enc="none" - - # Файрвол: открываем внешний порт xray - firewall_open "$XRAY_PORT" "xray-direct" - - # systemd - systemctl enable xray > /dev/null 2>&1 - systemctl restart xray - sleep 2 - svc_active xray && ok "Xray запущен" || die "Xray не запустился — journalctl -xe -u xray" - - # Сохраняем для РФ-ноды - local server_ip; server_ip="$(get_public_ip)" - cat > "$XRAY_STATE_FILE" << EOF -EURO_IP=${server_ip} -EURO_PORT=${XRAY_PORT} -EURO_UUID=${uuid} -EURO_PUBLIC_KEY=${pub_key} -EURO_SHORT_ID=${short_id} -EURO_SNI=${XRAY_SNI} -EURO_ENCRYPTION=${pq_enc} -EOF - ok "Данные евро-ноды → ${XRAY_STATE_FILE}" - - # Итог - echo "" - echo -e "${BOLD}${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}" - echo -e "${BOLD}${GREEN}║ ЕВРО-НОДА НАСТРОЕНА — СОХРАНИ ДАННЫЕ ${NC}" - echo -e "${BOLD}${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}" - echo "" - echo -e "${BOLD}Схема:${NC}" - echo -e " Клиент → xray:${XRAY_PORT} (Reality, SNI: ${XRAY_SNI}) → интернет (без nginx)" - echo "" - echo -e "${BOLD}Данные для РФ-ноды:${NC}" - echo -e " EURO_IP=${server_ip} EURO_PORT=${XRAY_PORT}" - echo -e " EURO_UUID=${uuid}" - echo -e " EURO_PUBLIC_KEY=${pub_key}" - echo -e " EURO_SHORT_ID=${short_id} EURO_SNI=${XRAY_SNI}" - echo -e " (сохранено в ${XRAY_STATE_FILE})" - echo "" - echo -e "${BOLD}VLESS URI (прямое подключение к :${XRAY_PORT}):${NC}" - echo -e "${GREEN}vless://${uuid}@${server_ip}:${XRAY_PORT}?encryption=${pq_enc}&security=reality&sni=${XRAY_SNI}&fp=${fp}&pbk=${pub_key}&sid=${short_id}&type=xhttp&path=%2F&mode=auto#EURO-Reality${NC}" - echo "" - echo -e "${BOLD}Проверка:${NC}" - echo -e " curl -v https://${server_ip}:${XRAY_PORT} --resolve ${XRAY_SNI}:${XRAY_PORT}:${server_ip}" - echo -e " journalctl -fu xray" - echo "" - echo -e "${YELLOW} ⚠️ Порт ${XRAY_PORT} должен быть открыт у хостера. Следующий шаг: bash setup.sh xray-rf${NC}" - echo "" -} - -setup_xray_rf() { - section "Xray РФ-нода (мост → евро)" - - detect_os - show_ports - - # Загружаем данные евро-ноды. EURO_PORT — порт ВЫХОДНОЙ евро-ноды (куда - # форвардим), не путать с портом прослушивания РФ-ноды (XRAY_PORT). - # Точное значение приходит из state-файла или ручного ввода; дефолт 443. - local EURO_IP="" EURO_PORT="443" EURO_UUID="" EURO_PUBLIC_KEY="" - local EURO_SHORT_ID="" EURO_SNI="www.amd.com" EURO_ENCRYPTION="none" - local RF_SNI="vkvideo.ru" - - if [[ -f "$XRAY_STATE_FILE" ]]; then - echo -e "${GREEN} Найден файл ${XRAY_STATE_FILE}${NC}" - echo -n " Загрузить данные евро-ноды из файла? [Y/n]: " - read -r ans - if [[ -z "$ans" || "$ans" =~ ^[Yy]$ ]]; then - # shellcheck disable=SC1090 - source "$XRAY_STATE_FILE" - ok "Данные загружены: ${EURO_IP}:${EURO_PORT}" - fi - fi - - if [[ -z "$EURO_IP" ]]; then - echo -e "${BOLD} Введите данные Евро-ноды вручную:${NC}" - read -rp " EURO_IP: " EURO_IP - read -rp " EURO_PORT [443]: " EURO_PORT; EURO_PORT="${EURO_PORT:-443}" - read -rp " EURO_UUID: " EURO_UUID - read -rp " EURO_PUBLIC_KEY: " EURO_PUBLIC_KEY - read -rp " EURO_SHORT_ID: " EURO_SHORT_ID - read -rp " EURO_SNI [www.amd.com]: " EURO_SNI; EURO_SNI="${EURO_SNI:-www.amd.com}" - read -rp " EURO_ENCRYPTION [none]: " EURO_ENCRYPTION; EURO_ENCRYPTION="${EURO_ENCRYPTION:-none}" - fi - - echo -n " SNI-домен для РФ-ноды [${RF_SNI}]: " - read -r inp_sni; XRAY_SNI="${inp_sni:-$RF_SNI}" - - # Выбор внешнего порта прослушивания из цепочки (443→853→8443). - # Это порт, на котором РФ-нода принимает клиентов — НЕ EURO_PORT (порт евро). - xray_resolve_port - info "Xray РФ будет слушать 0.0.0.0:${XRAY_PORT} напрямую (без nginx)" - - pkg_update - pkg_install curl wget unzip uuid-runtime openssl python3 - - install_xray_bin - - # Ключи для клиентского inbound - info "Генерирую ключи Reality для клиентов..." - local keys; keys="$("$XRAY_BIN" x25519)" - local priv_key pub_key - priv_key="$(echo "$keys" | grep -i 'private' | awk '{print $NF}')" - pub_key="$(echo "$keys" | grep -i 'public' | awk '{print $NF}')" - local uuid short_id fp - uuid="$(uuidgen)" - short_id="$(openssl rand -hex 8)" - fp="$(random_fp)" - info "TLS fingerprint: ${fp}" - - # Конфиг РФ-ноды через Python (PQ + chain) - export UUID="$uuid" PRIV_KEY="$priv_key" SHORT_ID="$short_id" FP="$fp" - export XRAY_SNI XRAY_PORT - export EURO_IP EURO_PORT EURO_UUID EURO_PUBLIC_KEY EURO_SHORT_ID EURO_SNI EURO_ENCRYPTION - export XRAY_DIR - - python3 - << 'PYEOF' -import json, os, subprocess - -e = os.environ - -# PQ ключи -try: - out = subprocess.check_output(["/usr/local/bin/xray", "vlessenc"]).decode() - dec_lines = [l for l in out.split("\n") if '"decryption"' in l] - enc_lines = [l for l in out.split("\n") if '"encryption"' in l] - pq_dec = dec_lines[1].split('"decryption": "')[1].rstrip('"') if len(dec_lines) > 1 else "none" - pq_enc = enc_lines[1].split('"encryption": "')[1].rstrip('"') if len(enc_lines) > 1 else "none" -except: - pq_dec = pq_enc = "none" - -cfg = { - "log": {"loglevel": "warning"}, - "dns": {"servers": [ - {"address": "223.5.5.5", "domains": ["geosite:category-ru", "geosite:yandex"]}, - "1.1.1.1", "8.8.8.8" - ]}, - "inbounds": [{ - "tag": "inbound-clients", - "listen": "0.0.0.0", - "port": int(e["XRAY_PORT"]), - "protocol": "vless", - "settings": { - "clients": [{"id": e["UUID"]}], - "decryption": pq_dec - }, - "streamSettings": { - "network": "xhttp", - "security": "reality", - "realitySettings": { - "show": False, - "dest": e["XRAY_SNI"] + ":443", - "xver": 0, - "serverNames": [e["XRAY_SNI"]], - "privateKey": e["PRIV_KEY"], - "shortIds": [e["SHORT_ID"]] - }, - "xhttpSettings": { - "path": "/", "mode": "packet-up", - "extra": {"xPaddingBytes": "100-1000"} - } - }, - "sniffing": {"enabled": True, "destOverride": ["http","tls","quic","fakedns"]} - }], - "outbounds": [ - { - "tag": "chain-to-euro", - "protocol": "vless", - "settings": {"vnext": [{ - "address": e["EURO_IP"], - "port": int(e["EURO_PORT"]), - "users": [{"id": e["EURO_UUID"], "encryption": e["EURO_ENCRYPTION"]}] - }]}, - "streamSettings": { - "network": "xhttp", - "security": "reality", - "realitySettings": { - "fingerprint": e["FP"], - "serverName": e["EURO_SNI"], - "publicKey": e["EURO_PUBLIC_KEY"], - "shortId": e["EURO_SHORT_ID"] - }, - "xhttpSettings": {"path": "/", "mode": "auto"} - }, - "mux": { - "enabled": True, - "concurrency": 8, - "xudpConcurrency": 16, - "xudpProxyUDP443": "reject" - } - }, - {"protocol": "freedom", "tag": "direct", "settings": {"domainStrategy": "UseIPv4v6"}}, - {"protocol": "blackhole", "tag": "block"} - ], - "routing": { - "domainStrategy": "IPIfNonMatch", - "rules": [ - {"type":"field","outboundTag":"block","ip":["geoip:private"],"domain":["geosite:category-ads-all"]}, - {"type":"field","outboundTag":"direct","domain":["geosite:category-ru","geosite:yandex","regexp:\\.ru$","full:cp.cloudflare.com"]}, - {"type":"field","outboundTag":"direct","ip":["geoip:ru"]}, - {"type":"field","inboundTag":["inbound-clients"],"outboundTag":"chain-to-euro"} - ] - } -} - -with open(e["XRAY_DIR"] + "/config.json", "w") as f: - json.dump(cfg, f, indent=2, ensure_ascii=False) - -with open("/tmp/pq_enc_rf.txt", "w") as f: - f.write(pq_enc) - -print(f" Config OK | PQ: {pq_dec[:30]}...") -PYEOF - - local pq_enc_rf; pq_enc_rf="$(cat /tmp/pq_enc_rf.txt 2>/dev/null || echo none)" - - # Файрвол: открываем внешний порт xray - firewall_open "$XRAY_PORT" "xray-direct" - - # systemd - systemctl enable xray > /dev/null 2>&1 - systemctl restart xray - sleep 2 - svc_active xray && ok "Xray запущен" || die "Xray не запустился — journalctl -xe -u xray" - - # Итог - local server_ip; server_ip="$(get_public_ip)" - echo "" - echo -e "${BOLD}${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}" - echo -e "${BOLD}${GREEN}║ РФ-НОДА НАСТРОЕНА УСПЕШНО ${NC}" - echo -e "${BOLD}${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}" - echo "" - echo -e "${BOLD}Схема трафика:${NC}" - echo -e " Клиент → xray:${XRAY_PORT} (Reality, SNI: ${XRAY_SNI}) → Евро (${EURO_IP}):${EURO_PORT} → Интернет (без nginx)" - echo "" - echo -e "${BOLD}VLESS URI для клиента (прямое подключение к :${XRAY_PORT}):${NC}" - echo -e "${GREEN}vless://${uuid}@${server_ip}:${XRAY_PORT}?encryption=${pq_enc_rf}&security=reality&sni=${XRAY_SNI}&fp=${fp}&pbk=${pub_key}&sid=${short_id}&type=xhttp&path=%2F&mode=packet-up#RF-Bridge${NC}" - echo "" - echo -e "${BOLD}Проверка:${NC}" - echo -e " curl -v https://${server_ip}:${XRAY_PORT} --resolve ${XRAY_SNI}:${XRAY_PORT}:${server_ip}" - echo -e " journalctl -fu xray" - echo "" - echo -e "${BOLD}Клиентские приложения:${NC}" - echo -e " Android: v2rayNG https://github.com/2dust/v2rayNG" - echo -e " Windows: v2rayN https://github.com/2dust/v2rayN" - echo -e " iOS: Streisand https://apps.apple.com/app/id6450534064" - echo "" - echo -e "${BOLD}Роутинг:${NC}" - echo -e " ✅ .ru, geoip:ru, yandex → DIRECT 🚫 ads → BLOCK 🌍 остальное → Евро" - echo "" - echo -e "${YELLOW} ⚠️ Порт ${XRAY_PORT} должен быть открыт у хостера РФ-ноды.${NC}" - echo "" -} - -# ══════════════════════════════════════════════════════════════════════════════ -# СТАТУС -# ══════════════════════════════════════════════════════════════════════════════ -show_status() { - echo "" - echo -e "${BOLD}${CYAN}━━━ Сервисы ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - # telemt теперь мульти-инстанс (telemt1/2/3) — проверяем каждый отдельно - for svc in nginx xray fail2ban; do - if command -v "$svc" &>/dev/null || systemctl list-units --type=service 2>/dev/null | grep -q "$svc"; then - if svc_active "$svc" 2>/dev/null; then - echo -e " ${GREEN}● ${svc}: активен${NC}" - else - echo -e " ${RED}● ${svc}: не запущен${NC}" - fi - fi - done - local tname tentry - for tentry in "${TELEMT_DIRECT_INSTANCES[@]}"; do - IFS=':' read -r tname _ _ _ <<< "$tentry" - if [[ -f "/etc/telemt/${tname}.toml" ]]; then - if telemt_instance_active "$tname" 2>/dev/null; then - echo -e " ${GREEN}● ${tname}: активен${NC}" - else - echo -e " ${RED}● ${tname}: не запущен${NC}" - fi - fi - done - - echo "" - echo -e "${BOLD}${CYAN}━━━ nginx stream конфиги ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - for f in "$NGINX_STREAM_DIR"/*.conf; do - [[ -f "$f" ]] || continue - echo -e " ${CYAN}$(basename "$f")${NC}" - grep -E 'listen|"[^"]+"|default' "$f" 2>/dev/null | head -8 | sed 's/^/ /' - done - - echo "" - local tbackend; tbackend="$(telemt_detect_backend)" - echo -e "${BOLD}${CYAN}━━━ Telemt${tbackend:+ (${tbackend})} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - local found_telemt=0 cfg - for cfg in /etc/telemt/telemt*.toml; do - [[ -f "$cfg" ]] || continue - found_telemt=1 - echo -e " ${CYAN}$(basename "$cfg")${NC}" - awk -F'"' '/tls_domain/{print " Домен: "$2} /client_mss/{print " MSS: "$2}' "$cfg" 2>/dev/null - grep -E '^port' "$cfg" 2>/dev/null | sed 's/^/ /' - # Режим выхода: socks5-upstream или direct - if grep -q 'type = "socks5"' "$cfg" 2>/dev/null; then - local up_addr; up_addr="$(awk -F'"' '/address =/{print $2; exit}' "$cfg" 2>/dev/null)" - echo -e " Выход: ${YELLOW}SOCKS5 ${up_addr}${NC}" - else - echo -e " Выход: direct" - fi - done - [[ "$found_telemt" -eq 0 ]] && echo -e " ${YELLOW}telemt не установлен${NC}" - - echo "" - echo -e "${BOLD}${CYAN}━━━ Xray ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - if [[ -f "${XRAY_DIR}/config.json" ]]; then - python3 - << 'PYEOF' -import json -try: - with open("/usr/local/etc/xray/config.json") as f: - c = json.load(f) - ib = c["inbounds"][0] - print(f" Listen: {ib.get('listen','0.0.0.0')}:{ib['port']}") - print(f" Протокол: {ib['protocol']}") - ob_tags = [o["tag"] for o in c.get("outbounds",[])] - print(f" Outbounds: {ob_tags}") -except Exception as e: - print(f" Ошибка чтения конфига: {e}") -PYEOF - else - echo -e " ${YELLOW}xray не установлен${NC}" - fi - - echo "" - show_ports -} - -# ══════════════════════════════════════════════════════════════════════════════ -# УДАЛЕНИЕ -# ══════════════════════════════════════════════════════════════════════════════ -do_purge() { - section "Полное удаление" - echo -n " Удалить telemt, xray, nginx stream конфиги и fail2ban? [y/N]: " - read -r ans - [[ "$ans" =~ ^[Yy]$ ]] || { echo "Отменено."; exit 0; } - - # ── ШАГ 1. Сначала ОСТАНАВЛИВАЕМ все сервисы ───────────────────────────── - # Это делается ДО удаления любых файлов: процессы не должны держать порты, - # сокеты и конфиги, которые мы собираемся стереть. nginx тоже глушим — - # иначе он продолжит слушать :443 с уже удалёнными stream-конфигами. - section "Остановка сервисов" - for svc in fail2ban xray nginx; do - if svc_active "$svc" 2>/dev/null; then - info "Останавливаю ${svc}..." - svc_stop "$svc" 2>/dev/null || true - fi - done - # telemt — мульти-инстанс (systemd или docker) - local tname tentry - if [[ -f "$TELEMT_COMPOSE" ]] && command -v docker &>/dev/null; then - info "Останавливаю telemt-контейнеры..." - (cd /etc/telemt && docker compose down 2>/dev/null) || true - for tentry in "${TELEMT_DIRECT_INSTANCES[@]}"; do - IFS=':' read -r tname _ _ _ <<< "$tentry" - docker rm -f "$tname" 2>/dev/null || true - done - fi - for tentry in "${TELEMT_DIRECT_INSTANCES[@]}"; do - IFS=':' read -r tname _ _ _ <<< "$tentry" - svc_active "$tname" 2>/dev/null && { info "Останавливаю ${tname}..."; svc_stop "$tname" 2>/dev/null || true; } - done - # Легаси: одиночный telemt.service из ранних версий скрипта (имя без цифры). - # Его нет в TELEMT_DIRECT_INSTANCES, поэтому гасим отдельно — иначе он - # продолжит держать 443 после удаления файла юнита. - if svc_active telemt 2>/dev/null; then - info "Останавливаю легаси telemt.service..." - svc_stop telemt 2>/dev/null || true - fi - # Контрольно добиваем процессы telemt/xray, если systemd-юнит уже снят - pkill -x telemt 2>/dev/null || true - pkill -x xray 2>/dev/null || true - sleep 1 - ok "Сервисы остановлены" - - # ── ШАГ 2. Отключаем автозапуск ────────────────────────────────────────── - for svc in xray fail2ban telemt; do - svc_disable "$svc" 2>/dev/null || true - done - for tentry in "${TELEMT_DIRECT_INSTANCES[@]}"; do - IFS=':' read -r tname _ _ _ <<< "$tentry" - svc_disable "$tname" 2>/dev/null || true - done - - # ── ШАГ 3. Удаляем файлы ───────────────────────────────────────────────── - # nginx stream конфиги (сам nginx оставляем — он мог быть до нас) - rm -f "$TELEMT_STREAM_CONF" "$XRAY_STREAM_CONF" - sed -i '/# proxy-stream-block/,/# \/proxy-stream-block/d' "$NGINX_CONF_MAIN" 2>/dev/null || true - sed -i '/# telemt-stream-block/,/# \/telemt-stream-block/d' "$NGINX_CONF_MAIN" 2>/dev/null || true - sed -i '/# xray-stream-block/,/# \/xray-stream-block/d' "$NGINX_CONF_MAIN" 2>/dev/null || true - # nginx запускаем обратно только если его конфиг валиден и сам бинарь остаётся - if command -v nginx &>/dev/null && nginx -t &>/dev/null; then - svc_start nginx || true - fi - - # fail2ban - rm -f "$F2B_FILTER" "$F2B_JAIL" - - # telemt — docker: удаляем образ (compose-файл уйдёт с /etc/telemt ниже) - if command -v docker &>/dev/null; then - docker rmi "$TELEMT_IMAGE" 2>/dev/null || true - fi - - # telemt — мульти-инстанс: юниты, конфиги, бинарь, юзер - for tentry in "${TELEMT_DIRECT_INSTANCES[@]}"; do - IFS=':' read -r tname _ _ _ <<< "$tentry" - rm -f "/etc/systemd/system/${tname}.service" "/etc/init.d/${tname}" - done - # на всякий случай — старый одиночный юнит из прежней версии скрипта - rm -f /etc/systemd/system/telemt.service /etc/init.d/telemt - rm -f /bin/telemt /usr/bin/telemt - rm -rf /etc/telemt /opt/telemt - pkill -u telemt 2>/dev/null || true - sleep 1; pkill -9 -u telemt 2>/dev/null || true - userdel telemt 2>/dev/null || deluser telemt 2>/dev/null || true - groupdel telemt 2>/dev/null || delgroup telemt 2>/dev/null || true - - # UFW rate-limit правила (вставленные telemt_ufw_ratelimit) - if [[ -f /etc/ufw/before.rules ]] && grep -q "MTProto rate-limit" /etc/ufw/before.rules 2>/dev/null; then - cp /etc/ufw/before.rules "/etc/ufw/before.rules.bak.$(date +%s)" 2>/dev/null || true - sed -i '/# === MTProto rate-limit/,/# === конец MTProto rate-limit ===/d' /etc/ufw/before.rules 2>/dev/null || true - command -v ufw &>/dev/null && ufw reload >/dev/null 2>&1 || true - info "UFW rate-limit правила удалены" - fi - - # xray - rm -f /etc/systemd/system/xray.service "$XRAY_BIN" - rm -rf "$XRAY_DIR" /var/log/xray - - systemctl daemon-reload 2>/dev/null || true - - echo -e "\n${GREEN}${BOLD}Удаление завершено.${NC}\n" -} - -# ══════════════════════════════════════════════════════════════════════════════ -# МЕНЮ -# ══════════════════════════════════════════════════════════════════════════════ -show_menu() { - banner - echo -e "${BOLD} Выберите действие:${NC}\n" - echo -e " ${GREEN}1)${NC} Установить ${BOLD}Telegram прокси (direct)${NC} (telemt: 3 инстанса + UFW rate-limit, выход прямой)" - echo -e " ${GREEN}2)${NC} Установить ${BOLD}Xray Евро-ноду${NC} (VLESS+Reality direct, порт 443→853→8443, выходная)" - echo -e " ${GREEN}3)${NC} Установить ${BOLD}Xray РФ-ноду${NC} (VLESS+Reality direct, порт 443→853→8443, мост → евро)" - echo -e " ${GREEN}4)${NC} Статус всех сервисов" - echo -e " ${GREEN}5)${NC} Удалить всё" - echo -e " ${GREEN}6)${NC} Установить ${BOLD}Telegram прокси РФ (через SOCKS5)${NC} (telemt → туннель на localhost)" - echo -e " ${GREEN}0)${NC} Выход\n" - echo -n " Ваш выбор [0-6]: " - read -r choice - case "$choice" in - 1) setup_telemt ;; - 2) setup_xray_euro ;; - 3) setup_xray_rf ;; - 4) show_status ;; - 5) do_purge ;; - 6) setup_telemt_rf ;; - 0) exit 0 ;; - *) echo -e "${RED}Неверный выбор${NC}"; show_menu ;; - esac -} - -# ══════════════════════════════════════════════════════════════════════════════ -# ТОЧКА ВХОДА -# ══════════════════════════════════════════════════════════════════════════════ -case "${1:-}" in - telemt) setup_telemt ;; - telemt-rf) setup_telemt_rf ;; - update) shift; update_telemt "${1:-}" ;; - xray-euro) setup_xray_euro ;; - xray-rf) setup_xray_rf ;; - status) show_status ;; - purge) do_purge ;; - *) show_menu ;; -esac diff --git a/setup.sh b/setup.sh index e95b061..e2bafe1 100755 --- a/setup.sh +++ b/setup.sh @@ -25,7 +25,6 @@ if command -v whiptail >/dev/null 2>&1; then "6" "Проверить сеть (маршрутизация, geo)" \ "7" "Обновить (git pull + перегенерация скриптов)" \ "8" "Android CLI (установить Google Android CLI)" \ - "9" "VLESS: настроить встроенный прокси (включить / отключить)" \ 3>&1 1>&2 2>&3) || exit 0 echo "" else @@ -64,10 +63,7 @@ else echo -e " ${BLD}8) Android CLI${CLR}" echo -e " ${GRY}Установить Google Android CLI для Android-разработки.${CLR}" echo "" - echo -e " ${BLD}9) VLESS${CLR}" - echo -e " ${GRY}Включить или отключить встроенный VLESS-прокси для AI API запросов.${CLR}" - echo "" - echo -n "Выбери [1-9] или Enter для выхода: " + echo -n "Выбери [1-8] или Enter для выхода: " read -r choice echo "" fi @@ -76,7 +72,7 @@ fi case "$choice" in 1) - AI_SETUP_VLESS=skip bash scripts/ai-setup.sh + bash scripts/ai-setup.sh ;; 2) echo -e "${GRY}Нужно указать параметры твоей локальной сети:${CLR}" @@ -228,20 +224,10 @@ case "$choice" in git -C "$REPO_DIR" pull --ff-only echo "" echo -e "${BLD}Перегенерация скриптов...${CLR}" - AI_SETUP_VLESS=skip bash "$REPO_DIR/scripts/ai-setup.sh" + bash "$REPO_DIR/scripts/ai-setup.sh" ;; 8) - AI_SETUP_VLESS=skip bash scripts/ai-setup.sh - ;; - 9) - echo -n "Включить VLESS-прокси? [Y/n] " - read -r _vless_choice - _vless_choice="${_vless_choice:-Y}" - if [[ "$_vless_choice" =~ ^[Yy]$ ]]; then - AI_SETUP_VLESS=1 bash scripts/ai-setup.sh - else - AI_SETUP_VLESS=0 bash scripts/ai-setup.sh - fi + bash scripts/ai-setup.sh ;; "") exit 0 diff --git a/tests/test_fixes.sh b/tests/test_fixes.sh index 5677613..9802bd6 100755 --- a/tests/test_fixes.sh +++ b/tests/test_fixes.sh @@ -188,14 +188,6 @@ test_android_symlink_to_bin_dir() { fi } -test_no_proxychains_for_android() { - if grep -q 'proxychains4.*android' "$SCRIPT"; then - fail "android-cli: should not be wrapped in proxychains4 (breaks child processes)" - else - ok "android-cli: not wrapped in proxychains4" - fi -} - test_android_skill_exists() { skill_file="$(cd "$(dirname "$0")/.." && pwd)/home-configs/claude/skills/android-cli/SKILL.md" if [ -f "$skill_file" ]; then @@ -265,7 +257,6 @@ test_fix7_trap_tmp test_android_cli_install_url test_android_init_called test_android_symlink_to_bin_dir -test_no_proxychains_for_android test_android_skill_exists test_opencode_claude_launcher test_opencode_routatic_proxy