The first successful galactus deploy came up all-green while the web tier was
running a build from two commits earlier. The registry held web:latest from
3ff56e6; the host still had a web:latest cached from 4ee7ec7; the deploy
reported success and served the old one. The API was only current because it
had been pulled by hand during earlier debugging.
Two independent failures, both fixed here.
1. Images are not pulled. The deploy action's `pull: true` does not reliably
refresh an already-cached moving tag on a standalone endpoint. Added a
Pull images step (deploy/scripts/pull-images.mjs) that pulls each image
through Portainer's Docker API with registry credentials and fails the
deploy if a pull fails — note the endpoint answers 200 even when the pull
errored, so the stream body has to be inspected, not just the status.
2. The drift check could not see it. Both the verify step and the web footer
compared APP_VERSION, but on a branch build BOTH tiers report "master", so
equality proved nothing. They now compare gitSha, which is the only field
that differs between two builds of the same branch. api and web come from
one matrix run, so a difference can only mean an image was not replaced.
This needed a /version on the web tier too — previously its build identity
was only readable by scraping window.__APP_BUILD__ out of the HTML.
pull-images.mjs builds the X-Registry-Auth header as URL-safe base64 WITH
padding: Node's "base64url" omits the padding and Portainer's Go decoder
rejects it with "Illegal base64 data at input byte N".
Verified against galactus: pulls both images, and exits non-zero on a
nonexistent tag.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>