SSH Terminal Implementation
1. Integration Overview (ssh2 + xterm.js)
Cosmosh terminal path is split into control plane and data plane:
- Control plane: Renderer calls backend session creation through Main IPC bridge.
- Data plane: Renderer connects directly to backend WebSocket session endpoint and streams terminal I/O.
sequenceDiagram
participant UI as Renderer SSH.tsx
participant MAIN as Electron Main
participant API as Backend SSH Route
participant SSH as SshSessionService
participant REM as Remote SSH Host
UI->>MAIN: backend:ssh-create-session(serverId, cols, rows, term)
MAIN->>API: POST /api/v1/ssh/sessions
API->>SSH: createSession(input)
SSH->>REM: ssh2 connect + shell()
SSH-->>API: sessionId + wsUrl + wsToken
API-->>UI: create-session response
UI->>SSH: WebSocket /ws/ssh/{sessionId}?token=...
UI-->>SSH: input/resize/ping
SSH-->>UI: output/telemetry/pong/exit
2. Backend Session Lifecycle
Create Session
- Route:
POST /api/v1/ssh/sessions - Service:
SshSessionService.createSession - Steps:
- Load server record + encrypted credentials.
- Resolve trusted host fingerprints.
- Open SSH shell via
ssh2.Client.shell. - Write
SshLoginAuditrecord:result = successon successful session creation, withsessionIdandsessionStartedAt.result = failedon host-trust/auth/connect failures, withfailureReason.
- Register live session state in memory (
Map<sessionId, SshLiveSession>). - Return short-lived attach token + WS endpoint.
Attach WebSocket
- Path:
/ws/ssh/{sessionId}?token=... - Invalid path/token/session is rejected (
1008). - Existing attached socket is replaced (
1012) to support single active attach. - Pending output is buffered before attach and flushed after ready.
Close Session
- API-driven close:
DELETE /api/v1/ssh/sessions/{sessionId} - Transport-driven close: socket close/error, SSH stream close, SSH client error.
- Dispose behavior: send terminal
exitevent, clear telemetry timer, close SSH stream/client, close WS. - Audit finalization: update matching
SshLoginAuditwithsessionEndedAtandcommandCount.
2.1 Connection Audit and Last-Used Sorting
- Server list payload maps
lastLoginAuditto the latest successful login audit (result = success). - This keeps "sort by last used" aligned with actual successful connections instead of failed attempts.
- Failed attempts are still persisted in
SshLoginAuditfor future query/audit features.
3. Data Stream Protocol
Client → Server
input: raw terminal input bytes as UTF-8 string.resize: terminal cols/rows with bounded normalization.ping: heartbeat.close: explicit disconnect request.
Server → Client
ready: attach acknowledged.output: shell stdout/stderr output.telemetry: CPU/memory/network + command history snapshot.pong: ping response.error: protocol/runtime error.exit: terminal session closed with reason.
flowchart LR XT[xterm.js onData] --> MSG[input JSON] MSG --> WS[WebSocket] WS --> SSH[ssh2 shell stream.write] SSH --> OUT[shell stdout/stderr] OUT --> WS2[WebSocket output event] WS2 --> XT2[xterm.write]
4. Host Verification & Trust Flow
- SSH connect uses
hostHash: 'sha256'andhostVerifier. - If fingerprint is unknown:
- backend returns
SSH_HOST_UNTRUSTEDpayload. - renderer opens trust dialog.
- user confirmation calls trust endpoint.
- renderer retries create-session.
- backend returns
5. Exception Handling & Reconnect
Current Behavior (Implemented)
- Session attach timeout: 30s.
- Any socket close/error transitions UI state to failed.
- Retry is manual via UI retry button (
SSH.tsx), which creates a new session. - No automatic exponential reconnection loop is implemented yet.
Recommended Next Step (Planned)
- Add bounded auto-reconnect only for transient WS transport failures.
- Keep host-verification and auth failures as terminal (non-retriable) errors.
6. Performance Strategies in Current Code
- Renderer uses
FitAddon+ resize observer to keep shell size synchronized. - Backend normalizes terminal sizes to prevent extreme allocations (
20-400 cols,10-200 rows). - Pending output queue avoids losing early SSH output before WS attach.
- Telemetry sampling is interval-based (5s) and lightweight text parsing to reduce per-frame cost.
- Command capture keeps bounded input buffer (512 chars per active line).
7. Developer Debug Checklist
When SSH session behavior is wrong, verify in order:
- Session creation API payload and validation path.
- Host verification branch (
SSH_HOST_UNTRUSTEDvs direct session creation). - WS attach token/sessionId matching.
- Stream direction integrity (
inputwrite andoutputflush). - Session disposal path (API close vs transport close vs SSH error).