v1.4.0 — Mise à jour firmware hors-ligne par upload ZIP
Nouvelle fonctionnalité permettant de mettre à jour le firmware sans connexion internet, via upload d'un fichier .zip depuis l'interface admin. Fichiers ajoutés: - update_firmware_from_file.sh (rsync + exclusions + chown + restart services) - .update-exclude (liste d'exclusions évolutive, versionnée) - html/.htaccess (limite upload PHP 50MB) Fichiers modifiés: - html/launcher.php (handler upload_firmware) - html/admin.html (UI upload + barre de progression) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
26
.update-exclude
Normal file
26
.update-exclude
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# NebuleAir Pro 4G - Fichiers exclus lors de la mise à jour par upload
|
||||||
|
# Ce fichier est versionné dans le repo et voyage avec chaque release.
|
||||||
|
# Quand on ajoute un nouveau capteur avec du cache local, mettre à jour cette liste.
|
||||||
|
|
||||||
|
# Base de données (données capteur, config locale)
|
||||||
|
sqlite/sensors.db
|
||||||
|
sqlite/*.db-journal
|
||||||
|
sqlite/*.db-wal
|
||||||
|
|
||||||
|
# Logs applicatifs
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# Historique git (pour que git pull fonctionne toujours après)
|
||||||
|
.git/
|
||||||
|
|
||||||
|
# Fichiers de configuration locale
|
||||||
|
config.json
|
||||||
|
deviceID.txt
|
||||||
|
wifi_list.csv
|
||||||
|
|
||||||
|
# Données capteurs en cache
|
||||||
|
envea/data/
|
||||||
|
NPM/data/
|
||||||
|
|
||||||
|
# Verrous
|
||||||
|
*.lock
|
||||||
@@ -1,5 +1,25 @@
|
|||||||
{
|
{
|
||||||
"versions": [
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "1.4.0",
|
||||||
|
"date": "2026-03-10",
|
||||||
|
"changes": {
|
||||||
|
"features": [
|
||||||
|
"Mise a jour firmware hors-ligne par upload de fichier ZIP via l'interface web admin",
|
||||||
|
"Barre de progression pour suivre l'upload du fichier",
|
||||||
|
"Fichier .update-exclude versionne pour gerer les exclusions rsync de maniere evolutive"
|
||||||
|
],
|
||||||
|
"improvements": [
|
||||||
|
"Vidage du buffer serie avant chaque commande AT dans sara.py (evite les URCs residuelles au demarrage)"
|
||||||
|
],
|
||||||
|
"fixes": [],
|
||||||
|
"compatibility": [
|
||||||
|
"Necessite l'ajout de update_firmware_from_file.sh dans les permissions sudo de www-data",
|
||||||
|
"Necessite Apache mod_rewrite pour html/.htaccess (upload 50MB)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": "Permet la mise a jour du firmware sans connexion internet : telecharger le .zip depuis Gitea, se connecter au hotspot du capteur, et uploader via admin.html."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"date": "2026-02-17",
|
"date": "2026-02-17",
|
||||||
|
|||||||
3
html/.htaccess
Normal file
3
html/.htaccess
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
php_value upload_max_filesize 50M
|
||||||
|
php_value post_max_size 55M
|
||||||
|
php_value max_execution_time 300
|
||||||
124
html/admin.html
124
html/admin.html
@@ -244,6 +244,20 @@
|
|||||||
<span id="updateSpinner" class="spinner-border spinner-border-sm ms-2" style="display: none;"></span>
|
<span id="updateSpinner" class="spinner-border spinner-border-sm ms-2" style="display: none;"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<hr class="my-3">
|
||||||
|
<label class="form-label fw-bold">Mise à jour hors-ligne (upload)</label>
|
||||||
|
<div class="input-group mb-2">
|
||||||
|
<input type="file" class="form-control" id="firmwareFileInput" accept=".zip">
|
||||||
|
<button class="btn btn-warning" type="button" onclick="uploadFirmware()" id="uploadBtn">
|
||||||
|
<span id="uploadBtnText">Upload & Install</span>
|
||||||
|
<span id="uploadSpinner" class="spinner-border spinner-border-sm ms-2" style="display: none;"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="progress mb-2" id="uploadProgressBar" style="display: none; height: 20px;">
|
||||||
|
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%" id="uploadProgress">0%</div>
|
||||||
|
</div>
|
||||||
|
<small class="text-muted">Télécharger le .zip depuis Gitea, puis le déposer ici</small>
|
||||||
|
|
||||||
<!-- Update Output Console -->
|
<!-- Update Output Console -->
|
||||||
<div id="updateOutput" class="mt-3" style="display: none;">
|
<div id="updateOutput" class="mt-3" style="display: none;">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@@ -824,6 +838,116 @@ function updateFirmware() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function uploadFirmware() {
|
||||||
|
const fileInput = document.getElementById('firmwareFileInput');
|
||||||
|
const file = fileInput.files[0];
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
showToast('Please select a .zip file first', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate extension
|
||||||
|
if (!file.name.toLowerCase().endsWith('.zip')) {
|
||||||
|
showToast('Only .zip files are allowed', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate size (50MB)
|
||||||
|
if (file.size > 50 * 1024 * 1024) {
|
||||||
|
showToast('File too large (max 50MB)', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confirm('Install firmware from "' + file.name + '"?\nThis will update the system files and restart services.')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI elements
|
||||||
|
const uploadBtn = document.getElementById('uploadBtn');
|
||||||
|
const uploadBtnText = document.getElementById('uploadBtnText');
|
||||||
|
const uploadSpinner = document.getElementById('uploadSpinner');
|
||||||
|
const progressBar = document.getElementById('uploadProgressBar');
|
||||||
|
const progress = document.getElementById('uploadProgress');
|
||||||
|
const updateOutput = document.getElementById('updateOutput');
|
||||||
|
const updateOutputContent = document.getElementById('updateOutputContent');
|
||||||
|
|
||||||
|
// Show loading state
|
||||||
|
uploadBtn.disabled = true;
|
||||||
|
uploadBtnText.textContent = 'Uploading...';
|
||||||
|
uploadSpinner.style.display = 'inline-block';
|
||||||
|
progressBar.style.display = 'flex';
|
||||||
|
progress.style.width = '0%';
|
||||||
|
progress.textContent = '0%';
|
||||||
|
updateOutput.style.display = 'block';
|
||||||
|
updateOutputContent.textContent = 'Uploading firmware file...\n';
|
||||||
|
|
||||||
|
// Build FormData
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('firmware_file', file);
|
||||||
|
|
||||||
|
// Use XMLHttpRequest for upload progress
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.timeout = 300000; // 5 minutes
|
||||||
|
|
||||||
|
xhr.upload.addEventListener('progress', function(e) {
|
||||||
|
if (e.lengthComputable) {
|
||||||
|
const pct = Math.round((e.loaded / e.total) * 100);
|
||||||
|
progress.style.width = pct + '%';
|
||||||
|
progress.textContent = pct + '%';
|
||||||
|
if (pct >= 100) {
|
||||||
|
uploadBtnText.textContent = 'Installing...';
|
||||||
|
updateOutputContent.textContent = 'Upload complete. Installing firmware...\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.addEventListener('load', function() {
|
||||||
|
try {
|
||||||
|
const response = JSON.parse(xhr.responseText);
|
||||||
|
if (response.success && response.output) {
|
||||||
|
const formattedOutput = response.output
|
||||||
|
.replace(/✓/g, '<span style="color: #28a745;">✓</span>')
|
||||||
|
.replace(/✗/g, '<span style="color: #dc3545;">✗</span>')
|
||||||
|
.replace(/⚠/g, '<span style="color: #ffc107;">⚠</span>')
|
||||||
|
.replace(/ℹ/g, '<span style="color: #17a2b8;">ℹ</span>');
|
||||||
|
updateOutputContent.innerHTML = formattedOutput;
|
||||||
|
showToast('Firmware updated: ' + (response.old_version || '?') + ' → ' + (response.new_version || '?'), 'success');
|
||||||
|
document.getElementById('reloadBtn').style.display = 'inline-block';
|
||||||
|
} else {
|
||||||
|
updateOutputContent.textContent = 'Error: ' + (response.message || 'Unknown error');
|
||||||
|
showToast('Upload failed: ' + (response.message || 'Unknown error'), 'error');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
updateOutputContent.textContent = 'Error parsing response: ' + xhr.responseText;
|
||||||
|
showToast('Update failed: invalid server response', 'error');
|
||||||
|
}
|
||||||
|
resetUploadUI();
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.addEventListener('error', function() {
|
||||||
|
updateOutputContent.textContent = 'Network error during upload';
|
||||||
|
showToast('Upload failed: network error', 'error');
|
||||||
|
resetUploadUI();
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.addEventListener('timeout', function() {
|
||||||
|
updateOutputContent.textContent = 'Upload timed out (5 min limit)';
|
||||||
|
showToast('Upload timed out', 'error');
|
||||||
|
resetUploadUI();
|
||||||
|
});
|
||||||
|
|
||||||
|
function resetUploadUI() {
|
||||||
|
uploadBtn.disabled = false;
|
||||||
|
uploadBtnText.textContent = 'Upload & Install';
|
||||||
|
uploadSpinner.style.display = 'none';
|
||||||
|
progressBar.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.open('POST', 'launcher.php?type=upload_firmware');
|
||||||
|
xhr.send(formData);
|
||||||
|
}
|
||||||
|
|
||||||
function clearUpdateOutput() {
|
function clearUpdateOutput() {
|
||||||
const updateOutput = document.getElementById('updateOutput');
|
const updateOutput = document.getElementById('updateOutput');
|
||||||
const updateOutputContent = document.getElementById('updateOutputContent');
|
const updateOutputContent = document.getElementById('updateOutputContent');
|
||||||
|
|||||||
@@ -410,6 +410,107 @@ if ($type == "update_firmware") {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($type == "upload_firmware") {
|
||||||
|
// Firmware update via ZIP file upload (offline mode)
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'POST method required']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check file upload
|
||||||
|
if (!isset($_FILES['firmware_file']) || $_FILES['firmware_file']['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
$upload_errors = [
|
||||||
|
UPLOAD_ERR_INI_SIZE => 'File exceeds server upload limit',
|
||||||
|
UPLOAD_ERR_FORM_SIZE => 'File exceeds form upload limit',
|
||||||
|
UPLOAD_ERR_PARTIAL => 'File was only partially uploaded',
|
||||||
|
UPLOAD_ERR_NO_FILE => 'No file was uploaded',
|
||||||
|
UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary folder',
|
||||||
|
UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
|
||||||
|
];
|
||||||
|
$error_code = $_FILES['firmware_file']['error'] ?? UPLOAD_ERR_NO_FILE;
|
||||||
|
$error_msg = $upload_errors[$error_code] ?? 'Unknown upload error';
|
||||||
|
echo json_encode(['success' => false, 'message' => $error_msg]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $_FILES['firmware_file'];
|
||||||
|
|
||||||
|
// Validate extension
|
||||||
|
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||||
|
if ($ext !== 'zip') {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Only .zip files are allowed']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate size (50MB max)
|
||||||
|
if ($file['size'] > 50 * 1024 * 1024) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'File too large (max 50MB)']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current version before update
|
||||||
|
$old_version = 'unknown';
|
||||||
|
if (file_exists('/var/www/nebuleair_pro_4g/VERSION')) {
|
||||||
|
$old_version = trim(file_get_contents('/var/www/nebuleair_pro_4g/VERSION'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare extraction directory
|
||||||
|
$tmp_dir = '/tmp/nebuleair_update';
|
||||||
|
$extract_dir = "$tmp_dir/extracted";
|
||||||
|
shell_exec("rm -rf $tmp_dir");
|
||||||
|
mkdir($extract_dir, 0755, true);
|
||||||
|
|
||||||
|
// Move uploaded file
|
||||||
|
$zip_path = "$tmp_dir/firmware.zip";
|
||||||
|
if (!move_uploaded_file($file['tmp_name'], $zip_path)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Failed to move uploaded file']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract ZIP
|
||||||
|
$unzip_output = shell_exec("unzip -o '$zip_path' -d '$extract_dir' 2>&1");
|
||||||
|
|
||||||
|
// Detect project root folder (Gitea creates nebuleair_pro_4g-main/ inside the zip)
|
||||||
|
$source_dir = $extract_dir;
|
||||||
|
$entries = scandir($extract_dir);
|
||||||
|
$subdirs = array_filter($entries, function($e) use ($extract_dir) {
|
||||||
|
return $e !== '.' && $e !== '..' && is_dir("$extract_dir/$e");
|
||||||
|
});
|
||||||
|
|
||||||
|
if (count($subdirs) === 1) {
|
||||||
|
$subdir = reset($subdirs);
|
||||||
|
$candidate = "$extract_dir/$subdir";
|
||||||
|
if (file_exists("$candidate/VERSION")) {
|
||||||
|
$source_dir = $candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate VERSION exists in the archive
|
||||||
|
if (!file_exists("$source_dir/VERSION")) {
|
||||||
|
shell_exec("rm -rf $tmp_dir");
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Invalid archive: VERSION file not found']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_version = trim(file_get_contents("$source_dir/VERSION"));
|
||||||
|
|
||||||
|
// Execute update script
|
||||||
|
$command = "sudo /var/www/nebuleair_pro_4g/update_firmware_from_file.sh '$source_dir' 2>&1";
|
||||||
|
$output = shell_exec($command);
|
||||||
|
|
||||||
|
// Cleanup (also done in script, but just in case)
|
||||||
|
shell_exec("rm -rf $tmp_dir");
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'output' => $output,
|
||||||
|
'old_version' => $old_version,
|
||||||
|
'new_version' => $new_version,
|
||||||
|
'timestamp' => date('Y-m-d H:i:s')
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
if ($type == "set_RTC_withNTP") {
|
if ($type == "set_RTC_withNTP") {
|
||||||
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py';
|
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py';
|
||||||
$output = shell_exec($command);
|
$output = shell_exec($command);
|
||||||
|
|||||||
185
update_firmware_from_file.sh
Normal file
185
update_firmware_from_file.sh
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# NebuleAir Pro 4G - Update from uploaded ZIP file
|
||||||
|
# Usage: sudo ./update_firmware_from_file.sh /path/to/extracted/folder
|
||||||
|
|
||||||
|
echo "======================================"
|
||||||
|
echo "NebuleAir Pro 4G - Firmware Update (File Upload)"
|
||||||
|
echo "======================================"
|
||||||
|
echo "Started at: $(date)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Set target directory
|
||||||
|
TARGET_DIR="/var/www/nebuleair_pro_4g"
|
||||||
|
EXCLUDE_FILE="$TARGET_DIR/.update-exclude"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate arguments
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
print_status "✗ Error: No source directory provided"
|
||||||
|
print_status "Usage: $0 /path/to/extracted/folder"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SOURCE_DIR="$1"
|
||||||
|
|
||||||
|
if [ ! -d "$SOURCE_DIR" ]; then
|
||||||
|
print_status "✗ Error: Source directory does not exist: $SOURCE_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 1: Validate source and compare versions
|
||||||
|
print_status "Step 1: Validating update package..."
|
||||||
|
|
||||||
|
if [ ! -f "$SOURCE_DIR/VERSION" ]; then
|
||||||
|
print_status "✗ Error: VERSION file not found in update package"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
NEW_VERSION=$(cat "$SOURCE_DIR/VERSION" | tr -d '[:space:]')
|
||||||
|
OLD_VERSION="unknown"
|
||||||
|
if [ -f "$TARGET_DIR/VERSION" ]; then
|
||||||
|
OLD_VERSION=$(cat "$TARGET_DIR/VERSION" | tr -d '[:space:]')
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_status "Current version: $OLD_VERSION"
|
||||||
|
print_status "New version: $NEW_VERSION"
|
||||||
|
|
||||||
|
# Step 2: Rsync with exclusions from .update-exclude
|
||||||
|
print_status ""
|
||||||
|
print_status "Step 2: Syncing files..."
|
||||||
|
|
||||||
|
# Build exclude args: use .update-exclude from the SOURCE (new version) if available,
|
||||||
|
# otherwise fall back to the one already installed
|
||||||
|
if [ -f "$SOURCE_DIR/.update-exclude" ]; then
|
||||||
|
EXCLUDE_FILE="$SOURCE_DIR/.update-exclude"
|
||||||
|
print_status "Using .update-exclude from update package"
|
||||||
|
elif [ -f "$TARGET_DIR/.update-exclude" ]; then
|
||||||
|
EXCLUDE_FILE="$TARGET_DIR/.update-exclude"
|
||||||
|
print_status "Using .update-exclude from current installation"
|
||||||
|
else
|
||||||
|
print_status "⚠ No .update-exclude file found, using built-in defaults"
|
||||||
|
# Fallback minimal exclusions
|
||||||
|
EXCLUDE_FILE=$(mktemp)
|
||||||
|
cat > "$EXCLUDE_FILE" <<'EXCL'
|
||||||
|
sqlite/sensors.db
|
||||||
|
sqlite/*.db-journal
|
||||||
|
sqlite/*.db-wal
|
||||||
|
logs/
|
||||||
|
.git/
|
||||||
|
config.json
|
||||||
|
deviceID.txt
|
||||||
|
wifi_list.csv
|
||||||
|
envea/data/
|
||||||
|
NPM/data/
|
||||||
|
*.lock
|
||||||
|
EXCL
|
||||||
|
fi
|
||||||
|
|
||||||
|
rsync -av --delete --exclude-from="$EXCLUDE_FILE" "$SOURCE_DIR/" "$TARGET_DIR/"
|
||||||
|
check_status "File sync (rsync)"
|
||||||
|
|
||||||
|
# Fix ownership and permissions
|
||||||
|
print_status "Fixing ownership..."
|
||||||
|
chown -R www-data:www-data "$TARGET_DIR/"
|
||||||
|
check_status "Ownership fix (chown)"
|
||||||
|
|
||||||
|
# Step 3: Update database configuration
|
||||||
|
print_status ""
|
||||||
|
print_status "Step 3: Updating database configuration..."
|
||||||
|
/usr/bin/python3 "$TARGET_DIR/sqlite/set_config.py"
|
||||||
|
check_status "Database configuration update"
|
||||||
|
|
||||||
|
# Step 4: Check and fix file permissions
|
||||||
|
print_status ""
|
||||||
|
print_status "Step 4: Checking file permissions..."
|
||||||
|
chmod +x "$TARGET_DIR/update_firmware.sh"
|
||||||
|
chmod +x "$TARGET_DIR/update_firmware_from_file.sh"
|
||||||
|
chmod 755 "$TARGET_DIR/sqlite/"*.py
|
||||||
|
chmod 755 "$TARGET_DIR/NPM/"*.py
|
||||||
|
chmod 755 "$TARGET_DIR/BME280/"*.py
|
||||||
|
chmod 755 "$TARGET_DIR/SARA/"*.py
|
||||||
|
chmod 755 "$TARGET_DIR/envea/"*.py
|
||||||
|
chmod 755 "$TARGET_DIR/MPPT/"*.py 2>/dev/null
|
||||||
|
chmod 755 "$TARGET_DIR/wifi/"*.py 2>/dev/null
|
||||||
|
check_status "File permissions update"
|
||||||
|
|
||||||
|
# Step 5: Restart critical services
|
||||||
|
print_status ""
|
||||||
|
print_status "Step 5: Managing system services..."
|
||||||
|
|
||||||
|
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
|
||||||
|
if systemctl is-enabled --quiet "$service" 2>/dev/null; then
|
||||||
|
print_status "Restarting enabled service: $service"
|
||||||
|
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 6: System health check
|
||||||
|
print_status ""
|
||||||
|
print_status "Step 6: System health check..."
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if [ -f "$TARGET_DIR/sqlite/sensors.db" ]; then
|
||||||
|
print_status "✓ Database file exists"
|
||||||
|
else
|
||||||
|
print_status "⚠ Warning: Database file not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cleanup logs > 10MB
|
||||||
|
find "$TARGET_DIR/logs" -name "*.log" -size +10M -exec truncate -s 0 {} \;
|
||||||
|
check_status "Log cleanup"
|
||||||
|
|
||||||
|
# Step 7: Cleanup temporary files
|
||||||
|
print_status ""
|
||||||
|
print_status "Step 7: Cleaning up temporary files..."
|
||||||
|
rm -rf /tmp/nebuleair_update/
|
||||||
|
check_status "Temp cleanup"
|
||||||
|
|
||||||
|
print_status ""
|
||||||
|
print_status "======================================"
|
||||||
|
print_status "Update from $OLD_VERSION to $NEW_VERSION completed successfully!"
|
||||||
|
print_status "======================================"
|
||||||
|
|
||||||
|
exit 0
|
||||||
Reference in New Issue
Block a user