Skip to content

Repository files navigation

Sub Manager VPN

Личный менеджер бесплатных VPN-подписок. Один статический бинарник на Go.

Он берёт только те GitHub-источники, которые ты сам добавил в whitelist, парсит и дедуплицирует их, отбрасывает сломанные/неподдерживаемые/незашифрованные/ вредоносные узлы, пропинговывает каждый кандидат 3× через встроенный движок mihomo (16–32 параллельных воркера), оставляет 3–5 лучших по стране по задержке и генерирует подписки для sing-box, v2rayN/xray и Clash.Meta. Долгоживущий встроенный планировщик сам обновляет их 24/7 (заменяет мёртвые/ деградировавшие узлы), а веб-интерфейс управления (пакет internal/web) управляет источниками и показывает статус. Движок (mihomo) встроен в бинарник — отдельной загрузки ядра не требуется.

Целевая платформа: Linux (Ubuntu 22+). Windows/macOS — только для разработки. Речь с пользователем ведётся на русском.


Содержание


Требования

  • Linux (Ubuntu 22.04 или новее). Код кроссплатформенный, но egress-изоляция заточена под Linux.
  • Go 1.25+ (для сборки из исходников). Бинарник внешних зависимостей не требует.
  • Доступ в интернет (HTTPS): менеджер скачивает базу GeoIP и сами подписки. Без egress к IP узлов пинги будут падать — проверь сеть заранее.
  • Опционально make (для make build/make run), но go достаточно.

Установка Go

Если Go уже есть (go version ≥ 1.25), пропусти этот раздел.

# скачать последнюю стабильную 1.25.x (пример для x86_64 Linux)
curl -fsSL https://go.dev/dl/go1.25.0.linux-amd64.tar.gz -o /tmp/go.tgz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf /tmp/go.tgz
# добавить в PATH (для текущей сессии и навсегда)
export PATH=$PATH:/usr/local/go/bin
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
go version   # должно показать go1.25.x

Без прав root можно распаковать в $HOME/sdk/go и прописать этот путь в PATH.


Сборка

Клонируй репозиторий и собери:

git clone <repo-url> sub-manager
cd sub-manager

make build        # эквивалентно `go build ./...`
# или собрать исполняемый файл сразу:
go build -o sub-manager .

Проверить код перед запуском:

make vet         # go vet ./...
make test        # go test ./...  (юнит + интеграционные тесты)

Бинарник sub-manager готов к запуску.


Быстрый старт

# запустить (откроется веб-интерфейс управления)
./sub-manager -web-token <токен> -web-secret <секрет-путь-24+ символа>
# или через make:
make run         # go run .

При первом запуске менеджер:

  1. Создаёт каталог конфигурации (~/.config/vpn-sub-manager/).
  2. Инициализирует встроенный движок mihomo (Go-модуль, часть бинарника — загрузки из сети не требуется).
  3. Скачивает базу GeoLite2-Country.mmdb в assets/ (один раз).
  4. Запускает планировщик в фоне и поднимает веб-интерфейс управления по скрытому пути http://<web-addr>/<web-secret>/ (по умолчанию http://127.0.0.1:8090/<web-secret>/).

Открой в браузере http://127.0.0.1:8090/<web-secret>/, введи веб-токен (-web-token) — и попадёшь в панель из 9 зон: Status, Sources, Nodes, Cycle, Generate, Publish, Settings, Security, Pipeline. Добавь источники (см. ниже) — планировщик сам их обойдёт, отфильтрует, пропингует и сгенерирует подписки в out/. Остановить: Ctrl+C.


Установка одной командой (скрипт)

Скрипт scripts/install.sh сам скачает/соберёт бинарник, запишет все ключи и настройки в config.json (со случайными секретами, chmod 600), поставит systemd-юнит и выведет URL админки и подписок.

