Skip to content

fix: make the backlight fix reversible and portable across distributions - #159

Merged
Stensel8 merged 3 commits into
mainfrom
claude/backlight-fix-script-audit-sae4gz
Sep 17, 2026
Merged

Stensel8 merged 3 commits into
mainfrom
claude/backlight-fix-script-audit-sae4gz

Conversation

@Stensel8

@Stensel8 Stensel8 commented Sep 17, 2026

Copy link
Copy Markdown
Member

Audits zephyrus-backlight.py and fixes five ways it could leave a system's backlight configuration worse off than it found it, without changing what the script is for.

Type of change

  • feat: new page or feature
  • fix: bug fix (broken link, incorrect command, layout issue)
  • content: update or improve existing content
  • docs: changes to CONTRIBUTING, README, or meta documentation
  • chore: maintenance (dependencies, config, CI/CD)
  • refactor: restructuring without content changes
  • style: formatting, whitespace, typos
  • revert: reverting a previous commit

PR title and commit types must follow these standards, see the contributing guide

Details

Nothing in the script could make a machine unbootable: it never runs grub-install, never touches the ESP or NVRAM, and grub-mkconfig validates with grub-script-check before installing anything. Five things could still leave a configuration worse than they found it:

  • The modprobe rule ran /usr/bin/modprobe, which only exists on Fedora 44+ where /usr/sbin became a symlink to bin. On an older base the install rule exits 127 and the module never loads, silently. /sbin/modprobe resolves everywhere this script supports.
  • disable removed acpi_backlight=native but never restored the value enable had replaced, so a machine that started with acpi_backlight=vendor ended up with neither. It now reads the old value back out of the backup enable writes.
  • The modprobe rule was overwritten without a backup and deleted outright on disable, under a filename generic enough that someone else may already be using it. A rule this script didn't write is now moved aside first and restored on disable, never deleted without leaving a copy (kept under /var/lib since older kmod still reads files in /etc/modprobe.d that don't end in .conf).
  • enable refused to run on anything that isn't a GA605WV; disable didn't, so it would strip acpi_backlight=native from an unrelated laptop where it was somebody else's fix. It now asks first, and refuses under --silent.
  • The root script read the rule and GRUB contents from files an unprivileged process could still write, leaving a TOCTOU window between the polkit prompt and root's read. Root now writes every file it reads.

Applying also now says up front how long it takes: rpm-ostree kargs writes a whole deployment with no output for 30-60s, which reads as a hang. The GRUB path traps INT/TERM and restores the old file, so a Ctrl+C during that window costs nothing.

