---
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` |
