Local web UI for peeking at remote files and ports over SSH
Project description
sshpeek
A small local web UI for peeking at remote machines over SSH: browse and view
files, live-view PDFs and images that auto-refresh when they change remotely
(remote LaTeX builds, plotting pipelines), open port tunnels, reach named
remote web apps at stable *.localhost URLs, and browse S3 buckets alongside.
One multiplexed SSH connection per host, kept alive in the background.
Everything that works with plain ssh <host> works here: hosts, keys,
ProxyJump, agents all come from ~/.ssh/config and your ssh-agent.
Quickstart
pip install sshpeek # or: uv tool install sshpeek
pip install 'sshpeek[s3]' # with S3 support
sshpeek # → http://127.0.0.1:8642
Or straight from the repo without installing: uv run sshpeek.
Zero config works (type any ~/.ssh/config alias in the host box), but the
point of sshpeek is the declarative config. Copy sshpeek.example.yaml to
./sshpeek.yaml or ~/.config/sshpeek/sshpeek.yaml (or point $SSHPEEK_CONFIG /
--config at it):
listen: { host: 127.0.0.1, port: 8642 }
hosts:
mercury:
tunnels:
- 5432 # 127.0.0.1:5432 -> mercury localhost:5432
- local: 18888
remote: localhost:8888
http:
jupyter: 8888 # http://jupyter.mercury.localhost:8642/
mlflow: localhost:5000
homebox:
http:
grafana: 3000
internultra: # just a host chip for browsing
s3:
data: my-bucket # chip "data" -> bucket "my-bucket"
results:
bucket: my-results-bucket
prefix: runs/ # browse only under this key prefix
profile: work # ~/.aws profile; SSO works too
Declared tunnels and services are established at startup and re-ensured every 20s, so they self-heal after connection drops or laptop sleep (re-binding the same local ports).
What it does
- Files — browse remote directories (sftp over the shared connection), view text/logs/PDFs inline, download anything. File-type icons, and a toggle for dotfiles (hidden by default).
- Live views — the
livelink next to any PDF or image opens a viewer that watches the file over SSE and re-renders when it changes, keeping your scroll position. PDF hyperlinks (URLs, citations, TOC) are clickable. Refreshes are debounced until the file size is stable across two polls, so alatexmkmid-write never renders garbage. A "Live views" panel lists every open view and can detach one remotely — its tab closes itself, handy for trimming after a long session. - S3 sources — buckets declared under
s3:appear next to the SSH hosts: same browsing, peeking, downloads and live views, with keys presented as paths. Credentials use the normal boto3 chain (env vars, profiles, SSO). Optional:pip install 'sshpeek[s3]'. - Tunnels — raw TCP binds on
127.0.0.1to any remote port through the SSH connection: databases, dashboards, anything. Declared in YAML (pinned) or opened ad hoc from the UI. - HTTP services — named remote web apps proxied by sshpeek itself under
<name>.<host>.localhost:<port>. Browsers resolve*.localhostto loopback natively, so the URLs are bookmarkable with no/etc/hostsedits. Each service lives at the root of its own origin, so apps that generate absolute paths (Jupyter, Grafana, ...) work unmodified, cookies stay isolated per service, and WebSockets are proxied too.
Tunnels vs. services: a tunnel gives you a local TCP port (use for anything
non-HTTP, or when the app itself must see 127.0.0.1); a service gives you
a stable named URL through sshpeek's proxy. Both ride the same SSH connection.
Notes
- Host keys are checked against
~/.ssh/known_hosts; if you have never ssh'd to a host from this machine, do that once first. - Binds to 127.0.0.1 by default, and the UI/API/proxied services require
auth: a fresh token printed with the startup URL, or a permanent
listen.passwordin sshpeek.yaml (so browser cookies survive restarts).listen.auth: noneopts out. A Host-header allowlist blocks DNS-rebinding from hostile web pages. Caveat: raw TCP tunnels bind plain local ports — no HTTP auth applies there, so any local process can use an open tunnel. *.localhostresolution is native in Chrome/Firefox/Safari and systemd-resolved; curl needs--resolveor a hosts entry.--port/--host/--config/-vflags on the CLI.
API
GET /api/hosts configured + connected hosts
GET /api/services declared HTTP services + proxy URLs
GET /api/ls?host=&path= directory listing (default: remote $HOME)
GET /api/file?host=&path=[&dl=1] stream file bytes
GET /api/events?host=&path= SSE change events (stable-size debounced)
GET /api/views open live views (one per events stream)
DELETE /api/views/{id} detach a live view (its tab closes itself)
GET /api/forwards list tunnels
POST /api/forwards {"host": "mercury", "port": 8888, "local": 18888}
DELETE /api/forwards/{id}
Roadmap
- Log tailing:
/api/filewith an offset param + incremental fetch, rendered as a live<pre>— cheaptail -ffor Slurm.outfiles. - Browser terminal: xterm.js over a WebSocket to an SSH PTY.
- Centralized deployment: containerize behind Caddy + Cloudflare Access;
the subdomain proxy pattern carries over (real subdomains instead of
*.localhost), tunnels become the piece that stays per-machine.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sshpeek-0.1.2.tar.gz.
File metadata
- Download URL: sshpeek-0.1.2.tar.gz
- Upload date:
- Size: 29.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cdf94b8af4d9756a7c223725698b8b6f764a4e30261c00cccaa79e4c014c76a5
|
|
| MD5 |
8e229460fc7075cfe09ec92758ac895f
|
|
| BLAKE2b-256 |
8e904486f0e2ba91487c8ddeac80e594809acb7c73de4f0b7cafa3f05bb2908f
|
Provenance
The following attestation bundles were made for sshpeek-0.1.2.tar.gz:
Publisher:
release.yml on tlamadon/sshpeek
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sshpeek-0.1.2.tar.gz -
Subject digest:
cdf94b8af4d9756a7c223725698b8b6f764a4e30261c00cccaa79e4c014c76a5 - Sigstore transparency entry: 2188026803
- Sigstore integration time:
-
Permalink:
tlamadon/sshpeek@e202ffb6fde37689b65ff130d9d5b82ce55e25fe -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/tlamadon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e202ffb6fde37689b65ff130d9d5b82ce55e25fe -
Trigger Event:
push
-
Statement type:
File details
Details for the file sshpeek-0.1.2-py3-none-any.whl.
File metadata
- Download URL: sshpeek-0.1.2-py3-none-any.whl
- Upload date:
- Size: 33.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
23a904423606ece530ee2e44c7ac102414d5646c84d6fac7ee4b148d970a2dad
|
|
| MD5 |
b05d290f8cc70f5d9e6c17a173740b7d
|
|
| BLAKE2b-256 |
0653b43106b59ae6a5b6b34db07e53ea634a74659056c85e42a1eecec77c80b9
|
Provenance
The following attestation bundles were made for sshpeek-0.1.2-py3-none-any.whl:
Publisher:
release.yml on tlamadon/sshpeek
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sshpeek-0.1.2-py3-none-any.whl -
Subject digest:
23a904423606ece530ee2e44c7ac102414d5646c84d6fac7ee4b148d970a2dad - Sigstore transparency entry: 2188026814
- Sigstore integration time:
-
Permalink:
tlamadon/sshpeek@e202ffb6fde37689b65ff130d9d5b82ce55e25fe -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/tlamadon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e202ffb6fde37689b65ff130d9d5b82ce55e25fe -
Trigger Event:
push
-
Statement type: