mirror of
https://gitlab.com/hacklab01/pivilion.git
synced 2026-08-06 16:08:22 +00:00
Harden installation and runtime diagnostics
This commit is contained in:
parent
34b4c4b036
commit
ed0b168ab6
14 changed files with 748 additions and 213 deletions
21
README.md
21
README.md
|
|
@ -32,6 +32,16 @@ sudo ./install.sh
|
|||
sudo reboot
|
||||
```
|
||||
|
||||
The installer performs preflight checks, backs up managed configuration under
|
||||
`/var/backups/pivilion`, validates the deployed services, and restores the
|
||||
previous configuration if deployment fails. Additional maintenance modes are:
|
||||
|
||||
```sh
|
||||
./install.sh --dry-run # read-only preflight and proposed actions
|
||||
sudo ./install.sh --check # validate an existing installation
|
||||
sudo ./install.sh --repair # repeat deployment without replacing gallery content
|
||||
```
|
||||
|
||||
The installer defaults to the user that invoked `sudo`, not to a hard-coded
|
||||
`pi` account. To customize paths, startup mode, hotspot SSID, or Wi-Fi
|
||||
interface, copy `pivilion.conf.example` to `/boot/firmware/pivilion.conf` (or
|
||||
|
|
@ -44,5 +54,14 @@ through Tor. Use `hotspot` and reboot to run the local captive gallery at
|
|||
local captive portal cannot present valid certificates for arbitrary sites.
|
||||
|
||||
If a phone joins but does not open its captive-login window, run
|
||||
`pivilion-diagnose` on the Pi to verify DHCP/DNS, Apache rewrite handling,
|
||||
`pivilion diagnose` on the Pi to verify DHCP/DNS, Apache rewrite handling,
|
||||
nftables, and the common Android, Apple, and Microsoft HTTP probes.
|
||||
|
||||
`pivilion status` distinguishes the requested next-boot mode from the last
|
||||
verified active mode and reports degraded activation. `pivilion diagnose
|
||||
--json` provides the same health checks for monitoring. The legacy
|
||||
`pivilion-diagnose` command remains an alias.
|
||||
|
||||
Diagnostic exit codes are stable: `10` invalid configuration, `11` missing or
|
||||
unsupported hardware, `12` service/port conflict, `13` core service failure,
|
||||
`14` captive portal failure, `15` Tor/onion failure, and `20` installer failure.
|
||||
|
|
|
|||
13
etc/systemd/system/pivilion-boot-greeter.service
Normal file
13
etc/systemd/system/pivilion-boot-greeter.service
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[Unit]
|
||||
Description=Display the Pivilion boot greeting and system status
|
||||
After=local-fs.target
|
||||
Before=getty@tty1.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/lib/pivilion/boot-greeter.sh
|
||||
StandardOutput=journal+console
|
||||
StandardError=journal+console
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
11
etc/systemd/system/pivilion-hostapd.service
Normal file
11
etc/systemd/system/pivilion-hostapd.service
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[Unit]
|
||||
Description=Pivilion legacy Wi-Fi access point
|
||||
After=network-pre.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/sbin/hostapd /run/pivilion/hostapd.conf
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -7,7 +7,7 @@ Wants=network-pre.target
|
|||
Type=oneshot
|
||||
ExecStart=/usr/local/lib/pivilion/mode.sh
|
||||
RemainAfterExit=yes
|
||||
TimeoutStartSec=90
|
||||
TimeoutStartSec=120
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
|||
266
install.sh
266
install.sh
|
|
@ -1,53 +1,183 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
set -Eeuo pipefail
|
||||
|
||||
INSTALL_VERSION=2
|
||||
ACTION=install
|
||||
case "${1:-}" in
|
||||
'') ;;
|
||||
--check) ACTION=check ;;
|
||||
--dry-run) ACTION=dry-run ;;
|
||||
--repair) ACTION=repair ;;
|
||||
-h|--help) echo "Usage: sudo ./install.sh [--check|--dry-run|--repair]"; exit 0 ;;
|
||||
*) echo "Usage: sudo ./install.sh [--check|--dry-run|--repair]" >&2; exit 2 ;;
|
||||
esac
|
||||
[ "$#" -le 1 ] || { echo "Only one installer action may be specified." >&2; exit 2; }
|
||||
|
||||
SOURCE_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
source "$SOURCE_DIR/usr/local/lib/pivilion/common.sh"
|
||||
pivilion_load_config
|
||||
pivilion_print_motd "Pivilion installer"
|
||||
pivilion_load_config || exit "$PIVILION_E_CONFIG"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Run this installer with sudo: sudo ./install.sh" >&2
|
||||
exit 1
|
||||
cat <<'PIVILION_LOGO'
|
||||
|
||||
Welcome to Pivilion!
|
||||
|
||||
@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@, &@& *@@@@@@
|
||||
@@@@@@@. .@@@@@@@
|
||||
@@@@ .@@@. @@@@
|
||||
@ &@@@@.@@@@# @
|
||||
@ @ . #@@@# . @ @
|
||||
@ @@@, # ,@@@ @
|
||||
@@, @ ,@@
|
||||
@@@@@#@@@@@@@#@@@@@
|
||||
|
||||
PIVILION_LOGO
|
||||
|
||||
if [ "$ACTION" != dry-run ] && [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Run this installer with sudo: sudo ./install.sh ${1:-}" >&2
|
||||
exit "$PIVILION_E_INSTALL"
|
||||
fi
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
BACKUP_DIR=""
|
||||
TRANSACTION_ACTIVE=0
|
||||
declare -a MANAGED_PATHS=(
|
||||
/etc/pivilion.defaults /etc/pivilion.php /etc/profile.d/pivilion-motd.sh
|
||||
/etc/apache2/ports.conf /etc/apache2/conf-available/pivilion.conf
|
||||
/etc/apache2/sites-available/000-default.conf /etc/apache2/sites-available/000-upload.conf
|
||||
/etc/apache2/conf-enabled/pivilion.conf /etc/apache2/sites-enabled/000-default.conf
|
||||
/etc/tor/torrc /etc/tor/torrc.d/pivilion.conf
|
||||
/etc/systemd/system/pivilion-mode.service /etc/systemd/system/pivilion-dnsmasq.service
|
||||
/etc/systemd/system/pivilion-hostapd.service /etc/systemd/system/pivilion-boot-greeter.service
|
||||
/usr/local/lib/pivilion /usr/local/bin/pivilion /usr/local/bin/pivilion-diagnose
|
||||
/usr/local/bin/hotspot /usr/local/bin/onion /usr/local/bin/pikey /usr/local/bin/generator
|
||||
/usr/local/bin/static /usr/local/bin/hotglue /usr/local/bin/htaccess
|
||||
/var/lib/pivilion/install-state
|
||||
/etc/php/7.4/apache2/conf.d/99-pivilion.ini
|
||||
/etc/php/8.2/apache2/conf.d/99-pivilion.ini
|
||||
/etc/php/8.4/apache2/conf.d/99-pivilion.ini
|
||||
)
|
||||
|
||||
log() { printf '==> %s\n' "$*"; }
|
||||
warn() { printf 'WARNING: %s\n' "$*" >&2; }
|
||||
|
||||
preflight() {
|
||||
local codename free_kb iface package
|
||||
log "Running preflight checks"
|
||||
[ -r /etc/os-release ] || { echo "Cannot identify the operating system." >&2; return "$PIVILION_E_INSTALL"; }
|
||||
. /etc/os-release
|
||||
codename=${VERSION_CODENAME:-unknown}
|
||||
case "$codename" in bullseye|bookworm|trixie) ;; *) echo "Unsupported OS codename: $codename" >&2; return "$PIVILION_E_INSTALL" ;; esac
|
||||
getent passwd "$PIVILION_USER" >/dev/null || return "$PIVILION_E_CONFIG"
|
||||
[ -d "$PIVILION_HOME" ] || { echo "Configured home does not exist: $PIVILION_HOME" >&2; return "$PIVILION_E_CONFIG"; }
|
||||
free_kb=$(df -Pk / | awk 'NR == 2 {print $4}')
|
||||
[ "${free_kb:-0}" -ge 524288 ] || { echo "At least 512 MiB free space is required." >&2; return "$PIVILION_E_INSTALL"; }
|
||||
iface=$(pivilion_wifi_interface)
|
||||
[ -n "$iface" ] || { echo "No Wi-Fi interface found." >&2; return "$PIVILION_E_HARDWARE"; }
|
||||
[ -d "/sys/class/net/$iface" ] || { echo "Configured Wi-Fi interface does not exist: $iface" >&2; return "$PIVILION_E_HARDWARE"; }
|
||||
if command -v iw >/dev/null 2>&1; then
|
||||
local phy
|
||||
phy=$(iw dev "$iface" info 2>/dev/null | awk '$1 == "wiphy" {print "phy" $2; exit}')
|
||||
[ -z "$phy" ] || iw phy "$phy" info 2>/dev/null | grep -Eq '^[[:space:]]*\* AP$' || warn "$iface does not advertise AP mode; hotspot activation will fail safely."
|
||||
fi
|
||||
if ss -H -ltn 'sport = :80' 2>/dev/null | grep -q .; then warn "Port 80 is currently occupied; this is expected only when Apache is already installed."; fi
|
||||
if ss -H -lntu 'sport = :53' 2>/dev/null | grep -Eq '(0\.0\.0\.0|\*):53'; then warn "A wildcard DNS listener may conflict with captive mode."; fi
|
||||
if command -v apt-cache >/dev/null 2>&1; then
|
||||
for package in apache2 php tor dnsmasq hostapd nftables network-manager; do
|
||||
apt-cache show "$package" >/dev/null 2>&1 || warn "Package metadata is unavailable for $package; apt-get update may be required."
|
||||
done
|
||||
fi
|
||||
log "Preflight passed for $codename using interface $iface"
|
||||
}
|
||||
|
||||
is_networkmanager_system() {
|
||||
command -v nmcli >/dev/null 2>&1 && {
|
||||
systemctl is-enabled --quiet NetworkManager 2>/dev/null ||
|
||||
systemctl is-active --quiet NetworkManager 2>/dev/null
|
||||
}
|
||||
command -v nmcli >/dev/null 2>&1 && { systemctl is-enabled --quiet NetworkManager 2>/dev/null || systemctl is-active --quiet NetworkManager 2>/dev/null; }
|
||||
}
|
||||
|
||||
install_packages() {
|
||||
local packages=(apache2 libapache2-mod-php php php-cli tor dnsmasq dnsutils nftables iproute2 iw rfkill curl wget unzip wpasupplicant)
|
||||
local packages=(apache2 libapache2-mod-php php php-cli tor dnsmasq dnsutils hostapd nftables iproute2 iw rfkill curl wget unzip wpasupplicant)
|
||||
log "Installing required packages"
|
||||
apt-get update
|
||||
apt-get install -y "${packages[@]}"
|
||||
if is_networkmanager_system; then
|
||||
echo "Using the installed NetworkManager backend."
|
||||
log "Using NetworkManager"
|
||||
elif command -v dhcpcd >/dev/null 2>&1 || dpkg-query -W dhcpcd5 >/dev/null 2>&1; then
|
||||
apt-get install -y hostapd dhcpcd5
|
||||
echo "Using the installed dhcpcd/hostapd backend."
|
||||
apt-get install -y dhcpcd5
|
||||
log "Using dhcpcd with dedicated Pivilion hostapd"
|
||||
else
|
||||
apt-get install -y network-manager
|
||||
systemctl enable NetworkManager
|
||||
echo "No active networking backend was found; installed NetworkManager."
|
||||
log "Installed NetworkManager because no supported backend was active"
|
||||
fi
|
||||
}
|
||||
|
||||
backup_managed_files() {
|
||||
local path relative
|
||||
install -d -m 0700 /var/backups/pivilion
|
||||
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-mode.service pivilion-boot-greeter.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
|
||||
for path in "${MANAGED_PATHS[@]}"; do
|
||||
relative=${path#/}
|
||||
if [ -e "$path" ] || [ -L "$path" ]; then
|
||||
printf '%s\n' "$path" >> "$BACKUP_DIR/present"
|
||||
install -d -m 0700 "$BACKUP_DIR/files/$(dirname "$relative")"
|
||||
cp -a "$path" "$BACKUP_DIR/files/$relative"
|
||||
else
|
||||
printf '%s\n' "$path" >> "$BACKUP_DIR/missing"
|
||||
fi
|
||||
done
|
||||
TRANSACTION_ACTIVE=1
|
||||
log "Configuration backup created at $BACKUP_DIR"
|
||||
}
|
||||
|
||||
rollback() {
|
||||
local rc=$? path relative service
|
||||
trap - ERR INT TERM
|
||||
[ "$TRANSACTION_ACTIVE" -eq 1 ] || exit "$rc"
|
||||
warn "Installation failed; restoring managed configuration from $BACKUP_DIR"
|
||||
while IFS= read -r path; do [ -n "$path" ] && rm -rf -- "$path"; done < "$BACKUP_DIR/missing"
|
||||
while IFS= read -r path; do
|
||||
[ -n "$path" ] || continue
|
||||
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-mode.service pivilion-boot-greeter.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-mode.service pivilion-boot-greeter.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"
|
||||
apache2ctl configtest >/dev/null 2>&1 && systemctl reload apache2 2>/dev/null || true
|
||||
warn "Rollback complete. Package installations were retained."
|
||||
exit "$rc"
|
||||
}
|
||||
trap rollback ERR INT TERM
|
||||
|
||||
atomic_from_stdin() {
|
||||
local destination=$1 mode=${2:-0644} tmp
|
||||
install -d -m 0755 "$(dirname "$destination")"
|
||||
tmp=$(mktemp "$(dirname "$destination")/.pivilion.XXXXXX")
|
||||
cat > "$tmp"; chmod "$mode" "$tmp"; mv -f "$tmp" "$destination"
|
||||
}
|
||||
|
||||
deploy_files() {
|
||||
local php_version
|
||||
install -d -m 0755 /usr/local/lib/pivilion /usr/local/bin /etc/systemd/system /etc/tor/torrc.d /etc/profile.d
|
||||
log "Deploying Pivilion files"
|
||||
install -d -m 0755 /usr/local/lib/pivilion /usr/local/bin /etc/systemd/system /etc/tor/torrc.d /etc/profile.d /var/lib/pivilion
|
||||
install -m 0644 "$SOURCE_DIR/usr/local/lib/pivilion/common.sh" /usr/local/lib/pivilion/common.sh
|
||||
install -m 0755 "$SOURCE_DIR/usr/local/lib/pivilion/mode.sh" /usr/local/lib/pivilion/mode.sh
|
||||
install -m 0755 "$SOURCE_DIR/usr/local/lib/pivilion/mode.sh" "$SOURCE_DIR/usr/local/lib/pivilion/diagnose.sh" \
|
||||
"$SOURCE_DIR/usr/local/lib/pivilion/boot-greeter.sh" /usr/local/lib/pivilion/
|
||||
install -m 0755 "$SOURCE_DIR"/usr/local/bin/* /usr/local/bin/
|
||||
install -m 0644 "$SOURCE_DIR/etc/systemd/system/pivilion-mode.service" /etc/systemd/system/pivilion-mode.service
|
||||
install -m 0644 "$SOURCE_DIR/etc/systemd/system/pivilion-dnsmasq.service" /etc/systemd/system/pivilion-dnsmasq.service
|
||||
install -m 0644 "$SOURCE_DIR"/etc/systemd/system/*.service /etc/systemd/system/
|
||||
install -m 0644 "$SOURCE_DIR/etc/tor/torrc.d/pivilion.conf" /etc/tor/torrc.d/pivilion.conf
|
||||
install -m 0644 "$SOURCE_DIR/etc/profile.d/pivilion-motd.sh" /etc/profile.d/pivilion-motd.sh
|
||||
|
||||
cat > /etc/pivilion.defaults <<EOF
|
||||
atomic_from_stdin /etc/pivilion.defaults <<EOF
|
||||
PIVILION_USER=$PIVILION_USER
|
||||
PIVILION_HOME=$PIVILION_HOME
|
||||
PIVILION_DIR=$PIVILION_DIR
|
||||
|
|
@ -56,80 +186,104 @@ PIVILION_MODE=$PIVILION_MODE
|
|||
PIVILION_HOTSPOT_SSID=$PIVILION_HOTSPOT_SSID
|
||||
PIVILION_WIFI_INTERFACE=$PIVILION_WIFI_INTERFACE
|
||||
EOF
|
||||
chmod 0644 /etc/pivilion.defaults
|
||||
|
||||
install -d -m 0755 -o "$PIVILION_USER" -g "$PIVILION_GROUP" "$PIVILION_DIR" "$PIVILION_DIR/config"
|
||||
cp -a "$SOURCE_DIR/home/pi/pivilion/config/." "$PIVILION_DIR/config/"
|
||||
chown -R "$PIVILION_USER:$PIVILION_GROUP" "$PIVILION_DIR/config"
|
||||
install -d -m 0755 -o "$PIVILION_USER" -g "$PIVILION_GROUP" \
|
||||
"$PIVILION_DIR/static" "$PIVILION_DIR/hotglue" "$PIVILION_DIR/hotglue/backup"
|
||||
install -d -m 0755 -o "$PIVILION_USER" -g "$PIVILION_GROUP" "$PIVILION_DIR/static" "$PIVILION_DIR/hotglue" "$PIVILION_DIR/hotglue/backup"
|
||||
[ -s "$PIVILION_DIR/mode" ] || pivilion_set_mode "$PIVILION_MODE"
|
||||
[ -e "$PIVILION_DIR/torname" ] || install -m 0644 -o "$PIVILION_USER" -g "$PIVILION_GROUP" /dev/null "$PIVILION_DIR/torname"
|
||||
|
||||
install -d -m 2775 -o www-data -g www-data "$PIVILION_WEBROOT" "$PIVILION_WEBROOT/gen"
|
||||
cp -a "$SOURCE_DIR/var/www/html/pivilion/scripts" "$PIVILION_WEBROOT/"
|
||||
cp -a "$SOURCE_DIR/var/www/html/pivilion/images" "$PIVILION_WEBROOT/"
|
||||
cp -a "$SOURCE_DIR/var/www/html/pivilion/skeleton" "$PIVILION_WEBROOT/"
|
||||
cp -a "$SOURCE_DIR/var/www/html/pivilion/scripts" "$SOURCE_DIR/var/www/html/pivilion/images" "$SOURCE_DIR/var/www/html/pivilion/skeleton" "$PIVILION_WEBROOT/"
|
||||
install -m 0664 -o www-data -g www-data "$SOURCE_DIR/var/www/html/pivilion/gen.php" "$PIVILION_WEBROOT/gen.php"
|
||||
install -m 0644 -o www-data -g www-data "$SOURCE_DIR/var/www/html/pivilion/index.html" "$PIVILION_WEBROOT/index.html"
|
||||
install -m 0644 -o www-data -g www-data "$SOURCE_DIR/var/www/html/pivilion/pi-logo_128.png" "$PIVILION_WEBROOT/pi-logo_128.png"
|
||||
if [ ! -e "$PIVILION_WEBROOT/gen/index.php" ]; then
|
||||
cp -a "$SOURCE_DIR/var/www/html/pivilion/gen/." "$PIVILION_WEBROOT/gen/"
|
||||
fi
|
||||
chown -R www-data:www-data "$PIVILION_WEBROOT/scripts" "$PIVILION_WEBROOT/images" "$PIVILION_WEBROOT/skeleton"
|
||||
chown -R www-data:www-data "$PIVILION_WEBROOT/gen"
|
||||
find "$PIVILION_WEBROOT" -type d -exec chmod 2775 {} +
|
||||
find "$PIVILION_WEBROOT" -type f -exec chmod 0664 {} +
|
||||
install -m 0644 -o www-data -g www-data "$SOURCE_DIR/var/www/html/pivilion/index.html" "$SOURCE_DIR/var/www/html/pivilion/pi-logo_128.png" "$PIVILION_WEBROOT/"
|
||||
if [ ! -e "$PIVILION_WEBROOT/gen/index.php" ]; then cp -a "$SOURCE_DIR/var/www/html/pivilion/gen/." "$PIVILION_WEBROOT/gen/"; fi
|
||||
chown -R www-data:www-data "$PIVILION_WEBROOT/scripts" "$PIVILION_WEBROOT/images" "$PIVILION_WEBROOT/skeleton" "$PIVILION_WEBROOT/gen"
|
||||
|
||||
cat > /etc/pivilion.php <<EOF
|
||||
atomic_from_stdin /etc/pivilion.php <<EOF
|
||||
<?php
|
||||
define('PIVILION_DIR', ${PIVILION_DIR@Q});
|
||||
define('PIVILION_WEBROOT', ${PIVILION_WEBROOT@Q});
|
||||
EOF
|
||||
chmod 0644 /etc/pivilion.php
|
||||
|
||||
php_version=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')
|
||||
install -d -m 0755 "/etc/php/$php_version/apache2/conf.d"
|
||||
install -m 0644 "$SOURCE_DIR/etc/php/pivilion.ini" "/etc/php/$php_version/apache2/conf.d/99-pivilion.ini"
|
||||
}
|
||||
|
||||
configure_services() {
|
||||
sed "s|/var/www/html/pivilion|$PIVILION_WEBROOT|g" "$SOURCE_DIR/etc/apache2/sites-available/000-default.conf" > /etc/apache2/sites-available/000-default.conf
|
||||
sed "s|/var/www/html/pivilion|$PIVILION_WEBROOT|g" "$SOURCE_DIR/etc/apache2/sites-available/000-upload.conf" > /etc/apache2/sites-available/000-upload.conf
|
||||
local tmp
|
||||
log "Configuring services"
|
||||
tmp=$(mktemp); sed "s|/var/www/html/pivilion|$PIVILION_WEBROOT|g" "$SOURCE_DIR/etc/apache2/sites-available/000-default.conf" > "$tmp"; install -m 0644 "$tmp" /etc/apache2/sites-available/000-default.conf; rm -f "$tmp"
|
||||
tmp=$(mktemp); sed "s|/var/www/html/pivilion|$PIVILION_WEBROOT|g" "$SOURCE_DIR/etc/apache2/sites-available/000-upload.conf" > "$tmp"; install -m 0644 "$tmp" /etc/apache2/sites-available/000-upload.conf; rm -f "$tmp"
|
||||
install -m 0644 "$SOURCE_DIR/etc/apache2/ports.conf" /etc/apache2/ports.conf
|
||||
cat > /etc/apache2/conf-available/pivilion.conf <<EOF
|
||||
atomic_from_stdin /etc/apache2/conf-available/pivilion.conf <<EOF
|
||||
<Directory "$PIVILION_WEBROOT">
|
||||
Options Indexes FollowSymLinks
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
EOF
|
||||
a2enmod rewrite
|
||||
a2enconf pivilion
|
||||
a2ensite 000-default.conf
|
||||
|
||||
a2enmod rewrite; a2enconf pivilion; a2ensite 000-default.conf
|
||||
if ! grep -Fqx '%include /etc/tor/torrc.d/*.conf' /etc/tor/torrc; then
|
||||
printf '\n%%include /etc/tor/torrc.d/*.conf\n' >> /etc/tor/torrc
|
||||
tmp=$(mktemp); cp /etc/tor/torrc "$tmp"; printf '\n%%include /etc/tor/torrc.d/*.conf\n' >> "$tmp"; install -m 0644 "$tmp" /etc/tor/torrc; rm -f "$tmp"
|
||||
fi
|
||||
systemctl daemon-reload
|
||||
systemctl enable apache2 pivilion-mode.service
|
||||
systemctl enable apache2 pivilion-mode.service pivilion-boot-greeter.service
|
||||
systemctl enable tor@default.service 2>/dev/null || systemctl enable tor.service
|
||||
systemctl disable --now dnsmasq 2>/dev/null || true
|
||||
systemctl disable pivilion-dnsmasq.service 2>/dev/null || true
|
||||
systemctl disable pivilion-dnsmasq.service pivilion-hostapd.service 2>/dev/null || true
|
||||
}
|
||||
|
||||
validate_install() {
|
||||
apache2ctl configtest
|
||||
tor --verify-config -f /etc/tor/torrc
|
||||
dnsmasq --test
|
||||
bash -n /usr/local/lib/pivilion/common.sh /usr/local/lib/pivilion/mode.sh /usr/local/bin/pivilion \
|
||||
/usr/local/bin/hotspot /usr/local/bin/onion /usr/local/bin/pikey /usr/local/bin/generator \
|
||||
/usr/local/bin/static /usr/local/bin/hotglue /usr/local/bin/htaccess /usr/local/bin/pivilion-diagnose
|
||||
local failed=0
|
||||
log "Validating installation"
|
||||
bash -n /usr/local/lib/pivilion/*.sh /usr/local/bin/pivilion /usr/local/bin/pivilion-diagnose /usr/local/bin/hotspot /usr/local/bin/onion /usr/local/bin/pikey /usr/local/bin/generator /usr/local/bin/static /usr/local/bin/hotglue /usr/local/bin/htaccess || failed=1
|
||||
apache2ctl configtest || failed=1
|
||||
tor --verify-config -f /etc/tor/torrc || failed=1
|
||||
dnsmasq --test || failed=1
|
||||
php -l "$PIVILION_WEBROOT/gen.php" || failed=1
|
||||
systemd-analyze verify /etc/systemd/system/pivilion-*.service || failed=1
|
||||
if command -v shellcheck >/dev/null 2>&1; then shellcheck /usr/local/lib/pivilion/*.sh /usr/local/bin/pivilion* /usr/local/bin/hotspot /usr/local/bin/onion /usr/local/bin/pikey || failed=1; fi
|
||||
[ "$failed" -eq 0 ] || return "$PIVILION_E_INSTALL"
|
||||
}
|
||||
|
||||
write_install_state() {
|
||||
atomic_from_stdin /var/lib/pivilion/install-state <<EOF
|
||||
version=$INSTALL_VERSION
|
||||
installed_at=$(date --iso-8601=seconds)
|
||||
user=$PIVILION_USER
|
||||
webroot=$PIVILION_WEBROOT
|
||||
backup=$BACKUP_DIR
|
||||
EOF
|
||||
}
|
||||
|
||||
check_install() {
|
||||
local failed=0 file
|
||||
for file in /etc/pivilion.defaults /var/lib/pivilion/install-state /usr/local/lib/pivilion/common.sh /usr/local/lib/pivilion/mode.sh /usr/local/lib/pivilion/diagnose.sh /etc/systemd/system/pivilion-mode.service; do
|
||||
if [ ! -e "$file" ]; then echo "Missing: $file" >&2; failed=1; fi
|
||||
done
|
||||
if [ "$failed" -eq 0 ]; then validate_install || failed=1; fi
|
||||
if [ "$failed" -eq 0 ]; then log "Installed files and service configuration are valid"; return 0; fi
|
||||
echo "Installation check failed. Run sudo ./install.sh --repair" >&2
|
||||
return "$PIVILION_E_INSTALL"
|
||||
}
|
||||
|
||||
preflight
|
||||
case "$ACTION" in
|
||||
dry-run)
|
||||
log "Dry run complete; installation would install packages, back up managed configuration, deploy files, validate, and enable services."
|
||||
exit 0 ;;
|
||||
check) check_install; exit $? ;;
|
||||
esac
|
||||
|
||||
backup_managed_files
|
||||
install_packages
|
||||
deploy_files
|
||||
configure_services
|
||||
validate_install
|
||||
write_install_state
|
||||
systemctl is-active --quiet apache2 2>/dev/null && systemctl reload apache2 || true
|
||||
TRANSACTION_ACTIVE=0
|
||||
trap - ERR INT TERM
|
||||
|
||||
echo "Pivilion installed for $PIVILION_USER. Reboot to enter $(pivilion_current_mode) mode."
|
||||
log "Pivilion $ACTION completed for $PIVILION_USER. Reboot to enter $(pivilion_current_mode) mode."
|
||||
|
|
|
|||
67
tests/run.sh
Executable file
67
tests/run.sh
Executable 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
62
tests/test-common.sh
Executable 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
|
||||
|
|
@ -4,7 +4,6 @@ source /usr/local/lib/pivilion/common.sh
|
|||
[ "$(id -u)" -eq 0 ] || exec sudo "$0" "$@"
|
||||
pivilion_load_config
|
||||
pivilion_set_mode onion
|
||||
rm -f "$PIVILION_WEBROOT/gen/.htaccess"
|
||||
echo "Pivilion will start in onion mode after the next reboot."
|
||||
if [ -s "$PIVILION_DIR/torname" ]; then
|
||||
printf 'Your onion domain is %s\n' "$(tr -d '\r\n' < "$PIVILION_DIR/torname")"
|
||||
|
|
|
|||
|
|
@ -3,11 +3,22 @@ set -euo pipefail
|
|||
source /usr/local/lib/pivilion/common.sh
|
||||
pivilion_load_config
|
||||
|
||||
case "${1:-status}" in
|
||||
status) [ "$#" -le 1 ] || { echo "Usage: pivilion [status|diagnose [--json]]" >&2; exit 2; } ;;
|
||||
diagnose) shift; exec /usr/local/lib/pivilion/diagnose.sh "$@" ;;
|
||||
*) echo "Usage: pivilion [status|diagnose [--json]]" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
pivilion_print_motd
|
||||
|
||||
failure=$(pivilion_state_value failure none)
|
||||
|
||||
cat <<EOF
|
||||
|
||||
Web root: $PIVILION_WEBROOT
|
||||
Apache: $(pivilion_service_state apache2)
|
||||
Tor: $(if systemctl cat tor@default.service >/dev/null 2>&1; then pivilion_service_state tor@default.service; else pivilion_service_state tor.service; fi)
|
||||
Last activation failure: $failure
|
||||
|
||||
Commands:
|
||||
pivilion show this status
|
||||
|
|
@ -18,5 +29,6 @@ Commands:
|
|||
static convert the current gallery to static files
|
||||
hotglue install or restore Hotglue
|
||||
htaccess reset or remove captive redirect rules
|
||||
pivilion-diagnose check captive DNS, HTTP redirects, and services
|
||||
pivilion diagnose [--json] check networking, redirects, and services
|
||||
pivilion-diagnose compatibility alias for diagnostics
|
||||
EOF
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
[ "$(id -u)" -eq 0 ] || exec sudo "$0" "$@"
|
||||
exec /usr/local/lib/pivilion/mode.sh diagnose
|
||||
exec /usr/local/lib/pivilion/diagnose.sh "$@"
|
||||
|
|
|
|||
6
usr/local/lib/pivilion/boot-greeter.sh
Executable file
6
usr/local/lib/pivilion/boot-greeter.sh
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
source /usr/local/lib/pivilion/common.sh
|
||||
pivilion_load_config
|
||||
pivilion_print_motd "Welcome to Pivilion!"
|
||||
|
|
@ -3,6 +3,18 @@
|
|||
# Shared configuration and small helpers for the Pivilion commands.
|
||||
|
||||
PIVILION_CONFIG=""
|
||||
PIVILION_STATE_DIR=${PIVILION_STATE_DIR:-/run/pivilion}
|
||||
PIVILION_STATE_FILE=${PIVILION_STATE_FILE:-$PIVILION_STATE_DIR/status.tsv}
|
||||
|
||||
# Stable command exit codes. Keep these public so scripts and monitoring can
|
||||
# distinguish configuration, hardware, service, captive portal, and Tor faults.
|
||||
PIVILION_E_CONFIG=10
|
||||
PIVILION_E_HARDWARE=11
|
||||
PIVILION_E_CONFLICT=12
|
||||
PIVILION_E_SERVICE=13
|
||||
PIVILION_E_CAPTIVE=14
|
||||
PIVILION_E_TOR=15
|
||||
PIVILION_E_INSTALL=20
|
||||
|
||||
_pivilion_trim() {
|
||||
local value=$1
|
||||
|
|
@ -12,8 +24,9 @@ _pivilion_trim() {
|
|||
}
|
||||
|
||||
_pivilion_config_value() {
|
||||
local key=$1 file line name value
|
||||
for file in /boot/firmware/pivilion.conf /boot/pivilion.conf /etc/pivilion.conf /etc/pivilion.defaults; do
|
||||
local key=$1 file line name value config_paths
|
||||
config_paths=${PIVILION_CONFIG_PATHS:-/boot/firmware/pivilion.conf /boot/pivilion.conf /etc/pivilion.conf /etc/pivilion.defaults}
|
||||
for file in $config_paths; do
|
||||
[ -r "$file" ] || continue
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
line=$(_pivilion_trim "$line")
|
||||
|
|
@ -112,18 +125,59 @@ pivilion_require_root() {
|
|||
[ "$(id -u)" -eq 0 ] || { echo "This operation must run as root (use sudo)." >&2; return 1; }
|
||||
}
|
||||
|
||||
pivilion_state_value() {
|
||||
local key=$1 fallback=${2:-unknown} value
|
||||
value=$(awk -F '\t' -v key="$key" '$1 == key {sub(/^[^\t]*\t/, ""); print; exit}' "$PIVILION_STATE_FILE" 2>/dev/null || true)
|
||||
printf '%s\n' "${value:-$fallback}"
|
||||
}
|
||||
|
||||
pivilion_write_state() {
|
||||
local active=$1 backend=$2 health=$3 failure=${4:-} tmp desired addresses
|
||||
pivilion_require_root
|
||||
install -d -m 0755 "$PIVILION_STATE_DIR"
|
||||
desired=$(pivilion_current_mode)
|
||||
addresses=$(hostname -I 2>/dev/null | xargs || true)
|
||||
failure=${failure//$'\n'/ }
|
||||
failure=${failure//$'\t'/ }
|
||||
tmp=$(mktemp "$PIVILION_STATE_DIR/status.XXXXXX")
|
||||
{
|
||||
printf 'desired_mode\t%s\n' "$desired"
|
||||
printf 'active_mode\t%s\n' "$active"
|
||||
printf 'backend\t%s\n' "$backend"
|
||||
printf 'health\t%s\n' "$health"
|
||||
printf 'activated_at\t%s\n' "$(date --iso-8601=seconds)"
|
||||
printf 'addresses\t%s\n' "${addresses:-none}"
|
||||
printf 'failure\t%s\n' "${failure:-none}"
|
||||
} > "$tmp"
|
||||
chmod 0644 "$tmp"
|
||||
mv -f "$tmp" "$PIVILION_STATE_FILE"
|
||||
}
|
||||
|
||||
pivilion_service_state() {
|
||||
local state
|
||||
state=$(systemctl is-active "$1" 2>/dev/null || true)
|
||||
printf '%s\n' "${state:-unknown}"
|
||||
}
|
||||
|
||||
pivilion_print_motd() {
|
||||
local heading=${1:-Welcome to Pivilion!}
|
||||
local uptime_text memory_free memory_total load_one load_five load_fifteen processes torname mode
|
||||
local uptime_text memory_free memory_total load_one load_five load_fifteen processes torname desired active backend health addresses
|
||||
|
||||
uptime_text=$(uptime -p 2>/dev/null || printf 'unknown')
|
||||
memory_free=$(awk '/^MemAvailable:/ {print $2; exit}' /proc/meminfo 2>/dev/null)
|
||||
memory_total=$(awk '/^MemTotal:/ {print $2; exit}' /proc/meminfo 2>/dev/null)
|
||||
read -r load_one load_five load_fifteen _ < /proc/loadavg
|
||||
processes=$(ps ax --no-headers 2>/dev/null | wc -l)
|
||||
if ! read -r load_one load_five load_fifteen _ < /proc/loadavg 2>/dev/null; then
|
||||
load_one=unknown; load_five=unknown; load_fifteen=unknown
|
||||
fi
|
||||
processes=$({ ps ax --no-headers 2>/dev/null || true; } | wc -l)
|
||||
[ "$processes" -gt 0 ] 2>/dev/null || processes=unknown
|
||||
torname=not-generated-yet
|
||||
[ ! -s "$PIVILION_DIR/torname" ] || torname=$(tr -d '\r\n' < "$PIVILION_DIR/torname")
|
||||
mode=$(pivilion_current_mode)
|
||||
desired=$(pivilion_current_mode)
|
||||
active=$(pivilion_state_value active_mode not-verified)
|
||||
backend=$(pivilion_state_value backend unknown)
|
||||
health=$(pivilion_state_value health unknown)
|
||||
addresses=$(pivilion_state_value addresses none)
|
||||
|
||||
printf '\n%s\n' "$heading"
|
||||
cat <<EOF
|
||||
|
|
@ -135,6 +189,8 @@ pivilion_print_motd() {
|
|||
@ @ . #@@@# . @ @ Running Processes..: $processes
|
||||
@ @@@, # ,@@@ @ Manual / more info.: pivilion.net
|
||||
@@, @ ,@@ Onion Address......: $torname
|
||||
@@@@@#@@@@@@@#@@@@@ Running in $mode mode
|
||||
@@@@@#@@@@@@@#@@@@@ Desired / active....: $desired / $active
|
||||
Backend / health...: $backend / $health
|
||||
IP addresses.......: $addresses
|
||||
EOF
|
||||
}
|
||||
|
|
|
|||
116
usr/local/lib/pivilion/diagnose.sh
Executable file
116
usr/local/lib/pivilion/diagnose.sh
Executable file
|
|
@ -0,0 +1,116 @@
|
|||
#!/bin/bash
|
||||
set -uo pipefail
|
||||
|
||||
source /usr/local/lib/pivilion/common.sh
|
||||
|
||||
[ "$(id -u)" -eq 0 ] || exec sudo /usr/local/lib/pivilion/diagnose.sh "$@"
|
||||
|
||||
OUTPUT=text
|
||||
QUIET=0
|
||||
MODE=""
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--json) OUTPUT=json ;;
|
||||
--quiet) QUIET=1 ;;
|
||||
--mode) shift; MODE=${1:-} ;;
|
||||
*) echo "Usage: pivilion diagnose [--json] [--mode hotspot|onion]" >&2; exit 2 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
pivilion_load_config || exit "$PIVILION_E_CONFIG"
|
||||
DESIRED=$(pivilion_current_mode)
|
||||
ACTIVE=$(pivilion_state_value active_mode not-verified)
|
||||
BACKEND=$(pivilion_state_value backend unknown)
|
||||
RECORDED_HEALTH=$(pivilion_state_value health unknown)
|
||||
ADDRESSES=$(pivilion_state_value addresses none)
|
||||
FAILURE_REASON=$(pivilion_state_value failure none)
|
||||
MODE=${MODE:-$ACTIVE}
|
||||
case "$MODE" in not-verified|none|unknown) MODE=$DESIRED ;; esac
|
||||
case "$MODE" in hotspot|onion) ;; *) MODE=$(pivilion_current_mode) ;; esac
|
||||
|
||||
declare -a CHECK_NAMES=() CHECK_VALUES=() CHECK_DETAILS=()
|
||||
FAILURES=0
|
||||
SERVICE_FAILURE=0
|
||||
MODE_FAILURE=0
|
||||
HARDWARE_FAILURE=0
|
||||
|
||||
record_check() {
|
||||
local name=$1 value=$2 detail=${3:-}
|
||||
CHECK_NAMES+=("$name")
|
||||
CHECK_VALUES+=("$value")
|
||||
CHECK_DETAILS+=("$detail")
|
||||
if [ "$value" != pass ]; then FAILURES=$((FAILURES + 1)); fi
|
||||
}
|
||||
|
||||
service_active() { systemctl is-active --quiet "$1"; }
|
||||
apache_rewrite() { apache2ctl -M 2>/dev/null | grep -q rewrite_module; }
|
||||
live_redirect() { cmp -s "$PIVILION_DIR/config/html/.htaccess" "$PIVILION_WEBROOT/gen/.htaccess"; }
|
||||
dns_answer() { [ "$(dig +short A @10.1.1.1 "$1" 2>/dev/null | tail -n1)" = 10.1.1.1 ]; }
|
||||
nft_active() { nft list table inet pivilion >/dev/null 2>&1; }
|
||||
probe_redirect() {
|
||||
local host=$1 path=$2 headers
|
||||
headers=$(curl -sS --max-time 5 -D - -o /dev/null -H "Host: $host" "http://10.1.1.1$path") || return 1
|
||||
grep -Eq '^HTTP/[0-9.]+ 302([[:space:]]|$)' <<<"$headers" &&
|
||||
grep -Eiq '^Location:[[:space:]]*http://10\.1\.1\.1/index\.php\r?$' <<<"$headers"
|
||||
}
|
||||
gallery_works() { curl -fsS --max-time 5 -o /dev/null http://10.1.1.1/index.php; }
|
||||
default_route() { ip route show default | grep -q '^default'; }
|
||||
valid_torname() {
|
||||
local hostname
|
||||
hostname=$(tr -d '\r\n' < "$PIVILION_DIR/torname" 2>/dev/null || true)
|
||||
[[ "$hostname" =~ ^[a-z2-7]{56}\.onion$ ]]
|
||||
}
|
||||
tor_active() {
|
||||
if systemctl cat tor@default.service >/dev/null 2>&1; then service_active tor@default.service; else service_active tor.service; fi
|
||||
}
|
||||
|
||||
IFACE=$(pivilion_wifi_interface)
|
||||
if [ -n "$IFACE" ] && [ -d "/sys/class/net/$IFACE" ]; then record_check wifi_interface pass "$IFACE"; else record_check wifi_interface fail missing; HARDWARE_FAILURE=1; fi
|
||||
if service_active apache2; then record_check apache pass active; else record_check apache fail inactive; SERVICE_FAILURE=1; fi
|
||||
if apache_rewrite; then record_check apache_rewrite pass enabled; else record_check apache_rewrite fail disabled; SERVICE_FAILURE=1; fi
|
||||
|
||||
if [ "$MODE" = hotspot ]; then
|
||||
if live_redirect; then record_check captive_redirect pass installed; else record_check captive_redirect fail missing_or_stale; MODE_FAILURE=1; fi
|
||||
if service_active pivilion-dnsmasq.service; then record_check dnsmasq pass active; else record_check dnsmasq fail inactive; SERVICE_FAILURE=1; fi
|
||||
if nft_active; then record_check nftables pass active; else record_check nftables fail missing; MODE_FAILURE=1; fi
|
||||
if dns_answer connectivitycheck.gstatic.com; then record_check captive_dns pass 10.1.1.1; else record_check captive_dns fail wrong_answer; MODE_FAILURE=1; fi
|
||||
if probe_redirect connectivitycheck.gstatic.com /generate_204; then record_check android_probe pass 302; else record_check android_probe fail no_redirect; MODE_FAILURE=1; fi
|
||||
if probe_redirect captive.apple.com /hotspot-detect.html; then record_check apple_probe pass 302; else record_check apple_probe fail no_redirect; MODE_FAILURE=1; fi
|
||||
if probe_redirect www.msftconnecttest.com /connecttest.txt; then record_check microsoft_probe pass 302; else record_check microsoft_probe fail no_redirect; MODE_FAILURE=1; fi
|
||||
if gallery_works; then record_check gallery pass reachable; else record_check gallery fail unreachable; MODE_FAILURE=1; fi
|
||||
else
|
||||
if default_route; then record_check upstream_route pass present; else record_check upstream_route fail missing; MODE_FAILURE=1; fi
|
||||
if tor_active; then record_check tor pass active; else record_check tor fail inactive; MODE_FAILURE=1; fi
|
||||
if valid_torname; then record_check onion_hostname pass valid_v3; else record_check onion_hostname fail missing_or_invalid; MODE_FAILURE=1; fi
|
||||
fi
|
||||
|
||||
json_escape() {
|
||||
local value=$1
|
||||
value=${value//\\/\\\\}; value=${value//\"/\\\"}; value=${value//$'\n'/\\n}; value=${value//$'\r'/\\r}; value=${value//$'\t'/\\t}
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
if [ "$OUTPUT" = json ]; then
|
||||
printf '{"desired_mode":"%s","active_mode":"%s","diagnosed_mode":"%s","backend":"%s","recorded_health":"%s","addresses":"%s","failure":"%s","healthy":%s,"checks":{' \
|
||||
"$(json_escape "$DESIRED")" "$(json_escape "$ACTIVE")" "$(json_escape "$MODE")" "$(json_escape "$BACKEND")" \
|
||||
"$(json_escape "$RECORDED_HEALTH")" "$(json_escape "$ADDRESSES")" "$(json_escape "$FAILURE_REASON")" \
|
||||
"$([ "$FAILURES" -eq 0 ] && printf true || printf false)"
|
||||
for ((i=0; i<${#CHECK_NAMES[@]}; i++)); do
|
||||
[ "$i" -eq 0 ] || printf ','
|
||||
printf '"%s":{"status":"%s","detail":"%s"}' "$(json_escape "${CHECK_NAMES[$i]}")" "${CHECK_VALUES[$i]}" "$(json_escape "${CHECK_DETAILS[$i]}")"
|
||||
done
|
||||
printf '}}\n'
|
||||
elif [ "$QUIET" -eq 0 ]; then
|
||||
printf 'Pivilion diagnostics (desired=%s, active=%s, checking=%s, backend=%s, recorded-health=%s):\n' \
|
||||
"$DESIRED" "$ACTIVE" "$MODE" "$BACKEND" "$RECORDED_HEALTH"
|
||||
for ((i=0; i<${#CHECK_NAMES[@]}; i++)); do
|
||||
printf ' [%s] %-20s %s\n' "${CHECK_VALUES[$i]}" "${CHECK_NAMES[$i]}" "${CHECK_DETAILS[$i]}"
|
||||
done
|
||||
fi
|
||||
|
||||
[ "$FAILURES" -eq 0 ] && exit 0
|
||||
[ "$HARDWARE_FAILURE" -eq 0 ] || exit "$PIVILION_E_HARDWARE"
|
||||
[ "$SERVICE_FAILURE" -eq 0 ] || exit "$PIVILION_E_SERVICE"
|
||||
[ "$MODE" != hotspot ] || exit "$PIVILION_E_CAPTIVE"
|
||||
exit "$PIVILION_E_TOR"
|
||||
|
|
@ -1,104 +1,97 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
set -Eeuo pipefail
|
||||
|
||||
source /usr/local/lib/pivilion/common.sh
|
||||
pivilion_require_root
|
||||
pivilion_load_config
|
||||
pivilion_load_config || exit "$PIVILION_E_CONFIG"
|
||||
|
||||
MODE=${1:-$(pivilion_current_mode)}
|
||||
case "$MODE" in hotspot|onion) ;; *) echo "Unknown Pivilion mode: $MODE" >&2; exit 2 ;; esac
|
||||
IFACE=$(pivilion_wifi_interface)
|
||||
[ -n "$IFACE" ] || { echo "No Wi-Fi interface found; set PIVILION_WIFI_INTERFACE" >&2; exit 1; }
|
||||
[ -n "$IFACE" ] && [ -d "/sys/class/net/$IFACE" ] || {
|
||||
pivilion_write_state none none failed "Wi-Fi interface not found"
|
||||
echo "No Wi-Fi interface found; set PIVILION_WIFI_INTERFACE" >&2
|
||||
exit "$PIVILION_E_HARDWARE"
|
||||
}
|
||||
|
||||
RUN_DIR=/run/pivilion
|
||||
DNSMASQ_CONF=/etc/dnsmasq.d/pivilion-runtime.conf
|
||||
HOSTAPD_CONF=$RUN_DIR/hostapd.conf
|
||||
NFT_CONF=$RUN_DIR/pivilion.nft
|
||||
NM_HOTSPOT=pivilion-hotspot
|
||||
CAPTIVE_TEMPLATE=$PIVILION_DIR/config/html/.htaccess
|
||||
CAPTIVE_LIVE=$PIVILION_WEBROOT/gen/.htaccess
|
||||
BACKEND=legacy
|
||||
pivilion_uses_networkmanager && BACKEND=networkmanager
|
||||
|
||||
activation_error() {
|
||||
local rc=$? line=${BASH_LINENO[0]:-unknown}
|
||||
trap - ERR
|
||||
pivilion_write_state none "$BACKEND" failed "Activation failed at line $line (exit $rc)" || true
|
||||
logger -t pivilion-mode "Failed to activate $MODE mode at line $line (exit $rc)" 2>/dev/null || true
|
||||
exit "$rc"
|
||||
}
|
||||
trap activation_error ERR
|
||||
|
||||
atomic_install() {
|
||||
local source=$1 destination=$2 owner=${3:-root} group=${4:-root} mode=${5:-0644} tmp
|
||||
install -d -m 0755 "$(dirname "$destination")"
|
||||
tmp=$(mktemp "$(dirname "$destination")/.pivilion.XXXXXX")
|
||||
if ! install -m "$mode" -o "$owner" -g "$group" "$source" "$tmp"; then rm -f "$tmp"; return 1; fi
|
||||
mv -f "$tmp" "$destination"
|
||||
}
|
||||
|
||||
install_captive_redirect() {
|
||||
local tmp
|
||||
[ -r "$CAPTIVE_TEMPLATE" ] || {
|
||||
echo "Captive redirect template is missing: $CAPTIVE_TEMPLATE" >&2
|
||||
return 1
|
||||
}
|
||||
[ -d "$PIVILION_WEBROOT/gen" ] || {
|
||||
echo "Gallery webroot is missing: $PIVILION_WEBROOT/gen" >&2
|
||||
return 1
|
||||
}
|
||||
tmp=$(mktemp "$PIVILION_WEBROOT/gen/.htaccess.XXXXXX")
|
||||
if ! install -m 0644 -o root -g www-data "$CAPTIVE_TEMPLATE" "$tmp"; then
|
||||
rm -f "$tmp"
|
||||
return 1
|
||||
fi
|
||||
mv -f "$tmp" "$CAPTIVE_LIVE"
|
||||
[ -r "$CAPTIVE_TEMPLATE" ] || { echo "Captive redirect template is missing: $CAPTIVE_TEMPLATE" >&2; return 1; }
|
||||
[ -d "$PIVILION_WEBROOT/gen" ] || { echo "Gallery webroot is missing: $PIVILION_WEBROOT/gen" >&2; return 1; }
|
||||
atomic_install "$CAPTIVE_TEMPLATE" "$CAPTIVE_LIVE" root www-data 0644
|
||||
}
|
||||
|
||||
diagnostic_result() {
|
||||
local label=$1
|
||||
shift
|
||||
if "$@"; then
|
||||
printf ' [ok] %s\n' "$label"
|
||||
return 0
|
||||
fi
|
||||
printf ' [fail] %s\n' "$label" >&2
|
||||
return 1
|
||||
validate_ap_support() {
|
||||
local phy
|
||||
command -v iw >/dev/null 2>&1 || return 1
|
||||
phy=$(iw dev "$IFACE" info 2>/dev/null | awk '$1 == "wiphy" {print "phy" $2; exit}')
|
||||
[ -n "$phy" ] && iw phy "$phy" info 2>/dev/null | grep -Eq '^[[:space:]]*\* AP$'
|
||||
}
|
||||
|
||||
probe_redirect() {
|
||||
local host=$1 path=$2 headers
|
||||
headers=$(curl --silent --show-error --max-time 5 --dump-header - --output /dev/null \
|
||||
--header "Host: $host" "http://10.1.1.1$path") || return 1
|
||||
grep -Eq '^HTTP/[0-9.]+ 302([[:space:]]|$)' <<<"$headers" &&
|
||||
grep -Eiq '^Location:[[:space:]]*http://10\.1\.1\.1/index\.php\r?$' <<<"$headers"
|
||||
}
|
||||
|
||||
dns_points_to_portal() {
|
||||
local host=$1 answer
|
||||
answer=$(dig +short A @10.1.1.1 "$host" 2>/dev/null | tail -n1)
|
||||
[ "$answer" = 10.1.1.1 ]
|
||||
}
|
||||
|
||||
apache_rewrite_enabled() {
|
||||
apache2ctl -M 2>/dev/null | grep -q 'rewrite_module'
|
||||
}
|
||||
|
||||
nft_portal_table_active() {
|
||||
nft list table inet pivilion >/dev/null 2>&1
|
||||
}
|
||||
|
||||
hotspot_diagnostics() {
|
||||
local failures=0
|
||||
echo "Pivilion hotspot diagnostics:"
|
||||
diagnostic_result "Apache is active" systemctl is-active --quiet apache2 || failures=$((failures + 1))
|
||||
diagnostic_result "Apache rewrite module is enabled" apache_rewrite_enabled || failures=$((failures + 1))
|
||||
diagnostic_result "live captive redirect matches its template" cmp -s "$CAPTIVE_TEMPLATE" "$CAPTIVE_LIVE" || failures=$((failures + 1))
|
||||
diagnostic_result "Pivilion dnsmasq is active" systemctl is-active --quiet pivilion-dnsmasq.service || failures=$((failures + 1))
|
||||
diagnostic_result "captive DNS resolves Android probe" dns_points_to_portal connectivitycheck.gstatic.com || failures=$((failures + 1))
|
||||
diagnostic_result "Pivilion nftables table is active" nft_portal_table_active || failures=$((failures + 1))
|
||||
diagnostic_result "Android probe receives portal redirect" probe_redirect connectivitycheck.gstatic.com /generate_204 || failures=$((failures + 1))
|
||||
diagnostic_result "Apple probe receives portal redirect" probe_redirect captive.apple.com /hotspot-detect.html || failures=$((failures + 1))
|
||||
diagnostic_result "Microsoft probe receives portal redirect" probe_redirect www.msftconnecttest.com /connecttest.txt || failures=$((failures + 1))
|
||||
[ "$failures" -eq 0 ] || {
|
||||
echo "$failures hotspot diagnostic check(s) failed." >&2
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
stop_hotspot() {
|
||||
systemctl stop pivilion-dnsmasq.service 2>/dev/null || true
|
||||
rm -f "$DNSMASQ_CONF"
|
||||
cleanup_hotspot() {
|
||||
systemctl stop 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 pivilion_uses_networkmanager; then
|
||||
nmcli connection down "$NM_HOTSPOT" 2>/dev/null || true
|
||||
nmcli connection delete "$NM_HOTSPOT" 2>/dev/null || true
|
||||
else
|
||||
systemctl stop hostapd 2>/dev/null || true
|
||||
ip address flush dev "$IFACE" 2>/dev/null || true
|
||||
if command -v nmcli >/dev/null 2>&1; then
|
||||
nmcli connection down "$NM_HOTSPOT" >/dev/null 2>&1 || true
|
||||
nmcli connection delete "$NM_HOTSPOT" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if ip -4 address show dev "$IFACE" | grep -q '10\.1\.1\.1/24'; then
|
||||
ip address del 10.1.1.1/24 dev "$IFACE" 2>/dev/null || true
|
||||
fi
|
||||
if systemctl is-active --quiet hostapd.service 2>/dev/null && grep -Fqx "ssid=$PIVILION_HOTSPOT_SSID" /etc/hostapd/hostapd.conf 2>/dev/null; then
|
||||
systemctl stop hostapd.service
|
||||
fi
|
||||
}
|
||||
|
||||
write_dnsmasq_config() {
|
||||
install -d -m 0755 /etc/dnsmasq.d
|
||||
cat > "$DNSMASQ_CONF" <<EOF
|
||||
check_dns_conflict() {
|
||||
local listeners
|
||||
listeners=$(ss -H -lntu 'sport = :53' 2>/dev/null || true)
|
||||
[ -z "$listeners" ] && return 0
|
||||
if grep -Eq '(0\.0\.0\.0|\*|10\.1\.1\.1):53([[:space:]]|$)' <<<"$listeners"; then
|
||||
echo "Port 53 is already occupied by a non-Pivilion service." >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
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
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
write_hotspot_configs() {
|
||||
local dns_tmp hostapd_tmp nft_tmp
|
||||
install -d -m 0755 "$RUN_DIR" /etc/dnsmasq.d
|
||||
dns_tmp=$(mktemp "$RUN_DIR/dnsmasq.XXXXXX")
|
||||
cat > "$dns_tmp" <<EOF
|
||||
interface=$IFACE
|
||||
bind-dynamic
|
||||
listen-address=10.1.1.1
|
||||
|
|
@ -107,11 +100,25 @@ dhcp-option=option:router,10.1.1.1
|
|||
dhcp-authoritative
|
||||
address=/#/10.1.1.1
|
||||
EOF
|
||||
}
|
||||
dnsmasq --test --conf-file="$dns_tmp"
|
||||
atomic_install "$dns_tmp" "$DNSMASQ_CONF"
|
||||
rm -f "$dns_tmp"
|
||||
|
||||
start_firewall() {
|
||||
install -d -m 0755 "$RUN_DIR"
|
||||
cat > "$RUN_DIR/pivilion.nft" <<EOF
|
||||
hostapd_tmp=$(mktemp "$RUN_DIR/hostapd.XXXXXX")
|
||||
cat > "$hostapd_tmp" <<EOF
|
||||
interface=$IFACE
|
||||
driver=nl80211
|
||||
ssid=$PIVILION_HOTSPOT_SSID
|
||||
hw_mode=g
|
||||
channel=6
|
||||
auth_algs=1
|
||||
wmm_enabled=0
|
||||
EOF
|
||||
atomic_install "$hostapd_tmp" "$HOSTAPD_CONF"
|
||||
rm -f "$hostapd_tmp"
|
||||
|
||||
nft_tmp=$(mktemp "$RUN_DIR/nft.XXXXXX")
|
||||
cat > "$nft_tmp" <<EOF
|
||||
table inet pivilion {
|
||||
chain prerouting {
|
||||
type nat hook prerouting priority dstnat; policy accept;
|
||||
|
|
@ -121,46 +128,24 @@ table inet pivilion {
|
|||
}
|
||||
}
|
||||
EOF
|
||||
nft delete table inet pivilion 2>/dev/null || true
|
||||
nft -f "$RUN_DIR/pivilion.nft"
|
||||
nft -c -f "$nft_tmp"
|
||||
atomic_install "$nft_tmp" "$NFT_CONF"
|
||||
rm -f "$nft_tmp"
|
||||
}
|
||||
|
||||
start_hotspot() {
|
||||
stop_hotspot
|
||||
install_captive_redirect
|
||||
rfkill unblock wifi 2>/dev/null || true
|
||||
systemctl start apache2
|
||||
if pivilion_uses_networkmanager; then
|
||||
nmcli connection add type wifi ifname "$IFACE" con-name "$NM_HOTSPOT" ssid "$PIVILION_HOTSPOT_SSID"
|
||||
nmcli connection modify "$NM_HOTSPOT" 802-11-wireless.mode ap 802-11-wireless.band bg \
|
||||
ipv4.method manual ipv4.addresses 10.1.1.1/24 ipv4.never-default yes ipv6.method disabled
|
||||
nmcli connection up "$NM_HOTSPOT"
|
||||
else
|
||||
systemctl stop "wpa_supplicant@$IFACE.service" 2>/dev/null || true
|
||||
command -v dhcpcd >/dev/null 2>&1 && dhcpcd -k "$IFACE" 2>/dev/null || true
|
||||
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
|
||||
install -d -m 0755 /etc/hostapd
|
||||
cat > /etc/hostapd/hostapd.conf <<EOF
|
||||
interface=$IFACE
|
||||
driver=nl80211
|
||||
ssid=$PIVILION_HOTSPOT_SSID
|
||||
hw_mode=g
|
||||
channel=6
|
||||
auth_algs=1
|
||||
wmm_enabled=0
|
||||
EOF
|
||||
systemctl unmask hostapd 2>/dev/null || true
|
||||
systemctl restart hostapd
|
||||
fi
|
||||
write_dnsmasq_config
|
||||
dnsmasq --test --conf-file="$DNSMASQ_CONF"
|
||||
systemctl restart pivilion-dnsmasq.service
|
||||
start_firewall
|
||||
hotspot_diagnostics
|
||||
echo "Pivilion hotspot '$PIVILION_HOTSPOT_SSID' is active on $IFACE (10.1.1.1)."
|
||||
apply_firewall() {
|
||||
nft delete table inet pivilion 2>/dev/null || true
|
||||
nft -f "$NFT_CONF"
|
||||
}
|
||||
|
||||
wait_for_default_route() {
|
||||
local i
|
||||
for i in $(seq 1 30); do ip route show default | grep -q '^default' && return 0; sleep 1; done
|
||||
return 1
|
||||
}
|
||||
|
||||
restart_tor() {
|
||||
if systemctl cat tor@default.service >/dev/null 2>&1; then systemctl restart tor@default.service; else systemctl restart tor.service; fi
|
||||
}
|
||||
|
||||
update_torname() {
|
||||
|
|
@ -168,45 +153,81 @@ update_torname() {
|
|||
for i in $(seq 1 30); do
|
||||
if [ -s "$source" ]; then
|
||||
hostname=$(tr -d '\r\n' < "$source")
|
||||
if [[ ! "$hostname" =~ ^[a-z2-7]{56}\.onion$ ]]; then
|
||||
echo "Tor produced an invalid v3 onion hostname." >&2
|
||||
return 1
|
||||
fi
|
||||
[[ "$hostname" =~ ^[a-z2-7]{56}\.onion$ ]] || return 1
|
||||
tmp=$(mktemp "$PIVILION_DIR/.torname.XXXXXX")
|
||||
printf '%s\n' "$hostname" > "$tmp"
|
||||
chown "$PIVILION_USER:$PIVILION_GROUP" "$tmp"
|
||||
chmod 0644 "$tmp"
|
||||
mv -f "$tmp" "$PIVILION_DIR/torname"
|
||||
chown "$PIVILION_USER:$PIVILION_GROUP" "$tmp"; chmod 0644 "$tmp"; mv -f "$tmp" "$PIVILION_DIR/torname"
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
echo "Tor started, but no onion hostname was generated within 30 seconds." >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
start_hotspot() {
|
||||
pivilion_write_state none "$BACKEND" activating none
|
||||
validate_ap_support || {
|
||||
pivilion_write_state none "$BACKEND" failed "$IFACE does not advertise Wi-Fi AP support"
|
||||
echo "$IFACE does not advertise Wi-Fi AP support." >&2
|
||||
exit "$PIVILION_E_HARDWARE"
|
||||
}
|
||||
cleanup_hotspot
|
||||
if ! check_hostapd_conflict; then
|
||||
pivilion_write_state none "$BACKEND" failed "System hostapd conflict"
|
||||
exit "$PIVILION_E_CONFLICT"
|
||||
fi
|
||||
if ! check_dns_conflict; then
|
||||
pivilion_write_state none "$BACKEND" failed "Port 53 conflict"
|
||||
exit "$PIVILION_E_CONFLICT"
|
||||
fi
|
||||
install_captive_redirect
|
||||
write_hotspot_configs
|
||||
rfkill unblock wifi 2>/dev/null || true
|
||||
systemctl start apache2
|
||||
if [ "$BACKEND" = networkmanager ]; then
|
||||
nmcli connection add type wifi ifname "$IFACE" con-name "$NM_HOTSPOT" ssid "$PIVILION_HOTSPOT_SSID"
|
||||
nmcli connection modify "$NM_HOTSPOT" 802-11-wireless.mode ap 802-11-wireless.band bg \
|
||||
ipv4.method manual ipv4.addresses 10.1.1.1/24 ipv4.never-default yes ipv6.method disabled connection.autoconnect no
|
||||
nmcli connection up "$NM_HOTSPOT"
|
||||
else
|
||||
command -v dhcpcd >/dev/null 2>&1 && dhcpcd -k "$IFACE" 2>/dev/null || true
|
||||
systemctl stop "wpa_supplicant@$IFACE.service" 2>/dev/null || true
|
||||
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
|
||||
apply_firewall
|
||||
if /usr/local/lib/pivilion/diagnose.sh --mode hotspot --quiet; then
|
||||
pivilion_write_state hotspot "$BACKEND" healthy none
|
||||
else
|
||||
pivilion_write_state hotspot "$BACKEND" degraded "Post-start captive portal diagnostics failed"
|
||||
logger -t pivilion-mode "Hotspot active but degraded; run pivilion diagnose" || true
|
||||
fi
|
||||
echo "Pivilion hotspot '$PIVILION_HOTSPOT_SSID' is active on $IFACE."
|
||||
}
|
||||
|
||||
start_onion() {
|
||||
stop_hotspot
|
||||
if pivilion_uses_networkmanager; then
|
||||
nmcli connection up pivilion-onion 2>/dev/null || echo "No saved Pivilion Wi-Fi connection; run pikey." >&2
|
||||
local failure=""
|
||||
pivilion_write_state none "$BACKEND" activating none
|
||||
cleanup_hotspot
|
||||
rm -f "$CAPTIVE_LIVE"
|
||||
if [ "$BACKEND" = networkmanager ]; then
|
||||
nmcli connection up pivilion-onion >/dev/null 2>&1 || failure="No saved Pivilion Wi-Fi connection; run pikey"
|
||||
else
|
||||
systemctl restart dhcpcd 2>/dev/null || true
|
||||
systemctl restart "wpa_supplicant@$IFACE.service" 2>/dev/null || systemctl restart wpa_supplicant 2>/dev/null || true
|
||||
fi
|
||||
rm -f "$CAPTIVE_LIVE"
|
||||
wait_for_default_route || failure="${failure:+$failure; }No upstream default route after 30 seconds"
|
||||
systemctl start apache2
|
||||
if systemctl cat tor@default.service >/dev/null 2>&1; then
|
||||
systemctl restart tor@default.service
|
||||
restart_tor
|
||||
update_torname || failure="${failure:+$failure; }Tor did not produce a valid hostname; retained previous value"
|
||||
if [ -z "$failure" ] && /usr/local/lib/pivilion/diagnose.sh --mode onion --quiet; then
|
||||
pivilion_write_state onion "$BACKEND" healthy none
|
||||
else
|
||||
systemctl restart tor.service
|
||||
pivilion_write_state onion "$BACKEND" degraded "${failure:-Onion diagnostics failed}"
|
||||
logger -t pivilion-mode "Onion mode active but degraded: ${failure:-diagnostics failed}" || true
|
||||
fi
|
||||
update_torname || true
|
||||
echo "Pivilion onion mode is active."
|
||||
echo "Pivilion onion mode activation completed."
|
||||
}
|
||||
|
||||
case "$MODE" in
|
||||
hotspot) start_hotspot ;;
|
||||
onion) start_onion ;;
|
||||
diagnose) hotspot_diagnostics ;;
|
||||
*) echo "Unknown Pivilion mode: $MODE" >&2; exit 2 ;;
|
||||
esac
|
||||
case "$MODE" in hotspot) start_hotspot ;; onion) start_onion ;; esac
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue