Files
OpenShell/deploy/docker/sandbox/Dockerfile.base
T
Drew Newberry 5fd4885a7c feat(sandbox): VS Code Remote-SSH support with platform detection fix and network policy (!42)
Closes #34

## Summary

- Add `nav ssh-proxy` name mode (`--cluster`/`--name`) and `nav sandbox ssh-config` for VS Code Remote-SSH integration
- Fix VS Code Remote-SSH platform misdetection (was defaulting to Windows/PowerShell)
- Fix sandbox network policy for VS Code server connectivity
- Fix sandbox user shell configuration

## Problem

VS Code Remote-SSH was failing to connect to Navigator sandboxes due to three issues:

1. **Platform misdetection**: VS Code sends `uname -rsv` after a `ready:` marker and expects a response within ~1ms. Forced PTY allocation in `shell_request`, interactive bash (`-i`), and leaked supervisor env vars added enough latency that VS Code timed out and defaulted to "Platform: windows", then tried `powershell`.
2. **Broken network policy**: The `vscode` entry in `dev-sandbox-policy.yaml` was at the YAML top-level instead of nested under `network_policies:` (indentation bug). The VS Code server binary also needed a glob entry.
3. **No login shell**: The sandbox user had `/usr/sbin/nologin` as its shell.

## Changes

| File | Change |
|------|--------|
| `crates/navigator-sandbox/src/ssh.rs` | Remove forced PTY in `shell_request`, remove `-i` flag from pipe exec, add `env_clear()` + explicit `SHELL`/`PATH` to both shell spawn paths |
| `deploy/docker/sandbox/Dockerfile.base` | Change sandbox user shell to `/bin/bash`, set home to `/sandbox`, add `.bashrc`/`.profile` |
| `dev-sandbox-policy.yaml` | Fix `vscode:` indentation under `network_policies:`, add `/sandbox/.vscode-server/*` binary glob |
| `crates/navigator-cli/src/main.rs` | Add `--cluster`/`--name` to `SshProxy`, add `SshConfig`/`Policy`/`Logs` subcommands (rebase merge) |
| `crates/navigator-cli/src/ssh.rs` | Update `print_ssh_config` to use `--cluster` flag |
| `examples/vscode-remote-sandbox.md` | User-facing example and docs |

## How to use

```bash
# Create a persistent sandbox
nav sandbox create --keep my-sandbox

# Generate SSH config
nav sandbox ssh-config my-sandbox >> ~/.ssh/config

# Open VS Code
code --remote ssh-remote+nav-my-sandbox /sandbox
```

## Testing

- All 307 tests pass
- Clippy clean on modified crates
2026-02-26 09:06:37 -08:00

172 lines
7.6 KiB
Docker

