diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index f2cf5630..d22066fa 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -10048,7 +10048,12 @@ def _update_smart_cron(): # scheduled tests in history" with no obvious clue why. cmd = f'/usr/local/share/proxmenux/scripts/storage/smart-scheduled-test.sh --schedule-id {schedule_id} --test-type {test_type} --retention {retention}' if disks != ['all']: - cmd += f" --disks '{','.join(disks)}'" + # Prepend /dev/ when the UI stored the disk as a basename + # (sdb, nvme0n1, …). The runner's `[[ -b "$disk" ]]` check + # only accepts full block-device paths, so basenames were + # silently skipped and no test ever ran. + disk_args = [d if d.startswith('/') else f'/dev/{d}' for d in disks] + cmd += f" --disks '{','.join(disk_args)}'" cron_lines.append(f'{cron_time} root {cmd} >> /var/log/proxmenux/smart-schedule.log 2>&1') diff --git a/scripts/backup_restore/run_scheduled_backup.sh b/scripts/backup_restore/run_scheduled_backup.sh index e21852dc..cb0709bc 100755 --- a/scripts/backup_restore/run_scheduled_backup.sh +++ b/scripts/backup_restore/run_scheduled_backup.sh @@ -547,19 +547,54 @@ main() { # jobs keep whatever KEEP_* the .env has. _sb_hydrate_attached_retention - local lock_file="${LOCK_DIR}/proxmenux-backup-${job_id}.lock" - if command -v flock >/dev/null 2>&1; then - exec 9>"$lock_file" || exit 1 - if ! flock -n 9; then - echo "Another run is active for job ${job_id}" >&2 - exit 1 - fi - fi - + # Create log_file and summary_file BEFORE the flock so any early + # failure (lock contention, missing tools, unreadable env) surfaces + # in the runner log the Monitor polls, instead of being lost to + # stderr/DEVNULL and leaving the UI stuck on "Waiting for runner + # to start…" forever. local ts log_file stage_root summary_file ts="$(date +%Y%m%d_%H%M%S)" log_file="${LOG_DIR}/${job_id}-${ts}.log" summary_file="${LOG_DIR}/${job_id}-last.status" + + local lock_file="${LOCK_DIR}/proxmenux-backup-${job_id}.lock" + if command -v flock >/dev/null 2>&1; then + if ! exec 9>"$lock_file"; then + { + echo "=== Scheduled backup job ${job_id} aborted at $(date -Iseconds) ===" + echo "Cannot open lock file: $lock_file" + echo "Check that ${LOCK_DIR} exists and is writable by root." + } >"$log_file" + { + echo "JOB_ID=${job_id}" + echo "RUN_AT=$(date -Iseconds)" + echo "RESULT=failed" + echo "REASON=lock_open_failed" + } >"$summary_file" + exit 1 + fi + if ! flock -n 9; then + { + echo "=== Scheduled backup job ${job_id} aborted at $(date -Iseconds) ===" + echo "Another run is already active for this job (lock held: $lock_file)." + echo "" + echo "Possible causes:" + echo " - A previous run is still in progress (check with: ps auxf | grep run_scheduled_backup)." + echo " - A previous run hung and left the lock behind. Kill the stale" + echo " process (if any) and remove the lock: rm $lock_file" + echo "" + echo "This run did NOT execute a backup." + } >"$log_file" + { + echo "JOB_ID=${job_id}" + echo "RUN_AT=$(date -Iseconds)" + echo "RESULT=failed" + echo "REASON=another_run_active" + } >"$summary_file" + exit 1 + fi + fi + stage_root="$(mktemp -d /tmp/proxmenux-sched-stage.XXXXXX)" { diff --git a/scripts/storage/smart-scheduled-test.sh b/scripts/storage/smart-scheduled-test.sh index 9e0dd169..1c7f331f 100644 --- a/scripts/storage/smart-scheduled-test.sh +++ b/scripts/storage/smart-scheduled-test.sh @@ -169,6 +169,13 @@ FAIL_COUNT=0 log "Found $TOTAL_DISKS disk(s) to test" for disk in $DISK_LIST; do + # Accept both full paths and basenames. Older cron entries written + # before the Monitor prepended /dev/ passed disk names as bare + # basenames (sdb, nvme0n1), which then failed the block-device + # check below. Normalising here keeps those legacy entries working + # after the update without forcing the operator to recreate them. + [[ "$disk" != /* ]] && disk="/dev/$disk" + # Skip if disk doesn't exist if [[ ! -b "$disk" ]]; then log "WARNING: Disk $disk not found, skipping"