Skip to content

solanian/tmux-web-manager

Repository files navigation

tmux-web-manager

The open source tmux hub for multi-server terminals.

English | 한국어

Operator brief
tmux-web-manager is a distributed tmux control surface built around a central hub (main) and one or more remote/local agents (sub).
It aggregates tmux sessions and panes across machines, lets operators attach through xterm.js, and exposes relay/orchestration APIs and a thin CLI for agent-to-agent workflows.


▣ Jump Links

▣ Project Docs

▣ Features

  • xterm.js terminal view with raw PTY-backed tmux attach-session
  • multiple backend server registry with persistent storage
  • left sidebar for backend management and tmux session management
  • create sessions with target backend, working path, and optional session name
  • backend sessions exposed through HTTP + WebSocket APIs
  • tmux backend defaults to the host's default tmux server, with an optional dedicated socket mode that sources oh-my-tmux and forces mouse mode on
  • single project with main / sub modes and Docker Compose services for both

▣ Environment Variables

  • HOST: central UI bind host in main mode, default 0.0.0.0
  • PORT: central UI port in main mode, default 8787
  • BASE_URL: central UI public URL, default http://localhost:8787
  • DATA_DIR: root data directory, default ~/.tmux-web-manager
  • ALLOWED_PROJECT_ROOTS: comma-separated absolute roots allowed for tmux session paths
  • BACKEND_HOST: tmux backend bind host, default 0.0.0.0
  • BACKEND_PORT: tmux backend port, default 8788
  • BACKEND_PUBLIC_URL: base URL the central service should use for its local backend entry
  • BACKEND_NAME: display name for the local backend entry
  • BACKEND_AUTH_TOKEN: bearer token required by the backend API and WebSocket; if omitted, the agent generates and persists one automatically
  • HUB_AUTH_USERNAME: bootstrap username used only when pre-seeding credentials via env, default admin
  • HUB_AUTH_PASSWORD: bootstrap password; if omitted on first run, the web UI falls into onboarding mode and asks the operator to create the first ID/password pair
  • HUB_API_TOKEN: optional API token for CLI/automation access when hub auth is enabled; if omitted while hub auth is enabled, one is generated and stored automatically
  • HUB_SESSION_TTL_MS: hub login session lifetime in milliseconds, default 43200000 (12h)
  • HUB_SECURE_COOKIES: force Secure cookies (true/false); defaults to true when BASE_URL is https://...
  • TMUX_SOCKET_MODE: default or dedicated, default default
  • TMUX_SOCKET_NAME: dedicated tmux socket name used when TMUX_SOCKET_MODE=dedicated
  • SESSION_PREFIX: default prefix for auto-generated tmux session names
  • OH_MY_TMUX_CONF: path to the oh-my-tmux config file used only for generated managed tmux config in dedicated mode

▣ Reverse proxy / HTTPS notes

  • Set BASE_URL to the exact external hub URL that browsers will use, for example https://tmux.example.com.
  • Enable HUB_SECURE_COOKIES=true when serving behind HTTPS.
  • If you terminate TLS at a reverse proxy, forward X-Forwarded-Proto so origin checks and secure-cookie expectations stay aligned.
  • Browser session writes now enforce origin + CSRF checks, so operators should use one canonical hub origin instead of mixing loopback/LAN/public URLs in the same browser session.

▣ Run Locally

npm install
npm run build
npm run start:main

By default the app binds HOST=0.0.0.0 and BACKEND_HOST=0.0.0.0, so the central UI and local backend are LAN-accessible unless you override them to loopback-only addresses such as 127.0.0.1.

By default the backend attaches to the host's default tmux server. If you want the old isolated behavior, set TMUX_SOCKET_MODE=dedicated.

Each agent requires a backend auth token. If BACKEND_AUTH_TOKEN is not set, the agent generates one automatically and stores it in:

$DATA_DIR/backend/agent-auth-token

Hub-side backend registration must use that token.

