# Docker: образ mtproxy_checker ## Что внутри образа | Компонент | Описание | |-----------|----------| | **База runtime** | Alpine Linux 3.20 | | **Бинарники** | CLI: `/usr/local/bin/mtproxy_checker`. HTTP-сервис списка: `/usr/local/bin/mtproxy_checkerd` (см. ниже) | | **Сертификаты** | Пакет `ca-certificates` (для TLS к реальным хостам при необходимости) | | **ENTRYPOINT** | Скрипт `docker-entrypoint.sh`: **есть аргументы** после имени образа → `mtproxy_checker` (разовая проверка); **нет аргументов** → `mtproxy_checkerd` (HTTP API) | | **Порты** | В режиме CLI ничего не слушает (только исходящий TCP). В режиме API слушает **8080** (или `MTPROXY_HTTP_ADDR`). Образ объявляет **EXPOSE 8080** | `docker run … ИМЯ_ОБРАЗА tg://…` или с флагами — одна проверка, **код выхода** `0`…`4`. `docker run … ИМЯ_ОБРАЗА` **без** command — долгоживущий API (нужны volume/env к файлу со списком). ## HTTP API: `mtproxy_checkerd` Лёгкий сервер на стандартной библиотеке Go: читает файл со списком `tg://` (как в разделе «файл — одна ссылка на строку»), **сразу после старта** выполняет первый цикл проверок, затем повторяет с интервалом. Результаты последнего цикла отдаются по HTTP в JSON. ### Запуск контейнера в режиме API **Без** `command` / аргументов после образа entrypoint сам запускает `mtproxy_checkerd`. **Linux / macOS:** ```bash docker run -d --name mtproxy-api --restart unless-stopped -p 8080:8080 \ -v /path/to/proxies.txt:/data/proxies.txt:ro \ -e MTPROXY_CHECK_INTERVAL=60m \ mtproxy_checker:local ``` **Windows (PowerShell):** ```powershell docker run -d --name mtproxy-api --restart unless-stopped -p 8080:8080 ` -v "${PWD}/proxies.txt:/data/proxies.txt:ro" ` -e MTPROXY_CHECK_INTERVAL=60m ` mtproxy_checker:local ``` Явно указать демон (редко нужно): `--entrypoint /usr/local/bin/mtproxy_checkerd`. Готовый пример: **`docker-compose.api.yaml`** — `docker compose -f docker-compose.api.yaml up --build`. ### Переменные окружения | Переменная | По умолчанию | Назначение | |------------|--------------|------------| | `MTPROXY_LIST_FILE` | `/data/proxies.txt` | Путь к файлу: одна `tg://` ссылка на строку; пустые строки и строки с `#` в начале пропускаются | | `MTPROXY_CHECK_INTERVAL` | `5m` | Интервал между циклами (`time.ParseDuration`, например `5m`, `1h`) | | `MTPROXY_HTTP_ADDR` | `:8080` | Адрес прослушивания HTTP | | `MTPROXY_CHECK_TIMEOUT` | `45s` (в демоне по умолчанию; CLI по-прежнему `15s` если не задано) | Таймаут **одной** попытки к выбранному DC; для `MTPROXY_PROBE=deep` нужен запас (TLS + drain + ответ DC). Если задан `MTPROXY_DC_IDS` с несколькими DC, общий бюджет цикла на строку ≈ `таймаут × число_DC` | | `MTPROXY_PROBE` | *(пусто)* → **`fast`** | `fast` — рукопожатие + init + короткое ожидание как в Telethon TcpMTProxy (#1134): OK, если прокси **не** рвёт TCP сразу после init (входящие байты не обязательны); после основного 2s-окна — ещё несколько коротких `Read`, чтобы поймать **отложенный** FIN/RST (код выхода `3`, как в `deep`). `deep` — после init отправляется `req_pq`, OK если от DC пришло **любое** валидное unencrypted MTProto-сообщение (туннель реально несёт ответ DC; ICMP «ping» до DC через MTProxy невозможен). `deep-strict` — как раньше: ответ должен быть именно `resPQ` | | `MTPROXY_DC_ID` | `2` | Один DC id (аналог `-dc-id` CLI), **игнорируется**, если задан непустой `MTPROXY_DC_IDS` | | `MTPROXY_DC_IDS` | *(пусто)* | Список DC через запятую, например `1,2,3,4,5`: для каждой строки прокси выполняется отдельная проверка на каждый DC **по очереди**. В JSON у записи появляется массив `dcs[]` с результатом по каждому DC. Поле `ok` у строки — **true, если хотя бы один DC прошёл** (как при «есть живой путь к Telegram» при переборе DC) | | `MTPROXY_ALLOWED_IPS` | *(не задана)* | Если задана непустая строка — доступ к **всем** маршрутам только с перечисленных IP/CIDR; остальные получают **403** и JSON `{"error":"forbidden"}`. Формат: через запятую, пробелы допускаются: `192.168.1.10`, `10.0.0.0/8`, IPv6 и CIDR вида `2001:db8::/32`. Учитывается только **`RemoteAddr`** TCP-соединения; заголовок `X-Forwarded-For` **не** используется | | `MTPROXY_TDLIB_HELPER` | *(пусто)* | Путь к исполняемому **внешнему** helper: один аргумент — строка `tg://` с прокси. В stdout — строка JSON `{"ok":bool,"error":"…","exit_code":int}`. Рекомендуется нативный `tdlib_ping` (см. `cmd/tdlib_ping/README.md`); при необходимости — обёртка `exec node …/ping.js` из `contrib/tdlib-ping`. Запускается **только** при `MTPROXY_PROBE=fast` (или пусто). Без TDLib в образе по умолчанию | | `MTPROXY_TDLIB_TIMEOUT` | `45s` | Таймаут **одного** вызова helper на строку прокси | ### HTTP | Метод | Путь | Ответ | |--------|------|--------| | GET | `/health` | `200`, `{"status":"ok"}` | | GET | `/api/v1/proxies` | `200`, JSON с полями `cycle_finished_at`, `next_check_after`, массив `proxies` | Элемент `proxies[]`: поля **`standard`** (наша проверка: `ok`, `exit_code`, `error`, `parse_error`) и **`tdlib`** (результат helper: `ran`, при пропуске — `skipped_reason`; при запуске — `ok`, `exit_code`, `error`, `duration_ms`). Для совместимости дублируются корневые `ok`, `exit_code`, `error`, `parse_error` — те же значения, что и в `standard`. `raw_line`, при успешном разборе — `url`, `checked_at`. Смысл кодов в `standard`: при `MTPROXY_PROBE=fast` — рукопожатие + init (+ ловля отложенного закрытия); при **`deep`** / **`deep-strict`** — см. описание `MTPROXY_PROBE`. Если задан `MTPROXY_DC_IDS` с **несколькими** DC — массив `dcs`; корневой `ok` true, если **хотя бы один** DC успешен. ### Whitelist IP и Docker Процесс видит **IP источника TCP** таким, каким его передал стек в контейнер. При пробросе порта с хоста (`-p 8080:8080`) на Linux часто это адрес **шлюза bridge** или **userland-proxy**, а не «настоящий» IP клиента с хоста. Имеет смысл задавать **CIDR подсети Docker** (например `172.17.0.0/16`), слушать только внутреннюю сеть, использовать **`--network host`** (на Linux) или ограничивать доступ на **обратном прокси** (nginx `allow` и т.п.). ## Требования - [Docker Engine](https://docs.docker.com/engine/install/) (Linux) или Docker Desktop (Windows/macOS). - Для `docker pull` из приватного registry — логин и токен. ## Сборка образа локально **Linux / macOS:** ```bash cd /path/to/mtproxy_checker docker build -t mtproxy_checker:local . ``` **Windows (PowerShell):** ```powershell cd C:\path\to\mtproxy_checker docker build -t mtproxy_checker:local . ``` Проверка, что образ собран (нужен хотя бы один аргумент, иначе стартует API): ```bash docker run --rm mtproxy_checker:local -timeout 5s 2>&1 | head -1 # ожидается сообщение usage CLI (код выхода 2 — нормально без URL) ``` ## Запуск под Linux ### Один прокси (ссылка `tg://`) ```bash docker run --rm mtproxy_checker:local \ 'tg://proxy?server=example.com&port=443&secret=eeYOURHEX...' ``` С флагами утилиты (таймаут, DC): ```bash docker run --rm mtproxy_checker:local \ -timeout 25s -dc-id 2 \ 'tg://proxy?server=example.com&port=443&secret=eeYOURHEX...' ``` Чтобы не светить секрет в истории shell, прочитайте его из файла и передайте флагами (аргументы уходят в CLI через `docker-entrypoint.sh`): ```bash SECRET=$(tr -d ' \n' < secret.hex) docker run --rm mtproxy_checker:local \ --server example.com --port 443 --secret "$SECRET" ``` ### Сеть и DNS на Linux - По умолчанию контейнер использует bridge Docker; нужен исходящий доступ к `server:port` и рабочий DNS. - Если имена не резолвятся: ```bash docker run --rm --dns 8.8.8.8 mtproxy_checker:local 'tg://proxy?...' ``` - Режим сети хоста (как на машине без NAT; используйте осознанно): ```bash docker run --rm --network host mtproxy_checker:local 'tg://proxy?...' ``` На Docker Desktop для Windows/macOS `--network host` ведёт себя иначе, чем на Linux. ## Несколько MTProxy ссылок подряд Утилита принимает **ровно один** прокси за запуск. Чтобы проверить много `tg://` ссылок, обходите их в оболочке. ### Вариант 1: цикл в bash ```bash IMAGE=mtproxy_checker:local # или registry/owner/mtproxy_checker:latest for url in \ 'tg://proxy?server=a.example.com&port=443&secret=ee1111...' \ 'tg://proxy?server=b.example.com&port=8443&secret=ee2222...' \ 'tg://proxy?server=c.example.com&port=443&secret=dd3333...' do echo "=== $url ===" if docker run --rm "$IMAGE" -timeout 20s "$url"; then echo "OK" else echo "FAIL (код $?)" fi done ``` ### Вариант 2: файл — одна ссылка на строку `proxies.txt` (пустые строки и строки с `#` в начале можно пропускать): ``` tg://proxy?server=one.example.com&port=443&secret=ee... tg://proxy?server=two.example.com&port=8443&secret=ee... ``` Скрипт: ```bash IMAGE=mtproxy_checker:local while IFS= read -r line || [ -n "$line" ]; do line="${line#"${line%%[![:space:]]*}"}" # trim leading spaces [ -z "$line" ] && continue case "$line" in \#*) continue ;; esac echo "=== $line ===" docker run --rm "$IMAGE" -timeout 20s "$line" && echo OK || echo "FAIL ($?)" done < proxies.txt ``` ### Вариант 3: параллельно (`xargs`) Осторожно: одновременно много контейнеров нагружают сеть и целевые прокси. ```bash IMAGE=mtproxy_checker:local grep -v '^\s*$\|^\s*#' proxies.txt | xargs -I{} -P 4 sh -c \ 'echo "=== {}"; docker run --rm '"$IMAGE"' -timeout 20s "{}" || echo FAIL' ``` `-P 4` — не более четырёх проверок одновременно; уменьшите при необходимости. ### Вариант 4: итоговый код для CI Если нужен «провал всего job при любом FAIL»: ```bash set -e IMAGE=mtproxy_checker:local while IFS= read -r url; do [ -z "$url" ] && continue case "$url" in \#*) continue ;; esac docker run --rm "$IMAGE" -timeout 25s "$url" done < proxies.txt ``` ## Запуск из PowerShell (Windows) Одинарные кавычки с `tg://` в PowerShell неудобны; проще двойные: ```powershell docker run --rm mtproxy_checker:local ` "tg://proxy?server=example.com&port=443&secret=eeYOURHEX..." ``` Несколько ссылок: ```powershell $urls = @( "tg://proxy?server=a.com&port=443&secret=ee...", "tg://proxy?server=b.com&port=8443&secret=ee..." ) foreach ($u in $urls) { Write-Host "=== $u ===" docker run --rm mtproxy_checker:local -timeout 20s $u if ($LASTEXITCODE -ne 0) { Write-Host "FAIL exit $LASTEXITCODE" } } ``` ## Загрузка образа из Container Registry Gitea 1. Включите **Packages / Container Registry** на инстансе Gitea ([документация](https://docs.gitea.com/usage/packages/container)). 2. Создайте токен с правом на пакеты. 3. Логин: ```bash docker login git.shts.su # или ваш registry ``` 4. Сборка workflow публикует теги `latest`, имя ветки/тега и `sha-…` — см. `.gitea/workflows/docker.yaml`. ```bash docker pull git.shts.su/owner/mtproxy_checker:latest docker run --rm git.shts.su/owner/mtproxy_checker:latest 'tg://proxy?...' ``` ## Пример docker-compose Одна проверка за `compose run`: ```yaml services: mtproxy-check: image: mtproxy_checker:local build: . command: ['tg://proxy?server=example.com&port=443&secret=ee...'] ``` ```bash docker compose run --rm mtproxy-check ``` Для списка прокси удобнее внешний bash-цикл или отдельный `command: ["sh","-c","..."]` с циклом по файлу, смонтированному в volume. ## Использование в CI другого проекта ```bash docker run --rm registry.example.com/owner/mtproxy_checker:latest \ "tg://proxy?server=${MTPROXY_HOST}&port=${MTPROXY_PORT}&secret=${MTPROXY_SECRET}" ``` ## Коды выхода и диагностика | Код | Значение | |-----|----------| | 0 | **`fast`**: Fake-TLS/dd + MTProxy init, прокси не закрыл соединение сразу (как Telethon). **`deep`**: плюс ответ DC после `req_pq` (валидное unencrypted MTProto). **`deep-strict`**: ответ должен быть `resPQ` | | 1 | Ошибка (сеть, протокол; в **`deep`** — нет ответа DC; в **`deep-strict`** — нет `resPQ`) | | 2 | Неверные аргументы CLI | | 3 | Прокси закрыл TCP сразу после начального payload | | 4 | Общий таймаут (`-timeout`) | **Не путать с ping:** ICMP не используется. Типичные сообщения: - `connection refused` — порт закрыт или фильтр. - `FAIL: timeout` — нет ответа за `-timeout`. - `no reply from telegram DC through proxy (timeout)` — в режиме **`deep`** за время ожидания не получено ни одного валидного unencrypted-ответа DC после `req_pq`. - `no resPQ from telegram through proxy (tunnel may be broken)` — только в **`deep-strict`**: ответ не распознан как `resPQ`. - Ошибки чтения/проверки ServerHello — несовместимый ответ или обрыв соединения. Для отладки без `--rm` можно посмотреть логи контейнера по id; с `--rm` контейнер удаляется сразу после выхода.