Skip to content

Latest commit

 

History

History
496 lines (364 loc) · 43.4 KB

README.ru.md

File metadata and controls

496 lines (364 loc) · 43.4 KB

ShellShift

Самопроверяющийся агрегатор публичных прокси-конфигураций: VLESS Reality, VMess, Trojan, Shadowsocks и Hysteria2, написан на Go

Бесплатные VPN-конфиги и подписки V2Ray для обхода блокировок и белых списков. Каждый узел перед публикацией проверяется настоящим HTTPS-запросом через собственный туннель, а не пингом. Подписки подходят для v2rayNG, Hiddify, Happ, Streisand и других клиентов.

Живой дашборд Документация English ci publish Загрузки Релиз Написан на Go Лицензия: GPL-3.0-or-later Hits-of-Code Размер кода

Скачать с GitHub   Открыть живой дашборд

Документация

Впервые видите ShellShift или хотите точно знать, что значит «доказан»? Начните с раздела «Как это работает» или прочитайте всё на английском.

Caution

ShellShift не VPN-сервис. Он не держит и не обслуживает ни одного сервера. Трафик идёт от вашего клиента прямо на узел, который держит кто-то другой; эти операторы неизвестны и видят то, что через них проходит. Про каждый опубликованный узел установлено ровно одно: через него завершился HTTPS-запрос с проверенной цепочкой сертификатов, в момент, который показывает дашборд.

Разделы:

🎉 Возможности

  • Всё, чего ждёшь от агрегатора прокси, кроме одного: ничего не публикуется на веру.

Доказательство и проверка

  • Каждый узел поднимается внутри процесса через собственный outbound sing-box, поэтому битый узел может уронить только себя.
  • Узел считается доказанным только после того, как через его туннель прошёл полный HTTPS-запрос с проверенной цепочкой сертификатов. Ответ 204 по открытому HTTP доказательством не считается, а проверка сертификатов не отключается никогда.
  • Планирование по фактам: сначала дешёвый сигнал доступности, затем дедлайны по наблюдаемому p95 времени соединения, гонка проб под одним жёстким дедлайном и ранний выход. Безусловных пауз нет.
  • Необязательный замер пропускной способности поверх задержки.
  • Команда doctor за две минуты отличает «узлы мертвы» от «эта машина не достаёт до эталонных адресов».

Протоколы и транспорты

  • VLESS (Reality и TLS), VMess, Trojan, Shadowsocks (включая 2022-blake3) и Hysteria2.
  • Транспорты tcp, ws, grpc, httpupgrade / xhttp, http / h2 и quic.
  • uTLS-отпечатки, плагины SIP003, публичные ключи и short ID для Reality.
  • Закрытые множества: всё нераспознанное отклоняется на границе с причиной, которую можно посчитать, а не тащится дальше строкой.
Полная матрица протоколов
Поддерживается
Протоколы vless · vmess · trojan · ss (Shadowsocks) · hy2 / hysteria2
Транспорты tcp (raw, none) · ws · grpc · httpupgrade (xhttp) · http / h2 · quic
Защита none · tls · reality
uTLS-отпечатки chrome, firefox, edge, safari, 360, qq, ios, android, random(ized)
Шифры Shadowsocks AES-128/192/256-GCM, (x)chacha20-ietf-poly1305 и три шифра 2022-blake3
Плагины SIP003 obfs-local (simple-obfs) · v2ray-plugin

Hysteria v1 отклоняется везде, намеренно и единообразно. Предшественник принимал её в одной ветке кода и игнорировал в другой, поэтому ссылки v1 выпадали из источников, но пропускались из пула и считались v2 (INV-12).

Подписки и выходные файлы

  • Семь текстовых файлов подписок: всё, белый список и по одному на протокол.
  • В sub_wl.txt попадают Reality-узлы, у которых SNI есть в белом списке цензора и маскировочный сайт проверен напрямую: он существует и отдаёт валидный сертификат на собственное имя.
  • network.json: полный машиночитаемый срез, то есть воронка, доказательства по каждому узлу и всё, что показывает дашборд.
  • Ни аккаунтов, ни токенов, ни персональных ссылок. Все получают одни и те же байты.
  • Набор, в который никто не прошёл, всё равно публикуется с заголовком, а не исчезает.
Публикуемые файлы
Файл Содержимое
sub_all.txt все доказанные узлы, по возрастанию задержки
sub_wl.txt Reality-узлы с SNI из белого списка и доказанной маскировкой
sub_vless.txt VLESS поверх Reality или TLS: самый распространённый протокол и обычно самый живучий против DPI
sub_vmess.txt VMess
sub_trojan.txt Trojan
sub_ss.txt Shadowsocks
sub_hy2.txt Hysteria2
network.json воронка, доказательства по узлам и всё, что показывает карточка узла
state/ledger.json память прогона; публикуется, чтобы следующий прогон забрал её обратно

Дашборд

  • Статический двуязычный сайт (русский и английский) со светлой и тёмной темой.
  • Полоса пульса: когда получены данные и свежие ли они.
  • Воронка в виде бухгалтерского тождества: каждый этап считает, что отбросил, и числа сходятся.
  • Состав каталога по протоколам и странам, таблица узлов с поиском и фильтрами.
  • Карточка узла, в которой ссылка, сервер, IP и учётные данные скрыты, пока не нажата кнопка «Показать». new
  • Адреса узлов есть только в загружаемом срезе и никогда в HTML, так что поисковику нечего индексировать, а скриншоту нечего выдать.
  • Собственная Content-Security-Policy.

Zero-Trust и безопасность

  • Валидация по схеме и явным белым спискам, а не по чёрным.
  • Один раз разрешить имя, проверить полученный адрес и подключаться именно к нему. Это же закрывает окно для DNS rebinding.
  • Защита исходящих соединений для каждого протокола и транспорта: loopback, частные сети, CGNAT, link-local (включая адрес облачных метаданных), multicast, зарезервированные диапазоны и их IPv6-аналоги запрещены.
  • allowinsecure, insecure и allow_insecure выбрасываются при публикации, поэтому опубликованная ссылка никогда не просит клиента пропустить проверку сертификата.
  • Секреты в логах маскируются, а ID узла представляет собой SHA-256 от канонической формы, а не сами учётные данные.

Автоматизация

  • Плановая публикация каждые два часа в :17, прямо в GitHub Pages через Pages-артефакт. Ничего сгенерированного не коммитится в main.
  • Ветка-зеркало: каждый прогон ещё и перезаписывает подписки в ветке subs одним коммитом без истории. Так у них появляется второй адрес на raw.githubusercontent.com, а ветка никогда не разрастается.
  • Круговорот памяти: память прогона (только хеши и статистика) публикуется вместе с сайтом и забирается следующим прогоном. Ни кэша, ни git.
  • Защита публикации: прогон может заменить то, что сейчас живёт на сайте, только если его результат правдоподобен. Пустой прогон никогда не стирает подписки.
  • Список источников хранится в секретах репозитория и маскируется в логах построчно.
  • Готовые бинарники для Linux, macOS и Windows на amd64 и arm64 с контрольными суммами SHA-256. new

Ещё несколько приятных мелочей

  • Источники по реальной отдаче: измеренная история показала, что один источник давал 48% всех живых узлов при 3% объёма разбора, поэтому источники упорядочены по тому, что они действительно дают, а не по кругу.
  • Пул, который стареет: интервалы перепроверки растут вместе с наблюдаемой стабильностью, неудачи откладываются экспоненциально, устаревшие записи вымываются.
  • decode -rejects: подайте на вход любой источник и увидите каждую отклонённую строку с причиной.
  • paramdiff: перекодирует корпус офлайн и показывает, какие query-параметры публикация теряет, добавляет или меняет.
  • proofbench: сырое распределение ошибок доказательства по транспортам, чтобы подбирать бюджеты и параллелизм.
  • 56 инвариантов (INV-00 … INV-55), каждый закрывает реальный сбой, за который предшественник уже заплатил, и каждый назван по номеру в тесте.

Как это работает

