diff --git a/README.md b/README.md index 185c745..a21a3d3 100755 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ www-data ALL=(ALL) NOPASSWD: /usr/bin/git pull www-data ALL=(ALL) NOPASSWD: /usr/bin/ssh www-data ALL=(ALL) NOPASSWD: /usr/bin/python3 * www-data ALL=(ALL) NOPASSWD: /bin/systemctl * +www-data ALL=(ALL) NOPASSWD: /var/www/nebuleair_pro_4g/* ``` ## Serial diff --git a/html/admin.html b/html/admin.html index b0df191..b9dd8e9 100755 --- a/html/admin.html +++ b/html/admin.html @@ -91,7 +91,7 @@
- + @@ -165,7 +165,34 @@

Updates

- + + + +
@@ -299,14 +326,20 @@ window.onload = function() { const modem_version = document.getElementById("modem_version"); modem_version.value = response.modem_version; - //nextPM send 5 channels + + const checkbox_nmp5channels = document.getElementById("check_NPM_5channels"); - checkbox_nmp5channels.checked = response.npm_5channel; - //windMeter (as a config not a script -> it's running with a systemd service) const checkbox_wind = document.getElementById("check_WindMeter"); - checkbox_wind.checked = response["windMeter"]; - //send uSpot const checkbox_uSpot = document.getElementById("check_uSpot"); + const checkbox_bme = document.getElementById("check_bme280"); + const checkbox_envea = document.getElementById("check_envea"); + const checkbox_solar = document.getElementById("check_solarBattery"); + + checkbox_bme.checked = response["BME280"]; + checkbox_envea.checked = response["envea"]; + checkbox_solar.checked = response["MPPT"]; + checkbox_nmp5channels.checked = response.npm_5channel; + checkbox_wind.checked = response["windMeter"]; checkbox_uSpot.checked = response["send_uSpot"]; }, @@ -315,37 +348,7 @@ window.onload = function() { } });//end AJAX - //getting config_scripts table - $.ajax({ - url: 'launcher.php?type=get_config_scripts_sqlite', - dataType:'json', - //dataType: 'json', // Specify that you expect a JSON response - method: 'GET', // Use GET or POST depending on your needs - success: function(response) { - console.log("Getting SQLite config scripts table:"); - console.log(response); - - const checkbox_NPM = document.getElementById("check_NPM"); - const checkbox_bme = document.getElementById("check_bme280"); - const checkbox_envea = document.getElementById("check_envea"); - const checkbox_solar = document.getElementById("check_solarBattery"); - - checkbox_NPM.checked = response["NPM/get_data_modbus_v3.py"]; - checkbox_bme.checked = response["BME280/get_data_v2.py"]; - checkbox_envea.checked = response["envea/read_value_v2.py"]; - checkbox_solar.checked = response["MPPT/read.py"]; - - - //si sonde envea is true - if (response["envea/read_value_v2.py"]) { - add_sondeEnveaContainer(); - - } - }, - error: function(xhr, status, error) { - console.error('AJAX request failed:', status, error); - } - });//end AJAX + //OLD way to get config (JSON) @@ -516,28 +519,112 @@ function update_config(param, value){ }); } -function updateGitPull(){ - console.log("Updating device (git pull)"); - +function updateFirmware() { + console.log("Starting comprehensive firmware update..."); + + // Show loading state + const updateBtn = document.getElementById('updateBtn'); + const updateBtnText = document.getElementById('updateBtnText'); + const updateSpinner = document.getElementById('updateSpinner'); + const updateOutput = document.getElementById('updateOutput'); + const updateOutputContent = document.getElementById('updateOutputContent'); + + // Disable button and show spinner + updateBtn.disabled = true; + updateBtnText.textContent = 'Updating...'; + updateSpinner.style.display = 'inline-block'; + + // Show output console + updateOutput.style.display = 'block'; + updateOutputContent.textContent = 'Starting update process...\n'; + $.ajax({ - url: 'launcher.php?type=git_pull', - method: 'GET', // Use GET or POST depending on your needs - dataType: 'text', // Specify that you expect a JSON response - + url: 'launcher.php?type=update_firmware', + method: 'GET', + dataType: 'json', + timeout: 120000, // 2 minutes timeout + success: function(response) { - // Handle success response if needed - console.log(response); - alert(response); - // Reload the page after the device update - location.reload(); // This will reload the page - + console.log('Update completed:', response); + + // Display formatted output + if (response.success && response.output) { + // Format the output for better readability + const formattedOutput = response.output + .replace(/\[\d{2}:\d{2}:\d{2}\]/g, function(match) { + return `${match}`; + }) + .replace(/✓/g, '') + .replace(/✗/g, '') + .replace(/⚠/g, '') + .replace(/ℹ/g, ''); + + updateOutputContent.innerHTML = formattedOutput; + + // Show success toast and reload button + showToast('Update completed successfully!', 'success'); + document.getElementById('reloadBtn').style.display = 'inline-block'; + } else { + updateOutputContent.textContent = 'Update completed but no output received.'; + showToast('Update may have completed with issues', 'warning'); + } }, + error: function(xhr, status, error) { - console.error('AJAX request failed:', status, error); + console.error('Update failed:', status, error); + updateOutputContent.textContent = `Update failed: ${error}\n\nStatus: ${status}\nResponse: ${xhr.responseText || 'No response'}`; + showToast('Update failed! Check the output for details.', 'error'); + }, + + complete: function() { + // Reset button state + updateBtn.disabled = false; + updateBtnText.textContent = 'Update firmware'; + updateSpinner.style.display = 'none'; } }); } +function clearUpdateOutput() { + const updateOutput = document.getElementById('updateOutput'); + const updateOutputContent = document.getElementById('updateOutputContent'); + const reloadBtn = document.getElementById('reloadBtn'); + + updateOutputContent.textContent = ''; + updateOutput.style.display = 'none'; + reloadBtn.style.display = 'none'; +} + +function showToast(message, type) { + const toastLiveExample = document.getElementById('liveToast'); + const toastBody = toastLiveExample.querySelector('.toast-body'); + + // Set toast color based on type + toastLiveExample.classList.remove('text-bg-primary', 'text-bg-success', 'text-bg-danger', 'text-bg-warning'); + switch(type) { + case 'success': + toastLiveExample.classList.add('text-bg-success'); + break; + case 'error': + toastLiveExample.classList.add('text-bg-danger'); + break; + case 'warning': + toastLiveExample.classList.add('text-bg-warning'); + break; + default: + toastLiveExample.classList.add('text-bg-primary'); + } + + toastBody.textContent = message; + const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample); + toastBootstrap.show(); +} + +// Legacy function for backward compatibility +function updateGitPull() { + updateFirmware(); +} + function set_RTC_withNTP(){ console.log("Set RTC module with WIFI (NTP server)"); diff --git a/html/launcher.php b/html/launcher.php index 615c565..e217c47 100755 --- a/html/launcher.php +++ b/html/launcher.php @@ -349,6 +349,20 @@ if ($type == "git_pull") { echo $output; } +if ($type == "update_firmware") { + // Execute the comprehensive update script + $command = 'sudo /var/www/nebuleair_pro_4g/update_firmware.sh 2>&1'; + $output = shell_exec($command); + + // Return the output as JSON for better web display + header('Content-Type: application/json'); + echo json_encode([ + 'success' => true, + 'output' => $output, + 'timestamp' => date('Y-m-d H:i:s') + ]); +} + if ($type == "set_RTC_withNTP") { $command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py'; $output = shell_exec($command); diff --git a/installation_part1.sh b/installation_part1.sh index a07277a..6ba4aa8 100644 --- a/installation_part1.sh +++ b/installation_part1.sh @@ -100,7 +100,7 @@ fi # Add sudo authorization (prevent duplicate entries) info "Setting up sudo authorization..." if ! sudo grep -q "/usr/bin/nmcli" /etc/sudoers; then - echo -e "ALL ALL=(ALL) NOPASSWD: /usr/bin/nmcli, /usr/sbin/reboot\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/git pull\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/ssh\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/python3 * www-data ALL=(ALL) NOPASSWD: /bin/systemctl *" | sudo tee -a /etc/sudoers > /dev/null + echo -e "ALL ALL=(ALL) NOPASSWD: /usr/bin/nmcli, /usr/sbin/reboot\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/git pull\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/ssh\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/python3 * www-data ALL=(ALL) NOPASSWD: /bin/systemctl * www-data ALL=(ALL) NOPASSWD: /var/www/nebuleair_pro_4g/*" | sudo tee -a /etc/sudoers > /dev/null success "Sudo authorization added." else warning "Sudo authorization already set. Skipping." diff --git a/sqlite/set_config.py b/sqlite/set_config.py index de6f940..6a6d24b 100644 --- a/sqlite/set_config.py +++ b/sqlite/set_config.py @@ -20,10 +20,8 @@ cursor = conn.cursor() print(f"Connected to database") -# Clear existing data (if any) -cursor.execute("DELETE FROM config_table") -cursor.execute("DELETE FROM envea_sondes_table") -print("Existing data cleared") +# Note: Using INSERT OR IGNORE to add only new configurations without overwriting existing ones +print("Adding new configurations (existing ones will be preserved)") # Insert general configurations @@ -54,7 +52,7 @@ config_entries = [ for key, value, value_type in config_entries: cursor.execute( - "INSERT INTO config_table (key, value, type) VALUES (?, ?, ?)", + "INSERT OR IGNORE INTO config_table (key, value, type) VALUES (?, ?, ?)", (key, value, value_type) ) @@ -67,7 +65,7 @@ envea_sondes = [ for connected, port, name, coefficient in envea_sondes: cursor.execute( - "INSERT INTO envea_sondes_table (connected, port, name, coefficient) VALUES (?, ?, ?, ?)", + "INSERT OR IGNORE INTO envea_sondes_table (connected, port, name, coefficient) VALUES (?, ?, ?, ?)", (1 if connected else 0, port, name, coefficient) ) diff --git a/update_firmware.sh b/update_firmware.sh new file mode 100644 index 0000000..e39ba6a --- /dev/null +++ b/update_firmware.sh @@ -0,0 +1,118 @@ +#!/bin/bash + +# NebuleAir Pro 4G - Comprehensive Update Script +# This script performs a complete system update including git pull, +# config initialization, and service management + +echo "======================================" +echo "NebuleAir Pro 4G - Firmware Update" +echo "======================================" +echo "Started at: $(date)" +echo "" + +# Set working directory +cd /var/www/nebuleair_pro_4g + +# 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..." +git fetch origin +check_status "Git fetch" + +# Show current branch and any changes +print_status "Current branch: $(git branch --show-current)" +if [ -n "$(git status --porcelain)" ]; then + print_status "Warning: Local changes detected:" + git status --short +fi + +# Pull latest changes +git pull origin $(git branch --show-current) +check_status "Git pull" + +# 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 +check_status "File permissions update" + +# 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" +) + +for service in "${services[@]}"; do + if systemctl list-unit-files | grep -q "$service"; then + print_status "Restarting service: $service" + sudo systemctl restart "$service" + if systemctl is-active --quiet "$service"; then + print_status "✓ $service is running" + else + print_status "⚠ $service may not be active" + 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 "Update completed successfully!" + +exit 0 \ No newline at end of file