batsaver: switch to pkexec helper, drop broken udev permission hack
Update PKGBUILD version / update-pkgver (push) Successful in 2s
Update PKGBUILD version / update-pkgver (push) Successful in 2s
The wheel-write-via-udev approach for charge_control_end_threshold has
been broken since 2026-04-08: the audit-remediation commit added
ACTION=="add" to the rule, but the threshold attribute doesn't exist
yet at the add event on Lenovo, so chmod fails silently and permissions
are never set. moonarch-batsaver-toggle has been returning Permission
denied since.
Replace the udev-rule approach with a pkexec helper:
defaults/bin/moonarch-batsaver-apply privileged: validate + write
defaults/bin/moonarch-batsaver-toggle user: read sysfs, dispatch via pkexec
defaults/bin/moonarch-batsaver-restore boot-time root restore (extracted
from inline ExecStart for clarity)
Default Standard-pkexec prompt — password cached per session for the
~5min auth window; no polkit no-password rule, no privilege escalation
surface from misvalidated input. Same pattern Battery-Health-Charging
GNOME extension uses.
The boot-time restore service now skips the kernel write when the
sysfs value already matches the saved state (Lenovo drivers reject
same-value writes with EINVAL).
DECISIONS.md documents the failure analysis and trade-offs.
CLAUDE.md updated to describe the new flow.
moonarch-doctor: udev-effectiveness check removed.
This commit is contained in:
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/bash
|
||||
# ABOUTME: Privileged helper invoked via pkexec from moonarch-batsaver-toggle.
|
||||
# ABOUTME: Validates the threshold value and writes it to sysfs and the state file.
|
||||
|
||||
set -eu
|
||||
|
||||
VAL="${1:-}"
|
||||
|
||||
case "$VAL" in
|
||||
""|*[!0-9]*)
|
||||
echo "Invalid argument: '$VAL' (expected integer 1-100)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$VAL" -lt 1 ] || [ "$VAL" -gt 100 ]; then
|
||||
echo "Out of range: $VAL (expected 1-100)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
THRESHOLD_FILE="/sys/class/power_supply/BAT0/charge_control_end_threshold"
|
||||
STATE_DIR="/var/lib/moonarch"
|
||||
STATE_FILE="$STATE_DIR/batsaver-threshold"
|
||||
|
||||
[ -f "$THRESHOLD_FILE" ] || { echo "No battery threshold support" >&2; exit 1; }
|
||||
|
||||
# Skip the kernel write when the value already matches — some Lenovo drivers
|
||||
# reject same-value writes with EINVAL.
|
||||
CURRENT=$(cat "$THRESHOLD_FILE")
|
||||
if [ "$CURRENT" != "$VAL" ]; then
|
||||
printf %s "$VAL" > "$THRESHOLD_FILE"
|
||||
fi
|
||||
|
||||
mkdir -p "$STATE_DIR"
|
||||
printf %s "$VAL" > "$STATE_FILE"
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
# ABOUTME: Restores the saved battery charge end threshold on boot.
|
||||
# ABOUTME: Skips silently when the kernel already reports the same value (avoids EINVAL on some Lenovo drivers).
|
||||
|
||||
set -eu
|
||||
|
||||
STATE_FILE="/var/lib/moonarch/batsaver-threshold"
|
||||
SYS_FILE="/sys/class/power_supply/BAT0/charge_control_end_threshold"
|
||||
|
||||
[ -f "$STATE_FILE" ] || exit 0
|
||||
[ -f "$SYS_FILE" ] || exit 0
|
||||
|
||||
V=$(cat "$STATE_FILE")
|
||||
case "$V" in
|
||||
""|*[!0-9]*) exit 0 ;;
|
||||
esac
|
||||
[ "$V" -ge 1 ] && [ "$V" -le 100 ] || exit 0
|
||||
|
||||
# Some Lenovo drivers reject writing the same value with EINVAL.
|
||||
C=$(cat "$SYS_FILE")
|
||||
[ "$C" = "$V" ] && exit 0
|
||||
|
||||
printf %s "$V" > "$SYS_FILE"
|
||||
@@ -1,10 +1,8 @@
|
||||
#!/usr/bin/bash
|
||||
# ABOUTME: Toggles battery conservation mode between 80% and 100% charge limit.
|
||||
# ABOUTME: Writes to sysfs (immediate) and state file (persistence across reboots).
|
||||
# ABOUTME: Reads sysfs as user, dispatches the privileged write via pkexec.
|
||||
|
||||
THRESHOLD_FILE="/sys/class/power_supply/BAT0/charge_control_end_threshold"
|
||||
STATE_DIR="/var/lib/moonarch"
|
||||
STATE_FILE="${STATE_DIR}/batsaver-threshold"
|
||||
CONSERVATION_LIMIT=80
|
||||
|
||||
[[ -f "$THRESHOLD_FILE" ]] || exit 1
|
||||
@@ -18,12 +16,7 @@ else
|
||||
NEW="$CONSERVATION_LIMIT"
|
||||
fi
|
||||
|
||||
# Apply immediately
|
||||
echo "$NEW" > "$THRESHOLD_FILE" || exit 1
|
||||
|
||||
# Persist for next boot
|
||||
mkdir -p "$STATE_DIR"
|
||||
echo "$NEW" > "$STATE_FILE" || exit 1
|
||||
pkexec /usr/bin/moonarch-batsaver-apply "$NEW" || exit 1
|
||||
|
||||
# Signal Waybar to refresh the batsaver module (SIGRTMIN+9)
|
||||
pkill -RTMIN+9 waybar
|
||||
|
||||
@@ -9,11 +9,7 @@ ConditionPathExists=/var/lib/moonarch/batsaver-threshold
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
# Validate the threshold (integer 1–100) before writing. The state file is
|
||||
# written by wheel-group users via moonarch-batsaver-toggle; the kernel rejects
|
||||
# non-numeric values on sysfs, but validating here prevents noise on boot and
|
||||
# makes the trust boundary explicit.
|
||||
ExecStart=/bin/sh -c 'V=$(cat /var/lib/moonarch/batsaver-threshold); case "$V" in ""|*[!0-9]*) exit 0;; esac; [ "$V" -ge 1 ] && [ "$V" -le 100 ] && printf %s "$V" > /sys/class/power_supply/BAT0/charge_control_end_threshold'
|
||||
ExecStart=/usr/bin/moonarch-batsaver-restore
|
||||
NoNewPrivileges=true
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# ABOUTME: udev rule granting wheel group write access to battery charge threshold.
|
||||
# ABOUTME: Enables unprivileged toggling of conservation mode via moonarch-batsaver-toggle.
|
||||
|
||||
SUBSYSTEM=="power_supply", ACTION=="add", ATTR{type}=="Battery", RUN+="/bin/sh -c 'chgrp wheel /sys%p/charge_control_end_threshold 2>/dev/null; chmod g+w /sys%p/charge_control_end_threshold 2>/dev/null'"
|
||||
Reference in New Issue
Block a user