2026-06-29 09:42:14 +02:00
#!/bin/bash
2026-06-29 21:43:47 +02:00
set -Eeuo pipefail
2026-07-01 14:24:12 +02:00
INSTALL_VERSION = 5
2026-06-29 21:43:47 +02:00
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; }
2026-06-29 09:42:14 +02:00
2026-06-29 17:29:00 +02:00
SOURCE_DIR = $( cd -- " $( dirname -- " ${ BASH_SOURCE [0] } " ) " && pwd )
source " $SOURCE_DIR /usr/local/lib/pivilion/common.sh "
2026-06-30 16:29:51 +02:00
PIVILION_CONFIG_PATHS = " $( pivilion_boot_config_path) /etc/pivilion.conf /etc/pivilion.defaults "
export PIVILION_CONFIG_PATHS
2026-06-29 21:43:47 +02:00
pivilion_load_config || exit " $PIVILION_E_CONFIG "
cat <<'PIVILION_LOGO'
Welcome to Pivilion!
@@@@@@@@@@@@@@@@@@@
@@@@@@, & @& *@@@@@@
@@@@@@@. .@@@@@@@
@@@@ .@@@. @@@@
@ & @@@@.@@@@# @
@ @ . #@@@# . @ @
@ @@@, # ,@@@ @
@@, @ ,@@
@@@@@#@@@@@@@#@@@@@
2026-06-29 17:29:00 +02:00
2026-06-29 21:43:47 +02:00
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 "
2026-06-29 09:42:14 +02:00
fi
export DEBIAN_FRONTEND = noninteractive
2026-06-29 21:43:47 +02:00
BACKUP_DIR = ""
TRANSACTION_ACTIVE = 0
declare -a MANAGED_PATHS = (
2026-06-29 23:08:56 +02:00
/etc/pivilion.defaults /etc/pivilion.php /etc/profile.d/pivilion-motd.sh /etc/issue.d/90-pivilion.issue
2026-06-29 21:43:47 +02:00
/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
2026-06-30 16:29:51 +02:00
/etc/systemd/system/pivilion-config.service
2026-06-29 21:43:47 +02:00
/etc/systemd/system/pivilion-hostapd.service /etc/systemd/system/pivilion-boot-greeter.service
2026-06-29 23:08:56 +02:00
/etc/systemd/system/pivilion-login-banner.service /etc/rc.local
2026-06-29 21:43:47 +02:00
/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
2026-06-30 16:29:51 +02:00
/var/lib/pivilion/install-state /var/lib/pivilion/pivilion.conf.last-good
2026-06-29 21:43:47 +02:00
/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 "
}
2026-06-29 09:42:14 +02:00
is_networkmanager_system( ) {
2026-06-29 21:43:47 +02:00
command -v nmcli >/dev/null 2>& 1 && { systemctl is-enabled --quiet NetworkManager 2>/dev/null || systemctl is-active --quiet NetworkManager 2>/dev/null; }
2026-06-29 09:42:14 +02:00
}
install_packages( ) {
2026-06-29 21:43:47 +02:00
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"
2026-06-29 09:42:14 +02:00
apt-get update
apt-get install -y " ${ packages [@] } "
if is_networkmanager_system; then
2026-06-29 21:43:47 +02:00
log "Using NetworkManager"
2026-06-29 09:42:14 +02:00
elif command -v dhcpcd >/dev/null 2>& 1 || dpkg-query -W dhcpcd5 >/dev/null 2>& 1; then
2026-06-29 21:43:47 +02:00
apt-get install -y dhcpcd5
log "Using dhcpcd with dedicated Pivilion hostapd"
2026-06-29 09:42:14 +02:00
else
apt-get install -y network-manager
systemctl enable NetworkManager
2026-06-29 21:43:47 +02:00
log "Installed NetworkManager because no supported backend was active"
2026-06-29 09:42:14 +02:00
fi
}
2026-06-29 21:43:47 +02:00
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 "
2026-06-30 16:29:51 +02:00
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
2026-06-29 21:43:47 +02:00
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
2026-06-30 16:29:51 +02:00
MANAGED_PATHS += ( " $( pivilion_boot_config_path) " " $( pivilion_boot_error_path) " )
2026-06-29 21:43:47 +02:00
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
2026-06-30 16:29:51 +02:00
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
2026-06-29 21:43:47 +02:00
while IFS = read -r path; do [ -n " $path " ] && systemctl enable " $path " 2>/dev/null || true; done < " $BACKUP_DIR /enabled-services "
2026-06-30 16:29:51 +02:00
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
2026-06-29 21:43:47 +02:00
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 "
}
2026-06-29 09:42:14 +02:00
deploy_files( ) {
local php_version
2026-06-29 21:43:47 +02:00
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
2026-07-01 18:33:13 +02:00
install -m 0644 " $SOURCE_DIR /usr/local/lib/pivilion/common.sh " " $SOURCE_DIR /usr/local/lib/pivilion/hostname.sh " \
" $SOURCE_DIR /usr/local/lib/pivilion/networkmanager.sh " /usr/local/lib/pivilion/
2026-06-30 16:29:51 +02:00
install -m 0755 " $SOURCE_DIR /usr/local/lib/pivilion/mode.sh " " $SOURCE_DIR /usr/local/lib/pivilion/config-sync.sh " " $SOURCE_DIR /usr/local/lib/pivilion/diagnose.sh " \
2026-06-29 23:08:56 +02:00
" $SOURCE_DIR /usr/local/lib/pivilion/issue-banner.sh " /usr/local/lib/pivilion/
2026-06-29 09:42:14 +02:00
install -m 0755 " $SOURCE_DIR " /usr/local/bin/* /usr/local/bin/
2026-06-29 21:43:47 +02:00
install -m 0644 " $SOURCE_DIR " /etc/systemd/system/*.service /etc/systemd/system/
2026-06-29 09:42:14 +02:00
install -m 0644 " $SOURCE_DIR /etc/tor/torrc.d/pivilion.conf " /etc/tor/torrc.d/pivilion.conf
2026-06-29 17:29:00 +02:00
install -m 0644 " $SOURCE_DIR /etc/profile.d/pivilion-motd.sh " /etc/profile.d/pivilion-motd.sh
2026-06-29 09:42:14 +02:00
2026-06-29 21:43:47 +02:00
atomic_from_stdin /etc/pivilion.defaults <<EOF
2026-06-29 09:42:14 +02:00
PIVILION_USER = $PIVILION_USER
PIVILION_HOME = $PIVILION_HOME
PIVILION_DIR = $PIVILION_DIR
PIVILION_WEBROOT = $PIVILION_WEBROOT
PIVILION_MODE = $PIVILION_MODE
PIVILION_HOTSPOT_SSID = $PIVILION_HOTSPOT_SSID
PIVILION_WIFI_INTERFACE = $PIVILION_WIFI_INTERFACE
2026-06-30 16:29:51 +02:00
PIVILION_WIFI_SSID = $PIVILION_WIFI_SSID
PIVILION_WIFI_PSK = $PIVILION_WIFI_PSK
PIVILION_WIFI_PSK_FOR_SSID = $PIVILION_WIFI_PSK_FOR_SSID
PIVILION_WIFI_COUNTRY = $PIVILION_WIFI_COUNTRY
PIVILION_HOTSPOT_CHANNEL = $PIVILION_HOTSPOT_CHANNEL
PIVILION_HOSTNAME = $PIVILION_HOSTNAME
PIVILION_GENERATOR_ENABLED = $PIVILION_GENERATOR_ENABLED
PIVILION_UPLOAD_MAX_SIZE = $PIVILION_UPLOAD_MAX_SIZE
2026-06-29 09:42:14 +02:00
EOF
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 "
2026-06-29 21:43:47 +02:00
install -d -m 0755 -o " $PIVILION_USER " -g " $PIVILION_GROUP " " $PIVILION_DIR /static " " $PIVILION_DIR /hotglue " " $PIVILION_DIR /hotglue/backup "
2026-06-29 09:42:14 +02:00
[ -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 "
2026-06-29 21:43:47 +02:00
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 / "
2026-06-29 09:42:14 +02:00
install -m 0664 -o www-data -g www-data " $SOURCE_DIR /var/www/html/pivilion/gen.php " " $PIVILION_WEBROOT /gen.php "
2026-06-29 21:43:47 +02:00
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 "
2026-06-29 09:42:14 +02:00
2026-06-29 21:43:47 +02:00
atomic_from_stdin /etc/pivilion.php <<EOF
2026-06-29 09:42:14 +02:00
<?php
define( 'PIVILION_DIR' , ${ PIVILION_DIR @Q } ) ;
define( 'PIVILION_WEBROOT' , ${ PIVILION_WEBROOT @Q } ) ;
EOF
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 "
}
2026-06-29 23:08:56 +02:00
migrate_legacy_console_greeter( ) {
local tmp signatures = 0
systemctl disable --now pivilion-boot-greeter.service 2>/dev/null || true
rm -f /etc/systemd/system/pivilion-boot-greeter.service /usr/local/lib/pivilion/boot-greeter.sh
[ -f /etc/rc.local ] || return 0
grep -q 'hostapd -B' /etc/rc.local && signatures = $(( signatures + 1 )) || true
grep -q 'ifconfig br0' /etc/rc.local && signatures = $(( signatures + 1 )) || true
grep -q 'iptables -t nat' /etc/rc.local && signatures = $(( signatures + 1 )) || true
grep -q 'service dnsmasq start' /etc/rc.local && signatures = $(( signatures + 1 )) || true
grep -q '/usr/local/lib/pivilion/mode.sh' /etc/rc.local && signatures = 4 || true
if [ " $signatures " -ge 4 ] ; then
cp -a /etc/rc.local " $BACKUP_DIR /legacy-rc.local "
atomic_from_stdin /etc/rc.local 0755 <<'EOF'
#!/bin/sh -e
# Pivilion startup is managed by pivilion-mode.service.
exit 0
EOF
log " Migrated legacy Pivilion rc.local; original saved in $BACKUP_DIR /legacy-rc.local "
elif grep -q '/var/lib/tor/hidden_service/hostname.*home/pi/torname' /etc/rc.local; then
cp -a /etc/rc.local " $BACKUP_DIR /legacy-rc.local "
if grep -Fq '_IP=$(hostname -I)' /etc/rc.local && grep -Fq 'My IP address is' /etc/rc.local; then
atomic_from_stdin /etc/rc.local 0755 <<'EOF'
#!/bin/sh -e
# Pivilion startup and the TTY banner are managed by systemd and agetty.
exit 0
EOF
else
tmp = $( mktemp)
grep -v '/var/lib/tor/hidden_service/hostname.*home/pi/torname' /etc/rc.local > " $tmp "
install -m 0755 " $tmp " /etc/rc.local
rm -f " $tmp "
fi
log " Migrated legacy Pivilion onion rc.local; original saved in $BACKUP_DIR /legacy-rc.local "
fi
}
2026-06-29 09:42:14 +02:00
configure_services( ) {
2026-06-29 21:43:47 +02:00
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 "
2026-06-29 09:42:14 +02:00
install -m 0644 " $SOURCE_DIR /etc/apache2/ports.conf " /etc/apache2/ports.conf
2026-06-29 21:43:47 +02:00
atomic_from_stdin /etc/apache2/conf-available/pivilion.conf <<EOF
2026-06-29 09:42:14 +02:00
<Directory " $PIVILION_WEBROOT " >
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
EOF
2026-06-29 21:43:47 +02:00
a2enmod rewrite; a2enconf pivilion; a2ensite 000-default.conf
2026-06-29 09:42:14 +02:00
if ! grep -Fqx '%include /etc/tor/torrc.d/*.conf' /etc/tor/torrc; then
2026-06-29 21:43:47 +02:00
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 "
2026-06-29 09:42:14 +02:00
fi
2026-06-29 23:08:56 +02:00
migrate_legacy_console_greeter
2026-06-29 09:42:14 +02:00
systemctl daemon-reload
2026-06-30 16:29:51 +02:00
systemctl enable apache2 pivilion-config.service pivilion-mode.service pivilion-login-banner.service
2026-06-29 09:42:14 +02:00
systemctl enable tor@default.service 2>/dev/null || systemctl enable tor.service
2026-06-29 21:43:47 +02:00
systemctl disable pivilion-dnsmasq.service pivilion-hostapd.service 2>/dev/null || true
2026-06-29 23:08:56 +02:00
/usr/local/lib/pivilion/issue-banner.sh
command -v agetty >/dev/null 2>& 1 && agetty --reload >/dev/null 2>& 1 || true
2026-06-29 09:42:14 +02:00
}
validate_install( ) {
2026-06-29 21:43:47 +02:00
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
2026-06-29 23:08:56 +02:00
grep -q '^My IP address is ' /etc/issue.d/90-pivilion.issue || failed = 1
grep -q '^@@@@@@@@@@@@@@@@@@@' /etc/issue.d/90-pivilion.issue || failed = 1
grep -q 'StandardOutput=null' /etc/systemd/system/pivilion-login-banner.service || failed = 1
2026-06-30 16:29:51 +02:00
[ -s " $( pivilion_boot_config_path) " ] || failed = 1
[ -s /var/lib/pivilion/pivilion.conf.last-good ] || failed = 1
2026-06-29 21:43:47 +02:00
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 "
2026-06-29 09:42:14 +02:00
}
2026-06-29 21:43:47 +02:00
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
2026-06-29 23:08:56 +02:00
for file in /etc/pivilion.defaults /var/lib/pivilion/install-state /etc/issue.d/90-pivilion.issue \
/usr/local/lib/pivilion/common.sh /usr/local/lib/pivilion/mode.sh /usr/local/lib/pivilion/diagnose.sh \
2026-07-01 18:33:13 +02:00
/usr/local/lib/pivilion/config-sync.sh /usr/local/lib/pivilion/hostname.sh /usr/local/lib/pivilion/networkmanager.sh \
2026-06-29 23:08:56 +02:00
/usr/local/lib/pivilion/issue-banner.sh /etc/systemd/system/pivilion-mode.service \
2026-06-30 16:29:51 +02:00
/etc/systemd/system/pivilion-config.service /etc/systemd/system/pivilion-login-banner.service; do
2026-06-29 21:43:47 +02:00
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
2026-06-30 16:29:51 +02:00
PIVILION_COMMON_PATH = " $SOURCE_DIR /usr/local/lib/pivilion/common.sh " bash " $SOURCE_DIR /usr/local/lib/pivilion/config-sync.sh " initialize
2026-06-29 09:42:14 +02:00
install_packages
deploy_files
configure_services
2026-06-30 16:29:51 +02:00
/usr/local/lib/pivilion/config-sync.sh apply
2026-06-29 09:42:14 +02:00
validate_install
2026-06-29 21:43:47 +02:00
write_install_state
systemctl is-active --quiet apache2 2>/dev/null && systemctl reload apache2 || true
TRANSACTION_ACTIVE = 0
trap - ERR INT TERM
2026-06-29 09:42:14 +02:00
2026-06-29 21:43:47 +02:00
log " Pivilion $ACTION completed for $PIVILION_USER . Reboot to enter $( pivilion_current_mode) mode. "