Some checks failed
Deploy Site / deploy-vercel (push) Has been cancelled
Deploy Site / deploy-docs (push) Has been cancelled
auto-fix lint issues & formatting / Generate eslint --fix patch (push) Has been cancelled
auto-fix lint issues & formatting / Apply patch (push) Has been cancelled
Skills Index Freshness Check / check-freshness (push) Has been cancelled
OSV-Scanner / Scan lockfiles (push) Has been cancelled
* feat(docker): publish nousresearch/hermes-sandbox:desktop for terminal backends
The terminal backends (docker, modal, daytona, singularity) all default to
nikolaik/python-nodejs:python3.11-nodejs20, a bare Python+Node base. For Bot
Screen, computer_use and the browser to run INSIDE that sandbox instead of on
the gateway host, the sandbox image needs the display stack.
docker/sandbox-desktop.Dockerfile is that base plus:
- the everyday tools it lacked (jq, ripgrep, fd, tmux, less, nano, vim,
zip, rsync, tree, procps, htop, sudo for the base's uid-1000 `pn`)
- the exact package set the Hermes -desktop image installs (TigerVNC,
Xfce components, dbus, xauth, fonts)
- Playwright's headed Chromium (same build as the -desktop image)
- cua-driver 0.28.2 from its pinned release tarball
No Hermes inside; the default user stays root like the base so nothing
changes for people who just switch docker_image. Desktop processes run as
`pn`. 4.27 GB on amd64.
docker.yml gains a `sandbox` variant with its own cache scope and repository
(nousresearch/hermes-sandbox:desktop, :main-desktop, :<release>-desktop);
the docker-integration suite is skipped for it (no Hermes to test) and
docker/sandbox-desktop-smoke.sh runs instead: as `pn`, every launcher and
cua-driver binary resolves, the real launcher.sh publishes :20, the RFB
socket completes the 3.8 handshake relayed over `docker exec -i` stdio, and
a headed Chromium maps a window on that display. hadolint lints the new
Dockerfile in docker-lint.yml.
* ci(docker): build the sandbox image on release/dispatch, not every main push
Leaves docker.yml exactly as on main. The sandbox image carries no Hermes code,
so two 4 GB multi-arch builds per merge bought nothing. sandbox-image.yml builds
and smokes on a PR that edits its own Dockerfile/smoke, and publishes only on a
release or a manual dispatch with publish=true. Stable tag stays :desktop.
* feat(docker): sandbox desktop base on python3.13-nodejs26
Matches the Hermes image (Python 3.13 / Node 26) and the top of requires-python;
the default docker_image tag it inherited was Python 3.11 / Node 20. Same pn
uid 1000, Debian 13; smoke (launcher, RFB relay, headed Chromium) passes.
* feat(docker): sandbox desktop image bakes agent-browser and Pillow
The runtime PR wraps the browser tools' agent-browser invocation in the sandbox
and grabs the Screen pane thumbnail inside it (the X socket and its cookie live
there). Both need to be in the image the default tag publishes, or the runtime
finds a tag-compatible image without them. agent-browser is pinned to the range
the gateway resolves (tools/browser_tool.py AGENT_BROWSER_NPX_SPEC), scripts off,
so the first browser_navigate in a fresh sandbox never waits on npm. The smoke
checks both as pn.
* docker(sandbox-desktop): login sessions find the browser (PLAYWRIGHT_BROWSERS_PATH via /etc/environment)
Dockerfile ENV reaches docker exec only. As the target of the ssh backend
(sshd on top of this image) a login session gets its environment from PAM,
so agent-browser reported 'Chrome not found' over ssh while working under
docker exec. Write the browser location to /etc/environment as well; the
smoke test asserts a clean login environment resolves it.
95 lines
5.4 KiB
Docker
95 lines
5.4 KiB
Docker
# Terminal-backend sandbox image WITH a desktop: the default sandbox base every
|
|
# docker/modal/daytona/singularity user already runs (nikolaik/python-nodejs), plus
|
|
# the tools that base was missing, plus the same display stack the -desktop Hermes
|
|
# image carries (TigerVNC + Xfce components + headed Chromium) and cua-driver, so
|
|
# Bot Screen, computer_use and the browser can live INSIDE the sandbox instead of
|
|
# on the gateway host. No Hermes runtime in here; the gateway shells in.
|
|
#
|
|
# docker build -f docker/sandbox-desktop.Dockerfile -t nousresearch/hermes-sandbox:desktop .
|
|
#
|
|
# Published as nousresearch/hermes-sandbox:desktop by .github/workflows/sandbox-image.yml
|
|
# on releases and manual dispatch only: it carries no Hermes code, so it does not track main.
|
|
# The tag lives in the ARG so CI and a local build read one place; hadolint cannot
|
|
# see through the substitution, hence the inline ignore.
|
|
ARG SANDBOX_BASE=nikolaik/python-nodejs:python3.13-nodejs26
|
|
# hadolint ignore=DL3006
|
|
FROM ${SANDBOX_BASE}
|
|
SHELL ["/bin/bash", "-euo", "pipefail", "-c"]
|
|
|
|
ARG CUA_DRIVER_VERSION=0.28.2
|
|
ARG TARGETARCH
|
|
|
|
ENV DEBIAN_FRONTEND=noninteractive \
|
|
PLAYWRIGHT_BROWSERS_PATH=/opt/playwright \
|
|
LANG=C.UTF-8
|
|
|
|
# Everyday tools the nikolaik base lacks (checked 2026-09: no jq, rg, fd, tmux,
|
|
# less, vim/nano, zip, rsync, tree, procps beyond ps). Kept to what agents reach
|
|
# for from a shell; language toolchains come from the base.
|
|
RUN apt-get -o Acquire::Retries=3 update && \
|
|
apt-get -o Acquire::Retries=3 install -y --no-install-recommends \
|
|
jq ripgrep fd-find tmux less nano vim-tiny zip rsync tree procps htop \
|
|
file bsdextrautils ca-certificates locales sudo && \
|
|
ln -sf /usr/bin/fdfind /usr/local/bin/fd && \
|
|
rm -rf /var/lib/apt/lists/*
|
|
|
|
# Display stack: identical package set to the Hermes -desktop image (Dockerfile,
|
|
# HERMES_BOT_DESKTOP=1) so tools/bot_desktop/launcher.sh finds the same binaries.
|
|
# Components are launched individually by the launcher, never xfce4-session.
|
|
RUN apt-get -o Acquire::Retries=3 update && \
|
|
apt-get -o Acquire::Retries=3 install -y --no-install-recommends \
|
|
tigervnc-standalone-server xfce4-panel xfwm4 xfdesktop4 xfce4-settings xfce4-terminal \
|
|
dbus-x11 x11-xserver-utils x11-utils x11-xkb-utils xauth fonts-dejavu-core \
|
|
at-spi2-core libgtk-3-0 libnss3 libasound2 libxss1 && \
|
|
rm -rf /var/lib/apt/lists/*
|
|
|
|
# Headed Chromium through Playwright (the same build the Hermes -desktop image
|
|
# uses), so agent-browser and the dock's Browser icon share one binary and one
|
|
# --user-data-dir. --with-deps pulls the Chromium runtime libraries. agent-browser
|
|
# itself is baked (the CLI the browser tools drive), pinned to the same range the
|
|
# gateway resolves (tools/browser_tool.py AGENT_BROWSER_NPX_SPEC), scripts off:
|
|
# the first browser_navigate in a fresh sandbox must not wait on an npm fetch.
|
|
RUN for i in 1 2 3; do \
|
|
npx --yes playwright@1 install --with-deps chromium && break || \
|
|
{ [ "$i" = 3 ] && exit 1; echo "playwright chromium install failed (attempt $i); retrying in 10s"; sleep 10; }; \
|
|
done && chmod -R a+rX /opt/playwright && \
|
|
npm install -g --ignore-scripts --no-audit --fetch-retries=5 "agent-browser@^0.26.0" && \
|
|
agent-browser --version
|
|
|
|
# cua-driver: computer_use's MCP driver. Pinned release tarball from the
|
|
# cua-driver-rs-v* tags (never releases/latest: prereleases publish 0 assets).
|
|
RUN set -eu; \
|
|
case "${TARGETARCH:-amd64}" in \
|
|
amd64) arch=x86_64 ;; \
|
|
arm64) arch=arm64 ;; \
|
|
*) echo "unsupported TARGETARCH ${TARGETARCH}"; exit 1 ;; \
|
|
esac; \
|
|
mkdir -p /opt/cua-driver; \
|
|
curl -fsSL "https://github.com/trycua/cua/releases/download/cua-driver-rs-v${CUA_DRIVER_VERSION}/cua-driver-rs-${CUA_DRIVER_VERSION}-linux-${arch}-binary.tar.gz" \
|
|
| tar -xz -C /opt/cua-driver; \
|
|
ln -sf /opt/cua-driver/cua-driver /usr/local/bin/cua-driver; \
|
|
/usr/local/bin/cua-driver --version
|
|
|
|
# Pillow: the Screen pane's thumbnail is grabbed INSIDE the sandbox (the X socket and its cookie live
|
|
# here, not on the gateway host). Also used by the base for ad-hoc image work.
|
|
RUN pip install --no-cache-dir "pillow>=10" && python3 -c "from PIL import ImageGrab"
|
|
|
|
# The base's default user stays root, exactly like nikolaik today, so existing
|
|
# docker_image users see no ownership or PATH change. Desktop processes (Xvnc,
|
|
# Xfce, Chromium, cua-driver) are exec'd as the base's unprivileged `pn` (uid
|
|
# 1000): Chromium refuses to run as root without --no-sandbox and cua-driver's
|
|
# AT-SPI bus wants a real user session. `pn` may sudo for ad-hoc installs.
|
|
RUN echo "pn ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/pn && chmod 0440 /etc/sudoers.d/pn && \
|
|
mkdir -p /tmp/.X11-unix && chmod 1777 /tmp/.X11-unix && \
|
|
mkdir -p /tmp/hermes-runtime && chown pn:pn /tmp/hermes-runtime && chmod 0700 /tmp/hermes-runtime
|
|
|
|
# Runtime dir for dbus/Xvnc; containers have no logind to create it. Both /tmp
|
|
# paths above are fixed by the X11 protocol / seeded per container, never shared.
|
|
ENV XDG_RUNTIME_DIR=/tmp/hermes-runtime
|
|
# Dockerfile ENV reaches `docker exec` only. When this image is the target of the
|
|
# ssh backend (sshd added on top), a login session gets its environment from PAM,
|
|
# so the browser location must also live where pam_env reads it or agent-browser
|
|
# reports "Chrome not found" over ssh while working under docker exec.
|
|
RUN printf 'PLAYWRIGHT_BROWSERS_PATH=/opt/playwright\nXDG_RUNTIME_DIR=/tmp/hermes-runtime\n' >> /etc/environment
|
|
CMD ["sleep", "infinity"]
|