thermograph/backend/scripts/smoke.sh
emi de8e847f9f
All checks were successful
Build + push backend image (Forgejo registry) / build-push (push) Successful in 1m23s
Build + push frontend image (Forgejo registry) / build-push (push) Successful in 1m22s
Sync infra to hosts / sync-beta (push) Successful in 9s
Sync infra to hosts / sync-prod (push) Successful in 8s
secrets-guard / encrypted (push) Successful in 6s
shell-lint / shellcheck (push) Successful in 9s
Deploy backend to beta VPS / deploy (push) Successful in 2m4s
Deploy frontend to beta VPS / deploy (push) Successful in 1m8s
shell: add shellcheck CI guard and drive the tree to zero findings (#19)
Adds .forgejo/workflows/shell-lint.yml (pinned shellcheck v0.11.0 + sha256, -x,
default severity, fail on any finding, not path-filtered) and drives all 26
scripts to zero findings.

Two defects shellcheck cannot see:

render-secrets.sh left DECRYPTED vault contents in /tmp whenever a sops decrypt
failed -- the caller's set -e aborted the function before either cleanup ran.
Now removed on every exit path, with `|| return 1` on both sops calls so a
decrypt failure can never write a partial /etc/thermograph.env regardless of the
caller's shell options. Explicitly not a `trap ... RETURN`: such a trap set in a
sourced function persists into the caller's shell and re-fires when the caller's
next `.`/source completes, where the function-local tmp is unset -- fatal and
silent under deploy.sh's set -u. The file now records that reasoning.

autoscale.sh ran `set -eu` without pipefail while piping docker stats into awk,
so a failed left side was swallowed and the loop autoscaled on empty input.
Promoted to pipefail with a missed sample treated as a skip; verified busybox ash
in docker:27-cli supports it.

Also: capture-fixtures.sh's `jq . || cat` ran cat after jq had consumed stdin,
silently writing truncated fixtures; deploy.sh/deploy-stack.sh `# shellcheck
source=` paths corrected for the monorepo layout.
2026-07-23 22:26:05 +00:00

45 lines
2 KiB
Bash
Executable file

#!/usr/bin/env bash
# Image boot-smoke: prove the backend IMAGE actually boots and serves its contract
# (a `docker build` succeeding does not prove the container starts). Builds the image
# locally (or uses a provided BACKEND_IMAGE_TAG), runs it + a throwaway TimescaleDB via
# docker-compose.test.yml, waits for /healthz, then asserts /healthz and the version
# endpoint. Always tears the stack down.
#
# ./scripts/smoke.sh # build :local and smoke it
# BACKEND_IMAGE_TAG=sha-abc123 ./scripts/smoke.sh # smoke a published image (CI)
set -euo pipefail
cd "$(dirname "$0")/.."
export POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-smoke}"
# The image to smoke, pulled from the registry (no local build). CI passes the just-
# pushed sha (BACKEND_IMAGE_TAG=sha-<12hex>); locally it defaults to a published tag.
export BACKEND_IMAGE_TAG="${BACKEND_IMAGE_TAG:-v0.0.2-split-ci}"
IMG="${REGISTRY_HOST:-git.thermograph.org}/${BACKEND_IMAGE_PATH:-emi/thermograph-backend/app}:${BACKEND_IMAGE_TAG}"
export SMOKE_HOST_PORT="${SMOKE_HOST_PORT:-18137}"
COMPOSE=(docker compose -f docker-compose.test.yml)
PORT_URL="http://127.0.0.1:${SMOKE_HOST_PORT}"
cleanup() { echo "==> tearing down"; "${COMPOSE[@]}" down -v --remove-orphans >/dev/null 2>&1 || true; }
trap cleanup EXIT
echo "==> pulling $IMG"
"${COMPOSE[@]}" pull backend
echo "==> starting backend + db ($IMG)"
"${COMPOSE[@]}" up -d
echo "==> waiting for /healthz (up to 90s)"
for _ in $(seq 1 45); do
if curl -fsS -o /dev/null "$PORT_URL/healthz"; then ok=1; break; fi
sleep 2
done
[ "${ok:-}" = 1 ] || { echo "!! backend never became healthy"; "${COMPOSE[@]}" logs --tail=40 backend; exit 1; }
echo "==> asserting contract"
curl -fsS "$PORT_URL/healthz" >/dev/null && echo " /healthz 200 ok"
ver=$(curl -fsS "$PORT_URL/api/version")
echo " /api/version -> $ver"
echo "$ver" | grep -q '"backend_version"[[:space:]]*:[[:space:]]*"2"' \
|| { echo "!! /api/version did not report backend_version=2"; exit 1; }
echo "==> SMOKE PASSED"