Files
Panama/config/dot/quickshell/scripts/panama-battery
T
Gabriel Brown 153554b5df Make ./install something you could hand a stranger
The audit's third tier: everything between this installer and a fresh machine
it has never met.

The one path that could cost a person their display: the interview probes
Secure Boot with mokutil, which install-packages had not installed yet, so on
a minimal base the MOK question silently never fired -- and install-hardware
still installed akmod-nvidia and blacklisted nouveau, arming a reboot into an
unloadable driver with its fallback disabled. The probe tools (pciutils,
mokutil, fwupd) now bootstrap beside gum, and install-hardware re-checks
Secure Boot for itself and refuses the driver rather than the display.

Secrets leave the checkout: the personal environment moves to
~/.config/panama/env at mode 600 by migration, and .bashrc sources it with a
permission check that quietly re-tightens drift. change-settings no longer
overwrites /etc/dnf/dnf.conf -- two performance keys are set additively, the
defaultyes=True that made every `dnf remove` treat Enter as yes is gone, and
a migration strips it from machines that already received it.

Package installation survives the world changing: the initial and desktop
lists run with --skip-unavailable and a report_missing pass that names what
was skipped (resolved through --whatprovides, so capability names like awk
do not cry wolf); the openh264, appstream and core-group extras go through
soft; RustDesk resolves its RPM for the machine's own architecture; and the
Claude Desktop repository script is fetched to a kept file and run, never
piped from the network into root.

The hardware predicates stop guessing: a wireless mouse's scope=Device
battery no longer turns a tower into a laptop, USB-PD-only machines read
their power state from the battery's own status instead of being permanently
"on AC", the lid falls back to logind's LidClosed where ACPI is silent, and
charge limits reach every pack of a two-battery machine in one authorization
-- with the reported percentage summed across packs.

And the parsers stop assuming this machine: snapper is read through
--machine-readable csv with named columns instead of a localized box-drawing
table, and reports whether snapshots are even possible so ext4 and
unconfigured-btrfs stop looking identical; fprintd is parsed under LC_ALL=C;
the hypridle drop-in resolves the binary it points at; the recorder's render
node became an "auto" token resolved at record time; update-grub writes the
config its firmware actually boots; the nvm prompt hook and the SSH tmux
takeover are guarded; hipblas and rocm-opencl move to an opt-in gpu-compute
category; and the two interactive python tools' libraries are declared.

Claude-Session: https://claude.ai/code/session_01Epx9ZC1gwm81K3jm9x9CKh
2026-08-23 12:10:03 -04:00

