Merge fa8f88ed86 into 4583f88626
This commit is contained in:
commit
569e07da4b
12
Dockerfile
12
Dockerfile
@ -4,6 +4,18 @@ FROM node:22-bookworm
|
|||||||
RUN curl -fsSL https://bun.sh/install | bash
|
RUN curl -fsSL https://bun.sh/install | bash
|
||||||
ENV PATH="/root/.bun/bin:${PATH}"
|
ENV PATH="/root/.bun/bin:${PATH}"
|
||||||
|
|
||||||
|
# Install Docker CLI from official Docker repo (Debian docker.io is too old for Docker Desktop)
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends ca-certificates curl && \
|
||||||
|
install -m 0755 -d /etc/apt/keyrings && \
|
||||||
|
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && \
|
||||||
|
chmod a+r /etc/apt/keyrings/docker.asc && \
|
||||||
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable" > /etc/apt/sources.list.d/docker.list && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends docker-ce-cli && \
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||||
|
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
services:
|
services:
|
||||||
moltbot-gateway:
|
moltbot-gateway:
|
||||||
image: ${CLAWDBOT_IMAGE:-moltbot:local}
|
image: ${CLAWDBOT_IMAGE:-moltbot:local}
|
||||||
|
# Required for Docker socket access when creating sandbox containers (DinD)
|
||||||
|
user: root
|
||||||
environment:
|
environment:
|
||||||
HOME: /home/node
|
HOME: /home/node
|
||||||
TERM: xterm-256color
|
TERM: xterm-256color
|
||||||
@ -8,9 +10,13 @@ services:
|
|||||||
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY}
|
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY}
|
||||||
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY}
|
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY}
|
||||||
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE}
|
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE}
|
||||||
|
# Docker-in-Docker: host paths for sandbox container volume mounts
|
||||||
|
CLAWDBOT_SANDBOX_HOST_CONFIG_DIR: ${CLAWDBOT_CONFIG_DIR}
|
||||||
|
CLAWDBOT_SANDBOX_HOST_WORKSPACE_DIR: ${CLAWDBOT_WORKSPACE_DIR}
|
||||||
volumes:
|
volumes:
|
||||||
- ${CLAWDBOT_CONFIG_DIR}:/home/node/.clawdbot
|
- ${CLAWDBOT_CONFIG_DIR}:/home/node/.clawdbot
|
||||||
- ${CLAWDBOT_WORKSPACE_DIR}:/home/node/clawd
|
- ${CLAWDBOT_WORKSPACE_DIR}:/home/node/clawd
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
ports:
|
ports:
|
||||||
- "${CLAWDBOT_GATEWAY_PORT:-18789}:18789"
|
- "${CLAWDBOT_GATEWAY_PORT:-18789}:18789"
|
||||||
- "${CLAWDBOT_BRIDGE_PORT:-18790}:18790"
|
- "${CLAWDBOT_BRIDGE_PORT:-18790}:18790"
|
||||||
|
|||||||
@ -373,6 +373,38 @@ Use config:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Docker-in-Docker (gateway in container)
|
||||||
|
|
||||||
|
If the gateway itself runs in a Docker container and you want sandbox browsers,
|
||||||
|
set `cdpHost` to `host.docker.internal` (Docker Desktop) so the gateway can
|
||||||
|
reach the browser container's CDP endpoint:
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
agents: {
|
||||||
|
defaults: {
|
||||||
|
sandbox: {
|
||||||
|
browser: {
|
||||||
|
enabled: true,
|
||||||
|
cdpHost: "host.docker.internal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tools: {
|
||||||
|
sandbox: {
|
||||||
|
tools: {
|
||||||
|
allow: ["*"] // browser is denied by default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The gateway automatically remaps volume mount paths for Docker-in-Docker when
|
||||||
|
`CLAWDBOT_SANDBOX_HOST_CONFIG_DIR` and `CLAWDBOT_SANDBOX_HOST_WORKSPACE_DIR`
|
||||||
|
environment variables are set (already configured in `docker-compose.yml`).
|
||||||
|
|
||||||
Custom browser image:
|
Custom browser image:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
|
|||||||
@ -34,6 +34,7 @@ fi
|
|||||||
CHROME_ARGS+=(
|
CHROME_ARGS+=(
|
||||||
"--remote-debugging-address=127.0.0.1"
|
"--remote-debugging-address=127.0.0.1"
|
||||||
"--remote-debugging-port=${CHROME_CDP_PORT}"
|
"--remote-debugging-port=${CHROME_CDP_PORT}"
|
||||||
|
"--remote-allow-origins=*"
|
||||||
"--user-data-dir=${HOME}/.chrome"
|
"--user-data-dir=${HOME}/.chrome"
|
||||||
"--no-first-run"
|
"--no-first-run"
|
||||||
"--no-default-browser-check"
|
"--no-default-browser-check"
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import dns from "node:dns/promises";
|
||||||
import { startBrowserBridgeServer, stopBrowserBridgeServer } from "../../browser/bridge-server.js";
|
import { startBrowserBridgeServer, stopBrowserBridgeServer } from "../../browser/bridge-server.js";
|
||||||
import { type ResolvedBrowserConfig, resolveProfile } from "../../browser/config.js";
|
import { type ResolvedBrowserConfig, resolveProfile } from "../../browser/config.js";
|
||||||
import {
|
import {
|
||||||
@ -11,15 +12,45 @@ import {
|
|||||||
dockerContainerState,
|
dockerContainerState,
|
||||||
execDocker,
|
execDocker,
|
||||||
readDockerPort,
|
readDockerPort,
|
||||||
|
remapPathForDinD,
|
||||||
} from "./docker.js";
|
} from "./docker.js";
|
||||||
import { updateBrowserRegistry } from "./registry.js";
|
import { updateBrowserRegistry } from "./registry.js";
|
||||||
import { slugifySessionKey } from "./shared.js";
|
import { slugifySessionKey } from "./shared.js";
|
||||||
import { isToolAllowed } from "./tool-policy.js";
|
import { isToolAllowed } from "./tool-policy.js";
|
||||||
import type { SandboxBrowserContext, SandboxConfig } from "./types.js";
|
import type { SandboxBrowserContext, SandboxConfig } from "./types.js";
|
||||||
|
|
||||||
async function waitForSandboxCdp(params: { cdpPort: number; timeoutMs: number }): Promise<boolean> {
|
/**
|
||||||
|
* Resolve a hostname to an IPv4 address for CDP connections.
|
||||||
|
* Chrome's CDP HTTP endpoints reject non-IP Host headers, so we resolve
|
||||||
|
* hostnames like "host.docker.internal" to their IP addresses.
|
||||||
|
*/
|
||||||
|
async function resolveHostToIp(host: string): Promise<string> {
|
||||||
|
// If already an IP address (v4 or v6), return as-is
|
||||||
|
if (/^(?:\d{1,3}\.){3}\d{1,3}$/.test(host) || host.includes(":")) {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
// localhost is special-cased by Chrome
|
||||||
|
if (host === "localhost") {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const result = await dns.lookup(host, { family: 4 });
|
||||||
|
return result.address;
|
||||||
|
} catch {
|
||||||
|
// If DNS resolution fails, return original host and let caller handle the error
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForSandboxCdp(params: {
|
||||||
|
cdpHost: string;
|
||||||
|
cdpPort: number;
|
||||||
|
timeoutMs: number;
|
||||||
|
}): Promise<boolean> {
|
||||||
const deadline = Date.now() + Math.max(0, params.timeoutMs);
|
const deadline = Date.now() + Math.max(0, params.timeoutMs);
|
||||||
const url = `http://127.0.0.1:${params.cdpPort}/json/version`;
|
// Resolve hostname to IP for Chrome CDP compatibility
|
||||||
|
const resolvedHost = await resolveHostToIp(params.cdpHost);
|
||||||
|
const url = `http://${resolvedHost}:${params.cdpPort}/json/version`;
|
||||||
while (Date.now() < deadline) {
|
while (Date.now() < deadline) {
|
||||||
try {
|
try {
|
||||||
const ctrl = new AbortController();
|
const ctrl = new AbortController();
|
||||||
@ -40,18 +71,20 @@ async function waitForSandboxCdp(params: { cdpPort: number; timeoutMs: number })
|
|||||||
|
|
||||||
function buildSandboxBrowserResolvedConfig(params: {
|
function buildSandboxBrowserResolvedConfig(params: {
|
||||||
controlPort: number;
|
controlPort: number;
|
||||||
|
cdpHost: string;
|
||||||
cdpPort: number;
|
cdpPort: number;
|
||||||
headless: boolean;
|
headless: boolean;
|
||||||
evaluateEnabled: boolean;
|
evaluateEnabled: boolean;
|
||||||
}): ResolvedBrowserConfig {
|
}): ResolvedBrowserConfig {
|
||||||
const cdpHost = "127.0.0.1";
|
const isLoopback =
|
||||||
|
params.cdpHost === "127.0.0.1" || params.cdpHost === "localhost" || params.cdpHost === "::1";
|
||||||
return {
|
return {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
evaluateEnabled: params.evaluateEnabled,
|
evaluateEnabled: params.evaluateEnabled,
|
||||||
controlPort: params.controlPort,
|
controlPort: params.controlPort,
|
||||||
cdpProtocol: "http",
|
cdpProtocol: "http",
|
||||||
cdpHost,
|
cdpHost: params.cdpHost,
|
||||||
cdpIsLoopback: true,
|
cdpIsLoopback: isLoopback,
|
||||||
remoteCdpTimeoutMs: 1500,
|
remoteCdpTimeoutMs: 1500,
|
||||||
remoteCdpHandshakeTimeoutMs: 3000,
|
remoteCdpHandshakeTimeoutMs: 3000,
|
||||||
color: DEFAULT_CLAWD_BROWSER_COLOR,
|
color: DEFAULT_CLAWD_BROWSER_COLOR,
|
||||||
@ -102,12 +135,15 @@ export async function ensureSandboxBrowser(params: {
|
|||||||
params.cfg.workspaceAccess === "ro" && params.workspaceDir === params.agentWorkspaceDir
|
params.cfg.workspaceAccess === "ro" && params.workspaceDir === params.agentWorkspaceDir
|
||||||
? ":ro"
|
? ":ro"
|
||||||
: "";
|
: "";
|
||||||
args.push("-v", `${params.workspaceDir}:${params.cfg.docker.workdir}${mainMountSuffix}`);
|
// Remap paths for Docker-in-Docker scenarios
|
||||||
|
const hostWorkspaceDir = remapPathForDinD(params.workspaceDir);
|
||||||
|
args.push("-v", `${hostWorkspaceDir}:${params.cfg.docker.workdir}${mainMountSuffix}`);
|
||||||
if (params.cfg.workspaceAccess !== "none" && params.workspaceDir !== params.agentWorkspaceDir) {
|
if (params.cfg.workspaceAccess !== "none" && params.workspaceDir !== params.agentWorkspaceDir) {
|
||||||
const agentMountSuffix = params.cfg.workspaceAccess === "ro" ? ":ro" : "";
|
const agentMountSuffix = params.cfg.workspaceAccess === "ro" ? ":ro" : "";
|
||||||
|
const hostAgentWorkspaceDir = remapPathForDinD(params.agentWorkspaceDir);
|
||||||
args.push(
|
args.push(
|
||||||
"-v",
|
"-v",
|
||||||
`${params.agentWorkspaceDir}:${SANDBOX_AGENT_WORKSPACE_MOUNT}${agentMountSuffix}`,
|
`${hostAgentWorkspaceDir}:${SANDBOX_AGENT_WORKSPACE_MOUNT}${agentMountSuffix}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
args.push("-p", `127.0.0.1::${params.cfg.browser.cdpPort}`);
|
args.push("-p", `127.0.0.1::${params.cfg.browser.cdpPort}`);
|
||||||
@ -153,6 +189,9 @@ export async function ensureSandboxBrowser(params: {
|
|||||||
const ensureBridge = async () => {
|
const ensureBridge = async () => {
|
||||||
if (bridge) return bridge;
|
if (bridge) return bridge;
|
||||||
|
|
||||||
|
// Resolve hostname to IP for Chrome CDP compatibility
|
||||||
|
const resolvedCdpHost = await resolveHostToIp(params.cfg.browser.cdpHost);
|
||||||
|
|
||||||
const onEnsureAttachTarget = params.cfg.browser.autoStart
|
const onEnsureAttachTarget = params.cfg.browser.autoStart
|
||||||
? async () => {
|
? async () => {
|
||||||
const state = await dockerContainerState(containerName);
|
const state = await dockerContainerState(containerName);
|
||||||
@ -160,12 +199,13 @@ export async function ensureSandboxBrowser(params: {
|
|||||||
await execDocker(["start", containerName]);
|
await execDocker(["start", containerName]);
|
||||||
}
|
}
|
||||||
const ok = await waitForSandboxCdp({
|
const ok = await waitForSandboxCdp({
|
||||||
|
cdpHost: resolvedCdpHost,
|
||||||
cdpPort: mappedCdp,
|
cdpPort: mappedCdp,
|
||||||
timeoutMs: params.cfg.browser.autoStartTimeoutMs,
|
timeoutMs: params.cfg.browser.autoStartTimeoutMs,
|
||||||
});
|
});
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Sandbox browser CDP did not become reachable on 127.0.0.1:${mappedCdp} within ${params.cfg.browser.autoStartTimeoutMs}ms.`,
|
`Sandbox browser CDP did not become reachable on ${resolvedCdpHost}:${mappedCdp} within ${params.cfg.browser.autoStartTimeoutMs}ms.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,6 +214,7 @@ export async function ensureSandboxBrowser(params: {
|
|||||||
return await startBrowserBridgeServer({
|
return await startBrowserBridgeServer({
|
||||||
resolved: buildSandboxBrowserResolvedConfig({
|
resolved: buildSandboxBrowserResolvedConfig({
|
||||||
controlPort: 0,
|
controlPort: 0,
|
||||||
|
cdpHost: resolvedCdpHost,
|
||||||
cdpPort: mappedCdp,
|
cdpPort: mappedCdp,
|
||||||
headless: params.cfg.browser.headless,
|
headless: params.cfg.browser.headless,
|
||||||
evaluateEnabled: params.evaluateEnabled ?? DEFAULT_BROWSER_EVALUATE_ENABLED,
|
evaluateEnabled: params.evaluateEnabled ?? DEFAULT_BROWSER_EVALUATE_ENABLED,
|
||||||
@ -203,7 +244,7 @@ export async function ensureSandboxBrowser(params: {
|
|||||||
|
|
||||||
const noVncUrl =
|
const noVncUrl =
|
||||||
mappedNoVnc && params.cfg.browser.enableNoVnc && !params.cfg.browser.headless
|
mappedNoVnc && params.cfg.browser.enableNoVnc && !params.cfg.browser.headless
|
||||||
? `http://127.0.0.1:${mappedNoVnc}/vnc.html?autoconnect=1&resize=remote`
|
? `http://${params.cfg.browser.cdpHost}:${mappedNoVnc}/vnc.html?autoconnect=1&resize=remote`
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import type { MoltbotConfig } from "../../config/config.js";
|
|||||||
import { resolveAgentConfig } from "../agent-scope.js";
|
import { resolveAgentConfig } from "../agent-scope.js";
|
||||||
import {
|
import {
|
||||||
DEFAULT_SANDBOX_BROWSER_AUTOSTART_TIMEOUT_MS,
|
DEFAULT_SANDBOX_BROWSER_AUTOSTART_TIMEOUT_MS,
|
||||||
|
DEFAULT_SANDBOX_BROWSER_CDP_HOST,
|
||||||
DEFAULT_SANDBOX_BROWSER_CDP_PORT,
|
DEFAULT_SANDBOX_BROWSER_CDP_PORT,
|
||||||
DEFAULT_SANDBOX_BROWSER_IMAGE,
|
DEFAULT_SANDBOX_BROWSER_IMAGE,
|
||||||
DEFAULT_SANDBOX_BROWSER_NOVNC_PORT,
|
DEFAULT_SANDBOX_BROWSER_NOVNC_PORT,
|
||||||
@ -93,6 +94,7 @@ export function resolveSandboxBrowserConfig(params: {
|
|||||||
agentBrowser?.containerPrefix ??
|
agentBrowser?.containerPrefix ??
|
||||||
globalBrowser?.containerPrefix ??
|
globalBrowser?.containerPrefix ??
|
||||||
DEFAULT_SANDBOX_BROWSER_PREFIX,
|
DEFAULT_SANDBOX_BROWSER_PREFIX,
|
||||||
|
cdpHost: agentBrowser?.cdpHost ?? globalBrowser?.cdpHost ?? DEFAULT_SANDBOX_BROWSER_CDP_HOST,
|
||||||
cdpPort: agentBrowser?.cdpPort ?? globalBrowser?.cdpPort ?? DEFAULT_SANDBOX_BROWSER_CDP_PORT,
|
cdpPort: agentBrowser?.cdpPort ?? globalBrowser?.cdpPort ?? DEFAULT_SANDBOX_BROWSER_CDP_PORT,
|
||||||
vncPort: agentBrowser?.vncPort ?? globalBrowser?.vncPort ?? DEFAULT_SANDBOX_BROWSER_VNC_PORT,
|
vncPort: agentBrowser?.vncPort ?? globalBrowser?.vncPort ?? DEFAULT_SANDBOX_BROWSER_VNC_PORT,
|
||||||
noVncPort:
|
noVncPort:
|
||||||
|
|||||||
@ -41,6 +41,7 @@ export const DEFAULT_SANDBOX_BROWSER_IMAGE = "moltbot-sandbox-browser:bookworm-s
|
|||||||
export const DEFAULT_SANDBOX_COMMON_IMAGE = "moltbot-sandbox-common:bookworm-slim";
|
export const DEFAULT_SANDBOX_COMMON_IMAGE = "moltbot-sandbox-common:bookworm-slim";
|
||||||
|
|
||||||
export const DEFAULT_SANDBOX_BROWSER_PREFIX = "moltbot-sbx-browser-";
|
export const DEFAULT_SANDBOX_BROWSER_PREFIX = "moltbot-sbx-browser-";
|
||||||
|
export const DEFAULT_SANDBOX_BROWSER_CDP_HOST = "127.0.0.1";
|
||||||
export const DEFAULT_SANDBOX_BROWSER_CDP_PORT = 9222;
|
export const DEFAULT_SANDBOX_BROWSER_CDP_PORT = 9222;
|
||||||
export const DEFAULT_SANDBOX_BROWSER_VNC_PORT = 5900;
|
export const DEFAULT_SANDBOX_BROWSER_VNC_PORT = 5900;
|
||||||
export const DEFAULT_SANDBOX_BROWSER_NOVNC_PORT = 6080;
|
export const DEFAULT_SANDBOX_BROWSER_NOVNC_PORT = 6080;
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import os from "node:os";
|
||||||
import { spawn } from "node:child_process";
|
import { spawn } from "node:child_process";
|
||||||
|
|
||||||
import { defaultRuntime } from "../../runtime.js";
|
import { defaultRuntime } from "../../runtime.js";
|
||||||
@ -8,6 +9,41 @@ import { computeSandboxConfigHash } from "./config-hash.js";
|
|||||||
import { resolveSandboxAgentId, resolveSandboxScopeKey, slugifySessionKey } from "./shared.js";
|
import { resolveSandboxAgentId, resolveSandboxScopeKey, slugifySessionKey } from "./shared.js";
|
||||||
import type { SandboxConfig, SandboxDockerConfig, SandboxWorkspaceAccess } from "./types.js";
|
import type { SandboxConfig, SandboxDockerConfig, SandboxWorkspaceAccess } from "./types.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For Docker-in-Docker scenarios, remap container paths to host paths.
|
||||||
|
* When the gateway runs in a container and creates sandbox containers,
|
||||||
|
* the volume mount paths must be host paths, not container paths.
|
||||||
|
*
|
||||||
|
* Uses environment variables set by docker-compose:
|
||||||
|
* - CLAWDBOT_SANDBOX_HOST_CONFIG_DIR: host path for ~/.clawdbot
|
||||||
|
* - CLAWDBOT_SANDBOX_HOST_WORKSPACE_DIR: host path for ~/clawd
|
||||||
|
*/
|
||||||
|
export function remapPathForDinD(containerPath: string): string {
|
||||||
|
const hostConfigDir = process.env.CLAWDBOT_SANDBOX_HOST_CONFIG_DIR;
|
||||||
|
const hostWorkspaceDir = process.env.CLAWDBOT_SANDBOX_HOST_WORKSPACE_DIR;
|
||||||
|
|
||||||
|
// Both must be set for DinD mode, or neither (partial config is invalid)
|
||||||
|
if (!hostConfigDir || !hostWorkspaceDir) {
|
||||||
|
return containerPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
const home = os.homedir();
|
||||||
|
const containerConfigDir = `${home}/.clawdbot`;
|
||||||
|
const containerWorkspaceDir = `${home}/clawd`;
|
||||||
|
|
||||||
|
// Remap config directory paths
|
||||||
|
if (hostConfigDir && containerPath.startsWith(containerConfigDir)) {
|
||||||
|
return containerPath.replace(containerConfigDir, hostConfigDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remap workspace directory paths
|
||||||
|
if (hostWorkspaceDir && containerPath.startsWith(containerWorkspaceDir)) {
|
||||||
|
return containerPath.replace(containerWorkspaceDir, hostWorkspaceDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
return containerPath;
|
||||||
|
}
|
||||||
|
|
||||||
const HOT_CONTAINER_WINDOW_MS = 5 * 60 * 1000;
|
const HOT_CONTAINER_WINDOW_MS = 5 * 60 * 1000;
|
||||||
|
|
||||||
export function execDocker(args: string[], opts?: { allowFailure?: boolean }) {
|
export function execDocker(args: string[], opts?: { allowFailure?: boolean }) {
|
||||||
@ -189,13 +225,13 @@ async function createSandboxContainer(params: {
|
|||||||
args.push("--workdir", cfg.workdir);
|
args.push("--workdir", cfg.workdir);
|
||||||
const mainMountSuffix =
|
const mainMountSuffix =
|
||||||
params.workspaceAccess === "ro" && workspaceDir === params.agentWorkspaceDir ? ":ro" : "";
|
params.workspaceAccess === "ro" && workspaceDir === params.agentWorkspaceDir ? ":ro" : "";
|
||||||
args.push("-v", `${workspaceDir}:${cfg.workdir}${mainMountSuffix}`);
|
// Remap paths for Docker-in-Docker scenarios
|
||||||
|
const hostWorkspaceDir = remapPathForDinD(workspaceDir);
|
||||||
|
args.push("-v", `${hostWorkspaceDir}:${cfg.workdir}${mainMountSuffix}`);
|
||||||
if (params.workspaceAccess !== "none" && workspaceDir !== params.agentWorkspaceDir) {
|
if (params.workspaceAccess !== "none" && workspaceDir !== params.agentWorkspaceDir) {
|
||||||
const agentMountSuffix = params.workspaceAccess === "ro" ? ":ro" : "";
|
const agentMountSuffix = params.workspaceAccess === "ro" ? ":ro" : "";
|
||||||
args.push(
|
const hostAgentWorkspaceDir = remapPathForDinD(params.agentWorkspaceDir);
|
||||||
"-v",
|
args.push("-v", `${hostAgentWorkspaceDir}:${SANDBOX_AGENT_WORKSPACE_MOUNT}${agentMountSuffix}`);
|
||||||
`${params.agentWorkspaceDir}:${SANDBOX_AGENT_WORKSPACE_MOUNT}${agentMountSuffix}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
args.push(cfg.image, "sleep", "infinity");
|
args.push(cfg.image, "sleep", "infinity");
|
||||||
|
|
||||||
@ -271,7 +307,10 @@ export async function ensureSandboxContainer(params: {
|
|||||||
running &&
|
running &&
|
||||||
(typeof lastUsedAtMs !== "number" || now - lastUsedAtMs < HOT_CONTAINER_WINDOW_MS);
|
(typeof lastUsedAtMs !== "number" || now - lastUsedAtMs < HOT_CONTAINER_WINDOW_MS);
|
||||||
if (isHot) {
|
if (isHot) {
|
||||||
const hint = formatSandboxRecreateHint({ scope: params.cfg.scope, sessionKey: scopeKey });
|
const hint = formatSandboxRecreateHint({
|
||||||
|
scope: params.cfg.scope,
|
||||||
|
sessionKey: scopeKey,
|
||||||
|
});
|
||||||
defaultRuntime.log(
|
defaultRuntime.log(
|
||||||
`Sandbox config changed for ${containerName} (recently used). Recreate to apply: ${hint}`,
|
`Sandbox config changed for ${containerName} (recently used). Recreate to apply: ${hint}`,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -31,6 +31,7 @@ export type SandboxBrowserConfig = {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
image: string;
|
image: string;
|
||||||
containerPrefix: string;
|
containerPrefix: string;
|
||||||
|
cdpHost: string;
|
||||||
cdpPort: number;
|
cdpPort: number;
|
||||||
vncPort: number;
|
vncPort: number;
|
||||||
noVncPort: number;
|
noVncPort: number;
|
||||||
|
|||||||
@ -48,6 +48,12 @@ export type SandboxBrowserSettings = {
|
|||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
image?: string;
|
image?: string;
|
||||||
containerPrefix?: string;
|
containerPrefix?: string;
|
||||||
|
/**
|
||||||
|
* Host to connect to for CDP (Chrome DevTools Protocol).
|
||||||
|
* Default: "127.0.0.1".
|
||||||
|
* Set to "host.docker.internal" when running gateway inside Docker.
|
||||||
|
*/
|
||||||
|
cdpHost?: string;
|
||||||
cdpPort?: number;
|
cdpPort?: number;
|
||||||
vncPort?: number;
|
vncPort?: number;
|
||||||
noVncPort?: number;
|
noVncPort?: number;
|
||||||
|
|||||||
@ -124,6 +124,7 @@ export const SandboxBrowserSchema = z
|
|||||||
enabled: z.boolean().optional(),
|
enabled: z.boolean().optional(),
|
||||||
image: z.string().optional(),
|
image: z.string().optional(),
|
||||||
containerPrefix: z.string().optional(),
|
containerPrefix: z.string().optional(),
|
||||||
|
cdpHost: z.string().optional(),
|
||||||
cdpPort: z.number().int().positive().optional(),
|
cdpPort: z.number().int().positive().optional(),
|
||||||
vncPort: z.number().int().positive().optional(),
|
vncPort: z.number().int().positive().optional(),
|
||||||
noVncPort: z.number().int().positive().optional(),
|
noVncPort: z.number().int().positive().optional(),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user