Skip to content

feat(power): configurable power button behavior + rename Power Mode → Power Options (OS-463)#2671

Open
elibosley wants to merge 6 commits into
masterfrom
feature/os-463-power-options
Open

feat(power): configurable power button behavior + rename Power Mode → Power Options (OS-463)#2671
elibosley wants to merge 6 commits into
masterfrom
feature/os-463-power-options

Conversation

@elibosley

@elibosley elibosley commented Jun 19, 2026

Copy link
Copy Markdown
Member

Summary

Implements OS-463: a native WebUI setting to control what the physical power button does, so an accidental bump on small-form-factor hardware (NUCs, etc.) no longer forces a shutdown.

The existing Power Mode settings page is renamed to Power Options and split into two tabs:

  • Power Mode — the existing CPU governor controls (moved verbatim).
  • Power Button — new action selector.

Power Button actions

Core ships only the trivially-safe primitives:

Action Behavior
Shut down (default) Clean shutdown (/sbin/init 0) — preserves today's behavior
Reboot Clean reboot (/sbin/init 6)
Do nothing Logs the press and takes no action — the core ask in OS-463

The choice is saved to [powermode] powerbutton= in dynamix.cfg (on the flash, so it persists) and read live on each press, so changes take effect with no reload or reboot. Unset/unexpected values fall back to Shut down.

Two handlers, both neutralized

Hardware testing surfaced that a single power press fires two independent events:

  1. acpid/etc/acpi/acpi_handler.sh. We install our setting-aware handler over the stock Slackware one at boot via rc.acpid (acpid_configure), so it wins regardless of package install order — mirroring how rc.cpufreq applies the [powermode] governor at boot.
  2. elogind → grabs the Power Button input device (PNP0C0C); its default HandlePowerKey=poweroff shut the box down regardless of acpid. A shipped logind.conf.d drop-in sets HandlePowerKey=ignore, making acpid the sole authority.

Both were verified on real hardware (elogind D-Bus HandlePowerKey now reports ignore).

Plugin extension hook

