#!/usr/bin/env bash # tools/verify-vm/verify.sh — verify ONE module in the right pre-built VM. # # Usage: # verify.sh # provision, run --explain --active, suspend VM # verify.sh --keep # keep VM running after for inspection # verify.sh --destroy # destroy VM after (full reset; slow next run) # verify.sh --list # show every module + the box it's mapped to # # What it does: # 1. Reads tools/verify-vm/targets.yaml: -> (box, kernel_pkg, kver, # expect_detect). # 2. Sets SKK_VM_* env vars + spins up the right Vagrant VM. # 3. If a kernel pin is needed, installs it + reboots the VM. # 4. Runs `skeletonkey --explain --active` inside the VM via # `vagrant provision --provision-with build-and-verify`. # 5. Captures stdout, parses the VERDICT line, compares against expect_detect. # 6. Emits a JSON verification record on stdout (timestamped) suitable for # piping into the per-module verified-on table (separate follow-up). # # Requirements: # - tools/verify-vm/setup.sh has been run successfully (Vagrant + # vagrant-parallels + boxes cached). # - Module name matches a key in targets.yaml. set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" VM_DIR="$REPO_ROOT/tools/verify-vm" TARGETS="$VM_DIR/targets.yaml" LOG_DIR="$VM_DIR/logs" mkdir -p "$LOG_DIR" # Minimal YAML field reader for targets.yaml's flat 2-level structure. # Usage: yget # yget af_packet box -> "ubuntu1804" # Strips surrounding quotes and trailing whitespace; empty fields -> "". yget() { local module="$1" local field="$2" awk -v m="${module}:" -v f=" ${field}:" ' $0 ~ "^"m"[[:space:]]*$" { inmod=1; next } inmod && /^[a-zA-Z]/ { inmod=0 } # next top-level key inmod && $0 ~ "^"f { sub("^[^:]+:[[:space:]]*", "") sub("[[:space:]]+#.*$", "") # trim trailing comment sub("^\"", ""); sub("\"$", "") print; exit } ' "$TARGETS" } # ── arg parsing ─────────────────────────────────────────────────────────── KEEP=0; DESTROY=0; LIST=0; MODULE="" while [[ $# -gt 0 ]]; do case "$1" in --keep) KEEP=1 ;; --destroy) DESTROY=1 ;; --list) LIST=1 ;; -h|--help) sed -n '1,30p' "$0"; exit 0 ;; --*) echo "[-] unknown flag: $1" >&2; exit 2 ;; *) MODULE="$1" ;; esac shift done # ── --list mode ─────────────────────────────────────────────────────────── if [[ $LIST -eq 1 ]]; then printf "%-22s %-14s %-18s %-14s %s\n" "MODULE" "BOX" "KERNEL" "EXPECT" "NOTES" printf "%-22s %-14s %-18s %-14s %s\n" "------" "---" "------" "------" "-----" # Iterate top-level keys (lines starting in column 0 with `something:`). awk '/^[a-z_][a-zA-Z0-9_]*:[[:space:]]*$/ { sub(":", ""); print }' "$TARGETS" | \ while read -r mod; do box=$(yget "$mod" box) kv=$(yget "$mod" kernel_version) exp=$(yget "$mod" expect_detect) notes=$(yget "$mod" notes | head -c 60) [[ -z "$box" ]] && box="(manual)" [[ -z "$kv" ]] && kv="stock" [[ -z "$exp" ]] && exp="?" printf "%-22s %-14s %-18s %-14s %s\n" "$mod" "$box" "$kv" "$exp" "$notes" done exit 0 fi if [[ -z "$MODULE" ]]; then echo "[-] usage: verify.sh [--keep|--destroy]" echo " verify.sh --list # show all targets" exit 2 fi # ── load target ─────────────────────────────────────────────────────────── BOX=$(yget "$MODULE" box) KERNEL_PKG=$(yget "$MODULE" kernel_pkg) KERNEL_VER=$(yget "$MODULE" kernel_version) EXPECT=$(yget "$MODULE" expect_detect) MANUAL=$(yget "$MODULE" manual) NOTES=$(yget "$MODULE" notes) if ! grep -q "^${MODULE}:" "$TARGETS"; then echo "[-] module not in targets.yaml: $MODULE" >&2 exit 3 fi if [[ "$MANUAL" == "true" || -z "$BOX" ]]; then echo "[-] $MODULE is marked manual: true (${NOTES:0:80})" >&2 exit 4 fi BOX="generic/$BOX" VM_HOSTNAME="skk-${MODULE}" SHORT_NOTES="${NOTES:0:80}" # ── kick off provisioning ───────────────────────────────────────────────── echo echo "════════════════════════════════════════════════════" echo " SKELETONKEY VM verifier: $MODULE" echo "════════════════════════════════════════════════════" echo " box: $BOX" echo " kernel: ${KERNEL_PKG:-(stock)} → $KERNEL_VER" echo " expect: $EXPECT" echo " notes: $SHORT_NOTES" echo cd "$VM_DIR" export SKK_VM_BOX="$BOX" export SKK_VM_KERNEL_PKG="$KERNEL_PKG" export SKK_VM_KERNEL_VERSION="$KERNEL_VER" export SKK_VM_HOSTNAME="$VM_HOSTNAME" export SKK_MODULE="$MODULE" export VAGRANT_VAGRANTFILE="$VM_DIR/Vagrantfile" # Spin up if not running. if ! vagrant status "$VM_HOSTNAME" 2>&1 | grep -q "running"; then echo "[*] vagrant up..." vagrant up "$VM_HOSTNAME" --provider=parallels fi # Reboot if a kernel pin was applied (uname -r != target). if [[ -n "$KERNEL_PKG" ]]; then current_kver=$(vagrant ssh "$VM_HOSTNAME" -c "uname -r" 2>/dev/null | tr -d '\r') if [[ "$current_kver" != *"$KERNEL_VER"* ]]; then echo "[*] current kernel $current_kver != target $KERNEL_VER; rebooting..." vagrant reload "$VM_HOSTNAME" sleep 5 fi fi # Run the explain probe. LOG="$LOG_DIR/verify-${MODULE}-$(date +%Y%m%d-%H%M%S).log" echo "[*] running verifier..." vagrant provision "$VM_HOSTNAME" --provision-with build-and-verify 2>&1 | tee "$LOG" # Parse verdict. VERDICT=$(grep -E "^VERDICT: " "$LOG" | tail -1 | awk '{print $2}') [[ -z "$VERDICT" ]] && VERDICT="?" # Compare. if [[ "$VERDICT" == "$EXPECT" ]]; then STATUS=match else STATUS=MISMATCH fi # Verification record (JSON). NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ) HOST_KVER=$(vagrant ssh "$VM_HOSTNAME" -c "uname -r" 2>/dev/null | tr -d '\r') HOST_DISTRO=$(vagrant ssh "$VM_HOSTNAME" -c \ "(. /etc/os-release && echo \"\$PRETTY_NAME\")" 2>/dev/null | tr -d '\r') echo echo "════════════════════════════════════════════════════" echo " Verification record" echo "════════════════════════════════════════════════════" cat <