mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-01 12:02:05 +00:00
refactor(ci): faster docker builds via --link and chmod removal
This commit is contained in:
parent
f6e815e378
commit
638243726e
3 changed files with 28 additions and 38 deletions
4
.github/workflows/docker-publish.yml
vendored
4
.github/workflows/docker-publish.yml
vendored
|
|
@ -120,13 +120,11 @@ jobs:
|
|||
- name: Install Python dependencies (for docker tests)
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
uv venv .venv --python 3.11
|
||||
source .venv/bin/activate
|
||||
# ``dev`` extra pulls in pytest, pytest-asyncio —
|
||||
# everything tests/docker/ needs. We deliberately avoid ``all``
|
||||
# here because the docker tests only drive the container via
|
||||
# subprocess and don't import hermes_agent's optional deps.
|
||||
uv pip install -e ".[dev]"
|
||||
uv sync --locked --python 3.11 --extra dev
|
||||
|
||||
- name: Run docker integration tests
|
||||
if: github.event_name != 'pull_request'
|
||||
|
|
|
|||
26
Dockerfile
26
Dockerfile
|
|
@ -189,7 +189,13 @@ RUN cd web && npm run build && \
|
|||
|
||||
# ---------- Source code ----------
|
||||
# .dockerignore excludes node_modules, so the installs above survive.
|
||||
COPY . .
|
||||
# --link decouples this layer from parents for cache purposes; --chmod bakes
|
||||
# the final read-only permissions at copy time so we skip the separate
|
||||
# `chmod -R` pass that previously walked ~30k files across the venv +
|
||||
# node_modules + source (21s amd64 / 222s arm64 — #49113). `a+rX,go-w`
|
||||
# gives the non-root hermes user read + traverse but no write; root retains
|
||||
# write so the build steps below don't need chmod u+w dances.
|
||||
COPY --link --chmod=a+rX,go-w . .
|
||||
|
||||
# ---------- Permissions ----------
|
||||
# Link hermes-agent itself (editable). Deps are already installed in the
|
||||
|
|
@ -197,19 +203,15 @@ COPY . .
|
|||
# resolution or downloads.
|
||||
RUN uv pip install --no-cache-dir --no-deps -e "."
|
||||
|
||||
# Keep /opt/hermes immutable for the runtime hermes user. Hosted/container
|
||||
# instances must not be able to self-edit the installed source or venv; user
|
||||
# data, skills, plugins, config, logs, and dashboard uploads live under
|
||||
# /opt/data instead. Root can still repair the image during build/boot, but
|
||||
# supervised Hermes processes drop to the non-root hermes user.
|
||||
# Wire the exec shim and install-method stamp. Files under /opt/hermes are
|
||||
# already root-owned (COPY, uv sync, npm install all run as root) and
|
||||
# read-only for the hermes user (go-w from the --chmod above).
|
||||
|
||||
USER root
|
||||
RUN mkdir -p /opt/hermes/bin && \
|
||||
cp /opt/hermes/docker/hermes-exec-shim.sh /opt/hermes/bin/hermes && \
|
||||
chmod 0755 /opt/hermes/bin/hermes && \
|
||||
printf 'docker\n' > /opt/hermes/.install_method && \
|
||||
chown -R root:root /opt/hermes && \
|
||||
chmod -R a+rX /opt/hermes && \
|
||||
chmod -R a-w /opt/hermes
|
||||
printf 'docker\n' > /opt/hermes/.install_method
|
||||
# The ``.install_method`` stamp is baked next to the running code (the install
|
||||
# tree), NOT into $HERMES_HOME. $HERMES_HOME (/opt/data) is a shared data
|
||||
# volume that is commonly bind-mounted from the host and even shared with a
|
||||
|
|
@ -240,9 +242,7 @@ RUN mkdir -p /opt/hermes/bin && \
|
|||
# every published image has it.
|
||||
ARG HERMES_GIT_SHA=
|
||||
RUN if [ -n "${HERMES_GIT_SHA}" ]; then \
|
||||
chmod u+w /opt/hermes && \
|
||||
printf '%s\n' "${HERMES_GIT_SHA}" > /opt/hermes/.hermes_build_sha && \
|
||||
chmod a-w /opt/hermes /opt/hermes/.hermes_build_sha; \
|
||||
printf '%s\n' "${HERMES_GIT_SHA}" > /opt/hermes/.hermes_build_sha; \
|
||||
fi
|
||||
|
||||
# ---------- s6-overlay service wiring ----------
|
||||
|
|
|
|||
|
|
@ -12,22 +12,16 @@ def _dockerfile_text() -> str:
|
|||
return DOCKERFILE.read_text()
|
||||
|
||||
|
||||
def test_dockerfile_makes_opt_hermes_root_owned_and_non_writable() -> None:
|
||||
def test_dockerfile_makes_opt_hermes_readonly_for_hermes_user() -> None:
|
||||
text = _dockerfile_text()
|
||||
|
||||
assert "COPY --chown=hermes:hermes . ." not in text
|
||||
assert "COPY . ." in text
|
||||
assert "chown -R root:root /opt/hermes" in text
|
||||
assert "chmod -R a+rX /opt/hermes" in text
|
||||
assert "chmod -R a-w /opt/hermes" in text
|
||||
|
||||
immutable_block = re.search(
|
||||
r"RUN mkdir -p /opt/hermes/bin && \\\n"
|
||||
r"(?:.*\\\n)+?"
|
||||
r"\s+chmod -R a-w /opt/hermes",
|
||||
text,
|
||||
)
|
||||
assert immutable_block, "Dockerfile must lock /opt/hermes after installing code/deps"
|
||||
# --chmod on the source COPY bakes read-only perms at copy time instead
|
||||
# of a separate chmod -R pass (which walked ~30k files — #49113).
|
||||
assert "COPY --link --chmod=a+rX,go-w . ." in text
|
||||
# The old tree-walking passes must not be present.
|
||||
assert "chown -R root:root /opt/hermes" not in text
|
||||
assert "chmod -R a+rX /opt/hermes" not in text
|
||||
assert "chmod -R a-w /opt/hermes" not in text
|
||||
|
||||
|
||||
def test_dockerfile_keeps_mutable_state_under_opt_data() -> None:
|
||||
|
|
@ -68,22 +62,20 @@ def test_dockerfile_bakes_code_scoped_install_method_stamp() -> None:
|
|||
(/opt/hermes/.install_method) first; baking it at build time keeps the
|
||||
published image self-identifying as 'docker' WITHOUT writing into the
|
||||
shared $HERMES_HOME data volume (which a host install may also use).
|
||||
It must live inside the immutable block so the runtime user can't alter it.
|
||||
The stamp is created by root in the shim-wiring RUN block; the hermes
|
||||
user can't modify it (go-w from the --chmod on the source COPY).
|
||||
"""
|
||||
text = _dockerfile_text()
|
||||
assert "printf 'docker\\n' > /opt/hermes/.install_method" in text
|
||||
|
||||
immutable_block = re.search(
|
||||
# The stamp must be in the RUN block that wires the exec shim.
|
||||
shim_block = re.search(
|
||||
r"RUN mkdir -p /opt/hermes/bin && \\\n"
|
||||
r"(?:.*\\\n)+?"
|
||||
r"\s+chmod -R a-w /opt/hermes",
|
||||
r"\s+printf 'docker\\n' > /opt/hermes/\.install_method",
|
||||
text,
|
||||
)
|
||||
assert immutable_block, "immutable block must exist"
|
||||
assert ".install_method" in immutable_block.group(0), (
|
||||
"the code-scoped install-method stamp must be baked inside the "
|
||||
"immutable /opt/hermes block"
|
||||
)
|
||||
assert shim_block, "install-method stamp must be in the shim-wiring RUN block"
|
||||
|
||||
|
||||
def test_dockerfile_redirects_lazy_installs_to_durable_target() -> None:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue