Раздел: Security & Network
Идентификатор:tunnels· Экран:ui/tunnels/TunnelsScreen.kt(429) +TunnelsViewModel.kt(361)
Снимок: 2026-10-11
1. Зачем и что это
Ключевая особенность: список обновляется раз в 5 секунд, пока экран активен, — туннели живут и меняются на стороне backend, и экран показывает это без ручного обновления.
Управление: открыть, переподключить, закрыть, проверить «Check now», перезапустить remote-демон и открыть его логи.
2. Как попасть
Хаб инструментов → секция Security & Network → карточка «Туннели» (hub_tunnels_title, подпись «Обратные SSH-туннели, состояние и переподключения», тайл — «Туннель»).
| Откуда ещё | Путь |
|---|---|
| Мост | app_navigate с screen: "tunnels" |
Маршрут: Screen.kt → MainRouter (TunnelsScreen).
Шапка — терминальная y83@tunnels :~$, в meta видно «N active · M servers».
3. Экран: элементы и действия
| Элемент | Что делает | Где в коде |
|---|---|---|
| Шапка | Промпт, мета, состояние | TUNNELS_PROMPT, tunnelsMetaLabel |
| Карточка туннеля | TunnelCard | Точка статуса, имя, хост, порт, метки |
| Кнопка Open | Открыть туннель | open(serverId) |
| Кнопка Reconnect | close + open | reconnect(serverId) |
| Кнопка Close | Закрыть туннель | close(serverId) |
| Кнопка Check now | Проверка и лечение обоих направлений | check(serverId) |
| Кнопка Restart daemon | Перезапуск codex app-server на сервере | restartDaemon(serverId) |
| Кнопка Logs | Логи демона в диалоге | loadDaemonLogs, DaemonLogsDialog |
| Копирование порта | copyPort — локальный порт в буфер | copyPort |
4. Возможности подробно
Список и обновление
refresh(silent) берёт все серверы из App.remoteServerDao().getAll() и все туннели одним запросом tunnelStatusAll(), затем mergeTunnelRows склеивает их по serverId. Серверы без туннеля не прячутся — они показываются со статусом disconnected.
Пока экран активен, список обновляется раз в 5 секунд (delay(5_000)). Фоновый поллинг идёт в «silent»-режиме: не мигает лоадером и не сыпет ошибками, а ошибка предыдущего видна остаётся.
Статусы и цвета
tunnelStatusColor и tunnelStatusLabel (tunnel_up → «tunnel up»):
| Статус | Цвет | Смысл |
|---|---|---|
ready, tunnel_up, codex_starting | успех | туннель живой |
connecting, reconnecting | предупреждение | поднимается |
failed | ошибка | упал |
| прочее | нейтральный | неизвестно / не поднят |
Разбор результата проверки
tunnelStatusAll возвращает по каждому туннелю: status, localPort, reverseHealthy, daemonRunning, lastError, lastErrorKind, lastErrorAt, errorActive, lastCheckAt, reconnectCount.
Важное различие, зафиксированное в скилле y83-remote:
ready+ непустойlastError— норма (сбой пережит), а не ошибка;- смотреть надо на
errorActive(сбой всё ещё актуален) и наreverseHealthy/daemonRunning; hostнужен, когда записи сервера в приложении уже нет.
Здоровье туннеля считается как ready && reverseHealthy && daemonRunning.
Действия
| Действие | Что делает |
|---|---|
open | POST /tunnel/open с креды сервера; успех = статус ready и валидный localPort |
reconnect | close + open; креды не нужны — backend берёт их из самого туннеля |
close | POST /tunnel/close |
check | POST /tunnel/check — проверяет оба направления и лечит их |
restartDaemon | POST /remote/service с действием restart (codex app-server) |
loadDaemonLogs | Логи демона в диалог |
Метка «disconnected» ставится до запроса на закрытие (markDisconnected), чтобы watchdog не успел пересоздать туннель за пользователя; при неудаче метка снимается (clearDisconnected).
5. Данные: БД, файлы, настройки
| Что | Где лежит | Как меняется | Кто читает |
|---|---|---|---|
| Список серверов | Room (RemoteServerEntity) | Экран серверов | Склейка строк |
| Статус туннелей | Backend (in-memory + проверки) | tunnelStatusAll | Экран |
| Метка «пользователь закрыл» | RemoteTunnelIntent | markDisconnected / clearDisconnected | Watchdog |
6. Backend: REST/роуты
| Метод и путь | Назначение |
|---|---|
GET /api/v1/remote/tunnel/status | статусы всех туннелей |
POST /api/v1/remote/tunnel/open | открыть туннель к серверу |
POST /api/v1/remote/tunnel/close | закрыть туннель |
POST /api/v1/remote/tunnel/reconnect | переподнять туннель |
POST /api/v1/remote/tunnel/check | «Check now»: проверить и вылечить |
POST /api/v1/remote/service | действие над codex app-server (restart) |
Роуты: backend/internal/api/v1/routes.go:416–423.
7. Мост и MCP
| Что | Как | Примечание |
|---|---|---|
| Открыть экран | app_navigate с screen: "tunnels" | BridgeScreenCatalog |
| Диагностика | GET …/tunnel/status, POST …/tunnel/check | Скилл y83-remote |
| e2e-проверка | PROBE=1 PROBE_MCP=1 bash scripts/remote-e2e.sh <host> | Скилл y83-remote |
8. Настройки, конфиг, квоты и лимиты
| Параметр | Значение | Где задаётся |
|---|---|---|
| Интервал опроса | 5 секунд | Экран (delay(5_000)) |
| Здоровье туннеля | ready + reverseHealthy + daemonRunning | Формула в скилле y83-remote |
| Серверов в списке | все из Room | mergeTunnelRows |
9. Ловушки и ограничения
lastError— это история, а не текущая проблема. Пережитый сбой не должен пугать; смотрите наerrorActiveиreverseHealthy/daemonRunning.- Поллинг каждые 5 секунд держит постоянный запрос к backend, пока экран открыт. При слабой сети это заметно.
- Reconnect не требует кред. Backend берёт их из самого туннеля; если туннеля ещё нет, сначала нужен
open. - Метка «disconnected» ставится до запроса — при сбое сети карточка может показать disconnected, хотя туннель ещё жив.
localPort = 0означает, что туннель не поднят. Порт можно скопировать в буфер, чтобы подключиться вручную.- Логи демона открываются диалогом и требуют доступа по SSH к серверу.
- Серверы без туннеля не скрываются — они видны со статусом disconnected, что может выглядеть как «сломанный» туннель.
10. Источники правды
app/src/main/java/com/y83/ws/ui/tunnels/TunnelsScreen.kt— экран, карточки, диалог логов.app/src/main/java/com/y83/ws/ui/tunnels/TunnelsViewModel.kt— состояние, склейка, действия.app/src/main/java/com/y83/ws/data/api/TunnelListApi.kt— DTO и клиент.app/src/main/java/com/y83/ws/data/RemoteTunnelIntent.kt— метки «пользователь закрыл туннель».backend/internal/api/v1/routes.go:416–423— роуты туннелей.skills/y83-remote/SKILL.md— архитектура и диагностика туннелей.