WebShell
WebShell — это терминал в браузере, который дотягивается до каждого зарегистрированного edge через тот же туннель geminio, который использует остальная платформа. Нет отдельного SSH bastion, нет jumpbox, нет входящего порта. Edge продолжает дозваниваться наружу; manager открывает мультиплексированный класс стрима для shell I/O.
Use case:
- Агент предлагает фикс; вы кликаете «Open shell on edge-prod-04» и подтверждаете изменение, не покидая SPA.
- Вендор / контрактор нуждается в разовом взгляде на один хост без VPN- enrolment.
- Incident-response: каждая команда записывается со audit-строкой исходной сессии.
Архитектура
browser ──WebSocket──> manager:/v1/webshell/ws
│
├─ Router.Register(sessionID, sink, ActiveSession)
│
└─ geminio Stream (shell class)
│
▼
edge agent
└─ pty.Start("/bin/bash")Manager-side router — в internal/manager/biz/webshell/router.go. Он поддерживает справочник sessionID → Sink: WebSocket-handler регистрируется на connect, tunnel-incoming dispatcher маршрутизирует output / exit-пуши edge на правильный браузер.
// internal/manager/biz/webshell/router.go:57
type Router struct {
mu sync.RWMutex
sinks map[string]Sink
meta map[string]*ActiveSession // sessionID → metadata
stdoutBytes sync.Map // sessionID → *uint64
}HTTP / WebSocket handler живёт по соседству в internal/manager/server/webshell, так что router остаётся HTTP-agnostic и unit-testable.
Два класса стримов
Туннель geminio мультиплексирует:
- Control class — JSON RPC (выполнение скиллов, plugin signalling, пробы alert evaluator).
- Shell class — сырые байтовые стримы (один на WebShell-сессию, один на
tail -ffollower и т.д.).
Разделение на уровне туннеля важно, потому что shell I/O — burst-овый и unframed; смешивание его с control RPC обделяет последние. Каждый класс имеет свой бюджет backpressure.
Метаданные сессии
У каждой живой сессии есть ActiveSession:
// router.go:37
type ActiveSession struct {
SessionID string
OngridUserID uint64
SSHUser string
DeviceID uint64
EdgeID uint64
StartedAt time.Time
LastInputAt time.Time // updated on every browser → edge frame
}LastInputAt тикает на каждом нажатии клавиши (Router.TouchInput). Watchdog idle-timeout выселяет сессии старше сконфигурированного предела без недавнего ввода — защищается от утечки «я закрыл вкладку браузера с запущенной командой».
Audit recording
Два слоя:
- Header row — таблица
webshell_sessions: кто, когда, какой edge, exit code, total bytes in/out. - Stream recording — manager-side интерфейс
Recorderберёт каждый байт, пересекающий провод (оба направления, с timestamp), и аппендит в asciinema-совместимый cast-файл под/var/lib/ongrid/webshell-recordings/<session_id>.cast. Админ страница/admin/webshellпроигрывает их.
Интерфейс Recorder нарочно узкий — в production используется file sink; тесты инъектят fake; будущие cloud-blob бэкенды подключаются без касания остального стека.
Лимиты concurrency
Per-user cap: Router.CountByUser вызывается из WebSocket-open handler; over-cap соединения отвергаются HTTP 429. По умолчанию cap 5 (настраивается). Per-edge cap защищает от runaway-агента, открывающего 100 одновременных shell'ов.
Убийство сессий
Три пути убивают сессию:
- Browser close — disconnect WebSocket распространяется на edge, который
kill -HUP-ит pty. - Admin kill — админ-SPA вызывает
Killer.Kill(reason="admin terminated")наSink, который туннелирует close вниз на edge. Причина записывается в exit-строку сессии. - Idle eviction — watchdog запускает
Kill("idle timeout")на сессиях, чейLastInputAtпревысил cap.
// router.go:50
type Killer interface {
Kill(reason string)
}Manager-side handler устанавливает Killer, когда регистрирует Sink. Любой Sink, который opt-in, становится admin-killable; остальные только browser-close-killable.
Role gating
WebShell гейтится на роли admin (ADR-022 RBAC). Роль user может чатиться с агентом, но не может открывать shell'ы; viewer может читать записи прошлых сессий, но не может открывать новые. Гейт работает на входе HTTP-handler, до WebSocket upgrade.
См. также
- Skills —
bashскилл — one-shot эквивалент WebShell (single command, без pty). Та же audit-подложка. - Установка edge — поднимание edge-агента хоста, чтобы WebShell мог до него дотянуться.
- Архитектура — где сидит туннель geminio.