Files
hermes-agent/.github/workflows/sandbox-image.yml

184 lines
6.7 KiB
YAML

name: Sandbox desktop image
# nousresearch/hermes-sandbox:desktop is the terminal-backend sandbox base
# (nikolaik/python-nodejs) plus a display stack; Bot Screen, computer_use and
# the bot's browser run inside it when terminal.backend is docker/ssh/etc.
# It contains NO Hermes code, so it does not need to track main: it changes
# when its Dockerfile changes, not when the agent changes.
#
# Triggers: a PR that edits the Dockerfile/smoke (build + smoke, no push),
# a release (publish :<tag>-desktop and roll :desktop), and manual dispatch.
# Deliberately NOT on every main push: two 4 GB multi-arch builds per commit
# for an image that changes a few times a year.
on:
pull_request:
paths:
- docker/sandbox-desktop.Dockerfile
- docker/sandbox-desktop-smoke.sh
- .github/workflows/sandbox-image.yml
release:
types: [published]
workflow_dispatch:
inputs:
publish:
description: Push to Docker Hub (rolls :desktop)
type: boolean
default: false
permissions:
contents: read
concurrency:
group: sandbox-image-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
IMAGE_NAME: nousresearch/hermes-sandbox
jobs:
# PR code: secret-free build + smoke per arch.
build:
if: github.repository == 'NousResearch/hermes-agent'
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ubuntu-latest-32-core
platform: linux/amd64
- arch: arm64
runner: ubuntu-latest-32-arm-core
platform: linux/arm64
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
steps:
- uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v7.0.1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Build (${{ matrix.arch }})
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
file: docker/sandbox-desktop.Dockerfile
platforms: ${{ matrix.platform }}
tags: ${{ env.IMAGE_NAME }}:test
load: true
cache-from: type=gha,scope=sandbox-${{ matrix.arch }}
cache-to: ${{ github.event_name != 'pull_request' && format('type=gha,mode=max,scope=sandbox-{0}', matrix.arch) || '' }}
# The contract: the launcher, cua-driver and a headed Chromium work as `pn`.
- name: Smoke
run: docker/sandbox-desktop-smoke.sh ${{ env.IMAGE_NAME }}:test
# Trusted source only; the sole Docker Hub credential boundary.
publish:
if: github.repository == 'NousResearch/hermes-agent' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.publish))
needs: [build]
environment: container-publish
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ubuntu-latest-32-core
platform: linux/amd64
- arch: arm64
runner: ubuntu-latest-32-arm-core
platform: linux/arm64
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
steps:
- uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v7.0.1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Log in to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Push ${{ matrix.arch }} by digest
id: push
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
file: docker/sandbox-desktop.Dockerfile
platforms: ${{ matrix.platform }}
labels: |
org.opencontainers.image.revision=${{ github.sha }}
outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=sandbox-${{ matrix.arch }}
- name: Export digest
run: |
mkdir -p "$RUNNER_TEMP/digests"
digest="${{ steps.push.outputs.digest }}"
touch "$RUNNER_TEMP/digests/${digest#sha256:}"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sandbox-digest-${{ matrix.arch }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
# Registry-side stitch of the two digests into one tagged manifest list.
merge:
if: ${{ !cancelled() && github.repository == 'NousResearch/hermes-agent' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.publish)) }}
needs: [publish]
runs-on: ubuntu-latest
timeout-minutes: 10
environment: container-publish
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: ${{ runner.temp }}/digests
pattern: sandbox-digest-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Log in to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
set -euo pipefail
shopt -s nullglob
args=()
for digest_file in *; do
args+=("${IMAGE_NAME}@sha256:${digest_file}")
done
if [ "${#args[@]}" -ne 2 ]; then
echo "::error::want 2 digests, found ${#args[@]} (a publish leg failed)"
exit 1
fi
# :desktop is the stable tag docker_image points at; releases also get a pinned tag.
tags=(-t "${IMAGE_NAME}:desktop")
if [ -n "${RELEASE_TAG}" ]; then
tags+=(-t "${IMAGE_NAME}:${RELEASE_TAG}-desktop")
fi
for i in 1 2 3; do
if docker buildx imagetools create "${tags[@]}" "${args[@]}"; then
break
fi
if [ "$i" = 3 ]; then
echo "::error::imagetools create failed after 3 attempts"
exit 1
fi
sleep 10
done
docker buildx imagetools inspect "${IMAGE_NAME}:desktop"