#!/bin/sh set -eu ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) ENV_FILE=${WNH_LOAD_ENV_FILE:-"$ROOT/output/runtime/load.env"} LOAD_TOOLS_IMAGE=${WNH_LOAD_TOOLS_IMAGE:-who-need-help:load-tools} RUNTIME_OWNER_IMAGE=python:3.14.6-alpine3.23@sha256:b165067c5afc37fa5608a3c05609cc3d51aafd808a30fbfd822ee594fef55ad4 if [ ! -f "$ENV_FILE" ]; then echo "Missing $ENV_FILE; no load-profile project was selected." >&2 exit 1 fi set -a # shellcheck source=/dev/null . "$ENV_FILE" set +a : "${LOAD_PROJECT:?LOAD_PROJECT is missing from the generated load runtime environment}" if [ "$LOAD_PROJECT" = who_need_help ]; then echo "Refusing to stop the staging Compose project." >&2 exit 1 fi case "$LOAD_PROJECT" in *[!a-z0-9_-]* | '') echo "LOAD_PROJECT contains unsupported characters." >&2 exit 1 ;; esac cd "$ROOT" docker compose \ --env-file "$ENV_FILE" \ -p "$LOAD_PROJECT" \ -f compose.yaml \ -f compose.load.yaml \ down --remove-orphans # Optional observability and backup overlays create additional networks which # are not present in the base load Compose file. Remove only unattached # networks carrying this exact isolated project label. for network_id in $(docker network ls -q \ --filter "label=com.docker.compose.project=$LOAD_PROJECT"); do observed_project=$(docker network inspect --format \ '{{index .Labels "com.docker.compose.project"}}' "$network_id") endpoints=$(docker network inspect --format '{{len .Containers}}' "$network_id") if [ "$observed_project" != "$LOAD_PROJECT" ] || [ "$endpoints" != 0 ]; then echo "Refusing unexpected or connected load-profile network: $network_id" >&2 exit 1 fi docker network rm "$network_id" >/dev/null done if image_id=$(docker image inspect --format '{{.Id}}' "$LOAD_TOOLS_IMAGE" 2>/dev/null); then if [ -z "$(docker ps -aq --filter "ancestor=$image_id")" ]; then docker image rm "$LOAD_TOOLS_IMAGE" >/dev/null fi fi # Prometheus and Grafana read run-scoped files under their container UIDs. Once # every container and network for this exact isolated project is gone, restore # the host owner and remove only that project's generated runtime directory. # Without this step, an otherwise successful cleanup can leave empty UID-owned # directories that the invoking user cannot remove. observability_runtime="$ROOT/tmp/observability/$LOAD_PROJECT" if [ -d "$observability_runtime" ]; then if [ -n "$(docker ps -aq --filter "label=com.docker.compose.project=$LOAD_PROJECT")" ] || [ -n "$(docker network ls -q --filter "label=com.docker.compose.project=$LOAD_PROJECT")" ]; then echo "Refusing to remove observability runtime while project resources remain." >&2 exit 1 fi docker run --rm \ --user 0:0 \ --volume "$observability_runtime:/runtime" \ --entrypoint /bin/sh \ "$RUNTIME_OWNER_IMAGE" \ -euc "chown -R $(id -u):$(id -g) /runtime; chmod -R u+rwX /runtime" find "$observability_runtime" -xdev -depth -delete fi echo "Removed the isolated load-profile containers, networks, and generated observability runtime; its named volumes remain."