# syntax=docker/dockerfile:1.4
# Supervisor container for navigator
# Multi-stage build with cross-compilation support for multi-arch
# Stage 1: Rust builder (runs on build platform, cross-compiles for target)
FROM --platform=$BUILDPLATFORM rust:1.88-slim AS rust-builder
ARG RUST_BUILD_PROFILE=debug
ARG TARGETARCH
ARG BUILDARCH
# Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
cmake g++ make protobuf-compiler && rm -rf /var/lib/apt/lists/*
# Install cross-compilation toolchain + Rust target (no-ops for native builds)
COPY deploy/docker/cross-build.sh /usr/local/bin/
RUN . cross-build.sh && install_cross_toolchain && add_rust_target
WORKDIR /build
COPY Cargo.toml Cargo.lock ./
COPY crates/ crates/
COPY proto/ proto/
COPY dev-sandbox-policy.rego ./
# Build with cross-compilation support.
# Cache mounts are keyed by TARGETARCH so parallel multi-platform builds
# don't corrupt each other's cargo registry or build artifacts.
RUN --mount=type=cache,id=cargo-registry-sandbox-${TARGETARCH},sharing=locked,target=/usr/local/cargo/registry \
--mount=type=cache,id=cargo-target-sandbox-${TARGETARCH},sharing=locked,target=/build/target \
. cross-build.sh && \
if [ "$RUST_BUILD_PROFILE" = "release" ]; then \
cargo_cross_build --release -p navigator-sandbox; \
else \
cargo_cross_build -p navigator-sandbox; \
fi && \
mkdir -p /build/out && \
cp "$(cross_output_dir "$RUST_BUILD_PROFILE")/navigator-sandbox" /build/out/
# Stage 2: Python base (uses target platform)
FROM python:3.12-slim AS base
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /sandbox
# Install system dependencies
# iproute2 is needed for network namespace management (ip netns, veth pairs)
RUN apt-get update && apt-get install -y --no-install-recommends \
curl dnsutils iproute2 iputils-ping \
net-tools netcat-openbsd traceroute \
&& rm -rf /var/lib/apt/lists/*
# Create supervisor and sandbox users/groups
RUN groupadd -r supervisor && useradd -r -g supervisor -s /usr/sbin/nologin supervisor && \
groupadd -r sandbox && useradd -r -g sandbox -d /sandbox -s /bin/bash sandbox
# Stage 3: Python dependencies builder
FROM base AS builder
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
# Copy project files for dependency resolution
COPY pyproject.toml uv.lock ./
# Install dependencies into a virtual environment
RUN uv venv /app/.venv && \
uv sync --frozen --no-dev --no-install-project 2>/dev/null || \
uv sync --no-dev --no-install-project 2>/dev/null || true
# Install navigator SDK dependencies
RUN uv pip install --python /app/.venv/bin/python --no-cache-dir cloudpickle grpcio protobuf openai
# Stage 4: Coding agents layer
FROM base AS coding-agents
# Install Node.js 22 via NodeSource (required by OpenClaw and other agents)
# build-essential & python3 are needed for npm packages with native addons (node-gyp)
# Include a minimal native toolchain so npm can compile optional native
# dependencies on platforms where prebuilt artifacts are unavailable.
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \
apt-get install -y --no-install-recommends build-essential git nodejs build-essential python3 vim-tiny nano && \
rm -rf /var/lib/apt/lists/*
# Install GitHub CLI (gh) from the official apt repository
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
-o /usr/share/keyrings/githubcli-archive-keyring.gpg && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
> /etc/apt/sources.list.d/github-cli.list && \
apt-get update && apt-get install -y --no-install-recommends gh && \
rm -rf /var/lib/apt/lists/*
# Install Claude CLI via native installer (npm method is deprecated)
RUN curl -fsSL https://claude.ai/install.sh | bash \
&& cp /root/.local/bin/claude /usr/local/bin/claude \
&& chmod 755 /usr/local/bin/claude
# Install OpenClaw CLI via npm (the native installer uses npm under the hood;
# installing directly avoids TTY and onboarding issues in Docker builds)
RUN npm install -g openclaw@2026.2.9
# Install OpenCode CLI and Codex CLI (OpenAI)
RUN npm install -g opencode-ai @openai/codex
# Install GitHub CLI (gh) via official Debian repository
RUN mkdir -p -m 755 /etc/apt/keyrings && \
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
-o /etc/apt/keyrings/githubcli-archive-keyring.gpg && \
chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
> /etc/apt/sources.list.d/github-cli.list && \
apt-get update && apt-get install -y --no-install-recommends gh && \
rm -rf /var/lib/apt/lists/*
# Install ai-pim-utils (NVIDIA PIM CLI tools: outlook, calendar, transcripts, etc.)
# The install script auto-detects Debian and installs via .deb package to /usr/bin.
# Skills are installed to /usr/share/ai-pim-utils/skills and symlinked to ~/.claude/skills.
# RUN curl -fsSL https://outlook-cli-80d21a.gitlab-master-pages.nvidia.com/install.sh | bash
# Stage 5: Final image
FROM coding-agents AS final
ARG RUST_BUILD_PROFILE=debug
COPY --from=builder /app/.venv /app/.venv
COPY --from=builder /usr/local/bin/uv /usr/local/bin/uv
COPY --from=rust-builder /build/out/navigator-sandbox /usr/local/bin/
# Copy navigator Python SDK into the virtual environment
COPY python/navigator/ /app/.venv/lib/python3.12/site-packages/navigator/
# Add venv to PATH
ENV PATH="/app/.venv/bin:$PATH"
# Copy custom navigator skills into the image
# To add a skill, create a subdirectory under deploy/docker/sandbox/skills/
# with a SKILL.md file (and any supporting files). It will be installed into
# both ~/.claude/skills/ and ~/.agents/skills/ for agent discovery.
COPY deploy/docker/sandbox/skills/ /sandbox/.openclaw/skills/
RUN mkdir -p /var/navigator /sandbox /var/log && \
touch /var/log/navigator.log && \
chown supervisor:supervisor /var/log/navigator.log && \
chmod 0664 /var/log/navigator.log && \
chown sandbox:sandbox /sandbox && \
# Minimal shell init files so interactive and non-interactive shells
# get a sane PATH and prompt. Without these, bash sources nothing
# under /sandbox and tools like VS Code Remote-SSH may mis-detect
# the platform.
printf 'export PATH="/app/.venv/bin:$PATH"\nexport PS1="\\u@\\h:\\w\\$ "\n' \
> /sandbox/.bashrc && \
printf '[ -f ~/.bashrc ] && . ~/.bashrc\n' > /sandbox/.profile && \
chown sandbox:sandbox /sandbox/.bashrc /sandbox/.profile && \
# Install agent skills into the sandbox user's home directory
# so Claude and other agents can discover them at runtime.
# Sources: ai-pim-utils (.deb) and custom navigator skills.
mkdir -p /sandbox/.claude/skills /sandbox/.agents/skills && \
for src in /usr/share/ai-pim-utils/skills /usr/share/navigator/skills; do \
[ -d "$src" ] || continue; \
find "$src" -mindepth 1 -maxdepth 1 -type d | while read -r skill; do \
ln -sf "$skill" "/sandbox/.claude/skills/$(basename "$skill")"; \
ln -sf "$skill" "/sandbox/.agents/skills/$(basename "$skill")"; \
done; \
done && \
chown -R sandbox:sandbox /sandbox/.openclaw /sandbox/.claude /sandbox/.agents
# OpenClaw startup helper for environments without systemd (e.g. sandboxes).
# Usage: nav sandbox create --forward 18789 -- openclaw-start
COPY deploy/docker/openclaw-start.sh /usr/local/bin/openclaw-start
RUN chmod +x /usr/local/bin/openclaw-start
ENTRYPOINT ["navigator-sandbox"]