Files
telemt-api/docs/GATEWAY_RUN.md
T
Denozordec ed785cb8f3
Publish telemt-api gateway Docker image / test (push) Successful in 28s
Publish telemt-api gateway Docker image / build-and-push (push) Successful in 2m10s
Update Mihomo configuration and documentation for clarity
- Revised comments in `config.example.yaml` to enhance understanding of Mihomo integration, including environment variable usage and Docker Compose setup.
- Updated `docker-compose.yml` comments to clarify the relationship between the gateway and Mihomo service.
- Enhanced `GATEWAY_RUN.md` to provide clearer instructions on configuring Mihomo parameters and their usage in the gateway.
2026-03-31 00:54:41 +07:00

237 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Запуск 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 <secret>`), как у `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 хоста в dockerbridge (часто `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 ./...`.