From b65b48d82b8701a610f22e3164ef4989a8ffe833 Mon Sep 17 00:00:00 2001 From: Dokril Date: Sat, 27 Dec 2025 20:01:38 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B2=D0=B5=D0=B1-=D0=BF=D0=B0=D0=BD?= =?UTF-8?q?=D0=B5=D0=BB=D1=8C=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20VPN-=D0=BF=D1=80=D0=BE=D0=BA=D1=81=D0=B8?= =?UTF-8?q?=20=D0=B8=20Docker-=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=D1=83?= =?UTF-8?q?=D1=80=D0=B0=D1=86=D0=B8=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 54 ++--- config/client.template.json | 52 ---- docker/Dockerfile.singbox | 6 +- docker/entrypoint.sh | 46 +--- scripts/gen-client-from-url.sh | 101 -------- scripts/menu.sh | 101 -------- web/index.html | 429 +++++++++++++++++++++++++++++++-- web/server.py | 370 ++++++++++++++++++++++++++-- 8 files changed, 785 insertions(+), 374 deletions(-) delete mode 100644 config/client.template.json delete mode 100755 scripts/gen-client-from-url.sh delete mode 100644 scripts/menu.sh diff --git a/README.md b/README.md index d588417..b4d1285 100644 --- a/README.md +++ b/README.md @@ -40,16 +40,13 @@ ## 📦 Что внутри? -| Файл | Описание простыми словами | -| ------------------------ | ------------------------------------------------------------------------ | -| `web_server.py` | Веб-интерфейс для управления через браузер | -| `web/index.html` | Страница с красивым интерфейсом | -| `client.template.json` | Шаблон настроек — как "бланк анкеты", который заполняется вашими данными | -| `gen-client-from-url.sh` | Скрипт, который берёт вашу VPN-ссылку и заполняет "анкету" | -| `menu.sh` | Интерактивное меню для выбора сервера из списка (консольная версия) | -| `entrypoint.sh` | Главный скрипт запуска с функцией авто-обновления | -| `Dockerfile.singbox` | Инструкция для создания изолированного VPN-приложения (контейнера) | -| `docker-compose.yml` | Файл для удобного запуска одной командой | +| Файл | Описание простыми словами | +| ----------------------- | ------------------------------------------------------------------ | +| `web/server.py` | Веб-интерфейс для управления через браузер | +| `web/index.html` | Страница с красивым интерфейсом | +| `docker/entrypoint.sh` | Главный скрипт запуска контейнера | +| `docker/Dockerfile` | Инструкция для создания изолированного VPN-приложения (контейнера) | +| `docker-compose.yml` | Файл для удобного запуска одной командой | --- @@ -57,7 +54,9 @@ ### Что вам понадобится -1. **VLESS-ссылка** — получите её от вашего VPN-провайдера. Она начинается с `vless://...` +1. **URL подписки** или **VLESS-ссылка** — получите её от вашего VPN-провайдера + - Подписка: формат sing-box + - VLESS: начинается с `vless://...` 2. **Docker** — программа для запуска изолированных приложений - [Скачать Docker Desktop](https://www.docker.com/products/docker-desktop/) (бесплатно) @@ -88,9 +87,12 @@ docker compose up -d ### После запуска 1. **Откройте веб-интерфейс**: http://localhost:3456 -2. **Вставьте вашу VLESS-ссылку** (vless://) -3. **Нажмите "Применить"** -4. Готово! Прокси работает на порту **8082** +2. **Выберите режим**: + - **📡 Подписка**: вставьте URL подписки, нажмите «Загрузить серверы», выберите сервер и нажмите «Применить» + - **🔑 VLESS Ключ**: вставьте VLESS-ссылку и нажмите «Применить» +3. Готово! Прокси работает на порту **8082** + +> 💡 **Подписка сохраняется** между перезагрузками контейнера — URL и выбранный сервер хранятся в папке `data/` --- @@ -112,7 +114,7 @@ docker compose build --no-cache docker compose up -d ``` -> 💡 **Примечание:** после пересборки нужно снова применить VPN-ссылку через веб-интерфейс http://localhost:3456 +> 💡 **Примечание:** подписка сохраняется и будет автоматически загружена при открытии веб-интерфейса http://localhost:3456 --- @@ -256,37 +258,17 @@ curl -x http://127.0.0.1:8082 https://ipinfo.io/json ## 🔧 Для продвинутых пользователей -### Запуск с VPN-ссылкой при старте - -Если хотите сразу применить ссылку при запуске (без веб-интерфейса): - -```bash -VLESS_URL="vless://..." docker compose up -d -``` - ### Запуск без Docker Если вы не хотите использовать Docker: 1. Установите [sing-box](https://sing-box.sagernet.org/) -2. Сгенерируйте конфигурацию: - ```bash - ./gen-client-from-url.sh "vless://..." client.json - ``` +2. Скопируйте конфигурацию из веб-интерфейса подписки и сохраните в `client.json` 3. Запустите: ```bash sing-box run -c client.json ``` -### Автоматическое обновление конфигурации - -Контейнер автоматически обновляет конфигурацию каждые 60 минут. Чтобы изменить интервал, добавьте в `docker-compose.yml`: - -```yaml -environment: - UPDATE_INTERVAL: 120 # обновлять каждые 120 минут -``` - --- ## 📚 Словарь терминов diff --git a/config/client.template.json b/config/client.template.json deleted file mode 100644 index 2445dbd..0000000 --- a/config/client.template.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "log": { - "level": "info", - "timestamp": true - }, - "inbounds": [ - { - "type": "mixed", - "tag": "mixed-in", - "listen": "0.0.0.0", - "listen_port": 8082, - "sniff": true, - "sniff_override_destination": true - } - ], - "outbounds": [ - { - "type": "vless", - "tag": "__TAG__", - "server": "__SERVER__", - "server_port": 0, - "uuid": "__UUID__", - "flow": "", - "tls": { - "enabled": true, - "server_name": "__SNI__", - "utls": { - "enabled": true, - "fingerprint": "__FINGERPRINT__" - }, - "reality": { - "enabled": true, - "public_key": "__PUBLIC_KEY__", - "short_id": "__SHORT_ID__" - } - }, - "packet_encoding": "xudp" - }, - { - "type": "direct", - "tag": "direct" - }, - { - "type": "block", - "tag": "block" - } - ], - "route": { - "final": "__TAG__", - "auto_detect_interface": true - } -} \ No newline at end of file diff --git a/docker/Dockerfile.singbox b/docker/Dockerfile.singbox index 35b49e3..1c80fc0 100644 --- a/docker/Dockerfile.singbox +++ b/docker/Dockerfile.singbox @@ -1,6 +1,5 @@ FROM alpine:3.20 ARG SINGBOX_VER=1.8.10 -ARG VLESS_URL # Устанавливаем зависимости, включая dos2unix для исправления скриптов RUN apk add --no-cache curl ca-certificates tar jq bash coreutils netcat-openbsd python3 dos2unix && update-ca-certificates @@ -16,14 +15,11 @@ RUN ARCH=$(uname -m) && \ && chmod +x /usr/local/bin/sing-box \ && adduser -D -u 1000 suser -COPY --chown=suser:suser config/client.template.json /app/ -COPY --chown=suser:suser scripts/gen-client-from-url.sh scripts/menu.sh /app/ COPY --chown=suser:suser docker/entrypoint.sh /app/ COPY --chown=suser:suser web/ /app/web/ # Исправляем окончания строк (важно для Windows пользователей) и даем права на запуск -RUN dos2unix /app/*.sh && chmod +x /app/gen-client-from-url.sh /app/entrypoint.sh /app/menu.sh +RUN dos2unix /app/*.sh && chmod +x /app/entrypoint.sh -ENV VLESS_URL=$VLESS_URL EXPOSE 8082 9090 3456 ENTRYPOINT ["/app/entrypoint.sh"] \ No newline at end of file diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 99c6742..3238d64 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,26 +1,12 @@ #!/usr/bin/env bash set -e -# Default update interval: 60 minutes -UPDATE_INTERVAL=${UPDATE_INTERVAL:-60} CONFIG_FILE="/app/data/client.json" SINGBOX_PID="" # Ensure data directory exists mkdir -p /app/data -# Function to generate config -generate_config() { - echo "$(date): Generating config..." - if ./gen-client-from-url.sh "$VLESS_URL" "$CONFIG_FILE"; then - echo "$(date): Config generated successfully." - return 0 - else - echo "$(date): Error generating config." - return 1 - fi -} - start_singbox() { if [[ -f "$CONFIG_FILE" ]]; then echo "$(date): Starting sing-box..." @@ -47,11 +33,6 @@ restart_singbox() { start_singbox } -# Initial generation (if URL provided) -if [[ -n "$VLESS_URL" ]]; then - generate_config -fi - start_singbox # Start Web UI Server @@ -61,22 +42,15 @@ WEBUI_PID=$! # HTTP Control Server (Simple Netcat loop) # Listens on 9090. -# Endpoints: -# /update -> Regenerate from ENV (VLESS_URL) & Restart -# /reload -> Just Restart (used by web_server.py after config change) +# Endpoint: /reload -> Restart sing-box (used by web_server.py after config change) ( while true; do # Read the request using nc. REQ=$(echo -e "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" | nc -l -p 9090 -q 1) echo "$(date): Received request on 9090" - if echo "$REQ" | grep -q "GET /update"; then - echo "$(date): Action: UPDATE (Regen from ENV + Restart)" - if generate_config; then - restart_singbox - fi - elif echo "$REQ" | grep -q "GET /reload"; then - echo "$(date): Action: RELOAD (Restart only)" + if echo "$REQ" | grep -q "GET /reload"; then + echo "$(date): Action: RELOAD (Restart sing-box)" restart_singbox else echo "$(date): Unknown request or ping." @@ -85,20 +59,6 @@ WEBUI_PID=$! ) & CONTROL_PID=$! -# Periodic Update Loop (only if VLESS_URL is set) -if [[ -n "$VLESS_URL" ]]; then - ( - while true; do - sleep "$((UPDATE_INTERVAL * 60))" - echo "$(date): Checking for periodic update..." - if generate_config; then - restart_singbox - fi - done - ) & - UPDATE_PID=$! -fi - # Keep container alive - wait for any background process echo "$(date): Entrypoint ready. Waiting for processes..." diff --git a/scripts/gen-client-from-url.sh b/scripts/gen-client-from-url.sh deleted file mode 100755 index d89a297..0000000 --- a/scripts/gen-client-from-url.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Usage: ./gen-client-from-url.sh "vless://uuid@host:443?type=tcp&security=reality&pbk=PUBLIC_KEY&fp=random&sni=yahoo.com&sid=SHORTID&spx=%2F&flow=xtls-rprx-vision#tag" [output.json] -# If output not set, defaults to client.json - -URL_INPUT=${1:-} -OUT_FILE=${2:-client.json} -TEMPLATE_DIR="$(cd "$(dirname "$0")" && pwd)" -TEMPLATE_FILE="$TEMPLATE_DIR/client.template.json" - -if [[ -z "$URL_INPUT" ]]; then - echo "Error: provide VLESS reality URL or Subscription URL" >&2 - exit 1 -fi - -if [[ ! -f "$TEMPLATE_FILE" ]]; then - echo "Template not found: $TEMPLATE_FILE" >&2 - exit 1 -fi - -# Check if input starts with vless:// -if [[ "$URL_INPUT" != vless://* ]]; then - echo "Error: Only vless:// URLs are supported." >&2 - exit 1 -fi - -# Strip scheme -URL_NOSCHEME=${URL_INPUT#vless://} - -UUID_HOST_PORT=${URL_NOSCHEME%%\?*} -QUERY_AND_TAG=${URL_NOSCHEME#*?} -QUERY=${QUERY_AND_TAG%%#*} -TAG_RAW=${URL_INPUT#*#} -TAG=${TAG_RAW:-reality} - -UUID=${UUID_HOST_PORT%%@*} -HOST_PORT=${UUID_HOST_PORT#*@} -HOST=${HOST_PORT%%:*} -PORT=${HOST_PORT##*:} - -# Parse query params (portable, no associative arrays) -PBK=""; FINGERPRINT="chrome"; SNI=""; SHORT_ID=""; SPX=""; FLOW="" -OLD_IFS=$IFS -IFS='&' -set +u -for kv in $QUERY; do - key=${kv%%=*} - val=${kv#*=} - case "$key" in - pbk) PBK=$val ;; - fp) FINGERPRINT=$val ;; - sni) SNI=$val ;; - sid) SHORT_ID=$val ;; - spx) SPX=$val ;; - flow) FLOW=$val ;; - esac -done -set -u -IFS=$OLD_IFS -SNI=${SNI:-$HOST} -# SPX currently not used - -if [[ -z "$UUID" || -z "$HOST" || -z "$PORT" || -z "$PBK" || -z "$SHORT_ID" ]]; then - echo "Missing required fields (uuid/host/port/pbk/sid)" >&2 - exit 1 -fi - -TMP=$(mktemp) -cp "$TEMPLATE_FILE" "$TMP" - -# Perform replacements safely using jq -# Replace simple placeholders -jq \ - --arg uuid "$UUID" \ - --arg server "$HOST" \ - --argjson port "$PORT" \ - --arg tag "$TAG" \ - --arg sni "$SNI" \ - --arg fp "$FINGERPRINT" \ - --arg pk "$PBK" \ - --arg sid "$SHORT_ID" \ - --arg flow "$FLOW" ' - (.outbounds[] | select(.type=="vless")) as $v | ( - .outbounds |= map(if .type=="vless" then ( - .uuid=$uuid - | .server=$server - | .server_port=$port - | .tag=$tag - | .tls.server_name=$sni - | .tls.utls.fingerprint=$fp - | .tls.reality.public_key=$pk - | .tls.reality.short_id=$sid - | .flow=$flow - ) else . end) - | .route.final=$tag - )' "$TMP" > "$OUT_FILE" - -rm "$TMP" - -echo "Generated $OUT_FILE from URL (tag=$TAG)" diff --git a/scripts/menu.sh b/scripts/menu.sh deleted file mode 100644 index 197b839..0000000 --- a/scripts/menu.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env bash -set -u - -URL_INPUT=${1:-} -CONFIG_FILE="client.json" - -if [[ -z "$URL_INPUT" ]]; then - echo "Usage: ./menu.sh " - exit 1 -fi - -# Function to decode URL params specially for VLESS -decode_url() { - local encoded="$1" - # Basic URL decode - echo -e "${encoded//%/\\x}" -} - -# 1. Detect type -if [[ "$URL_INPUT" =~ ^vless:// ]]; then - echo "Direct VLESS URL detected. Applying..." - ./gen-client-from-url.sh "$URL_INPUT" "$CONFIG_FILE" - echo "Triggering reload..." - curl -s http://localhost:9090/reload - echo "Done." - exit 0 -fi - -# 2. It's likely a subscription -echo "Fetching subscription..." -SUB_CONTENT=$(curl -sSL "$URL_INPUT") - -if [[ -z "$SUB_CONTENT" ]]; then - echo "Error: Empty response." - exit 1 -fi - -# Try Base64 decode -if DECODED=$(echo "$SUB_CONTENT" | base64 -d 2>/dev/null); then - echo "Subscription is Base64 encoded." - RAW_LIST="$DECODED" -else - echo "Subscription is plain text." - RAW_LIST="$SUB_CONTENT" -fi - -# 3. Parse VLESS links -# We will use an array to store links and names -declare -a LINKS -declare -a NAMES - -i=0 -while IFS= read -r line; do - # trimming - line=$(echo "$line" | xargs) - if [[ "$line" =~ ^vless:// ]]; then - LINKS[$i]="$line" - - # Extract name from hash #Name - if [[ "$line" =~ \#(.*)$ ]]; then - NAME=$(decode_url "${BASH_REMATCH[1]}") - else - NAME="Config_$((i+1))" - fi - NAMES[$i]="$NAME" - ((i++)) - fi -done <<< "$RAW_LIST" - -COUNT=${#LINKS[@]} - -if [[ "$COUNT" -eq 0 ]]; then - echo "No VLESS configs found in subscription." - exit 1 -fi - -# 4. Display Menu -echo "Found $COUNT configurations:" -echo "--------------------------------" -for (( j=0; j @@ -375,21 +541,62 @@ -
+ +
+ + +
+ + +
- +
-
-

Вставьте VLESS ссылку

+

Вставьте ссылку подписки (sing-box формат)

- - + + + + +
+ + +
+
+
+ +
+ +
+

Вставьте VLESS ссылку

+
+ + +
+
@@ -416,6 +623,7 @@