Skip to content

balyakin/ssh-apphub

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SSH App Hub

SSH App Hub is a small local control panel for web apps that already run on your SSH hosts.

It finds tools such as JupyterLab, TensorBoard, MLflow, Streamlit, Grafana, Prometheus and code-server on a remote machine, then opens them through ordinary ssh -N -L tunnels bound to your loopback interface. The goal is simple: stop remembering remote ports, stop copy-pasting half-redacted URLs, and keep SSH in charge of authentication.

SSH App Hub web UI

What It Does

  • Scans an SSH alias with read-only remote commands: ps, ss, lsof, and Jupyter's server list.
  • Shows discovered apps in a local web UI and a CLI.
  • Starts and reuses OpenSSH local-forward tunnels for selected apps.
  • Supports manifest files for apps that should be named, pinned, or treated as defaults.
  • Stores hosts, discovered apps, and tunnel events in a local SQLite database.
  • Keeps API access local and token-protected by default.

This is not a hosted gateway, a reverse proxy, or a new SSH client. It is a local helper around the OpenSSH you already use.

Requirements

  • Python 3.12+
  • uv
  • OpenSSH client available as ssh
  • SSH aliases configured in ~/.ssh/config

Remote discovery works best when the target host has the usual process and socket tools available. Missing tools do not abort the whole scan; SSH App Hub returns warnings and keeps whatever it could confidently identify.

Quick Start

uv sync --extra dev
uv run ssh-apphub init
uv run ssh-apphub check-ssh gpu-box
uv run ssh-apphub serve --no-browser

Open:

http://127.0.0.1:8765/

If the UI asks for an API token:

uv run ssh-apphub token --show

Scan a host and open an app:

uv run ssh-apphub scan gpu-box
uv run ssh-apphub open gpu-box :8888

scan, open, hosts, and tunnels talk to the running local server. If the server is not running, the CLI exits with SERVER_NOT_RUNNING.

Typical Workflow

  1. Add an SSH alias to ~/.ssh/config, for example gpu-box.
  2. Start your remote tool on that host: JupyterLab on 127.0.0.1:8888, TensorBoard on 6006, and so on.
  3. Run ssh-apphub serve.
  4. Use the web UI or ssh-apphub scan gpu-box to discover apps.
  5. Click Open, or run ssh-apphub open gpu-box :8888.

SSH App Hub allocates a local port, starts an OpenSSH tunnel, and opens the resulting local URL.

CLI

ssh-apphub init
ssh-apphub serve --host 127.0.0.1 --port 8765 --no-browser
ssh-apphub check-ssh gpu-box
ssh-apphub scan gpu-box
ssh-apphub open gpu-box :8888
ssh-apphub hosts list
ssh-apphub hosts add gpu-box --name "GPU box"
ssh-apphub hosts remove <host_id>
ssh-apphub hosts import --dry-run
ssh-apphub tunnels list
ssh-apphub tunnels stop <tunnel_id>

Install it as a tool from a checkout:

uv tool install .

Manifests

Automatic discovery is useful, but explicit manifests are better for tools you use every day. Put YAML files in:

~/.config/ssh-apphub/manifests/*.yaml

Example:

version: 1
apps:
  - id: "jupyter-lab-main"
    host: "gpu-box"
    type: "jupyter"
    name: "JupyterLab main"
    is_default: true
    connect:
      scheme: "http"
      remote_host: "127.0.0.1"
      remote_port: 8888
      path: "/lab"
    health:
      path: "/api/status"
      expected_statuses: [200, 302, 401, 403]
      timeout_seconds: 5
    tags: ["ml", "gpu"]

See docs/manifest-format.md for the full schema and validation rules.

Configuration

ssh-apphub init creates private config and data directories using XDG paths:

~/.config/ssh-apphub/config.yaml
~/.config/ssh-apphub/manifests/
~/.local/share/ssh-apphub/token
~/.local/share/ssh-apphub/apphub.sqlite3

Useful environment overrides:

SSH_APPHUB_SERVER__HOST=127.0.0.1
SSH_APPHUB_SERVER__PORT=8765
SSH_APPHUB_SERVER__OPEN_BROWSER=false
SSH_APPHUB_RUNTIME__MAX_PARALLEL_SCANS=3
SSH_APPHUB_NO_BROWSER=true

For isolated development or tests:

SSH_APPHUB_CONFIG_DIR=/tmp/ssh-apphub-config \
SSH_APPHUB_DATA_DIR=/tmp/ssh-apphub-data \
uv run ssh-apphub serve --no-browser

Security Model

The short version:

  • The HTTP server binds to 127.0.0.1 by default.
  • Every /api/v1/* endpoint except /api/v1/healthz requires X-AppHub-Token.
  • The token is stored separately from config.yaml.
  • Private keys are never read by the app; OpenSSH handles keys, agents, ProxyJump, and host verification.
  • API responses and logs avoid returning discovered secrets.
  • Host-header checks reduce DNS rebinding risk for local API calls.

The default host key mode is accept-new, which is convenient for first-time use. If you want stricter behavior, edit config.yaml:

security:
  host_key_checking: strict

Read docs/threat-model.md before exposing anything beyond loopback.

Troubleshooting

SERVER_NOT_RUNNING : Start ssh-apphub serve first. Most CLI commands are local API clients.

SSH_CONFIG_INVALID : Run ssh -G -- <alias> and fix the SSH config entry.

SSH_AUTH_FAILED : Confirm key auth and ssh-agent outside SSH App Hub.

REMOTE_COMMAND_TIMEOUT : Increase the scan timeout or check whether the remote shell is slow.

LOCAL_PORT_UNAVAILABLE : Adjust runtime.local_port_min and runtime.local_port_max.

More Docs

Development

uv sync --extra dev
uv run pytest
uv run ruff check .
uv run mypy src

The test suite covers config loading, parsing, discovery, API contracts, tunnel lifecycle behavior, redaction, database repositories, and the static web UI.

This project was developed with AI assistance and is maintained by the author.

Releases

No releases published

Packages

 
 
 

Contributors