#!/bin/bash # NebuleAir Pro 4G - Comprehensive Update Script # This script performs a complete system update including git pull, # config initialization, and service management # Non-interactive version for WebUI echo "======================================" echo "NebuleAir Pro 4G - Firmware Update" echo "======================================" echo "Started at: $(date)" echo "" # Set working directory cd /var/www/nebuleair_pro_4g # Ensure this script is executable chmod +x /var/www/nebuleair_pro_4g/update_firmware.sh # Function to print status messages print_status() { echo "[$(date '+%H:%M:%S')] $1" } # Function to check command success check_status() { if [ $? -eq 0 ]; then print_status "✓ $1 completed successfully" else print_status "✗ $1 failed" return 1 fi } # Step 1: Git operations print_status "Step 1: Updating firmware from repository..." # Disable filemode to prevent permission issues git -C /var/www/nebuleair_pro_4g config core.fileMode false check_status "Git fileMode disabled" # Fetch latest changes git fetch origin check_status "Git fetch" # Show current branch print_status "Current branch: $(git branch --show-current)" # Check for local changes if [ -n "$(git status --porcelain)" ]; then print_status "Warning: Local changes detected, stashing..." git stash push -m "Auto-stash before update $(date)" check_status "Git stash" fi # Pull latest changes git pull origin $(git branch --show-current) check_status "Git pull" # Display firmware version if [ -f "/var/www/nebuleair_pro_4g/VERSION" ]; then print_status "Firmware version: $(cat /var/www/nebuleair_pro_4g/VERSION)" fi # Step 2: Update database configuration print_status "" print_status "Step 2: Updating database configuration..." /usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/set_config.py check_status "Database configuration update" # Step 3: Check and fix file permissions print_status "" print_status "Step 3: Checking file permissions..." sudo chmod +x /var/www/nebuleair_pro_4g/update_firmware.sh sudo chmod 755 /var/www/nebuleair_pro_4g/sqlite/*.py sudo chmod 755 /var/www/nebuleair_pro_4g/NPM/*.py sudo chmod 755 /var/www/nebuleair_pro_4g/BME280/*.py sudo chmod 755 /var/www/nebuleair_pro_4g/SARA/*.py sudo chmod 755 /var/www/nebuleair_pro_4g/envea/*.py sudo chmod 755 /var/www/nebuleair_pro_4g/MPPT/*.py 2>/dev/null sudo chmod 755 /var/www/nebuleair_pro_4g/wifi/*.py 2>/dev/null sudo chmod 755 /var/www/nebuleair_pro_4g/services/*.sh 2>/dev/null check_status "File permissions update" # Step 3b: Ensure Apache/PHP config allows file uploads APACHE_MAIN_CONF="/etc/apache2/apache2.conf" if grep -q '' "$APACHE_MAIN_CONF"; then if ! sed -n '//,/<\/Directory>/p' "$APACHE_MAIN_CONF" | grep -q "AllowOverride All"; then sed -i '//,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' "$APACHE_MAIN_CONF" print_status "✓ AllowOverride All enabled for Apache" APACHE_CHANGED=true fi fi PHP_INI=$(php -r "echo php_ini_loaded_file();" 2>/dev/null) if [ -n "$PHP_INI" ]; then CURRENT_UPLOAD=$(grep -oP 'upload_max_filesize = \K\d+' "$PHP_INI" 2>/dev/null) if [ -n "$CURRENT_UPLOAD" ] && [ "$CURRENT_UPLOAD" -lt 50 ]; then sed -i 's/upload_max_filesize = .*/upload_max_filesize = 50M/' "$PHP_INI" sed -i 's/post_max_size = .*/post_max_size = 55M/' "$PHP_INI" print_status "✓ PHP upload limits set to 50M" APACHE_CHANGED=true fi fi if [ "${APACHE_CHANGED:-false}" = true ]; then systemctl reload apache2 2>/dev/null print_status "✓ Apache reloaded" fi # Step 3c: Reconcile systemd services with the repo (self-heal) # Re-running setup_services.sh ensures any service that was removed, # masked, or never installed gets recreated from the canonical source. # Idempotent: rewriting an existing service file with the same content # is a no-op for already-running services. # Note: git config core.fileMode=false strips the executable bit, so we # chmod +x explicitly (same pattern as installation_part2.sh). print_status "" print_status "Step 3c: Reconciling systemd services with setup_services.sh..." SETUP_SERVICES=/var/www/nebuleair_pro_4g/services/setup_services.sh if [ -f "$SETUP_SERVICES" ]; then sudo chmod +x "$SETUP_SERVICES" sudo "$SETUP_SERVICES" check_status "Setup services reconciliation" else print_status "⚠ setup_services.sh not found, skipping" fi # Step 3d: Bootstrap Tailscale (install + fetch authkey + enroll on tailnet) # Self-heal for sensors deployed before v1.9.0 that never had Tailscale. # - Installs the tailscale binary if missing. # - Drops a sudoers rule in /etc/sudoers.d/ (safer than touching /etc/sudoers). # - Fetches the preauth key from data.nebuleair.fr if not already on disk. # HTTPS → HTTP fallback because data.nebuleair.fr may not have TLS yet. # TODO: drop the HTTP fallback once the cert is in place. # - Triggers the bootstrap script to enroll on the tailnet. print_status "" print_status "Step 3d: Bootstrap Tailscale..." # 3d.1: Install tailscale binary if absent if ! command -v tailscale >/dev/null 2>&1; then print_status "Installing Tailscale..." curl -fsSL https://tailscale.com/install.sh | sh check_status "Tailscale install" else print_status "ℹ Tailscale already installed ($(tailscale version | head -1))" fi # 3d.2: Self-heal sudoers rule (for sensors installed pre-v1.9.0) TAILSCALE_SUDOERS=/etc/sudoers.d/nebuleair-tailscale if [ ! -f "$TAILSCALE_SUDOERS" ]; then echo "www-data ALL=(ALL) NOPASSWD: /usr/bin/tailscale *" | sudo tee "$TAILSCALE_SUDOERS" > /dev/null sudo chmod 0440 "$TAILSCALE_SUDOERS" if sudo visudo -c -f "$TAILSCALE_SUDOERS" >/dev/null 2>&1; then print_status "✓ Sudoers rule for tailscale added" else sudo rm -f "$TAILSCALE_SUDOERS" print_status "⚠ Tailscale sudoers rule failed validation, removed" fi fi # 3d.3: Fetch preauth key from data.nebuleair.fr if not already present AUTHKEY_FILE=/etc/tailscale/authkey if [ ! -s "$AUTHKEY_FILE" ]; then deviceID=$(sqlite3 /var/www/nebuleair_pro_4g/sqlite/sensors.db \ "SELECT value FROM config_table WHERE key='deviceID'" 2>/dev/null) if [ -z "$deviceID" ]; then print_status "⚠ deviceID empty, cannot fetch tailscale authkey" else print_status "Fetching tailscale authkey for deviceID=$deviceID..." URL_HTTPS="https://data.nebuleair.fr/pro_4G/get_tailscale_key.php?deviceID=${deviceID}" URL_HTTP="http://data.nebuleair.fr/pro_4G/get_tailscale_key.php?deviceID=${deviceID}" authkey=$(curl -fsS --max-time 30 "$URL_HTTPS" 2>/dev/null || \ curl -fsS --max-time 30 "$URL_HTTP" 2>/dev/null || true) if [[ "$authkey" == hskey-auth-* ]]; then sudo mkdir -p /etc/tailscale echo "$authkey" | sudo tee "$AUTHKEY_FILE" > /dev/null sudo chmod 600 "$AUTHKEY_FILE" sudo chown root:root "$AUTHKEY_FILE" print_status "✓ Tailscale authkey stored at $AUTHKEY_FILE" else print_status "⚠ Could not fetch tailscale authkey (server unreachable or invalid response)" fi fi else print_status "ℹ Tailscale authkey already present" fi # 3d.4: Trigger the bootstrap (idempotent, no-op if already enrolled) BOOTSTRAP=/var/www/nebuleair_pro_4g/services/tailscale_bootstrap.sh if [ -x "$BOOTSTRAP" ]; then sudo "$BOOTSTRAP" || print_status "⚠ Tailscale bootstrap returned non-zero (see logs/tailscale_bootstrap.log)" elif [ -f "$BOOTSTRAP" ]; then sudo chmod +x "$BOOTSTRAP" sudo "$BOOTSTRAP" || print_status "⚠ Tailscale bootstrap returned non-zero (see logs/tailscale_bootstrap.log)" else print_status "⚠ Bootstrap script not found at $BOOTSTRAP" fi # Step 4: Restart critical services if they exist print_status "" print_status "Step 4: Managing system services..." # List of services to check and restart services=( "nebuleair-npm-data.timer" "nebuleair-envea-data.timer" "nebuleair-sara-data.timer" "nebuleair-bme280-data.timer" "nebuleair-mppt-data.timer" "nebuleair-noise-data.timer" "nebuleair-wifi-powersave.timer" ) for service in "${services[@]}"; do if systemctl list-unit-files | grep -q "$service"; then # Check if service is enabled before restarting if systemctl is-enabled --quiet "$service" 2>/dev/null; then print_status "Restarting enabled service: $service" sudo systemctl restart "$service" if systemctl is-active --quiet "$service"; then print_status "✓ $service is running" else print_status "⚠ $service failed to start" fi else print_status "ℹ Service $service is disabled, skipping restart" fi else print_status "ℹ Service $service not found (may not be installed)" fi done # Step 5: System health check print_status "" print_status "Step 5: System health check..." # Check disk space disk_usage=$(df / | awk 'NR==2 {print $5}' | sed 's/%//') if [ "$disk_usage" -gt 90 ]; then print_status "⚠ Warning: Disk usage is high ($disk_usage%)" else print_status "✓ Disk usage is acceptable ($disk_usage%)" fi # Check if database is accessible if [ -f "/var/www/nebuleair_pro_4g/sqlite/sensors.db" ]; then print_status "✓ Database file exists" else print_status "⚠ Warning: Database file not found" fi # Step 6: Final cleanup print_status "" print_status "Step 6: Cleaning up..." sudo find /var/www/nebuleair_pro_4g/logs -name "*.log" -size +10M -exec truncate -s 0 {} \; check_status "Log cleanup" print_status "" print_status "======================================" print_status "Update completed successfully!" print_status "======================================" exit 0