165 lines
6.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# The battery, for the shell and the Power page.
#
# panama-battery paths resolve which sysfs files to watch
# panama-battery status one JSON reading, for scripts and contracts
# panama-battery set-threshold N cap charging at N% (needs root)
#
# `paths` exists so the shell does not have to poll a subprocess. Globbing is
# the one thing QML cannot do -- a battery is BAT0 on most machines, BAT1 on
# some, CMB0 on a few, and the mains supply is AC, AC0, ADP1 or ACAD depending
# on the firmware -- so this resolves the names once and the shell reads the
# files directly from then on, the way Vitals.qml reads procfs.
#
# Which machine has what is panama-hw's question, so the search lives there and
# this asks it rather than keeping a second copy of the answer.
#
# Charge thresholds are a root write to a sysfs attribute, and the only part of
# this that needs privilege. It goes through panama-sudo so the prompt names
# what is being changed, rather than asking for a password with polkit's
# generic "run a program as another user".
set -uo pipefail
PANAMA_PATH="${PANAMA_PATH:-$HOME/.local/share/Panama}"
HW="$PANAMA_PATH/bin/panama-hw"
SYS="${PANAMA_HW_SYS:-/sys}"
battery_dir() {
[[ -x "$HW" ]] || return 1
"$HW" battery-path 2>/dev/null
}
# Every system battery, for the machines that have two. The primary pack
# answers `paths` and drives the watch files; these answer the questions
# where ignoring the second pack gives a wrong answer -- the total charge,
# and which packs a threshold write must reach.
all_battery_dirs() {
local supply type scope
for supply in "$SYS"/class/power_supply/*; do
[[ -r "$supply/type" ]] || continue
type="$(cat "$supply/type" 2>/dev/null)"
[[ "$type" == "Battery" ]] || continue
scope="$(cat "$supply/scope" 2>/dev/null || echo System)"
[[ "$scope" == "Device" ]] && continue
printf '%s\n' "$supply"
done
}
# The mains supply, if the machine has one. A desktop has none, and that is
# not an error: panama-hw's `ac` predicate treats "no mains at all" as being on
# wall power, and the shell falls back to the same assumption.
mains_dir() {
local supply
for supply in "$SYS"/class/power_supply/*; do
[[ -r "$supply/type" ]] || continue
[[ "$(cat "$supply/type" 2>/dev/null)" == "Mains" ]] || continue
printf '%s\n' "$supply"
return 0
done
return 1
}
read_int() {
local file="$1" value
[[ -r "$file" ]] || return 1
value="$(cat "$file" 2>/dev/null)" || return 1
[[ "$value" =~ ^[0-9]+$ ]] || return 1
printf '%s\n' "$value"
}
cmd_paths() {
local battery mains threshold=""
battery="$(battery_dir)" || battery=""
mains="$(mains_dir)" || mains=""
# Only report the threshold file when it exists AND is writable through
# root -- a machine whose kernel exposes a read-only stub would otherwise
# get a control that silently does nothing.
if [[ -n "$battery" && -r "$battery/charge_control_end_threshold" ]]; then
threshold="$battery/charge_control_end_threshold"
fi
printf '{"battery":"%s","mains":"%s","threshold":"%s"}\n' \
"$battery" "$mains" "$threshold"
}
cmd_status() {
local battery mains capacity="" state="Unknown" online=1 threshold=0
battery="$(battery_dir)" || battery=""
mains="$(mains_dir)" || mains=""
if [[ -n "$battery" ]]; then
capacity="$(read_int "$battery/capacity")" || capacity=""
[[ -r "$battery/status" ]] && state="$(cat "$battery/status" 2>/dev/null)"
threshold="$(read_int "$battery/charge_control_end_threshold")" || threshold=0
# With two packs, one pack's percentage is not the machine's: sum the
# stored and full energy across every system battery and answer with
# the real total. Single-battery machines never reach this.
local dirs=() dir now full total_now=0 total_full=0
mapfile -t dirs < <(all_battery_dirs)
if (( ${#dirs[@]} > 1 )); then
for dir in "${dirs[@]}"; do
now="$(read_int "$dir/energy_now" || read_int "$dir/charge_now")" || continue
full="$(read_int "$dir/energy_full" || read_int "$dir/charge_full")" || continue
total_now=$(( total_now + now ))
total_full=$(( total_full + full ))
done
(( total_full > 0 )) && capacity=$(( (total_now * 100 + total_full / 2) / total_full ))
fi
fi
if [[ -n "$mains" ]]; then
online="$(read_int "$mains/online")" || online=0
fi
printf '{"available":%s,"percent":%s,"status":"%s","acOnline":%s,"chargeLimit":%s}\n' \
"$([[ -n "$capacity" ]] && echo true || echo false)" \
"${capacity:-0}" "$state" \
"$([[ "$online" == "1" ]] && echo true || echo false)" \
"$threshold"
}
cmd_set_threshold() {
local value="${1:-}" battery file
[[ "$value" =~ ^[0-9]+$ ]] || { echo 'set-threshold needs a percentage' >&2; return 2; }
(( value >= 50 && value <= 100 )) || { echo 'threshold must be between 50 and 100' >&2; return 2; }
# Every pack that has the attribute, not just the first: capping one
# battery of a two-battery machine leaves the other charging to full,
# which is the opposite of what the person asked for.
local files=() dir
while IFS= read -r dir; do
[[ -e "$dir/charge_control_end_threshold" ]] && files+=("$dir/charge_control_end_threshold")
done < <(all_battery_dirs)
(( ${#files[@]} > 0 )) || { echo 'this machine cannot set a charge threshold' >&2; return 1; }
# tee rather than a redirect: the redirect is performed by the calling
# shell, which is not the one holding root. One authorization writes every
# pack.
"$PANAMA_PATH/bin/panama-sudo" \
--reason "Capping battery charging at ${value}% to reduce wear" \
-- sh -c "printf '%s\n' '$value' | tee ${files[*]} >/dev/null" || return 1
# Read it back rather than reporting success from the write's exit code:
# some firmware silently clamps or ignores the value.
read_int "${files[0]}"
}
case "${1:-status}" in
paths) cmd_paths ;;
status) cmd_status ;;
set-threshold) shift; cmd_set_threshold "$@" ;;
-h|--help)
cat <<'USAGE'
usage: panama-battery [paths|status|set-threshold <50-100>]
paths JSON: which sysfs files hold the battery, mains and threshold
status JSON: one reading of charge, state, power source and limit
set-threshold cap charging at N percent (asks for a password)
USAGE
;;
*) echo "panama-battery: unknown command: $1" >&2; exit 2 ;;
esac