Files
linux-scripts/vpn-smoke-tests.sh
chiefgeek a1a17e81a1 Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist,
same-site referer bypass, Blackbox-Exporter allowed bot, and all
new exporters, cheat sheets, and automation scripts.
2026-05-25 03:31:08 +02:00

588 lines
25 KiB
Bash
Executable File

#!/usr/bin/env bash
#####################################################################################
#### vpn-smoke-tests.sh — Verify VPN tunnels are healthy ####
#### Checks WireGuard and OpenVPN interface, handshake, peers, DNS, routes, MTU. ####
#### ####
#### Author: Phil Connor ####
#### Contact: contact@mylinux.work ####
#### License: MIT ####
#### Version: 1.0 ####
#### ####
#### Usage: ./vpn-smoke-tests.sh ####
#### VPN_TYPE=wireguard VPN_INTERFACE=wg0 ./vpn-smoke-tests.sh ####
#### ####
#### See --help for all options. ####
#####################################################################################
set -euo pipefail
# ── Defaults ──────────────────────────────────────────────────────────
VPN_TYPE="${VPN_TYPE:-auto}"
VPN_INTERFACE="${VPN_INTERFACE:-}"
HANDSHAKE_MAX_SEC="${HANDSHAKE_MAX_SEC:-180}"
PING_TARGET="${PING_TARGET:-}"
DNS_TEST_DOMAIN="${DNS_TEST_DOMAIN:-}"
VPN_DNS_SERVER="${VPN_DNS_SERVER:-}"
EXPECTED_ROUTES="${EXPECTED_ROUTES:-}"
MGMT_SOCKET="${MGMT_SOCKET:-}"
EXPECTED_MTU="${EXPECTED_MTU:-}"
VPN_ENDPOINT="${VPN_ENDPOINT:-}"
SKIP_HANDSHAKE="${SKIP_HANDSHAKE:-false}"
SKIP_DNS="${SKIP_DNS:-false}"
SKIP_PING="${SKIP_PING:-false}"
OUTPUT_FORMAT="${OUTPUT_FORMAT:-text}"
COLOR="${COLOR:-auto}"
VERBOSE="${VERBOSE:-false}"
# ── State ─────────────────────────────────────────────────────────────
PASS=0
FAIL=0
SKIP=0
TOTAL=0
RESULTS=()
START_TIME=""
# ── Colors ────────────────────────────────────────────────────────────
setup_colors() {
if [[ "$COLOR" == "never" ]]; then
RED="" GREEN="" YELLOW="" BLUE="" BOLD="" RESET=""
return
fi
if [[ "$COLOR" == "always" ]] || [[ -t 1 ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
RESET='\033[0m'
else
RED="" GREEN="" YELLOW="" BLUE="" BOLD="" RESET=""
fi
}
# ── Logging ───────────────────────────────────────────────────────────
log() { echo -e "${BLUE}[INFO]${RESET} $*"; }
warn() { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; }
err() { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${BLUE}[DEBUG]${RESET} $*"; fi; }
# ── Test Result Recording ─────────────────────────────────────────────
record_pass() {
local name="$1" detail="${2:-}"
((PASS++)) || true; ((TOTAL++)) || true
RESULTS+=("PASS|${name}|${detail}")
if [[ "$OUTPUT_FORMAT" == "tap" ]]; then echo "ok ${TOTAL} - ${name}${detail:+ (${detail})}"
else echo -e " ${GREEN}${RESET} ${name}${detail:+ — ${detail}}"; fi
}
record_fail() {
local name="$1" detail="${2:-}"
((FAIL++)) || true; ((TOTAL++)) || true
RESULTS+=("FAIL|${name}|${detail}")
if [[ "$OUTPUT_FORMAT" == "tap" ]]; then
echo "not ok ${TOTAL} - ${name}"
[[ -n "$detail" ]] && echo " # ${detail}"
else echo -e " ${RED}${RESET} ${name}${detail:+ — ${detail}}"; fi
}
record_skip() {
local name="$1" reason="${2:-}"
((SKIP++)) || true; ((TOTAL++)) || true
RESULTS+=("SKIP|${name}|${reason}")
if [[ "$OUTPUT_FORMAT" == "tap" ]]; then echo "ok ${TOTAL} - ${name} # SKIP ${reason}"
else echo -e " ${YELLOW}${RESET} ${name}${reason:+ — ${reason}}"; fi
}
# ── Helpers ───────────────────────────────────────────────────────────
has_cmd() { command -v "$1" >/dev/null 2>&1; }
section() {
if [[ "$OUTPUT_FORMAT" != "tap" ]]; then echo ""; echo -e "${BOLD}$1${RESET}"; fi
}
# ── Cleanup ───────────────────────────────────────────────────────────
# shellcheck disable=SC2317
cleanup() {
verbose "Cleanup complete."
}
trap cleanup EXIT
# ── VPN Type Detection ───────────────────────────────────────────────
detect_vpn_type() {
if [[ "$VPN_TYPE" != "auto" ]]; then
verbose "VPN type set to ${VPN_TYPE}"
return
fi
if has_cmd wg && wg show interfaces 2>/dev/null | grep -q .; then
VPN_TYPE="wireguard"
verbose "Auto-detected WireGuard"
elif pgrep -x openvpn >/dev/null 2>&1; then
VPN_TYPE="openvpn"
verbose "Auto-detected OpenVPN"
elif ip link show wg0 >/dev/null 2>&1; then
VPN_TYPE="wireguard"
verbose "Auto-detected WireGuard (interface wg0 exists)"
elif ip link show tun0 >/dev/null 2>&1; then
VPN_TYPE="openvpn"
verbose "Auto-detected OpenVPN (interface tun0 exists)"
else
err "Could not auto-detect VPN type. Set VPN_TYPE=wireguard or VPN_TYPE=openvpn."
exit 1
fi
}
set_defaults() {
if [[ -z "$VPN_INTERFACE" ]]; then
case "$VPN_TYPE" in
wireguard) VPN_INTERFACE="wg0" ;;
openvpn) VPN_INTERFACE="tun0" ;;
esac
verbose "Default interface: ${VPN_INTERFACE}"
fi
}
# ══════════════════════════════════════════════════════════════════════
# TEST FUNCTIONS
# ══════════════════════════════════════════════════════════════════════
# ── 1. VPN type detection ────────────────────────────────────────────
test_vpn_type() {
case "$VPN_TYPE" in
wireguard|openvpn) record_pass "VPN type detection" "${VPN_TYPE}" ;;
*) record_fail "VPN type detection" "unknown type: ${VPN_TYPE}" ;;
esac
}
# ── 2. Interface up ──────────────────────────────────────────────────
test_interface_up() {
if ! ip link show "$VPN_INTERFACE" >/dev/null 2>&1; then
record_fail "Interface up" "${VPN_INTERFACE} does not exist"
return
fi
local state
state=$(ip -o link show "$VPN_INTERFACE" 2>/dev/null | grep -oP 'state \K\S+') || true
if [[ "$state" == "UP" || "$state" == "UNKNOWN" ]]; then
record_pass "Interface up" "${VPN_INTERFACE} state=${state}"
else
record_fail "Interface up" "${VPN_INTERFACE} state=${state:-DOWN}"
fi
}
# ── 3. WireGuard handshake ───────────────────────────────────────────
test_wg_handshake() {
if [[ "$VPN_TYPE" != "wireguard" ]]; then record_skip "WireGuard handshake" "not WireGuard"; return; fi
if [[ "$SKIP_HANDSHAKE" == "true" ]]; then record_skip "WireGuard handshake" "SKIP_HANDSHAKE=true"; return; fi
if ! has_cmd wg; then record_skip "WireGuard handshake" "wg not installed"; return; fi
local handshakes oldest_age=0 peer_count=0 now
now=$(date +%s)
handshakes=$(wg show "$VPN_INTERFACE" latest-handshakes 2>/dev/null) || true
if [[ -z "$handshakes" ]]; then
record_fail "WireGuard handshake" "no handshake data from wg show"
return
fi
while IFS=$'\t' read -r _peer ts; do
((peer_count++)) || true
if [[ "$ts" == "0" ]]; then
record_fail "WireGuard handshake" "peer has never completed a handshake"
return
fi
local age=$(( now - ts ))
if [[ $age -gt $oldest_age ]]; then oldest_age=$age; fi
done <<< "$handshakes"
if [[ $peer_count -eq 0 ]]; then
record_fail "WireGuard handshake" "no peers configured"
return
fi
if [[ $oldest_age -le $HANDSHAKE_MAX_SEC ]]; then
record_pass "WireGuard handshake" "${oldest_age}s ago (<= ${HANDSHAKE_MAX_SEC}s), ${peer_count} peer(s)"
else
record_fail "WireGuard handshake" "${oldest_age}s ago (> ${HANDSHAKE_MAX_SEC}s)"
fi
}
# ── 4. WireGuard peer reachable ──────────────────────────────────────
test_wg_peer_reachable() {
if [[ "$VPN_TYPE" != "wireguard" ]]; then record_skip "WireGuard peer reachable" "not WireGuard"; return; fi
if [[ "$SKIP_PING" == "true" ]]; then record_skip "WireGuard peer reachable" "SKIP_PING=true"; return; fi
if ! has_cmd wg; then record_skip "WireGuard peer reachable" "wg not installed"; return; fi
local endpoints peer_count=0 reachable=0
endpoints=$(wg show "$VPN_INTERFACE" endpoints 2>/dev/null) || true
if [[ -z "$endpoints" ]]; then
record_skip "WireGuard peer reachable" "no endpoint data"
return
fi
while IFS=$'\t' read -r _peer endpoint; do
[[ "$endpoint" == "(none)" || -z "$endpoint" ]] && continue
((peer_count++)) || true
local host="${endpoint%:*}"
# Strip brackets from IPv6
host="${host#[}"
host="${host%]}"
if ping -c 1 -W 3 "$host" >/dev/null 2>&1; then
((reachable++)) || true
fi
done <<< "$endpoints"
if [[ $peer_count -eq 0 ]]; then
record_skip "WireGuard peer reachable" "no endpoints configured"
elif [[ $reachable -eq $peer_count ]]; then
record_pass "WireGuard peer reachable" "${reachable}/${peer_count} endpoints"
else
record_fail "WireGuard peer reachable" "${reachable}/${peer_count} endpoints reachable"
fi
}
# ── 5. WireGuard transfer ────────────────────────────────────────────
test_wg_transfer() {
if [[ "$VPN_TYPE" != "wireguard" ]]; then record_skip "WireGuard transfer" "not WireGuard"; return; fi
if ! has_cmd wg; then record_skip "WireGuard transfer" "wg not installed"; return; fi
local transfer total_rx=0 total_tx=0
transfer=$(wg show "$VPN_INTERFACE" transfer 2>/dev/null) || true
if [[ -z "$transfer" ]]; then
record_fail "WireGuard transfer" "no transfer data"
return
fi
while IFS=$'\t' read -r _peer rx tx; do
total_rx=$((total_rx + rx))
total_tx=$((total_tx + tx))
done <<< "$transfer"
if [[ $total_rx -gt 0 && $total_tx -gt 0 ]]; then
local rx_h tx_h
rx_h=$(numfmt --to=iec "$total_rx" 2>/dev/null) || rx_h="${total_rx}B"
tx_h=$(numfmt --to=iec "$total_tx" 2>/dev/null) || tx_h="${total_tx}B"
record_pass "WireGuard transfer" "rx=${rx_h} tx=${tx_h}"
elif [[ $total_rx -gt 0 || $total_tx -gt 0 ]]; then
record_fail "WireGuard transfer" "one-way traffic only (rx=${total_rx} tx=${total_tx})"
else
record_fail "WireGuard transfer" "no traffic (rx=0 tx=0)"
fi
}
# ── 6. OpenVPN process ───────────────────────────────────────────────
test_ovpn_process() {
if [[ "$VPN_TYPE" != "openvpn" ]]; then record_skip "OpenVPN process" "not OpenVPN"; return; fi
local pid
pid=$(pgrep -x openvpn 2>/dev/null | head -1) || true
if [[ -n "$pid" ]]; then
record_pass "OpenVPN process" "PID ${pid}"
else
record_fail "OpenVPN process" "openvpn not running"
fi
}
# ── 7. OpenVPN management ────────────────────────────────────────────
test_ovpn_management() {
if [[ "$VPN_TYPE" != "openvpn" ]]; then record_skip "OpenVPN management" "not OpenVPN"; return; fi
if [[ -z "$MGMT_SOCKET" ]]; then record_skip "OpenVPN management" "MGMT_SOCKET not set"; return; fi
if [[ -S "$MGMT_SOCKET" ]]; then
local status_output
status_output=$(echo "status" | socat - "UNIX-CONNECT:${MGMT_SOCKET}" 2>/dev/null) || true
if [[ -n "$status_output" ]] && echo "$status_output" | grep -qi "client\|connected\|bytes"; then
record_pass "OpenVPN management" "socket responding"
elif [[ -n "$status_output" ]]; then
record_pass "OpenVPN management" "socket responding"
else
record_fail "OpenVPN management" "socket not responding"
fi
elif [[ -e "$MGMT_SOCKET" ]]; then
record_fail "OpenVPN management" "${MGMT_SOCKET} exists but is not a socket"
else
record_fail "OpenVPN management" "${MGMT_SOCKET} not found"
fi
}
# ── 8. Tunnel IP assigned ────────────────────────────────────────────
test_tunnel_ip() {
local ip_addr
ip_addr=$(ip -o -4 addr show "$VPN_INTERFACE" 2>/dev/null | awk '{print $4}' | head -1) || true
if [[ -z "$ip_addr" ]]; then
# Try IPv6
ip_addr=$(ip -o -6 addr show "$VPN_INTERFACE" 2>/dev/null | grep -v "fe80" | awk '{print $4}' | head -1) || true
fi
if [[ -n "$ip_addr" ]]; then
record_pass "Tunnel IP assigned" "${VPN_INTERFACE} ${ip_addr}"
else
record_fail "Tunnel IP assigned" "${VPN_INTERFACE} has no IP address"
fi
}
# ── 9. DNS over VPN ──────────────────────────────────────────────────
test_dns_over_vpn() {
if [[ "$SKIP_DNS" == "true" ]]; then record_skip "DNS over VPN" "SKIP_DNS=true"; return; fi
if [[ -z "$DNS_TEST_DOMAIN" ]]; then record_skip "DNS over VPN" "DNS_TEST_DOMAIN not set"; return; fi
if [[ -z "$VPN_DNS_SERVER" ]]; then record_skip "DNS over VPN" "VPN_DNS_SERVER not set"; return; fi
local output
if has_cmd dig; then
output=$(dig +short +time=5 +tries=1 "@${VPN_DNS_SERVER}" "${DNS_TEST_DOMAIN}" A 2>/dev/null) || true
elif has_cmd nslookup; then
output=$(nslookup "${DNS_TEST_DOMAIN}" "${VPN_DNS_SERVER}" 2>/dev/null | grep -i "address" | tail -1) || true
elif has_cmd drill; then
output=$(drill "@${VPN_DNS_SERVER}" "${DNS_TEST_DOMAIN}" A 2>/dev/null | grep -A1 "ANSWER SECTION" | tail -1) || true
else
record_skip "DNS over VPN" "no DNS tool available (dig, nslookup, drill)"
return
fi
if [[ -n "$output" ]] && ! echo "$output" | grep -qi "timed out\|SERVFAIL\|connection refused"; then
record_pass "DNS over VPN" "${DNS_TEST_DOMAIN} via ${VPN_DNS_SERVER}"
else
record_fail "DNS over VPN" "failed to resolve ${DNS_TEST_DOMAIN} via ${VPN_DNS_SERVER}"
fi
}
# ── 10. Route check ─────────────────────────────────────────────────
test_route_check() {
if [[ -z "$EXPECTED_ROUTES" ]]; then record_skip "Route check" "EXPECTED_ROUTES not set"; return; fi
local IFS=',' missing=0 checked=0
for route in $EXPECTED_ROUTES; do
route=$(echo "$route" | xargs)
[[ -z "$route" ]] && continue
((checked++)) || true
if ip route show "$route" 2>/dev/null | grep -q "$VPN_INTERFACE"; then
verbose "Route ${route} via ${VPN_INTERFACE} — OK"
else
verbose "Route ${route} via ${VPN_INTERFACE} — MISSING"
((missing++)) || true
fi
done
if [[ $checked -eq 0 ]]; then
record_skip "Route check" "no routes to check"
elif [[ $missing -eq 0 ]]; then
record_pass "Route check" "${checked} route(s) via ${VPN_INTERFACE}"
else
record_fail "Route check" "${missing}/${checked} route(s) missing from ${VPN_INTERFACE}"
fi
}
# ── 11. Peer connectivity ───────────────────────────────────────────
test_peer_connectivity() {
if [[ "$SKIP_PING" == "true" ]]; then record_skip "Peer connectivity" "SKIP_PING=true"; return; fi
if [[ -z "$PING_TARGET" ]]; then record_skip "Peer connectivity" "PING_TARGET not set"; return; fi
if ping -c 3 -W 5 -I "$VPN_INTERFACE" "$PING_TARGET" >/dev/null 2>&1; then
record_pass "Peer connectivity" "ping ${PING_TARGET} via ${VPN_INTERFACE}"
else
record_fail "Peer connectivity" "cannot reach ${PING_TARGET} via ${VPN_INTERFACE}"
fi
}
# ── 12. MTU check ────────────────────────────────────────────────────
test_mtu_check() {
if [[ -z "$EXPECTED_MTU" ]]; then record_skip "MTU check" "EXPECTED_MTU not set"; return; fi
local actual_mtu
actual_mtu=$(ip -o link show "$VPN_INTERFACE" 2>/dev/null | grep -oP 'mtu \K[0-9]+') || true
if [[ -z "$actual_mtu" ]]; then
record_fail "MTU check" "could not read MTU for ${VPN_INTERFACE}"
elif [[ "$actual_mtu" == "$EXPECTED_MTU" ]]; then
record_pass "MTU check" "${VPN_INTERFACE} MTU=${actual_mtu}"
else
record_fail "MTU check" "${VPN_INTERFACE} MTU=${actual_mtu} (expected ${EXPECTED_MTU})"
fi
}
# ── 13. Endpoint reachable ───────────────────────────────────────────
test_endpoint_reachable() {
if [[ -z "$VPN_ENDPOINT" ]]; then record_skip "Endpoint reachable" "VPN_ENDPOINT not set"; return; fi
local host port
# Handle host:port format
if [[ "$VPN_ENDPOINT" =~ ^\[.*\]:[0-9]+$ ]]; then
# IPv6 [addr]:port
host="${VPN_ENDPOINT%:*}"
host="${host#[}"
host="${host%]}"
port="${VPN_ENDPOINT##*:}"
elif [[ "$VPN_ENDPOINT" =~ ^[^:]+:[0-9]+$ ]]; then
host="${VPN_ENDPOINT%:*}"
port="${VPN_ENDPOINT##*:}"
else
host="$VPN_ENDPOINT"
port=""
fi
if [[ -n "$port" ]]; then
# Test TCP/UDP reachability
if has_cmd nc; then
if nc -z -w 5 "$host" "$port" >/dev/null 2>&1; then
record_pass "Endpoint reachable" "${VPN_ENDPOINT}"
return
fi
fi
# Fall back to ping if nc fails or unavailable
if ping -c 1 -W 5 "$host" >/dev/null 2>&1; then
record_pass "Endpoint reachable" "${host} (ping OK, port ${port} not tested)"
else
record_fail "Endpoint reachable" "${VPN_ENDPOINT} unreachable"
fi
else
if ping -c 1 -W 5 "$host" >/dev/null 2>&1; then
record_pass "Endpoint reachable" "${host}"
else
record_fail "Endpoint reachable" "${host} unreachable"
fi
fi
}
# ══════════════════════════════════════════════════════════════════════
# OUTPUT
# ══════════════════════════════════════════════════════════════════════
print_tap_header() {
echo "TAP version 13"
}
print_tap_footer() {
echo "1..${TOTAL}"
echo "# pass ${PASS}"
echo "# fail ${FAIL}"
echo "# skip ${SKIP}"
}
print_summary() {
local end_time; end_time=$(date +%s)
local duration=$(( end_time - START_TIME ))
echo ""
echo -e "${BOLD}────────────────────────────────────────${RESET}"
echo -e "${BOLD}Summary${RESET} VPN Smoke Tests ${VPN_TYPE}/${VPN_INTERFACE}"
echo -e " ${GREEN}${PASS} passed${RESET} ${RED}${FAIL} failed${RESET} ${YELLOW}${SKIP} skipped${RESET} (${duration}s)"
echo -e "${BOLD}────────────────────────────────────────${RESET}"
if [[ $FAIL -eq 0 ]]; then echo -e "${GREEN}${BOLD}All tests passed.${RESET}"
else echo -e "${RED}${BOLD}${FAIL} test(s) failed.${RESET}"; fi
}
# ══════════════════════════════════════════════════════════════════════
# MAIN
# ══════════════════════════════════════════════════════════════════════
usage() {
cat <<EOF
Usage: $(basename "$0") [OPTIONS]
Smoke-test VPN tunnels. Supports WireGuard and OpenVPN.
Environment variables (all optional):
VPN_TYPE VPN backend: auto, wireguard, openvpn (default: auto)
VPN_INTERFACE Network interface (default: wg0 or tun0)
HANDSHAKE_MAX_SEC Max seconds since WireGuard handshake (default: 180)
PING_TARGET IP to ping through tunnel (skip if empty)
DNS_TEST_DOMAIN Domain to resolve via VPN DNS (skip if empty)
VPN_DNS_SERVER DNS server for VPN resolution (skip if empty)
EXPECTED_ROUTES Comma-separated CIDRs to verify (skip if empty)
MGMT_SOCKET OpenVPN management socket path (skip if empty)
EXPECTED_MTU Expected MTU value (skip if empty)
VPN_ENDPOINT VPN server IP:port to check (skip if empty)
SKIP_HANDSHAKE Skip WireGuard handshake check (default: false)
SKIP_DNS Skip DNS-over-VPN test (default: false)
SKIP_PING Skip ping-based tests (default: false)
OUTPUT_FORMAT Output: text (default), tap
COLOR Color: auto (default), always, never
VERBOSE Show debug output (default: false)
Options:
--type TYPE Set VPN_TYPE
--interface IFACE Set VPN_INTERFACE
--format FORMAT Output format: text (default), tap
--skip-handshake Skip WireGuard handshake check
--skip-dns Skip DNS-over-VPN test
--skip-ping Skip ping-based tests
--verbose Show debug output
--no-color Disable colored output
--help Show this help
Examples:
./$(basename "$0")
VPN_TYPE=wireguard PING_TARGET=10.0.0.1 ./$(basename "$0")
VPN_TYPE=openvpn MGMT_SOCKET=/run/openvpn/mgmt.sock ./$(basename "$0")
OUTPUT_FORMAT=tap ./$(basename "$0") --skip-ping
EOF
}
main() {
while [[ $# -gt 0 ]]; do
case "$1" in
--type) VPN_TYPE="$2"; shift ;;
--interface) VPN_INTERFACE="$2"; shift ;;
--format) OUTPUT_FORMAT="$2"; shift ;;
--skip-handshake) SKIP_HANDSHAKE=true ;;
--skip-dns) SKIP_DNS=true ;;
--skip-ping) SKIP_PING=true ;;
--verbose) VERBOSE=true ;;
--no-color) COLOR=never ;;
--help|-h) usage; exit 0 ;;
*) err "Unknown option: $1"; usage; exit 1 ;;
esac
shift
done
setup_colors
detect_vpn_type
set_defaults
START_TIME=$(date +%s)
if [[ "$OUTPUT_FORMAT" == "tap" ]]; then
print_tap_header
else
echo ""
echo -e "${BOLD}VPN Smoke Tests${RESET}"
echo -e "Type: ${VPN_TYPE} Interface: ${VPN_INTERFACE}"
echo -e "Time: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
fi
section "Detection"
test_vpn_type
section "Interface"
test_interface_up
test_tunnel_ip
test_mtu_check
section "WireGuard"
test_wg_handshake
test_wg_peer_reachable
test_wg_transfer
section "OpenVPN"
test_ovpn_process
test_ovpn_management
section "Connectivity"
test_endpoint_reachable
test_peer_connectivity
test_dns_over_vpn
test_route_check
# ── Results ──
if [[ "$OUTPUT_FORMAT" == "tap" ]]; then
print_tap_footer
else
print_summary
fi
[[ $FAIL -eq 0 ]] && exit 0 || exit 1
}
main "$@"