Actions that need their own setup or are hardware-fragile (suspend, running a script) are not baked into core. Instead the power button is extensible so plugins add actions in their own repos:

  • UI: PowerButton.page includes plugins/*/include/powerbutton-option.php (and powerbutton-extra.php for action-specific fields).
  • Action: unraid_power_handler.sh dispatches any non-builtin action to /etc/acpi/powerbutton.d/<action>.
  • Contract documented in etc/acpi/powerbutton.d/README.

Follow-up work to wire the first two consumers (tracked as sub-tasks of OS-463):

  • S3 Sleep plugin → adds a Sleep action.
  • User Scripts plugin → adds a Run script action.

Files

File Change
emhttp/plugins/dynamix/PowerMode.page Now a tabbed container titled "Power Options" (basename kept, so /Settings/PowerMode URLs still work)
emhttp/plugins/dynamix/PowerModeCpu.page New — tab 1, the CPU governor form
emhttp/plugins/dynamix/PowerButton.page New — tab 2, action selector + plugin include hooks
etc/acpi/unraid_power_handler.sh New — setting-aware ACPI handler with plugin dispatch
etc/acpi/powerbutton.d/README New — plugin extension contract
etc/rc.d/rc.acpid Installs the handler + creates powerbutton.d/ at boot
etc/elogind/logind.conf.d/20-unraid-powerbutton.conf New — elogind ignores the power key
emhttp/languages/en_US/helptext.txt Help text for the new setting

Testing

  • bash -n clean on the handler and rc.acpid; php -l clean on both page bodies.
  • Handler routing verified for every action (shutdown / reboot / ignore / unset-default) plus plugin dispatch and missing-handler fallback.
  • Boot-time install simulated and verified live on devgen.local; acpid + elogind both confirmed neutralized for "Do nothing".

Notes for reviewers

  • The new etc/* files (acpi, elogind, rc.acpid) reach a system via the package build, not the deploy_to_unraid.sh dev script (which only syncs emhttp/).
  • Scope per OS-463 is Shut down + Do nothing; Reboot is a cheap safe addition. Suspend and Run-script are deliberately left to their plugins via the hook.

Summary by CodeRabbit

  • New Features
    • Added Power Options settings for configuring physical power-button behavior.
    • Choose clean shutdown, reboot, or no action when the button is pressed.
    • Added CPU power-mode selection with availability indicators and virtual-machine guidance.
    • Enabled plugin-provided power-button actions and additional configuration fields.
  • Documentation
    • Added help text and integration guidance for power-button actions.

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds configurable physical power-button behavior with shutdown, reboot, ignore, and plugin-defined actions. The ACPI and elogind integration routes events through the new handler. A WebUI page exposes the setting. The Power Options menu now includes a separate CPU governor configuration page.

Changes

Physical Power Button Behavior

Layer / File(s) Summary
ACPI handler and daemon wiring
etc/acpi/unraid_power_handler.sh, etc/rc.d/rc.acpid, etc/elogind/logind.conf.d/20-unraid-powerbutton.conf
Handles debounced power-button events, dispatches configured shutdown, reboot, ignore, or plugin actions, installs the handler before acpid starts, and disables elogind power-key handling.
Power-button configuration UI and extension contract
emhttp/plugins/dynamix/PowerButton.page, emhttp/languages/en_US/helptext.txt, etc/acpi/powerbutton.d/README
Adds shutdown, reboot, ignore, and plugin-provided options, conditional extra UI, helptext, and documentation for action extensions.
Installation-time daemon reactivation
.github/scripts/generate-pr-plugin.sh
Conditionally restarts elogind and acpid when their overlaid files are changed during installation.

Power Options Menu Reorganization

Layer / File(s) Summary
CPU governor configuration page
emhttp/plugins/dynamix/PowerModeCpu.page
Adds availability-aware CPU governor radio controls, submission logic, unavailable labels, and a hypervisor notice.
Power Options menu metadata
emhttp/plugins/dynamix/PowerMode.page
Renames the menu to “Power Options”, enables tabs, and updates icon metadata.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant update.php
  participant dynamix.cfg
  participant acpid
  participant unraid_power_handler
  participant init

  Browser->>update.php: Submit selected powerbutton action
  update.php->>dynamix.cfg: Write configured action
  acpid->>unraid_power_handler: Dispatch button/power event
  unraid_power_handler->>dynamix.cfg: Read current action
  alt shutdown or reboot
    unraid_power_handler->>init: Invoke clean shutdown or reboot
  else ignore
    unraid_power_handler->>unraid_power_handler: Log event
  else plugin action
    unraid_power_handler->>unraid_power_handler: Execute configured extension
  end
Loading

Poem

🐇 A button hops, the choices grow,
Shut down, reboot, or softly say “no.”
Plugins add tricks in the action den,
While CPU modes find their page again.
Power Options shines in a tidier glow!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: configurable power button behavior and the Power Mode to Power Options rename.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/os-463-power-options

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown

🔧 PR Test Plugin Available

A test plugin has been generated for this PR that includes the modified files.

Version: 2026.07.21.1743
Build: View Workflow Run

📥 Installation Instructions:

Install via Unraid Web UI:

  1. Go to Plugins → Install Plugin
  2. Copy and paste this URL:
https://preview.dl.unraid.net/pr-plugins/pr-2671/webgui-pr-2671.plg
  1. Click Install

Alternative: Direct Download

⚠️ Important Notes:

  • Testing only: This plugin is for testing PR changes
  • Backup included: Original files are automatically backed up
  • Easy removal: Files are restored when plugin is removed
  • Conflicts: Remove this plugin before installing production updates
  • Post-merge behavior: This preview stays available after merge until preview storage expires or it is manually cleaned up

📝 Modified Files:

Click to expand file list
emhttp/languages/en_US/helptext.txt
emhttp/plugins/dynamix/PowerButton.page
emhttp/plugins/dynamix/PowerMode.page
emhttp/plugins/dynamix/PowerModeCpu.page
etc/acpi/powerbutton.d/README
etc/acpi/unraid_power_handler.sh
etc/elogind/logind.conf.d/20-unraid-powerbutton.conf
etc/rc.d/rc.acpid

🔄 To Remove:

Navigate to Plugins → Installed Plugins and remove webgui-pr-2671, or run:

plugin remove webgui-pr-2671

🤖 This comment is automatically generated and will be updated with each new push to this PR.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@emhttp/plugins/dynamix/PowerModeCpu.page`:
- Around line 38-40: The jQuery selector `$(this).next('span')` in the disabled
radio button check loop is not finding the correct elements because radio inputs
are not directly followed by span siblings, but rather by text nodes or
differently structured elements. Inspect the actual HTML structure around the
radio inputs to determine what element actually follows them, then update the
traversal method in the loop (replacing `.next('span')`) to correctly locate the
element that should receive the unavailable label text. This may require using a
different jQuery method like .closest(), .parent(), or .find() depending on the
actual DOM hierarchy.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 14608339-fb32-4abb-a61e-ba54b9d0fe05

📥 Commits

Reviewing files that changed from the base of the PR and between cc6a800 and 2e3f39f.

📒 Files selected for processing (6)
  • emhttp/languages/en_US/helptext.txt
  • emhttp/plugins/dynamix/PowerButton.page
  • emhttp/plugins/dynamix/PowerMode.page
  • emhttp/plugins/dynamix/PowerModeCpu.page
  • etc/acpi/unraid_power_handler.sh
  • etc/rc.d/rc.acpid

Comment on lines +38 to +40
$('input[type=radio]').each(function(){
if ($(this).prop('disabled')) $(this).next('span').html(" <i>(_(unavailable)_)</i>");
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Unavailable-label injection currently never matches the DOM.

At Line 39, $(this).next('span') does not find anything because the radio inputs are followed by text nodes, not <span> siblings. As written, disabled options won’t get the “unavailable” note.

Suggested fix
 $(function(){
   $('input[type=radio]').each(function(){
-    if ($(this).prop('disabled')) $(this).next('span').html(" <i>(_(unavailable)_)</i>");
+    if ($(this).prop('disabled')) {
+      $(this).after(" <i>(_(unavailable)_)</i>");
+    }
   });
 <?if (exec("dmesg | grep -Pom1 'Hypervisor detected'")):?>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$('input[type=radio]').each(function(){
if ($(this).prop('disabled')) $(this).next('span').html(" <i>(_(unavailable)_)</i>");
});
$(function(){
$('input[type=radio]').each(function(){
if ($(this).prop('disabled')) {
$(this).after(" <i>(_(unavailable)_)</i>");
}
});
<?if (exec("dmesg | grep -Pom1 'Hypervisor detected'")):?>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@emhttp/plugins/dynamix/PowerModeCpu.page` around lines 38 - 40, The jQuery
selector `$(this).next('span')` in the disabled radio button check loop is not
finding the correct elements because radio inputs are not directly followed by
span siblings, but rather by text nodes or differently structured elements.
Inspect the actual HTML structure around the radio inputs to determine what
element actually follows them, then update the traversal method in the loop
(replacing `.next('span')`) to correctly locate the element that should receive
the unavailable label text. This may require using a different jQuery method
like .closest(), .parent(), or .find() depending on the actual DOM hierarchy.

@elibosley

Copy link
Copy Markdown
Member Author

Update — hardware testing on devgen.local revealed a second handler.

The power button generates two events: an ACPI event (acpid → acpi_handler.sh) and an input-key event handled by elogind, whose default HandlePowerKey=poweroff shut the box down regardless of the acpid handler. There's a Power Button input device (PNP0C0C) that elogind grabs, so the acpid-only fix was not sufficient.

Added etc/elogind/logind.conf.d/20-unraid-powerbutton.conf (HandlePowerKey=ignore, HandlePowerKeyLongPress=ignore) so acpid is the sole authority over the power button. Verified live: elogind's D-Bus HandlePowerKey now reports "ignore" and the button no longer powers off in "Do nothing" mode.

Note for reviewers: the new etc/acpi/* and etc/elogind/* files land via the package build, not the deploy_to_unraid.sh dev script (which only syncs emhttp/).

@elibosley

Copy link
Copy Markdown
Member Author

Update — added more actions + a plugin extension point (commit 98fdab5ea)

Power Button options are now: Shut down (default), Reboot, Sleep (suspend) (shown only when the kernel reports suspend-to-RAM support), and Do nothing.

Rather than baking plugin-specific actions into core, the power button is now extensible so plugins add their own actions in their own repos:

  • UI hook: PowerButton.page includes plugins/*/include/powerbutton-option.php (and powerbutton-extra.php for action-specific fields).
  • Action hook: unraid_power_handler.sh dispatches non-builtin actions to /etc/acpi/powerbutton.d/<action>. The builtin Sleep also defers to powerbutton.d/sleep if present, so the S3 Sleep plugin can do array-aware prep before suspend.
  • Contract documented in etc/acpi/powerbutton.d/README.

