From 0506f39bf3214254caff10031b28f5c75eab7a0f Mon Sep 17 00:00:00 2001 From: Joseph McQueen Date: Mon, 6 Jul 2026 10:12:08 -0400 Subject: [PATCH] fix(deploy): install.sh always docker-loads so stale wrong-arch images can't hide the shipped tarball Previously install.sh short-circuited with "Image ... already present - skipping load" whenever a tag with the same version already existed in Docker. That optimization was actively harmful: if an earlier deploy attempt had loaded a wrong-arch (e.g. arm64) image at the same tag, we'd never load the corrected tarball in the current ZIP and the arch check would keep failing against the stale image forever. docker load reassigns the tag atomically to whatever is in the tarball and is a fast no-op when the layers are already present, so unconditional load is both safe and self-healing. install.sh now also prints the loaded image id + arch and, on mismatch, tells the operator to `git pull` on the build host before re-running package.sh so they pick up the buildx/binfmt fixes. Co-authored-by: Cursor --- docker/remote-agent/deploy/README.md | 8 ++++- docker/remote-agent/deploy/install.sh | 46 +++++++++++++++++++-------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/docker/remote-agent/deploy/README.md b/docker/remote-agent/deploy/README.md index 8b9bcbb..a13651d 100644 --- a/docker/remote-agent/deploy/README.md +++ b/docker/remote-agent/deploy/README.md @@ -106,12 +106,18 @@ it — handy if you need to roll back quickly. does not match this host"** — the ZIP was built for the wrong CPU architecture (typically an Apple Silicon Mac produced an `arm64` image for an `x86_64` Linux host). `install.sh` catches this and prints the - exact rebuild command; ask your build operator to run: + exact rebuild command; ask your build operator to `git pull` first + (to pick up the fixed cross-arch build) and then run: ``` ./docker/remote-agent/package.sh --platform linux/amd64 ``` (or `linux/arm64` if this host is ARM — run `uname -m` to check: `x86_64` → `linux/amd64`, `aarch64` → `linux/arm64`.) + + Note that `install.sh` **always** re-runs `docker load` on the bundled + tarball, so a stale image left from an earlier wrong-arch attempt at the + same version tag will be transparently replaced when you install a + corrected bundle — no need to `docker rmi` by hand. - **Agent connects, then disconnects immediately** — `WS_TOKEN` doesn't match the server. Fix in `.env`, then `docker compose restart`. - **Agent never connects** — check `WS_URL` (correct hostname, correct diff --git a/docker/remote-agent/deploy/install.sh b/docker/remote-agent/deploy/install.sh index 42f0ca3..afddf9c 100755 --- a/docker/remote-agent/deploy/install.sh +++ b/docker/remote-agent/deploy/install.sh @@ -65,21 +65,29 @@ else fi # --- 3. Load the image ----------------------------------------------------- +# ALWAYS docker load, unconditionally. `docker load` reassigns the tag to +# whatever's in the tarball and is a fast no-op when the layers are already +# present, so this is safe to re-run. We intentionally do NOT "skip if the +# tag already exists" — a stale image left over from a previous wrong-arch +# attempt has this exact tag, and skipping the load would hide the correct +# image in the tarball we're actually holding. IMAGE_TAG="sha-remote-agent:${VERSION}" -if docker image inspect "$IMAGE_TAG" >/dev/null 2>&1; then - log "Image $IMAGE_TAG already present — skipping load." -else - log "Loading Docker image from $IMAGE_TARBALL..." - docker load -i "$IMAGE_TARBALL" -fi + +log "Loading Docker image from ${IMAGE_TARBALL}..." +docker load -i "$IMAGE_TARBALL" # --- 3a. Platform sanity check -------------------------------------------- -# If the image was built for a different CPU architecture than this host, -# Docker will let it "run" but tini (and node) fail with cryptic errors -# like "exec format error". Catch that up front with a clear message. +# Inspect the image we just (re)loaded — this is the ground truth for what +# was actually shipped in this ZIP, independent of anything that was on the +# host before. If the tarball was built for a different CPU architecture +# than this host, Docker will let it "run" but tini (and node) fail with +# cryptic errors like "exec format error". Catch that up front with a +# clear message. IMAGE_ARCH="$(docker image inspect --format '{{.Architecture}}' "$IMAGE_TAG" 2>/dev/null || true)" +IMAGE_ID_SHORT="$(docker image inspect --format '{{.Id}}' "$IMAGE_TAG" 2>/dev/null | sed 's|^sha256:||' | cut -c1-12)" + HOST_ARCH_RAW="$(uname -m)" case "$HOST_ARCH_RAW" in x86_64|amd64) HOST_ARCH="amd64" ;; @@ -88,10 +96,22 @@ case "$HOST_ARCH_RAW" in *) HOST_ARCH="$HOST_ARCH_RAW" ;; esac -if [[ -n "$IMAGE_ARCH" && "$IMAGE_ARCH" != "$HOST_ARCH" ]]; then - warn "Image architecture ($IMAGE_ARCH) does not match this host ($HOST_ARCH)." - warn "The container will fail to start with 'exec format error'." - die "Rebuild on the dev host with: ./docker/remote-agent/package.sh --platform linux/${HOST_ARCH}" +log "Loaded ${IMAGE_TAG} (id: ${IMAGE_ID_SHORT:-unknown}, arch: ${IMAGE_ARCH:-unknown}); host arch: ${HOST_ARCH}." + +if [[ -z "$IMAGE_ARCH" ]]; then + die "Could not read image architecture from Docker — is the image really loaded?" +fi + +if [[ "$IMAGE_ARCH" != "$HOST_ARCH" ]]; then + warn "Image architecture (${IMAGE_ARCH}) does not match this host (${HOST_ARCH})." + warn "The container would fail to start with 'exec /sbin/tini: exec format error'." + warn "" + warn "This means the ZIP was built for the wrong CPU. On the build host:" + warn " 1. git pull # make sure you have the fixed package.sh" + warn " 2. ./docker/remote-agent/package.sh --platform linux/${HOST_ARCH}" + warn "The updated package.sh verifies the architecture during build and" + warn "refuses to produce a ZIP that would fail this check." + die "Aborting install. Ship a linux/${HOST_ARCH} bundle and re-run this script." fi # --- 4. Bootstrap .env -----------------------------------------------------