From 15d802ae3d3eebd380c77dba2651238a3a8e662f Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Sat, 22 Aug 2026 13:52:34 -0400 Subject: [PATCH] Preview mode that survives a reboot, and a fallback log that survives one too --- Deployment/master.conf.template | 11 ++++++ Fallback/fallback.sh | 46 +++++++++++++++++++++++++- Plugin/unraid/api/fallback_control.php | 10 +++--- Plugin/unraid/include/fallback.php | 1 + Plugin/unraid/pages/fallback.php | 14 ++++++++ 5 files changed, 77 insertions(+), 5 deletions(-) diff --git a/Deployment/master.conf.template b/Deployment/master.conf.template index f18ddb2..fba158b 100644 --- a/Deployment/master.conf.template +++ b/Deployment/master.conf.template @@ -865,6 +865,17 @@ # Success returns on the first try, so a healthy cycle costs # nothing extra. Only a failure pays: 3×4s + 2×2s = 16s worst # case, which must stay under FALLBACK_CHECK_INTERVAL. + FALLBACK_DRY_RUN=false # run the daemon in PREVIEW mode, permanently + # Survives array start, unlike a --dry-run typed at a shell: + # array_started.sh launches with no arguments, so a hand-run + # preview is replaced by the LIVE daemon at the next boot. + # Decides nothing, starts nothing, moves no DNS, sends no + # notification — it only reports what it WOULD have done. + # Set true to observe real outages before arming for real. + FALLBACK_LOG_MAX_MB=5 # size cap for data/logs/fallback.log before one rotation + # Event-only output (the default, no --log) is a few lines + # per incident and holds years; --log fills this in about a + # fortnight and keeps the previous one as fallback.log.1 FALLBACK_STATE_FILE="$STATE_DIR/fallback_state.db" FALLBACK_ENABLED=false # set true once both servers are configured and paired # false = suppresses "not running" warnings in status scripts diff --git a/Fallback/fallback.sh b/Fallback/fallback.sh index 44b81b1..77a9d7f 100755 --- a/Fallback/fallback.sh +++ b/Fallback/fallback.sh @@ -314,6 +314,50 @@ if [[ "$EUID" -ne 0 ]]; then exit 1 fi + +# ── Persistent dry-run ──────────────────────────────────────────────────────────────────────── +# Conf-driven, not argument-driven, deliberately. array_started.sh launches every entry as a bare +# `bash script.sh &` with no arguments, so a --dry-run typed at a terminal survives exactly until +# the next array start — and then the LIVE daemon comes up in its place, silently, which is the +# one transition nobody would be watching for. +# +# Setting it here means the mode is a property of the install rather than of how the process +# happened to be started: array start, the Fallback tab's button, and a hand-run all agree. +# +# OR, never override: --dry-run on the command line still wins over a conf that says false, so an +# ad-hoc preview against a live install needs no conf edit. +if [[ "${FALLBACK_DRY_RUN:-false}" == "true" ]]; then + DRY_RUN=true +fi + +# ── Persistent log ──────────────────────────────────────────────────────────────────────────── +# array_started.sh launches every entry as a bare `bash script.sh &` with no redirection, so this +# daemon's output has never been captured anywhere: /var/log/varaverk has a directory for every +# other script family and none for Fallback. A month of dry-run observation would have persisted +# nothing at all. +# +# /var/log is a 128 MB tmpfs on Unraid — RAM, and cleared on reboot — so the log goes to +# LOG_ARCHIVE_DIR, which follows DATA_DIR onto real storage. +# +# Only when stdout is not a terminal. Run by hand, output still goes to the terminal exactly as +# before; run by array_started or the Fallback tab's button, it lands in the file. A plain append +# redirect rather than `tee` through process substitution: no extra child to outlive, and nothing +# for the shutdown trap to race. +FALLBACK_LOG="${LOG_ARCHIVE_DIR:-${DATA_DIR:-/tmp}/logs}/fallback.log" +if [[ ! -t 1 ]]; then + mkdir -p "$(dirname "$FALLBACK_LOG")" 2>/dev/null + # One rotation, sized rather than line-counted — the whole point of this log is a long run, + # and _orch_trim_log()'s 1000-line cap would discard weeks of it. Event-only output (no + # --log) is a few lines per incident, so this holds years; --log fills it in about a fortnight + # and then keeps the most recent fortnight plus the one before it. + _fb_max=$(( ${FALLBACK_LOG_MAX_MB:-5} * 1048576 )) + if [[ -f "$FALLBACK_LOG" ]] && (( $(stat -c %s "$FALLBACK_LOG" 2>/dev/null || echo 0) > _fb_max )); then + mv -f "$FALLBACK_LOG" "${FALLBACK_LOG}.1" 2>/dev/null + fi + exec >> "$FALLBACK_LOG" 2>&1 + echo "" + echo "═══ fallback.sh started $(date '+%Y-%m-%d %H:%M:%S') — dry_run=${DRY_RUN} pid=$$ ═══" +fi # FALLBACK_ENABLED gate — exits cleanly when disabled. # Fail-closed: anything that isn't exactly "true" disables fallback. Matching only the # literal "false" would let a typo ("no", "0", "FALSE") hand this script DDNS authority @@ -1222,4 +1266,4 @@ while [[ "$FALLBACK_RUNNING" == true ]]; do sleep "$FALLBACK_CHECK_INTERVAL" & wait $! -done \ No newline at end of file +done diff --git a/Plugin/unraid/api/fallback_control.php b/Plugin/unraid/api/fallback_control.php index 5325d7e..9bf3bf2 100644 --- a/Plugin/unraid/api/fallback_control.php +++ b/Plugin/unraid/api/fallback_control.php @@ -84,9 +84,11 @@ $scripts = rtrim(SCRIPTS_DIR, '/'); $cmds = [ 'stop' => 'bash ' . escapeshellarg("$scripts/Fallback/fallback.sh") . ' --stop 2>&1', 'stop_test' => 'bash ' . escapeshellarg("$scripts/Fallback/fallback_test.sh") . ' --stop 2>&1', - // setsid so it outlives this request; own log so the page can show what the preview said. + // setsid so it outlives this request. No redirect: fallback.sh writes its own persistent + // log when stdout is not a terminal, so an ad-hoc preview and the array-start daemon leave + // their record in the same file rather than one going to tmpfs and vanishing on reboot. 'start_dry' => 'setsid bash ' . escapeshellarg("$scripts/Fallback/fallback.sh") - . ' --dry-run --log > /tmp/varaverk/fallback_dryrun.log 2>&1 < /dev/null & echo started', + . ' --dry-run --log > /dev/null 2>&1 < /dev/null & echo started', 'clear_lock' => 'rm -f /tmp/unraid_locks/fallback.lock /tmp/unraid_locks/fallback_test.lock && echo cleared', ]; @@ -119,8 +121,8 @@ if ($remoteDir === '') $remoteDir = '/boot/config/plugins/varaverk'; $remoteCmds = [ 'stop' => "bash '$remoteDir/Fallback/fallback.sh' --stop 2>&1", 'stop_test' => "bash '$remoteDir/Fallback/fallback_test.sh' --stop 2>&1", - 'start_dry' => "mkdir -p /tmp/varaverk; setsid bash '$remoteDir/Fallback/fallback.sh'" - . " --dry-run --log > /tmp/varaverk/fallback_dryrun.log 2>&1 < /dev/null & echo started", + 'start_dry' => "setsid bash '$remoteDir/Fallback/fallback.sh'" + . " --dry-run --log > /dev/null 2>&1 < /dev/null & echo started", 'clear_lock' => 'rm -f /tmp/unraid_locks/fallback.lock /tmp/unraid_locks/fallback_test.lock && echo cleared', ]; diff --git a/Plugin/unraid/include/fallback.php b/Plugin/unraid/include/fallback.php index a592946..712e006 100644 --- a/Plugin/unraid/include/fallback.php +++ b/Plugin/unraid/include/fallback.php @@ -353,6 +353,7 @@ function vv_fb_all(): array { 'fb_enabled' => $fbEnabled, 'partnership_enabled' => $ptEnabled, 'fb_rsync_enabled' => $rsyncEnabled, + 'dry_run' => vv_fb_scalar($masterRaw, 'FALLBACK_DRY_RUN') === 'true', 'handback_req' => $handbackReq, 'check_interval' => $checkInterval, 'suspend_after' => $suspendAfter, diff --git a/Plugin/unraid/pages/fallback.php b/Plugin/unraid/pages/fallback.php index a5e1cf9..a6ea79b 100644 --- a/Plugin/unraid/pages/fallback.php +++ b/Plugin/unraid/pages/fallback.php @@ -237,6 +237,19 @@ if (vv_ai_ui_on()) vv_ai_chat_assets();
+ +
+ + +
+
+
Preview mode DRY RUN
+
Watch and report, change nothing — no containers, no DDNS, no notifications. Survives reboots, so a long observation run stays a preview.
+
+
+
@@ -712,6 +725,7 @@ function _setToggles(data) { ['vv-fb-pt-tog', !!data.partnership_enabled], ['vv-fb-en-tog', !!data.fb_enabled], ['vv-fb-rsync-tog', !!data.fb_rsync_enabled], + ['vv-fb-dry-tog', !!data.dry_run], ]; pairs.forEach(([id, on]) => { const el = document.getElementById(id);