This is the mechanism the User Scripts plugin would use to add a "Run script" action — intentionally left to that plugin rather than coupled into core.

Verified on devgen.local: all four handler branches (shutdown/reboot/sleep/ignore) dispatch correctly, the sleep plugin-override and generic echo mem fallback both work, and unknown/plugin actions route through powerbutton.d/.

@elibosley

Copy link
Copy Markdown
Member Author

Update — Sleep is now plugin-provided, not a core option (commit b23289324)

Dropped the built-in Sleep option and its generic echo mem suspend. Rationale: a core suspend bypasses the array/Docker/VM/wake prep the S3 Sleep plugin does and is hardware-fragile, and Unraid intentionally ships S3 sleep as a plugin rather than core.

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 as any other plugin action (/etc/acpi/powerbutton.d/sleep + powerbutton-option.php). The handler already routes powerbutton="sleep" through the plugin dir, so no special-casing remains in core.

elibosley and others added 4 commits June 19, 2026 10:36
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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/<action>
  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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
@elibosley
elibosley force-pushed the feature/os-463-power-options branch from b232893 to 1e7413b Compare June 19, 2026 14:37
elibosley added a commit that referenced this pull request Jun 23, 2026
…rop shared-file edits

- Save mode is now much faster: pipe the single-pass streaming zip
  (scripts/flash_backup) into the destination file instead of appending each
  top-level item to a growing archive (one zip invocation vs many).
