From 3b2d096d2b9eac7c3f1d2330991514d53cae6f52 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Thu, 18 Jun 2026 23:16:31 -0400 Subject: [PATCH 1/5] feat(power): add configurable power button behavior (OS-463) Rename the "Power Mode" settings page to "Power Options" and turn it into a tabbed page with two tabs: - Power Mode - the existing CPU governor controls (moved verbatim) - Power Button - new setting to choose what the physical power button does The power button behavior (Shut down / Do nothing) is stored in the [powermode] section of dynamix.cfg and read live by the ACPI handler on each press, so changes take effect without a reload or reboot. A setting-aware ACPI handler (etc/acpi/unraid_power_handler.sh) is installed over the stock Slackware acpi_handler.sh at boot by rc.acpid, so it reliably overrides the default poweroff regardless of package install order. This mirrors how rc.cpufreq applies the [powermode] governor setting at boot. Closes OS-463 Co-Authored-By: Claude Opus 4.8 --- emhttp/languages/en_US/helptext.txt | 8 +++ emhttp/plugins/dynamix/PowerButton.page | 35 +++++++++++ emhttp/plugins/dynamix/PowerMode.page | 75 +----------------------- emhttp/plugins/dynamix/PowerModeCpu.page | 75 ++++++++++++++++++++++++ etc/acpi/unraid_power_handler.sh | 46 +++++++++++++++ etc/rc.d/rc.acpid | 13 ++++ 6 files changed, 178 insertions(+), 74 deletions(-) create mode 100644 emhttp/plugins/dynamix/PowerButton.page create mode 100644 emhttp/plugins/dynamix/PowerModeCpu.page create mode 100755 etc/acpi/unraid_power_handler.sh diff --git a/emhttp/languages/en_US/helptext.txt b/emhttp/languages/en_US/helptext.txt index 92ee679a46..2471b15988 100644 --- a/emhttp/languages/en_US/helptext.txt +++ b/emhttp/languages/en_US/helptext.txt @@ -1648,6 +1648,14 @@ Choose if rebooting or powering down the server needs a confirmation checkbox. Choose if stopping the array needs a confirmation checkbox. :end +:powerbutton_behavior_help: +Choose what happens when the server's physical power button is pressed. + +**Shut down** performs a clean shutdown (stops the array and powers off). This is the default behavior. + +**Do nothing** ignores the button press and only logs the event. This is useful on small form factor hardware where the power button is easily bumped, to prevent accidental shutdowns. +:end + :display_settings_help: The display settings below determine how items are displayed on screen. Use these settings to tweak the visual effects to your likings. diff --git a/emhttp/plugins/dynamix/PowerButton.page b/emhttp/plugins/dynamix/PowerButton.page new file mode 100644 index 0000000000..0a08ea1fea --- /dev/null +++ b/emhttp/plugins/dynamix/PowerButton.page @@ -0,0 +1,35 @@ +Menu="PowerMode:2" +Title="Power Button" +Tag="icon-power" +--- + + +
+ + + +_(Physical power button)_: +: + +:powerbutton_behavior_help: + +  +: + + + +
diff --git a/emhttp/plugins/dynamix/PowerMode.page b/emhttp/plugins/dynamix/PowerMode.page index 03be51bee4..967b1fbf78 100644 --- a/emhttp/plugins/dynamix/PowerMode.page +++ b/emhttp/plugins/dynamix/PowerMode.page @@ -1,77 +1,4 @@ Menu="OtherSettings" Type="xmenu" -Title="Power Mode" +Title="Power Options" Icon="icon-energysaving" -Tag="icon-energysaving" ---- - -/dev/null"); -exec("cat $cpufreq/scaling_available_governors 2>/dev/null | tr ' ' '\n' | sed '/^$/d' | sort -u",$governor); - -function value(...$modes) { - global $current, $governor; - $checked = $value = ''; - $disabled = ' disabled'; - foreach ($modes as $mode) { - if ($mode==$current) $checked = ' checked'; - if (in_array($mode,$governor)) {$value = "value=\"$mode\""; $disabled = '';} - } - return $value.$checked.$disabled; -} -?> - - -
- - - - - -_(Change power mode)_: -: - >_(Best power efficiency)_ - - -  -: - >_(Balanced operation)_ - - -  -: - >_(Best performance)_ - - -  -: - - - -
- -
_(When running Unraid virtualized, there are no available power modes)_
diff --git a/emhttp/plugins/dynamix/PowerModeCpu.page b/emhttp/plugins/dynamix/PowerModeCpu.page new file mode 100644 index 0000000000..583272b0fb --- /dev/null +++ b/emhttp/plugins/dynamix/PowerModeCpu.page @@ -0,0 +1,75 @@ +Menu="PowerMode:1" +Title="Power Mode" +Tag="icon-energysaving" +--- + +/dev/null"); +exec("cat $cpufreq/scaling_available_governors 2>/dev/null | tr ' ' '\n' | sed '/^$/d' | sort -u",$governor); + +function value(...$modes) { + global $current, $governor; + $checked = $value = ''; + $disabled = ' disabled'; + foreach ($modes as $mode) { + if ($mode==$current) $checked = ' checked'; + if (in_array($mode,$governor)) {$value = "value=\"$mode\""; $disabled = '';} + } + return $value.$checked.$disabled; +} +?> + + +
+ + + + + +_(Change power mode)_: +: + >_(Best power efficiency)_ + + +  +: + >_(Balanced operation)_ + + +  +: + >_(Best performance)_ + + +  +: + + + +
+ +
_(When running Unraid virtualized, there are no available power modes)_
diff --git a/etc/acpi/unraid_power_handler.sh b/etc/acpi/unraid_power_handler.sh new file mode 100755 index 0000000000..3ce71a7c6c --- /dev/null +++ b/etc/acpi/unraid_power_handler.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# +# script: unraid_power_handler.sh +# +# Unraid ACPI event handler. Installed over /etc/acpi/acpi_handler.sh at boot +# by /etc/rc.d/rc.acpid, so it reliably overrides the stock Slackware acpid +# handler regardless of package install order. +# +# The physical power button behavior is configurable from the WebUI +# (Settings > Power Options > Power Button). The choice is stored in +# dynamix.cfg and read live on each event, so no acpid reload is needed +# when the setting changes. +# +# LimeTech - modified for Unraid OS + +CFG=/boot/config/plugins/dynamix/dynamix.cfg + +# acpid passes the raw event line, e.g.: "button/power PBTN 00000080 00000000" +set -- $@ +group=${1%%/*} +action=${1#*/} + +case "$group" in +button) + case "$action" in + power) + behavior=$(grep -Pom1 '^powerbutton="\K[^"]+' "$CFG" 2>/dev/null) + case "${behavior:-shutdown}" in + ignore) + logger -t acpid "Power button pressed - ignored (Power Options setting)" + ;; + *) + logger -t acpid "Power button pressed - initiating clean shutdown" + /sbin/init 0 + ;; + esac + ;; + *) + logger -t acpid "ACPI action button/$action is not handled" + ;; + esac + ;; +*) + logger -t acpid "ACPI event $1 is not handled" + ;; +esac diff --git a/etc/rc.d/rc.acpid b/etc/rc.d/rc.acpid index 09d71a2342..7854918b56 100755 --- a/etc/rc.d/rc.acpid +++ b/etc/rc.d/rc.acpid @@ -17,6 +17,18 @@ acpid_running(){ ps axc | grep -q ' acpid' } +# Install Unraid's ACPI event handler over the stock Slackware acpi_handler.sh. +# Done at boot so it reliably wins regardless of package install order. The +# handler reads the configurable power button behavior (Settings > Power +# Options > Power Button) live from dynamix.cfg, so no reload is needed when +# the setting changes. +acpid_configure(){ + local SRC=/etc/acpi/unraid_power_handler.sh + if [[ -f $SRC ]]; then + install -m 0755 "$SRC" /etc/acpi/acpi_handler.sh + fi +} + acpid_start(){ log "Starting $DAEMON..." local REPLY @@ -24,6 +36,7 @@ acpid_start(){ REPLY="Already started" else if [[ -d /proc/acpi ]]; then + acpid_configure run /usr/sbin/acpid if acpid_running; then REPLY="Started"; else REPLY="Failed"; fi else From 70281a73016b0ef7924c7291039589124b568cbf Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Thu, 18 Jun 2026 23:25:04 -0400 Subject: [PATCH 2/5] fix(power): stop elogind from handling the power button A physical power press fires both an ACPI event (handled by acpid) and an input key event handled by elogind, whose default HandlePowerKey=poweroff shuts the system down regardless of the acpid handler. Ship a logind.conf.d drop-in setting HandlePowerKey/HandlePowerKeyLongPress to ignore so acpid is the sole authority over the power button, making the "Do nothing" Power Options setting actually take effect. Verified on hardware: elogind D-Bus HandlePowerKey now reports "ignore". Co-Authored-By: Claude Opus 4.8 --- etc/elogind/logind.conf.d/20-unraid-powerbutton.conf | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 etc/elogind/logind.conf.d/20-unraid-powerbutton.conf diff --git a/etc/elogind/logind.conf.d/20-unraid-powerbutton.conf b/etc/elogind/logind.conf.d/20-unraid-powerbutton.conf new file mode 100644 index 0000000000..7f2532ed50 --- /dev/null +++ b/etc/elogind/logind.conf.d/20-unraid-powerbutton.conf @@ -0,0 +1,9 @@ +# Unraid: let acpid be the sole handler of the physical power button. +# +# A power press generates both an ACPI event (handled by acpid via +# /etc/acpi/acpi_handler.sh) and an input key event (handled by elogind). +# To make the configurable power button behavior in Settings > Power Options +# work, elogind must not act on the key, leaving acpid in full control. +[Login] +HandlePowerKey=ignore +HandlePowerKeyLongPress=ignore From c4fa856504cae22a26bfbbcb99a78fbb3fd2506d Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Thu, 18 Jun 2026 23:37:09 -0400 Subject: [PATCH 3/5] feat(power): add reboot/suspend actions and a plugin extension hook Power Button now offers Shut down, Reboot, Sleep (suspend, shown only when the kernel supports suspend-to-RAM), and Do nothing. Plugins can add or override actions without touching core: - WebUI: PowerButton.page includes plugins/*/include/powerbutton-option.php (and powerbutton-extra.php for action-specific fields). - Action: unraid_power_handler.sh dispatches to /etc/acpi/powerbutton.d/ for non-builtin actions, and the builtin "sleep" defers to powerbutton.d/sleep when present (so the S3 Sleep plugin can do array-aware prep). Contract documented in /etc/acpi/powerbutton.d/README. This lets the User Scripts plugin add a "Run script" action in its own repo rather than coupling it into core. Co-Authored-By: Claude Opus 4.8 --- emhttp/languages/en_US/helptext.txt | 6 +++ emhttp/plugins/dynamix/PowerButton.page | 36 +++++++++++++++-- etc/acpi/powerbutton.d/README | 43 ++++++++++++++++++++ etc/acpi/unraid_power_handler.sh | 54 ++++++++++++++++++++----- etc/rc.d/rc.acpid | 2 + 5 files changed, 129 insertions(+), 12 deletions(-) create mode 100644 etc/acpi/powerbutton.d/README diff --git a/emhttp/languages/en_US/helptext.txt b/emhttp/languages/en_US/helptext.txt index 2471b15988..5fe70b3723 100644 --- a/emhttp/languages/en_US/helptext.txt +++ b/emhttp/languages/en_US/helptext.txt @@ -1653,7 +1653,13 @@ Choose what happens when the server's physical power button is pressed. **Shut down** performs a clean shutdown (stops the array and powers off). This is the default behavior. +**Reboot** performs a clean reboot. + +**Sleep (suspend)** suspends the server to RAM (only shown when the hardware supports it). If the S3 Sleep plugin is installed, its prep is used. + **Do nothing** ignores the button press and only logs the event. This is useful on small form factor hardware where the power button is easily bumped, to prevent accidental shutdowns. + +Plugins may add further actions (for example, running a User Script). :end :display_settings_help: diff --git a/emhttp/plugins/dynamix/PowerButton.page b/emhttp/plugins/dynamix/PowerButton.page index 0a08ea1fea..fa90970cdd 100644 --- a/emhttp/plugins/dynamix/PowerButton.page +++ b/emhttp/plugins/dynamix/PowerButton.page @@ -14,22 +14,52 @@ Tag="icon-power" * all copies or substantial portions of the Software. */ ?> +
_(Physical power button)_: -: + + + + + + + lines for custom actions. + // In scope: $cur (current powerbutton value). See /etc/acpi/powerbutton.d/README. + foreach (glob('/usr/local/emhttp/plugins/*/include/powerbutton-option.php') ?: [] as $frag) include $frag; + ?> :powerbutton_behavior_help: +" markdown="1"> ...fields... +foreach (glob('/usr/local/emhttp/plugins/*/include/powerbutton-extra.php') ?: [] as $frag) include $frag; +?> +   :
+ + diff --git a/etc/acpi/powerbutton.d/README b/etc/acpi/powerbutton.d/README new file mode 100644 index 0000000000..d9152cb239 --- /dev/null +++ b/etc/acpi/powerbutton.d/README @@ -0,0 +1,43 @@ +/etc/acpi/powerbutton.d/ - Unraid power button action handlers (extension point) +================================================================================ + +The physical power button behavior is configured in the WebUI at +Settings > Power Options > Power Button. The selected action is stored as + [powermode] + powerbutton="" +in /boot/config/plugins/dynamix/dynamix.cfg and dispatched at press time by +/etc/acpi/unraid_power_handler.sh. + +Built-in actions: shutdown (default), reboot, sleep, ignore. + +Plugins can ADD or OVERRIDE actions in two parts: + +1) Action handler (required) + Ship an executable here, named after the action value: + /etc/acpi/powerbutton.d/ + It runs when the power button is pressed and that action is selected. + The built-in "sleep" action also defers to powerbutton.d/sleep if present, + so the S3 Sleep plugin can do array-aware prep before suspending. + +2) WebUI option (so users can pick it) + Add the action to the selector by shipping: + /usr/local/emhttp/plugins//include/powerbutton-option.php + which echoes one or more options, e.g.: + + ($cur is the currently saved powerbutton value.) + + For extra fields shown only when your action is selected, also ship: + /usr/local/emhttp/plugins//include/powerbutton-extra.php + which outputs a block tagged with your action value: +
+ _(Script)_: + : +
+ Fields are POSTed into dynamix.cfg [powermode] alongside powerbutton, so + your handler can read them with the same grep pattern used above. + +Example (User Scripts plugin, "Run script" action): + - include/powerbutton-option.php : adds the "script" option + script picker + - include/powerbutton-extra.php : script selection dropdown + - /etc/acpi/powerbutton.d/script : reads powerbuttonscript= from dynamix.cfg + and executes the chosen user script diff --git a/etc/acpi/unraid_power_handler.sh b/etc/acpi/unraid_power_handler.sh index 3ce71a7c6c..a3b6c4d405 100755 --- a/etc/acpi/unraid_power_handler.sh +++ b/etc/acpi/unraid_power_handler.sh @@ -11,29 +11,65 @@ # dynamix.cfg and read live on each event, so no acpid reload is needed # when the setting changes. # +# Plugins can add or override actions by dropping an executable handler in +# /etc/acpi/powerbutton.d/ - see /etc/acpi/powerbutton.d/README. +# # LimeTech - modified for Unraid OS CFG=/boot/config/plugins/dynamix/dynamix.cfg +PLUGIN_DIR=/etc/acpi/powerbutton.d # acpid passes the raw event line, e.g.: "button/power PBTN 00000080 00000000" set -- $@ group=${1%%/*} action=${1#*/} +run_powerbutton(){ + local behavior=$1 + case "$behavior" in + ignore) + logger -t acpid "Power button: ignored (Power Options setting)" + ;; + reboot) + logger -t acpid "Power button: rebooting" + /sbin/init 6 + ;; + sleep) + # Let the S3 Sleep plugin (if installed) perform array-aware prep, + # otherwise fall back to a generic suspend-to-RAM. + if [[ -x $PLUGIN_DIR/sleep ]]; then + logger -t acpid "Power button: sleeping (plugin handler)" + "$PLUGIN_DIR/sleep" + elif grep -qw mem /sys/power/state 2>/dev/null; then + logger -t acpid "Power button: suspending to RAM" + sync + echo -n mem > /sys/power/state + else + logger -t acpid "Power button: suspend not supported, ignoring" + fi + ;; + shutdown) + logger -t acpid "Power button: initiating clean shutdown" + /sbin/init 0 + ;; + *) + # Extension point: plugin-provided action handler. + if [[ -n $behavior && -x $PLUGIN_DIR/$behavior ]]; then + logger -t acpid "Power button: running plugin action '$behavior'" + "$PLUGIN_DIR/$behavior" + else + logger -t acpid "Power button: unknown action '$behavior', ignoring" + fi + ;; + esac +} + case "$group" in button) case "$action" in power) behavior=$(grep -Pom1 '^powerbutton="\K[^"]+' "$CFG" 2>/dev/null) - case "${behavior:-shutdown}" in - ignore) - logger -t acpid "Power button pressed - ignored (Power Options setting)" - ;; - *) - logger -t acpid "Power button pressed - initiating clean shutdown" - /sbin/init 0 - ;; - esac + run_powerbutton "${behavior:-shutdown}" ;; *) logger -t acpid "ACPI action button/$action is not handled" diff --git a/etc/rc.d/rc.acpid b/etc/rc.d/rc.acpid index 7854918b56..4aaf559a14 100755 --- a/etc/rc.d/rc.acpid +++ b/etc/rc.d/rc.acpid @@ -24,6 +24,8 @@ acpid_running(){ # the setting changes. acpid_configure(){ local SRC=/etc/acpi/unraid_power_handler.sh + # Drop-in dir for plugin-provided power button actions (see README there). + mkdir -p /etc/acpi/powerbutton.d if [[ -f $SRC ]]; then install -m 0755 "$SRC" /etc/acpi/acpi_handler.sh fi From 1e7413bd23ab37e4fba41294c9e964897bd4ba0e Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Thu, 18 Jun 2026 23:44:12 -0400 Subject: [PATCH 4/5] refactor(power): make sleep a plugin-provided action, not core Drop the built-in Sleep option and its generic echo-mem suspend. A core suspend bypasses the array/Docker/VM/wake prep the S3 Sleep plugin performs and is hardware-fragile, and Unraid intentionally ships S3 sleep as a plugin. Core now offers only the trivially-safe primitives (Shut down, Reboot, Do nothing). Sleep is added by the S3 Sleep plugin via the same extension hook used for other plugin actions: it ships /etc/acpi/powerbutton.d/sleep and a powerbutton-option.php fragment. The handler already routes powerbutton="sleep" through the plugin dir. Co-Authored-By: Claude Opus 4.8 --- emhttp/languages/en_US/helptext.txt | 4 +--- emhttp/plugins/dynamix/PowerButton.page | 5 ----- etc/acpi/powerbutton.d/README | 11 ++++++++--- etc/acpi/unraid_power_handler.sh | 14 -------------- 4 files changed, 9 insertions(+), 25 deletions(-) diff --git a/emhttp/languages/en_US/helptext.txt b/emhttp/languages/en_US/helptext.txt index 5fe70b3723..9eed2d0feb 100644 --- a/emhttp/languages/en_US/helptext.txt +++ b/emhttp/languages/en_US/helptext.txt @@ -1655,11 +1655,9 @@ Choose what happens when the server's physical power button is pressed. **Reboot** performs a clean reboot. -**Sleep (suspend)** suspends the server to RAM (only shown when the hardware supports it). If the S3 Sleep plugin is installed, its prep is used. - **Do nothing** ignores the button press and only logs the event. This is useful on small form factor hardware where the power button is easily bumped, to prevent accidental shutdowns. -Plugins may add further actions (for example, running a User Script). +Plugins may add further actions (for example, Sleep via the S3 Sleep plugin, or running a User Script). :end :display_settings_help: diff --git a/emhttp/plugins/dynamix/PowerButton.page b/emhttp/plugins/dynamix/PowerButton.page index fa90970cdd..d9196427c1 100644 --- a/emhttp/plugins/dynamix/PowerButton.page +++ b/emhttp/plugins/dynamix/PowerButton.page @@ -16,8 +16,6 @@ Tag="icon-power" ?>
@@ -28,9 +26,6 @@ _(Physical power button)_: :