#!/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