Container Conduit
Public 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
name: container-conduit author: Noah description: "Use when the user wants to manage remote containers via Container Conduit — connecting satellite containers, running commands remotely, transferring files, opening interactive PTY shells, viewing the Conduit dashboard, or installing the Conduit agent on new containers. Covers the WebSocket bridge between a main Gallia container and satellite Adom containers."
Container Conduit
Container Conduit is a WebSocket bridge that connects a main Gallia container to one or more satellite Adom containers. It enables remote command execution, file transfer, system status queries, and interactive PTY terminal sessions — all from Claude Code or the Gallia Viewer dashboard.
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
- server.js — WebSocket server on port 8800 that accepts agent connections, plus an HTTP API on port 8801 for MCP tools and the dashboard
- agent.js — Lightweight satellite agent that connects back to the main container and handles exec, file, status, and PTY commands
- mcp/server.js — MCP stdio server exposing Container Conduit tools to Claude Code
- dashboard.html — Interactive management dashboard displayed in Gallia Viewer with Shell (xterm.js), Terminal, and Prompts tabs
Key Files
| File | Location | Purpose |
|---|---|---|
server.js |
services/container-conduit/server.js |
Main WS+HTTP server |
agent.js |
services/container-conduit/agent/agent.js |
Satellite agent |
install.sh |
services/container-conduit/agent/install.sh |
One-line agent bootstrap |
mcp/server.js |
services/container-conduit/mcp/server.js |
MCP tool definitions |
dashboard.html |
services/container-conduit/dashboard.html |
GV management dashboard |
service.json |
services/container-conduit/service.json |
Auto-start manifest |
MCP Tools
All tools are prefixed container_ in the MCP server:
| Tool | Description |
|---|---|
container_list |
List all connected satellite containers |
container_exec |
Execute a shell command on a specific container |
container_exec_all |
Execute a command on ALL connected containers |
container_file_read |
Read a file from a remote container (base64) |
container_file_write |
Write a file to a remote container (base64) |
container_file_list |
List directory contents on a remote container |
container_status |
Get system status (memory, disk, uptime) of a container |
container_kick |
Disconnect a container (it will auto-reconnect) |
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) |
container_shell_stop |
Stop a persistent shell session |
container_dashboard |
Display the Conduit dashboard in Gallia Viewer |
container_create |
Create a new Adom container (repo + curium + container) |
container_install_cmd |
Generate the one-liner install command for a satellite |
Installing the Agent on a Satellite Container
One-liner install (direct network)
curl -sL http://<MAIN_IP>:8800/agent/install | CC_NAME=my-service bash
Proxy install (Coder proxy for cross-container communication)
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://...) |
Dashboard (Gallia Viewer)
Open the dashboard via MCP tool:
container_dashboard()
The dashboard has three tabs:
- Shell — Interactive xterm.js terminal connected to a remote container via PTY WebSocket relay. Supports full TTY (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.
Persistent Shell Sessions (MCP)
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:
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_stop(sessionId)
How it works: The server spawns a PTY on the agent, then wraps each command with SOH-byte sentinel markers to detect completion and extract output. Sessions auto-cleanup after 30 minutes of inactivity.
PTY Terminal (Dashboard)
The Shell tab provides a full interactive terminal via:
Dashboard (xterm.js) → WS port 8801 /pty → server.js relay → agent.js → script -qfc 'bash -i' /dev/null
The agent uses script to allocate a real PTY without requiring node-pty. This supports curses-based applications (nano, vim, htop, etc.).
Auto-Start
The service.json manifest registers Container Conduit with the gallia auto-start system:
{
"name": "container-conduit",
"port": 8800,
"health": "tcp",
"start": "node server.js",
"cwd": "."
}
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 between containers | Use CC_BASE_URL with the Coder proxy URL |
\r': command not found during install |
CRLF line endings in agent files | 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 in srcdoc iframe | MCP server injects __CONDUIT_API_BASE__ — restart MCP if stale |
| PTY timeout on Connect | Agent running old code without PTY support | 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 |
---
name: container-conduit
author: Noah
description: "Use when the user wants to manage remote containers via Container Conduit — connecting satellite containers, running commands remotely, transferring files, opening interactive PTY shells, viewing the Conduit dashboard, or installing the Conduit agent on new containers. Covers the WebSocket bridge between a main Gallia container and satellite Adom containers."
---
# Container Conduit
Container Conduit is a WebSocket bridge that connects a main Gallia container to one or more satellite Adom containers. It enables remote command execution, file transfer, system status queries, and interactive PTY terminal sessions — all from Claude Code or the Gallia Viewer dashboard.
## 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
```
- **server.js** — WebSocket server on port 8800 that accepts agent connections, plus an HTTP API on port 8801 for MCP tools and the dashboard
- **agent.js** — Lightweight satellite agent that connects back to the main container and handles exec, file, status, and PTY commands
- **mcp/server.js** — MCP stdio server exposing Container Conduit tools to Claude Code
- **dashboard.html** — Interactive management dashboard displayed in Gallia Viewer with Shell (xterm.js), Terminal, and Prompts tabs
## Key Files
| File | Location | Purpose |
|------|----------|---------|
| `server.js` | `services/container-conduit/server.js` | Main WS+HTTP server |
| `agent.js` | `services/container-conduit/agent/agent.js` | Satellite agent |
| `install.sh` | `services/container-conduit/agent/install.sh` | One-line agent bootstrap |
| `mcp/server.js` | `services/container-conduit/mcp/server.js` | MCP tool definitions |
| `dashboard.html` | `services/container-conduit/dashboard.html` | GV management dashboard |
| `service.json` | `services/container-conduit/service.json` | Auto-start manifest |
## MCP Tools
All tools are prefixed `container_` in the MCP server:
| Tool | Description |
|------|-------------|
| `container_list` | List all connected satellite containers |
| `container_exec` | Execute a shell command on a specific container |
| `container_exec_all` | Execute a command on ALL connected containers |
| `container_file_read` | Read a file from a remote container (base64) |
| `container_file_write` | Write a file to a remote container (base64) |
| `container_file_list` | List directory contents on a remote container |
| `container_status` | Get system status (memory, disk, uptime) of a container |
| `container_kick` | Disconnect a container (it will auto-reconnect) |
| `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) |
| `container_shell_stop` | Stop a persistent shell session |
| `container_dashboard` | Display the Conduit dashboard in Gallia Viewer |
| `container_create` | Create a new Adom container (repo + curium + container) |
| `container_install_cmd` | Generate the one-liner install command for a satellite |
## Installing the Agent on a Satellite Container
### One-liner install (direct network)
```bash
curl -sL http://<MAIN_IP>:8800/agent/install | CC_NAME=my-service bash
```
### Proxy install (Coder proxy for cross-container communication)
```bash
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:
1. Creates `/opt/container-conduit/` owned by `adom`
2. Downloads `agent.js` and `package.json` from the main container
3. Runs `npm install`
4. Writes `config.json` with WebSocket URL and token
5. Starts the agent from `~/project`
6. Adds a cron `@reboot` entry 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://...`) |
## Dashboard (Gallia Viewer)
Open the dashboard via MCP tool:
```
container_dashboard()
```
The dashboard has three tabs:
1. **Shell** — Interactive xterm.js terminal connected to a remote container via PTY WebSocket relay. Supports full TTY (nano, vim, htop, etc.)
2. **Terminal** — Simple command execution with output display (non-interactive)
3. **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.
## Persistent Shell Sessions (MCP)
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:
```
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_stop(sessionId)
```
**How it works:** The server spawns a PTY on the agent, then wraps each command with SOH-byte sentinel markers to detect completion and extract output. Sessions auto-cleanup after 30 minutes of inactivity.
## PTY Terminal (Dashboard)
The Shell tab provides a full interactive terminal via:
```
Dashboard (xterm.js) → WS port 8801 /pty → server.js relay → agent.js → script -qfc 'bash -i' /dev/null
```
The agent uses `script` to allocate a real PTY without requiring `node-pty`. This supports curses-based applications (nano, vim, htop, etc.).
## Auto-Start
The `service.json` manifest registers Container Conduit with the gallia auto-start system:
```json
{
"name": "container-conduit",
"port": 8800,
"health": "tcp",
"start": "node server.js",
"cwd": "."
}
```
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 between containers | Use `CC_BASE_URL` with the Coder proxy URL |
| `\r': command not found` during install | CRLF line endings in agent files | 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 in srcdoc iframe | MCP server injects `__CONDUIT_API_BASE__` — restart MCP if stale |
| PTY timeout on Connect | Agent running old code without PTY support | 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` |