#!/bin/sh # Run on the deployment host, or in the runner with /runtime bind-mounted. set -eu TAG=${1:?Usage: deploy.sh IMAGE_TAG} case "$TAG" in *[!A-Za-z0-9_.-]*|'') echo "Invalid image tag" >&2; exit 2;; esac RUNTIME=${MQTT5_RUNTIME:-/opt/test-mqtt-5/backend} mkdir -p "$RUNTIME" # Serialize manual and CI deployments on the shared mounted directory. exec 9>"$RUNTIME/deploy.lock" flock -w 120 9 [ -s "$RUNTIME/secrets/backend.json" ] && [ -s "$RUNTIME/ca.crt" ] || { echo "Server credentials/CA missing" >&2; exit 1; } docker image inspect "mqtt5-test-backend:$TAG" >/dev/null SOURCE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) HAD_PREVIOUS=false if [ -s "$RUNTIME/.env" ] && [ -s "$RUNTIME/compose.yaml" ]; then cp "$RUNTIME/.env" "$RUNTIME/.env.previous" cp "$RUNTIME/compose.yaml" "$RUNTIME/compose.previous.yaml" HAD_PREVIOUS=true fi rollback() { if [ "$HAD_PREVIOUS" = true ]; then cp "$RUNTIME/.env.previous" "$RUNTIME/.env" cp "$RUNTIME/compose.previous.yaml" "$RUNTIME/compose.yaml" docker compose --project-directory "$RUNTIME" --env-file "$RUNTIME/.env" -f "$RUNTIME/compose.yaml" up -d --force-recreate backend echo "Previous backend release restored; inspect its health." else docker compose --project-directory "$RUNTIME" --env-file "$RUNTIME/.env" -f "$RUNTIME/compose.yaml" stop backend || true echo "First deployment failed; backend stopped." fi } cp "$SOURCE/compose.yaml" "$RUNTIME/compose.next.yaml" printf 'IMAGE_TAG=%s\n' "$TAG" > "$RUNTIME/.env.next" if ! docker compose --project-directory "$RUNTIME" --env-file "$RUNTIME/.env.next" -f "$RUNTIME/compose.next.yaml" config --quiet; then echo "Compose validation failed; running backend untouched" >&2; exit 1 fi mv "$RUNTIME/compose.next.yaml" "$RUNTIME/compose.yaml" mv "$RUNTIME/.env.next" "$RUNTIME/.env" if ! docker compose --project-directory "$RUNTIME" --env-file "$RUNTIME/.env" -f "$RUNTIME/compose.yaml" up -d --force-recreate backend; then rollback; exit 1; fi CID=$(docker compose --project-directory "$RUNTIME" --env-file "$RUNTIME/.env" -f "$RUNTIME/compose.yaml" ps -q backend) for attempt in $(seq 1 30); do HEALTH=$(docker inspect --format '{{.State.Health.Status}}' "$CID" 2>/dev/null || true) if [ "$HEALTH" = healthy ]; then echo "Backend healthy: $TAG"; exit 0; fi sleep 2 done echo "Backend health failed; rolling back" >&2 rollback exit 1