fix(deploy): hash every file a locally built image COPYs, not just its Containerfile
build_compose_image skips `podman build` when the installed image's `cfhash` label matches a hash taken from the checkout now — the skip is what keeps frank-tts-engine's ten-minute recursive source clone off every re-deploy. The hash covered the Containerfile ALONE, so any change to a file the image COPYs was invisible: edit dankclaw/voiceservice/app.py or tts-engine-warm.sh, re-run the deploy, and podman reports the existing image is fine. The redeployed container runs the previous code, /health answers 200, and the e2e gate has no way to see it. It bit the boot warmer twice this week. Each case arm now names its own extra=(…) — the paths its Containerfile COPYs — and the label hashes the Containerfile plus those. The two arms are kept apart deliberately: the façade bakes app.py and tts-start.sh, the engine bakes tts-engine-start.sh and tts-engine-warm.sh, and comfyui/loki/alloy have no extra files, so their hash is unchanged and they do not rebuild on this commit. Hashing rather than word-splitting because these are repo-relative paths under a fixed checkout root. The seam drives the real function against a stub podman and asserts observable output — did the deploy ask podman to build, or did it skip? — in five steps: first deploy builds, unchanged tree does not, editing the engine's COPY'd warm hook rebuilds the ENGINE image and not the façade, editing app.py rebuilds the façade. Reverted scripts/deploy-remote.sh to prove it red on the old hash: the warm-hook step is exactly where it fails.
This commit is contained in:
@@ -541,6 +541,14 @@ deploy_observability() {
|
||||
|
||||
build_compose_image() {
|
||||
local service="$1" image cfhash oldhash containerfile
|
||||
# Every file the image COPIES IN, beside the Containerfile. The skip below
|
||||
# compares a hash against the image's `cfhash` label, so anything the build
|
||||
# bakes must be in that hash or the deploy silently keeps serving the old
|
||||
# bytes: an edited app.py or warm hook leaves the Containerfile untouched,
|
||||
# the label matches, `podman build` never runs, and the health gate passes on
|
||||
# a stale container. Services whose Containerfile pulls everything at
|
||||
# build time (comfyui, loki, alloy) keep an empty list.
|
||||
local -a extra=()
|
||||
case "$service" in
|
||||
comfyui)
|
||||
image=comfyui-rocm:gfx1151
|
||||
@@ -557,6 +565,7 @@ build_compose_image() {
|
||||
# fails on a tag that was never made.
|
||||
image=franksdank/frank-tts:latest
|
||||
containerfile=dankclaw/voiceservice/Containerfile
|
||||
extra=(dankclaw/voiceservice/app.py)
|
||||
;;
|
||||
frank-tts-engine)
|
||||
# The arm A TTS engine (qwentts.cpp `tts-server`). Built from
|
||||
@@ -565,12 +574,16 @@ build_compose_image() {
|
||||
# offline: the clone only happens when this Containerfile changes.
|
||||
image=franksdank/frank-tts-engine:latest
|
||||
containerfile=dankclaw/voiceservice/Containerfile.engine
|
||||
# start.sh is the image's CMD and the warmer is the unit's
|
||||
# ExecStartPost; both are COPY'd in, so both decide what this image is.
|
||||
extra=(dankclaw/voiceservice/tts-engine-start.sh
|
||||
dankclaw/voiceservice/tts-engine-warm.sh)
|
||||
;;
|
||||
*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
cfhash="$(sha256sum "$containerfile" | cut -d' ' -f1)"
|
||||
cfhash="$(sha256sum "$containerfile" "${extra[@]}" | cut -d' ' -f1 | tr -d '\n')"
|
||||
oldhash="$(podman image inspect "$image" --format '{{index .Config.Labels "cfhash"}}' 2>/dev/null || echo none)"
|
||||
if [[ "$oldhash" != "$cfhash" ]]; then
|
||||
# --format docker is required, not cosmetic: podman defaults to the OCI
|
||||
|
||||
Reference in New Issue
Block a user