Harden installation and runtime diagnostics

This commit is contained in:
v3d 2026-06-29 21:43:47 +02:00
parent 34b4c4b036
commit ed0b168ab6
14 changed files with 748 additions and 213 deletions

67
tests/run.sh Executable file
View file

@ -0,0 +1,67 @@
#!/bin/bash
set -euo pipefail
ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
failures=0
run() {
local label=$1
shift
if "$@"; then printf '[pass] %s\n' "$label"; else printf '[fail] %s\n' "$label" >&2; failures=$((failures + 1)); fi
}
shell_syntax() {
bash -n "$ROOT/install.sh" "$ROOT"/usr/local/lib/pivilion/*.sh "$ROOT"/usr/local/bin/* "$ROOT/etc/profile.d/pivilion-motd.sh"
}
php_syntax() {
command -v php >/dev/null 2>&1 || return 0
while IFS= read -r file; do php -l "$file" >/dev/null || return 1; done < <(find "$ROOT/var" "$ROOT/home" -name '*.php' -type f)
}
systemd_syntax() {
local output
command -v systemd-analyze >/dev/null 2>&1 || return 0
output=$(systemd-analyze verify "$ROOT"/etc/systemd/system/*.service 2>&1) || true
output=$(printf '%s\n' "$output" | grep -Ev 'SO_PASS(RIGHTS|CRED)|handoff timestamp|user lookup socket' || true)
[ -z "$output" ]
}
captive_rules() {
grep -q 'R=302' "$ROOT/home/pi/pivilion/config/html/.htaccess" &&
! grep -q 'R=301' "$ROOT/home/pi/pivilion/config/html/.htaccess" &&
! grep -Eq 'dport[[:space:]]+443|redirect to :443' "$ROOT/usr/local/lib/pivilion/mode.sh"
}
installer_safety() {
grep -q -- '--dry-run' "$ROOT/install.sh" &&
grep -q -- '--repair' "$ROOT/install.sh" &&
grep -q 'backup_managed_files' "$ROOT/install.sh" &&
grep -q 'rollback()' "$ROOT/install.sh" &&
! grep -Eq 'disable( --now)? dnsmasq' "$ROOT/install.sh" &&
! grep -q 'rm -f.*CAPTIVE\|rm -f.*\.htaccess' "$ROOT/usr/local/bin/onion"
}
state_contract() {
grep -q "printf 'desired_mode" "$ROOT/usr/local/lib/pivilion/common.sh" &&
grep -q "printf 'active_mode" "$ROOT/usr/local/lib/pivilion/common.sh" &&
grep -q "printf 'health" "$ROOT/usr/local/lib/pivilion/common.sh" &&
grep -q 'degraded' "$ROOT/usr/local/lib/pivilion/mode.sh"
}
shellcheck_scripts() {
command -v shellcheck >/dev/null 2>&1 || return 0
shellcheck "$ROOT/install.sh" "$ROOT"/usr/local/lib/pivilion/*.sh "$ROOT"/usr/local/bin/pivilion* "$ROOT/usr/local/bin/hotspot" "$ROOT/usr/local/bin/onion" "$ROOT/usr/local/bin/pikey"
}
run "configuration and state fixtures" "$ROOT/tests/test-common.sh"
run "shell syntax" shell_syntax
run "PHP syntax" php_syntax
run "systemd unit syntax" systemd_syntax
run "temporary HTTP-only captive rules" captive_rules
run "transactional installer safety contract" installer_safety
run "truthful runtime state contract" state_contract
run "ShellCheck (when installed)" shellcheck_scripts
[ "$failures" -eq 0 ] || { echo "$failures test group(s) failed." >&2; exit 1; }
echo "All available tests passed."

62
tests/test-common.sh Executable file
View file

@ -0,0 +1,62 @@
#!/bin/bash
set -euo pipefail
ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT
USER_NAME=$(id -un)
cat > "$TMP/primary.conf" <<EOF
PIVILION_USER=$USER_NAME
PIVILION_MODE=hotspot
EOF
cat > "$TMP/fallback.conf" <<EOF
PIVILION_USER=$USER_NAME
PIVILION_HOME=$HOME
PIVILION_DIR=$TMP/pivilion
PIVILION_WEBROOT=/var/www/html/pivilion
PIVILION_MODE=onion
PIVILION_HOTSPOT_SSID=fixture-portal
EOF
export PIVILION_CONFIG_PATHS="$TMP/primary.conf $TMP/fallback.conf"
export PIVILION_STATE_DIR="$TMP/run"
export PIVILION_STATE_FILE="$TMP/run/status.tsv"
source "$ROOT/usr/local/lib/pivilion/common.sh"
pivilion_load_config
[ "$PIVILION_MODE" = hotspot ]
[ "$PIVILION_HOTSPOT_SSID" = fixture-portal ]
[ "$PIVILION_DIR" = "$TMP/pivilion" ]
install -d "$PIVILION_DIR" "$PIVILION_STATE_DIR"
printf 'onion\n' > "$PIVILION_DIR/mode"
[ "$(pivilion_current_mode)" = onion ]
cat > "$PIVILION_STATE_FILE" <<EOF
desired_mode onion
active_mode hotspot
backend networkmanager
health degraded
failure probe failed
EOF
[ "$(pivilion_state_value active_mode)" = hotspot ]
[ "$(pivilion_state_value health)" = degraded ]
[ "$(pivilion_state_value missing fallback)" = fallback ]
mkdir "$TMP/bin"
cat > "$TMP/bin/nmcli" <<'EOF'
#!/bin/sh
exit 0
EOF
cat > "$TMP/bin/systemctl" <<'EOF'
#!/bin/sh
[ "${MOCK_NM_ACTIVE:-0}" = 1 ]
EOF
chmod +x "$TMP/bin/nmcli" "$TMP/bin/systemctl"
PATH="$TMP/bin:$PATH" MOCK_NM_ACTIVE=1 pivilion_uses_networkmanager
if PATH="$TMP/bin:$PATH" MOCK_NM_ACTIVE=0 pivilion_uses_networkmanager; then exit 1; fi
sed 's/PIVILION_MODE=hotspot/PIVILION_MODE=invalid/' "$TMP/primary.conf" > "$TMP/invalid.conf"
PIVILION_CONFIG_PATHS="$TMP/invalid.conf $TMP/fallback.conf"
export PIVILION_CONFIG_PATHS
if pivilion_load_config >/dev/null 2>&1; then exit 1; fi