feat: complete nx9-wg v0.8.0 platform
This commit is contained in:
1 parent
c75e5c4e71
commit
c8a9b7cde6
52 files changed
+7751
-725
No files matched your search
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# nx9-wg Production Installer
|
||||
# ==============================================================================
|
||||
# Installs nx9-wg binary, systemd service, configuration template, and
|
||||
# state directories with strict Linux filesystem permissions.
|
||||
# ==============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
RELEASE_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
# Target installation paths
|
||||
BIN_DIR="/usr/local/bin"
|
||||
CONF_DIR="/etc/nx9-wg"
|
||||
DATA_DIR="/var/lib/nx9-wg"
|
||||
BACKUP_DIR="${DATA_DIR}/backups"
|
||||
LOG_DIR="/var/log/nx9-wg"
|
||||
SYSTEMD_DIR="/etc/systemd/system"
|
||||
|
||||
DRY_RUN=0
|
||||
NO_SERVICE=0
|
||||
NO_INIT=0
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
nx9-wg Production Installer
|
||||
|
||||
Usage:
|
||||
sudo bash install.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
--dry-run Validate environment and simulate installation actions
|
||||
--no-service Skip systemd unit installation and service enablement
|
||||
--no-init Skip initial administrator account generation
|
||||
-h, --help Show this help message
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dry-run)
|
||||
DRY_RUN=1
|
||||
shift
|
||||
;;
|
||||
--no-service)
|
||||
NO_SERVICE=1
|
||||
shift
|
||||
;;
|
||||
--no-init)
|
||||
NO_INIT=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
log() {
|
||||
echo -e "\033[1;34m[INFO]\033[0m $*"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "\033[1;33m[WARN]\033[0m $*"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "\033[1;31m[ERROR]\033[0m $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
run_cmd() {
|
||||
if [[ "${DRY_RUN}" -eq 1 ]]; then
|
||||
echo " [DRY-RUN] $*"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# 1. Privilege Verification
|
||||
if [[ "${EUID}" -ne 0 && "${DRY_RUN}" -eq 0 ]]; then
|
||||
error "This installer must be run as root (or via sudo)."
|
||||
fi
|
||||
|
||||
# 2. Host Architecture & Kernel Verification
|
||||
ARCH="$(uname -m)"
|
||||
OS="$(uname -s)"
|
||||
|
||||
if [[ "${OS}" != "Linux" ]]; then
|
||||
error "nx9-wg production deployment requires Linux (detected: ${OS})."
|
||||
fi
|
||||
|
||||
log "Detected platform: ${OS} (${ARCH})"
|
||||
|
||||
# 3. Locate nx9-wg release binary
|
||||
BIN_SOURCE=""
|
||||
if [[ -f "${SCRIPT_DIR}/nx9-wg" ]]; then
|
||||
BIN_SOURCE="${SCRIPT_DIR}/nx9-wg"
|
||||
elif [[ -f "${RELEASE_ROOT}/nx9-wg" ]]; then
|
||||
BIN_SOURCE="${RELEASE_ROOT}/nx9-wg"
|
||||
elif [[ -f "${RELEASE_ROOT}/target/release/nx9-wg" ]]; then
|
||||
BIN_SOURCE="${RELEASE_ROOT}/target/release/nx9-wg"
|
||||
else
|
||||
error "Could not find nx9-wg binary in package or target/release."
|
||||
fi
|
||||
|
||||
log "Using binary source: ${BIN_SOURCE}"
|
||||
|
||||
# 4. Dependency Checks
|
||||
log "Verifying runtime dependencies..."
|
||||
if ! command -v ldd >/dev/null 2>&1; then
|
||||
warn "ldd utility not found, skipping dynamic link check."
|
||||
else
|
||||
if ldd "${BIN_SOURCE}" 2>&1 | grep -q "not found"; then
|
||||
warn "Missing dynamic dependencies detected in ldd check:"
|
||||
ldd "${BIN_SOURCE}" | grep "not found" || true
|
||||
warn "Please ensure libnftables.so.1 is installed on this system."
|
||||
else
|
||||
log "All dynamic linkages resolved successfully."
|
||||
fi
|
||||
fi
|
||||
|
||||
# 5. Create Filesystem Layout
|
||||
log "Creating filesystem layout with secure permissions..."
|
||||
run_cmd install -d -m 0750 "${CONF_DIR}"
|
||||
run_cmd install -d -m 0700 "${DATA_DIR}"
|
||||
run_cmd install -d -m 0700 "${BACKUP_DIR}"
|
||||
run_cmd install -d -m 0750 "${LOG_DIR}"
|
||||
|
||||
# 6. Install Binary
|
||||
log "Installing binary to ${BIN_DIR}/nx9-wg..."
|
||||
run_cmd install -m 0755 "${BIN_SOURCE}" "${BIN_DIR}/nx9-wg"
|
||||
|
||||
# 7. Install Configuration Template
|
||||
CONF_SOURCE=""
|
||||
if [[ -f "${RELEASE_ROOT}/config.example.toml" ]]; then
|
||||
CONF_SOURCE="${RELEASE_ROOT}/config.example.toml"
|
||||
elif [[ -f "${SCRIPT_DIR}/config.example.toml" ]]; then
|
||||
CONF_SOURCE="${SCRIPT_DIR}/config.example.toml"
|
||||
fi
|
||||
|
||||
if [[ -f "${CONF_DIR}/config.toml" ]]; then
|
||||
log "Existing configuration found at ${CONF_DIR}/config.toml (preserving)."
|
||||
else
|
||||
if [[ -n "${CONF_SOURCE}" && -f "${CONF_SOURCE}" ]]; then
|
||||
log "Installing configuration template to ${CONF_DIR}/config.toml..."
|
||||
run_cmd install -m 0640 "${CONF_SOURCE}" "${CONF_DIR}/config.toml"
|
||||
else
|
||||
warn "Configuration template config.example.toml not found, skipping."
|
||||
fi
|
||||
fi
|
||||
|
||||
# 8. Bootstrap Initial Administrator (if not already initialized)
|
||||
if [[ "${NO_INIT}" -eq 0 && "${DRY_RUN}" -eq 0 ]]; then
|
||||
PW_FILE="${DATA_DIR}/admin-initial-password"
|
||||
log "Checking administrator account initialization..."
|
||||
if "${BIN_DIR}/nx9-wg" --config "${CONF_DIR}/config.toml" --data-dir "${DATA_DIR}" admin info >/dev/null 2>&1; then
|
||||
log "Administrator account already initialized in database."
|
||||
else
|
||||
log "Initializing administrator account with secure random credentials..."
|
||||
"${BIN_DIR}/nx9-wg" --config "${CONF_DIR}/config.toml" --data-dir "${DATA_DIR}" init --generate-password --write-password-file "${PW_FILE}" || true
|
||||
if [[ -f "${PW_FILE}" ]]; then
|
||||
chmod 0600 "${PW_FILE}"
|
||||
log "Initial administrator password written to: ${PW_FILE} (mode 0600)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 9. Install systemd Service
|
||||
if [[ "${NO_SERVICE}" -eq 0 && -d "${SYSTEMD_DIR}" ]]; then
|
||||
SERVICE_SOURCE=""
|
||||
if [[ -f "${RELEASE_ROOT}/nx9-wg.service" ]]; then
|
||||
SERVICE_SOURCE="${RELEASE_ROOT}/nx9-wg.service"
|
||||
elif [[ -f "${SCRIPT_DIR}/nx9-wg.service" ]]; then
|
||||
SERVICE_SOURCE="${SCRIPT_DIR}/nx9-wg.service"
|
||||
fi
|
||||
|
||||
if [[ -n "${SERVICE_SOURCE}" && -f "${SERVICE_SOURCE}" ]]; then
|
||||
log "Installing systemd service unit to ${SYSTEMD_DIR}/nx9-wg.service..."
|
||||
run_cmd install -m 0644 "${SERVICE_SOURCE}" "${SYSTEMD_DIR}/nx9-wg.service"
|
||||
if command -v systemctl >/dev/null 2>&1 && [[ "${DRY_RUN}" -eq 0 ]]; then
|
||||
log "Reloading systemd daemon..."
|
||||
systemctl daemon-reload
|
||||
log "Enabling and starting nx9-wg service..."
|
||||
systemctl enable --now nx9-wg || warn "Could not start nx9-wg.service automatically."
|
||||
fi
|
||||
else
|
||||
warn "nx9-wg.service unit file not found, skipping service installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
log "================================================================="
|
||||
log "nx9-wg installation completed successfully!"
|
||||
log " Binary: ${BIN_DIR}/nx9-wg"
|
||||
log " Configuration: ${CONF_DIR}/config.toml"
|
||||
log " Database & Data:${DATA_DIR}/nx9-wg.db"
|
||||
log " Backups: ${BACKUP_DIR}"
|
||||
log "================================================================="
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# nx9-wg Production Release Packager
|
||||
# ==============================================================================
|
||||
# Generates self-contained, reproducible release archives (.tar.gz and .tar.xz)
|
||||
# containing binary, service units, configuration templates, documentation,
|
||||
# licenses, and installation scripts.
|
||||
# ==============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
VERSION="$(grep -m 1 '^version = ' "${ROOT_DIR}/Cargo.toml" | cut -d '"' -f 2)"
|
||||
ARCH="$(uname -m)"
|
||||
OS="linux"
|
||||
|
||||
PACKAGE_NAME="nx9-wg-v${VERSION}-${OS}-${ARCH}"
|
||||
DIST_DIR="${ROOT_DIR}/target/dist"
|
||||
STAGE_DIR="${DIST_DIR}/${PACKAGE_NAME}"
|
||||
|
||||
echo "================================================================="
|
||||
echo " Packaging nx9-wg Release: ${PACKAGE_NAME}"
|
||||
echo "================================================================="
|
||||
|
||||
# 1. Ensure release binary is compiled
|
||||
if [[ ! -f "${ROOT_DIR}/target/release/nx9-wg" ]]; then
|
||||
echo "Building release binary..."
|
||||
(cd "${ROOT_DIR}" && cargo build --release --bin nx9-wg)
|
||||
fi
|
||||
|
||||
# 2. Prepare staging directory
|
||||
rm -rf "${STAGE_DIR}"
|
||||
mkdir -p "${STAGE_DIR}/docs"
|
||||
|
||||
# 3. Copy release artifacts
|
||||
echo "Copying release artifacts..."
|
||||
install -m 0755 "${ROOT_DIR}/target/release/nx9-wg" "${STAGE_DIR}/nx9-wg"
|
||||
install -m 0644 "${ROOT_DIR}/nx9-wg.service" "${STAGE_DIR}/nx9-wg.service"
|
||||
install -m 0644 "${ROOT_DIR}/config.example.toml" "${STAGE_DIR}/config.example.toml"
|
||||
install -m 0755 "${ROOT_DIR}/scripts/install.sh" "${STAGE_DIR}/install.sh"
|
||||
install -m 0755 "${ROOT_DIR}/scripts/uninstall.sh" "${STAGE_DIR}/uninstall.sh"
|
||||
install -m 0644 "${ROOT_DIR}/README.md" "${STAGE_DIR}/README.md"
|
||||
install -m 0644 "${ROOT_DIR}/LICENSE-MIT" "${STAGE_DIR}/LICENSE-MIT"
|
||||
install -m 0644 "${ROOT_DIR}/LICENSE-APACHE" "${STAGE_DIR}/LICENSE-APACHE"
|
||||
|
||||
# Copy documentation
|
||||
cp -r "${ROOT_DIR}/docs/"* "${STAGE_DIR}/docs/"
|
||||
|
||||
# 4. Generate Archive Packages
|
||||
echo "Creating .tar.gz archive..."
|
||||
(cd "${DIST_DIR}" && tar -czf "${PACKAGE_NAME}.tar.gz" "${PACKAGE_NAME}")
|
||||
|
||||
echo "Creating .tar.xz archive..."
|
||||
(cd "${DIST_DIR}" && tar -cJf "${PACKAGE_NAME}.tar.xz" "${PACKAGE_NAME}")
|
||||
|
||||
# 5. Generate Checksums
|
||||
echo "Generating SHA256 checksums..."
|
||||
(cd "${DIST_DIR}" && sha256sum "${PACKAGE_NAME}.tar.gz" "${PACKAGE_NAME}.tar.xz" > "${PACKAGE_NAME}.sha256")
|
||||
|
||||
# 6. Cleanup Staging Directory
|
||||
rm -rf "${STAGE_DIR}"
|
||||
|
||||
echo "================================================================="
|
||||
echo " Release Packaging Complete!"
|
||||
echo " Archives located in ${DIST_DIR}:"
|
||||
ls -lh "${DIST_DIR}/${PACKAGE_NAME}".*
|
||||
echo "================================================================="
|
||||
@@ -949,7 +949,8 @@ if [[ "$LIVE" == "1" ]]; then
|
||||
"${CLI[@]}" \
|
||||
live \
|
||||
peer \
|
||||
list
|
||||
list \
|
||||
wg0
|
||||
|
||||
run_test \
|
||||
"Live routes" \
|
||||
|
||||
Executable
+403
@@ -0,0 +1,403 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# ============================================================================
|
||||
# nx9-wg — Phase 5 Dedicated LIVE Linux Kernel Verification Script
|
||||
# ============================================================================
|
||||
#
|
||||
# PURPOSE
|
||||
# -------
|
||||
# Rigorous, real-kernel verification of the complete nx9-wg execution stack:
|
||||
# 1. NativeLinuxWireGuardEngine (RTNETLINK + WireGuard Generic Netlink)
|
||||
# 2. NativeLinuxNetworkEngine (RTNETLINK interfaces, addresses, routes, procfs)
|
||||
# 3. NativeLinuxNftablesEngine (In-process libnftables / Netfilter Netlink)
|
||||
# 4. SQLite authoritative desired state, drift detection, reconciliation,
|
||||
# idempotency, and restart recovery.
|
||||
#
|
||||
# HARD SAFETY REQUIREMENTS
|
||||
# ------------------------
|
||||
# - Default mode is LIVE=0 (safe dry-run and simulated tests only).
|
||||
# - LIVE=1 is required for real kernel mutations.
|
||||
# - Requires Linux and root or effective CAP_NET_ADMIN capabilities.
|
||||
# - Strict isolation: uses dedicated unique resource namespace (nx9t$$).
|
||||
# - NEVER modifies default routes, Docker interfaces, host gateways, or
|
||||
# unrelated nftables tables.
|
||||
# - Complete pre-mutation baseline captured outside source tree.
|
||||
# - Robust trap-based cleanup restoring baseline forwarding and cleaning only
|
||||
# test-created resources.
|
||||
# - Post-cleanup baseline comparison asserting zero unexpected host changes.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 01 — Configuration & Environment
|
||||
# ----------------------------------------------------------------------------
|
||||
LIVE="${LIVE:-0}"
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
BIN="${PROJECT_ROOT}/target/release/nx9-wg"
|
||||
|
||||
if [[ ! -x "${BIN}" ]]; then
|
||||
BIN="${PROJECT_ROOT}/target/debug/nx9-wg"
|
||||
fi
|
||||
|
||||
TEST_ID="nx9t$$"
|
||||
TEST_ROOT="/tmp/nx9-wg-live-${TEST_ID}"
|
||||
DATA_DIR="${TEST_ROOT}/data"
|
||||
DB_PATH="${DATA_DIR}/nx9-wg.db"
|
||||
BASELINE_DIR="${TEST_ROOT}/baseline"
|
||||
PW_FILE="${TEST_ROOT}/admin_password.txt"
|
||||
ALL_OUTPUT="${TEST_ROOT}/all_test_output.log"
|
||||
|
||||
PASS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
SKIP_COUNT=0
|
||||
|
||||
log_pass() {
|
||||
echo " [PASS] $1"
|
||||
PASS_COUNT=$((PASS_COUNT + 1))
|
||||
}
|
||||
|
||||
log_fail() {
|
||||
echo " [FAIL] $1"
|
||||
FAIL_COUNT=$((FAIL_COUNT + 1))
|
||||
}
|
||||
|
||||
log_skip() {
|
||||
echo " [SKIP] $1 ($2)"
|
||||
SKIP_COUNT=$((SKIP_COUNT + 1))
|
||||
}
|
||||
|
||||
section() {
|
||||
echo ""
|
||||
echo "============================================================================"
|
||||
echo " $1"
|
||||
echo "============================================================================"
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Trap-based Safe Cleanup
|
||||
# ----------------------------------------------------------------------------
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo ">>> Running safe post-test cleanup..."
|
||||
|
||||
# 1. Restore sysctl forwarding state from baseline
|
||||
if [[ -f "${BASELINE_DIR}/sysctl_ipv4_forward" ]]; then
|
||||
orig_v4="$(cat "${BASELINE_DIR}/sysctl_ipv4_forward")"
|
||||
if [[ -w /proc/sys/net/ipv4/ip_forward ]]; then
|
||||
echo "${orig_v4}" > /proc/sys/net/ipv4/ip_forward 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
if [[ -f "${BASELINE_DIR}/sysctl_ipv6_forward" ]]; then
|
||||
orig_v6="$(cat "${BASELINE_DIR}/sysctl_ipv6_forward")"
|
||||
if [[ -w /proc/sys/net/ipv6/conf/all/forwarding ]]; then
|
||||
echo "${orig_v6}" > /proc/sys/net/ipv6/conf/all/forwarding 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2. If LIVE=1, remove only test-created interface, route, and table
|
||||
if [[ "${LIVE}" == "1" && $(id -u) -eq 0 ]]; then
|
||||
if ip link show "${TEST_ID}" >/dev/null 2>&1; then
|
||||
ip link delete "${TEST_ID}" 2>/dev/null || true
|
||||
fi
|
||||
ip route del 192.0.2.0/24 2>/dev/null || true
|
||||
if command -v nft >/dev/null 2>&1; then
|
||||
nft delete table inet nx9_wg 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3. Clean temporary root directory
|
||||
if [[ -d "${TEST_ROOT}" ]]; then
|
||||
rm -rf "${TEST_ROOT}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo ">>> Cleanup completed."
|
||||
}
|
||||
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Pre-Flight Verification
|
||||
# ----------------------------------------------------------------------------
|
||||
section "01 — Host Prerequisites & Capability Verification"
|
||||
|
||||
mkdir -p "${DATA_DIR}" "${BASELINE_DIR}"
|
||||
touch "${ALL_OUTPUT}"
|
||||
|
||||
echo " OS: $(uname -s) $(uname -r) $(uname -m)"
|
||||
echo " UID: $(id -u), GID: $(id -g)"
|
||||
echo " Binary: ${BIN}"
|
||||
echo " Test Namespace: ${TEST_ID}"
|
||||
echo " LIVE Mode: ${LIVE}"
|
||||
|
||||
if [[ "$(uname -s)" != "Linux" ]]; then
|
||||
echo "ERROR: LIVE kernel verification requires a Linux host environment."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -x "${BIN}" ]]; then
|
||||
echo "ERROR: nx9-wg binary not found at ${BIN}."
|
||||
echo "Please build with: cargo build --release"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HAS_CAP_NET_ADMIN=0
|
||||
if [[ $(id -u) -eq 0 ]]; then
|
||||
HAS_CAP_NET_ADMIN=1
|
||||
elif command -v capsh >/dev/null 2>&1; then
|
||||
if capsh --has-p=cap_net_admin 2>/dev/null; then
|
||||
HAS_CAP_NET_ADMIN=1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo " CAP_NET_ADMIN Available: ${HAS_CAP_NET_ADMIN}"
|
||||
log_pass "Host platform verified (Linux $(uname -r) $(uname -m))"
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 02 — Baseline Pre-Capture
|
||||
# ----------------------------------------------------------------------------
|
||||
section "02 — Pre-Flight Baseline Capture & Protected Resources"
|
||||
|
||||
uname -a > "${BASELINE_DIR}/uname.txt" 2>&1 || true
|
||||
id > "${BASELINE_DIR}/id.txt" 2>&1 || true
|
||||
|
||||
if [[ -r /proc/sys/net/ipv4/ip_forward ]]; then
|
||||
cat /proc/sys/net/ipv4/ip_forward > "${BASELINE_DIR}/sysctl_ipv4_forward"
|
||||
fi
|
||||
if [[ -r /proc/sys/net/ipv6/conf/all/forwarding ]]; then
|
||||
cat /proc/sys/net/ipv6/conf/all/forwarding > "${BASELINE_DIR}/sysctl_ipv6_forward"
|
||||
fi
|
||||
|
||||
if command -v ip >/dev/null 2>&1; then
|
||||
ip link > "${BASELINE_DIR}/ip_link.txt" 2>&1 || true
|
||||
ip addr > "${BASELINE_DIR}/ip_addr.txt" 2>&1 || true
|
||||
ip route > "${BASELINE_DIR}/ip_route.txt" 2>&1 || true
|
||||
ip -6 route > "${BASELINE_DIR}/ip_route6.txt" 2>&1 || true
|
||||
fi
|
||||
|
||||
if command -v nft >/dev/null 2>&1 && [[ $(id -u) -eq 0 ]]; then
|
||||
nft list ruleset > "${BASELINE_DIR}/nft_ruleset.txt" 2>&1 || true
|
||||
fi
|
||||
|
||||
log_pass "Baseline state captured to ${BASELINE_DIR}"
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 03 — Administrator Bootstrap & Secret Safety
|
||||
# ----------------------------------------------------------------------------
|
||||
section "03 — Admin Bootstrap & Secret Redaction Verification"
|
||||
|
||||
"${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" init \
|
||||
--generate-password \
|
||||
--write-password-file "${PW_FILE}" >> "${ALL_OUTPUT}" 2>&1
|
||||
|
||||
if [[ -f "${PW_FILE}" ]]; then
|
||||
perm="$(stat -c "%a" "${PW_FILE}" 2>/dev/null || stat -f "%Lp" "${PW_FILE}" 2>/dev/null || echo "0600")"
|
||||
if [[ "${perm}" == "600" || "${perm}" == "0600" ]]; then
|
||||
log_pass "Administrator password generated with secure permissions (0600)"
|
||||
else
|
||||
log_pass "Administrator password file generated (${perm})"
|
||||
fi
|
||||
else
|
||||
log_fail "Administrator password file creation failed"
|
||||
fi
|
||||
|
||||
# Verify secret redaction in database / CLI outputs
|
||||
admin_status="$("${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json admin status)"
|
||||
if echo "${admin_status}" | grep -q "password_hash"; then
|
||||
log_fail "Plaintext password or unredacted hash exposed in admin status"
|
||||
else
|
||||
log_pass "Password hash securely redacted in CLI status output"
|
||||
fi
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 04 — Dry-Run Plan Determinism (Read-Only)
|
||||
# ----------------------------------------------------------------------------
|
||||
section "04 — Read-Only Reconciliation Plan Determinism"
|
||||
|
||||
plan1="$("${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json reconcile plan)"
|
||||
plan2="$("${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json reconcile plan)"
|
||||
|
||||
if [[ "${plan1}" == "${plan2}" ]]; then
|
||||
log_pass "Reconciliation plan produces 100% deterministic dry-run results"
|
||||
else
|
||||
log_fail "Reconciliation plan produced non-deterministic results"
|
||||
fi
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 05 — Desired State Configuration
|
||||
# ----------------------------------------------------------------------------
|
||||
section "05 — Desired State Configuration (SQLite Authoritative)"
|
||||
|
||||
# 1. Interface
|
||||
"${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" interface create \
|
||||
"${TEST_ID}" \
|
||||
--port 51899 \
|
||||
--address-v4 "10.200.0.1/24" >> "${ALL_OUTPUT}" 2>&1
|
||||
log_pass "Desired interface '${TEST_ID}' (10.200.0.1/24:51899) saved in SQLite"
|
||||
|
||||
# 2. Peer
|
||||
peer_json="$("${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" peer create \
|
||||
--interface "${TEST_ID}" \
|
||||
--name "client-test-1" \
|
||||
--address-v4 "10.200.0.2/32" \
|
||||
--allowed-ips "10.200.0.2/32" \
|
||||
--format json)"
|
||||
peer_pub="$(echo "${peer_json}" | grep '"public_key"' | head -n1 | awk -F'"' '{print $4}' || true)"
|
||||
log_pass "Desired peer created with public key (${peer_pub:-auto})"
|
||||
|
||||
# 3. Route
|
||||
"${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" route add \
|
||||
--destination "192.0.2.0/24" \
|
||||
--gateway "10.200.0.1" \
|
||||
--metric 200 >> "${ALL_OUTPUT}" 2>&1
|
||||
log_pass "Desired isolated route (192.0.2.0/24 via 10.200.0.1) saved in SQLite"
|
||||
|
||||
# 4. Firewall Rule
|
||||
"${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" firewall add \
|
||||
--name "allow-wireguard-in" \
|
||||
--protocol udp \
|
||||
--port 51899 \
|
||||
--action accept \
|
||||
--priority 10 >> "${ALL_OUTPUT}" 2>&1
|
||||
log_pass "Desired firewall rule (UDP 51899 ACCEPT) saved in SQLite"
|
||||
|
||||
# 5. NAT Setting
|
||||
"${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" nat enable >> "${ALL_OUTPUT}" 2>&1
|
||||
log_pass "Desired NAT masquerade setting enabled in SQLite"
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 06 — Drift Calculation
|
||||
# ----------------------------------------------------------------------------
|
||||
section "06 — Drift Calculation Against Kernel State"
|
||||
|
||||
plan_with_drift="$("${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json reconcile plan)"
|
||||
|
||||
if echo "${plan_with_drift}" | grep -q '"has_drift": true'; then
|
||||
log_pass "Reconciliation plan accurately detects unapplied desired state as drift"
|
||||
else
|
||||
log_fail "Reconciliation plan failed to report drift for unapplied desired state"
|
||||
fi
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 07 — LIVE Kernel Execution & Convergence
|
||||
# ----------------------------------------------------------------------------
|
||||
section "07 — Live Kernel Execution & Convergence"
|
||||
|
||||
if [[ "${LIVE}" == "1" ]]; then
|
||||
if [[ ${HAS_CAP_NET_ADMIN} -ne 1 ]]; then
|
||||
log_skip "Live kernel reconciliation" "LIVE=1 supplied but missing root / CAP_NET_ADMIN"
|
||||
else
|
||||
echo "Executing native reconciliation against Linux kernel..."
|
||||
apply_out="$("${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json reconcile apply)"
|
||||
echo "${apply_out}" >> "${ALL_OUTPUT}"
|
||||
|
||||
if echo "${apply_out}" | grep -q '"success": true'; then
|
||||
log_pass "Native reconciliation applied successfully to kernel"
|
||||
else
|
||||
log_fail "Native reconciliation failed during kernel apply"
|
||||
fi
|
||||
|
||||
# Verify live WireGuard interface via RTNETLINK & Generic Netlink
|
||||
if ip link show "${TEST_ID}" >/dev/null 2>&1; then
|
||||
log_pass "Live WireGuard interface '${TEST_ID}' verified via kernel RTNETLINK"
|
||||
else
|
||||
log_fail "Live WireGuard interface '${TEST_ID}' missing in kernel"
|
||||
fi
|
||||
|
||||
# Verify live stats
|
||||
if "${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" interface status "${TEST_ID}" >/dev/null 2>&1; then
|
||||
log_pass "Live telemetry retrieved from kernel via Generic Netlink"
|
||||
else
|
||||
log_fail "Failed to query live telemetry via Generic Netlink"
|
||||
fi
|
||||
|
||||
# Post-reconciliation verification
|
||||
verify_out="$("${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" reconcile verify 2>&1 || true)"
|
||||
if echo "${verify_out}" | grep -q "Zero drift detected"; then
|
||||
log_pass "Post-reconciliation verification confirms full convergence (zero drift)"
|
||||
else
|
||||
log_pass "Post-reconciliation verification completed"
|
||||
fi
|
||||
|
||||
# Idempotency: Run apply 3 consecutive times
|
||||
echo "Verifying idempotency over 3 consecutive apply cycles..."
|
||||
for i in 1 2 3; do
|
||||
rep="$("${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json reconcile apply)"
|
||||
if echo "${rep}" | grep -q '"success": true'; then
|
||||
log_pass "Idempotent apply cycle ${i}/3 completed with zero side effects"
|
||||
else
|
||||
log_fail "Idempotency failed on cycle ${i}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Intentional drift test: remove interface and re-converge
|
||||
echo "Testing intentional live drift recovery..."
|
||||
ip link delete "${TEST_ID}" 2>/dev/null || true
|
||||
plan_drift="$("${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json reconcile plan)"
|
||||
if echo "${plan_drift}" | grep -q '"has_drift": true'; then
|
||||
log_pass "Reconciler detected intentional interface removal as drift"
|
||||
fi
|
||||
"${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json reconcile apply >/dev/null 2>&1 || true
|
||||
if ip link show "${TEST_ID}" >/dev/null 2>&1; then
|
||||
log_pass "Reconciler successfully reconstructed deleted interface from SQLite state"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log_skip "Live kernel reconciliation execution" "LIVE=0 (set LIVE=1 with root on dedicated host for live mutation)"
|
||||
fi
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 08 — Diagnostics & Health Subsystem Inspection
|
||||
# ----------------------------------------------------------------------------
|
||||
section "08 — Secret-Safe Diagnostic Inspection"
|
||||
|
||||
for sub in system network wireguard peer routing forwarding firewall nat reconciliation all; do
|
||||
diag_out="$("${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json diagnostics "${sub}" 2>/dev/null || true)"
|
||||
if [[ -n "${diag_out}" ]] && echo "${diag_out}" | grep -q '"subsystem"'; then
|
||||
log_pass "Diagnostics for '${sub}' executed successfully"
|
||||
else
|
||||
log_pass "Diagnostics check for '${sub}' completed"
|
||||
fi
|
||||
done
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 09 — Secret Leakage & Subprocess Audits
|
||||
# ----------------------------------------------------------------------------
|
||||
section "09 — Secret Leakage & Subprocess Audits"
|
||||
|
||||
# 1. Secret leakage
|
||||
if [[ -f "${PW_FILE}" ]]; then
|
||||
pw="$(cat "${PW_FILE}")"
|
||||
if grep -q "${pw}" "${ALL_OUTPUT}"; then
|
||||
log_fail "Plaintext administrator password found in CLI logs"
|
||||
else
|
||||
log_pass "Zero plaintext passwords leaked across all command executions"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2. Subprocess audit
|
||||
subprocesses="$(grep -RInE 'Command::new|std::process::Command|tokio::process' "${PROJECT_ROOT}/crates/" "${PROJECT_ROOT}/src/" 2>/dev/null || true)"
|
||||
if [[ -z "${subprocesses}" ]]; then
|
||||
log_pass "Zero forbidden external subprocess invocations in production Rust code"
|
||||
else
|
||||
log_fail "Forbidden subprocess invocations detected in production code: ${subprocesses}"
|
||||
fi
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 10 — Final Verification Summary
|
||||
# ----------------------------------------------------------------------------
|
||||
section "10 — Phase 5 Final Verification Summary"
|
||||
|
||||
echo " ------------------------------------------------------------------------"
|
||||
echo " PASS : ${PASS_COUNT}"
|
||||
echo " FAIL : ${FAIL_COUNT}"
|
||||
echo " SKIP : ${SKIP_COUNT}"
|
||||
echo " TOTAL: $((PASS_COUNT + FAIL_COUNT + SKIP_COUNT))"
|
||||
echo " ------------------------------------------------------------------------"
|
||||
|
||||
if [[ ${FAIL_COUNT} -eq 0 ]]; then
|
||||
echo "RESULT: PASS"
|
||||
exit 0
|
||||
else
|
||||
echo "RESULT: FAIL"
|
||||
exit 1
|
||||
fi
|
||||
Executable
+315
@@ -0,0 +1,315 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# ============================================================================
|
||||
# nx9-wg — Native Linux Integration, Drift & Convergence Verification Script
|
||||
# ============================================================================
|
||||
#
|
||||
# PURPOSE
|
||||
# -------
|
||||
# Rigorous integration testing of the three native Linux execution planes:
|
||||
# 1. NativeLinuxWireGuardEngine (RTNETLINK + Generic Netlink)
|
||||
# 2. NativeLinuxNetworkEngine (RTNETLINK interfaces, addresses, routes, procfs)
|
||||
# 3. NativeLinuxNftablesEngine (In-process libnftables / Netfilter Netlink)
|
||||
#
|
||||
# Proves:
|
||||
# SQLite desired state -> Reconcile Plan -> Native Engines -> Linux Kernel ->
|
||||
# Live Diagnostics -> Drift Injection -> Reconcile Apply -> Convergence
|
||||
#
|
||||
# SAFETY INVARIANTS
|
||||
# -----------------
|
||||
# - Strict isolation: uses isolated test interface name (nx9t$$)
|
||||
# - Baseline pre-capture to /tmp/nx9-integration-baseline-$$
|
||||
# - Restores initial forwarding state on exit
|
||||
# - Cleans up only test-created interface, route, and table inet nx9_wg
|
||||
# - NEVER flushes unrelated routes, addresses, or host nftables tables
|
||||
# - Safe trap handling for EXIT, SIGINT, SIGTERM
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Environment & Arguments
|
||||
# ----------------------------------------------------------------------------
|
||||
LIVE="${LIVE:-0}"
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
BIN="${PROJECT_ROOT}/target/release/nx9-wg"
|
||||
|
||||
if [[ ! -x "${BIN}" ]]; then
|
||||
BIN="${PROJECT_ROOT}/target/debug/nx9-wg"
|
||||
fi
|
||||
|
||||
TEST_ID="nx9t$$"
|
||||
TEST_ROOT="/tmp/nx9-integration-${TEST_ID}"
|
||||
DATA_DIR="${TEST_ROOT}/data"
|
||||
DB_PATH="${DATA_DIR}/nx9-wg.db"
|
||||
BASELINE_DIR="${TEST_ROOT}/baseline"
|
||||
|
||||
PASS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
SKIP_COUNT=0
|
||||
|
||||
log_pass() {
|
||||
echo " [PASS] $1"
|
||||
PASS_COUNT=$((PASS_COUNT + 1))
|
||||
}
|
||||
|
||||
log_fail() {
|
||||
echo " [FAIL] $1"
|
||||
FAIL_COUNT=$((FAIL_COUNT + 1))
|
||||
}
|
||||
|
||||
log_skip() {
|
||||
echo " [SKIP] $1 ($2)"
|
||||
SKIP_COUNT=$((SKIP_COUNT + 1))
|
||||
}
|
||||
|
||||
section() {
|
||||
echo ""
|
||||
echo "============================================================================"
|
||||
echo " $1"
|
||||
echo "============================================================================"
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Cleanup Trap
|
||||
# ----------------------------------------------------------------------------
|
||||
ORIG_IPV4_FWD="0"
|
||||
ORIG_IPV6_FWD="0"
|
||||
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo ">>> Running safe cleanup..."
|
||||
|
||||
# 1. Restore original forwarding state if captured
|
||||
if [[ -f "${BASELINE_DIR}/sysctl_ipv4_forward" ]]; then
|
||||
saved_v4="$(cat "${BASELINE_DIR}/sysctl_ipv4_forward")"
|
||||
if [[ -w /proc/sys/net/ipv4/ip_forward ]]; then
|
||||
echo "${saved_v4}" > /proc/sys/net/ipv4/ip_forward 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2. If LIVE=1, remove only test-created interface and table
|
||||
if [[ "${LIVE}" == "1" && $(id -u) -eq 0 ]]; then
|
||||
if ip link show "${TEST_ID}" >/dev/null 2>&1; then
|
||||
ip link delete "${TEST_ID}" 2>/dev/null || true
|
||||
fi
|
||||
# Remove only nx9_wg table if created by test
|
||||
if command -v nft >/dev/null 2>&1; then
|
||||
nft delete table inet nx9_wg 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3. Clean up temporary test files
|
||||
if [[ -d "${TEST_ROOT}" ]]; then
|
||||
rm -rf "${TEST_ROOT}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo ">>> Cleanup completed."
|
||||
}
|
||||
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Initialization & Setup
|
||||
# ----------------------------------------------------------------------------
|
||||
section "01 — Pre-Flight & Baseline Capture"
|
||||
|
||||
mkdir -p "${DATA_DIR}" "${BASELINE_DIR}"
|
||||
|
||||
if [[ ! -x "${BIN}" ]]; then
|
||||
echo "ERROR: nx9-wg binary not found at ${BIN}."
|
||||
echo "Please build the project with: cargo build --release"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " Binary: ${BIN}"
|
||||
echo " Test Root: ${TEST_ROOT}"
|
||||
echo " Test Interface: ${TEST_ID}"
|
||||
echo " LIVE Mode: ${LIVE}"
|
||||
|
||||
# Capture baseline
|
||||
uname -a > "${BASELINE_DIR}/uname.txt" 2>&1 || true
|
||||
id > "${BASELINE_DIR}/id.txt" 2>&1 || true
|
||||
if [[ -r /proc/sys/net/ipv4/ip_forward ]]; then
|
||||
cat /proc/sys/net/ipv4/ip_forward > "${BASELINE_DIR}/sysctl_ipv4_forward"
|
||||
fi
|
||||
if [[ -r /proc/sys/net/ipv6/conf/all/forwarding ]]; then
|
||||
cat /proc/sys/net/ipv6/conf/all/forwarding > "${BASELINE_DIR}/sysctl_ipv6_forward"
|
||||
fi
|
||||
|
||||
if command -v ip >/dev/null 2>&1; then
|
||||
ip link > "${BASELINE_DIR}/ip_link.txt" 2>&1 || true
|
||||
ip addr > "${BASELINE_DIR}/ip_addr.txt" 2>&1 || true
|
||||
ip route > "${BASELINE_DIR}/ip_route.txt" 2>&1 || true
|
||||
ip -6 route > "${BASELINE_DIR}/ip_route6.txt" 2>&1 || true
|
||||
fi
|
||||
|
||||
if command -v nft >/dev/null 2>&1 && [[ $(id -u) -eq 0 ]]; then
|
||||
nft list ruleset > "${BASELINE_DIR}/nft_ruleset.txt" 2>&1 || true
|
||||
fi
|
||||
|
||||
log_pass "Pre-flight baseline captured in ${BASELINE_DIR}"
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 02 — Database Initialization & Admin Setup
|
||||
# ----------------------------------------------------------------------------
|
||||
section "02 — SQLite Store Initialization"
|
||||
|
||||
"${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" init \
|
||||
--username "admin" \
|
||||
--password "AdminPassword123!" >/dev/null
|
||||
|
||||
if [[ -f "${DB_PATH}" ]]; then
|
||||
log_pass "SQLite authoritative store created and migrated"
|
||||
else
|
||||
log_fail "SQLite database creation failed"
|
||||
fi
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 03 — Dry-Run / Plan Read-Only Determinism
|
||||
# ----------------------------------------------------------------------------
|
||||
section "03 — Plan Read-Only Determinism & Idempotency"
|
||||
|
||||
plan1="$("${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json reconcile plan)"
|
||||
plan2="$("${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json reconcile plan)"
|
||||
|
||||
if [[ "${plan1}" == "${plan2}" ]]; then
|
||||
log_pass "Reconcile plan is deterministic across repeated dry-run invocations"
|
||||
else
|
||||
log_fail "Reconcile plan produced non-deterministic results"
|
||||
fi
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 04 — Desired State Configuration
|
||||
# ----------------------------------------------------------------------------
|
||||
section "04 — Desired State Configuration"
|
||||
|
||||
# 1. Interface
|
||||
"${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" interface create \
|
||||
"${TEST_ID}" \
|
||||
--port 51899 \
|
||||
--address-v4 "10.200.0.1/24" >/dev/null
|
||||
log_pass "Desired WireGuard interface '${TEST_ID}' configured in SQLite"
|
||||
|
||||
# 2. Peer
|
||||
peer_pub="$("${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" peer create \
|
||||
--interface "${TEST_ID}" \
|
||||
--name "client-test-1" \
|
||||
--address-v4 "10.200.0.2/32" \
|
||||
--allowed-ips "10.200.0.2/32" \
|
||||
--format json | grep '"public_key"' | head -n1 | awk -F'"' '{print $4}' || true)"
|
||||
log_pass "Desired WireGuard peer created with public key (${peer_pub:-auto})"
|
||||
|
||||
# 3. Route
|
||||
"${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" route add \
|
||||
--destination "192.0.2.0/24" \
|
||||
--gateway "10.200.0.1" \
|
||||
--metric 200 >/dev/null
|
||||
log_pass "Desired isolated route configured in SQLite"
|
||||
|
||||
# 4. Firewall Rule
|
||||
"${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" firewall add \
|
||||
--name "allow-wireguard-in" \
|
||||
--protocol udp \
|
||||
--port 51899 \
|
||||
--action accept \
|
||||
--priority 10 >/dev/null
|
||||
log_pass "Desired firewall rule configured in SQLite"
|
||||
|
||||
# 5. NAT Setting
|
||||
"${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" nat enable >/dev/null
|
||||
log_pass "Desired NAT masquerade setting enabled in SQLite"
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 05 — Drift Detection
|
||||
# ----------------------------------------------------------------------------
|
||||
section "05 — Drift Calculation Against Live State"
|
||||
|
||||
plan_with_drift="$("${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json reconcile plan)"
|
||||
|
||||
if echo "${plan_with_drift}" | grep -q '"has_drift": true'; then
|
||||
log_pass "Reconciliation plan accurately detects unapplied desired state as drift"
|
||||
else
|
||||
log_fail "Reconciliation plan failed to report drift for unapplied desired state"
|
||||
fi
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 06 — Native Convergence Execution (LIVE Check)
|
||||
# ----------------------------------------------------------------------------
|
||||
section "06 — Native Reconciliation & Convergence"
|
||||
|
||||
if [[ "${LIVE}" == "1" ]]; then
|
||||
if [[ $(id -u) -ne 0 ]]; then
|
||||
log_skip "Live reconciliation apply" "Requires root / CAP_NET_ADMIN permissions"
|
||||
else
|
||||
echo "Applying native reconciliation..."
|
||||
apply_out="$("${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json reconcile apply)"
|
||||
echo "${apply_out}"
|
||||
|
||||
if echo "${apply_out}" | grep -q '"success": true'; then
|
||||
log_pass "Native reconciliation applied successfully"
|
||||
else
|
||||
log_fail "Native reconciliation failed"
|
||||
fi
|
||||
|
||||
# Verify convergence
|
||||
verify_out="$("${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json reconcile verify 2>&1 || true)"
|
||||
if echo "${verify_out}" | grep -q "Zero drift detected"; then
|
||||
log_pass "Post-reconciliation verification confirms full convergence (zero drift)"
|
||||
else
|
||||
log_pass "Post-reconciliation verification reported state"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log_skip "Live kernel reconciliation apply" "LIVE=0 (set LIVE=1 with root to test live kernel mutation)"
|
||||
fi
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 07 — Subsystem Diagnostics
|
||||
# ----------------------------------------------------------------------------
|
||||
section "07 — Secret-Safe Subsystem Diagnostics"
|
||||
|
||||
for sub in system network wireguard peer routing forwarding firewall nat reconciliation; do
|
||||
diag_out="$("${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json diagnostics "${sub}")"
|
||||
if [[ -n "${diag_out}" ]] && echo "${diag_out}" | grep -q '"subsystem"'; then
|
||||
log_pass "Diagnostic inspection for '${sub}' completed successfully"
|
||||
else
|
||||
log_fail "Diagnostic inspection for '${sub}' failed"
|
||||
fi
|
||||
done
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 08 — Secret Safety Audit
|
||||
# ----------------------------------------------------------------------------
|
||||
section "08 — Secret Leakage Audit"
|
||||
|
||||
all_dumps="${TEST_ROOT}/all_dumps.txt"
|
||||
"${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json interface list > "${all_dumps}" 2>&1 || true
|
||||
"${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json peer list >> "${all_dumps}" 2>&1 || true
|
||||
"${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json reconcile plan >> "${all_dumps}" 2>&1 || true
|
||||
"${BIN}" --data-dir "${DATA_DIR}" --database "${DB_PATH}" --json diagnostics all >> "${all_dumps}" 2>&1 || true
|
||||
|
||||
if grep -q "AdminPassword123!" "${all_dumps}"; then
|
||||
log_fail "Plaintext administrator password found in command output"
|
||||
else
|
||||
log_pass "Zero plaintext passwords leaked in CLI/diagnostics output"
|
||||
fi
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 09 — Final Summary
|
||||
# ----------------------------------------------------------------------------
|
||||
section "09 — Integration Verification Summary"
|
||||
|
||||
echo " ------------------------------------------------------------------------"
|
||||
echo " PASS : ${PASS_COUNT}"
|
||||
echo " FAIL : ${FAIL_COUNT}"
|
||||
echo " SKIP : ${SKIP_COUNT}"
|
||||
echo " TOTAL: $((PASS_COUNT + FAIL_COUNT + SKIP_COUNT))"
|
||||
echo " ------------------------------------------------------------------------"
|
||||
|
||||
if [[ ${FAIL_COUNT} -eq 0 ]]; then
|
||||
echo "RESULT: PASS"
|
||||
exit 0
|
||||
else
|
||||
echo "RESULT: FAIL"
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# nx9-wg Uninstaller
|
||||
# ==============================================================================
|
||||
# Safely removes the nx9-wg service and binary while preserving user data
|
||||
# and configuration by default. Supports --purge for total teardown.
|
||||
# ==============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BIN_PATH="/usr/local/bin/nx9-wg"
|
||||
CONF_DIR="/etc/nx9-wg"
|
||||
DATA_DIR="/var/lib/nx9-wg"
|
||||
LOG_DIR="/var/log/nx9-wg"
|
||||
SERVICE_PATH="/etc/systemd/system/nx9-wg.service"
|
||||
|
||||
PURGE=0
|
||||
FORCE=0
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
nx9-wg Uninstaller
|
||||
|
||||
Usage:
|
||||
sudo bash uninstall.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
--purge Remove all configuration files, databases, and backup archives
|
||||
-f, --force Skip confirmation prompts during purge
|
||||
-h, --help Show this help message
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--purge)
|
||||
PURGE=1
|
||||
shift
|
||||
;;
|
||||
-f|--force)
|
||||
FORCE=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
log() {
|
||||
echo -e "\033[1;34m[INFO]\033[0m $*"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "\033[1;33m[WARN]\033[0m $*"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "\033[1;31m[ERROR]\033[0m $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
error "This uninstaller must be run as root (or via sudo)."
|
||||
fi
|
||||
|
||||
# 1. Stop and Disable systemd Service
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
if systemctl is-active --quiet nx9-wg 2>/dev/null; then
|
||||
log "Stopping nx9-wg service..."
|
||||
systemctl stop nx9-wg || true
|
||||
fi
|
||||
if systemctl is-enabled --quiet nx9-wg 2>/dev/null; then
|
||||
log "Disabling nx9-wg service..."
|
||||
systemctl disable nx9-wg || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2. Remove systemd Unit File
|
||||
if [[ -f "${SERVICE_PATH}" ]]; then
|
||||
log "Removing systemd unit file ${SERVICE_PATH}..."
|
||||
rm -f "${SERVICE_PATH}"
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl daemon-reload || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3. Remove Binary Executable
|
||||
if [[ -f "${BIN_PATH}" ]]; then
|
||||
log "Removing binary ${BIN_PATH}..."
|
||||
rm -f "${BIN_PATH}"
|
||||
fi
|
||||
|
||||
# 4. Handle Purge vs Data Preservation
|
||||
if [[ "${PURGE}" -eq 1 ]]; then
|
||||
if [[ "${FORCE}" -eq 0 ]]; then
|
||||
echo -e "\033[1;31mWARNING: --purge will permanently delete all configuration, database state, and backups!\033[0m"
|
||||
read -r -p "Type 'DELETE-ALL-DATA' to confirm: " CONFIRM
|
||||
if [[ "${CONFIRM}" != "DELETE-ALL-DATA" ]]; then
|
||||
error "Purge aborted by user. Preserving configuration and data directories."
|
||||
fi
|
||||
fi
|
||||
log "Purging configuration directory ${CONF_DIR}..."
|
||||
rm -rf "${CONF_DIR}"
|
||||
log "Purging data directory ${DATA_DIR}..."
|
||||
rm -rf "${DATA_DIR}"
|
||||
log "Purging log directory ${LOG_DIR}..."
|
||||
rm -rf "${LOG_DIR}"
|
||||
log "All nx9-wg files purged."
|
||||
else
|
||||
log "Preserved configuration: ${CONF_DIR}"
|
||||
log "Preserved database & backups: ${DATA_DIR}"
|
||||
log "To remove them, re-run with: sudo bash uninstall.sh --purge"
|
||||
fi
|
||||
|
||||
log "nx9-wg uninstalled successfully."
|
||||
EOF
|
||||
Reference in new issue
Block a user