diff --git a/docs/GATEWAY_RUN.md b/docs/GATEWAY_RUN.md index 2ae65fe..44386ef 100644 --- a/docs/GATEWAY_RUN.md +++ b/docs/GATEWAY_RUN.md @@ -13,7 +13,7 @@ 9. [Проверка](#проверка) 10. [Обновление и CI/CD](#обновление-и-cicd) 11. [Устранение неполадок](#устранение-неполадок) -12. [Mihomo (external-controller)](#mihomo-external-controller) +12. [Mihomo (external-controller)](#mihomo-external-controller) — при **400** на REST: [отладка](#mihomo-debug-400) ## Назначение @@ -114,7 +114,25 @@ services: За **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. + + +#### Отладка 400 на маршрутах Mihomo (`/api/{alias}/mihomo/…`) + +Если **напрямую** к контроллеру всё ок (`curl -H "Authorization: Bearer …" http://mihomo:9090/version` → JSON), а **через шлюз** те же пути дают **400** и в логах шлюза `duration_ms` около **1** — проблема в **сборке исходящего HTTP на шлюзе**, а не в секрете Mihomo. + +Что зафиксировано в коде и не стоит «упрощать» назад без причины: + +| Что | Зачем | +|-----|--------| +| **REST** (GET `/version`, `/proxies`, `/connections`, POST к API контроллера и т.д.) | Тот же путь, что и прокси к Telemt: `http.NewRequest` + `RoundTrip` (`internal/proxy/forward.go`, общая логика `newAliasForward`). Так совпадает с рабочим `curl` к upstream. | +| **Не** единый `httputil.ReverseProxy` на весь Mihomo | Для обычного HTTP `ReverseProxy` нередко даёт **400** у строгих upstream при том, что прямой запрос работает. | +| **WebSocket** (`/traffic`, `/memory`, …) | Отдельно `httputil.ReverseProxy` + `Rewrite` (`internal/proxy/mihomo.go`). | +| Заголовок **Host** | Не пересылается с клиента; на upstream уходит authority из `mihomo_base_url` (как для Telemt и `base_url`). | + +Проверки конфигурации: + +- `mihomo_authorization_env` в YAML — это **имя** переменной; **полная** строка `Authorization` (например `Bearer `) задаётся в **environment** контейнера gateway, не в YAML. +- После изменений в этом месте пересоберите образ / задеплойте актуальный бинарь — иначе будет старое поведение. ## Переменные окружения @@ -228,6 +246,7 @@ docker compose down - **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` нормализуется). +- **`400` только на `/api/{alias}/mihomo/…`, при этом прямой `curl` к контроллеру Mihomo ок**: см. [Отладка 400 (Mihomo)](#mihomo-debug-400) в разделе [Mihomo](#mihomo-external-controller) — REST к Mihomo идёт через тот же механизм, что и к Telemt; не путать с отдельным WebSocket-прокси. - **Список пользователей через шлюз**: запрос **`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`. diff --git a/internal/proxy/mihomo.go b/internal/proxy/mihomo.go index 10ad7ae..ad9e3c5 100644 --- a/internal/proxy/mihomo.go +++ b/internal/proxy/mihomo.go @@ -11,8 +11,9 @@ import ( // NewMihomoForward proxies /api/{alias}/mihomo/... to Mihomo external-controller. // // REST и обычные GET/POST идут тем же путём, что и NewAliasForward (http.NewRequest + RoundTrip): -// httputil.ReverseProxy в таком режиме часто даёт 400 на строгих upstream (в т.ч. Mihomo), хотя curl к ним работает. +// httputil.ReverseProxy для всего Mihomo часто давал 400 на upstream при том, что curl к контроллеру работал. // Только WebSocket (traffic, memory, …) остаётся на ReverseProxy + Rewrite. +// Чеклист при повторении проблемы: docs/GATEWAY_RUN.md#mihomo-debug-400 func NewMihomoForward( target *url.URL, stripPrefix string, diff --git a/web/src/routes/servers/[alias]/mihomo/+page.svelte b/web/src/routes/servers/[alias]/mihomo/+page.svelte index 0013c1d..0d13fff 100644 --- a/web/src/routes/servers/[alias]/mihomo/+page.svelte +++ b/web/src/routes/servers/[alias]/mihomo/+page.svelte @@ -202,8 +202,12 @@ void loadProxies(); }); - function isSelectableGroup(t?: string) { - return t === 'Selector' || t === 'URLTest'; + /** Группы, у которых есть `all` + `now` и PUT /proxies/{name} переключает узел (как в Clash/Mihomo). */ + function isSelectableGroup(p: MihomoProxyEntry) { + const t = (p.type ?? '').toLowerCase(); + if (t === 'selector' || t === 'urltest' || t === 'fallback') return true; + if (t === 'loadbalance' && Array.isArray(p.all) && p.all.length > 0) return true; + return false; } function lastDelay(p: MihomoProxyEntry): number | null { @@ -386,7 +390,7 @@ {#snippet children()} {#if proxiesData?.proxies}
- {#each Object.entries(proxiesData.proxies).filter(([_, v]) => isSelectableGroup(v.type)) as [gName, group] (gName)} + {#each Object.entries(proxiesData.proxies).filter(([_, v]) => isSelectableGroup(v)) as [gName, group] (gName)}