#!/bin/bash # MPVJ Headless Setup Script (Final Polish & Robustness) # Usage: curl -sSL /setup.sh | sudo bash -s -- set -euo pipefail REPO_URL=${1:-} AP_ADDRESS="192.168.4.1/24" AP_GATEWAY="192.168.4.1" AP_DHCP_RANGE_START="192.168.4.20" AP_DHCP_RANGE_END="192.168.4.150" AP_CONNECTION_NAME="MPVJ-AP" set_wifi_country() { if command -v raspi-config > /dev/null 2>&1; then raspi-config nonint do_wifi_country "$WIFI_COUNTRY" || true fi mkdir -p /etc/wpa_supplicant if [ -f /etc/wpa_supplicant/wpa_supplicant.conf ]; then if grep -q '^country=' /etc/wpa_supplicant/wpa_supplicant.conf; then sed -i "s/^country=.*/country=$WIFI_COUNTRY/" /etc/wpa_supplicant/wpa_supplicant.conf else sed -i "1icountry=$WIFI_COUNTRY" /etc/wpa_supplicant/wpa_supplicant.conf fi else cat < /etc/wpa_supplicant/wpa_supplicant.conf country=$WIFI_COUNTRY ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev update_config=1 EOF fi if [ -f /etc/default/crda ]; then if grep -q '^REGDOMAIN=' /etc/default/crda; then sed -i "s/^REGDOMAIN=.*/REGDOMAIN=$WIFI_COUNTRY/" /etc/default/crda else echo "REGDOMAIN=$WIFI_COUNTRY" >> /etc/default/crda fi fi if command -v iw > /dev/null 2>&1; then iw reg set "$WIFI_COUNTRY" || true fi } configure_wlan0_static_profile() { if [ -f /etc/dhcpcd.conf ]; then sed -i '/# BEGIN MPVJ AP/,/# END MPVJ AP/d' /etc/dhcpcd.conf cat <> /etc/dhcpcd.conf # BEGIN MPVJ AP interface wlan0 static ip_address=$AP_ADDRESS nohook wpa_supplicant # END MPVJ AP EOF fi } cleanup_legacy_access_point_stack() { rm -f /etc/NetworkManager/conf.d/99-mpvj-unmanage-wlan0.conf rm -f /etc/systemd/system/mpvj-ap-mode.service systemctl disable mpvj-ap-mode.service hostapd.service dnsmasq.service 2>/dev/null || true systemctl stop mpvj-ap-mode.service hostapd.service dnsmasq.service 2>/dev/null || true if [ -f /etc/dhcpcd.conf ]; then sed -i '/# BEGIN MPVJ AP/,/# END MPVJ AP/d' /etc/dhcpcd.conf fi } configure_networkmanager_access_point() { mkdir -p /etc/systemd/system /etc/NetworkManager/conf.d cleanup_legacy_access_point_stack cat < /etc/NetworkManager/conf.d/90-mpvj-wifi-powersave.conf [connection] wifi.powersave = 2 EOF cat < /etc/sysctl.d/90-mpvj-ap.conf net.ipv4.ip_forward=1 EOF systemctl enable NetworkManager.service systemctl restart NetworkManager.service nmcli radio wifi on || true nmcli connection delete "$AP_CONNECTION_NAME" 2>/dev/null || true nmcli device set wlan0 managed yes || true nmcli device set wlan0 autoconnect yes || true nmcli device disconnect wlan0 2>/dev/null || true nmcli connection add type wifi ifname wlan0 con-name "$AP_CONNECTION_NAME" autoconnect yes ssid "$WIFI_SSID" nmcli connection modify "$AP_CONNECTION_NAME" connection.interface-name wlan0 nmcli connection modify "$AP_CONNECTION_NAME" connection.autoconnect yes connection.autoconnect-priority 100 nmcli connection modify "$AP_CONNECTION_NAME" 802-11-wireless.mode ap 802-11-wireless.band bg 802-11-wireless.powersave 2 nmcli connection modify "$AP_CONNECTION_NAME" ipv4.method shared ipv4.addresses "$AP_ADDRESS" ipv6.method disabled nmcli connection modify "$AP_CONNECTION_NAME" wifi-sec.key-mgmt wpa-psk wifi-sec.psk "$WIFI_PASS" cat < /etc/systemd/system/mpvj-nm-ap.service [Unit] Description=Bring up MPVJ NetworkManager hotspot on boot After=NetworkManager.service Wants=NetworkManager.service [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/bin/bash -lc 'nmcli radio wifi on || true; nmcli device set wlan0 managed yes || true; nmcli device set wlan0 autoconnect yes || true; nmcli connection up "$AP_CONNECTION_NAME" ifname wlan0 || (nmcli connection reload && nmcli connection up "$AP_CONNECTION_NAME" ifname wlan0)' ExecStop=/usr/bin/bash -lc 'nmcli connection down "$AP_CONNECTION_NAME" 2>/dev/null || true' [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable mpvj-nm-ap.service avahi-daemon.service systemctl restart mpvj-nm-ap.service } configure_legacy_access_point_stack() { mkdir -p /etc/hostapd /etc/systemd/system /etc/NetworkManager/conf.d cat < /etc/hostapd/hostapd.conf country_code=$WIFI_COUNTRY interface=wlan0 driver=nl80211 ssid=$WIFI_SSID hw_mode=g channel=7 ieee80211n=1 wmm_enabled=1 auth_algs=1 ignore_broadcast_ssid=0 wpa=2 wpa_passphrase=$WIFI_PASS wpa_key_mgmt=WPA-PSK rsn_pairwise=CCMP EOF cat < /etc/default/hostapd DAEMON_CONF="/etc/hostapd/hostapd.conf" EOF if [ -f /etc/dnsmasq.conf ] && [ ! -f /etc/dnsmasq.conf.mpvj-backup ]; then cp /etc/dnsmasq.conf /etc/dnsmasq.conf.mpvj-backup fi cat < /etc/dnsmasq.conf interface=wlan0 bind-interfaces listen-address=$AP_GATEWAY domain-needed bogus-priv dhcp-range=$AP_DHCP_RANGE_START,$AP_DHCP_RANGE_END,255.255.255.0,24h address=/$HOSTNAME.local/$AP_GATEWAY EOF cat <<'EOF' > /etc/systemd/system/mpvj-ap-mode.service [Unit] Description=Prepare wlan0 for MPVJ access point mode DefaultDependencies=no After=systemd-udev-settle.service Before=network-pre.target hostapd.service dnsmasq.service Wants=network-pre.target hostapd.service dnsmasq.service [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/bin/bash -c 'rfkill unblock wlan || true; systemctl stop wpa_supplicant.service wpa_supplicant@wlan0.service 2>/dev/null || true; ip link set wlan0 down || true; ip addr flush dev wlan0 || true; iw dev wlan0 set power_save off || true; ip link set wlan0 up; ip address add 192.168.4.1/24 dev wlan0' ExecStop=/usr/bin/bash -c 'systemctl stop hostapd.service dnsmasq.service 2>/dev/null || true; ip addr flush dev wlan0 || true' [Install] WantedBy=multi-user.target EOF cat < /etc/NetworkManager/conf.d/99-mpvj-unmanage-wlan0.conf [keyfile] unmanaged-devices=interface-name:wlan0 EOF cat < /etc/sysctl.d/90-mpvj-ap.conf net.ipv4.ip_forward=1 EOF configure_wlan0_static_profile systemctl daemon-reload systemctl unmask hostapd dnsmasq 2>/dev/null || true systemctl enable mpvj-ap-mode.service hostapd.service dnsmasq.service avahi-daemon.service systemctl disable wpa_supplicant.service wpa_supplicant@wlan0.service 2>/dev/null || true } configure_access_point_stack() { if command -v nmcli > /dev/null 2>&1 && systemctl list-unit-files NetworkManager.service --no-legend 2>/dev/null | grep -q '^NetworkManager\.service'; then echo "Using NetworkManager hotspot configuration for wlan0..." configure_networkmanager_access_point else echo "Using hostapd/dnsmasq hotspot configuration for wlan0..." configure_legacy_access_point_stack fi } if [ -z "$REPO_URL" ]; then echo "Usage: $0 " exit 1 fi # 0. BASH & ROOT CHECK if [ -z "$BASH_VERSION" ]; then echo "ERROR: This script must be run with BASH, not SH." echo "Please use: sudo bash setup.sh $REPO_URL" exit 1 fi if [ "$(id -u)" -ne 0 ]; then echo "ERROR: This script must be run as root (use sudo)." exit 1 fi # Detect user REAL_USER=${SUDO_USER:-$(logname)} REAL_HOME=$(eval echo "~$REAL_USER") echo "Target User: $REAL_USER ($REAL_HOME)" # 0.5 SCRUB & SANITIZE PHASE echo "Scrubbing system for a clean installation..." systemctl stop mpvj-backend.service 2>/dev/null || true pkill -9 ofxPiMapper 2>/dev/null || true pkill -9 node 2>/dev/null || true # Remove old setup artifacts swapoff /swapfile_mpvj 2>/dev/null || true rm -f /swapfile_mpvj 2>/dev/null || true # Clear any stale apt locks rm -f /var/lib/dpkg/lock-frontend /var/lib/apt/lists/lock /var/cache/apt/archives/lock 2>/dev/null || true # Ask to wipe previous data for a fresh start if [ -d "$REAL_HOME/mpvj" ] || [ -d "$REAL_HOME/openFrameworks" ]; then if whiptail --title "Cleanup" --yesno "Found existing MPVJ or OpenFrameworks data. Wipe them for a clean install?" 10 60 < /dev/tty; then echo "Wiping old directories..." rm -rf "$REAL_HOME/mpvj" "$REAL_HOME/openFrameworks" fi fi # 1. INTERROGATION PHASE echo "Interrogating user for configuration..." # 1.1 Hostname while true; do HOSTNAME=$(whiptail --inputbox "Enter Hostname (e.g. mpvj)" 8 45 "mpvj" --title "Hostname Configuration" 3>&1 1>&2 2>&3 < /dev/tty) || exit 1 HOSTNAME=${HOSTNAME:-mpvj} if echo "$HOSTNAME" | grep -qE '^[a-zA-Z0-9-]+$'; then break fi whiptail --msgbox "Error: Hostname may only contain letters, numbers, and hyphens." 8 60 < /dev/tty done # 1.2 SSID WIFI_SSID=$(whiptail --inputbox "Enter WiFi SSID (e.g. MPVJ-AP)" 8 45 "MPVJ-AP" --title "WiFi SSID Configuration" 3>&1 1>&2 2>&3 < /dev/tty) || exit 1 WIFI_SSID=${WIFI_SSID:-MPVJ-AP} # 1.3 Country Code & Password while true; do WIFI_COUNTRY=$(whiptail --inputbox "Enter 2-letter Country Code (e.g. US, GB)" 8 45 "US" --title "WiFi Country Configuration" 3>&1 1>&2 2>&3 < /dev/tty) || exit 1 WIFI_COUNTRY=$(echo "$WIFI_COUNTRY" | tr '[:lower:]' '[:upper:]') if ! echo "$WIFI_COUNTRY" | grep -qE "^[A-Z]{2}$"; then whiptail --msgbox "Error: Country Code must be 2 letters." 8 45 < /dev/tty else break fi done while true; do WIFI_PASS=$(whiptail --passwordbox "Enter WiFi Password (min 8 chars)" 8 45 --title "WiFi Password Configuration" 3>&1 1>&2 2>&3 < /dev/tty) || exit 1 if [ ${#WIFI_PASS} -lt 8 ]; then whiptail --msgbox "Error: Minimum 8 characters required." 8 45 < /dev/tty else break fi done # 2. SYSTEM STAGING PHASE echo "System Staging Phase..." # 2.1 Pre-Flight Disk Check FREE_SPACE_KB=$(df / --output=avail | tail -n1) if [ "$FREE_SPACE_KB" -lt 4194304 ]; then whiptail --msgbox "Error: 4GB free space required." 8 45 < /dev/tty exit 1 fi # 2.2 System Updates if command -v apt-get > /dev/null; then apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get upgrade -y elif command -v pacman > /dev/null; then pacman -Syu --noconfirm fi # 2.3 Install Dependencies echo "Installing Core Dependencies..." if command -v apt-get > /dev/null; then DEBIAN_FRONTEND=noninteractive apt-get install -y git hostapd dnsmasq avahi-daemon curl ffmpeg build-essential libzmq3-dev libavahi-compat-libdnssd-dev wget unzip \ libmpg123-dev libsndfile1-dev libopenal-dev libassimp-dev \ libglew-dev libglfw3-dev liburiparser-dev \ libcurl4-openssl-dev libpugixml-dev libasound2-dev \ libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ gstreamer1.0-plugins-good gstreamer1.0-plugins-bad \ gstreamer1.0-plugins-ugly gstreamer1.0-libav \ libgtk-3-dev libboost-filesystem-dev \ libfontconfig1-dev libfreetype-dev libx11-dev \ libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev \ libpulse-dev libudev-dev libfreeimage-dev librtaudio-dev \ freeglut3-dev libxxf86vm-dev elif command -v pacman > /dev/null; then pacman -S --noconfirm git hostapd dnsmasq avahi curl ffmpeg base-devel zeromq wget unzip fi # Install Node.js if missing if ! command -v node > /dev/null; then curl -fsSL https://deb.nodesource.com/setup_20.x | bash - || true command -v apt-get > /dev/null && apt-get install -y nodejs fi # 2.4 Hostname & WiFi Country hostnamectl set-hostname "$HOSTNAME" sed -i "s/127.0.1.1.*/127.0.1.1\t$HOSTNAME/g" /etc/hosts || true set_wifi_country # 2.5 OS-Agnostic Swap Increase (2GB) echo "Ensuring 2GB Swap for build..." if [ ! -f /swapfile_mpvj ]; then fallocate -l 2G /swapfile_mpvj || dd if=/dev/zero of=/swapfile_mpvj bs=1M count=2048 chmod 600 /swapfile_mpvj mkswap /swapfile_mpvj swapon /swapfile_mpvj fi # 3. NETWORKING echo "Configuring Networking..." rfkill unblock wlan || true configure_access_point_stack # 4. DEPLOYMENT echo "Deploying Application..." [ -d "$REAL_HOME/mpvj" ] && mv "$REAL_HOME/mpvj" "$REAL_HOME/mpvj.old.$(date +%s)" sudo -u "$REAL_USER" git clone "$REPO_URL" "$REAL_HOME/mpvj" mkdir -p "$REAL_HOME/media" chown -R "$REAL_USER:$REAL_USER" "$REAL_HOME/mpvj" "$REAL_HOME/media" # 4.2 Build export NODE_OPTIONS="--max-old-space-size=512" cd "$REAL_HOME/mpvj/backend" echo "Installing backend dependencies..." rm -rf node_modules package-lock.json sudo -u "$REAL_USER" npm install --omit=optional --package-lock=false cd "$REAL_HOME/mpvj/frontend" if [ ! -d "dist" ]; then echo "Installing frontend dependencies and building..." rm -rf node_modules package-lock.json sudo -u "$REAL_USER" npm install --omit=optional --package-lock=false sudo -u "$REAL_USER" npm run build else echo "Pre-built frontend detected. Skipping frontend build." fi # 4.3 Setup Systemd cat < /etc/systemd/system/mpvj-backend.service [Unit] Description=MPVJ Backend After=network.target [Service] Type=simple User=$REAL_USER WorkingDirectory=$REAL_HOME/mpvj/backend ExecStart=/usr/bin/node index.js Restart=on-failure [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable mpvj-backend.service # 4.4 .env cat < "$REAL_HOME/mpvj/backend/.env" PORT=80 MPVJ_HOSTNAME=$HOSTNAME MPVJ_SSID=$WIFI_SSID MPVJ_MEDIA_DIR=$REAL_HOME/media EOF # 4.5 THE PRE-BUILT ENGINE SOLUTION (FASTEST) echo "Installing Pre-compiled ofxPiMapper Binary..." BINARY_URL="https://gitea.superwcpot.nl/timothy/mapper/raw/branch/main/bin/ofxPiMapper" if ! curl -sSL -o /usr/local/bin/ofxPiMapper "$BINARY_URL"; then wget -O /usr/local/bin/ofxPiMapper "$BINARY_URL" fi chmod +x /usr/local/bin/ofxPiMapper # 5. FINALIZATION echo "Cleaning up..." swapoff /swapfile_mpvj 2>/dev/null || true rm /swapfile_mpvj 2>/dev/null || true whiptail --title "Success" --msgbox "INSTALL COMPLETE!\n\nRebooting into AP Mode.\nHostname: $HOSTNAME.local\nSSID: $WIFI_SSID\nPassword: $WIFI_PASS\nIP: $AP_GATEWAY" 14 60 < /dev/tty reboot