Opera Proxy — справочник для zapret-gui
SkillProductivityComplete reference for Opera Proxy in the zapret-gui project (Keenetic routers on Entware / OpenWrt / Linux): a standalone Opera VPN (SurfEasy) client that sets up a local HTTP or SOCKS5 proxy. Use for any tasks about: opera-proxy CLI flags (-country/-bind-address/-socks-mode/-proxy-bypa
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Opera Proxy — справочник для zapret-gui skill
What this skill tells your AI
The instructions your AI receives, as published by avatardd/zapret-gui in .claude/skills/opera-proxy/SKILL.md and read by ahel’s review.
Единый источник истины о том, как работает opera-proxy и как с ним
обращаться в zapret-gui. Читать перед тем, как трогать менеджер, watchdog,
установку бинарника или объяснять «почему прокси не проксирует».
Источники истины (в порядке убывания авторитета):
- Сам бинарник.
opera-proxy -h— единственный достоверный список флагов и дефолтов для КОНКРЕТНОЙ версии;opera-proxy -versionпечатает ровно тег релиза (v1.28.0). Всё в §3 сверено с выводом-hреальной сборкиopera-proxy.linux-amd64v1.28.0. - Alexey71/opera-proxy (MIT) — исходники и релизы, откуда мы ставим бинарник. Апстрим релизится часто (раз в 1–2 недели), флаги между версиями добавляются.
- Наш код —
core/opera_proxy_manager.py(жизненный цикл, валидация, детект, страны, лог),core/opera_proxy_watchdog.py(проба и рестарт),api/opera_proxy.py(REST),web/js/pages/opera_proxy.js(страница),core/ext_binary_installer.py(BINARIES["opera"]),core/cli.py(zapret-gui opera),core/config_manager.py(секцияopera_proxy),app.py(автозапуск при boot),core/tunnel_monitor.py,core/update_checker.py,core/selfcheck.py.
⚠️ Главное, что путает при отладке: живой процесс ≠ работающий прокси. opera-proxy сначала регистрируется в API SurfEasy и только потом открывает слушателя. Без доступа к API он бесконечно ретраится, процесс висит, порт закрыт. Поэтому статус отдаёт
listeningотдельно отrunning(§8), а причина всегда в «Логе» (§7).
1. Что это такое и чем НЕ является
Что. Standalone-клиент Opera VPN. Использует ту же инфраструктуру SurfEasy, что встроенный VPN в браузере Opera: клиент сам регистрирует анонимное устройство в API и получает доступ к прокси-узлам. Аккаунта, подписки и своего сервера не нужно — отсюда роль «бесплатный запасной канал».
Чем НЕ является:
- Не туннель и не метод единого слоя маршрутизации. Это локальный
HTTP/SOCKS5-прокси на порту. Он ничего не заворачивает прозрачно: трафик
пойдёт через него, только если приложение/браузер настроены на этот порт
либо кто-то снаружи (redsocks/tproxy) направил соединения туда.
В
config_manager.routing.tunnel_priorityopera намеренно отсутствует, аauto_remediationего не выбирает —UnifiedRoute(method="opera:…")не существует, это закреплено тестомtests/test_dev_merge_regressions.py::test_opera_not_in_default_priority. - Не приватный VPN. Инфраструктура чужая, вход анонимный и бесплатный: скорость/доступность не гарантированы, для чувствительного трафика — свой сервер (sing-box / AmneziaWG).
- Не «настроил и забыл» на уровне протокола. Логин обновляется каждые 4 ч
(
-refresh), при потере API-доступа прокси перестаёт работать.
2. Цепочка подключения (что происходит на самом деле)
opera-proxy -country EU -bind-address 127.0.0.1:18080
│
├─1. bootstrap-DNS: резолв api2.sec-tunnel.com через ВСТРОЕННЫЙ список
│ DoH/DoT-резолверов (НЕ через DNS роутера!) — см. -bootstrap-dns
├─2. POST https://api2.sec-tunnel.com/v4/register_subscriber
│ — анонимная регистрация (login/password/client-type зашиты)
├─3. регистрация устройства → получение proxy-credentials
├─4. discover: список прокси-узлов для выбранной страны
├─5. server-selection (по умолчанию fastest → замер скачивания)
└─6. ТОЛЬКО ТЕПЕРЬ открывается слушатель на bind-address
│
└─ клиентское соединение → CONNECT на <cc>0.sec-tunnel.com:443
по TLS, Proxy-Authorization: Basic <device-creds>
Ключевые следствия, которые объясняют 90 % жалоб:
| Факт | Что из него следует |
|---|---|
| Порт открывается после успешной инициализации | «Процесс есть, порт закрыт» — это не наш баг, а незавершённая регистрация |
| Резолв идёт через DoH-пул, а не системный DNS | Провайдер/роутер режет DoH (443 на резолверы) → клиент не стартует вовсе. Лечится -bootstrap-dns dns://127.0.0.1:53 |
-init-retries по умолчанию 0 = бесконечно, интервал 5 с | Клиент не падает и не сдаётся: он будет ретраиться вечно. Именно поэтому наши таймауты обязаны быть жёсткими (§6) |
Узлы — eu0/as0/am0.sec-tunnel.com:443, обычный TLS+CONNECT | Если DPI режет по SNI — помогает -fake-SNI; блок по IP узлов не лечится ничем из наших настроек |
| Логин обновляется каждые 4 ч | Долгоживущий процесс может «умереть» логически, оставаясь живым процессом → watchdog по TCP-пробе (§9) |
3. CLI-флаги (сверено с -h v1.28.0)
3.1. Те, что передаём мы
Порядок и условия — OperaProxyManager.start():
opera-proxy -country <CC> -bind-address <host:port>
[-socks-mode] [-proxy-bypass <list>] [-fake-SNI <domain>]
-verbosity <N>
| Флаг | Дефолт бинарника | Что делает |
|---|---|---|
-country | EU | Регион. Для -list-proxies-all допускает список через запятую и ALL |
-bind-address | 127.0.0.1:18080 | Адрес слушателя |
-socks-mode | выкл | SOCKS вместо HTTP на том же порту |
-proxy-bypass | пусто | Список через запятую: хосты/URL-паттерны мимо прокси, регистр не важен, поддерживает * в именах хостов |
-fake-SNI | пусто | Домен, который подставляется как SNI в исходящий TLS и в туннелируемый ClientHello, где это возможно — то есть маскирует не только связь с SurfEasy |
-verbosity | 20 | 10 debug, 20 info, 30 warning, 40 error, 50 critical, 60 silent |
В GUI-селекторе
verbosityнет уровня 50 — это осознанно (шкала 10/20/30/40/60), ноvalidate_settingsпропускает любой int 0…60, так что 50 через API задать можно.
3.2. Служебные, которые вызываем отдельно
| Флаг | Где используем |
|---|---|
-version | _get_version() — печатает ровно тег (v1.28.0), на этом держится сравнение «уже актуальная версия» в установщике и has_update в «Обновлениях» |
-list-countries | _fetch_countries() — сетевая операция, см. §6 |
3.3. Что бинарник умеет, а GUI не показывает
Знать полезно: это готовые обходные пути, когда «не работает».
| Флаг | Зачем |
|---|---|
-bootstrap-dns | Список резолверов для поиска API. Дефолт — 9 публичных DoH (1.1.1.3, 8.8.8.8, dns.google, security.cloudflare-dns.com, dns.quad9.net, dns.adguard-dns.com, wikimedia-dns.org, doh.cleanbrowsing.org, fidelity.vm-0.com). Схемы: dns://, https://, tls://, tcp:// |
-api-proxy, -api-proxy-file, -api-proxy-list-url, -api-proxy-parallel | Достучаться до API SurfEasy через сторонний прокси, когда api2.sec-tunnel.com недоступен. Кандидаты перебираются по порядку |
-proxy | Базовый прокси для ВСЕХ исходящих (http/https/socks5/socks5h://[user:pass@]host:port) |
-server-selection | first / random / fastest (дефолт). fastest перед стартом качает тестовый файл с каждого узла — на слабом канале это заметная задержка старта; таймаут отбора — 1 мин |
-timeout | Таймаут сетевых операций, дефолт 30s |
-refresh, -refresh-retry | Интервал обновления логина (4 ч) и повтора (5 с) |
-init-retries, -init-retry-interval | Попытки инициализации (0 = бесконечно) и пауза (5 с) |
-override-proxy-address, -discover-csv, -list-proxies, -list-proxies-all[-out], -proxy-blacklist, -sort-proxies-by, -estimate-proxy-speed | Ручная работа со списком узлов в обход discover |
-cafile | Свой CA-бандл |
-config | Читать конфигурацию из файла «ключ значение» |
-api-login/-api-password/-api-client-type/-api-client-version/-api-user-agent | Подмена зашитых учёток и «маскировки под Opera» |
При добавлении нового флага в GUI: сначала проверь
-hна той версии, которую реально ставит установщик. Флаг, которого нет, роняет старт целиком (Go печатает usage и выходит с кодом 2) — старт вернёт ошибку, а причина будет в буфере «Лог».
4. Наши настройки (config_manager, секция opera_proxy)
| Ключ | Дефолт | Комментарий |
|---|---|---|
enabled | false | «Opera должна работать». Выставляется при успешном up (API и CLI), снимается при down. Гейт для boot-автозапуска и watchdog'а |
autostart | false | Подъём после перезагрузки и watchdog — одна галка в GUI |
country | EU | EU / AS / AM (список стран см. §6) |
bind | 127.0.0.1:18080 | 0.0.0.0:… — отдать в LAN; IPv6 — [::1]:… |
socks_mode | false | |
proxy_bypass | "" | |
fake_sni | "" | |
verbosity | 20 | |
debug_log | false | Глубина буфера «Лог»: 60 → 600 строк. Применяется со следующего запуска |
installed_tag / installed_arch | "" | Исторические ключи, сейчас никем не пишутся |
Единый источник параметров запуска — start_kwargs_from_config(). Все три
пути старта (API up, boot-автозапуск в app.py, рестарт watchdog'ом) и CLI
обязаны ходить через него. Раньше они расходились, и после перезагрузки прокси
поднимался без fake_sni/proxy_bypass/verbosity, а CLI вообще стартовал с
дефолтами — это чинилось дважды, не отменяй.
Настройки применяются только при следующем запуске. PUT /config живой
процесс не трогает: после «Сохранить» нужен «Остановить» → «Запустить».
5. Валидация (parse_bind, validate_settings)
Обе функции — в core/opera_proxy_manager.py, и они общие для API, старта,
watchdog-пробы и монитора. Не заводи локальный разбор адреса.
parse_bind(bind) -> (host, port) понимает 127.0.0.1:18080, localhost:8080,
[::1]:18080, [::]:18080; кидает ValueError с человекочитаемой причиной на
пустом адресе, адресе без порта, порте вне 1…65535 и на голом IPv6 без скобок.
validate_settings(dict) -> dict нормализует только переданные ключи (частичный
PUT): страна → upper и isalnum, bind → канонизируется через parse_bind,
bool-поля принимают true/1/yes/on, proxy_bypass — пробелы вокруг элементов
вычищаются (а пробел внутри записи это ошибка), fake_sni — только
[A-Za-z0-9.-_], verbosity — целое 0…60. Ошибка → ValueError, API
отвечает HTTP 400 с текстом.
start() прогоняет ту же валидацию перед Popen: в settings.json мог остаться
мусор с тех времён, когда валидации не было.
6. Страны: почему это дорого и как устроен кэш
-list-countries — не локальная команда. Бинарник ради неё проходит шаги
1–4 из §2, то есть регистрирует новое устройство в API SurfEasy.
Поэтому:
detect()никогда не вызывает-list-countriesсам. Он дешёвый: версия берётся из кэша по(path, mtime, size), страны — из памяти.- Реальный запрос делает только
list_countries(refresh=True)→GET /api/opera-proxy/countries?refresh=1(кнопка «Обновить список стран»). - Анти-дребезг
_COUNTRIES_MIN_REFRESH_SEC = 60+ неблокирующий лок: два клика или две вкладки не породят две регистрации. - Таймаут
_COUNTRIES_TIMEOUT = 30секунд. Он обязан быть, потому что при недоступном API бинарник ретраится бесконечно (§2). Истёк → отдаём «opera-proxy не ответил за 30s — нет доступа к API SurfEasy?». - Формат вывода — CSV с заголовком
country code,country name; парсер пропускает строки, начинающиеся сcountry.
Историческая ошибка, которую нельзя повторить:
detect()вызывался из общего поллинга страницы (раз в 3 с) — получалась регистрация устройства каждые три секунды плюс два форка на тик на роутерном CPU. Поллинг GUI тянет только/status;detectиconfig— по открытию страницы и по кнопке «Обновить».
Базовые регионы: EU (Europe), AS (Asia), AM (Americas).
7. Менеджер: процесс, pid-файл, лог
core/opera_proxy_manager.py, singleton get_opera_proxy_manager().
Поиск бинарника (_find_binary, первый существующий и исполняемый):
/opt/usr/bin/opera-proxy → /opt/bin/opera-proxy →
/usr/local/bin/opera-proxy → /usr/bin/opera-proxy.
Старт. Popen(..., stdout=PIPE, stderr=STDOUT, start_new_session=True),
затем sleep(1) и проверка poll(): мгновенные падения — это занятый порт и
негодные аргументы. PID пишется в <config_dir>/opera-proxy.pid.
Дренаж stdout — обязателен. Поток opera-proxy-drain непрерывно читает
пайп в кольцевой буфер. Без него OS-буфер (~64 КБ) переполняется, Go-шный
логгер блокируется на write() вместе с обработчиком соединения, и прокси
перестаёт форвардить трафик. Это не теория: регресс-тест
TestProxying::test_traffic_survives_chatty_logging гоняет 512 КБ через
CONNECT при -verbosity 10 и падает по таймауту, если дренаж убрать.
Буфер заводится до Popen, поэтому в «Логе» видна и командная строка, и
причина неудачного старта.
Стоп. Есть объект процесса → SIGTERM, wait(3), иначе kill. Объекта нет
(GUI перезапускали) → гасим по pid-файлу с проверкой _pid_is_opera()
(читаем /proc/<pid>/cmdline; файл лежит в /opt и переживает перезагрузку,
так что чужой PID вполне возможен), затем SIGTERM → до 3 с → SIGKILL.
Лог. read_log(lines) → {ok, debug, captured, log}; глубина 60 строк,
600 в режиме отладки (opera_proxy.debug_log), каждая строка обрезается до
400 символов. Подробность строк задаёт -verbosity самого бинарника, наш
режим отладки влияет только на глубину.
8. Статус: running ≠ listening
status(probe=True) возвращает:
{"running": true, "pid": 1234, "bind": "127.0.0.1:18080", "listening": false}
running— жив ли процесс (объект в памяти или pid-файл +/proc).bind— фактический адрес запущенного процесса (_running_bind), после перезапуска GUI — из конфига.listening— TCP-проба, есть только когдаrunning. Именно это отличает «работает» от «висит в ретраях регистрации» (§2).
GUI пишет «Запущен, но порт не отвечает» и отправляет в «Лог»; CLI —
bind: … (порт не отвечает).
9. Watchdog
core/opera_proxy_watchdog.py. Гейт: enabled И autostart — обе
проверяются и в reconfigure(), и на каждом тике (конфиг могли поменять из
другой вкладки).
Константы: интервал 60 с, порог 3 подряд, cooldown 120 с, не больше 6 рестартов в час.
probe_proxy(bind, timeout) — общая TCP-проба (её же использует status() и
tunnel_monitor): разбирает адрес через parse_bind, ходит через
socket.create_connection (то есть IPv6 работает), wildcard заменяет на
loopback (0.0.0.0 → 127.0.0.1, :: → ::1).
Тик:
enabled/autostartсняты → watchdog сам себя останавливает.- Процесс мёртв → счётчик, на третий раз рестарт.
- Bind не парсится → проба пропускается, счётчик сбрасывается. Иначе был бы вечный цикл «проба провалилась → рестарт → прокси не стартует».
- Проба прошла → счётчик в ноль; не прошла → как п.2.
Рестарт = stop() + start(**start_kwargs_from_config()), то есть watchdog
всегда приводит прокси к тому, что записано в конфиге.
10. Установка бинарника
core/ext_binary_installer.py, BINARIES["opera"].
"repo": "Alexey71/opera-proxy",
"release_tag": "", # пусто = /releases/latest
"pinned_tag": "v1.28.0", # версия, для которой известны sha256
"allow_unpinned": True,
"dest": "/opt/usr/bin/opera-proxy",
"arch_map": {"aarch64": "opera-proxy.linux-arm64",
"x86_64": "opera-proxy.linux-amd64",
"mipsel": "opera-proxy.linux-mipsle",
"mips": "opera-proxy.linux-mips",
"armv7": "opera-proxy.linux-arm"},
Ставим последний релиз. Закреплённый тег давал тупик: «Обновления» видят
новую версию апстрима, а кнопка «Установить» молча ставит старую. Проверка
«уже актуально» работает благодаря тому, что -version печатает ровно тег.
Политика sha256 (общая для всех allow_unpinned-бинарников):
| Ситуация | Поведение |
|---|---|
Тег == pinned_tag, хэш для арх. есть | Сверка, несовпадение → InstallError, установка прервана |
Тег == pinned_tag, хэша для арх. НЕТ | Отказ: это дыра в манифесте, а не «версия новее» |
| Тег новее, у релиза есть файл контрольных сумм | Сверяемся с ним (_verify_downloaded_file) |
| Тег новее, контрольных сумм нет | Ставим, но sha256_verified: false + предупреждение в лог и тост в GUI |
Апстрим сейчас не публикует checksums (checksums.txt, SHA256SUMS,
*.sha256 — 404), так что на практике для версий новее pinned_tag работает
последняя строка. Поднимая pinned_tag, обязательно пересчитай sha256 всех
четырёх сборок с релизных URL и проверь процедуру на предыдущей версии (хэши
старого тега должны совпасть с тем, что уже лежит в манифесте).
Архитектуры. Апстрим публикует ~29 ассетов (linux/darwin/freebsd/android,
включая linux-arm и linux-386); мы маппим пять, которые встречаются на
роутерах: aarch64, x86_64, mipsel, mips, armv7 (opera-proxy.linux-arm,
ELF ARM EABI5). Сборки под riscv64 нет (404) — на ней установка честно
отказывает «Архитектура … не поддерживается». Прежде чем добавлять новую
архитектуру в arch_map, проверь, что ассет реально существует в релизе
(curl -o /dev/null -w '%{http_code}' -L <release-url>/<asset>), и посчитай
его sha256.
11. API (api/opera_proxy.py)
| Метод | Путь | Что делает |
|---|---|---|
| GET | /api/opera-proxy/status | running, pid, bind, listening |
| GET | /api/opera-proxy/detect | Дёшево: installed, binary, version, страны из кэша |
| GET | /api/opera-proxy/countries[?refresh=1] | Без refresh — кэш; с refresh — сетевой запрос (§6) |
| POST | /api/opera-proxy/up | Конфиг + валидированные overrides из тела; при успехе enabled=true и reconfigure() watchdog'а |
| POST | /api/opera-proxy/down | enabled=false, стоп, reconfigure() |
| GET/PUT | /api/opera-proxy/config | Настройки; PUT валидирует (HTTP 400 с текстом) |
| GET/POST | /api/opera-proxy/debug | Флаг debug_log |
| GET | /api/opera-proxy/log?lines=N | Хвост вывода |
| POST | /api/opera-proxy/install | /uninstall | install_binary_by_name("opera") / uninstall_binary("opera") |
Тело запроса читается через _body() — кривой JSON даёт {}, а не HTTP 500.
CLI: zapret-gui opera {status|start|stop} — тот же путь, что у GUI
(настройки из конфига, выставление enabled).
Прочие потребители: /api/dashboard/status (ключ opera),
core/selfcheck.py (наличие и версия), core/update_checker.py::_check_opera
(сравнение с _github_latest), core/tunnel_monitor.py::_read_opera_stats
(порт берётся из настроек, не хардкод; метрики эмулируются из числа
ESTABLISHED-соединений).
12. Диагностика «не работает»
- Бинарник есть?
/api/opera-proxy/detect→installed,version. Кнопка «Запустить» в GUI гасится, пока детект не подтвердит наличие. - Процесс поднялся?
status.running. Нет → смотриerrorотupи «Лог»: там командная строка и причина (занятый порт, негодный флаг). - Порт отвечает?
status.listening.running && !listening— почти всегда незавершённая инициализация:- в «Логе» строки
Attempting action "anonymous registration"/Action … failed— API недоступен; - в ошибке видны
lookup api2.sec-tunnel.com … dns-query …— не работает bootstrap-DoH (провайдер режет DoH, перехват DNS, MITM-сертификат). Обход —-bootstrap-dns dns://<локальный резолвер>(в GUI не вынесено); dial tcp … i/o timeoutна*.sec-tunnel.com— блок самой инфраструктуры SurfEasy; помогает только внешний обход (-api-proxy/-proxy) или другой канал.
- в «Логе» строки
- Порт слушает, но трафик не идёт?
- проверь, что клиент настроен на нужный режим:
socks_modeменяет протокол на том же порту — HTTP-клиент в SOCKS-порт не пойдёт; bind=127.0.0.1виден только самому роутеру: с ноутбука не подключиться, нужен0.0.0.0;proxy_bypassмог увести нужный домен напрямую.
- проверь, что клиент настроен на нужный режим:
- Прокси «зависает» под нагрузкой. Первым делом проверь, что дренаж
stdout жив (§7) — это классика для
verbosity ≤ 20. - Watchdog дёргает исправный прокси. Смотри
bind: проба ходит по адресу из конфига, а процесс мог быть запущен с другим (override вup). - Страны не загружаются. Это отдельная сетевая операция (§6); её отказ не мешает прокси работать — регион задаётся кодом, а не списком.
- «Обновления» показывают новую версию. Кнопка «Обновить до последней версии» на странице; после замены файла работающий процесс остаётся на старой версии — нужен перезапуск.
13. Инварианты — что не ломать
detect()дешёвый. Никаких сетевых вызовов: его дёргают поллинг GUI, selfcheck и update-checker.- Дренаж stdout не отключать и не заменять на «читать по запросу».
- Один источник параметров запуска —
start_kwargs_from_config(). - Один разбор адреса —
parse_bind(); проба — толькоprobe_proxy(). enabledвыставляют все пути старта/остановки (API, CLI), иначе boot-автозапуск и watchdog молча мертвы.- Валидация до записи в конфиг, а не при старте: иначе мусор оседает в
settings.jsonи всплывает usage-дампом Go-бинарника. - Форма настроек не перерисовывается по таймеру (затирает ввод) — рендер
один раз, как в
usque.js. - opera не добавлять в
tunnel_priority/ unified-методы — это не прозрачный метод маршрутизации (§1). - Флаги сверять с
-hцелевой версии перед добавлением в GUI.
Регресс-тесты: tests/test_opera_proxy.py (менеджер, watchdog, API, CLI,
монитор + сквозной прогон трафика через CONNECT-прокси),
tests/test_ext_binary_installer.py::TestOperaLatestRelease (политика
latest/pinned/unpinned).
Signals
- GitHub stars
- 133
- Forks
- 10
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
opera-proxy- Source
- github.com/avatardd/zapret-gui