#!/bin/sh set -eu ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) cd "$ROOT" SHELLCHECK_IMAGE="koalaman/shellcheck-alpine:v0.11.0@sha256:9955be09ea7f0dbf7ae942ac1f2094355bb30d96fffba0ec09f5432207544002" HADOLINT_IMAGE="hadolint/hadolint:v2.14.0-debian@sha256:158cd0184dcaa18bd8ec20b61f4c1cabdf8b32a592d062f57bdcb8e4c1d312e2" ACTIONLINT_IMAGE="rhysd/actionlint:1.7.12@sha256:b1934ee5f1c509618f2508e6eb47ee0d3520686341fec936f3b79331f9315667" TRIVY_IMAGE="aquasec/trivy:0.72.0@sha256:cffe3f5161a47a6823fbd23d985795b3ed72a4c806da4c4df16266c02accdd6f" run_id="$(date -u +%Y%m%d%H%M%S)-$$" project="wnh_quality_$(printf '%s' "$run_id" | tr -d '-')" quality_image="who-need-help:quality-$run_id" assets_image="who-need-help:assets-audit-$run_id" e2e_image="who-need-help:e2e-audit-$run_id" release_image="who-need-help:security-$run_id" scan_dir=$(mktemp -d "${TMPDIR:-/tmp}/wnh-quality-scan.XXXXXX") scan_list="${scan_dir}.files" scan_tar="${scan_dir}.tar" umask 077 QUALITY_POSTGRES_USER="wnh_quality_$(openssl rand -hex 6)" QUALITY_POSTGRES_PASSWORD=$(openssl rand -base64 48 | tr -d '\n') export QUALITY_POSTGRES_USER QUALITY_POSTGRES_PASSWORD compose="docker compose -p $project -f $ROOT/compose.quality.yaml" cleanup() { $compose down --volumes --remove-orphans >/dev/null 2>&1 || true docker image rm "$quality_image" "$assets_image" "$e2e_image" "$release_image" \ >/dev/null 2>&1 || true rm -rf "$scan_dir" "$scan_list" "$scan_tar" } trap cleanup EXIT HUP INT TERM echo "Checking shell scripts with ShellCheck 0.11.0" # Word splitting is intentional: find emits repository-controlled paths and # ShellCheck expects each file as a separate argument. # shellcheck disable=SC2046 docker run --rm \ --volume "$ROOT:/mnt:ro" \ --workdir /mnt \ --entrypoint shellcheck \ "$SHELLCHECK_IMAGE" \ $(find scripts -type f -name '*.sh' -print | sort) echo "Checking Dockerfiles with Hadolint 2.14.0" for dockerfile in Dockerfile android/Dockerfile e2e/Dockerfile; do docker run --rm --interactive "$HADOLINT_IMAGE" \ hadolint --failure-threshold warning - <"$dockerfile" done echo "Checking the GitHub Actions workflow with actionlint 1.7.12" docker run --rm \ --volume "$ROOT:/repo:ro" \ --workdir /repo \ "$ACTIONLINT_IMAGE" echo "Rendering every Docker Compose profile" docker compose --env-file .env.example -f compose.yaml config --quiet mkdir -p "$scan_dir/e2e-output" E2E_OUTPUT_DIR="$scan_dir/e2e-output" docker compose --env-file .env.e2e.example \ -f compose.yaml -f compose.e2e.yaml config --quiet docker compose --env-file .env.load.example \ -f compose.yaml -f compose.load.yaml config --quiet docker compose -p "$project" -f compose.quality.yaml config --quiet echo "Linting the Helm chart" "$ROOT/scripts/bootstrap-kubernetes-tools.sh" >/dev/null "$ROOT/.tools/bin/helm" lint \ --values "$ROOT/deploy/helm/who-need-help/values-kind.yaml" \ "$ROOT/deploy/helm/who-need-help" echo "Scanning only tracked and non-ignored source files" git ls-files --cached --others --exclude-standard -z >"$scan_list" tar --null --no-recursion --files-from="$scan_list" --create --file="$scan_tar" tar --extract --file="$scan_tar" --directory "$scan_dir" "$ROOT/.tools/bin/helm" template who-need-help \ --values "$ROOT/deploy/helm/who-need-help/values-kind.yaml" \ "$ROOT/deploy/helm/who-need-help" \ >"$scan_dir/rendered-helm.yaml" mkdir -p "$ROOT/.tools/trivy-cache" docker run --rm \ --volume "$scan_dir:/scan:ro" \ --volume "$ROOT/.tools/trivy-cache:/root/.cache/trivy" \ "$TRIVY_IMAGE" fs \ --scanners misconfig,secret \ --severity HIGH,CRITICAL \ --exit-code 1 \ /scan echo "Building the pinned quality image and cached Dialyzer PLTs" docker build --target quality --tag "$quality_image" . echo "Running Elixir format, compiler, xref, Credo, Sobelow, Dialyzer, and Hex audit" docker run --rm "$quality_image" sh -euc ' mix format --check-formatted mix compile --force --warnings-as-errors mix xref graph --label compile-connected --fail-above 0 mix credo --strict --min-priority high mix sobelow --exit --strict --private --skip mix dialyzer mix hex.audit ' echo "Starting an isolated PostgreSQL/PostGIS volume for the Phoenix suite" $compose up --detach --wait db docker run --rm \ --network "${project}_internal" \ --env MIX_ENV=test \ --env DB_HOST=db \ --env "DB_USER=$QUALITY_POSTGRES_USER" \ --env "DB_PASSWORD=$QUALITY_POSTGRES_PASSWORD" \ --env TEST_POOL_SIZE=10 \ "$quality_image" \ mix test echo "Auditing locked browser dependencies" docker build --target node_deps --tag "$assets_image" . docker run --rm "$assets_image" npm audit --audit-level=high docker build --tag "$e2e_image" e2e docker run --rm "$e2e_image" npm audit --audit-level=high echo "Building and scanning the production release image" docker build --target release --tag "$release_image" . docker run --rm \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$ROOT/.tools/trivy-cache:/root/.cache/trivy" \ "$TRIVY_IMAGE" image \ --scanners vuln \ --severity HIGH,CRITICAL \ --ignore-unfixed \ --exit-code 1 \ "$release_image" echo "All isolated quality and security gates passed."