Один статический бинарник на Go проводит один цикл сбора от начала до конца:

  1. Сбор: источники подписок скачиваются по HTTPS с жёсткими лимитами по байтам, строкам и времени.
  2. Разбор: каждая строка превращается в проверенный узел или отклоняется с причиной, которую машина может посчитать.
  3. Проверка на границе Zero-Trust: только разрешённые поля, одно разрешение DNS, закреплённый адрес.
  4. Доказательство: для одного узла внутри процесса собирается outbound sing-box, и через него должен завершиться HTTPS-запрос с проверенной цепочкой сертификатов.
  5. Классификация: настоящий ли маскировочный сайт у Reality? Проверяется напрямую, а не берётся из списка.
  6. Публикация выживших, но только если результат достаточно правдоподобен, чтобы заменить то, что уже опубликовано.

Воронка, публикуемая с каждым прогоном, это бухгалтерское тождество, а не сводка:

Collected == Rejected + Duplicates + Deferred + NotAttempted + Failed + Proven
Этапы воронки
Этап Смысл
collected строки, скачанные из всех источников
rejected строки, которые разбор отклонил, с подсчётом по причинам (unknown_scheme, bad_uuid, unsupported_cipher, bad_psk_length, bad_reality_key, …)
decoded строки, давшие валидный узел
duplicates разобранные узлы, которые уже назвал другой источник
candidates уникальные узлы после дедупликации
deferred кандидаты, которых память признала не стоящими полной проверки в этом прогоне
policy узлы, прошедшие границу Zero-Trust
reached узлы, ответившие на дешёвый сигнал доступности
failed узлы, которые проверка попробовала и отклонила
not_attempted узлы, до которых очередь дошла бы, но бюджет прогона кончился раньше
proven опубликованные узлы

Именно подсчёт отказов по причинам делает регрессию парсера видимой, а не похожей на обычную текучку источников.

Note

Что точно значит «доказан»: HTTPS-запрос GET к одному из трёх известных адресов проверки связи (www.gstatic.com, cp.cloudflare.com, connectivitycheck.gstatic.com), завершённый через собственный outbound узла, с проверенной цепочкой сертификатов. Не 204 по открытому HTTP, который враждебный или captive-узел может подделать (INV-31), и никогда с отключённой проверкой (INV-30, INV-38).

Скриншоты

Тёмная тема Светлая тема
Первый экран дашборда и полоса пульса, тёмная тема Первый экран дашборда и полоса пульса, светлая тема
Карточки подписок с кнопками копирования Состав каталога по протоколам и странам и таблица узлов
Карточка узла Телефон
Карточка узла со скрытыми сервером, IP и учётными данными Дашборд на телефоне, русский интерфейс

Установка

Платформа Стабильная
Linux amd64 arm64
macOS Apple Silicon Intel
Windows x64 ARM64

Каждый архив содержит один статический бинарник, лицензию, оба README и пример конфигурации. Контрольные суммы лежат в SHA256SUMS.txt.

Нужны только подписки?

Ничего ставить не нужно. Откройте живой дашборд, скопируйте адрес подписки и вставьте его в клиент:

https://tuftytech.github.io/ShellShift/sub_all.txt

Замените sub_all.txt на sub_wl.txt, sub_vless.txt, sub_vmess.txt, sub_trojan.txt, sub_ss.txt или sub_hy2.txt, чтобы получить более узкий список.

Если в вашей сети заблокирован github.io, каждый прогон зеркалирует те же файлы в ветку subs:

https://raw.githubusercontent.com/TuftyTech/ShellShift/subs/sub_all.txt

Запуск ShellShift у себя

  1. Скачайте и проверьте

    Возьмите архив для своей платформы из таблицы выше, распакуйте его и сверьте с SHA256SUMS.txt.

  2. Сначала получите положительные контроли

    ./shellshift doctor

    doctor запрашивает каждый эталонный адрес напрямую, без туннеля, а затем прогоняет сам механизм доказательства по заведомо рабочему пути. Если не прошло хоть что-то, проблема в вашей сети, а не в узлах.

  3. Дайте ему список источников

    Скопируйте config/sources.example.json в config/sources.json и добавьте массив sources, либо передайте список через SHELLSHIFT_SOURCES (по одному URL в строке). Подробнее в разделе Конфигурация.

  4. Запустите цикл

    # проверить и отчитаться, ничего не записывая
    ./shellshift run -config config/sources.json -dry-run
    
    # полный цикл: сбор, проверка, классификация, публикация в dist/
    ./shellshift run -config config/sources.json -out dist -state .state/ledger.json
