#!/usr/bin/env bash # ============================================================================= # preflight/preflight.sh — foundation host & toolchain pre-flight validation. # # PLAN-002 Phase 1 / §9.1: a freshly-cloned repo validates host + toolchain # BEFORE any `pulumi up`. Fails CLOSED: exits NON-ZERO if ANY required check # fails. Composable: one script per concern under checks/, aggregated here. # # Runs on the operator workstation AND in CI, on macOS (bash 3.2) and Linux. # No hard bashisms: no associative arrays, no mapfile, no ${var^^}, no # process substitution. # # USAGE # ./preflight/preflight.sh # run all checks # ./preflight/preflight.sh tools env # run only the named checks # NO_COLOR=1 ./preflight/preflight.sh # disable ANSI color # # EXIT CODES # 0 all REQUIRED checks passed (gated ssh/dns may WARN — that's fine) # 1 at least one required check failed (missing/old tool, missing ENV, etc.) # ============================================================================= set -euo pipefail PF_DIR=$(cd "$(dirname "$0")" && pwd) CHECKS_DIR="$PF_DIR/checks" # Colors for the top-level summary (lib/common.sh handles per-check coloring). if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then C_RED=$(printf '\033[31m'); C_GRN=$(printf '\033[32m') C_YEL=$(printf '\033[33m'); C_BLD=$(printf '\033[1m'); C_RST=$(printf '\033[0m') else C_RED=""; C_GRN=""; C_YEL=""; C_BLD=""; C_RST="" fi # Ordered list of checks. REQUIRED checks gate the exit code; GATED checks # (ssh, dns) are allowed to be absent/unreachable and only warn — they are # listed so they run, but their result never flips the overall status. REQUIRED_CHECKS="versions tools env docker" GATED_CHECKS="ssh dns" # Optional positional args restrict which checks run (any from either list). if [ "$#" -gt 0 ]; then REQUIRED_CHECKS="" GATED_CHECKS="" for arg in "$@"; do case "$arg" in ssh|dns) GATED_CHECKS="$GATED_CHECKS $arg" ;; *) REQUIRED_CHECKS="$REQUIRED_CHECKS $arg" ;; esac done fi printf '%s== foundation preflight ==%s\n' "$C_BLD" "$C_RST" printf 'repo: %s\n' "$(cd "$PF_DIR/.." && pwd)" printf 'host: %s (%s)\n\n' "$(uname -s)" "$(uname -m)" FAILED_CHECKS="" RAN_CHECKS="" run_check() { name="$1" script="$CHECKS_DIR/$name.sh" if [ ! -f "$script" ]; then printf '%s[%s] no such check (%s) %s\n' "$C_RED" "$name" "$script" "$C_RST" FAILED_CHECKS="$FAILED_CHECKS $name" return fi RAN_CHECKS="$RAN_CHECKS $name" # Run in its own shell so `set -e` inside a check can't abort the orchestrator. if bash "$script"; then : # check returned 0 else FAILED_CHECKS="$FAILED_CHECKS $name" fi printf '\n' } # Required checks first (these gate exit status). for c in $REQUIRED_CHECKS; do [ -n "$c" ] && run_check "$c" done # Gated checks (run for visibility; their failure does NOT gate exit status, # but a non-zero from them would only happen on an internal error — we still # don't let it fail the run, matching the "skip with WARNING" requirement). for c in $GATED_CHECKS; do [ -n "$c" ] || continue script="$CHECKS_DIR/$c.sh" if [ -f "$script" ]; then RAN_CHECKS="$RAN_CHECKS $c" bash "$script" || true # gated: never gates the exit code printf '\n' fi done # ---- summary ---- printf '%s== summary ==%s\n' "$C_BLD" "$C_RST" printf 'ran:%s\n' "$RAN_CHECKS" if [ -n "$FAILED_CHECKS" ]; then printf '%sFAIL%s — failing checks:%s\n' "$C_RED" "$C_RST" "$FAILED_CHECKS" printf '%spreflight FAILED — fix the above before `pulumi up`.%s\n' "$C_RED" "$C_RST" exit 1 fi printf '%sPASS%s — all required checks passed.\n' "$C_GRN" "$C_RST" printf '%sNote:%s gated ssh/dns checks WARN-skip until the Pulumi stack is configured.\n' "$C_YEL" "$C_RST" exit 0