@@ -113,19 +113,56 @@ pass "image builds"
113113docker run -d --name " $NAME " --memory=512m --memory-swap=512m --cpus=0.5 -p 4599:4000 " $TAG " > /dev/null \
114114 || fail " container did not start"
115115
116+ # NO `... | grep -q` ANYWHERE BELOW, and that is not style. Under `set -o pipefail` — set at
117+ # the top of this file, correctly — `grep -q` exits the instant it matches, the writer upstream
118+ # gets SIGPIPE, and the PIPELINE reports 141. So the check fails exactly when the string is
119+ # found EARLY with output still to come, and passes when the output is short enough to finish
120+ # writing first. Measured here: three consecutive runs of the same green image went pass,
121+ # fail, fail, each failure dumping logs that visibly contained the string it had just failed
122+ # to find. Capturing first and matching in the shell has no pipe and no race.
123+ logs_of () { docker logs " $1 " 2>&1 || true ; }
124+
116125for _ in $( seq 1 40) ; do
117126 if curl -fsS -m 3 http://127.0.0.1:4599/api/health > /dev/null 2>&1 ; then break ; fi
118127 sleep 1
119128done
120- curl -fsS -m 5 http://127.0.0.1:4599/api/health | grep -q ' "status":"ok"' \
121- || { docker logs " $NAME " 2>&1 | tail -20 >&2 ; fail " /api/health did not report ok under 512MB/0.5cpu" ; }
122- pass " /api/health reports ok under --memory=512m --cpus=0.5"
129+ health=$( curl -fsS -m 5 http://127.0.0.1:4599/api/health 2> /dev/null || true)
130+ case " $health " in
131+ * ' "status":"ok"' * ) pass " /api/health reports ok under --memory=512m --cpus=0.5" ;;
132+ * ) logs_of " $NAME " | tail -20 >&2 ; fail " /api/health did not report ok under 512MB/0.5cpu (got: ${health:- no response} )" ;;
133+ esac
123134
124135# The worker is what claims queued jobs. With CG_USE_WORKER=true baked into the image and no
125136# worker running, every index would sit in the queue forever while the app looked healthy —
126137# the failure mode the entrypoint refuses to boot into, asserted here too.
127- docker logs " $NAME " 2>&1 | grep -q " worker started" \
128- || { docker logs " $NAME " 2>&1 | tail -20 >&2 ; fail " the analysis worker did not start" ; }
129- pass " analysis worker started"
138+ #
139+ # TWO SIGNALS, because they fail differently and a gate that cannot tell them apart is a gate
140+ # people re-run instead of read:
141+ #
142+ # · `entrypoint: starting analysis worker` is printed synchronously by the entrypoint before
143+ # the web server is launched at all. Absent => the worker was never launched, which is a
144+ # real defect in the image.
145+ # · `worker started` is the worker's own line, emitted only after it opens SQLite and runs
146+ # migrations — which can land well AFTER /api/health is already answering. Absent while
147+ # the first line is present => slow start, not a broken image.
148+ worker_wait=0
149+ while : ; do
150+ container_logs=$( logs_of " $NAME " )
151+ case " $container_logs " in * " worker started" * ) break ;; esac
152+ [ " $worker_wait " -ge 60 ] && break
153+ worker_wait=$(( worker_wait + 1 ))
154+ sleep 1
155+ done
156+
157+ case " $container_logs " in
158+ * " entrypoint: starting analysis worker" * ) : ;;
159+ * ) printf ' %s\n' " $container_logs " | tail -20 >&2
160+ fail " the entrypoint never launched the worker (is CG_USE_WORKER=true in the image?)" ;;
161+ esac
162+ case " $container_logs " in
163+ * " worker started" * ) pass " analysis worker started (after ${worker_wait} s)" ;;
164+ * ) printf ' %s\n' " $container_logs " | tail -20 >&2
165+ fail " the worker was launched but never reported ready (waited ${worker_wait} s)" ;;
166+ esac
130167
131168printf ' \n%sall docker paths resolve and the image serves%s\n' " $GREEN " " $OFF "
0 commit comments