Skip to content

🖥️ WebSocket-консоль приложений (демон) ​

📌 Описание ​

Демон предоставляет WebSocket-консоль конкретного приложения: терминал и live-стрим логов в браузере (attach к контейнеру или журналу нативного приложения).

🎯 Сценарии использования ​

  • 🧑‍💻 Разработчик открывает вкладку «Консоль» приложения — видит live-вывод и вводит команды в stdin (например, консоль Minecraft-сервера).
  • 🔎 Во вкладке «Логи» ищет момент падения по временным меткам и фильтру.

🏗️ Техническое решение ​

🖥️ Вкладки приложения в UI ​

У каждого приложения в платформе — две вкладки:

  • Консоль — реальное время: стрим вывода Docker-приложения или runtime-процесса нативного приложения; снизу — строка ввода команд в stdin приложения. Это интерактивная сессия (attach), а не просто просмотр.
  • Логи — история: каждая строка с временной меткой, поиск/фильтрация по тексту, сортировка по времени, выбор диапазона; переход «показать в контексте» из результата поиска.

🎨 Терминал ​

Терминал — удобный и современный: xterm.js, полная поддержка ANSI-цветов и управляющих последовательностей, автоподбор размера (resize → PTY), копирование/вставка, поиск по буферу, история прокрутки, выбор шрифта/темы (наследует тему UI).

⚙️ Транспорт ​

  • WebSocket-консоль: сервер в демоне; сессии logs (read-only стрим: docker logs -f / journald follow, с метками времени), attach (PTY/stdin приложения, двунаправленно) и exec (независимый шелл внутри приложения, DMN-082 — см. ниже). Протокол — бинарные фреймы (stdin/stdout/resize) как в ttyd/gotty.
  • Доступ по временному токену: WebSocket-подключение к консоли открывается только с временным токеном, который выдаётся через API демона (AppService.IssueConsoleToken / POST /v1/apps/{id}/console-token): TTL 30 секунд, одноразовый, привязан к приложению и типу сессии logs/attach/exec. Грант exec дополнительно несёт команду для запуска (пусто = пробовать найти шелл, см. ниже). Платформа получает токен автоматически при открытии вкладки консоли (backend проверяет права → запрашивает токен у демона → передаёт его браузеру); в автономном режиме токен выдаёт CLI (asc app console-token <id>).
  • Endpoint: GET /v1/console?token=<токен>[&tail=N][&cols=N&rows=N] — вне bearer-аутентификации API (браузер не может выставить заголовки на WS-handshake), защита — только одноразовым токеном; неверный/использованный токен → 401 до апгрейда. cols/rows задают начальную геометрию PTY для сессии exec (по умолчанию 80×24 до первого resize-фрейма от клиента); logs/attach их игнорируют.
  • Реализация MVP (DMN-007): сессия logs — текстовые фреймы, источник по рантайму: для Docker — стрим логов через Engine API (follow + timestamps по unix-сокету); для systemd — journalctl -f -o short-iso; для process — tail -F app.log (subprocess убивается при отключении клиента). Сессия attach — пока только Docker (Engine API attach, бинарные фреймы stdin/stdout); для systemd/process нужен PTY при запуске — придёт вместе с бинарным протоколом ttyd-стиля (resize и пр.) при работе над UI (FE-006).
  • Сессия exec (DMN-082): интерактивный шелл внутри Docker-контейнера, независимый от attach — не присоединяется к hub консоли и никогда не раздаётся другим наблюдателям; каждое подключение exec получает свой процесс через Engine API (create_exec + start_exec с PTY, attach_stdin/stdout/stderr). Если команда в токене пуста, демон пробует /bin/bash, затем /bin/sh — какой найдётся в образе. Фреймы в обе стороны — тегированные бинарные, один ведущий байт совпадает байт-в-байт с протоколом релея платформы (wsapi/protocol.go): 0x00 — данные (дальше сырой payload stdin/stdout), 0x01 — resize, только от клиента к демону (4-байтовый payload: big-endian cols, затем rows), применяется через resize_exec Engine API. Нераспознанный тег отбрасывается, а не рвёт соединение. Пока только Docker-приложения; systemd/process отдают ту же ошибку «не поддерживается», что и attach. Известное ограничение: у exec в Engine API нет kill — при резком обрыве сокета демон может только закрыть stdin, но не принудительно завершить процесс; это задокументировано, а не обойдено — правильное решение зависит от поддержки со стороны Docker.
  • Несколько подключений к одному приложению (вкладки/пользователи): attach-сессии мультиклиентные по схеме wings — в демоне на приложение создаётся одна общая сессия-источник (hub), её вывод раздаётся всем подключениям через broadcast-канал; отстающий клиент теряет старые чанки, но не тормозит остальных. Hub держит replay-буфер последних ~128 КиБ вывода — новая вкладка сразу видит недавний вывод, а stdin всех клиентов сходится в один пайп контейнера. Источник (Engine API attach) закрывается вместе с последним клиентом. logs-сессии независимы по построению: каждое подключение — свой follow-поток со своим tail, поэтому несколько вкладок работают и там.
  • Только текущий запуск (DMN-116): каждый рантайм дописывает все запуски в один общий лог (Engine-лог контейнера, журнал, app.log), поэтому бэклог консольной сессии начинается с момента текущего запуска приложения — после старта или рестарта консоль показывает только то, что напечатал этот запуск, и никогда — вывод прошлых. Docker logs: since = State.StartedAt контейнера, точная отсечка — по наносекундной метке каждой строки. Docker attach: живой attach открывается без logs-replay Engine (тот отдаёт все запуски контейнера за всё время); бэклог — последние 1000 сообщений текущего запуска — читается из лога после этого, в границах от StartedAt до момента, когда attach поднялся, так что они стыкуются без пропусков и дублей. systemd: journalctl _SYSTEMD_INVOCATION_ID=<текущий InvocationID юнита>. process: драйвер при каждом старте записывает смещение в app.log (app.log.run-offset), консоль следит от него (tail -c +N -F, последние tail строк запуска). compose: docker compose logs --since <самый ранний StartedAt запущенных контейнеров проекта>. Где начало запуска не определить (приложение ни разу не стартовало, процесс запущен старым демоном), работает обычный tail. Терминал в браузере полностью сбрасывается на каждую новую сессию — после рестарта старый вывод заменяется новым, а не остаётся над ним.
  • CLI: asc attach <id> (синоним asc app attach <id>) — интерактивная консоль приложения прямо из терминала сервера: stdin/stdout процесса CLI пробрасываются в приложение. При запущенном демоне CLI идёт через собственную консоль демона (DMN-043): запрашивает токен консоли по unix-сокету и открывает ту же WebSocket-сессию, что и браузер, поэтому обычному пользователю не нужны ни членство в группе docker, ни доступ к root-owned дереву приложений — авторизацию полностью определяет peer uid, прочитанный демоном из сокета. Без демона (автономная установка) CLI идёт напрямую в Engine API. В обоих случаях Docker раздаёт вывод всем подключённым (CLI + вкладки браузера). Пока только Docker-приложения; systemd/process — вместе с PTY (FE-006). Отключение — Ctrl+C, приложение продолжает работать.
  • Маршрутизация: браузер ↔ nodeservice (wss) ↔ туннель демона ↔ модуль консоли.
  • Права: доступ к консоли приложения регулируется правом apps.console (🔐 access-control).

🔗 Связанные задачи ​

DMN-007, DMN-082, DMN-088, DMN-116, FE-006, FE-061 в ROADMAP.md.

Распространяется по лицензии MIT.