From 01f44d176ec43ed5772f8564360c7a480eb1f465 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Fri, 26 Jun 2026 23:04:25 -0400 Subject: [PATCH 1/3] feat(os-update): fire 'os_update' event for pre-reboot driver staging Add a generic, self-registering os_update event dispatched via emhttp_event. unRAIDServer.plg fires it after the new release files (incl. changes.txt) are on the flash but before the reboot prompt, passing the incoming Unraid version and kernel release. Driver plugins can drop an event/os_update hook to stage a kernel-matched build pre-reboot, replacing the hardcoded third-party helper detection. Additive and backward compatible. OS-486 --- emhttp/plugins/unRAIDServer/unRAIDServer.plg | 12 ++++++++++++ sbin/emhttp_event | 10 ++++++++++ 2 files changed, 22 insertions(+) diff --git a/emhttp/plugins/unRAIDServer/unRAIDServer.plg b/emhttp/plugins/unRAIDServer/unRAIDServer.plg index a9ddf690a3..3c5882b28a 100644 --- a/emhttp/plugins/unRAIDServer/unRAIDServer.plg +++ b/emhttp/plugins/unRAIDServer/unRAIDServer.plg @@ -366,6 +366,18 @@ rm -f /boot/config/plugins/dynamix.plg if [[ -x /usr/local/emhttp/plugins/unraid.patch/scripts/patch.php ]]; then /usr/local/emhttp/plugins/unraid.patch/scripts/patch.php check &version; fi +# Fire the 'os_update' event so kernel-dependent plugins (out-of-tree driver +# plugins, etc.) can stage a build matching the INCOMING kernel before reboot. +# The new release files - including changes.txt - are already on the flash here, +# so the incoming kernel release can be read from it. Hooks run synchronously, +# so the reboot prompt below only appears once they have finished. This is the +# generic, self-registering replacement for hardcoded third-party helpers: a +# plugin just installs an executable at its event/os_update path. +new_kernel=$(awk '/[Ll]inux kernel/{f=3} f>0{if(match($0,/[0-9]+(\.[0-9]+)+/)){print substr($0,RSTART,RLENGTH);exit}f--}' /boot/changes.txt 2>/dev/null | head -1) +if [ -n "$new_kernel" ]; then new_kernel="${new_kernel}-Unraid"; fi +if [ -x /usr/local/sbin/emhttp_event ]; then + /usr/local/sbin/emhttp_event os_update "&version;" "$new_kernel" +fi # ensure writes to USB flash boot device have completed sync -f /boot if [ -z "${plg_update_helper}" ]; then diff --git a/sbin/emhttp_event b/sbin/emhttp_event index c9aab091f6..161c420aaa 100755 --- a/sbin/emhttp_event +++ b/sbin/emhttp_event @@ -79,6 +79,16 @@ # Note that if array is not Started, emhttpd will not spin down any disk, but emhttpd will # still poll SMART data (for spun-up devices) and generate this event. +# os_update +# Occurs during an Unraid OS update, after the new release files (including +# /boot/changes.txt) have been written to the flash but BEFORE the user reboots. +# Fired by the unRAIDServer plugin, not by emhttpd. Lets kernel-dependent +# plugins (e.g. out-of-tree driver plugins) stage a build matching the INCOMING +# kernel before reboot, so a reboot never lands on a kernel without a driver. +# Extra arguments: $2 = incoming Unraid version, $3 = incoming kernel release +# (uname -r form, e.g. 6.18.33-Unraid; empty if it could not be determined). +# Hooks run synchronously - keep them quick and use network timeouts. + # Invoke all 'any_event' scripts that might exist for Dir in /usr/local/emhttp/plugins/* ; do if [ -d $Dir/event/any_event ]; then From fb537cb08e099f0383a651aa8b9b592a2ffa8d40 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Fri, 26 Jun 2026 23:13:21 -0400 Subject: [PATCH 2/3] docs(os-update): frame os_update as a general pre-reboot event, not driver-only The mechanism already runs any plugin's event/os_update; clarify in the unRAIDServer.plg comment and emhttp_event docs that it is a general pre-reboot OS-update hook (config migration, backups, compat checks, driver staging), with version/kernel passed as optional context. No behavior change. OS-486 --- emhttp/plugins/unRAIDServer/unRAIDServer.plg | 16 +++++++++------- sbin/emhttp_event | 13 ++++++++----- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/emhttp/plugins/unRAIDServer/unRAIDServer.plg b/emhttp/plugins/unRAIDServer/unRAIDServer.plg index 3c5882b28a..ece6af5470 100644 --- a/emhttp/plugins/unRAIDServer/unRAIDServer.plg +++ b/emhttp/plugins/unRAIDServer/unRAIDServer.plg @@ -366,13 +366,15 @@ rm -f /boot/config/plugins/dynamix.plg if [[ -x /usr/local/emhttp/plugins/unraid.patch/scripts/patch.php ]]; then /usr/local/emhttp/plugins/unraid.patch/scripts/patch.php check &version; fi -# Fire the 'os_update' event so kernel-dependent plugins (out-of-tree driver -# plugins, etc.) can stage a build matching the INCOMING kernel before reboot. -# The new release files - including changes.txt - are already on the flash here, -# so the incoming kernel release can be read from it. Hooks run synchronously, -# so the reboot prompt below only appears once they have finished. This is the -# generic, self-registering replacement for hardcoded third-party helpers: a -# plugin just installs an executable at its event/os_update path. +# Fire the generic 'os_update' event: a pre-reboot hook for an OS update that +# any plugin can register for (config migration, backups, compatibility checks +# and - for out-of-tree driver plugins - staging a build matching the INCOMING +# kernel so a reboot never lands on a kernel without its driver). The new release +# files - including changes.txt - are already on the flash here; pass the +# incoming Unraid version and kernel release as context (consumers ignore what +# they do not need). Hooks run synchronously, so the reboot prompt below only +# appears once they have finished. Self-registering: a plugin just installs an +# executable at its event/os_update path - no hardcoded list. new_kernel=$(awk '/[Ll]inux kernel/{f=3} f>0{if(match($0,/[0-9]+(\.[0-9]+)+/)){print substr($0,RSTART,RLENGTH);exit}f--}' /boot/changes.txt 2>/dev/null | head -1) if [ -n "$new_kernel" ]; then new_kernel="${new_kernel}-Unraid"; fi if [ -x /usr/local/sbin/emhttp_event ]; then diff --git a/sbin/emhttp_event b/sbin/emhttp_event index 161c420aaa..85107f534d 100755 --- a/sbin/emhttp_event +++ b/sbin/emhttp_event @@ -82,11 +82,14 @@ # os_update # Occurs during an Unraid OS update, after the new release files (including # /boot/changes.txt) have been written to the flash but BEFORE the user reboots. -# Fired by the unRAIDServer plugin, not by emhttpd. Lets kernel-dependent -# plugins (e.g. out-of-tree driver plugins) stage a build matching the INCOMING -# kernel before reboot, so a reboot never lands on a kernel without a driver. -# Extra arguments: $2 = incoming Unraid version, $3 = incoming kernel release -# (uname -r form, e.g. 6.18.33-Unraid; empty if it could not be determined). +# Fired by the unRAIDServer plugin, not by emhttpd. A general pre-reboot hook +# for an OS update: any plugin can run work that must happen before rebooting +# into the new release - e.g. migrate/convert config, back something up, warn +# about an incompatibility, or (for out-of-tree driver plugins) stage a build +# matching the incoming kernel so the reboot never lands on a kernel without it. +# Extra arguments, passed as context (ignore if not needed): $2 = incoming +# Unraid version, $3 = incoming kernel release (uname -r form, e.g. +# 6.18.33-Unraid; empty if it could not be determined). # Hooks run synchronously - keep them quick and use network timeouts. # Invoke all 'any_event' scripts that might exist From 41b445bdeac35ee122aa35dcc857c3a7814d9e8d Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Sat, 27 Jun 2026 08:40:56 -0400 Subject: [PATCH 3/3] feat(os-update): bound os_update dispatch with a timeout Wrap the synchronous os_update event dispatch in 'timeout 300' so a wedged third-party hook cannot stall the OS update indefinitely. Document the self-background escape hatch for hooks that need long/async work. OS-486 --- emhttp/plugins/unRAIDServer/unRAIDServer.plg | 4 +++- sbin/emhttp_event | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/emhttp/plugins/unRAIDServer/unRAIDServer.plg b/emhttp/plugins/unRAIDServer/unRAIDServer.plg index ece6af5470..9e95fc77a5 100644 --- a/emhttp/plugins/unRAIDServer/unRAIDServer.plg +++ b/emhttp/plugins/unRAIDServer/unRAIDServer.plg @@ -378,7 +378,9 @@ fi new_kernel=$(awk '/[Ll]inux kernel/{f=3} f>0{if(match($0,/[0-9]+(\.[0-9]+)+/)){print substr($0,RSTART,RLENGTH);exit}f--}' /boot/changes.txt 2>/dev/null | head -1) if [ -n "$new_kernel" ]; then new_kernel="${new_kernel}-Unraid"; fi if [ -x /usr/local/sbin/emhttp_event ]; then - /usr/local/sbin/emhttp_event os_update "&version;" "$new_kernel" + # Bound the whole dispatch so a wedged hook cannot stall the update forever; + # a hook that needs longer should background itself and notify (see emhttp_event). + timeout 300 /usr/local/sbin/emhttp_event os_update "&version;" "$new_kernel" fi # ensure writes to USB flash boot device have completed sync -f /boot diff --git a/sbin/emhttp_event b/sbin/emhttp_event index 85107f534d..64f1dbbdcc 100755 --- a/sbin/emhttp_event +++ b/sbin/emhttp_event @@ -90,7 +90,10 @@ # Extra arguments, passed as context (ignore if not needed): $2 = incoming # Unraid version, $3 = incoming kernel release (uname -r form, e.g. # 6.18.33-Unraid; empty if it could not be determined). -# Hooks run synchronously - keep them quick and use network timeouts. +# Hooks run synchronously and the whole dispatch is bounded by a timeout, so +# keep them quick and use network timeouts. A hook that genuinely needs long +# or async work should fork itself, return promptly, and surface its own result +# via notify - rather than blocking the OS update. # Invoke all 'any_event' scripts that might exist for Dir in /usr/local/emhttp/plugins/* ; do