From 5e5a959bb35f50f4b23cf4e44a54fafe8a8ebad7 Mon Sep 17 00:00:00 2001 From: Abhishek Anand Date: Wed, 12 Aug 2026 05:34:33 +0530 Subject: [PATCH 1/4] Add resource and workspace mount controls --- README.md | 18 +++++++ install.sh | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 153 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 86aa379..495123b 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,24 @@ To start over with a clean sandbox, delete the container and run the installer a container delete coderunner && ./install.sh ``` +### Resource limits and workspace mounts + +CPU and memory limits can be set with installer options or the `CODERUNNER_CPUS` and `CODERUNNER_MEMORY` environment variables: + +```bash +./install.sh --cpus 4 --memory 8g +``` + +Additional host directories must be mounted under `/workspace`. They are read-only by default; add `:rw` only when the sandbox needs to write to the host: + +```bash +./install.sh \ + --mount "$HOME/projects/api:/workspace/api" \ + --mount "$HOME/projects/output:/workspace/output:rw" +``` + +Extra mounts default to none. Resource and mount settings are fixed when the container is created; delete the container before changing them. + ## Run Claude Code inside a Sandbox diff --git a/install.sh b/install.sh index 2726492..b7c7c50 100755 --- a/install.sh +++ b/install.sh @@ -1,5 +1,116 @@ #!/bin/bash +CPUS="${CODERUNNER_CPUS:-8}" +MEMORY="${CODERUNNER_MEMORY:-4g}" +MOUNT_SPECS=() +VOLUME_ARGS=() + +usage() { + cat <<'EOF' +Usage: ./install.sh [options] + +Options: + --cpus N CPU count (default: 8) + --memory SIZE Memory limit, e.g. 4g or 8192m (default: 4g) + --mount HOST:TARGET[:ro|rw] + Mount a host directory under /workspace. + Mounts are read-only unless :rw is specified. + -h, --help Show this help +EOF +} + +while [ "$#" -gt 0 ]; do + case "$1" in + --cpus) + [ "$#" -ge 2 ] || { echo "Error: --cpus requires a value." >&2; exit 2; } + CPUS="$2" + shift 2 + ;; + --memory) + [ "$#" -ge 2 ] || { echo "Error: --memory requires a value." >&2; exit 2; } + MEMORY="$2" + shift 2 + ;; + --mount) + [ "$#" -ge 2 ] || { echo "Error: --mount requires a value." >&2; exit 2; } + MOUNT_SPECS+=("$2") + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Error: unknown option '$1'." >&2 + usage >&2 + exit 2 + ;; + esac +done + +if ! [[ "$CPUS" =~ ^[1-9][0-9]*$ ]]; then + echo "Error: CPU count must be a positive integer." >&2 + exit 2 +fi +if ! [[ "$MEMORY" =~ ^[1-9][0-9]*([KkMmGgTtPp][Bb]?)?$ ]]; then + echo "Error: memory must be a positive size such as 4g or 8192m." >&2 + exit 2 +fi + +NORMALIZED_MOUNTS=() +for spec in "${MOUNT_SPECS[@]}"; do + source_path=${spec%%:*} + remainder=${spec#*:} + if [ "$remainder" = "$spec" ] || [ -z "$source_path" ] || [ -z "$remainder" ]; then + echo "Error: mount must use HOST:TARGET[:ro|rw]." >&2 + exit 2 + fi + target_path=${remainder%%:*} + if [ "$target_path" = "$remainder" ]; then + mode="ro" + else + mode=${remainder#*:} + if [[ "$mode" == *:* ]]; then + echo "Error: mount must use HOST:TARGET[:ro|rw]." >&2 + exit 2 + fi + fi + if [ "$mode" != "ro" ] && [ "$mode" != "rw" ]; then + echo "Error: mount mode must be 'ro' or 'rw'." >&2 + exit 2 + fi + if [ ! -d "$source_path" ]; then + echo "Error: mount source is not a directory: $source_path" >&2 + exit 2 + fi + source_path=$(cd "$source_path" && pwd -P) + case "$target_path" in + /workspace|/workspace/*) ;; + *) + echo "Error: mount targets must be /workspace or a path below it." >&2 + exit 2 + ;; + esac + if [[ "$target_path" == *"/../"* ]] || [[ "$target_path" == */.. ]] || [[ "$target_path" == *"//"* ]]; then + echo "Error: mount target contains an invalid path segment." >&2 + exit 2 + fi + for existing in "${NORMALIZED_MOUNTS[@]}"; do + existing_target=${existing#*:} + existing_target=${existing_target%:*} + if [ "$existing_target" = "$target_path" ]; then + echo "Error: duplicate mount target: $target_path" >&2 + exit 2 + fi + done + NORMALIZED_MOUNTS+=("$source_path:$target_path:$mode") + if [ "$mode" = "ro" ]; then + VOLUME_ARGS+=(--volume "$source_path:$target_path:ro") + else + VOLUME_ARGS+=(--volume "$source_path:$target_path") + fi +done + # Function to get current macOS version get_macos_version() { sw_vers -productVersion | awk -F. '{print $1 "." $2}' @@ -87,6 +198,25 @@ ASSETS_SRC="$HOME/.coderunner/assets" mkdir -p "$ASSETS_SRC/skills/user" mkdir -p "$ASSETS_SRC/outputs" +CONFIG_FILE="$HOME/.coderunner/container-config" +desired_config="cpus=$CPUS"$'\n'"memory=$MEMORY" +for mount in "${NORMALIZED_MOUNTS[@]}"; do + desired_config="${desired_config}"$'\n'"mount=$mount" +done +if container inspect coderunner &>/dev/null; then + if [ -f "$CONFIG_FILE" ]; then + if [ "$(cat "$CONFIG_FILE")" != "$desired_config" ]; then + echo "❌ Existing container was created with different resource or mount settings." + echo " Recreate it with: container delete coderunner && ./install.sh [options]" + exit 1 + fi + elif [ "$desired_config" != $'cpus=8\nmemory=4g' ]; then + echo "❌ Existing container predates configurable resources and mounts." + echo " Recreate it with: container delete coderunner && ./install.sh [options]" + exit 1 + fi +fi + # Stop any existing coderunner container echo "Stopping any existing coderunner container..." container stop coderunner 2>/dev/null || true @@ -105,15 +235,17 @@ if ! container image pull instavm/coderunner; then fi # Run the command to start the sandbox container -echo "Running: container run --volume \"$ASSETS_SRC/skills/user:/app/uploads/skills/user\" --volume \"$ASSETS_SRC/outputs:/app/uploads/outputs\" --name coderunner --detach --cpus 8 --memory 4g instavm/coderunner" +echo "Starting coderunner with $CPUS CPUs and $MEMORY memory..." if container run \ --volume "$ASSETS_SRC/skills/user:/app/uploads/skills/user" \ --volume "$ASSETS_SRC/outputs:/app/uploads/outputs" \ + "${VOLUME_ARGS[@]}" \ --name coderunner \ --detach \ - --cpus 8 \ - --memory 4g \ + --cpus "$CPUS" \ + --memory "$MEMORY" \ instavm/coderunner; then + printf '%s' "$desired_config" > "$CONFIG_FILE" echo "✅ Setup complete. MCP server is available at http://coderunner.local:8222/mcp" else echo "❌ Failed to start coderunner container. Please check the logs with: container logs coderunner" From 0308d81c65d47760fa7bcc5ea1d47b4ee336b0b3 Mon Sep 17 00:00:00 2001 From: mkagenius Date: Wed, 12 Aug 2026 10:51:05 +0530 Subject: [PATCH 2/4] fix(install.sh): guard empty array expansions under set -u macOS ships bash 3.2, where expanding an empty array under 'set -u' is a fatal 'unbound variable' error. MOUNT_SPECS, NORMALIZED_MOUNTS and VOLUME_ARGS are all empty when no --mount is passed, which breaks once this branch is combined with the 'set -u' added in #24. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- install.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/install.sh b/install.sh index b7c7c50..947e644 100755 --- a/install.sh +++ b/install.sh @@ -58,7 +58,7 @@ if ! [[ "$MEMORY" =~ ^[1-9][0-9]*([KkMmGgTtPp][Bb]?)?$ ]]; then fi NORMALIZED_MOUNTS=() -for spec in "${MOUNT_SPECS[@]}"; do +for spec in ${MOUNT_SPECS[@]+"${MOUNT_SPECS[@]}"}; do source_path=${spec%%:*} remainder=${spec#*:} if [ "$remainder" = "$spec" ] || [ -z "$source_path" ] || [ -z "$remainder" ]; then @@ -95,7 +95,7 @@ for spec in "${MOUNT_SPECS[@]}"; do echo "Error: mount target contains an invalid path segment." >&2 exit 2 fi - for existing in "${NORMALIZED_MOUNTS[@]}"; do + for existing in ${NORMALIZED_MOUNTS[@]+"${NORMALIZED_MOUNTS[@]}"}; do existing_target=${existing#*:} existing_target=${existing_target%:*} if [ "$existing_target" = "$target_path" ]; then @@ -200,7 +200,7 @@ mkdir -p "$ASSETS_SRC/outputs" CONFIG_FILE="$HOME/.coderunner/container-config" desired_config="cpus=$CPUS"$'\n'"memory=$MEMORY" -for mount in "${NORMALIZED_MOUNTS[@]}"; do +for mount in ${NORMALIZED_MOUNTS[@]+"${NORMALIZED_MOUNTS[@]}"}; do desired_config="${desired_config}"$'\n'"mount=$mount" done if container inspect coderunner &>/dev/null; then @@ -239,7 +239,7 @@ echo "Starting coderunner with $CPUS CPUs and $MEMORY memory..." if container run \ --volume "$ASSETS_SRC/skills/user:/app/uploads/skills/user" \ --volume "$ASSETS_SRC/outputs:/app/uploads/outputs" \ - "${VOLUME_ARGS[@]}" \ + ${VOLUME_ARGS[@]+"${VOLUME_ARGS[@]}"} \ --name coderunner \ --detach \ --cpus "$CPUS" \ From fc9fdd5bee480a2e48186a65635f1e3240ee311f Mon Sep 17 00:00:00 2001 From: Abhishek Anand Date: Wed, 12 Aug 2026 11:07:41 +0530 Subject: [PATCH 3/4] Address installer review feedback --- install.sh | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/install.sh b/install.sh index 947e644..09c5b42 100755 --- a/install.sh +++ b/install.sh @@ -56,6 +56,8 @@ if ! [[ "$MEMORY" =~ ^[1-9][0-9]*([KkMmGgTtPp][Bb]?)?$ ]]; then echo "Error: memory must be a positive size such as 4g or 8192m." >&2 exit 2 fi +MEMORY=$(printf '%s' "$MEMORY" | tr '[:upper:]' '[:lower:]') +MEMORY=${MEMORY%b} NORMALIZED_MOUNTS=() for spec in ${MOUNT_SPECS[@]+"${MOUNT_SPECS[@]}"}; do @@ -107,6 +109,7 @@ for spec in ${MOUNT_SPECS[@]+"${MOUNT_SPECS[@]}"}; do if [ "$mode" = "ro" ]; then VOLUME_ARGS+=(--volume "$source_path:$target_path:ro") else + # Apple's --volume syntax represents writable mounts by omitting :ro. VOLUME_ARGS+=(--volume "$source_path:$target_path") fi done @@ -200,9 +203,10 @@ mkdir -p "$ASSETS_SRC/outputs" CONFIG_FILE="$HOME/.coderunner/container-config" desired_config="cpus=$CPUS"$'\n'"memory=$MEMORY" -for mount in ${NORMALIZED_MOUNTS[@]+"${NORMALIZED_MOUNTS[@]}"}; do - desired_config="${desired_config}"$'\n'"mount=$mount" -done +if [ -n "${NORMALIZED_MOUNTS[*]-}" ]; then + sorted_mounts=$(printf '%s\n' ${NORMALIZED_MOUNTS[@]+"${NORMALIZED_MOUNTS[@]}"} | LC_ALL=C sort) + desired_config="${desired_config}"$'\n'"$(printf '%s\n' "$sorted_mounts" | sed 's/^/mount=/')" +fi if container inspect coderunner &>/dev/null; then if [ -f "$CONFIG_FILE" ]; then if [ "$(cat "$CONFIG_FILE")" != "$desired_config" ]; then @@ -227,6 +231,11 @@ if container start coderunner 2>/dev/null; then echo "✅ Setup complete. MCP server is available at http://coderunner.local:8222/mcp" exit 0 fi +if container inspect coderunner &>/dev/null; then + echo "❌ Existing coderunner container could not be started." + echo " Check its logs, or recreate it with: container delete coderunner && ./install.sh [options]" + exit 1 +fi echo "Pulling the latest image: instavm/coderunner" if ! container image pull instavm/coderunner; then From 34d59cb202e9cf4814000cb3c42ca9117bcc46b2 Mon Sep 17 00:00:00 2001 From: Abhishek Anand Date: Wed, 12 Aug 2026 11:47:03 +0530 Subject: [PATCH 4/4] Keep MCP on the compatible API --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 52ecaad..03f8d89 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,7 +31,7 @@ openai requests>=2.33.0 -mcp[cli] +mcp[cli]>=1.26,<2 fastmcp