termelix
Self-hosted SSH and tmux management, for humans and their agents.
One supervised Elixir app on the BEAM: a browser terminal, your host fleet, and a live tmux monitor. Agents drive the same tmux sessions you can attach to — over a tiny CLI or MCP, with keys that are deliberately narrower than you.
Your fleet, in panes.
Hosts with folders, tags, and credentials on the left. Real terminals and a live tmux tree on the right. Everything runs on your server, in one container.
Browser SSH terminal
OTP-native :ssh with PTY, resize, and key or password auth. Sessions detach instead of dying: close the tab, come back, replay 512 KB of scrollback.
Host fleet
Folders, tags, reusable credentials, reachability probes, and Wake-on-LAN. Secrets are write-only: the read APIs answer with hasPassword, never the password.
tmux monitor
Sessions → windows → panes across every host, with per-pane CPU and memory and an activity classifier. It recognizes claude, codex, aider, and friends by name — your agent fleet at a glance.
SFTP file manager
Browse, edit, upload, and download over :ssh_sftp on the same connection machinery — with pinned paths and shortcuts per host.
SSH tunnels
Supervised local forwards with auto-reconnect — each tunnel its own OTP process. Jump-host chains and a SOCKS5 dialer are built and tested; wiring them into the connect path is next.
Serious auth
OIDC/SSO, LDAP, TOTP 2FA, RBAC with per-host grants, trusted devices, session revocation — and AES-256-GCM encryption at rest for every secret you save.
Agents get a terminal you can watch.
An agent opens a named tmux session on a real host, runs
something in it, and is told when that something finishes or
starts asking questions. The work happens in a visible tmux
session: tmux attach at any moment to see — and
take over — exactly what the agent is doing.
Browser
The SPA for humans: fleet, terminals, monitor, admin.
tmx CLI
One bash script, one dependency: curl. Every command is a single HTTP call to /agent.
MCP
POST /mcp for Claude Code, Codex, and any MCP client speaking streamable HTTP.
All three doors end in the same Termelix.Tmux modules, so
the verbs cannot drift apart.
Keys narrower than you
- tmux:read list panes, capture a screen
- tmux:write open a session, run a command, send keys
- tmux:wait block until a pane changes state
- Hosts are named explicitly on the key — an empty list means no hosts
- A key cannot read credentials, edit hosts, or mint another key
- 60 acting calls/min per user and host, 8 concurrent waits — every session open, command, and keystroke batch audited per key
The loop that works
open_session— idempotent, stable name, returns the panerun_command— types it, presses Enter, returns at oncewait_for_pane— sleeps until the pane needs attentioncapture_pane— read the screen, with an activity verdictsend_keys— answer the prompt, loop again
Wire up your agent
# log in, list host ids, mint a scoped key (shown exactly once)
curl -s -c jar.txt -X POST "$BASE/users/login" \
-H 'Content-Type: application/json' \
-d '{"username":"you","password":"…"}'
curl -s -b jar.txt "$BASE/host/db/host" | jq -c '[.[] | {id, name}]'
curl -s -b jar.txt -X POST "$BASE/api-keys" \
-H 'Content-Type: application/json' \
-d '{"name":"claude-code",
"scopes":["tmux:read","tmux:write","tmux:wait"],
"hostIds":[3],"expiresAt":"2026-12-31T00:00:00Z"}'claude mcp add --transport http termelix \
https://termelix.example.com/mcp \
--header "Authorization: Bearer tmx_…"
# tools appear as mcp__termelix__list_hosts, open_session, …
# allowlist the read-only tools, keep run_command and send_keys interactive[mcp_servers.termelix]
url = "https://termelix.example.com/mcp"
bearer_token_env_var = "TERMELIX_TOKEN"
tool_timeout_sec = 900 # wait_for_pane needs headroom
default_tools_approval_mode = "approve"
[mcp_servers.termelix.tools.run_command]
approval_mode = "prompt" # keep typing into terminals interactiveexport TERMELIX_URL=https://termelix.example.com
export TERMELIX_TOKEN=tmx_…
tmx hosts # what this key may touch
tmx open 3 build /srv/app # create or attach; prints the pane id
tmx watch 3 %4 'make test' # run, wait, then show the screen
tmx capture 3 %4 # read the screen
tmx keys 3 %4 y # answer a prompt| Tool | Scope | What it does |
|---|---|---|
list_hosts | — | The hosts this key may act on, plus its scopes. Start here. |
list_panes | read | Every pane on a host, with its state and the evidence for it. |
open_session | write | Create or attach a named session; returns the pane to work in. |
capture_pane | read | The last lines of a pane's screen, plus an activity verdict. |
run_command | write | Type a single-line command, press Enter, return at once. |
send_keys | write | Type without Enter — for answering prompts. |
wait_for_pane | wait | Block until the pane needs attention or the command finishes. |
tools/list is filtered by the key's scopes, so an agent
is never offered a tool it will be refused.
Termix, ported to the BEAM.
Termelix began as an Elixir/Phoenix port of Termix, the self-hosted SSH platform. Think of it as Termix with less features and deeper tmux and agent support.
The trade was deliberate. RDP, VNC, Docker panels, and the widget dashboard stayed behind; what remained got the depth treatment: terminals, tmux, and the agent interface. The React frontend carried over, because the Phoenix app preserves the HTTP and WebSocket contract it depends on.
The BEAM earns its keep: OTP ships a native SSH client, so connections, pools, and tunnels are supervised processes with restart semantics instead of singletons with retry loops. When a terminal dies, its supervisor knows. When you deploy, sessions drain gracefully.
One container. One port. Your data stays yours.
Embedded SQLite, auto-migrating at boot. Root secrets generate themselves on first start. The first account becomes the admin, and one toggle switches registration off. There is no cloud half.
services:
termelix:
build:
context: ..
dockerfile: docker/Dockerfile
container_name: termelix-beam
restart: unless-stopped
stop_grace_period: 30s # live terminals drain gracefully
ports:
- "8080:8080"
environment:
PHX_HOST: termelix.example.com
volumes:
- termix-data:/app/data
volumes:
termix-data: # pre-rename key kept — existing Termix data stays attachedGoing open source, on GitHub.
Termelix goes fully open source with its public release. The repository is being prepared now; the frontend and mobile companions carry their Apache-2.0 lineage from Termix. The list below is how you hear the moment the release ships.
Release news by email.
A handful of messages: the GitHub release, then major versions. You also join sysinit-news, the sysinit newsletter with occasional product and book updates.
The signup uses double opt-in and covers both lists. Every message includes an unsubscribe link.