Skip to content
Merged
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
11 changes: 7 additions & 4 deletions scripts/brev-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

info() { echo -e "${GREEN}[brev]${NC} $1"; }
warn() { echo -e "${YELLOW}[brev]${NC} $1"; }
_ts() { date '+%H:%M:%S'; }
info() { echo -e "${GREEN}[$(_ts) brev]${NC} $1"; }
warn() { echo -e "${YELLOW}[$(_ts) brev]${NC} $1"; }
fail() {
echo -e "${RED}[brev]${NC} $1"
echo -e "${RED}[$(_ts) brev]${NC} $1"
exit 1
}

Expand Down Expand Up @@ -120,7 +121,9 @@ fi

# --- 4. vLLM (local inference, if GPU present) ---
VLLM_MODEL="nvidia/nemotron-3-nano-30b-a3b"
if command -v nvidia-smi >/dev/null 2>&1; then
if [ "${SKIP_VLLM:-}" = "1" ]; then
info "Skipping vLLM install (SKIP_VLLM=1)"
elif command -v nvidia-smi >/dev/null 2>&1; then
if ! python3 -c "import vllm" 2>/dev/null; then
info "Installing vLLM..."
if ! command -v pip3 >/dev/null 2>&1; then
Expand Down
52 changes: 48 additions & 4 deletions scripts/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
# shellcheck source=./lib/runtime.sh
. "$SCRIPT_DIR/lib/runtime.sh"

info() { echo -e "${GREEN}>>>${NC} $1"; }
warn() { echo -e "${YELLOW}>>>${NC} $1"; }
_ts() { date '+%H:%M:%S'; }
info() { echo -e "${GREEN}[$(_ts)]${NC} $1"; }
warn() { echo -e "${YELLOW}[$(_ts)]${NC} $1"; }
fail() {
echo -e "${RED}>>>${NC} $1"
echo -e "${RED}[$(_ts)]${NC} $1"
exit 1
}

Expand Down Expand Up @@ -92,7 +93,7 @@ fi
if [ "$CONTAINER_RUNTIME" != "unknown" ]; then
info "Container runtime: $CONTAINER_RUNTIME"
fi
SANDBOX_NAME="${1:-nemoclaw}"
SANDBOX_NAME="${1:-${NEMOCLAW_SANDBOX_NAME:-nemoclaw}}"
info "Using sandbox name: ${SANDBOX_NAME}"

OPEN_SHELL_VERSION_RAW="$(openshell -V 2>/dev/null || true)"
Expand Down Expand Up @@ -192,6 +193,20 @@ openshell inference set --no-verify --provider nvidia-nim --model nvidia/nemotro
info "Deleting old ${SANDBOX_NAME} sandbox (if any)..."
openshell sandbox delete "$SANDBOX_NAME" >/dev/null 2>&1 || true

# Pre-build the base image if it's not available (GHCR image may not exist on
# forks or before the first base-image workflow run). This ensures the
# Dockerfile's `FROM ${BASE_IMAGE}` can resolve locally.
BASE_IMAGE="${BASE_IMAGE:-ghcr.io/nvidia/nemoclaw/sandbox-base:latest}"
if ! docker image inspect "$BASE_IMAGE" >/dev/null 2>&1 && ! docker pull "$BASE_IMAGE" 2>/dev/null; then
if [ -f "$REPO_DIR/Dockerfile.base" ]; then
info "Base image not in registry — building Dockerfile.base locally..."
docker build -f "$REPO_DIR/Dockerfile.base" -t "$BASE_IMAGE" "$REPO_DIR" 2>&1 | tail -5
info "Local base image built"
else
warn "Dockerfile.base not found — sandbox build may fall back to full rebuild"
fi
fi

info "Building and creating NemoClaw sandbox (this takes a few minutes on first run)..."

# Stage a clean build context (openshell doesn't honor .dockerignore)
Expand All @@ -206,14 +221,43 @@ bash "$BUILD_CTX/scripts/clean-staged-tree.sh" "$BUILD_CTX/nemoclaw-blueprint" 2
# Capture full output to a temp file so we can filter for display but still
# detect failures. The raw log is kept on failure for debugging.
CREATE_LOG=$(mktemp /tmp/nemoclaw-create-XXXXXX.log)
SANDBOX_BUILD_START=$(date +%s)

