feat(preflight): host/toolchain validation + VERSIONS pin-file — T01

- VERSIONS: 7 container images (CONTRACT_003 §3.2) + 13 host tools, KEY=value,
  source-able+greppable; images carry :PIN_DIGEST placeholders with a documented
  pin-digests procedure (D5 determinism — no real deploy until pinned).
- preflight.sh: fails closed (non-zero on any required check), bash-3.2 safe,
  composable checks/ (versions,tools,env,docker) + gated (ssh,dns) that WARN-skip
  until the stack is configured.
- env check honors D2 (passphrase presence only, never printed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Niemann 2026-06-30 18:00:26 +02:00
parent 188e30e23e
commit edc708b826
12 changed files with 763 additions and 0 deletions

58
preflight/checks/dns.sh Executable file
View file

@ -0,0 +1,58 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# checks/dns.sh — OPTIONAL / GATED: DNS resolution of the configured hosts.*.
# PLAN-002 §9.4: forge.olsitec.de (+ vault/s3) MUST resolve to the VM before
# TLS/handover — but NOT necessarily at scaffold time. So: if the host names
# are not configured in the Pulumi stack yet, SKIP with a WARNING. When they
# ARE configured, attempt resolution; a miss is a WARNING (DNS may not be set
# up this early), never a hard failure. This check never fails preflight alone.
# -----------------------------------------------------------------------------
set -euo pipefail
PF_DIR=$(cd "$(dirname "$0")/.." && pwd)
# shellcheck source=../lib/common.sh
. "$PF_DIR/lib/common.sh"
BOOTSTRAP_DIR=$(cd "$PF_DIR/.." && pwd)/bootstrap
echo "[dns] (gated) DNS resolution of configured hosts.*"
pf_pulumi_config() {
pf_have pulumi || { printf ''; return; }
[ -d "$BOOTSTRAP_DIR" ] || { printf ''; return; }
( cd "$BOOTSTRAP_DIR" 2>/dev/null && pulumi config get "$1" 2>/dev/null ) || printf ''
}
# pf_resolve <name> : true if name resolves to at least one address.
# Tries the tools likely present on macOS/Linux without requiring any one.
pf_resolve() {
n="$1"
if pf_have getent; then getent hosts "$n" >/dev/null 2>&1 && return 0; fi
if pf_have dscacheutil; then dscacheutil -q host -a name "$n" 2>/dev/null | grep -q ip_address && return 0; fi
if pf_have host; then host "$n" >/dev/null 2>&1 && return 0; fi
if pf_have dig; then [ -n "$(dig +short "$n" 2>/dev/null)" ] && return 0; fi
if pf_have nslookup; then nslookup "$n" >/dev/null 2>&1 && return 0; fi
if pf_have python3; then python3 -c "import socket,sys; socket.gethostbyname(sys.argv[1])" "$n" >/dev/null 2>&1 && return 0; fi
return 1
}
if ! pf_have pulumi || [ ! -d "$BOOTSTRAP_DIR" ]; then
pf_warn "no pulumi stack config available yet -> SKIPPING dns resolution (expected pre-Phase-0)"
pf_summary "dns"; exit $?
fi
any=0
for key in foundation:hosts.forge foundation:hosts.vault foundation:hosts.s3; do
name=$(pf_pulumi_config "$key")
[ -n "$name" ] || continue
any=1
if pf_resolve "$name"; then
pf_pass "DNS: $name resolves"
else
pf_warn "DNS: $name does NOT resolve yet (required before TLS/handover, PLAN-002 §9.4) — not failing"
fi
done
if [ "$any" -eq 0 ]; then
pf_warn "no hosts.* configured in stack yet -> SKIPPING dns resolution"
fi
pf_summary "dns"