fix(update): scope install-method stamp to the code tree, not $HERMES_HOME (#48188)

The install method (docker/git/pip/...) describes the *running binary*, but
detect_install_method() read it from $HERMES_HOME/.install_method — a shared
DATA directory. The Docker docs deliberately bind-mount $HERMES_HOME
(~/.hermes:/opt/data) so config/sessions/memory persist and can be shared with
a host-side Desktop/CLI install.

When a containerized gateway and a host install share one $HERMES_HOME, the
home-scoped stamp is a single slot describing two installs: the published image
stamps 'docker' on every boot, the host install then reads 'docker' and the
in-app updater refuses to run 'hermes update' ("doesn't apply inside the Docker
container"). Reinstalling the Desktop app from the DMG doesn't help because the
contaminated stamp is re-read every time.

Fix (option 1 — code-scoped stamp):
- detect_install_method() reads <install tree>/.install_method first (next to
  the running code, immune to the shared data dir). It falls back to the legacy
  $HERMES_HOME stamp for back-compat, but IGNORES a 'docker' home stamp when
  not actually containerized — so already-poisoned shared homes self-heal.
- stamp_install_method() writes the code-scoped stamp.
- install.sh stamps $INSTALL_DIR instead of $HERMES_HOME.
- Dockerfile bakes 'docker' into /opt/hermes/.install_method at build time
  (inside the immutable block); stage2-hook.sh no longer writes the home stamp
  and proactively removes a stale 'docker' one to heal existing shared homes.

Genuine containers still resolve to 'docker' (baked stamp, or legacy home stamp
honored when containerized). Unstamped installs in generic containers still fall
through to git/pip (preserves the #34397 fix).
This commit is contained in:
Ben Barclay 2026-06-18 14:14:41 +10:00 committed by GitHub
parent 3769dff5dd
commit 4440d77bf3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 352 additions and 34 deletions

View file

@ -291,13 +291,25 @@ as_hermes mkdir -p \
"$HERMES_HOME/pairing" \
"$HERMES_HOME/platforms/pairing"
# --- Install-method stamp (read by detect_install_method() in hermes status) ---
# Preserved from the tini-era entrypoint (PR #27843). Must be written as
# the hermes user so ownership matches the file's documented owner.
# tee is invoked directly via s6-setuidgid (no `sh -c` wrapper) for the
# same shell-metacharacter safety described above.
printf 'docker\n' | as_hermes tee "$HERMES_HOME/.install_method" >/dev/null \
|| true
# --- Install-method stamp ---
# The 'docker' stamp is baked into the immutable install tree at
# /opt/hermes/.install_method (see Dockerfile), NOT written here into
# $HERMES_HOME. detect_install_method() reads the code-scoped stamp first.
#
# Why we no longer stamp $HERMES_HOME: it is a shared DATA volume, commonly
# bind-mounted from the host (~/.hermes:/opt/data) and sometimes shared with a
# host-side Desktop/CLI install. Stamping 'docker' here clobbered that host
# install's marker, so its in-app updater read 'docker' and refused to run
# 'hermes update'. To heal homes already poisoned by older images, remove a
# stale 'docker' stamp from $HERMES_HOME if one is present (the host install's
# own installer re-creates its code-scoped stamp; a genuine container relies on
# the baked /opt/hermes stamp, so deleting the data-dir copy is safe).
if [ -f "$HERMES_HOME/.install_method" ]; then
stamped="$(tr -d '[:space:]' < "$HERMES_HOME/.install_method" 2>/dev/null || true)"
if [ "$stamped" = "docker" ]; then
rm -f "$HERMES_HOME/.install_method" 2>/dev/null || true
fi
fi
# --- Seed config files (only on first boot) ---
seed_one() {