- On completion the save job raises an Unraid notification (the tray bell) with a
  click-to-download link, exposes the saved file for download via a docroot
  symlink, and the GUI shows a Download button next to the saved path.
- Revert the helptext.txt and .gitignore edits to base so this PR touches only
  its own files. helptext.txt was the single file shared with other in-flight PR
  test plugins (#2671, #2673), which made the stacked PR-plugin patch fail to
  apply; with it gone, #2677 stacks cleanly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
QA (jorgeb) failed PR 2671. Fixes:

- Preview-plugin activation: the PR test plugin patches /etc late (after
  rc.M started acpid/elogind with the stock config) and re-applies every
  boot, so the new ACPI handler + elogind power-key drop-in never took
  effect and "Do nothing" still shut the server down. generate-pr-plugin.sh
  now restarts acpid/elogind when the patch touches their files. (A real
  release bakes these into bzroot, so its boot ordering is already correct.)
- Force tabbed rendering of Power Options (Tabs="true") so Power Mode and
  Power Button show as tabs instead of stacked sections.
- Let the "no available power modes" notice grow (height:auto) so it no
  longer overflows and overlaps the Power Button heading at 390px.
- Debounce duplicate power-button ACPI events so a doubled event is not
  logged (or acted on) twice.
…r-options

# Conflicts:
#	.github/scripts/generate-pr-plugin.sh

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
etc/acpi/unraid_power_handler.sh (1)

43-46: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider a timeout around plugin action invocation.

"$PLUGIN_DIR/$behavior" runs synchronously with no bound on execution time. A wedged or misbehaving plugin handler (e.g. a Sleep plugin stuck on array-quiesce prep) would stall this handler indefinitely, delaying/losing subsequent power-button events depending on acpid's dispatch model.

♻️ Suggested guard
       logger -t acpid "Power button: running plugin action '$behavior'"
-      "$PLUGIN_DIR/$behavior"
+      timeout 30 "$PLUGIN_DIR/$behavior"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@etc/acpi/unraid_power_handler.sh` around lines 43 - 46, Wrap the plugin
action invocation in the power-button handler with a bounded timeout so a wedged
executable cannot block event processing indefinitely. Preserve the existing
executable check, logging, and plugin arguments while applying the timeout
specifically to "$PLUGIN_DIR/$behavior".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/scripts/generate-pr-plugin.sh:
- Around line 283-295: Update the elogind handling block in the payload-change
logic to avoid invoking `/etc/rc.d/rc.elogind restart`, which can terminate
active sessions. Use the supported non-disruptive reload mechanism, or defer
elogind configuration changes until reboot; leave the separate acpid restart
behavior unchanged.

In `@etc/acpi/unraid_power_handler.sh`:
- Around line 41-49: Update the unknown-action fallback in run_powerbutton so
non-empty values that match neither a built-in action nor an executable plugin
invoke the documented shutdown behavior instead of being ignored. Preserve the
plugin execution path for valid executable handlers and retain shutdown handling
for unset values.
- Around line 41-49: Validate the config-derived behavior value before using it
in the plugin path within the wildcard action branch: accept only a simple
executable name with no slash or path traversal components, and ignore invalid
values. Keep the existing executable check, logging, and invocation behavior for
validated plugin names, using the behavior handling in the power-button case as
the change point.

---

Nitpick comments:
In `@etc/acpi/unraid_power_handler.sh`:
- Around line 43-46: Wrap the plugin action invocation in the power-button
handler with a bounded timeout so a wedged executable cannot block event
processing indefinitely. Preserve the existing executable check, logging, and
plugin arguments while applying the timeout specifically to
"$PLUGIN_DIR/$behavior".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1e8c5336-c9a0-4852-b50f-70dc985a4369

📥 Commits

Reviewing files that changed from the base of the PR and between 2e3f39f and 1bc8a52.

📒 Files selected for processing (9)
  • .github/scripts/generate-pr-plugin.sh
  • emhttp/languages/en_US/helptext.txt
  • emhttp/plugins/dynamix/PowerButton.page
  • emhttp/plugins/dynamix/PowerMode.page
  • emhttp/plugins/dynamix/PowerModeCpu.page
  • etc/acpi/powerbutton.d/README
  • etc/acpi/unraid_power_handler.sh
  • etc/elogind/logind.conf.d/20-unraid-powerbutton.conf
  • etc/rc.d/rc.acpid
🚧 Files skipped from review as they are similar to previous changes (5)
  • emhttp/plugins/dynamix/PowerMode.page
  • emhttp/languages/en_US/helptext.txt
  • emhttp/plugins/dynamix/PowerButton.page
  • etc/rc.d/rc.acpid
  • emhttp/plugins/dynamix/PowerModeCpu.page

Comment on lines +283 to +295
if [ -s "$PAYLOAD/text_files.txt" ]; then
# elogind owns the physical power key unless a drop-in tells it to ignore it;
# it only reads logind.conf.d/ at startup, so reload it when that changes.
if grep -qE 'etc/elogind/|rc\.d/rc\.elogind$' "$PAYLOAD/text_files.txt" && [ -x /etc/rc.d/rc.elogind ]; then
echo "Reloading elogind (session/power-key policy changed)..."
/etc/rc.d/rc.elogind restart >/dev/null 2>&1 || true
fi
# rc.acpid installs Unraid's ACPI handler over the stock one at (re)start.
if grep -qE 'etc/acpi/|rc\.d/rc\.acpid$' "$PAYLOAD/text_files.txt" && [ -x /etc/rc.d/rc.acpid ]; then
echo "Restarting acpid (ACPI handler changed)..."
/etc/rc.d/rc.acpid restart >/dev/null 2>&1 || true
fi
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

does restarting elogind service terminate active login sessions

💡 Result:

Yes, restarting the elogind service typically causes the loss of all existing login sessions [1]. Elogind acts as a system daemon that manages user logins, tracks sessions, and maintains seat information [2][3]. When the service is restarted via the standard service manager, it generally triggers a stop operation that cleans up these sessions and seats, effectively terminating them [1][2]. Key technical considerations include: - Session Data Loss: Restarting the elogind service often results in the system failing to recognize previously active sessions, causing them to be considered stale or lost [4][5]. - Preferred Alternatives: To refresh configuration changes without terminating sessions, the command loginctl reload is recommended instead of restarting the service [1]. - Potential for Instability: Even if session processes (like the desktop environment or user shells) continue to run, the loss of elogind's tracking information can lead to various issues, such as failure to access system resources (e.g., audio, removable media) or improper handling of power management and shutdown requests [1][4][6][7]. In some specialized configurations, users may attempt to work around this by using specific signals to prevent the daemon from clearing session data upon exit, though this is generally discouraged and may not reliably preserve all session functionality across different distributions and software environments [4][6][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files '.github/scripts/generate-pr-plugin.sh' 'README.md' 2>/dev/null || true
wc -l .github/scripts/generate-pr-plugin.sh
sed -n '260,310p' .github/scripts/generate-pr-plugin.sh

rg -n "elogind|loginctl reload|rc\.elogind" .github/scripts README.md .github -S || true

Repository: unraid/webgui

Length of output: 4019


Avoid restarting elogind live This restarts elogind and can drop active login sessions; use a non-disruptive reload path or defer the change until reboot.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/generate-pr-plugin.sh around lines 283 - 295, Update the
elogind handling block in the payload-change logic to avoid invoking
`/etc/rc.d/rc.elogind restart`, which can terminate active sessions. Use the
supported non-disruptive reload mechanism, or defer elogind configuration
changes until reboot; leave the separate acpid restart behavior unchanged.

Comment on lines +41 to +49
*)
# 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
;;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Unexpected config values don't fall back to shutdown, contradicting the documented default.

The PR spec states the power button "defaults to shutdown for unset or unexpected values," but this code only defaults to shutdown when behavior is empty (${behavior:-shutdown} at line 68). Once inside run_powerbutton, a non-empty value that matches no built-in and has no executable under $PLUGIN_DIR falls into the *) branch and merely logs "unknown action ... ignoring" — the button silently does nothing instead of shutting down. A corrupted or stale dynamix.cfg value (e.g. from a removed plugin) would leave the physical power button unresponsive.

🔧 Proposed fix
     else
-      logger -t acpid "Power button: unknown action '$behavior', ignoring"
+      logger -t acpid "Power button: unknown action '$behavior', defaulting to shutdown"
+      /sbin/init 0
     fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
*)
# 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
;;
*)
# 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', defaulting to shutdown"
/sbin/init 0
fi
;;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@etc/acpi/unraid_power_handler.sh` around lines 41 - 49, Update the
unknown-action fallback in run_powerbutton so non-empty values that match
neither a built-in action nor an executable plugin invoke the documented
shutdown behavior instead of being ignored. Preserve the plugin execution path
for valid executable handlers and retain shutdown handling for unset values.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Unsanitized config value used as a path component enables path traversal.

$behavior comes straight from dynamix.cfg (line 67) and is concatenated into $PLUGIN_DIR/$behavior with only an -x existence check — no validation that it excludes /. A value like powerbutton="../../../some/other/executable" would pass the -x check and get executed by root on every power-button press, reaching outside the intended /etc/acpi/powerbutton.d/ extension point. PowerButton.page's client-side validation isn't visible in this batch, so the handler shouldn't rely on it being the only guard.

🔒 Proposed fix
   *)
     # Extension point: plugin-provided action handler.
-    if [[ -n $behavior && -x $PLUGIN_DIR/$behavior ]]; then
+    if [[ -n $behavior && $behavior != */* && -x $PLUGIN_DIR/$behavior ]]; then
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
*)
# 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
;;
*)
# Extension point: plugin-provided action handler.
if [[ -n $behavior && $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
;;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@etc/acpi/unraid_power_handler.sh` around lines 41 - 49, Validate the
config-derived behavior value before using it in the plugin path within the
wildcard action branch: accept only a simple executable name with no slash or
path traversal components, and ignore invalid values. Keep the existing
executable check, logging, and invocation behavior for validated plugin names,
using the behavior handling in the power-button case as the change point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant