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 <cursoragent@cursor.com>
This commit is contained in:
Joseph McQueen 2026-07-06 10:12:08 -04:00
parent 74b3a9fcb6
commit 0506f39bf3
2 changed files with 40 additions and 14 deletions

View file

@ -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

View file

@ -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..."
log "Loading Docker image from ${IMAGE_TARBALL}..."
docker load -i "$IMAGE_TARBALL"
fi
# --- 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 -----------------------------------------------------