# Background progress reporter: tails the log for Docker build steps and
# prints a heartbeat every 30s so CI (and humans) can see what's happening.
(
while true; do
sleep 30
if [ ! -f "$CREATE_LOG" ]; then break; fi
ELAPSED=$(($(date +%s) - SANDBOX_BUILD_START))
LAST_STEP=$(grep -oE "^Step [0-9]+/[0-9]+" "$CREATE_LOG" 2>/dev/null | tail -1 || true)
LAST_LINE=$(tail -1 "$CREATE_LOG" 2>/dev/null | head -c 120 || true)
# Filter out lines that might contain secrets
if echo "$LAST_LINE" | grep -qi "API_KEY\|TOKEN\|SECRET\|CREDENTIAL"; then
LAST_LINE="[filtered]"
fi
echo -e "${GREEN}[$(_ts)]${NC} ⏳ Sandbox build ${ELAPSED}s elapsed${LAST_STEP:+ — $LAST_STEP}${LAST_LINE:+ — $LAST_LINE}"
done
) &
PROGRESS_PID=$!

set +e
# NVIDIA_API_KEY is NOT passed into the sandbox. Inference is proxied through
# the OpenShell gateway which injects the stored credential server-side.
openshell sandbox create --from "$BUILD_CTX/Dockerfile" --name "$SANDBOX_NAME" \
--provider nvidia-nim \
--no-tty -- true \
>"$CREATE_LOG" 2>&1
CREATE_RC=$?
set -e

# Stop progress reporter
kill "$PROGRESS_PID" 2>/dev/null || true
wait "$PROGRESS_PID" 2>/dev/null || true

SANDBOX_BUILD_ELAPSED=$(($(date +%s) - SANDBOX_BUILD_START))
info "Sandbox build finished in ${SANDBOX_BUILD_ELAPSED}s (exit code: $CREATE_RC)"

rm -rf "$BUILD_CTX"

# Show progress lines (filter apt noise and env var dumps that contain NVIDIA_API_KEY)
Expand Down
21 changes: 15 additions & 6 deletions test/setup-sandbox-name.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ const ROOT = path.resolve(import.meta.dirname, "..");
describe("setup.sh sandbox name parameterization (#197)", () => {
const content = fs.readFileSync(path.join(ROOT, "scripts/setup.sh"), "utf-8");

it("accepts sandbox name as $1 with default", () => {
expect(content.includes('SANDBOX_NAME="${1:-nemoclaw}"')).toBeTruthy();
it("accepts sandbox name as $1 with env var fallback and default", () => {
// $1 takes priority, then NEMOCLAW_SANDBOX_NAME env var, then "nemoclaw"
expect(content.includes('SANDBOX_NAME="${1:-${NEMOCLAW_SANDBOX_NAME:-nemoclaw}}"')).toBeTruthy();
});

it("sandbox create uses $SANDBOX_NAME, not hardcoded", () => {
Expand Down Expand Up @@ -51,16 +52,24 @@ describe("setup.sh sandbox name parameterization (#197)", () => {

it("$1 arg actually sets SANDBOX_NAME in bash", () => {
const result = execSync(
'bash -c \'SANDBOX_NAME="${1:-nemoclaw}"; echo "$SANDBOX_NAME"\' -- my-test-box',
'bash -c \'SANDBOX_NAME="${1:-${NEMOCLAW_SANDBOX_NAME:-nemoclaw}}"; echo "$SANDBOX_NAME"\' -- my-test-box',
{ encoding: "utf-8" }
).trim();
expect(result).toBe("my-test-box");
});

it("no arg defaults to nemoclaw in bash", () => {
it("NEMOCLAW_SANDBOX_NAME env var is used when no $1 arg", () => {
const result = execSync(
'bash -c \'SANDBOX_NAME="${1:-nemoclaw}"; echo "$SANDBOX_NAME"\'',
{ encoding: "utf-8" }
'bash -c \'SANDBOX_NAME="${1:-${NEMOCLAW_SANDBOX_NAME:-nemoclaw}}"; echo "$SANDBOX_NAME"\'',
{ encoding: "utf-8", env: { ...process.env, NEMOCLAW_SANDBOX_NAME: "e2e-test" } }
).trim();
expect(result).toBe("e2e-test");
});

it("no arg and no env var defaults to nemoclaw in bash", () => {
const result = execSync(
'bash -c \'SANDBOX_NAME="${1:-${NEMOCLAW_SANDBOX_NAME:-nemoclaw}}"; echo "$SANDBOX_NAME"\'',
{ encoding: "utf-8", env: { PATH: process.env.PATH } }
).trim();
expect(result).toBe("nemoclaw");
});
Expand Down
Loading