Files
nx9-wg/scripts/test-cli-comprehensive.sh

1334 lines
28 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# ============================================================================
# nx9-wg — Comprehensive CLI Verification Script
# ============================================================================
#
# PURPOSE
# -------
# Comprehensive operator-facing verification of the nx9-wg CLI.
#
# This script checks:
#
# 01. Binary/version
# 02. Top-level CLI commands
# 03. Subcommand help
# 04. Administrator initialization
# 05. System
# 06. Administrator/authentication
# 07. Client profiles / MTU
# 08. Networks
# 09. WireGuard interfaces
# 10. Peers
# 11. Routes
# 12. Firewall
# 13. NAT
# 14. Forwarding
# 15. Reconciliation
# 16. Backup
# 17. Audit
# 18. Live kernel state
# 19. Diagnostics
# 20. Output format matrix
# 21. Global CLI options
# 22. NX9_WG_* environment namespace
# 23. Explicit database paths
# 24. Data-directory resolution
# 25. Source-level architectural safety
# 26. Final summary
#
# SAFETY
# ------
# Default mode is SAFE.
#
# It uses:
#
# /tmp/nx9-wg-cli-test-XXXXXX
#
# and never modifies the host WireGuard/network configuration.
#
# LIVE=1 enables read-only live Linux inspection commands.
#
# Example:
#
# ./scripts/test-cli-comprehensive.sh
#
# LIVE=1 ./scripts/test-cli-comprehensive.sh
#
# BIN=./target/release/nx9-wg ./scripts/test-cli-comprehensive.sh
#
# IMPORTANT
# ---------
# Do NOT run this with:
#
# source scripts/test-cli-comprehensive.sh
#
# or:
#
# . scripts/test-cli-comprehensive.sh
#
# Run it as a program:
#
# bash scripts/test-cli-comprehensive.sh
#
# or:
#
# ./scripts/test-cli-comprehensive.sh
#
# ============================================================================
set -uo pipefail
###############################################################################
# Configuration
###############################################################################
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
LIVE="${LIVE:-0}"
TIMEOUT="${TIMEOUT:-20}"
BIN="${BIN:-}"
if [[ -z "$BIN" ]]; then
if [[ -x "$ROOT/target/debug/nx9-wg" ]]; then
BIN="$ROOT/target/debug/nx9-wg"
elif [[ -x "$ROOT/target/release/nx9-wg" ]]; then
BIN="$ROOT/target/release/nx9-wg"
else
echo
echo "ERROR: nx9-wg binary not found."
echo
echo "Build with:"
echo " cargo build"
echo
echo "or:"
echo " cargo build --release"
echo
echo "Then run this script again."
echo
exit 1
fi
fi
if [[ ! -x "$BIN" ]]; then
echo
echo "ERROR: binary is not executable:"
echo " $BIN"
echo
exit 1
fi
###############################################################################
# Temporary isolated test environment
###############################################################################
TEST_ROOT="$(mktemp -d /tmp/nx9-wg-cli-test.XXXXXX)"
DATA_DIR="$TEST_ROOT/data"
DB="$DATA_DIR/nx9-wg.db"
PASSWORD_FILE="$TEST_ROOT/admin-password"
mkdir -p "$DATA_DIR"
###############################################################################
# Counters
###############################################################################
PASS=0
FAIL=0
SKIP=0
TOTAL=0
###############################################################################
# State
###############################################################################
KEEP_TEST_DATA=0
###############################################################################
# Formatting
###############################################################################
BOLD=""
DIM=""
GREEN=""
RED=""
YELLOW=""
CYAN=""
RESET=""
if [[ -t 1 ]]; then
BOLD=$'\033[1m'
DIM=$'\033[2m'
GREEN=$'\033[32m'
RED=$'\033[31m'
YELLOW=$'\033[33m'
CYAN=$'\033[36m'
RESET=$'\033[0m'
fi
###############################################################################
# Cleanup
###############################################################################
cleanup() {
local rc=$?
echo
echo "============================================================================"
echo " nx9-wg CLI verification finished"
echo "============================================================================"
echo
if [[ "$FAIL" -eq 0 && "$rc" -eq 0 ]]; then
echo "${GREEN}RESULT: PASS${RESET}"
echo
echo " PASS : $PASS"
echo " FAIL : $FAIL"
echo " SKIP : $SKIP"
echo " TOTAL: $TOTAL"
echo
echo "Temporary test data:"
echo " $TEST_ROOT"
echo
echo "Removing temporary test data..."
rm -rf "$TEST_ROOT"
else
echo "${RED}RESULT: FAIL${RESET}"
echo
echo " PASS : $PASS"
echo " FAIL : $FAIL"
echo " SKIP : $SKIP"
echo " TOTAL: $TOTAL"
echo
KEEP_TEST_DATA=1
echo "${YELLOW}Temporary test data PRESERVED for investigation:${RESET}"
echo " $TEST_ROOT"
echo
echo "Database:"
echo " $DB"
echo
echo "Password file:"
echo " $PASSWORD_FILE"
echo
echo "To inspect:"
echo " ls -la \"$TEST_ROOT\""
echo " ls -la \"$DATA_DIR\""
echo
fi
echo
return "$rc"
}
trap cleanup EXIT
###############################################################################
# Reporting functions
###############################################################################
section() {
echo
echo "============================================================================"
echo " $1"
echo "============================================================================"
}
info() {
echo " ${CYAN}[INFO]${RESET} $1"
}
pass() {
PASS=$((PASS + 1))
TOTAL=$((TOTAL + 1))
echo " ${GREEN}[PASS]${RESET} $1"
}
fail() {
FAIL=$((FAIL + 1))
TOTAL=$((TOTAL + 1))
echo " ${RED}[FAIL]${RESET} $1"
}
skip() {
SKIP=$((SKIP + 1))
TOTAL=$((TOTAL + 1))
echo " ${YELLOW}[SKIP]${RESET} $1"
}
###############################################################################
# Generic command execution
###############################################################################
run_test() {
local description="$1"
shift
echo
echo " ${BOLD}>>> $description${RESET}"
echo " $BIN $*"
timeout "$TIMEOUT" "$BIN" "$@" >/tmp/nx9-wg-cli-command.out 2>/tmp/nx9-wg-cli-command.err
local rc=$?
if [[ "$rc" -eq 0 ]]; then
pass "$description"
cat /tmp/nx9-wg-cli-command.out
return 0
fi
fail "$description (exit=$rc)"
if [[ -s /tmp/nx9-wg-cli-command.out ]]; then
echo " --- stdout ---"
sed 's/^/ /' /tmp/nx9-wg-cli-command.out
fi
if [[ -s /tmp/nx9-wg-cli-command.err ]]; then
echo " --- stderr ---"
sed 's/^/ /' /tmp/nx9-wg-cli-command.err
fi
return 0
}
run_quiet() {
local description="$1"
shift
timeout "$TIMEOUT" "$BIN" "$@" >/dev/null 2>&1
local rc=$?
if [[ "$rc" -eq 0 ]]; then
pass "$description"
else
fail "$description (exit=$rc)"
fi
return 0
}
###############################################################################
# Safe assertion helpers
###############################################################################
assert_file() {
local description="$1"
local file="$2"
if [[ -f "$file" ]]; then
pass "$description"
else
fail "$description"
fi
}
assert_nonempty_file() {
local description="$1"
local file="$2"
if [[ -s "$file" ]]; then
pass "$description"
else
fail "$description"
fi
}
###############################################################################
# CLI context
#
# Every runtime database operation in this script is isolated.
###############################################################################
CLI=(
--data-dir "$DATA_DIR"
--database "$DB"
)
###############################################################################
# Wrapper
###############################################################################
nx() {
"$BIN" "${CLI[@]}" "$@"
}
###############################################################################
# 01 — Binary
###############################################################################
section "01 — Binary and Version"
run_test \
"Version" \
version
run_test \
"Version JSON" \
--format json \
version
###############################################################################
# 02 — Top-level help
###############################################################################
section "02 — Top-Level CLI Surface"
run_test \
"Top-level --help" \
--help
###############################################################################
# 03 — Top-level command discovery
###############################################################################
section "03 — Top-Level Command Help Audit"
TOP_LEVEL_COMMANDS=(
serve
init
system
admin
interface
peer
network
route
firewall
nat
forwarding
reconcile
backup
audit
live
diagnostics
profile
version
)
for cmd in "${TOP_LEVEL_COMMANDS[@]}"; do
run_quiet \
"$cmd --help" \
"$cmd" \
--help
done
###############################################################################
# 04 — Subcommand help
###############################################################################
section "04 — Feature/Subcommand Help Audit"
declare -A SUBCOMMANDS
SUBCOMMANDS[system]="info health settings"
SUBCOMMANDS[admin]="status create password sessions tokens"
SUBCOMMANDS[interface]="list show create update enable disable delete status reconcile"
SUBCOMMANDS[peer]="list show create update enable disable revoke delete status config qr expire lifecycle"
SUBCOMMANDS[network]="list show create update delete available allocations"
SUBCOMMANDS[route]="list show add update delete status sync"
SUBCOMMANDS[firewall]="list show add update delete enable disable status sync"
SUBCOMMANDS[nat]="status enable disable list sync"
SUBCOMMANDS[forwarding]="status enable disable sync"
SUBCOMMANDS[reconcile]="status plan apply verify"
SUBCOMMANDS[backup]="create list show verify restore delete"
SUBCOMMANDS[audit]="list show"
SUBCOMMANDS[live]="interface peer routes firewall forwarding nat"
SUBCOMMANDS[profile]="list show validate resolve"
for cmd in "${!SUBCOMMANDS[@]}"; do
read -ra subs <<< "${SUBCOMMANDS[$cmd]}"
for sub in "${subs[@]}"; do
run_quiet \
"$cmd $sub --help" \
"$cmd" \
"$sub" \
--help
done
done
###############################################################################
# 05 — Administrator bootstrap
###############################################################################
section "05 — Administrator Bootstrap"
rm -rf "$DATA_DIR"
mkdir -p "$DATA_DIR"
run_test \
"Initialize administrator with generated password" \
"${CLI[@]}" \
init \
--generate-password \
--write-password-file "$PASSWORD_FILE"
assert_nonempty_file \
"Generated administrator password file exists" \
"$PASSWORD_FILE"
###############################################################################
# 06 — System
###############################################################################
section "06 — System"
run_test \
"System info" \
"${CLI[@]}" \
system \
info
run_test \
"System health" \
"${CLI[@]}" \
system \
health
run_test \
"System info JSON" \
"${CLI[@]}" \
--format json \
system \
info
run_test \
"System health JSON" \
"${CLI[@]}" \
--format json \
system \
health
run_test \
"System info YAML" \
"${CLI[@]}" \
--format yaml \
system \
info
run_test \
"System health CSV" \
"${CLI[@]}" \
--format csv \
system \
health
###############################################################################
# 07 — Administrator
###############################################################################
section "07 — Administrator / Authentication"
run_test \
"Admin status" \
"${CLI[@]}" \
admin \
status
run_test \
"Admin status JSON" \
"${CLI[@]}" \
--format json \
admin \
status
###############################################################################
# 08 — Client profiles / MTU
###############################################################################
section "08 — Client Environment / MTU Profiles"
run_test \
"Profile list" \
"${CLI[@]}" \
profile \
list
run_test \
"Profile list JSON" \
"${CLI[@]}" \
--format json \
profile \
list
run_test \
"Default mobile profile" \
"${CLI[@]}" \
profile \
show \
default-mobile
run_test \
"Default CGNAT profile" \
"${CLI[@]}" \
profile \
show \
default-cgnat
run_test \
"Default Wi-Fi profile" \
"${CLI[@]}" \
profile \
show \
default-wifi
run_test \
"Default Web profile" \
"${CLI[@]}" \
profile \
show \
default-web
run_test \
"Default Wired profile" \
"${CLI[@]}" \
profile \
show \
default-wired
run_test \
"Validate MTU 1280" \
"${CLI[@]}" \
profile \
validate \
1280
run_test \
"Validate MTU 1360" \
"${CLI[@]}" \
profile \
validate \
1360
run_test \
"Validate MTU 1420" \
"${CLI[@]}" \
profile \
validate \
1420
run_test \
"Validate MTU 1500" \
"${CLI[@]}" \
profile \
validate \
1500
run_test \
"Resolve Android Mobile CGNAT" \
"${CLI[@]}" \
--format json \
profile \
resolve \
--connection mobile \
--device android \
--nat cgnat
run_test \
"Resolve iOS Mobile CGNAT" \
"${CLI[@]}" \
--format json \
profile \
resolve \
--connection mobile \
--device ios \
--nat cgnat
run_test \
"Resolve Linux Wi-Fi Direct" \
"${CLI[@]}" \
--format json \
profile \
resolve \
--connection wifi \
--device linux \
--nat direct
run_test \
"Resolve Windows Wired" \
"${CLI[@]}" \
--format json \
profile \
resolve \
--connection wired \
--device windows \
--nat direct
run_test \
"Resolve Jio Mobile CGNAT" \
"${CLI[@]}" \
--format json \
profile \
resolve \
--provider jio \
--connection mobile \
--nat cgnat
run_test \
"Resolve T-Mobile Mobile CGNAT" \
"${CLI[@]}" \
--format json \
profile \
resolve \
--provider tmobile \
--connection mobile \
--nat cgnat
run_test \
"Resolve Verizon Mobile CGNAT" \
"${CLI[@]}" \
--format json \
profile \
resolve \
--provider verizon \
--connection mobile \
--nat cgnat
run_test \
"Resolve Starlink CGNAT" \
"${CLI[@]}" \
--format json \
profile \
resolve \
--provider starlink \
--connection other \
--nat cgnat
run_test \
"Resolve manual MTU override" \
"${CLI[@]}" \
--format json \
profile \
resolve \
--connection mobile \
--device android \
--nat cgnat \
--mtu 1300
run_test \
"Resolve explicit profile" \
"${CLI[@]}" \
--format json \
profile \
resolve \
--profile android-mobile
###############################################################################
# 09 — Network
###############################################################################
section "09 — Network Management"
run_test \
"Network list" \
"${CLI[@]}" \
network \
list
run_test \
"Network list JSON" \
"${CLI[@]}" \
--format json \
network \
list
###############################################################################
# 10 — Interface
###############################################################################
section "10 — WireGuard Interface Management"
run_test \
"Interface list" \
"${CLI[@]}" \
interface \
list
run_test \
"Interface list JSON" \
"${CLI[@]}" \
--format json \
interface \
list
###############################################################################
# 11 — Peer
###############################################################################
section "11 — Peer Management"
run_test \
"Peer list" \
"${CLI[@]}" \
peer \
list
run_test \
"Peer list JSON" \
"${CLI[@]}" \
--format json \
peer \
list
###############################################################################
# 12 — Route
###############################################################################
section "12 — Routing"
run_test \
"Route list" \
"${CLI[@]}" \
route \
list
run_test \
"Route list JSON" \
"${CLI[@]}" \
--format json \
route \
list
###############################################################################
# 13 — Firewall
###############################################################################
section "13 — Firewall"
run_test \
"Firewall list" \
"${CLI[@]}" \
firewall \
list
run_test \
"Firewall list JSON" \
"${CLI[@]}" \
--format json \
firewall \
list
###############################################################################
# 14 — NAT
###############################################################################
section "14 — NAT"
run_test \
"NAT status" \
"${CLI[@]}" \
nat \
status
run_test \
"NAT list" \
"${CLI[@]}" \
nat \
list
run_test \
"NAT status JSON" \
"${CLI[@]}" \
--format json \
nat \
status
###############################################################################
# 15 — Forwarding
###############################################################################
section "15 — IP Forwarding"
run_test \
"Forwarding status" \
"${CLI[@]}" \
forwarding \
status
run_test \
"Forwarding status JSON" \
"${CLI[@]}" \
--format json \
forwarding \
status
###############################################################################
# 16 — Reconciliation
###############################################################################
section "16 — Reconciliation"
run_test \
"Reconciliation status" \
"${CLI[@]}" \
reconcile \
status
run_test \
"Reconciliation plan" \
"${CLI[@]}" \
reconcile \
plan
run_test \
"Reconciliation verify" \
"${CLI[@]}" \
reconcile \
verify
run_test \
"Reconciliation status JSON" \
"${CLI[@]}" \
--format json \
reconcile \
status
###############################################################################
# 17 — Backup
###############################################################################
section "17 — Backup"
run_test \
"Backup list" \
"${CLI[@]}" \
backup \
list
run_test \
"Backup list JSON" \
"${CLI[@]}" \
--format json \
backup \
list
###############################################################################
# 18 — Audit
###############################################################################
section "18 — Audit"
run_test \
"Audit list" \
"${CLI[@]}" \
audit \
list
run_test \
"Audit list JSON" \
"${CLI[@]}" \
--format json \
audit \
list
###############################################################################
# 19 — Live state
###############################################################################
section "19 — Live Linux State"
if [[ "$LIVE" == "1" ]]; then
info "LIVE=1 enabled."
run_test \
"Live interface list" \
"${CLI[@]}" \
live \
interface \
list
run_test \
"Live peer list" \
"${CLI[@]}" \
live \
peer \
list \
wg0
run_test \
"Live routes" \
"${CLI[@]}" \
live \
routes
run_test \
"Live firewall" \
"${CLI[@]}" \
live \
firewall
run_test \
"Live forwarding" \
"${CLI[@]}" \
live \
forwarding
run_test \
"Live NAT" \
"${CLI[@]}" \
live \
nat
else
skip "Live interface list — use LIVE=1"
skip "Live peer list — use LIVE=1"
skip "Live routes — use LIVE=1"
skip "Live firewall — use LIVE=1"
skip "Live forwarding — use LIVE=1"
skip "Live NAT — use LIVE=1"
fi
###############################################################################
# 20 — Diagnostics
###############################################################################
section "20 — Native Diagnostics"
run_test \
"Diagnostics all" \
"${CLI[@]}" \
diagnostics \
all
run_test \
"Diagnostics all JSON" \
"${CLI[@]}" \
--format json \
diagnostics \
all
DIAGNOSTIC_SUBSYSTEMS=(
system
network
wan
wireguard
routing
forwarding
firewall
nat
mtu
reconciliation
)
for subsystem in "${DIAGNOSTIC_SUBSYSTEMS[@]}"; do
run_test \
"Diagnostics: $subsystem" \
"${CLI[@]}" \
diagnostics \
"$subsystem"
done
if [[ "$LIVE" == "1" ]]; then
run_test \
"Diagnostics: peer" \
"${CLI[@]}" \
diagnostics \
peer
else
skip "Diagnostics: peer — requires LIVE=1 and peer context"
fi
###############################################################################
# 21 — Output format matrix
###############################################################################
section "21 — Output Format Matrix"
FORMATS=(
table
json
yaml
csv
)
for format in "${FORMATS[@]}"; do
run_test \
"Network list — $format" \
"${CLI[@]}" \
--format "$format" \
network \
list
run_test \
"Profile list — $format" \
"${CLI[@]}" \
--format "$format" \
profile \
list
run_test \
"System info — $format" \
"${CLI[@]}" \
--format "$format" \
system \
info
run_test \
"System health — $format" \
"${CLI[@]}" \
--format "$format" \
system \
health
run_test \
"Audit list — $format" \
"${CLI[@]}" \
--format "$format" \
audit \
list
done
###############################################################################
# 22 — Global options
###############################################################################
section "22 — Global CLI Options"
run_test \
"Quiet mode" \
"${CLI[@]}" \
--quiet \
system \
health
run_test \
"JSON shortcut" \
"${CLI[@]}" \
--json \
system \
health
run_test \
"Verbose mode" \
"${CLI[@]}" \
--verbose \
system \
health
###############################################################################
# 23 — Environment namespace
###############################################################################
section "23 — NX9_WG_* Environment Namespace"
NX9_WG_LOG_LEVEL=debug \
"$BIN" \
--data-dir "$DATA_DIR" \
--database "$DB" \
system \
health \
>/dev/null \
2>/dev/null
if [[ "$?" -eq 0 ]]; then
pass "NX9_WG_LOG_LEVEL accepted"
else
fail "NX9_WG_LOG_LEVEL accepted"
fi
###############################################################################
# 24 — Explicit database path
###############################################################################
section "24 — Explicit Database Path Resolution"
EXPLICIT_DB="$TEST_ROOT/explicit/nested/nx9-wg.db"
rm -rf "$TEST_ROOT/explicit"
run_test \
"Explicit nested database path" \
--database "$EXPLICIT_DB" \
system \
health
assert_file \
"Explicit database file created" \
"$EXPLICIT_DB"
###############################################################################
# 25 — Data directory resolution
###############################################################################
section "25 — Data Directory Resolution"
DATA_ONLY="$TEST_ROOT/data-only"
rm -rf "$DATA_ONLY"
mkdir -p "$DATA_ONLY"
run_test \
"Database created from data-dir" \
--data-dir "$DATA_ONLY" \
system \
health
FOUND_DB="$(
find "$DATA_ONLY" \
-type f \
\( \
-name '*.db' \
-o -name '*.sqlite' \
-o -name '*.sqlite3' \
\) \
-print \
-quit \
2>/dev/null
)"
if [[ -n "$FOUND_DB" ]]; then
pass "Database exists below data-dir: $FOUND_DB"
else
fail "No database found below data-dir"
fi
###############################################################################
# 26 — Source architecture safety
###############################################################################
section "26 — Source-Level Architecture Safety"
PROCESS_SCAN="$TEST_ROOT/forbidden-process.txt"
MARKER_SCAN="$TEST_ROOT/forbidden-markers.txt"
SQL_SCAN="$TEST_ROOT/sql-outside-db.txt"
ENV_SCAN="$TEST_ROOT/env-scan.txt"
if grep -RInE \
'Command::new|tokio::process|std::process::Command' \
"$ROOT/src" \
"$ROOT/crates" \
--include='*.rs' \
>"$PROCESS_SCAN" \
2>/dev/null
then
echo
echo "Forbidden subprocess references:"
sed 's/^/ /' "$PROCESS_SCAN"
fail "No external subprocess invocation in production Rust"
else
pass "No external subprocess invocation in production Rust"
fi
if grep -RInE \
'TODO|FIXME|XXX|HACK|unimplemented!\(\)|todo!\(\)' \
"$ROOT/src" \
"$ROOT/crates" \
--include='*.rs' \
>"$MARKER_SCAN" \
2>/dev/null
then
echo
echo "Forbidden markers:"
sed 's/^/ /' "$MARKER_SCAN"
fail "No forbidden technical-debt markers"
else
pass "No forbidden technical-debt markers"
fi
if grep -RIn \
'sqlx::query' \
"$ROOT/src" \
--include='*.rs' \
>"$SQL_SCAN" \
2>/dev/null
then
echo
echo "SQL found outside nx9-wg-db:"
sed 's/^/ /' "$SQL_SCAN"
fail "SQL isolation"
else
pass "SQL isolated from application root"
fi
###############################################################################
# Environment variable source scan
###############################################################################
section "27 — Environment Variable Namespace Source Audit"
if grep -RInE \
'env\s*\(\s*"(NX9_|WG_|RUST_LOG)' \
"$ROOT/src" \
"$ROOT/crates" \
"$ROOT/Cargo.toml" \
--include='*.rs' \
--include='*.toml' \
>"$ENV_SCAN" \
2>/dev/null
then
echo
echo "Potential non-canonical environment references:"
sed 's/^/ /' "$ENV_SCAN"
if grep -E \
'env\s*\(\s*"(NX9_[^W]|WG_|RUST_LOG)' \
"$ENV_SCAN" \
>/dev/null 2>&1
then
fail "Strict NX9_WG_* environment namespace"
else
pass "Environment namespace appears canonical"
fi
else
pass "No suspicious environment namespace references"
fi
###############################################################################
# Final summary
###############################################################################
section "28 — FINAL VERIFICATION SUMMARY"
echo
echo " Binary:"
echo " $BIN"
echo
echo " Project:"
echo " $ROOT"
echo
echo " Temporary test root:"
echo " $TEST_ROOT"
echo
echo " Test database:"
echo " $DB"
echo
echo " LIVE mode:"
echo " $LIVE"
echo
echo " ------------------------------------------------------------------------"
echo " PASS : $PASS"
echo " FAIL : $FAIL"
echo " SKIP : $SKIP"
echo " TOTAL: $TOTAL"
echo " ------------------------------------------------------------------------"
echo
if [[ "$FAIL" -eq 0 ]]; then
echo "${GREEN}${BOLD}RESULT: PASS${RESET}"
echo
echo "All executed nx9-wg CLI verification checks passed."
echo
exit 0
fi
echo "${RED}${BOLD}RESULT: FAIL${RESET}"
echo
echo "One or more verification checks failed."
echo
echo "The temporary environment will be preserved for investigation."
echo
exit 1