# 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](https://github.com/tsl0922/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@ │ │ │ │ │ └─────────────────┘ ▼ ┌─────────────────┐ │ SQLite DB │ │ (sessions) │ └─────────────────┘ ``` ## Prerequisites ### 1. Install ttyd #### Linux (Debian/Ubuntu) ```bash sudo apt-get install ttyd ``` #### Linux (from source) ```bash git clone https://github.com/tsl0922/ttyd.git cd ttyd && mkdir build && cd build cmake .. make && sudo make install ``` #### macOS ```bash brew install ttyd ``` #### Windows (WSL) ```bash 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 ```http GET /api/terminal/status ``` Returns whether ttyd is installed and terminal feature is available. **Response:** ```json { "available": true, "ttyd_installed": true, "max_sessions_per_user": 3, "session_ttl_minutes": 30, "active_sessions": 2 } ``` ### Create Terminal Session ```http POST /api/terminal/{host_id}/terminal-sessions Authorization: Bearer Content-Type: application/json { "mode": "embedded" // or "popout" } ``` **Response:** ```json { "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 ```http GET /api/terminal/sessions Authorization: Bearer ``` ### Close Terminal Session ```http DELETE /api/terminal/sessions/{session_id} Authorization: Bearer ``` ### Connect to Terminal ```http GET /api/terminal/connect/{session_id}?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: ```bash 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: ```bash 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 ```sql 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`