The hub now defaults to a login-first web flow. On a brand-new install with no saved hub credentials, the first browser visit shows an onboarding screen that asks the operator to create the initial user ID and password. If you prefer unattended bootstrap, pre-seed HUB_AUTH_USERNAME / HUB_AUTH_PASSWORD in the environment instead.

A hub API token is also available for CLI/automation access via:

$DATA_DIR/central/hub-api-token

Backend-only mode:

npm run start:sub

For a long-running auto-restarting host process:

nohup ./scripts/run-main-supervised.sh >/tmp/tmux-web-manager-supervised/nohup.out 2>&1 &

For a more robust user-level service, install the bundled systemd unit:

mkdir -p ~/.config/systemd/user
cp ./scripts/systemd/tmux-web-manager.service ~/.config/systemd/user/
mkdir -p ~/.config/tmux-web-manager
cp ./scripts/systemd/tmux-web-manager.env.example ~/.config/tmux-web-manager/tmux-web-manager.env
# edit ~/.config/tmux-web-manager/tmux-web-manager.env for your machine
systemctl --user daemon-reload
systemctl --user enable --now tmux-web-manager.service

▣ Agent-local CLI Wrapper

The repository also ships a thin CLI wrapper for agent shells and tmux panes.

It runs locally on the agent host, but it talks to the hub relay APIs under the hood.

Examples:

npm run bridge -- panes
npm run bridge -- resolve server-b reviewer
npm run bridge -- read server-b reviewer 20
npm run bridge -- message server-b reviewer "Please review the failing test output."

When running inside tmux, the wrapper can use $TMUX_PANE as the default source pane.

Useful environment variables:

  • TWM_BASE_URL or BASE_URL
  • TWM_HUB_API_TOKEN or HUB_API_TOKEN
  • TWM_SOURCE_BACKEND
  • TWM_SOURCE_PANE
  • TWM_SOURCE_LABEL

Example:

export TWM_BASE_URL=http://127.0.0.1:8787
export TWM_HUB_API_TOKEN=$(cat ~/.tmux-web-manager/central/hub-api-token)
export TWM_SOURCE_BACKEND=server-a
export TWM_SOURCE_PANE=%1
npm run bridge -- read server-b reviewer 20

▣ Native Install

Install into a standalone prefix without Docker:

cd tmux-web-manager
./scripts/install-native.sh \
  --prefix "$HOME/.local/share/tmux-web-manager" \
  --data-dir "$HOME/.local/state/tmux-web-manager" \
  --allowed-root /workspace \
  --tmux-socket-mode default \
  --oh-my-tmux-conf "$HOME/.tmux.conf"

This generates:

  • PREFIX/app/ with dist/, node_modules/, and package metadata
  • PREFIX/etc/tmux-web-manager.env
  • PREFIX/bin/run-main.sh
  • PREFIX/bin/run-sub.sh

Run natively after install:

$HOME/.local/share/tmux-web-manager/bin/run-main.sh

▣ Docker Compose

docker compose up --build

This starts:

  • main on port 8787 with a local backend on 8788
  • sub on port 8790 as an extra remote-style backend server

▣ Inspiration / lineage

This project did not come from a vacuum. A few open-source projects were especially useful as reference points for interaction style, web-terminal delivery, and agent-oriented tmux control:

  • ShawnPana/smux — pane label/resolve, read-before-write guard, and agent-to-agent tmux automation patterns.
  • tsl0922/ttyd — lightweight terminal-over-web delivery and practical web terminal ergonomics.
  • sorenisanerd/gotty — early terminal-as-web-application ideas that informed browser-facing terminal exposure.
  • butlerx/wetty — browser-based terminal UX and remote terminal access patterns over HTTP/HTTPS.

This project diverges by combining a hub/agent registry, tmux session + pane orchestration, relay logging, and an agent-local, hub-backed CLI in one system.

▣ API Summary

