Container Conduit
UnreviewedWebSocket bridge connecting a main Gallia container to satellite Adom containers — remote command execution, persistent shell sessions, file transfer, system monitoring, and an interactive terminal da
Container Conduit
Container Conduit is a WebSocket bridge that connects a main Gallia container to one or more satellite Adom containers. It gives Claude Code full remote control over satellite containers — execute commands, transfer files, monitor system health, and open persistent interactive shell sessions — all without leaving your main workspace.
The Gallia Viewer dashboard provides a browser-based interactive terminal (xterm.js) with full TTY support for curses applications like vim, nano, and htop.
Architecture
Main Container (Gallia) Satellite Container(s)
server.js (WS port 8800) ←──WS──→ agent.js
HTTP API (port 8801) ↑
↑ install.sh (bootstrap)
MCP tools (mcp/server.js)
↑
Claude Code / Dashboard
| Component | Role |
|---|---|
| server.js | WebSocket server (port 8800) + HTTP API (port 8801) — accepts agent connections, relays commands, serves the dashboard |
| agent.js | Lightweight satellite agent — handles exec, file ops, system status, and PTY sessions |
| mcp/server.js | MCP stdio server — exposes all Container Conduit tools to Claude Code |
| dashboard.html | Interactive management dashboard in Gallia Viewer with Shell, Terminal, and Prompts tabs |
MCP Tools
All tools are prefixed container_ and available to Claude Code via MCP:
Command Execution
| Tool | Description |
|---|---|
container_exec |
Execute a shell command on a specific container (one-shot) |
container_exec_all |
Execute a command on all connected containers simultaneously |
Persistent Shell Sessions
| Tool | Description |
|---|---|
container_shell_start |
Start a persistent interactive shell session (returns sessionId) |
container_shell_exec |
Run a command in a persistent shell — state persists across calls (cwd, env vars, aliases) |
container_shell_stop |
Stop a persistent shell session |
File Operations
| Tool | Description |
|---|---|
container_file_read |
Read a file from a remote container |
container_file_write |
Write a file to a remote container |
container_file_list |
List directory contents on a remote container |
Container Management
| Tool | Description |
|---|---|
container_list |
List all connected satellite containers |
container_status |
Get system status (memory, disk, uptime) of a container |
container_kick |
Disconnect a container (it will auto-reconnect) |
container_create |
Create a new Adom container (repo + curium + container) |
container_install_cmd |
Generate the one-liner install command for a satellite |
container_dashboard |
Display the Conduit dashboard in Gallia Viewer |
Persistent Shell Sessions
The container_shell_* tools give Claude Code a persistent interactive shell on any satellite container. Unlike container_exec (which spawns a new process per call), shell sessions preserve state across calls:
container_shell_start("service-test1") → sessionId
container_shell_exec(sessionId, "cd /tmp")
container_shell_exec(sessionId, "pwd") → /tmp (cwd persists!)
container_shell_exec(sessionId, "export FOO=bar")
container_shell_exec(sessionId, "echo $FOO") → bar (env vars persist!)
container_shell_exec(sessionId, "whoami") → adom
container_shell_stop(sessionId)
How It Works
shell_startspawns a PTY on the satellite agent (real terminal viascript -qfc 'bash -i')shell_execwraps each command with SOH-byte sentinel markers, sends it as PTY input, and waits for the output between the markers- The server buffers PTY output and uses pattern matching to detect command completion and extract the result
- Sessions auto-cleanup after 30 minutes of inactivity
This approach gives Claude Code a true interactive shell experience — including sudo, package management, process control, and anything else you'd do in a terminal — all through the MCP tool interface.
Dashboard (Gallia Viewer)
Open the interactive management dashboard:
container_dashboard()
The dashboard has three tabs:
- Shell — Full interactive xterm.js terminal connected to a remote container via PTY WebSocket relay. Supports curses-based applications (
nano,vim,htop, etc.) - Terminal — Simple command execution with output display (non-interactive)
- Claude Prompts — Pre-built prompts for common container management tasks
The left panel shows all connected containers with name, hostname, IP, capabilities, and connection age.
Installing the Agent
One-liner install (direct network)
curl -sL http://<MAIN_IP>:8800/agent/install | CC_NAME=my-service bash
Proxy install (cross-container via Coder proxy)
curl -sL https://coder.<SLUG>.containers.adom.inc/proxy/8800/agent/install | \
CC_BASE_URL=https://coder.<SLUG>.containers.adom.inc/proxy/8800 \
CC_NAME=my-service sudo -E bash
The install script:
- Creates
/opt/container-conduit/owned byadom - Downloads
agent.jsandpackage.jsonfrom the main container - Runs
npm install - Writes
config.jsonwith WebSocket URL and token - Starts the agent from
~/project - Adds a cron
@rebootentry for auto-start
Environment Variables
| Variable | Default | Description |
|---|---|---|
CC_HOST |
(required) | Main container IP |
CC_PORT |
8800 |
Main container WS port |
CC_TOKEN |
adom-container-token-2025 |
Authentication token |
CC_NAME |
$(hostname) |
Container name shown in dashboard |
CC_BASE_URL |
http://$CC_HOST:$CC_PORT |
Override download URL (for proxy setups) |
CC_WS_URL |
auto-detected | Override WebSocket URL (e.g. wss://...) |
Auto-Start
Container Conduit registers with the Gallia auto-start system via service.json. Both gallia-start.sh and gallia-watchdog.sh auto-discover this manifest — the server starts on boot and restarts automatically if it crashes.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Agent can't connect | Internal hostname not routable | Use CC_BASE_URL with the Coder proxy URL |
\r': command not found during install |
CRLF line endings | Run sed -i 's/\r$//' /opt/container-conduit/agent.js |
Permission denied on /opt/container-conduit |
Script not run with sudo | Use sudo -E bash to preserve env vars |
| Dashboard shows 0 containers | API URL detection fails | Restart MCP server (it injects __CONDUIT_API_BASE__) |
| PTY timeout on Connect | Agent running old code | Restart agent with updated agent.js |
| Shell tab WebSocket error | Port 8801 not reachable via proxy | Ensure server.js binds 8801 to 0.0.0.0 |