# одной строкой (бинарник собирается CI и кладётся в bin/ репозитория,
# релиз создавать не нужно). REPO/BRANCH можно переопределить:
bash <(curl -fsSL https://raw.githubusercontent.com/HnDK0/Sub-Manager-VPN/main/scripts/install.sh)

# или, если клонировал репозиторий локально — собрать из исходников:
bash scripts/install.sh

Переопределить любые значения можно через переменные окружения: WEB_TOKEN, WEB_SECRET, SERVE_TOKEN, WEB_ADDR, SERVE_ADDR, INTERVAL, TOPN, CORPSE, INSTALL_DIR. Повторный запуск с FORCE=1 перезаписывает бинарник и config.json (секреты сгенерируются заново).

После установки админка доступна по http://<web-addr>/<web-secret>/ (по умолчанию http://127.0.0.1:8090/<web-secret>/); токен — значение web_token из config.json. Для headless-сервера, чтобы юнит жил без активной сессии: loginctl enable-linger $USER.

Подписки публикуются встроенным HTTP-сервером по http://<serve-addr>/s/<serve-token>/ (по умолчанию http://127.0.0.1:18080/s/<serve-token>/). Чтобы выставить их в интернет через HTTPS-домен — см. раздел «nginx / Caddy / Apache reverse proxy» ниже.

Настройка источников

Источники хранятся только в твоём whitelist (обычный текстовый файл-реестр; сделано намеренно, чтобы избежать блокировок SQLITE_BUSY). Никакие источники не зашиты в код — менеджер не скачает ничего, чего ты не добавил и не включил.

Вариант А — веб-интерфейс управления

Панель управления доступна по скрытому пути http://<web-addr>/<web-secret>/ (по умолчанию http://127.0.0.1:8090/<web-secret>/). При открытии запрашивается веб-токен (-web-token); дальше всё управление — через браузер. Зоны панели:

  • Status — живой прогресс планировщика и KPI узлов.
  • Sources — управление whitelist-источниками (добавить/вкл-выкл/заменить/удалить).
  • Nodes — таблица живых/мёртвых узлов, фильтр по стране, ручной тест выбранных.
  • Cycle — принудительный прогон цикла прямо сейчас.
  • Generate — превью сгенерированных подписок (sing-box / v2rayN / Clash).
  • Publish — статус и ссылки встроенного HTTP-сервера подписок, snippet nginx.
  • Settings — просмотр и редактирование параметров запуска (сохраняются в config.json; часть параметров применяется после перезапуска).
  • Security — сводка применённых фильтров безопасности.
  • Pipeline — воронка стадий обработки (fetched → parsed → dedup → … → kept).

Добавленные и включённые источники подхватываются планировщиком на следующем цикле автоматически — перезапуск не нужен.

Нагрузка на источники минимальна. Каждый цикл планировщик скачивает источник условным запросом: отправляет сохранённые ETag/Last-Modified (If-None-Match/If-Modified-Since). Если сервер отвечает 304 Not Modified — тело не качается и не перепарсивается, берутся ранее распарсенные узлы из кэша. Если сервер не отдаёт заголовки валидации — тело сравнивается по SHA с прошлым скачиванием, и при совпадении тоже пропускается репарс. Дубли внутри цикла убираются filter.Dedup; а «трупы» (узлы, мёртвые ≥ CorpseCycles, по умолчанию 5 циклов подряд) не пингуются повторно (filterCorpses). Живые узлы перепроверяются каждый цикл — это и есть суть 24/7-мониторинга (детект смерти/деградации).

Вариант Б — флаг -seed (одноразово, воспроизводимо)

Положи список ссылок в файл (одна https-ссылка на строку, # — комментарий), например sources.txt, и запусти:

./sub-manager -seed sources.txt

Команда заменяет весь текущий список источников содержимым файла (только валидные https), выводит seeded N sources (M skipped) и завершается. Дальше запускай обычно: ./sub-manager. Содержащийся в репозитории sources.txt — это твой собственный список, а не встроенный набор.


Каталог конфигурации

По умолчанию: ~/.config/vpn-sub-manager/ (переопределяется флагами -state, -assets, -out).

Путь Назначение
config.json Сохранённые параметры запуска (входные: -web-secret, -web-token, -web-addr, -serve-*, -interval, -topn, …); флаги CLI переопределяют файл. Если забыл путь/токен админки — см. этот файл. Содержит секреты (web-token/web-secret) в открытом виде — защити правами (chmod 600) и не коммить.
state.db SQLite: источники, узлы, результаты пингов, история (bounded retention)
assets/ GeoLite2-Country.mmdb (GeoIP, скачивается один раз, далее офлайн)
out/ Сгенерированные подписки (см. ниже)

Выходные подписки

Генерируются в out/ и обновляются каждым циклом планировщика:

  1. sing-box JSONout/singbox.json (SFA-конфиг; импортируй файл в клиент напрямую или отдай по HTTP).
  2. v2rayN / xray base64out/v2rayn.txt (base64 от newline-списка нативных URI).
  3. Clash.Meta YAMLout/clash.yaml (proxies[] + proxy-groups url-test).
  4. Сводкаout/meta.json (небольшая текстовая сводка: время генерации / число узлов; не полные метаданные SFA).

Все три формата содержат только явные, известные поля узла; никакие непроверенные/опасные ключи (поля Extra/Raw) в подписку не попадают. Если цикл вернул пустой результат, предыдущий рабочий файл не перезаписывается (защита от затирания хорошей подписки пустым циклом).

Имена узлов — единообразные. В подписках каждый узел назван по шаблону <TYPE> <host:port> <ENC> <CC>: <TYPE> — латиницей (VLESS/TROJAN/HY2/TUIC/VMESS), <ENC> — метод шифрования/безопасность, <CC> — реальный ISO-код страны (гео-база, XX только если гео недоступно). Узлы с одинаковым host:port, но разными креденшелами, получают стабильный суффикс-хеш от креденшела — имена воспроизводимы между циклами. Оригинальное сырое имя хранится только в БД (Node.Name) — оно нужно для точного маркера мёртвых узлов и чтобы не дублировать узлы при пинге; в выходные подписки уходит только нормализованное имя.


Встроенный HTTP-сервер подписок

Менеджер запускает встроенный HTTP-сервер, который отдаёт файлы из out/ по в запрошенном формате. По умолчанию слушает на 127.0.0.1:18080 (переопределяется флагом -serve-addr; -serve-addr "" — выключить).

Токенный путь (рекомендуется)

Если передан -serve-token <токен>, доступ к файлам только по пути /s/<токен>/:

http://127.0.0.1:18080/s/mysecret/singbox.json
http://127.0.0.1:18080/s/mysecret/v2rayn.txt
http://127.0.0.1:18080/s/mysecret/clash.yaml

При попытке обратиться без токена → 404. Пустой путь /s/<токен>/ покажет список файлов.

Без токена

Если токен не задан, файлы отдаются из корня:

http://127.0.0.1:18080/singbox.json

Зона Publish

Открой зону Publish в веб-панели, чтобы управлять публикацией подписок:

  • s — включить/выключить HTTP-сервер (или управление через флаги при запуске).
  • d — обнаружить установленные nginx/caddy/apache и показать их пути.
  • g <path> — сгенерировать snippet обратного проксирования nginx (напр. g /vpn-sub):
    location /vpn-sub/ {
        proxy_pass http://127.0.0.1:18080/s/<token>/;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
    
  • w <n> <path> — (НЕ реализовано) запись snippet в sites-enabled выбранного HTTPD; на данный момент реализована только генерация snippet (g), сама запись на диск отсутствует — snippet нужно скопировать вручную.

nginx / Caddy / Apache reverse proxy

Чтобы опубликовать подписки и админку за пределами localhost (через свой HTTPS-домен), повесь обратный прокси. Готовые snippet'ы для обоих сервисов также показываются прямо в зоне Publish веб-панели (поля «nginx snippet · подписки» и «nginx snippet · админка (SSE)») — их можно скопировать как есть.

Подписки (HTTP-сервер подписок)

# /etc/nginx/sites-enabled/vpn-sub
# замени /vpn-sub/ и mysecret на свои
location /vpn-sub/ {
    proxy_pass http://127.0.0.1:18080/s/mysecret/;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Подписки станут доступны по https://твой-домен/vpn-sub/singbox.json и т.д.

Админка (веб-интерфейс управления)

Админка монтируется под секретным префиксом /<web-secret>/ и стримит живой статус по SSE — поэтому прокси обязательно отключает буферизацию, иначе интерфейс не будет обновляться в реальном времени.

# /etc/nginx/sites-enabled/vpn-admin
# замени myverylongwebsecret12+12 на свой -web-secret
location /myverylongwebsecret12+12/ {
    proxy_pass http://127.0.0.1:8090/myverylongwebsecret12+12/;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    # SSE: держим стрим без буферизации
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 1d;
}

Открывай админку по https://твой-домен/<web-secret>/.


Флаги командной строки

Флаг По умолчанию Описание
-state ~/.config/vpn-sub-manager/state.db путь к БД состояния
-assets …/assets каталог гео-базы и ассетов
-out …/out каталог сгенерированных подписок
-interval 30m интервал обновления планировщика
-topn 5 сколько лучших узлов на страну (зажимается 3..5)
-degrade 0 порог деградации задержки (мс); 0 — медианная логика
-minkeep 1 минимум сохраняемых версий подписки
-seed "" засеять источники из файла и выйти (заменяет список)
-serve-addr 127.0.0.1:18080 адрес встроенного HTTP-сервера подписок ("" — выкл.)
-serve-token "" секретный токен пути (/s/<token>/); пусто — без авторизации
-web-addr 127.0.0.1:8090 адрес веб-интерфейса управления ("" — выкл.)
-web-token "" обязательный токен (Bearer) для веб-интерфейса; пусто — веб не стартует
-web-secret "" секретный префикс пути (≥24 символа / «12+12»); весь UI+API монтируется под http://<addr>/<secret>/; пусто — веб не стартует

Пример:

./sub-manager -interval 15m -topn 4 -out /srv/vpn/out

Планировщик 24/7 и встроенный движок

  • Планировщик работает в памяти процесса: тикер time.Ticker запускает циклы обхода источников → фильтрации → пинга → выбора → генерации.
  • Движок: встроенный mihomo (Go-модуль, часть бинарника) — никакой загрузки ядра из сети и SHA-проверки не требуется.
  • GeoIP: GeoLite2-Country.mmdb берётся из sapics/ip-location-db (релиз latest; один раз, далее офлайн). Если скачать не удалось — используется имя из #fragment узла.
  • Самовосстановление: мёртвые/деградирующие узлы вытесняются живыми кандидатами той же страны; узлы, мёртвые K циклов подряд (флаг -corpse/CorpseCycles, по умолчанию 5, 0 — выключено), пропускаются при пинге, чтобы не тратить пробы. На первом цикле часть узлов может прийти «мёртвыми», пока mihomo-хаб не поднял порт, — последующие циклы исправляют это сами (см. ниже).

Безопасность

  • Белый список протоколов: Trojan, VLESS (tls/reality), VMess (tls), Hysteria2, TUIC, и Shadowsocks — только AEAD-шифры (aes-256-gcm, aes-128-gcm, chacha20-ietf-poly1305, xchacha20-ietf-poly1305, 2022-blake3-aes-256-gcm, 2022-blake3-aes-128-gcm, 2022-blake3-chacha20-poly1305). Не-AEAD Shadowsocks (cfb/rc4/none/…) и WireGuard отбрасываются фильтром DropUnsupported; любой SS-плагин (obfs/v2ray-plugin) отбрасывается DropMalware.
  • Только HTTPS-источники: не-https ссылки отклоняются и никогда не сохраняются.
  • Генераторы строят выходные структуры/URI строго из известных полей узла — опасный ключ не может «протечь» в твою подписку.
  • Egress-изоляция: тестовый конфиг mihomo использует freedom-outbound (прямой egress), а кандидат-узел выступает целью SOCKS5 CONNECT; хост-трафик никогда не маршрутизируется через узел как транзит (свойство безопасности сохраняется), а проба измеряет задержку TCP-соединения с узлом — не задержку «туннеля через узел».
  • Малварь: эвристики (в рамках Node.Extra) отбрасывают exec/command/ script/hijack/route и ssconf://-ссылки на исполняемые пейлоады.
  • Незащищённые узлы (VLESS без tls/reality, security=none, insecure=1/allow_insecure/skip-cert-verify) отбрасываются фильтром DropInsecure.
  • Скрытый путь админки: весь веб-UI и API монтируются только под секретным префиксом http://<web-addr>/<web-secret>/ (длина секрета ≥ 24 символа / «12+12»). Запросы вне этого пути (в т.ч. / и /api/...) получают 404 — панель невозможно найти перебором. Поверх скрытого пути обязателен Bearer-токен (-web-token); секрет пути и токен действуют совместно (оба нужны).

Устранение неполадок

  • Все узлы мертвы на самом первом цикле — скорее всего, mihomo-хаб ещё не успел поднять порт, когда движок начал пинговать. Это исправлено: движок теперь ждёт, пока mihomo-хаб реально привяжет порт, прежде чем слать пробы. Если видишь это на очень медленной машине, подними таймаут готовности движка в internal/mihomo (по умолчанию 5 с). На последующих циклах всё само восстановится.
  • Узлы мертвы стабильно — проверь исходящий egress из хоста/контейнера к IP узлов (например, curl -I https://github.com). Менеджер пингует реальные удалённые узлы, а не локальные заглушки.
  • Источник не скачивается — убедись, что ссылка https:// и (для github.com) это либо raw-файл, либо /raw/branch/path, либо корень репозитория. Форматы кандидатов: .txt, .yaml, .yml, .json либо имена с sub/subscribe/ subscription.
  • Планировщик не видит новый источник — источник должен быть включён (вкл/выкл в зоне Sources веб-панели, или добавлен через -seed). Только enabled-источники обходятся.

Запуск как сервиса (systemd)

Пример юнита /etc/systemd/system/vpn-sub-manager.service:

[Unit]
Description=Sub Manager VPN (free VPN subscription manager)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/sub-manager -serve-addr 127.0.0.1:18080 -serve-token mysecret
Restart=on-failure
RestartSec=10
# раскомментируй, чтобы засеять источники один раз:
# ExecStartPre=/usr/local/bin/sub-manager -seed /etc/vpn-sub-manager/sources.txt

[Install]
WantedBy=multi-user.target
sudo cp sub-manager /usr/local/bin/
sudo systemctl daemon-reload
sudo systemctl enable --now vpn-sub-manager
journalctl -u vpn-sub-manager -f

Подписки появятся в ~/.config/vpn-sub-manager/out/. Встроенный HTTP-сервер отдаёт их по http://127.0.0.1:18080/s/<token>/ (или без токена — в корне). Подключи в свой клиент (sing-box / v2rayN / Clash.Meta) напрямую файлом или URL.


Разработка

make build      # go build ./...
make vet        # go vet ./...
make test       # go test ./... (полный набор: юнит + интеграция)
make test-race  # go test -race ./... (перед релизами)
make run        # go run .
make clean      # go clean ./...

Интеграционный тест internal/integration (TestIntegrationRealMihomo) поднимает встроенный mihomo-хаб, синхронизирует узел и пропинговывает его; при отсутствии реального egress к узлу проба возвращает dead и тест чисто пропускается (никакой загрузки ядра/сети не требуется).

Пайплайн (6 стадий + подсистемы):

whitelist (config) → fetch → parse → filter → test (ping через mihomo) → select → generate (3 формата)
                          │                                                        │
                          └────────── state (SQLite) ◄── scheduler (24/7) ◄───────┘
                                          │
                                     mihomo (встроен) + geo (mmdb)

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages