Hub Setup
The hub is a coordinator that manages multiple muxd-daemon instances (nodes) across machines. It tracks registered nodes, proxies requests between them, serves the web dashboard, and manages workers.
There are three components:
- muxd-hub — the coordinator process. Runs on one machine.
- muxd-daemon — the agent server. Runs on one or more machines, registers with the hub.
- muxd — the TUI client. Connects to the hub from your laptop.
Quick start
Same machine
# Terminal 1: start the hub (prints token + QR code)
MUXD_HUB_BIND=0.0.0.0 muxd-hub
# Terminal 2: start the daemon
MUXD_HUB_URL=http://localhost:4097 \
MUXD_HUB_NODE_TOKEN=<token> \
MUXD_BIND=0.0.0.0 \
muxd-daemon
# Terminal 3: connect from TUI
muxd --remote localhost:4097 --token <token>
Separate servers
# Server A (hub)
MUXD_HUB_BIND=0.0.0.0 muxd-hub
# Server B (daemon)
MUXD_HUB_URL=http://server-a:4097 \
MUXD_HUB_NODE_TOKEN=<token> \
MUXD_NODE_NAME=worker-1 \
MUXD_BIND=0.0.0.0 \
muxd-daemon
# Your laptop
muxd --remote server-a:4097 --token <token>
Adding more servers
Each additional machine just runs muxd-daemon pointing at the hub:
MUXD_HUB_URL=http://server-a:4097 \
MUXD_HUB_NODE_TOKEN=<token> \
MUXD_NODE_NAME=worker-3 \
MUXD_BIND=0.0.0.0 \
muxd-daemon
Web dashboard
The hub serves a web dashboard at its root URL. The web UI is embedded in the hub binary — no separate setup needed. Open http://<hub-ip>:4097 in your browser and enter the hub token to log in.
Pages:
- Dashboard — overview of nodes, workers, sessions, and cost
- Workers — create and manage autonomous agents triggered by webhooks or schedules
- Worker Detail — activity feed with approve/reject for human-in-the-loop
- Nodes — registered daemon nodes with status
In development, the web UI runs separately with hot reload:
cd web && npm run dev # starts on :5173, proxies /api to hub on :4097
Hub configuration
The hub has no CLI flags. Configure via environment variables or config.json.
| Env Var | Config Key | What |
|---|---|---|
MUXD_HUB_BIND | hub_bind_address | Bind interface (default: localhost) |
MUXD_HUB_TOKEN | hub_auth_token | Auth token (auto-generated if not set) |
Daemon registration
Each daemon registers with the hub on startup using these settings:
| Env Var | Config Key | What |
|---|---|---|
MUXD_HUB_URL | hub_url | Hub URL (e.g. http://hub:4097) |
MUXD_HUB_NODE_TOKEN | hub_node_token | Token to authenticate with hub (same as hub's auth token) |
MUXD_NODE_NAME | hub_node_name | Display name (defaults to hostname) |
MUXD_BIND | daemon_bind_address | Bind interface (0.0.0.0 for network access) |
Set via environment variables:
# Linux / macOS
export MUXD_HUB_URL=http://hub:4097
export MUXD_HUB_NODE_TOKEN=<token>
export MUXD_NODE_NAME=worker-1
muxd-daemon
# Windows (PowerShell)
$env:MUXD_HUB_URL = "http://hub:4097"
$env:MUXD_HUB_NODE_TOKEN = "<token>"
$env:MUXD_NODE_NAME = "worker-1"
muxd-daemon
Or set inside the TUI:
/config set hub.url http://hub:4097
/config set hub.node_token <token>
/config set hub.node_name worker-1
How it works
The hub runs on port 4097 and provides:
- Node registry — nodes register on startup and send heartbeats every 30 seconds. The hub marks a node offline after 90 seconds of inactivity.
- Request proxy — session and agent traffic is routed through the hub to the target node. The hub swaps its own auth token for the node's daemon token transparently.
- Log broker — nodes push log entries to the hub. The hub stores them and streams them to SSE subscribers.
- Session aggregation — the hub queries all online nodes and returns sessions grouped by node for cross-node discovery.
- Shared memory — nodes push project memory facts to the hub and pull new facts every 60 seconds.
- Web UI — serves the browser dashboard for monitoring and worker management.
- Workers — manages autonomous agents triggered by webhooks or schedules.
Authentication
Bearer token auth (32 random bytes, hex-encoded). The token is auto-generated on first start. All protected routes validate the token via middleware.
The hub and daemons share the same ~/.config/muxd/config.json. The hub only reads hub_* keys. The daemon reads hub_url, hub_node_token, and hub_node_name along with its own model/provider/API key settings.
Service management
Install the hub as a system service:
# Linux
export XDG_RUNTIME_DIR=/run/user/$(id -u)
export DBUS_SESSION_BUS_ADDRESS=unix:path=$XDG_RUNTIME_DIR/bus
MUXD_HUB_BIND=0.0.0.0 muxd-hub -service install
muxd-hub -service start
# macOS
MUXD_HUB_BIND=0.0.0.0 muxd-hub -service install
muxd-hub -service start
# Windows (PowerShell)
$env:MUXD_HUB_BIND = "0.0.0.0"
muxd-hub -service install
muxd-hub -service start
On Linux, enable lingering (run as root, replace YOUR_USER):
loginctl enable-linger YOUR_USER
Without enable-linger, systemd kills user services when you close your SSH session.
All service commands:
muxd-hub -service status # check if running
muxd-hub -service stop # stop the service
muxd-hub -service uninstall # remove the service
muxd-hub -service qr # re-show the QR code and token
Same commands work for muxd-daemon -service <action>.
Open the firewall
The hub listens on port 4097.
Fedora / RHEL (firewalld):
sudo firewall-cmd --add-port=4097/tcp --permanent && sudo firewall-cmd --reload
Ubuntu / Debian (ufw):
sudo ufw allow 4097/tcp
Docker
git clone https://github.com/batalabs/muxd.git
cd muxd
docker compose build
# Create .env with your API keys
echo "MUXD_MODEL=claude-sonnet" > .env
echo "ANTHROPIC_API_KEY=sk-ant-..." >> .env
# Start hub + daemon
docker compose up -d
# Connect from your laptop
muxd --remote localhost:4097 --token <token>
The web dashboard is available at http://localhost:4097.
Configuration keys
Hub group
| Key | Env var | What |
|---|---|---|
hub_bind_address | MUXD_HUB_BIND | Hub bind address (default: localhost, use 0.0.0.0 for LAN access) |
hub_auth_token | MUXD_HUB_TOKEN | Bearer token for hub authentication (auto-generated on first start) |
Node group
| Key | Env var | What |
|---|---|---|
hub_url | MUXD_HUB_URL | URL of the hub this daemon registers with |
hub_node_token | MUXD_HUB_NODE_TOKEN | Token to authenticate with the hub |
hub_node_name | MUXD_NODE_NAME | Display name for this node in the hub (defaults to hostname) |