Skip to content

Repository files navigation

SSH Console — Infrastructure Workspace

An open-source SSH workspace with a black-and-white, Linear/Vercel-grade UI. Manage many servers, monitor GPUs live, run an interactive terminal in the browser, and operate Docker / tmux / systemd / processes without dropping to the CLI. Real multi-user accounts, per-server access control, and an audit log — self-hostable behind your own identity provider or standalone.

Black & white theme · React + Tailwind (shadcn-style) frontend · Python stdlib backend. Everything runs on infrastructure you control. No AI features. No telemetry.

Run

One command — starts the backend and the web app:

cd web
npm install      # first time only
npm run dev      # boots the Python backend (if not already up) + the web UI

Then open http://localhost:5180. (start.bat still works if you prefer a double-click.) On first run, a local admin account is created automatically and its generated password is printed once to the console — log in with it, then change it from Admin → Users.

You connect to each host from the UI itself — pick a server and hit Connect (one click). Disconnect terminates the session.

Optional — full in-browser terminal (htop/vim/tmux):

pip install pywinpty

Without it the in-browser terminal still works (interactive shell, colors) via a piped fallback, and Native terminal always opens a full TTY in Windows Terminal.

What's included

Module What it does
Servers Multi-server sidebar with folders, live status, one-click add/edit. Connect by key, SSH config alias, Telnet (username/password), or local. Upload a private key straight from the browser — it lands on whichever machine runs the backend, not just wherever you're typing from. Optional Wake-on-LAN (magic packet before connecting), outbound SOCKS5 proxy, and TCP port-knocking (SSH hosts) — per-host or as an admin-wide default.
Connect / Disconnect Start and terminate a persistent SSH or Telnet session per host from the UI. Sessions survive terminal-tab switches and browser reloads; Disconnect kills them.
Overview Live GPU cards (util/mem/temp/power/fan) with sparklines + history chart, system metrics (CPU/RAM/load/disk), and independent TCP reachability polling (up/down even if SSH/Telnet itself is what's hung).
Alerts Three tabs — Alerts (firing/resolved feed), Rules (threshold conditions), Channels (delivery: ntfy, webhook, or email via admin-configured SMTP).
Terminal Real interactive SSH or Telnet terminal in the browser (xterm.js + WebSocket PTY/socket bridge). Tabs + split panes, persistent sessions, reconnect, optional session recording. A Telnet host has its own port (default 23), optional username/auto-login password, and optional auto-login delay — everything else (Console, file browser, Docker/tmux/services/network, SSH key manager, tunnels) stays SSH-only, since Telnet has no per-command exec channel to run them over.
Job Queue GPU compute processes (pid/user/mem/elapsed) with Kill.
Console + Run on all Command runner per host (OS-aware presets), plus cross-server execution across selected connected servers.
Docker Containers, images, volumes, networks — start/stop/restart/logs/remove + system prune.
tmux Sessions list with Kill (guards shared sessions).
Services Running systemd units with Restart.
Network Interfaces + listening ports.
Logs Live tail -f of a file / journal streamed over WebSocket.
Notifications Bell + desktop notifications driven by GPU temp/memory/reachability threshold alerts.
Tunnels SSH port forwarding — saved Local / Remote / SOCKS tunnels with start/stop + live status.
Keys SSH key manager — list (with fingerprints), generate, copy public key, authorize a key, delete, or upload an existing one.
Files Remote browser with upload / download / rename / delete and a Monaco editor (save).
Quick Connect One-off connection (host/port/user + password, key, or a saved credential) without adding it as a saved server — connect to a terminal or the file browser directly, with an optional "save as server" follow-up.
Jump hosts Route a server's SSH connection through one or more intermediate hops, configured as an ordered chain on the host editor (ssh -J under the hood — no protocol work, just argv).
Credentials Reusable stored secrets (password or key), decoupled from any single host, so one credential attaches to many servers instead of re-entering it per host.
Snippets Saved-command library in folders, searchable, insertable straight into the focused terminal tab, with JSON export/import.
Session Logs Every terminal session's full text transcript, browsable by host/start-time/duration, with a read-only playback viewer — off entirely via the global Command History admin toggle.
Connections Live read-only list of every currently-open session (terminal, files, …) across the app, for at-a-glance "what's active right now."
Personal ID Claim a handle and publish your SSH public key(s) at a stable, unauthenticated URL (/pubkeys/<handle>) — pipe it straight into authorized_keys on any box instead of copy-pasting.
Backup / Restore Admin-only full-instance export (auth.db + every JSON config store, zipped) and restore, using SQLite's own online backup API — safe even with the app live.
Host quick actions + customizable metrics dashboard Per-host one-click command buttons in the Overview toolbar, plus drag-to-reposition/resize widget cards (GPU, history, system stats, reachability) — per user, per host, with a reset-to-default.
Admin Users (create/delete, per-server access grants), Auth & Session policy (local/OIDC/2FA toggles, session timeout, self-service password reset, silent SSO), SSO provider CRUD, active sessions (device/OS, revoke), API keys for programmatic access, host defaults (global new-host toggles), and a durable audit log. Admin-only.
Command palette ⌘K / Ctrl+K — jump to any server, module, or action.

All modules are OS-aware: remote Linux hosts use shell tooling; local "This PC" uses PowerShell equivalents (Get-Service, Get-NetTCPConnection, ssh-keygen, etc.) — Windows-local only for now (see Roadmap).

Auth

Real accounts, not "anyone who reaches the port has full control." Two modes, pick one via the AUTH_MODE environment variable:

AUTH_MODE=local (default) — the app's own login. Usernames/passwords, hashed with PBKDF2-HMAC-SHA256 (stdlib hashlib, no external crypto dependency), stored in a local SQLite file (auth.db, created automatically, 0600 permissions). First run bootstraps one admin account (see ADMIN_USERNAME/ADMIN_PASSWORD below). Good default for a standalone install with no other identity system in front.

AUTH_MODE=trusted_header — for deployments already sitting behind an external forward-auth reverse proxy (Caddy/nginx/Traefik + oauth2-proxy or similar, backed by any OIDC provider — Pocket-ID, Authelia, Keycloak, Authentik, Google Workspace, etc.). The app trusts the X-Forwarded-Email header the proxy sets after it verifies identity, and auto-provisions/recognizes a user per distinct email — no separate local login screen at all. Requires REQUIRE_FORWARDED_EMAIL=1 too (refuses to start otherwise) — without that, the header would be unverified client-supplied input, not a real identity signal.

Either mode gets the same RBAC (below), audit log, and session/identity model — the only difference is where the credential actually lives.

Optional external sign-in tab (works alongside AUTH_MODE=local): the login screen can show two tabs at once — Login (username/password) and External (one or more OIDC providers) — so a subset of users can sign in with an outside identity provider without switching the whole deployment to trusted_header. This app drives the redirect and code exchange itself (Authorization Code + PKCE, stdlib urllib.request/hashlib/secrets only, no extra dependency), then auto-provisions a local account per distinct email the provider returns — same identity model as trusted_header, just triggered by a button instead of a proxy header. Two ways to add a provider, usable together:

  • Env vars (OIDC_* below) — one provider, set at deploy time, endpoints found via the issuer's /.well-known/openid-configuration. Leave any of the five unset and it simply doesn't show up.
  • Admin → Auth & Session — any number of providers, added/edited/removed at runtime, each with its client ID/secret and the three endpoint URLs entered directly (no discovery). The same panel has a toggle for the Login tab itself, in case a deployment wants to go external-only without redeploying with AUTH_MODE=trusted_header. The server refuses to leave zero sign-in options enabled, so you can't lock yourself out through this panel.

Every provider — env-configured or admin-added — redirects back to the one OIDC_REDIRECT_URI below, since they all return to this same app instance.

Two-factor auth (TOTP): optional per-account second factor (RFC 6238, stdlib hmac/hashlib/base64 only — no authenticator-app SDK needed), with a "remember this device for 30 days" option that skips the code prompt on a browser that already proved it once. Login attempts and second-factor checks are both rate-limited (per account and per source address) to make online guessing impractical.

Self-service password reset: a "forgot password" flow where the one-time code is logged server-side (console/journal) rather than emailed — no mail infrastructure required. Toggled from Admin → Auth & Session; off by default.

Active sessions & API keys: Admin → Sessions lists every live login (device/OS label, issued/expiry, a "YOU" badge) with one-click revoke. Admin → API Keys issues bearer tokens for programmatic access to this app's own HTTP API, independent of a browser session.

RBAC: every server entry can be scoped to specific non-admin users via Admin → Users → (expand a user) → toggle per-server access. Admins always see/use everything; server management itself (create/edit/delete a server, upload a key) is admin-only.

Custom roles: a second, narrower layer on top of the per-server access above. Admin → Users → Roles creates named capability sets (view_hosts, exec_commands, manage_tunnels) -- e.g. a "read-only operator" role with only view_hosts can see a host's status, metrics, and logs but can't open a terminal, run a command, edit a file, or touch a tunnel on it. Each non-admin user gets a role picker on their row in Admin → Users; leaving it unset ("No role") keeps that account's original full access within whatever servers it's already granted -- assigning a role only ever takes capabilities away, never grants anything beyond that.

Audit log: every login, connect/disconnect, command execution, kill/restart action, and user/access change is recorded with who, what, where, when, and success/failure. Admin-only viewer under Admin → Audit log.

Environment variables

Variable Default Meaning
PORT 8765 backend port
AUTH_MODE local local or trusted_header
ADMIN_USERNAME / ADMIN_PASSWORD (generated) seed the first admin account explicitly (local mode only); unset after first boot
REQUIRE_FORWARDED_EMAIL unset when set, every request must carry a non-empty X-Forwarded-Email header — required for trusted_header mode, optional extra layer in local mode if you also sit behind a proxy
EXTERNAL_LOGOUT_URL unset where the frontend's Sign Out button sends the browser in trusted_header mode, to actually end the session at your identity provider (e.g. https://auth.example.com/oauth2/sign_out?rd=https://ops.example.com/)
OIDC_PROVIDER_NAME unset display name for the legacy env-configured provider's button (e.g. Authentik); all five OIDC_* variables must be set for it to appear
OIDC_ISSUER unset that provider's issuer URL — {issuer}/.well-known/openid-configuration must resolve
OIDC_CLIENT_ID / OIDC_CLIENT_SECRET unset credentials for the OIDC client registered with that provider
OIDC_REDIRECT_URI unset this app's own callback URL, shared by every provider (env-configured or admin-added) — must exactly match what's registered with each one, e.g. https://ops.example.com/api/auth/oidc/callback
OIDC_SCOPES openid email profile scopes requested by the legacy env-configured provider (admin-added providers set their own scopes in Admin → Auth & Session)

Putting it behind an OIDC provider (trusted_header mode)

The shape that works with any OIDC-capable reverse-auth proxy (this project doesn't bundle one — bring your own: oauth2-proxy is a solid, free choice):

your reverse proxy (Caddy/nginx/Traefik)
  -> forward_auth to oauth2-proxy (verifies session, talks OIDC to your provider)
  -> on success, sets X-Forwarded-Email (or equivalent) and proxies to this app

With Caddy + oauth2-proxy specifically, the relevant site block looks like:

your-ops-domain.example.com {
    forward_auth 127.0.0.1:4180 {
        uri /oauth2/auth
        copy_headers X-Auth-Request-Email
        @error status 401
        handle_response @error {
            redir * https://your-auth-domain.example.com/oauth2/sign_in?rd={scheme}://{host}{uri}
        }
    }
    reverse_proxy 127.0.0.1:8765 {
        header_up X-Forwarded-Email {http.request.header.X-Auth-Request-Email}
    }
}

Two easy-to-miss oauth2-proxy settings this depends on: set_xauthrequest = true (without it, /oauth2/auth never returns the email header for copy_headers to copy — every request silently arrives with an empty identity), and an exact-match allowlist via authenticated_emails_file if you want to restrict to specific people (email_domains matches a domain, not a full address — setting it to a full email address matches nothing, ever).

Architecture

ops-dashboard/
  server.py        Python stdlib backend: REST API + WebSocket terminal bridge
  auth.py          users/sessions/RBAC/audit log (SQLite, stdlib sqlite3)
  auth.db          created on first run, gitignored, 0600
  servers.json     saved servers (edit from the UI), gitignored
  keys/            uploaded private keys, gitignored, 0700 dir / 0600 files
  static/          production frontend build output (or a placeholder page in dev)
  web/             React + Vite + Tailwind frontend (the app)

The web app proxies /api and /ws to the Python backend, which reaches each host over your SSH config / key. Every request (except the static shell and the auth endpoints themselves) requires a valid session; mutating actions are further validated server-side (RBAC scoping, path/input sanitization) regardless of what the UI allows.

Roadmap (next)

Docker Compose packaging · GPU job scheduler · RDP/VNC connection types (both need a real protocol-gateway service — a proprietary binary protocol and, for VNC, a block cipher for the auth handshake — neither fits in Python stdlib; Telnet, needing neither, already shipped below) · more themes (OLED, Nord, Catppuccin) · Linux/macOS parity for "This PC" local mode (currently Windows-only) · Kubernetes.

Note on the Credentials manager: stored secrets live in the same auth.db SQLite file as password hashes, protected the same way (filesystem 0600, never returned to the frontend) — not encrypted at rest with a separate key, since stdlib has no block-cipher primitive and a hand-rolled one isn't worth shipping. Fine for a self-hosted single file already protected by OS permissions; know that before storing something you wouldn't want readable by whoever can read auth.db.

_Shipped: multi-user accounts (local password, trusted-header/OIDC-proxy, or a native external-provider tab with admin-managed multi-provider config) · TOTP 2FA with device trust · self-service password reset · active-session & API-key management · rate-limited login · RBAC · durable audit log · multi-server connect/disconnect · Quick Connect · jump-host chaining · credentials manager · live GPU + history + alerts (rules/channels, incl. email) & desktop notifications · independent TCP reachability monitoring · terminal tabs & split · session recording + log browser · per-terminal command history · SFTP file ops + Monaco editor · port forwarding · outbound SOCKS5 proxy + TCP port-knocking · SSH key manager incl. browser upload · Personal ID public-key publishing · snippets manager · full backup/restore · host quick actions

  • customizable metrics dashboard · connections tracker · Docker (containers/images/volumes/ networks) · tmux · services · network · live log tail · cross-server execution · command palette · Wake-on-LAN before connecting · Telnet connection type (own port/auth/auto-login, terminal, and session recording, alongside SSH)._

Safety

Several hosts may be shared with other people. The UI warns before killing tmux sessions/processes. Confirm dialogs guard destructive actions. Server management and key upload are admin-only; non-admin access is explicitly scoped per server.

About

Browser-based SSH & infrastructure console: multi-server terminal, Docker/systemd/tmux control, live GPU & system monitoring, SSH tunnels, and multi-user RBAC with audit logging.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages