diff --git a/Dockerfile b/Dockerfile index 894e686e..c023d0d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ procps \ psmisc \ lsof \ + socat \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/Dockerfile-dev b/Dockerfile-dev index 33eb889a..974ea767 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -16,6 +16,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ procps \ psmisc \ lsof \ + socat \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/package-lock.json b/package-lock.json index ae698b10..244738eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1149,6 +1149,13 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/shell-quote": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@types/shell-quote/-/shell-quote-1.7.5.tgz", + "integrity": "sha512-+UE8GAGRPbJVQDdxi16dgadcBfQ+KG2vgZhV1+3A1XmHbmwcdwhCUwIdy+d3pAGrbvgRoVSjeI9vOWyq376Yzw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/tinycolor2": { "version": "1.4.6", "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.6.tgz", @@ -5534,7 +5541,6 @@ "version": "1.8.2", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6850,6 +6856,7 @@ "lowlight": "^3.3.0", "react": "^18.3.1", "read-package-up": "^11.0.0", + "shell-quote": "^1.8.2", "yargs": "^17.7.2" }, "bin": { @@ -6861,6 +6868,7 @@ "@types/dotenv": "^6.1.1", "@types/node": "^20.11.24", "@types/react": "^19.1.0", + "@types/shell-quote": "^1.7.5", "@types/yargs": "^17.0.32", "typescript": "^5.3.3", "vitest": "^3.1.1" diff --git a/packages/cli/package.json b/packages/cli/package.json index 3699328a..c2e8ba2c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -42,6 +42,7 @@ "lowlight": "^3.3.0", "react": "^18.3.1", "read-package-up": "^11.0.0", + "shell-quote": "^1.8.2", "yargs": "^17.7.2" }, "devDependencies": { @@ -49,6 +50,7 @@ "@types/dotenv": "^6.1.1", "@types/node": "^20.11.24", "@types/react": "^19.1.0", + "@types/shell-quote": "^1.7.5", "@types/yargs": "^17.0.32", "typescript": "^5.3.3", "vitest": "^3.1.1" diff --git a/packages/cli/src/gemini.ts b/packages/cli/src/gemini.ts index 1d189493..2cecbedb 100644 --- a/packages/cli/src/gemini.ts +++ b/packages/cli/src/gemini.ts @@ -8,6 +8,7 @@ import os from 'os'; import path from 'path'; import fs from 'fs'; import React from 'react'; +import { quote } from 'shell-quote'; import { render } from 'ink'; import { App } from './ui/App.js'; import { loadCliConfig } from './config/config.js'; @@ -204,17 +205,24 @@ async function start_sandbox(sandbox: string) { } // open additional ports if SANDBOX_PORTS is set + // also set up redirects (via socat) so servers can listen on localhost instead of 0.0.0.0 + let bash_cmd = ''; if (process.env.SANDBOX_PORTS) { for (let port of process.env.SANDBOX_PORTS.split(',')) { if ((port = port.trim())) { console.log(`SANDBOX_PORTS: ${port}`); args.push('-p', `${port}:${port}`); + bash_cmd += `socat TCP4-LISTEN:${port},bind=$(hostname -i),fork,reuseaddr TCP4:127.0.0.1:${port} 2> /dev/null & `; } } } - // append remaining args (image, node, node args, cli path, cli args) - args.push(image, 'node', ...nodeArgs, cliPath, ...process.argv.slice(2)); + // append remaining args (image, bash -c "node node_args... cli path cli_args...") + // node_args and cli_args need to be quoted before being inserted into bash_cmd + const quotedNodeArgs = nodeArgs.map((arg) => quote([arg])); + const quotedCliArgs = process.argv.slice(2).map((arg) => quote([arg])); + bash_cmd += `node ${quotedNodeArgs.join(' ')} ${quote([cliPath])} ${quotedCliArgs.join(' ')}`; + args.push(image, 'bash', '-c', bash_cmd); // spawn child and let it inherit stdio const child = spawn(sandbox, args, { diff --git a/scripts/start_sandbox.sh b/scripts/start_sandbox.sh index 67c9cf27..444efbd3 100755 --- a/scripts/start_sandbox.sh +++ b/scripts/start_sandbox.sh @@ -135,20 +135,24 @@ fi node_args+=("$CLI_PATH" "$@") # open additional ports if SANDBOX_PORTS is set +# also set up redirects (via socat) so servers can listen on localhost instead of 0.0.0.0 +bash_cmd="" if [ -n "${SANDBOX_PORTS:-}" ]; then ports=$(echo "$SANDBOX_PORTS" | tr ',' '\n') for port in $ports; do if [ -n "$port" ]; then echo "SANDBOX_PORTS: $port" run_args+=(-p "$port:$port") + bash_cmd+="socat TCP4-LISTEN:$port,bind=\$(hostname -i),fork,reuseaddr TCP4:127.0.0.1:$port 2> /dev/null& " fi done fi +bash_cmd+="node $(printf '%q ' "${node_args[@]}")" # printf fixes quoting within args # run gemini-code in sandbox container if [[ "$CMD" == "podman" ]]; then # use empty --authfile to skip unnecessary auth refresh overhead - $CMD run "${run_args[@]}" --authfile <(echo '{}') "$IMAGE" node "${node_args[@]}" + $CMD run "${run_args[@]}" --authfile <(echo '{}') "$IMAGE" bash -c "$bash_cmd" else - $CMD run "${run_args[@]}" "$IMAGE" node "${node_args[@]}" + $CMD run "${run_args[@]}" "$IMAGE" bash -c "$bash_cmd" fi