Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 28 additions & 7 deletions container/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
# SolClaw Agent Container
# Runs Claude Agent SDK in isolated Linux VM with browser automation
# Runs multi-model agent in isolated Linux VM with browser automation

# ── Stage 1: Build Vulcan CLI (Rust) ────────────────────────────────────────
FROM rust:1-slim AS vulcan-builder

RUN apt-get update && apt-get install -y pkg-config libssl-dev && rm -rf /var/lib/apt/lists/*

# Copy vendored source (vulcan + rise dependency)
COPY vendor/vulcan /build/vulcan
COPY vendor/rise /build/rise

WORKDIR /build/vulcan
RUN cargo build --release --bin vulcan 2>&1 \
&& strip target/release/vulcan

# ── Stage 2: Runtime container ──────────────────────────────────────────────
FROM node:22-slim

# Install system dependencies for Chromium
Expand Down Expand Up @@ -29,8 +43,11 @@ RUN apt-get update && apt-get install -y \
ENV AGENT_BROWSER_EXECUTABLE_PATH=/usr/bin/chromium
ENV PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium

# Install agent-browser, claude-code, and tsx globally (before NODE_OPTIONS is set)
RUN npm install -g agent-browser @anthropic-ai/claude-code tsx
# Install Vulcan binary from builder stage
COPY --from=vulcan-builder /build/vulcan/target/release/vulcan /usr/local/bin/vulcan

# Install agent-browser and tsx globally (before NODE_OPTIONS is set)
RUN npm install -g agent-browser tsx

# Create app directory
WORKDIR /app
Expand All @@ -56,21 +73,25 @@ RUN ln -s $(npm root -g)/tsx node_modules/tsx
# ESM interop for all node processes (safe, no tsx dependency)
ENV NODE_OPTIONS="--experimental-require-module --require /app/solana-tx-preload.cjs"

# Create workspace and IPC directories
RUN mkdir -p /workspace/group /workspace/global /workspace/extra /data/ipc/messages /data/ipc/tasks /data/ipc/input /data/ipc/transactions
# Create workspace and IPC directories (conversation/ needed for multi-model persistence)
RUN mkdir -p /workspace/group /workspace/global /workspace/extra /data/ipc/messages /data/ipc/tasks /data/ipc/input /data/ipc/transactions /data/ipc/conversation

# Backward-compat symlink: legacy scripts may still reference /workspace/ipc
RUN ln -sf /data/ipc /workspace/ipc

# Vulcan config directory and default wallet password (container is the security boundary)
ENV VULCAN_WALLET_PASSWORD=solclaw
RUN mkdir -p /home/node/.vulcan

# Create entrypoint script
# Secrets are passed via stdin JSON — temp file is deleted immediately after Node reads it
# Follow-up messages arrive via IPC files in /data/ipc/input/
RUN printf '#!/bin/bash\nset -e\ncd /app && npx tsc --outDir /tmp/dist 2>&1 >&2\nln -s /app/node_modules /tmp/dist/node_modules\nchmod -R a-w /tmp/dist\ncat > /tmp/input.json\nnode --import tsx /tmp/dist/index.js < /tmp/input.json\n' > /app/entrypoint.sh && chmod +x /app/entrypoint.sh

# Set ownership to node user (non-root) for writable directories
RUN chown -R node:node /workspace /data && chmod 777 /home/node
RUN chown -R node:node /workspace /data /home/node/.vulcan && chmod 777 /home/node

# Switch to non-root user (required for --dangerously-skip-permissions)
# Switch to non-root user
USER node

# Set working directory to group workspace
Expand Down
14 changes: 14 additions & 0 deletions container/agent-runner/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,20 @@ async function main(): Promise<void> {
sdkEnv[key] = value;
}

// Selectively expose tool-specific secrets to process.env so CLI tools
// invoked via Bash can read them (e.g. vulcan reads VULCAN_WALLET_PASSWORD).
// LLM API keys are intentionally excluded.
const TOOL_ENV_KEYS = [
'DFLOW_API_KEY',
'JUPITER_API_KEY',
'BREEZE_API_KEY',
'HELIUS_API_KEY',
];
for (const key of TOOL_ENV_KEYS) {
const val = containerInput.secrets?.[key];
if (val) process.env[key] = val;
}

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const mcpServerPath = path.join(__dirname, 'ipc-mcp-stdio.js');

Expand Down
1 change: 1 addition & 0 deletions container/agent-runner/src/known-protocols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const KNOWN_PROTOCOLS = new Set([
'metaplex',
'meteora',
'orca',
'phoenix',
'pumpfun',
'raydium',
'swig',
Expand Down
Loading