Make Pivilion the exclusive dnsmasq owner

This commit is contained in:
v3d 2026-07-02 21:20:47 +02:00
parent 5a27dfeceb
commit 0238f3a531
3 changed files with 61 additions and 7 deletions

View file

@ -121,7 +121,7 @@ backup_managed_files() {
BACKUP_DIR=$(mktemp -d /var/backups/pivilion/install-$(date +%Y%m%d-%H%M%S).XXXXXX)
install -d -m 0700 "$BACKUP_DIR/files"
: > "$BACKUP_DIR/present"; : > "$BACKUP_DIR/missing"; : > "$BACKUP_DIR/enabled-services"; : > "$BACKUP_DIR/active-services"
for service in apache2 pivilion-config.service pivilion-mode.service pivilion-boot-greeter.service pivilion-login-banner.service pivilion-dnsmasq.service pivilion-hostapd.service tor.service tor@default.service; do
for service in apache2 dnsmasq.service pivilion-config.service pivilion-mode.service pivilion-boot-greeter.service pivilion-login-banner.service pivilion-dnsmasq.service pivilion-hostapd.service tor.service tor@default.service; do
systemctl is-enabled --quiet "$service" 2>/dev/null && printf '%s\n' "$service" >> "$BACKUP_DIR/enabled-services" || true
systemctl is-active --quiet "$service" 2>/dev/null && printf '%s\n' "$service" >> "$BACKUP_DIR/active-services" || true
done
@ -151,9 +151,9 @@ rollback() {
relative=${path#/}; rm -rf -- "$path"; install -d -m 0755 "$(dirname "$path")"; cp -a "$BACKUP_DIR/files/$relative" "$path"
done < "$BACKUP_DIR/present"
systemctl daemon-reload 2>/dev/null || true
systemctl disable apache2 pivilion-config.service pivilion-mode.service pivilion-boot-greeter.service pivilion-login-banner.service tor.service tor@default.service 2>/dev/null || true
systemctl disable apache2 dnsmasq.service pivilion-config.service pivilion-mode.service pivilion-boot-greeter.service pivilion-login-banner.service tor.service tor@default.service 2>/dev/null || true
while IFS= read -r path; do [ -n "$path" ] && systemctl enable "$path" 2>/dev/null || true; done < "$BACKUP_DIR/enabled-services"
for service in apache2 pivilion-config.service pivilion-mode.service pivilion-boot-greeter.service pivilion-login-banner.service pivilion-dnsmasq.service pivilion-hostapd.service tor.service tor@default.service; do
for service in apache2 dnsmasq.service pivilion-config.service pivilion-mode.service pivilion-boot-greeter.service pivilion-login-banner.service pivilion-dnsmasq.service pivilion-hostapd.service tor.service tor@default.service; do
grep -Fqx "$service" "$BACKUP_DIR/active-services" || systemctl stop "$service" 2>/dev/null || true
done
while IFS= read -r service; do [ -n "$service" ] && systemctl start "$service" 2>/dev/null || true; done < "$BACKUP_DIR/active-services"
@ -281,9 +281,10 @@ EOF
fi
migrate_legacy_console_greeter
systemctl daemon-reload
systemctl disable --now dnsmasq.service
systemctl enable apache2 pivilion-config.service pivilion-mode.service pivilion-login-banner.service
systemctl enable tor@default.service 2>/dev/null || systemctl enable tor.service
systemctl disable pivilion-dnsmasq.service pivilion-hostapd.service 2>/dev/null || true
systemctl disable pivilion-dnsmasq.service pivilion-hostapd.service
/usr/local/lib/pivilion/issue-banner.sh
command -v agetty >/dev/null 2>&1 && agetty --reload >/dev/null 2>&1 || true
}

View file

@ -41,7 +41,8 @@ installer_safety() {
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 -Fq 'systemctl disable --now dnsmasq.service' "$ROOT/install.sh" &&
grep -Fq 'systemctl disable pivilion-dnsmasq.service pivilion-hostapd.service' "$ROOT/install.sh" &&
! grep -q 'rm -f.*CAPTIVE\|rm -f.*\.htaccess' "$ROOT/usr/local/bin/onion"
}
@ -88,6 +89,20 @@ networkmanager_startup_contract() {
grep -q 'nmcli connection up "$connection" ifname "$iface"' "$ROOT/usr/local/lib/pivilion/networkmanager.sh"
}
dnsmasq_ownership_contract() {
[ -f "$ROOT/etc/systemd/system/pivilion-dnsmasq.service" ] &&
! find "$ROOT/etc/systemd/system" -type l \( -name 'dnsmasq.service' -o -name 'pivilion-dnsmasq.service' \) -print -quit | grep -q . &&
grep -Fq 'systemctl stop dnsmasq.service pivilion-dnsmasq.service' "$ROOT/usr/local/lib/pivilion/mode.sh" &&
grep -Fq 'systemctl stop pivilion-dnsmasq.service' "$ROOT/usr/local/lib/pivilion/mode.sh" &&
grep -q 'start_pivilion_dnsmasq' "$ROOT/usr/local/lib/pivilion/mode.sh" &&
grep -q 'systemctl is-active --quiet pivilion-dnsmasq.service' "$ROOT/usr/local/lib/pivilion/mode.sh" &&
grep -Fq 'address=/#/10.1.1.1' "$ROOT/usr/local/lib/pivilion/mode.sh" &&
grep -q 'android_probe.*302' "$ROOT/usr/local/lib/pivilion/diagnose.sh" &&
grep -q 'apple_probe.*302' "$ROOT/usr/local/lib/pivilion/diagnose.sh" &&
grep -q 'microsoft_probe.*302' "$ROOT/usr/local/lib/pivilion/diagnose.sh" &&
grep -Fqx 'RewriteCond %{REQUEST_FILENAME} -f [OR]' "$ROOT/home/pi/pivilion/config/html/.htaccess"
}
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"
@ -106,6 +121,7 @@ run "canonical boot configuration contract" boot_config_contract
run "same-boot hotspot and onion activation contract" same_boot_mode_contract
run "NetworkManager software-radio and delayed-device handling" "$ROOT/tests/test-networkmanager.sh"
run "NetworkManager hotspot startup contract" networkmanager_startup_contract
run "exclusive captive dnsmasq ownership contract" dnsmasq_ownership_contract
run "ShellCheck (when installed)" shellcheck_scripts
[ "$failures" -eq 0 ] || { echo "$failures test group(s) failed." >&2; exit 1; }

View file

@ -90,7 +90,7 @@ validate_ap_support() {
}
cleanup_hotspot() {
systemctl stop pivilion-dnsmasq.service pivilion-hostapd.service 2>/dev/null || true
systemctl stop dnsmasq.service pivilion-dnsmasq.service pivilion-hostapd.service 2>/dev/null || true
rm -f "$DNSMASQ_CONF" "$HOSTAPD_CONF"
nft delete table inet pivilion 2>/dev/null || true
if command -v nmcli >/dev/null 2>&1; then
@ -115,6 +115,41 @@ check_dns_conflict() {
fi
}
log_dnsmasq_failure() {
{
echo "Pivilion dnsmasq diagnostics:"
echo "-- TCP/UDP port 53 listeners --"
ss -H -lntup 'sport = :53' || true
echo "-- pivilion-dnsmasq.service status --"
systemctl status pivilion-dnsmasq.service --no-pager -l || true
echo "-- generic dnsmasq.service status --"
systemctl status dnsmasq.service --no-pager -l || true
echo "-- recent pivilion-dnsmasq.service journal --"
journalctl -u pivilion-dnsmasq.service -n 50 --no-pager || true
} >&2
}
start_pivilion_dnsmasq() {
local i stable=0
if ! systemctl start pivilion-dnsmasq.service; then
echo "Could not start pivilion-dnsmasq.service" >&2
log_dnsmasq_failure
return 1
fi
for i in $(seq 1 10); do
if systemctl is-active --quiet pivilion-dnsmasq.service; then
stable=$((stable + 1))
[ "$stable" -ge 3 ] && return 0
else
stable=0
fi
sleep 1
done
echo "pivilion-dnsmasq.service did not remain active" >&2
log_dnsmasq_failure
return 1
}
check_hostapd_conflict() {
if systemctl is-active --quiet hostapd.service 2>/dev/null; then
echo "The system hostapd service already owns a wireless interface." >&2
@ -225,6 +260,7 @@ start_hotspot() {
fi
if ! check_dns_conflict; then
pivilion_write_state none "$BACKEND" failed "Port 53 conflict"
log_dnsmasq_failure
refresh_login_banner
exit "$PIVILION_E_CONFLICT"
fi
@ -245,7 +281,7 @@ start_hotspot() {
ip link set "$IFACE" down; ip address flush dev "$IFACE"; ip address add 10.1.1.1/24 dev "$IFACE"; ip link set "$IFACE" up
systemctl restart pivilion-hostapd.service
fi
systemctl restart pivilion-dnsmasq.service
start_pivilion_dnsmasq
apply_firewall
if wait_for_diagnostics hotspot; then
pivilion_write_state hotspot "$BACKEND" healthy none
@ -264,6 +300,7 @@ start_onion() {
pivilion_write_state none "$BACKEND" activating none
if [ "$BACKEND" = networkmanager ]; then pivilion_nm_prepare_interface "$IFACE"; fi
cleanup_hotspot
systemctl stop pivilion-dnsmasq.service 2>/dev/null || true
rm -f "$CAPTIVE_LIVE"
if [ "$BACKEND" = networkmanager ]; then
nmcli connection up pivilion-onion >/dev/null 2>&1 || {