-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat(sandbox): persistent per-user sandbox container (V1 Phase A) #6584
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c7d907d
2f49a42
e679cd6
8fb26bb
82a0d04
6815ed4
8331330
39cb8e2
249d617
20c6eef
5dd4952
36a1991
83b382c
b7d77b2
a988b69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,10 @@ on: | |
|
|
||
| env: | ||
| IMAGE_NAME: nearaidev/ironclaw | ||
| # Sandbox worker image (Dockerfile.process-sandbox): the image the | ||
| # `ironclaw-dind` bake step pulls for scoped process-sandbox launches. | ||
| # Published from this same run so it always matches `ironclaw`'s tag/sha. | ||
| IMAGE_NAME_WORKER: nearaidev/ironclaw-worker | ||
|
|
||
| jobs: | ||
| build: | ||
|
|
@@ -42,6 +46,9 @@ jobs: | |
| contents: read | ||
| packages: read | ||
| actions: write | ||
| outputs: | ||
| skip: ${{ steps.check.outputs.skip }} | ||
| worker_sha_tag: ${{ steps.tags.outputs.sha_tag }} | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||
|
|
@@ -79,6 +86,7 @@ jobs: | |
| IS_RELEASE_BUILD: ${{ inputs.release && 'true' || 'false' }} | ||
| INPUT_TAG: ${{ inputs.tag }} | ||
| SOURCE_SHA: ${{ steps.source_sha.outputs.sha }} | ||
| IMAGE_NAME_WORKER: ${{ env.IMAGE_NAME_WORKER }} | ||
| run: | | ||
| if [[ -n "${INPUT_TAG}" && ! "${INPUT_TAG}" =~ ^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$ ]]; then | ||
| echo "::error::Input tag '${INPUT_TAG}' does not match Docker tag grammar" | ||
|
|
@@ -108,6 +116,11 @@ jobs: | |
| fi | ||
| echo "tags=${TAGS}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| # ironclaw-worker mirrors ironclaw's tag scheme exactly — derive it | ||
| # by swapping the image name so the two never drift apart. | ||
| WORKER_TAGS="${TAGS//${IMAGE_NAME}/${IMAGE_NAME_WORKER}}" | ||
| echo "worker_tags=${WORKER_TAGS}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| # The Reborn Dockerfile has a single `runtime` stage — WASM | ||
| # extensions are compiled into the binary via | ||
| # `ironclaw_first_party_extensions`, so there is no separate | ||
|
|
@@ -161,6 +174,24 @@ jobs: | |
| cache-from: type=gha | ||
| cache-to: type=gha,mode=max | ||
|
|
||
| - name: Build and push (ironclaw-worker) | ||
| if: steps.check.outputs.skip != 'true' | ||
| uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 | ||
| with: | ||
| context: . | ||
| file: Dockerfile.process-sandbox | ||
| push: true | ||
| tags: ${{ steps.tags.outputs.worker_tags }} | ||
| labels: | | ||
| ironclaw.git.sha=${{ steps.source_sha.outputs.sha }} | ||
| platforms: linux/amd64 | ||
| # Separate cache scope: process-sandbox has a completely different | ||
| # layer graph (debian:bookworm-slim + apt packages) than the | ||
| # ironclaw runtime image — a shared scope would evict useful layers | ||
| # from both builds on every run. | ||
| cache-from: type=gha,scope=worker | ||
| cache-to: type=gha,mode=max,scope=worker | ||
|
|
||
| - name: Create releases-manager app token | ||
| id: app-token | ||
| if: steps.check.outputs.skip != 'true' | ||
|
|
@@ -197,6 +228,7 @@ jobs: | |
| if: steps.check.outputs.skip != 'true' | ||
| env: | ||
| TAGS: ${{ steps.tags.outputs.tags }} | ||
| WORKER_TAGS: ${{ steps.tags.outputs.worker_tags }} | ||
| VERSION: ${{ steps.version.outputs.version }} | ||
| SOURCE_SHA: ${{ steps.source_sha.outputs.sha }} | ||
| run: | | ||
|
|
@@ -208,6 +240,11 @@ jobs: | |
| echo "${TAGS}" | tr ',' '\n' | ||
| echo '```' | ||
| echo "" | ||
| echo "**ironclaw-worker:**" | ||
| echo '```' | ||
| echo "${WORKER_TAGS}" | tr ',' '\n' | ||
| echo '```' | ||
| echo "" | ||
| echo "- version: \`${VERSION}\`" | ||
| echo "- sha: \`${SOURCE_SHA}\`" | ||
| } >> "$GITHUB_STEP_SUMMARY" | ||
|
|
@@ -220,6 +257,35 @@ jobs: | |
| { | ||
| echo "## Docker Images — skipped" | ||
| echo "" | ||
| echo "Current commit already built for \`${IMAGE_NAME}:staging\`." | ||
| echo "Current commit already built for \`${IMAGE_NAME}:staging\` / \`${IMAGE_NAME_WORKER}:staging\`." | ||
| echo "- sha: \`${SOURCE_SHA}\`" | ||
| } >> "$GITHUB_STEP_SUMMARY" | ||
|
Comment on lines
+260
to
262
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Verify the worker image before skipping. The skip gate only pulls and inspects 🤖 Prompt for AI Agents |
||
|
|
||
| smoke-worker-image: | ||
| name: Smoke test (ironclaw-worker) | ||
| needs: build | ||
| if: needs.build.outputs.skip != 'true' | ||
| runs-on: ubuntu-24.04 | ||
| permissions: | ||
| contents: read | ||
| packages: read | ||
| env: | ||
| IMAGE_NAME_WORKER: nearaidev/ironclaw-worker | ||
| steps: | ||
| - name: Log in to Docker Hub | ||
| uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 | ||
| with: | ||
| username: ${{ vars.DOCKER_REGISTRY_USER }} | ||
| password: ${{ secrets.DOCKER_REGISTRY_TOKEN }} | ||
|
|
||
| - name: Pull and run ironclaw-worker | ||
| env: | ||
| WORKER_SHA_TAG: ${{ needs.build.outputs.worker_sha_tag }} | ||
| run: | | ||
| IMAGE="${IMAGE_NAME_WORKER}:${WORKER_SHA_TAG}" | ||
| echo "Smoke testing ${IMAGE}" | ||
| docker pull "${IMAGE}" | ||
| # Proves the tini -> process-sandbox-entrypoint -> capsh chain | ||
| # actually execs the given command instead of falling through to | ||
| # the image's "missing command" default CMD. | ||
| docker run --rm "${IMAGE}" sh -c "echo ok" | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -25,15 +25,39 @@ RUN apt-get update \ | |||||||||||
| iptables \ | ||||||||||||
| libcap2-bin \ | ||||||||||||
| tini \ | ||||||||||||
| tmux \ | ||||||||||||
| && apt-get clean \ | ||||||||||||
| && rm -rf /var/lib/apt/lists/* | ||||||||||||
|
|
||||||||||||
| # GitHub CLI (gh) | ||||||||||||
| 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 \ | ||||||||||||
| && apt-get clean && rm -rf /var/lib/apt/lists/* | ||||||||||||
|
|
||||||||||||
| COPY docker/process-sandbox-entrypoint.sh /usr/local/bin/process-sandbox-entrypoint | ||||||||||||
| RUN chmod +x /usr/local/bin/process-sandbox-entrypoint \ | ||||||||||||
| && useradd -m -u 1000 -s /bin/bash sandbox \ | ||||||||||||
| && mkdir -p /workspace /ironclaw/state/tools /ironclaw/state/cache /ironclaw/broker \ | ||||||||||||
| && mkdir -p /workspace/.home /ironclaw/state/tools /ironclaw/state/cache /ironclaw/broker \ | ||||||||||||
| && chown -R sandbox:sandbox /workspace /ironclaw | ||||||||||||
|
|
||||||||||||
| # Rust toolchain, installed as the sandbox user under the workspace-relative | ||||||||||||
| # HOME so it survives container-rm/recreate only via the bind-mounted | ||||||||||||
| # workspace (matches the design's "HOME=/workspace/.home so caches/dotfiles | ||||||||||||
| # persist" decision) — installed at build time into a location Cargo can | ||||||||||||
| # still find once HOME is redirected to /workspace/.home at runtime via | ||||||||||||
| # CARGO_HOME/RUSTUP_HOME pointed at a workspace-relative path is NOT done at | ||||||||||||
| # build time (the workspace doesn't exist yet); instead rustup installs to | ||||||||||||
| # the image's default /home/sandbox/.cargo, and the container launch env | ||||||||||||
| # additionally sets CARGO_HOME=/workspace/.home/.cargo, RUSTUP_HOME=/workspace/.home/.rustup | ||||||||||||
| # with a first-run copy step in the entrypoint script — see entrypoint change below. | ||||||||||||
| USER sandbox | ||||||||||||
| RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal | ||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Make Rust bootstrap fail on download errors. With Proposed fix-RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
+RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o /tmp/rustup-init \
+ && sh /tmp/rustup-init -y --profile minimal \
+ && /home/sandbox/.cargo/bin/cargo --version \
+ && rm /tmp/rustup-initAs per path instructions, the Fail loud invariant rejects silent-failure patterns. 📝 Committable suggestion
Suggested change
🧰 Tools🪛 Hadolint (2.14.0)[warning] 57-57: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check (DL4006) 🤖 Prompt for AI AgentsSources: Path instructions, Linters/SAST tools |
||||||||||||
| ENV PATH="/home/sandbox/.cargo/bin:${PATH}" | ||||||||||||
| USER root | ||||||||||||
|
|
||||||||||||
| WORKDIR /workspace | ||||||||||||
| ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/process-sandbox-entrypoint"] | ||||||||||||
| CMD ["bash", "-lc", "echo missing command >&2; exit 64"] | ||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The skipped summary now claims both
ironclaw:stagingandironclaw-worker:stagingare current, but the preceding skip check only pulls/inspectsIMAGE_NAME:staging. If a prior run pushed the main image and failed before pushing or smoking the worker image, the next scheduled/staging run will setskip=true, skip the worker build and smoke job, and leave the worker tag missing or stale.Useful? React with 👍 / 👎.