#!/usr/bin/env bash set -euo pipefail umask 077 ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) BASE_ENV=${WNH_LOAD_BASE_ENV_FILE:-"$ROOT/.env.load"} LABEL=${1:-"cycle-$(date -u +%Y%m%dT%H%M%SZ)"} MODE=${2:-load} if [[ ! "$LABEL" =~ ^[A-Za-z0-9._-]+$ ]]; then echo "Run label may contain only letters, numbers, dot, underscore, and dash." >&2 exit 1 fi case "$MODE" in load | resilience | both) ;; *) echo "Mode must be load, resilience, or both." >&2 exit 1 ;; esac WNH_LOAD_ENV_FILE="$BASE_ENV" "$ROOT/scripts/ensure-local-load-env.sh" if [[ ! -f "$BASE_ENV" ]]; then echo "Missing base load environment: $BASE_ENV." >&2 exit 1 fi run_id="$(date -u +%Y%m%d%H%M%S)-$$" safe_id=$(printf '%s' "$run_id" | tr -cd 'A-Za-z0-9') project="who_need_help_load_$safe_id" temporary_env=$(mktemp "${TMPDIR:-/tmp}/wnh-load-cycle.XXXXXX.env") export WNH_LOAD_ENV_FILE=$temporary_env export WNH_LOAD_TOOLS_IMAGE="who-need-help:load-tools-$safe_id" cp "$BASE_ENV" "$temporary_env" chmod 600 "$temporary_env" set_env_value() { local name=$1 local value=$2 local replacement="$name=$value" if grep -q "^${name}=" "$temporary_env"; then sed -i "s|^${name}=.*|$replacement|" "$temporary_env" else printf '%s\n' "$replacement" >>"$temporary_env" fi } set_env_value LOAD_PROJECT "$project" set_env_value COMPOSE_PROJECT_NAME "$project" set_env_value TRAEFIK_PROJECT_CONSTRAINT "$project" set_env_value TRAEFIK_DOCKER_NETWORK "${project}_ingress" set_env_value APP_IMAGE "who-need-help:load-$safe_id" set_env_value SOCKET_PROXY_IMAGE "who-need-help:socket-proxy-load-$safe_id" set_env_value POSTGIS_IMAGE "who-need-help:postgis-load-$safe_id" set -a # shellcheck source=/dev/null . "$temporary_env" set +a for name in APP_IMAGE SOCKET_PROXY_IMAGE POSTGIS_IMAGE; do if [[ -z "${!name:-}" ]]; then echo "$name is missing from the generated cycle environment." >&2 exit 1 fi done if [[ -n "$(docker ps -aq --filter "label=com.docker.compose.project=$project")" ]] || [[ -n "$(docker network ls -q --filter "label=com.docker.compose.project=$project")" ]] || [[ -n "$(docker volume ls -q --filter "label=com.docker.compose.project=$project")" ]]; then echo "Generated load-cycle project already owns Docker resources: $project." >&2 exit 1 fi started=0 cleanup() { local status=$? local cleanup_status=0 trap - EXIT HUP INT TERM if [[ "$started" -eq 1 ]] || [[ -n "$(docker ps -aq --filter "label=com.docker.compose.project=$project")" ]] || [[ -n "$(docker network ls -q --filter "label=com.docker.compose.project=$project")" ]]; then "$ROOT/scripts/load-stack-stop.sh" >/dev/null 2>&1 || cleanup_status=1 fi while IFS= read -r volume; do [[ -n "$volume" ]] || continue observed_project=$(docker volume inspect --format \ '{{index .Labels "com.docker.compose.project"}}' "$volume") references=$(docker ps -aq --filter "volume=$volume") if [[ "$observed_project" != "$project" ]] || [[ -n "$references" ]]; then echo "Refusing unexpected or referenced load-cycle volume: $volume" >&2 cleanup_status=1 continue fi docker volume rm "$volume" >/dev/null || cleanup_status=1 done < <(docker volume ls -q --filter "label=com.docker.compose.project=$project") for image in "$APP_IMAGE" "$SOCKET_PROXY_IMAGE" "$POSTGIS_IMAGE" \ "$WNH_LOAD_TOOLS_IMAGE"; do if image_id=$(docker image inspect --format '{{.Id}}' "$image" 2>/dev/null); then if [[ -n "$(docker ps -aq --filter "ancestor=$image_id")" ]]; then echo "Refusing referenced load-cycle image: $image" >&2 cleanup_status=1 else docker image rm "$image" >/dev/null || cleanup_status=1 fi fi done rm -f "$temporary_env" if [[ -n "$(docker ps -aq --filter "label=com.docker.compose.project=$project")" ]] || [[ -n "$(docker network ls -q --filter "label=com.docker.compose.project=$project")" ]] || [[ -n "$(docker volume ls -q --filter "label=com.docker.compose.project=$project")" ]]; then echo "Load-cycle cleanup left project resources behind: $project." >&2 cleanup_status=1 fi if [[ "$status" -eq 0 && "$cleanup_status" -ne 0 ]]; then status=$cleanup_status fi exit "$status" } trap cleanup EXIT HUP INT TERM "$ROOT/scripts/load-stack-up.sh" started=1 case "$MODE" in load) "$ROOT/scripts/load-run.sh" "$LABEL" ;; resilience) "$ROOT/scripts/load-resilience-run.sh" "$LABEL" ;; both) "$ROOT/scripts/load-run.sh" "$LABEL-load" "$ROOT/scripts/load-resilience-run.sh" "$LABEL-resilience" ;; esac echo "Load cycle completed; its project resources will now be removed: $project"