Central web server:

  • GET /api/state
  • GET /api/panes
  • GET /api/orchestration/panes
  • GET /api/orchestration/panes/resolve?backendName=...&label=...
  • POST /api/backends
  • PUT /api/backends/:id
  • DELETE /api/backends/:id
  • POST /api/sessions
  • PUT /api/sessions/:backendId/:sessionId
  • DELETE /api/sessions/:backendId/:sessionId
  • POST /api/relay/send-text
  • POST /api/relay/send-text-no-enter
  • POST /api/relay/send-keys
  • POST /api/relay/message
  • POST /api/relay/read
  • POST /api/relay/panes/read
  • POST /api/relay/panes/send-text
  • POST /api/relay/panes/send-text-no-enter
  • POST /api/relay/panes/send-keys
  • POST /api/relay/panes/message
  • POST /api/relay/panes/label
  • WS /ws/terminal?backendId=...&sessionId=...

Relay usage example:

curl -X POST http://127.0.0.1:8787/api/relay/send-text \
  -H 'content-type: application/json' \
  -d '{
    "sourceBackendName": "server-a",
    "sourceSessionName": "source-session",
    "targetBackendName": "server-b",
    "targetSessionName": "target-session",
    "text": "echo hello"
  }'

Relay audit logs are written under:

$DATA_DIR/central/relay-log.jsonl

The relay request uses backend/session names only so the audit log always records a human-readable source and target.

Pane orchestration discovery example:

curl http://127.0.0.1:8787/api/orchestration/panes

This returns:

  • targetIdFormat: "backendName/paneId"
  • readBeforeWrite
  • relay endpoint hints
  • pane summaries with backendName, paneId, sessionName, location, label, currentCommand, and currentPath

Pane resolve example:

curl "http://127.0.0.1:8787/api/orchestration/panes/resolve?backendName=server-b&label=reviewer"

Pane relay read example:

curl -X POST http://127.0.0.1:8787/api/relay/panes/read \
  -H 'content-type: application/json' \
  -d '{
    "sourceBackendName": "server-a",
    "sourcePaneId": "%1",
    "targetBackendName": "server-b",
    "targetLabel": "reviewer",
    "lines": 20
  }'

Pane relay message example:

curl -X POST http://127.0.0.1:8787/api/relay/panes/message \
  -H 'content-type: application/json' \
  -d '{
    "sourceBackendName": "server-a",
    "sourcePaneId": "%1",
    "targetBackendName": "server-b",
    "targetLabel": "reviewer",
    "text": "Please review the failing test output."
  }'

Pane label example:

curl -X POST http://127.0.0.1:8787/api/relay/panes/label \
  -H 'content-type: application/json' \
  -d '{
    "sourceBackendName": "server-a",
    "sourcePaneId": "%1",
    "targetBackendName": "server-b",
    "targetPaneId": "%12",
    "label": "reviewer"
  }'

If a pane has no explicit label, the system derives one from the session name with a numeric suffix such as build-1, build-2. Those derived labels also work for discovery and resolve.

tmux backend server:

  • GET /api/health
  • GET /api/sessions
  • GET /api/panes
  • GET /api/panes/resolve/:label
  • POST /api/sessions
  • POST /api/sessions/by-name/:sessionName/send-text
  • POST /api/sessions/by-name/:sessionName/send-text-no-enter
  • POST /api/sessions/by-name/:sessionName/send-keys
  • POST /api/sessions/by-name/:sessionName/message
  • GET /api/sessions/by-name/:sessionName/read
  • POST /api/panes/by-id/:paneId/label
  • POST /api/panes/by-id/:paneId/send-text
  • POST /api/panes/by-id/:paneId/send-text-no-enter
  • POST /api/panes/by-id/:paneId/send-keys
  • POST /api/panes/by-id/:paneId/message
  • GET /api/panes/by-id/:paneId/read
  • PUT /api/sessions/:id
  • DELETE /api/sessions/:id
  • WS /ws/sessions/:id

Notes

  • Native install expects node and tmux to exist on the host.
  • In dedicated mode, the generated tmux config sources OH_MY_TMUX_CONF and forces mouse on.

About

A web-based tmux session manager and relay hub for multi-server environments.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors