#!/bin/bash # ReticulumHF First Boot Script # Verifies pre-installed software and activates WiFi AP using hostapd/dnsmasq # All software is pre-installed in the image - NO compilation needed # # NOTE: Do NOT use 'set -e' - we need robust error handling LOG="/var/log/reticulumhf-firstboot.log" SETUP_COMPLETE="/etc/reticulumhf/.setup_complete" # Ensure log directory exists touch "$LOG" 2>/dev/null || LOG="/tmp/reticulumhf-firstboot.log" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG" echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" } # Check if already complete if [ -f "$SETUP_COMPLETE" ]; then log "Setup already complete, skipping first-boot" exit 0 fi log "============================================" log "ReticulumHF First Boot Starting" log "============================================" mkdir -p /run/reticulumhf || log "WARN: Failed to create /run/reticulumhf" mkdir -p /etc/reticulumhf || { log "ERROR: Cannot create /etc/reticulumhf"; exit 1; } mkdir -p /etc/reticulumhf/backups || log "WARN: Failed to create backups directory" # Backup original configuration files for recovery log "Creating configuration backups..." if [ -f /etc/hostapd/hostapd.conf ]; then cp /etc/hostapd/hostapd.conf /etc/reticulumhf/backups/hostapd.conf.default 2>/dev/null log " [OK] hostapd.conf backed up" fi if [ -f /etc/dnsmasq.d/reticulumhf.conf ]; then cp /etc/dnsmasq.d/reticulumhf.conf /etc/reticulumhf/backups/dnsmasq.conf.default 2>/dev/null log " [OK] dnsmasq.conf backed up" fi if [ -f /etc/dhcpcd.conf ]; then cp /etc/dhcpcd.conf /etc/reticulumhf/backups/dhcpcd.conf.default 2>/dev/null log " [OK] dhcpcd.conf backed up" fi # Give system time to fully initialize log "Waiting for system initialization..." sleep 5 # Verify pre-installed software log "Verifying pre-installed software..." if ldconfig -p 2>/dev/null | grep -q libcodec2; then log " [OK] codec2 library found" else log " [WARN] codec2 not found" fi if [ -x /home/pi/.local/bin/rnstatus ]; then log " [OK] RNS installed" elif [ -d /home/pi/.local/pipx/venvs/rns ]; then log " [WARN] RNS venv exists but symlink missing" else log " [WARN] RNS not found" fi if [ -x /home/pi/.local/bin/freedvtnc2 ]; then log " [OK] freedvtnc2 installed" elif [ -d /home/pi/.local/pipx/venvs/freedvtnc2 ]; then log " [WARN] freedvtnc2 venv exists but symlink missing" else log " [WARN] freedvtnc2 not found" fi if command -v rigctl &> /dev/null; then log " [OK] hamlib (rigctl) installed" else log " [WARN] hamlib not found" fi log "" log "Setting up WiFi AP with hostapd..." # Unblock WiFi log "Unblocking WiFi..." rfkill unblock wlan 2>&1 || log " rfkill not needed or failed" # Wait for wlan0 log "Waiting for wlan0 interface..." WLAN_FOUND=0 for i in {1..30}; do if ip link show wlan0 &>/dev/null; then log " wlan0 found after $i seconds" WLAN_FOUND=1 break fi sleep 1 done if [ "$WLAN_FOUND" -eq 0 ]; then log " [ERROR] wlan0 not found after 30 seconds" INTERFACES=$(ip link show 2>/dev/null | grep -E '^[0-9]+:' | cut -d: -f2 | tr -d ' ') log " Available interfaces: $INTERFACES" log " [ERROR] First boot FAILED - do NOT mark as complete" log " To retry: delete /etc/reticulumhf/.setup_complete and reboot" # DO NOT mark setup_complete on failure - this prevents recovery exit 1 fi # Bring up wlan0 with static IP (dhcpcd should handle this) log "Bringing up wlan0..." ip link set wlan0 up 2>&1 || log " Could not bring up wlan0" sleep 2 # Verify static IP is assigned IP_ADDR=$(ip addr show wlan0 2>/dev/null | grep 'inet ' | awk '{print $2}') if [ -n "$IP_ADDR" ]; then log " wlan0 IP: $IP_ADDR" else log " Manually assigning 192.168.4.1/24..." ip addr add 192.168.4.1/24 dev wlan0 2>&1 || log " Could not assign IP (may already exist)" fi # Start hostapd log "Starting hostapd..." if [ -f /etc/hostapd/hostapd.conf ]; then systemctl unmask hostapd 2>/dev/null || true HOSTAPD_OUTPUT=$(systemctl start hostapd 2>&1) HOSTAPD_EXIT=$? log " hostapd start exit code: $HOSTAPD_EXIT" if [ $HOSTAPD_EXIT -ne 0 ]; then log " hostapd error: $HOSTAPD_OUTPUT" log " Trying hostapd directly..." hostapd -B /etc/hostapd/hostapd.conf 2>&1 || log " Direct hostapd also failed" fi sleep 2 else log " [ERROR] /etc/hostapd/hostapd.conf not found" fi # Check hostapd status HOSTAPD_RUNNING=0 if pgrep hostapd &>/dev/null; then log " [OK] hostapd is running" HOSTAPD_RUNNING=1 # Enable for persistence after reboot systemctl enable hostapd 2>/dev/null || true log " [OK] hostapd enabled for boot" else log " [ERROR] hostapd not running" journalctl -u hostapd --no-pager -n 10 >> "$LOG" 2>&1 fi # Start dnsmasq log "Starting dnsmasq..." if [ -f /etc/dnsmasq.d/reticulumhf.conf ]; then systemctl start dnsmasq 2>&1 DNSMASQ_EXIT=$? log " dnsmasq start exit code: $DNSMASQ_EXIT" sleep 2 else log " [ERROR] /etc/dnsmasq.d/reticulumhf.conf not found" fi # Check dnsmasq status DNSMASQ_RUNNING=0 if pgrep dnsmasq &>/dev/null; then log " [OK] dnsmasq is running" DNSMASQ_RUNNING=1 # Enable for persistence after reboot systemctl enable dnsmasq 2>/dev/null || true log " [OK] dnsmasq enabled for boot" else log " [ERROR] dnsmasq not running" journalctl -u dnsmasq --no-pager -n 10 >> "$LOG" 2>&1 fi # Configure RNS for TCP bridge log "Configuring RNS..." RNS_CONFIG_DIR="/home/pi/.reticulum" RNS_CONFIG_SRC="/opt/reticulumhf/configs/rns-config" if [ -f "$RNS_CONFIG_SRC" ]; then mkdir -p "$RNS_CONFIG_DIR" cp "$RNS_CONFIG_SRC" "$RNS_CONFIG_DIR/config" chown -R pi:pi "$RNS_CONFIG_DIR" chmod 700 "$RNS_CONFIG_DIR" chmod 600 "$RNS_CONFIG_DIR/config" log " [OK] RNS config installed" else log " [WARN] RNS config not found at $RNS_CONFIG_SRC" fi # Restart RNS daemon to load new config log "Starting RNS daemon..." systemctl enable reticulumhf-rnsd 2>/dev/null || true # Use restart (not start) to ensure config is reloaded RNSD_OUTPUT=$(systemctl restart reticulumhf-rnsd 2>&1) RNSD_EXIT=$? log " rnsd restart exit code: $RNSD_EXIT" sleep 5 RNSD_RUNNING=0 if pgrep -f "rnsd" &>/dev/null; then log " [OK] rnsd is running" RNSD_RUNNING=1 # Check if TCP port is listening if ss -tlnp 2>/dev/null | grep -q ':4242'; then log " [OK] RNS TCP server listening on port 4242" else log " [WARN] RNS TCP port 4242 not yet listening" fi else log " [ERROR] rnsd not running" journalctl -u reticulumhf-rnsd --no-pager -n 10 >> "$LOG" 2>&1 fi # Start the setup portal service log "Starting setup portal service..." systemctl enable reticulumhf-portal 2>/dev/null || true PORTAL_OUTPUT=$(systemctl start reticulumhf-portal 2>&1) PORTAL_EXIT=$? log " portal service start exit code: $PORTAL_EXIT" sleep 3 PORTAL_RUNNING=0 if systemctl is-active reticulumhf-portal &>/dev/null; then log " [OK] Setup portal service is running" PORTAL_RUNNING=1 if ss -tlnp 2>/dev/null | grep -q ':80'; then log " [OK] Setup portal listening on port 80" else log " [WARN] Nothing listening on port 80 yet" fi else log " [ERROR] Setup portal service failed" journalctl -u reticulumhf-portal --no-pager -n 10 >> "$LOG" 2>&1 fi # NOTE: Do NOT mark setup_complete here - wizard does this after radio config # The .setup_complete flag is created by /api/complete-setup in app.py log "" log "============================================" log " ReticulumHF First Boot Complete" log "============================================" log "" if [ $HOSTAPD_RUNNING -eq 1 ] && [ $DNSMASQ_RUNNING -eq 1 ]; then log " WiFi AP: ReticulumHF-Setup" log " Password: reticulumhf" log " Portal: http://192.168.4.1" else log " [ERROR] WiFi AP services not all running" log " hostapd: $HOSTAPD_RUNNING, dnsmasq: $DNSMASQ_RUNNING" fi if [ $RNSD_RUNNING -eq 1 ]; then log " RNS TCP: 192.168.4.1:4242 (for Sideband)" else log " [ERROR] RNS daemon not running" fi log "" log " Log file: $LOG" log "============================================" exit 0