Bruno Charest 493668f746
Some checks failed
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
Add comprehensive SSH terminal drawer feature with embedded and popout modes, integrate playbook lint results API with local cache fallback, and enhance host management UI with terminal access buttons
2025-12-17 23:59:17 -05:00

8.0 KiB

SSH Web Terminal Feature

This document describes the SSH web terminal feature integrated into the Homelab Dashboard.

Overview

The SSH Web Terminal allows users to connect to bootstrapped hosts directly from the web interface using ttyd as the terminal emulator backend.

Features

  • Embedded Terminal: Opens in a slide-over drawer panel within the Hosts page
  • Pop-out Terminal: Opens in a standalone window (or new tab if popups are blocked)
  • Session Management: Automatic expiration, session limits per user
  • Security: Token-based authentication, no SSH keys exposed to browser

Architecture

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   Web Browser   │────▶│   FastAPI       │────▶│   ttyd Process  │
│   (iframe)      │     │   Backend       │     │   (per session) │
└─────────────────┘     └─────────────────┘     └─────────────────┘
                                │                        │
                                │                        ▼
                                │                 ┌─────────────────┐
                                │                 │   SSH Client    │
                                │                 │   automation@   │
                                │                 │   <host>        │
                                │                 └─────────────────┘
                                ▼
                        ┌─────────────────┐
                        │   SQLite DB     │
                        │   (sessions)    │
                        └─────────────────┘

Prerequisites

1. Install ttyd

Linux (Debian/Ubuntu)

sudo apt-get install ttyd

Linux (from source)

git clone https://github.com/tsl0922/ttyd.git
cd ttyd && mkdir build && cd build
cmake ..
make && sudo make install

macOS

brew install ttyd

Windows (WSL)

sudo apt-get install ttyd

Docker

ttyd can also be run via Docker, but the current implementation spawns ttyd processes directly.

2. SSH Key Configuration

Hosts must be bootstrapped with the automation user and SSH key configured. The bootstrap process:

  1. Creates the automation user on the target host
  2. Deploys SSH public key for passwordless authentication
  3. Configures sudo without password for the automation user

3. Environment Variables

Variable Default Description
TERMINAL_SESSION_TTL_MINUTES 30 Session timeout in minutes
TERMINAL_MAX_SESSIONS_PER_USER 3 Maximum concurrent sessions per user
TERMINAL_PORT_RANGE_START 7680 First port for ttyd instances
TERMINAL_PORT_RANGE_END 7700 Last port for ttyd instances
TERMINAL_SSH_USER automation SSH username for connections
TTYD_PATH ttyd Path to ttyd binary

API Endpoints

Check Terminal Feature Status

GET /api/terminal/status

Returns whether ttyd is installed and terminal feature is available.

Response:

{
  "available": true,
  "ttyd_installed": true,
  "max_sessions_per_user": 3,
  "session_ttl_minutes": 30,
  "active_sessions": 2
}

Create Terminal Session

POST /api/terminal/{host_id}/terminal-sessions
Authorization: Bearer <token>
Content-Type: application/json

{
  "mode": "embedded"  // or "popout"
}

Response:

{
  "session_id": "abc123...",
  "url": "/terminal/connect/abc123...?token=xyz...",
  "websocket_url": "ws://localhost:7680/ws",
  "expires_at": "2024-12-17T20:00:00Z",
  "ttl_seconds": 1800,
  "mode": "embedded",
  "host": {
    "id": "host-id",
    "name": "server01",
    "ip": "192.168.1.100",
    "status": "online",
    "bootstrap_ok": true
  }
}

List Active Sessions

GET /api/terminal/sessions
Authorization: Bearer <token>

Close Terminal Session

DELETE /api/terminal/sessions/{session_id}
Authorization: Bearer <token>

Connect to Terminal

GET /api/terminal/connect/{session_id}?token=<session_token>

Returns HTML page with embedded terminal iframe.

Security Considerations

Authentication

  • All API endpoints require JWT Bearer token authentication
  • Session tokens are generated per-session and hashed before storage
  • Token verification on every terminal page load

Session Isolation

  • Each terminal session spawns a dedicated ttyd process
  • ttyd uses --once flag to exit after client disconnects
  • Sessions are bound to specific users

Rate Limiting

  • Maximum sessions per user (default: 3)
  • Session TTL prevents resource exhaustion
  • Expired sessions are automatically cleaned up

Audit Logging

  • Session creation logged with user and host info
  • Session closure logged
  • No sensitive data (tokens, keys) in logs

SSH Security

  • SSH keys never exposed to browser
  • StrictHostKeyChecking=accept-new for initial connections
  • Uses dedicated automation user with limited sudo

UI Components

Terminal Button (Host Card)

  • Enabled: When host is online AND bootstrap OK
  • Disabled: When host is offline OR bootstrap not completed
  • Shows tooltip explaining why disabled

Terminal Drawer

  • Slides in from right side of screen
  • Header: Host name, IP, connection status
  • Body: Embedded ttyd iframe
  • Footer: SSH command copy, reconnect, session timer
  • Close with Escape key or X button

Pop-out Window

  • Opens in new window with minimal UI
  • Fullscreen terminal experience
  • PWA hint for toolbar-free experience

Troubleshooting

"ttyd is not installed"

Install ttyd using the instructions above. Verify with:

which ttyd
ttyd --version

"Host not bootstrapped"

Run bootstrap on the host first:

  1. Go to Hosts section
  2. Click "Bootstrap" button on the target host
  3. Enter root password when prompted
  4. Wait for bootstrap to complete

"Maximum sessions reached"

Close existing terminal sessions before opening new ones.

Pop-up blocked

If the browser blocks the pop-out window, the terminal will open in a new tab instead.

Session expired

Click "Reconnect" button to create a new session.

PWA / Toolbar-free Window

Modern browsers don't allow complete removal of the URL bar via window.open(). To get a true "app-like" experience:

Option 1: Chrome/Edge App Mode

Create a shortcut with:

chrome --app=https://your-dashboard.com/terminal/connect/SESSION_ID?token=TOKEN

Option 2: Install as PWA

  1. Open the dashboard in Chrome/Edge
  2. Click the install icon in the address bar
  3. Launch terminals from the installed PWA

Database Schema

CREATE TABLE terminal_sessions (
    id VARCHAR(64) PRIMARY KEY,
    host_id VARCHAR NOT NULL,
    host_name VARCHAR NOT NULL,
    host_ip VARCHAR NOT NULL,
    user_id VARCHAR,
    username VARCHAR,
    token_hash VARCHAR(128) NOT NULL,
    ttyd_port INTEGER NOT NULL,
    ttyd_pid INTEGER,
    mode VARCHAR(20) DEFAULT 'embedded',
    status VARCHAR(20) DEFAULT 'active',
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
    closed_at TIMESTAMP WITH TIME ZONE
);

Files

Backend

  • app/models/terminal_session.py - SQLAlchemy model
  • app/schemas/terminal.py - Pydantic schemas
  • app/crud/terminal_session.py - Database operations
  • app/services/terminal_service.py - ttyd process management
  • app/routes/terminal.py - API endpoints

Frontend

  • app/main.js - Terminal methods in DashboardManager
  • app/index.html - CSS styles for terminal drawer

Migration

  • alembic/versions/0013_add_terminal_sessions_table.py

Tests

  • tests/backend/test_terminal.py