Imported from zeklop/shadowrocket-configs (
AGENTS.md). Install upstream withnpx skills add zeklop/shadowrocket-configs. Copyright stays with the author.
AGENTS.md
Единый конфиг проекта для всех агентов. CLAUDE.md и GEMINI.md — симлинки сюда, своей логики не содержат.
Что это
Не программа, а набор данных: три конфига Shadowrocket и списки правил маршрутизации (*.list) для РФ. Сборки нет, приложение локально не запустить.
Единственная проверка — python3 scripts/check_lists.py. Гонять перед каждым push. Она ловит коллизии между списками, дубли, битый синтаксис, RULE-SET на несуществующий файл, update-url на чужой конфиг, нарушенную сортировку AI.list и несуществующие домены (NXDOMAIN). Тот же скрипт крутится в .github/workflows/check.yml, но там он срабатывает после push, то есть уже после деплоя — как вторая линия, а не как ворота.
Скрипт резолвит через явные публичные DNS, а не через системный: при поднятом прокси с fake-IP системный резолвер выдумывает адрес на любой домен, включая несуществующий, и проверка молча вырождается.
simple.conf— для новичков, входная точка из README. Комментарий на каждой строке, секции[Proxy Group]нет: одно подключение и всё.template.conf— полный, с группамиNL/US/RU.simple-dns.conf— экспериментальный split DNS: глобально Yandex Safe DoH, Instagram/Meta через NextDNS. NextDNS и fallback идут через выбранный прокси. В README не выносить без отдельного решения.docs/sub-conversion.md— инструкция и Python-скрипт конвертации подписок Sing-Box / INCY с HWID-авторизацией в формат VLESS Base64 для Shadowrocket.
Идея: РУ-трафик идёт DIRECT, всё остальное — через прокси, реклама и трекеры режутся в REJECT.
Деплой = push в main
Клиенты тянут файлы по raw.githubusercontent.com/zeklop/shadowrocket-configs/main/..., update-url в каждом конфиге задаёт interval=60. Отдельного шага релиза нет: коммит в main разъезжается по пользователям в течение часа. Отката через CI не существует. Сломанное правило — это сломанный интернет у чужих людей. Правки в main — только по явной просьбе.
Правка доезжает минутами, а не мгновенно. raw.githubusercontent.com отдаёт cache-control: max-age=300 — пять минут CDN держит старую версию даже при ручном переподключении в приложении. Потом до часа тикает interval=60. Проверять свежесть так: curl -sI <raw-url> | grep -i age, а содержимое дёргать с ?cb=$RANDOM. Практическое следствие: аварийный откат — это минуты, а не секунды, и «я переключил, а изменений нет» обычно означает кеш, а не сломанный конфиг.
update-url каждого конфига указывает на него самого. Если перепутать — конфиг перезапишет себя чужим.
При изменении любого конфига обновлять # UPDATED: в шапке.
Порядок правил решает всё
Первое совпадение выигрывает, поэтому порядок RULE-SET — это приоритет.
template.conf: REJECT → NORU(NL) → YOUTUBE(NL) → AI(US) → RUSSIA(DIRECT) → DOMAIN-SUFFIX,ru → #GEOIP,RU → FINAL,PROXY
simple.conf: REJECT → RUSSIA(DIRECT) → DOMAIN-SUFFIX,ru → #GEOIP,RU → FINAL,PROXY
simple-dns.conf: REJECT → Instagram/Meta (локальный DNS + PROXY) → RUSSIA(DIRECT) → DOMAIN-SUFFIX,ru → #GEOIP,RU → FINAL,PROXY
Следствия:
GEOIP,RUзакомментирован во всех конфигах — маршрутизация только по доменам. ЗначитRUSSIA.list— единственная страховка для РУ-сайтов вне зоны.ru, страховки по IP больше нет.- Один домен в двух списках → выигрывает тот, что выше. Перед добавлением домена —
grepпо всем*.list. DOMAIN-SUFFIX,ruпродублирован вRUSSIA.listи в самих конфигах. Безвредно, оба ведут в DIRECT.- YOUTUBE стоит выше AI не случайно:
youtubei.googleapis.com— поддоменgoogleapis.com.
Правила матчатся по запрошенному хосту, а не по CNAME
Поэтому DOMAIN-SUFFIX,cloudfront.net ловил бы только «голые» *.cloudfront.net-хосты, а сервисы со своим доменом за тем же CDN — нет. Не пытаться маршрутизировать по инфраструктуре, только по сервисам.
Имя хоста Shadowrocket берёт в том числе из SNI: соединение на голый IP 77.88.8.8 с SNI common.dot.dns.yandex.net попало в лог под именем и было поймано правилом DOMAIN-KEYWORD,yandex. То есть доменные правила работают и там, где приложение обращается по IP.
Как проверить конфиг на живом устройстве
Единственный способ узнать правду: статика и DNS-запросы не покажут ни поведения клиента, ни реальных маршрутов. На macOS (приложение есть и там) это делается так.
Логи — SQLite в ~/Library/Group Containers/group.com.liguangming.Shadowrocket/Library/Caches/Logs/: proxy-*.db и dns-*.db. Включаются в настройках приложения.
Копировать обязательно комплектом .db + .db-wal + .db-shm, потом PRAGMA wal_checkpoint(TRUNCATE). Свежие записи лежат в WAL: сам .db весит 4 КБ и покажет ноль строк, хотя данные есть.
Таблица logging — FTS3, читать из logging_content по позиционным колонкам:
- DNS:
c0host, c1result, c2server, c3type, c4elapsed, c5created - proxy:
c0url, c1ua, c2result, c3type, c4created
Поле c2result в proxy-логе — сработавшее правило целиком, вида DOMAIN-SUFFIX,2gis.com,DIRECT или FINAL,PROXY # <имя подключения>. Это прямой ответ на вопрос «куда ушёл домен и почему».
На iPhone доступен live-лог по http://<ip-устройства>:1082/api/log. Ответ потоковый и сам не завершается, поэтому читать через curl --max-time 5–10. В начале лога видны загруженный конфиг, число обычных и logical rules, основной и fallback DNS. Для проверки split DNS нужны три типа записей:
dns response record:host, полученныйresultи фактическийserver;tcp rule: сработавшее правило и итоговая политика;proxy lookup host/connect stream: реальный тип подключения и адрес прокси.
Не считать одного успешного запуска доказательством: приложение могло использовать прогретый DNS-кеш. После установки конфига переподключить Shadowrocket, полностью выгрузить приложение, запустить заново и проверить свежие записи.
Грабли, на которых уже стояли:
- По умолчанию резолвятся только DIRECT-домены. Проксируемые получают fake-IP и наверх не идут, в DNS-логе их нет. В
simple-dns.confскрытый параметрalways-ip-address = trueпринудительно резолвит все домены локально. - В DNS-логе только победитель гонки. Замеров проигравшего не существует — по логу нельзя сравнивать скорость резолверов, если они в одной строке
dns-server. - Свой трафик не считать за трафик клиента. Запросы скрипта к резолверу идут через туннель как обычное приложение и попадают в proxy-лог под
FINAL,PROXY. Собственный DNS Shadowrocket туда не попадает вообще — это и есть признак, что резолвер идёт мимо правил. - Активный конфиг:
CurrentRuleFileNameв~/Library/Group Containers/group.com.liguangming.Shadowrocket/Library/Preferences/group.com.liguangming.Shadowrocket.plist..db.rule— бинарь,stringsпо нему ничего не доказывает. - Системный резолвер на машине с запущенным Shadowrocket — фейковый (
198.18.0.2), выдумывает адрес на любой домен. Проверять существование доменов только через явные публичные DNS.
Группы прокси
[Proxy Group] в template.conf объявляет NL, US, RU. Имя группы должно совпадать с именем VPN-подключения, которое пользователь завёл руками по инструкции из README. Переименовывать нельзя — сломает конфиг у всех, кто уже настроился. Группа RU объявлена, но ни одним правилом не используется.
Последнее поле правила (policy) может быть встроенным (DIRECT, PROXY, REJECT и варианты), именем группы или именем конкретного сервера. Но сослаться по имени можно только на то, что в этом конфиге объявлено: секции [Proxy] с серверами тут нет — сервер у каждого пользователя свой, заводится руками. Поэтому единственное стабильное имя, на которое можно указать в правиле, — имя группы (оно же имя подключения). Имя конкретного сервера сработало бы только в личном конфиге с секцией [Proxy], а такой нельзя раздавать чужим людям. В simple.conf групп нет вовсе: FINAL,PROXY, где PROXY — текущее выбранное подключение.
Состав списков
REJECT.list— реклама и трекеры. Метрика (mc.yandex.ru) намеренно НЕ блокируется — это осознанное решение, а не пропуск. По той же причине не трогатьgoogletagmanager.com,ads.google.com,googlesyndication.com,googleadservices.com. Генерические антирекламные списки (blackmatrix7 и подобные) сюда не подключать: они режут ровно то, что здесь нужно живым.RUSSIA.list— РУ-сайты вне.ru.NORU.list— домены под NL. Секции#FIN,#ADS.AI.list— только AI-сервисы.github.comиworkos.comздесь не по ошибке: это логин Copilot и Cursor, они должны выходить с того же IP, что и сами сервисы. Gemini/AI Studio прописаны точечнымиDOMAIN, чтобы не утащить в US весь Google. Отсортирован по алфавиту — держать сортировку.YOUTUBE.list— всё про видео YouTube, включаяggpht.com(аватарки) иyoutubei.googleapis.com(API приложения).GOOGLE.list— заготовка, не подключена ни к одному конфигу. Собрана наDOMAINс ручным перечислением поддоменов: под «все домены Google» понадобитсяDOMAIN-SUFFIX,google.com.
Идиомы
#— комментарий. Закомментированная строка = выключенное правило, штатный способ временно отключить домен. Не вычищать (#DOMAIN-SUFFIX,reddit.comвNORU.list,#DOMAIN-SUFFIX,salebot.proвRUSSIA.list).- Первая строка каждого файла — метка-заголовок (
#REJECT,#AI, ...). DOMAIN-KEYWORDловит подстроку в любом месте домена — легко хватает лишнего.DOMAIN-KEYWORD,adjustпо этой причине заменён на точные суффиксы.
Перед добавлением домена — проверить, что он существует
ozon-os.solutions был скопирован из чужого конфига и оказался NXDOMAIN. Чужие конфиги — источник идей, не истины. https://common.dot.dns.yandex.net/dns-query из чужого конфига не работает, но не надо обобщать это до «у Яндекса нет DoH»: https://safe.dot.dns.yandex.net/dns-query проверен на живом Shadowrocket и работает как Yandex Safe DoH.
DNS
dns-server = quic://dns11.quad9.net, tls://common.dot.dns.yandex.net
fallback-dns-server = https://dns.google/dns-query#proxy, https://cloudflare-dns.com/dns-query#proxy, system
Несколько серверов в dns-server опрашиваются параллельно, побеждает быстрейший — это гонка, а не резерв, порядок в строке ни на что не влияет. Резерв — fallback-dns-server. Поэтому нельзя подмешивать сюда фильтрующий резолвер: dns.comss.one отдаёт 0.0.0.0 на mc.yandex.ru, и Метрика отваливалась бы через раз.
Собственный DNS-трафик Shadowrocket не проходит через [Rule] — его делает процесс туннеля напрямую. Правила вида DOMAIN-SUFFIX,quad9.net,DIRECT на резолвер не влияют, они про трафик приложений. Единственное, что уводит DNS в прокси, — суффикс #proxy; в основных конфигах он стоит на Google и Cloudflare, а в simple-dns.conf — на NextDNS для Instagram/Meta и fallback.
Замерено на живом клиенте 17.07.2026: Яндекс и Quad9 примерно равны, медиана 62 и 73 мс, Яндекс выигрывает около 70% гонок. Разница в пределах шума и плавает во времени — назначать «быстрейшего» руками не нужно и вредно, гонка сама подстраивается под сеть на каждом запросе.
Осторожно с методикой: в DNS-логе фиксируется только победитель гонки, поэтому по логу нельзя судить о скорости проигравшего — его замеров там просто нет. На этом легко построить ложный вывод (я построил: сравнил прогретый Quad9 с холодным Яндексом и получил «80:0», которое не воспроизвелось).
Вариант dns11, а не dns.quad9.net, взят из-за ECS: он сообщает подсеть клиента, и CDN отдаёт ближний edge. Ценой скорости — точка anycast у dns11 с этой машины медленнее. Малварь он фильтрует, но Метрику, GTM, Ads и Stape не трогает — проверено. AdGuard из основных убран: за 80 гонок не выиграл ни одной.
Синтаксис: tls:// (DoT), quic:// (DoQ), h3:// (DoH3), https:// (DoH).
Split DNS в simple-dns.conf
По умолчанию проксируемый домен получает fake-IP и резолвится на удалённой стороне прокси. Одной записи domain = server:... в [Host] недостаточно. Текущая схема на Shadowrocket build 3378 состоит из трёх частей:
[General]
always-ip-address = true
use-local-host-item-for-proxy = true
[Rule]
AND,((DOMAIN-SUFFIX,example.com),(IP-CIDR,0.0.0.0/0)),PROXY
[Host]
example.com = server:https://resolver.example/dns-query
*.example.com = server:https://resolver.example/dns-query
always-ip-address = true принудительно резолвит все домены локально, а use-local-host-item-for-proxy = true передаёт прокси полученный IP вместо hostname. Это влияет на все домены и может ухудшить выбор CDN, зато удалённый прокси не должен повторять DNS-запрос своим резолвером. IP-CIDR внутри logical rule сохраняет внешнюю политику PROXY. Добавлять сервисный домен в skip-proxy нельзя: Shadowrocket превращает такую запись в DOMAIN-SUFFIX,...,DIRECT.
fallback-dns-server глобален и срабатывает при ошибке или таймауте основного/назначенного DNS примерно через две секунды. Пустое значение также означает system; отдельного fallback для конкретной записи [Host] нет. В simple-dns.conf fallback назначен тому же NextDNS #proxy, поэтому Instagram/Meta не переходят на другой резолвер. Цена решения: при недоступности NextDNS резерв также не ответит.
Эксперименты Gemini с Xbox DNS и Comss удалены из конфига. Один глобальный fallback не может одновременно обслуживать Instagram через NextDNS и Gemini через Smart DNS, а внешние прокси повторно резолвят hostname собственным DNS. В регионе с белыми списками DIRECT также непригоден. Если Gemini понадобится снова, ему нужен отдельный конфиг.
Instagram/Meta использует NextDNS-профиль dc5494#proxy для семейств instagram.com, cdninstagram.com, facebook.com и fbcdn.net. Живой лог 08.08.2026 показал корень обхода: NextDNS вернул 0.0.0.0 для i.instagram.com, затем WireGuard/VLESS повторно разрешил hostname своим DNS в настоящий 57.144.222.192 и подключился. Поэтому always-real-ip только для Meta оказался недостаточен и заменён глобальным always-ip-address = true.
Критерий живой проверки: после ответа NextDNS 0.0.0.0 не должно быть последующего lwip lookup result или proxy lookup host с тем же Meta-hostname и настоящим IP. Незаблокированный Meta-домен должен получить IP от NextDNS и уйти по правилу PROXY; DIRECT для Meta недопустим.