Все команды
Команда Что делает
run полный цикл: сбор, проверка, классификация, публикация
site собирает статический сайт из опубликованного network.json (без сети и без проверки)
decode разбирает прокси-URI со stdin и показывает, что отклонено (-rejects, -json, -labels)
doctor устанавливает положительные контроли, без которых диагноз невозможен (INV-55)
version печатает версию сборки
./shellshift site -data dist/network.json -out dist -origin https://tuftytech.github.io/ShellShift
./shellshift decode -rejects < some-feed.txt

Сборка из исходников

Important

Тег сборки with_utls не опционален. sing-box держит uTLS, а вместе с ним и клиент Reality, за этим тегом. Без него каждый Reality-узел не собирается и молча отбрасывается (в одном реальном корпусе это было 540 узлов из 865), и выглядит это как деградация источников, а не как ошибка сборки. tunnel.CheckBuild() отказывается запускать прогон в таком состоянии (INV-49).

git clone https://github.com/TuftyTech/ShellShift.git
cd ShellShift
./tools/install-hooks.sh                         # один раз: ставит pre-commit хук гигиены

go build -tags with_utls -o shellshift ./cmd/shellshift
./tools/verify.sh                                # все гейты, которые гоняет CI

Нужен Go 1.26 или новее. Без cgo и без сопутствующих процессов.

Развёртывание своего экземпляра

  1. Сделайте форк репозитория.

  2. Добавьте секреты в Settings → Secrets and variables → Actions:

    Секрет Назначение
    SHELLSHIFT_SOURCES список источников, по одному URL в строке (обязателен)
    SHELLSHIFT_WHITELIST_DOMAINS списки доменов для классификации по белому списку (необязателен)
    SHELLSHIFT_WHITELIST_NETWORKS списки CIDR для того же (необязателен)
  3. В Settings → Pages выберите для Source значение GitHub Actions.

  4. Поменяйте site_url в config/run.json и SITE_ORIGIN в .github/workflows/publish.yml на свой адрес Pages.

  5. Запустите Actions → publish → Run workflow или дождитесь ближайшего слота в :17.

Note

Плановый прогон отказывается стартовать без SHELLSHIFT_SOURCES и называет секрет по имени. Отсутствие списка для белого списка даёт только предупреждение, но оно звучит явно, чтобы «никто не прошёл» никогда не выглядело как «никто не мог пройти».

Конфигурация

Один JSON-файл, загружаемый явно. config/sources.example.json показывает форму и намеренно не содержит ключа sources: строку, похожую на адрес источника в публичном дереве, кто-нибудь обязательно скопирует.

Поля и переопределения через окружение
Поле Смысл
sources адреса источников для сбора
whitelist_domains адреса списков доменов для классификации по белому списку
whitelist_networks адреса списков CIDR для того же
site_url опубликованный адрес, используется в ссылках подписок
run_budget потолок по времени на фазу проверки (например, "6m")
concurrency параллелизм проверки
proof_concurrency параллелизм именно доказательных запросов
geoip определять страну узла
measure_throughput замерять пропускную способность в дополнение к задержке

Три переменные заменяют соответствующие списки после чтения файла и до его валидации:

Переменная Заменяет
SHELLSHIFT_SOURCES sources
SHELLSHIFT_WHITELIST_DOMAINS whitelist_domains
SHELLSHIFT_WHITELIST_NETWORKS whitelist_networks

По одному URL в строке, пустые строки и комментарии # игнорируются. Два свойства сделаны намеренно, и на оба есть тесты:

  • Переменная заменяет свой список, а не сливается с ним. Слияние молча оставило бы источники, которые оператор уже убрал.
  • Переменная, которая задана, но пуста, считается ошибкой, а не поводом откатиться к файлу. Такой случай означает неправильно настроенный секрет.

Клиенты:

Tip

Подойдёт любой клиент, который понимает адреса подписок, например:

  • 📦 sing-box, то же ядро, через которое ShellShift доказывает узлы
  • 🌀 Hiddify на Android, iOS, Windows, macOS и Linux
  • 🤖 v2rayNG на Android
  • 🍏 Streisand и YukariBox на iOS

Сетевые обращения:

Куда подключается ShellShift и зачем:

  • Адреса источников из вашей конфигурации: по HTTPS, с жёсткими лимитами по байтам, строкам и времени.
  • Системный DNS-резолвер: один раз на узел. Полученный адрес проверяется и закрепляется для подключения.
  • Сам узел: через его собственный outbound sing-box внутри процесса, и никогда на частный или зарезервированный адрес.
  • www.gstatic.com, cp.cloudflare.com, connectivitycheck.gstatic.com: доказательный запрос, отправленный через узел.
  • Маскировочные сайты Reality: прямое TLS-рукопожатие на порт 443, чтобы проверить, что сайт существует и отдаёт валидный сертификат на собственное имя.
  • ip-api.com: пакетное определение страны для доказанных узлов. Поставьте geoip в false, чтобы отключить.
  • Ваш опубликованный сайт: плановый workflow забирает с него предыдущий state/ledger.json.

О посетителях дашборда ничего не собирается и не хранится: это статические файлы.

Архитектура

Один статический бинарник на Go. Ни сопутствующего интерпретатора, ни sidecar-процесса, ничего не читается в момент импорта. Каждая подкоманда является чистой функцией от своих флагов и файлов, на которые ей указали.

Пакеты и несущие решения
cmd/shellshift        run · site · decode · doctor · version
internal/model        канонические типы; структурная идентичность узла
internal/decode       URI -> проверенный узел; здесь живут INV-01..29
internal/intake       скачивание источников с жёсткими лимитами, порядок по отдаче
internal/policy       граница Zero-Trust: защита исходящих, резолвер, белый список, маскировка, geoip
internal/tunnel       единственный интерфейс подключения; sing-box как библиотека, один outbound на узел
internal/verify       вердикт: HTTPS с проверенной цепочкой; адаптивные бюджеты; пропускная способность
internal/ledger       память: пул со старением, отдача источников
internal/publish      наборы, network.json, статический сайт (html/template + встроенные ресурсы)
internal/pipeline     один цикл сбора; планировщик параллелизма
internal/obs          структурные логи с маскировкой секретов
internal/config       явная загрузка конфигурации
  • sing-box используется как библиотека, а не как подпроцесс. Предшественник запускал один процесс sing-box на пачку из пятидесяти узлов с общей конфигурацией, так что один отклонённый outbound убивал все пятьдесят (INV-00).
  • Подключению передаётся разрешённый адрес, а не имя хоста. Получив имя, outbound внутри процесса потребовал бы доступных из враждебного ввода DNS, маршрутизации и реестров сервисов sing-box (INV-33, INV-48).
  • Идентичность структурная и хешированная, потому что кортеж содержит учётные данные, а ключ пишется в логи, состояние и на дашборд (INV-21).
  • Сборка результата и его публикация разделены. pipeline.Runner ничего не пишет, решает publish.Guard. Предшественник опустошал все файлы подписок в прогоне без выживших (INV-43).
  • Артефакты никогда не коммитятся в main. Предшественник коммитил около 406 КБ перегенерированных файлов за прогон, шесть раз в день. Зеркало subs состоит из одного коммита без истории, который заменяется на каждом прогоне, поэтому в нём всегда одна копия.
Тесты и гейты

./tools/verify.sh запускает gofmt, go build, go vet, тесты с детектором гонок, проверку на пропущенные тесты, govulncheck, гейты гигиены и smoke-тест CLI. Он отказывается выдавать незапущенный гейт за пройденный.

  • Пропущенный тест роняет CI.
  • Инвариант считается перенесённым, только когда тест называет его номер. tools/hygiene.sh падает, если объявленный INV-nn не назван ни в одном _test.go.
  • Каждый action в workflow запинен по 40-символьному SHA коммита, а не по подвижному тегу.
  • Ни сгенерированный артефакт, ни бинарник, ни строка, похожая на секрет, не могут попасть в индекс.
  • Два гейта точности охраняют публикацию: опции sing-box, собранные из исходного URI, сравниваются с нашими, а отдельная проверка падает, когда публикация начинает терять query-параметр без объявленной причины.

Сам каталог инвариантов хранится вне публичного дерева, потому что в нём были рабочие адреса. На чистом клоне этот гейт сообщает НЕ ПРОВЕРЕНО вместо «пройдено» и говорит об этом в последней строке.

Благодарности:

  • @SagerNet и nekohasekai за sing-box и sing, через которые проходит каждое доказательство.
  • MetaCubeX за форк uTLS, благодаря которому возможен Reality.
  • Мейнтейнерам quic-go и miekg/dns.
  • ip-api.com за бесплатный пакетный эндпоинт геолокации.
  • Всем, кто поддерживает публичные источники живыми.

Перевод

  • Дашборд говорит на русском и английском. Все строки интерфейса лежат в internal/publish/strings.go, а TestStringsNamedFromGoExistInBothLanguages падает, если ключ есть в одном языке и отсутствует в другом.
  • Хотите ещё один язык? Добавьте таблицу рядом с ru и en и откройте pull request.

Участие в разработке

  • Прочитайте internal/decode, internal/verify и internal/tunnel перед тем, как их менять: защиты там не стилистические.
  • Запускайте ./tools/verify.sh перед пушем. Это тот же набор гейтов, что гоняет CI.
  • Называйте инвариант, который тест охраняет (INV-nn), или правило, которое он кодирует (GHOST-nnn).
  • Комментарии объясняют почему и ссылаются на сбой, ради которого существует защита.
  • Ошибка несёт причину, которую машина может посчитать, а не только сообщение.
  • Отсутствующее значение отсутствует и никогда не подменяется заглушкой.
  • Гейт, который не может упасть, хуже отсутствия гейта.
  • Баги, идеи и вопросы идут через формы issue. Проблемы безопасности сообщайте приватно.
Автор коммитов

Коммиты подписываются как tuftyxxeW <343729923+tuftyxxeW@users.noreply.github.com> и не содержат трейлера Co-Authored-By ни для какого инструмента и ни в какой форме. GitHub строит список Contributors и по адресу автора, и по трейлерам соавторов, поэтому важны обе половины. На свежем клоне это нужно выставить до первого коммита:

git config user.name  "tuftyxxeW"
git config user.email "343729923+tuftyxxeW@users.noreply.github.com"

Поддержка

  • Лучшая поддержка: отчёт о рабочем узле и понятный баг-репорт.
  • Не забудьте поставить 🌟 репозиторию, если ShellShift вам пригодился ^^

Ссылки

Warning

ShellShift живёт только на github.com/TuftyTech/ShellShift и tuftytech.github.io/ShellShift. Любой другой сайт, предлагающий подписки или бинарники «ShellShift», не имеет к нам отношения, и мы не отвечаем за то, что вы там скачаете.

Лицензия

Проект распространяется по лицензии GNU General Public License v3.0 or later.

© Copyright (C) 2026-present TuftyTech
- ShellShift статически линкует sing-box и sing (GPL-3.0-or-later) в один бинарник,
  поэтому совместное произведение тоже распространяется под GPL-3.0-or-later.
- sing-box и sing принадлежат © 2022 nekohasekai, с дополнительным условием по разделу 7 GPL:
  производное произведение не может использовать их название или намекать на связь без согласия.

ShellShift не связан с проектом sing-box и его авторами, не одобрен ими и не аффилирован с ними. Названия протоколов (VLESS, VMess, Trojan, Shadowsocks, Hysteria2, Reality) и клиентов используются описательно; все товарные знаки принадлежат их владельцам.