# Запуск Telemt API Gateway (Docker) Оглавление: 1. [Назначение](#назначение) 2. [Требования](#требования) 3. [Минимальная конфигурация](#минимальная-конфигурация) 4. [Переменные окружения](#переменные-окружения) 5. [Готовый образ из registry](#готовый-образ-из-registry) 6. [Локальная сборка образа](#локальная-сборка-образа) 7. [Запуск через Docker CLI](#запуск-через-docker-cli) 8. [Запуск через Docker Compose](#запуск-через-docker-compose) 9. [Проверка](#проверка) 10. [Обновление и CI/CD](#обновление-и-cicd) 11. [Устранение неполадок](#устранение-неполадок) 12. [Mihomo (external-controller)](#mihomo-external-controller) ## Назначение Шлюз — это один HTTP‑вход для нескольких экземпляров [Telemt Control API](API.md): - **Web UI** (в образе Docker): статика панели на **`GET /`** (и клиентские маршруты SPA), агрегаты и прокси на **`/api/…`**. Тот же порт, что и у API (например `8080`). Исходники UI — каталог [web/](../web/README.md), сборка встроена в [Dockerfile](../Dockerfile) (стадия Node + `embed` в Go). - **Белый список IP** (CIDR): кто может обращаться к шлюзу (кроме `GET /health`, см. ниже). - **Маршрутизация по alias**: клиент вызывает `GET /api/{alias}/health`, шлюз проксирует на `{base_url}/v1/health` у соответствующего сервера. - **Метрики Prometheus**: `GET /metrics` (под тем же правилом whitelist, что и API). - **Доверенные прокси**: если прямой TCP‑peer входит в `trusted_proxies`, для проверки whitelist берётся первый адрес из `X-Forwarded-For` или `X-Real-IP`. Эндпоинты и контракт ответов бэкенда описаны в [API.md](API.md). ## Требования - Установленные [Docker](https://docs.docker.com/get-docker/) и при необходимости [Docker Compose](https://docs.docker.com/compose/) v2. - Для локальной сборки **Docker-образа** из репозитория: Docker сам подтянет [Node](https://nodejs.org/) на стадии сборки фронта и [Go 1.22+](https://go.dev/dl/) на стадии компиляции (см. [Dockerfile](../Dockerfile)). - Для `go test ./...` без Docker на машине нужен только Go 1.22+. ## Минимальная конфигурация Скопируйте [config.example.yaml](../config.example.yaml) в свой `config.yaml` и отредактируйте. Минимальный рабочий фрагмент для **разработки** (без проверки IP): ```yaml listen: ":8080" allow_all: true servers: - alias: main_srv base_url: http://127.0.0.1:9091 ``` Минимальный фрагмент для **продакшена** (только перечисленные сети/хосты): ```yaml listen: ":8080" allow_all: false whitelist_cidrs: - "203.0.113.10/32" - "10.0.0.0/8" servers: - alias: main_srv base_url: http://telemt-internal:9091 ``` Правила: - При `allow_all: false` и **пустом** `whitelist_cidrs` доступ будет **закрыт для всех** (кроме `GET /health`). - В `whitelist_cidrs` и `trusted_proxies` допустимы **CIDR** (`10.0.0.0/8`) и **одиночный IPv4/IPv6** без маски (`87.103.241.8` эквивалентно `87.103.241.8/32`). - `GET /health` на шлюзе **не** проверяется по whitelist — так проще настроить Docker `HEALTHCHECK` и оркестраторы. - Поле `path_prefix` по умолчанию равно `/v1` (префикс Telemt Control API). Опционально для бэкенда с включённым `auth_header` в Telemt задайте в конфиге имя переменной окружения, значение которой будет отправлено как заголовок `Authorization` на этот upstream: ```yaml servers: - alias: main_srv base_url: http://telemt:9091 authorization_env: TELEMT_API_AUTH ``` Значение должно **точно** совпадать с настроенным в Telemt `auth_header` (см. [API.md](API.md)). ### Mihomo (external-controller) Опционально для каждого `servers[]` можно включить проксирование **Mihomo** (Clash Meta) API: в Web UI появится раздел **«Mihomo»** для выбранного alias. - Клиент (браузер) обращается к шлюзу: `GET /api/{alias}/mihomo/proxies`, WebSocket `…/mihomo/traffic` и т.д. - Шлюз проксирует на `{mihomo_base_url}/proxies`, `…/traffic` и т.д. с заголовком `Authorization` из переменной окружения (секрет **не** попадает в фронтенд). - Служебный ответ: `GET /api/{alias}/mihomo/meta` — JSON с полем `controller_base` (без учётных данных) для строки «Подключено к: …» в панели. В **config.yaml** задаются только параметры **шлюза** (`mihomo_base_url` / `mihomo_base_url_env`, `mihomo_authorization_env`). Сервис контейнера Mihomo (`build`, `CLASH_SECRET`, сеть `proxy-net` и т.д.) описывается в вашем Docker Compose отдельно; связка URL и переменных для gateway — ниже и в [docker-compose.yml](../docker-compose.yml). Поля в конфиге: | Поле | Описание | |------|----------| | `mihomo_base_url` | Базовый URL контроллера, например `http://mihomo:9090` (порт контроллера по умолчанию в контейнере Mihomo — **9090**). | | `mihomo_base_url_env` | Имя переменной окружения; если задано и значение **непустое**, URL контроллера берётся из `os.Getenv` при старте (удобно в Docker без хардкода IP). Если env пустой, используется `mihomo_base_url`. | | `mihomo_authorization_env` | Имя env: **полное** значение заголовка `Authorization` (например `Bearer `), как у `authorization_env` для Telemt. Должно совпадать с секретом на стороне Mihomo (`secret` / `CLASH_SECRET` в конфиге ядра). | Правила: - Если указан `mihomo_base_url` или `mihomo_base_url_env`, обязательно задайте `mihomo_authorization_env` и непустые значения в env при старте шлюза. - Контейнер **gateway** должен иметь **сетевую связность** с контроллером Mihomo (лучше одна пользовательская Docker-сеть; имя сервиса `http://mihomo:9090` предпочтительнее статического IP). Публиковать порт **9090** на хост не обязательно: браузер ходит в шлюз, шлюз — в контейнер Mihomo по overlay-сети. - Пример `environment` для compose (секреты не в git — через `.env`): ```yaml services: gateway: environment: MIHOMO_CONTROLLER_URL: http://mihomo:9090 TELEMT_MIHOMO_AUTH: Bearer ${CLASH_SECRET} ``` и в `config.yaml` для нужного сервера: `mihomo_base_url_env: MIHOMO_CONTROLLER_URL`, `mihomo_authorization_env: TELEMT_MIHOMO_AUTH`. За **reverse proxy** (nginx) перед панелью убедитесь, что для WebSocket проксируются заголовки `Upgrade` и `Connection`. Если раздел Mihomo отдаёт **400** и в браузере «Ответ не JSON»: проверьте, что `mihomo_authorization_env` — это **имя** env (например `TELEMT_MIHOMO_AUTH`), а полный заголовок `Bearer …` задан в **environment** контейнера gateway (не в YAML). После обновления шлюза прокси Mihomo использует `Rewrite` и сбрасывает `RequestURI` на исходящем запросе — без этого строгий upstream может отвечать 400. ## Переменные окружения | Переменная | Описание | |----------------|----------| | `CONFIG_PATH` | Путь к YAML внутри контейнера. По умолчанию: `/etc/telemt-gateway/config.yaml`. | | `TELEMT_API_AUTH` | Пример: секрет для `authorization_env` в конфиге (имя может быть любым). | | `MIHOMO_CONTROLLER_URL` | Пример: URL для `mihomo_base_url_env` (если используете в конфиге). | | `TELEMT_MIHOMO_AUTH` | Пример: `Bearer …` для `mihomo_authorization_env` (если используете). | ## Готовый образ из registry CI публикует образ в Container Registry Gitea. Для репозитория `denozord/telemt-api` на `git.shts.su` стабильный тег: ```bash docker pull git.shts.su/denozord/telemt-api:latest ``` Если registry **не публичный**, сначала войдите (логин — пользователь Gitea, пароль — personal access token с правом **`read:package`**): ```bash docker login git.shts.su ``` Другие полезные теги из того же workflow: имя ветки (с `/` заменённым на `-`) и `sha-<12 символов коммита>` — см. раздел [Обновление и CI/CD](#обновление-и-cicd). ## Локальная сборка образа Если нужно собрать образ самостоятельно из клона репозитория: ```bash docker build -t telemt-api-gateway:local . ``` Сборка **многостадийная**: сначала `npm ci` + `npm run build` в каталоге `web/` (в бандл вшивается пустой `PUBLIC_TELEMT_GATEWAY_URL`, запросы API с того же origin), затем компиляция Go со встраиванием `web/build` через `embed`. В командах `docker run` ниже вместо имени из registry подставьте `telemt-api-gateway:local`. ## Запуск через Docker CLI Пример для Linux (подставьте путь к `config.yaml`; ниже — файл из текущего каталога). Используется **готовый** образ: ```bash docker run -d --name telemt-gateway \ -p 8080:8080 \ -v "$(pwd)/config.yaml:/etc/telemt-gateway/config.yaml:ro" \ -e CONFIG_PATH=/etc/telemt-gateway/config.yaml \ git.shts.su/denozord/telemt-api:latest ``` Проверка: ```bash curl -sS -i http://127.0.0.1:8080/health curl -sS -i http://127.0.0.1:8080/api/main_srv/health curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8080/ ``` Второй запрос проксируется на `{base_url}/v1/health` для alias `main_srv`. Третий — HTML панели (в образах, собранных с Web UI; ожидайте `200`). Панель в браузере: `http://127.0.0.1:8080/` (при `allow_all: false` ваш IP должен быть в `whitelist_cidrs`, иначе для `/` будет `403`, как и для API). Остановка и удаление: ```bash docker stop telemt-gateway docker rm telemt-gateway ``` ## Запуск через Docker Compose В репозитории есть [docker-compose.yml](../docker-compose.yml) (один сервис **gateway**) и пример [config.compose.yaml](../config.compose.yaml) с `allow_all: true` и `base_url: http://host.docker.internal:9091` (Telemt на хосте; на Docker Desktop для Linux это обычно работает из коробки). Образ по умолчанию **скачивается** из registry, локальная сборка не требуется. UI доступен на том же порту, что и шлюз: `http://127.0.0.1:8080/`. ```bash docker compose pull docker compose up -d docker compose logs -f gateway docker compose down ``` Чтобы пересобрать образ из исходников вместо pull, в [docker-compose.yml](../docker-compose.yml) временно замените блок `image:` на `build: .` (и нужный тег при необходимости). На Linux без `host.docker.internal` сделайте одно из: - в `docker-compose.yml` для сервиса `gateway` добавьте `extra_hosts: ["host.docker.internal:host-gateway"]` (Docker Engine 20.10+); - либо замените в `config.compose.yaml` значение `base_url` на IP хоста в docker‑bridge (часто `172.17.0.1`) или на имя сервиса Telemt в той же сети compose. ## Проверка | Сценарий | Ожидание | |----------|----------| | `GET /health` | `200`, JSON `{"status":"ok"}` | | `GET /` (образ с Web UI) | `200`, HTML панели | | Разрешённый IP, корректный alias | ответ бэкенда (например `200` для `/v1/health`) | | IP не в whitelist | `403`, JSON с `code: forbidden` | | Неизвестный alias | `404`, JSON с `code: not_found` | | Бэкенд недоступен | `502`, JSON с `code: bad_gateway` | | `GET /metrics` | текст метрик Prometheus (при разрешённом IP) | ## Обновление и CI/CD - **Образ**: подтяните свежий тег (`docker pull git.shts.su/denozord/telemt-api:latest` или `docker compose pull`), пересоздайте контейнер (`docker compose up -d` или новый `docker run` с тем же volume конфига). Локальная пересборка нужна только если вы меняете Dockerfile/код и не пользуетесь CI. Образы, собранные **до** добавления стадии `web/` в Dockerfile, могут отдавать на `/` только заглушку — нужен образ из актуального CI или локальный `docker build`. - **Конфиг**: отредактируйте файл на хосте и перезапустите контейнер (шлюз не перечитывает конфиг на лету). - **Gitea Actions**: workflow [.gitea/workflows/docker.yaml](../.gitea/workflows/docker.yaml) сначала выполняет `go mod tidy && go test ./...`, затем собирает образ через Buildx по [Dockerfile](../Dockerfile) (стадии Node для `web/` и Go) и пушит в Container Registry Gitea. - В репозитории должен быть secret **`ACTIONS_PAT`** — personal access token пользователя с правом **`write:package`** (и при необходимости `read:package`), как для обычного `docker login` к registry. - Логин в registry: пользователь **`gitea.actor`** (кто запустил workflow), пароль — этот PAT. - Хост registry задаётся в workflow в `env.REGISTRY` (по умолчанию `git.shts.su`); при другом инстансе Gitea измените значение в `.gitea/workflows/docker.yaml`. - Теги образа: `latest`, имя ветки/тега (с `/` заменённым на `-`), и `sha-<первые 12 символов коммита>`. Полный путь: `{REGISTRY}/{gitea.repository}:<тег>`. ## Устранение неполадок - **Nginx с `location /api/` и `proxy_pass http://…:9091/;` (со слэшем в конце)** на бэкенд уходит путь **без** префикса `/api/` (например запрос к nginx `GET /api/v1/users` превращается в `GET /v1/users` на Telemt). Шлюз при `base_url: https://gt2.example/api/` должен запрашивать именно **`/api/v1/…`** на стороне nginx. Если в `base_url` нет пути `/api/` (только `https://gt2.example`), шлюз обратится к `https://gt2.example/v1/…` — часто это **не** попадает в `location /api/`, и nginx отдаёт **чужой vhost / заглушку**. Задавайте `base_url` с завершающим слэшем: `https://gt2.example/api/`. - **Заголовок `Host`**: шлюз выставляет `Host` равным хосту из `base_url` (как у обычного клиента к этому имени). Если после обновления образа проблема остаётся, с хоста шлюза проверьте: `curl -sv -o /dev/null https://gt2…/api/v1/health` и сравните с запросом через шлюз. - **`400` на `/api/{alias}/…` при локальном `base_url` (например `http://172.20.0.3:9091`), хотя `curl` к `:9091/v1/…` даёт `200`**: частая причина — **несовпадение заголовка `Host`**: браузер шлёт `Host: публичное_имя:8888`, а при прямом `curl` к IP в `Host` попадает `172.20.0.3:9091`. Строгий upstream (часто hyper/Rust) отвечает `400`, если `Host` не совпадает с ожидаемым authority. Шлюз при проксировании **не пересылает** клиентский `Host` и выставляет authority из `base_url` (как серверные запросы агрегатора). Убедитесь также, что в YAML **нет пробела/переноса** в конце `base_url` (поля обрезаются `TrimSpace`), и в URL нет лишнего `/api//…` (путь под `/api` нормализуется). - **Список пользователей через шлюз**: запрос **`GET` или `HEAD`** на **`/api/{alias}/users`** шлюз перенаправляет на upstream **`GET/HEAD /v1/stats/users`** (как и агрегатор). Так совместимы сборки Telemt, где прямой **`GET /v1/users`** даёт ошибку (например `400`), а **`/v1/stats/users`** работает. **`POST /api/{alias}/users`** (создание) и **`GET /api/{alias}/users/{username}`** по-прежнему идут на **`/v1/users`** и **`/v1/users/{username}`**. Явный путь **`/api/{alias}/stats/users`** не меняется. См. [API.md](API.md). - **`docker pull`: `unauthorized` / `denied`**: выполните `docker login git.shts.su` с учётной записью Gitea и PAT с **`read:package`**. - **`403 forbidden` с хоста при `allow_all: false`**: добавьте CIDR клиента в `whitelist_cidrs`. Запросы из контейнера к самому себе идут с `127.0.0.1` — при необходимости добавьте `127.0.0.1/32`. - **За reverse proxy**: укажите CIDR прокси в `trusted_proxies`, иначе whitelist видит IP прокси, а не клиента. - **`502 bad_gateway`**: проверьте `base_url`, DNS в Docker‑сети и то, что Telemt слушает API (`[server.api].enabled=true` и корректный `listen`). - **Сборка Go без Docker**: в корне репозитория выполните `go mod tidy && go test ./...`.