Checklist

  • PR title follows the commit convention (e.g. fix: correct nmcli command in eduroam guide)
  • Both EN and NL versions updated (if applicable)
  • Media is in AVIF format (not PNG/JPG)
  • No broken image references (/images/*.avif all exist in static/images/)
  • Tested locally with hugo server

Audit of zephyrus-backlight.py. Nothing in it could make a machine
unbootable: it never runs grub-install, never touches the ESP or NVRAM,
and grub-mkconfig validates with grub-script-check before installing
anything. Five things could still leave a configuration worse than they
found it.

- The modprobe rule ran /usr/bin/modprobe, which only exists on Fedora 44
  and newer, where /usr/sbin became a symlink to bin. On an older base the
  install rule exits 127 and the module never loads, so brightness stays
  dead in Hybrid mode with no visible error. /sbin/modprobe resolves
  everywhere this script supports.

- disable removed acpi_backlight=native but never put back the value that
  enable had replaced, so a machine that started with acpi_backlight=vendor
  ended up with neither. It now reads the old value back out of the backup
  that enable writes.

- The modprobe rule was overwritten without a backup and deleted outright
  on disable, under a filename generic enough that somebody may already be
  using it. A rule this script did not write is now moved aside first and
  restored on disable, and is never deleted without leaving a copy. The
  copy lives under /var/lib rather than in /etc/modprobe.d, because older
  kmod still reads files there that do not end in .conf.

- enable refused to run on anything that is not a GA605WV; disable did not,
  so it would strip acpi_backlight=native from an unrelated laptop where it
  was somebody else's fix. It now asks first, and refuses under --silent.

- The root script read the rule and the GRUB contents from files this
  unprivileged process could still write, leaving a window to swap them
  between the polkit prompt and the copy. Root now writes every file it
  reads.

Applying also says up front how long it takes. rpm-ostree kargs writes a
whole deployment with no output for 30 to 60 seconds, which reads as a
hang, so Ctrl+C is the natural reaction. The GRUB path now traps INT and
TERM and puts the old file back, so that reaction costs nothing either.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EDz8am5VoAy2xbgHunuim
Copilot AI lite review requested due to automatic review settings September 17, 2026 12:53
@Stensel8 Stensel8 self-assigned this Sep 17, 2026

Copilot AI 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.

🟡 Changes recommended

Unresolved rollback, restoration, and partial-application safety issues remain in the backlight script.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Updates the backlight utility to improve portability, reversibility, rollback handling, and documentation across GRUB and rpm-ostree systems.

Changes:

  • Uses portable modprobe paths and safer configuration backups.
  • Adds interruption handling, restoration logic, and model confirmation.
  • Updates English and Dutch documentation checksums and guidance.
File summaries
File Description
src/static/scripts/zephyrus-backlight.py Implements portability, backups, rollback, and safety checks; has unresolved review findings.
src/content/docs/known-issues.md Updates usage guidance and checksum.
src/content/docs/known-issues.nl.md Updates Dutch guidance and checksum.
Review details

Suppressed comments (4)

src/static/scripts/zephyrus-backlight.py:721

  • On a disable, this overwrites the persistent GRUB_BACKUP, which is the only source replaced_by_enable() uses for restoring a value such as acpi_backlight=vendor, before the new file has been validated. If grub-mkconfig then fails or is interrupted, put_back() restores the just-copied native file and the original vendor backup is already lost; a retry removes native without restoring vendor. Keep the enable backup separate from the per-run rollback copy, or update it only after disable succeeds.
cp -p {q(str(GRUB_DEFAULT))} {q(str(GRUB_BACKUP))}
put_back() {{ cp -p {q(str(GRUB_BACKUP))} {q(str(GRUB_DEFAULT))} 2>/dev/null || true; }}

src/static/scripts/zephyrus-backlight.py:752

  • When disable is run before this script has ever enabled (so no backup exists), a pre-existing different rule enters this branch, is copied aside, and then is unconditionally removed. That still deactivates an unrelated user's modprobe configuration; the backup only helps recovery and is not restored. If there is no script-owned backup, leave a different rule untouched or require explicit confirmation instead of treating it as this fix's rule.
    if [ -e {q(str(MODPROBE_CONF))} ] \\
       && ! cmp -s "$work/rule.conf" {q(str(MODPROBE_CONF))}; then
        install -D -m 644 {q(str(MODPROBE_CONF))} {q(str(MODPROBE_BACKUP))}
    fi
    rm -f {q(str(MODPROBE_CONF))}

src/static/scripts/zephyrus-backlight.py:721

  • If the restore copy fails, 2>/dev/null || true hides that failure and the caller still reports Nothing was changed on exit 130 (and the failure path otherwise continues). The new configuration can therefore remain in /etc/default/grub; this safety path should verify the restore and surface an error when it cannot put the old file back.
put_back() {{ cp -p {q(str(GRUB_BACKUP))} {q(str(GRUB_DEFAULT))} 2>/dev/null || true; }}

src/static/scripts/zephyrus-backlight.py:559

  • restore can contain multiple original acpi_backlight= tokens because enable records every matching token. After the first is appended, this key check suppresses all remaining saved values, so duplicate kernel arguments are not restored to their original order/content and the change is not fully reversible.
                # Only when nothing else claims that key now, so a value the user
                # set by hand after enabling wins over the one from the backup.
                if not any(p.startswith(key) for p in params):
                    params.append(old)
                    replaced.append(old)
  • Files reviewed: 3/3 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/static/scripts/zephyrus-backlight.py
Comment thread src/static/scripts/zephyrus-backlight.py
Comment thread src/static/scripts/zephyrus-backlight.py Outdated
Comment thread src/static/scripts/zephyrus-backlight.py
GRUB_BACKUP served two conflicting purposes: the transient rollback
copy for undoing a single run, and the persistent record disable
reads to restore the pre-fix acpi_backlight= value. Every GRUB-touching
run overwrote it before validating anything, so a failed or interrupted
disable clobbered the persistent record with the fix's own value, and a
retry lost the original permanently. The rollback copy now lives under
the per-run $work dir instead, and the persistent backup is only
written by enable and only cleared by a successful disable.

Also, from the same review:
- put_back() silently swallowed its own failure, so a restore that
  didn't actually happen could still be reported as "nothing changed".
  It now surfaces PUT_BACK_FAILED instead.
- disable's modprobe removal only touches /etc/modprobe.d's rule now
  when the live file still matches the one this script installed,
  instead of deleting or replacing whatever is there whenever a rule
  exists at that path.
- rewrite_grub() only restores a backed-up acpi_backlight= value when
  the fix is actually present to remove, so a stale backup left over
  from a manually-undone enable can't reintroduce it. It also restores
  every original value when there was more than one, instead of only
  the first.

All eight `_apply` code paths (enable/disable x GRUB/ostree x write/
remove/none) were regenerated and checked with `bash -n`, and
rewrite_grub()'s new branches were exercised directly, both outside
the actual privileged execution.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019tcTRe2u556WqGciAvDh7G

Stensel8 commented Sep 17, 2026

Copy link
Copy Markdown
Member Author

Pushed a549eab addressing the Copilot review. Summary of the review's four collapsed ("suppressed") findings, since there's no inline thread to reply to for those individually:

  • GRUB_BACKUP overwritten before validation / lost on a failed-then-retried disable: real bug. GRUB_BACKUP was doing two jobs — the transient copy put_back() uses to undo a single run, and the persistent record replaced_by_enable() reads on a later disable. Every GRUB-touching run overwrote it before grub-mkconfig even validated the new content, so a failed or interrupted disable clobbered the persistent record with the fix's own value, and a retry lost the original permanently. Fixed: the rollback copy now lives under the per-run $work dir, and the persistent backup is only written by enable and only cleared by a successful disable.
  • disable removing a pre-existing, never-backed-up "different" rule: real bug, same root cause as the separately-threaded finding below — fixed by the same change (only touch MODPROBE_CONF when it still matches what this script installed).
  • put_back()'s 2>/dev/null || true hiding a failed restore: real bug — a failed restore could still be reported as "Interrupted. Nothing was changed." Fixed: put_back() now surfaces a distinct PUT_BACK_FAILED exit code with its own message instead of swallowing the failure.
  • Only the first of multiple duplicate acpi_backlight= backup values gets restored: real, if narrow. rewrite_grub() now computes the "already claimed" keys once from the current params, so it no longer blocks a duplicate from restoring itself.

The four separately-threaded comments are addressed inline below.

@Stensel8
Stensel8 merged commit e3ec883 into main Sep 17, 2026
14 checks passed
@Stensel8
Stensel8 deleted the claude/backlight-fix-script-audit-sae4gz branch September 17, 2026 13:16
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.

3 participants