≡ Battery Guardian ≡
Configuration
├ Battery logging starts at 30% SOC
╰ Cronjob scheduled ● Yes
Battery Status
├ Charger connected ● Yes
├ Charging state ● Charging
├ Power profile ● Performance
╰ Battery capacity ● Normal at 76% SOC
Invoke battery-guardian with one of these options:
├ --batterylog Open battery log
├ --help Show description, usage and license
├ --log Open and follow runtime log; quit with Ctrl+C + Q
├ --scheduleAuto-schedule the cronjob (every minute)
├ --test Force-run the critical suspend sequence
├ --unschedule Remove the cronjob
╰ --watchWatch guardian, periodically updated; quit with Ctrl+C
Script path: /home/martin/bin/battery-guardianBattery Guardian is a proactive, headless background daemon designed to prevent hardware degradation and data corruption caused by deep battery discharge. It monitors the battery state-of-charge (SOC) directly through the Linux kernel's sysfs interface and safely suspends or powers off the machine before an abrupt hardware power loss occurs.
Battery Guardian is particularly dedicated to Linux Systems which do not support Hibernation due to their LUKS-encrypted system partition with a separate swap partition.
Encourages healthy charging habits by alerting the user at a specific discharge level.
Prevents the Lithium-Ion cells from reaching chemically damaging deep-discharge states.
Replaces abrupt, destructive power loss with a controlled systemctl suspend or systemctl poweroff, ensuring file systems are cleanly unmounted and data in RAM is safely managed or discarded predictably.
Operates entirely within the user space using D-Bus session delegation, requiring no sudo privileges or root Crontab modifications.
Periodically runs in the background (via Cron) without user intervention.
Fully localized interfaces for English, German, and Thai.
Seamlessly interfaces with GNOME's notification system and PulseAudio/PipeWire for unobtrusive user alerts.
To deploy Battery Guardian, the target environment requires:
Any Linux distribution, based on Debian or Ubuntu with Bourne-again shell (v4.0 or higher recommended for associative array support) or compatible, such as ZORIN OS.
GNOME desktop environment, and Wayland display driver installed and in use.
gsettingsnotify-send(libnotify)pw-play(PipeWire) orpaplay(PulseAudio)upowercron
The UPower daemon must be configured to delegate low-power states to the script. Edit /etc/UPower/UPower.conf to include strictly descending values:
PercentageLow=20
PercentageCritical=10
PercentageAction=5The script operates as a highly resilient state-machine triggered every minute via a user cronjob:
Reads the raw hardware state from /sys/class/power_supply/BAT1/ and checks capacity percentage, charging state, and power profiles using /sys/class/power_supply and upower.
If the kernel reports the battery capacity level as Low or Critical, it initiates a 20-second grace period. If the charger is not connected within this window, it drops a state-persistence marker and suspends the system to RAM.
If the user manually wakes the system from a Guardian-initiated suspend while the battery is still below the critical threshold (6%), it bypasses standard critical power action and executes an emergency systemctl poweroff to physically protect the storage controller and battery cells.
- Sends a notification of charging started and logs this event once, if the battery state changes to charging from any other state;
- Sends desktop notifications with urgency levels and plays sound alerts accordingly, when battery level falls below warning or critical thresholds (configurable inside the script);
- Avoids repeated alerts when already charging or battery levels are above warning threshold;
- Logs detailed battery discharging to a dated log file in
$HOME/.var/log/battery-guardian_YYYY-MM-DD.log - Logs runtime events to
$HOME/.var/battery-guardian.log.
Uses a cache file to remember the last battery state to detect transitions to charging state and avoids repeating notifications unnecessarily. Marker files are created for charging recommendation and suspend-to-RAM trigger to prevent repeating these actions at every run triggered by Cron;
All thresholds are declared as variables and can be adapted by the user.
Triggers audio-visual recommendation to connect the power supply below 30% SOC, more or less at the same time, the system default battery icon silently changes to orange battery-caution. Simultaneously, a battery log is created and appended every run triggered by Cron. A new battery log will be created every calendar day.
Audio-visually announces to suspend the system to RAM, and initiates suspend-to-RAM after a short grace period, if no power supply is connected to prevent data loss and preserve battery health.
- Warning
Audio-visual alerts at 8% SOC every run triggered by Cron. - Critical
Final audio-visual announcement at 6% SOC of the imminent system failure due to the fully drained battery. System will be forced to power off, ensuring file systems are cleanly unmounted and data in RAM is safely managed or discarded predictably.
A simulation mode (--test) that mimics critical low-battery conditions to verify the suspend logic without actually draining the battery. Bypasses capacity checks and forces a simulated trigger to verify notification and audio routing.
WARNING: This simulates a critical state and will suspend your computer after the grace period unless you connect a power supply or you cancel the test (Ctrl+C) within the short grace period.
Detects the system language $LANG and switches interface text between generic English en (fallback), generic German de, and Thai th.
The script uses a modular structure with extensive variable declarations for configuration and external files for localization.
Battery information is retrieved by acpi -b and parsed to extract state, level, and estimated time remaining (ETR):
ACPI_OUTPUT=$(acpi -b)
STATE=$(echo "$ACPI_OUTPUT" | sed -E 's/^Battery [0-9]+: ([^,]+),.*/\1/')
LEVEL=$(echo "$ACPI_OUTPUT" | grep -oP '\d+%' | tr -d '%')
RAW_TIME=$(echo "$ACPI_OUTPUT" | grep -oP '\d{1,2}:\d{2}(:\d{2})?' | head -n1)The script defines critical thresholds and paths at the top for easy adjustment.
# Configuration
THRESHOLD_WARNING=8 # % SOC battery; system failure warning threshold
THRESHOLD_CRITICAL=6 # % SOC battery; system failure critical threshold
SAFETY_MARGIN=4 # safety margin reduction from ETR
GRACE_PERIOD=20 # seconds before autosuspend
# Environment
BATTERY_ID="BAT1"
AUDIO_PLAYER="/usr/bin/pw-play"This function determines whether to initiate a first-strike suspend based on hardware states, or delegate to the fallback protocol based on the state-marker.
autosuspend() {
# Check if the system recently woke from a Guardian-initiated suspend
if [[ -f "$TRIGGER_MARKER" ]]; then
# Delegate to the emergency fallback protocol
handle_fallback
# Evaluate primary hardware state: triggers if the kernel reports Low or Critical
elif { [[ "$LEVEL_CAPACITY_LEVEL" == "Low" ]] || [[ "$LEVEL_CAPACITY_LEVEL" == "Critical" ]]; } && [[ "$CHS" != "Charging" ]]; then
echo "╰ ${MSG[log_auto_crit]}" >> "$RUNTIME_LOG"
# Initiate graceful suspend with a user warning
trigger_autosuspend
fi
}This block enforces the definitive end-of-line protocol if the system is woken up in a dangerously low state and the user did not connect a power supply.
handle_fallback() {
# ... (State localization and safe-state exit logic omitted for brevity) ...
# Evaluate if the battery is at or below the hard limit (6%)
if (( LEVEL <= THRESHOLD_CRITICAL )); then
# Blast critical visual and audio warnings
notify-send -u critical -i battery-empty "${MSG[fallback_fail_imm]}" "$NOTIFICATION_SHUTDOWN"
"$AUDIO_PLAYER" "$SOUND_CRITICAL" &
echo "${MSG[log_fb_fail_imm]}" >> "$RUNTIME_LOG"
# Yield 5 seconds for UI elements to render
sleep 5
# Erase the persistence marker to prevent boot-loops
rm -f "$TRIGGER_MARKER" 2>/dev/null
# Delegate the poweroff to the active user systemd session.
# This leverages D-Bus IPC to bypass headless cron Polkit restrictions.
/usr/bin/systemd-run --user --no-ask-password --quiet systemctl poweroff
exit 0
# Issue severe warnings if woke between 7% and 8%
elif (( LEVEL <= THRESHOLD_WARNING )); then
notify-send -u critical -i battery-caution "${MSG[fallback_fail_warn]}" "$NOTIFICATION_SHUTDOWN"
"$AUDIO_PLAYER" "$SOUND_WARNING" &
fi
}Battery Guardian dynamically resolves hardware paths, such as using upower to verify AC connection reliability.
CHARGER_CONNECTED="$(upower -i /org/freedesktop/UPower/devices/line_power_ACAD | grep -E 'online' | grep -E 'yes|no' | cut -d\: -f2 | cut -c 15- || echo "Unknown")"The external localization files use an associative array MSG to map keys to localized strings, ensuring easy translation updates. The logic probes presence of the fallback localization file and aborts if it is missing, prompting the user to ensure that language files are in same directory as the script.
declare -A MSG
SYSTEM_LANG="${LANG:0:2}"
SCRIPT_DIR="$(dirname "$SCRIPT_PATH")"
SCRIPT_NAME="$(basename "$SCRIPT_PATH")"
LOCALE_FILE="${SCRIPT_DIR}/${SCRIPT_NAME}.${SYSTEM_LANG}"
FALLBACK_FILE="${SCRIPT_DIR}/${SCRIPT_NAME}.en"
if [[ -f "$LOCALE_FILE" ]]; then
source "$LOCALE_FILE"
elif [[ -f "$FALLBACK_FILE" ]]; then
source "$FALLBACK_FILE"
else
printf "\n%b%b%b\n" "${FORMATTING_HEADER}" ":: Battery Guardian ::" "${FORMATTING_RESET}"
printf "\n%b%b%b%b\n" "$INDICATOR_FAIL" "$FORMATTING_BOLD" "Localization error" "${FORMATTING_RESET}"
printf " Missing fallback file '$FALLBACK_FILE'.\n"
printf " Ensure language files are in same directory as script.\n\n"
exit 1
fiThe default fallback language is generic English [en].
-
Download
Extract thebattery-guardianbash script and the associated language files (.en,.de,.th) into a persistent user directory (e.g.,~/.local/bin/). -
Permissions
Assign execution permissions to the script:
chmod +x battery-guardian. -
Dependencies
Ensureupower,acpi, andlibnotifyare installed via your package manager (e.g.,sudo apt install upower acpi libnotify-bin). -
Scheduling
Install the cron dispatcher by executing
./battery-guardian --schedule. -
Verification
Verify the installation by checking the status:
./battery-guardian.
The script is designed to run autonomously. Manual invocation provides status readouts and management options.
-
./battery-guardian
Displays the current charging state, battery health, and configuration. -
./battery-guardian --schedule
Installs the minute-tick Crontab entry. -
./battery-guardian --unschedule
Removes the script from thshe Crontab. -
./battery-guardian --log
Opens the runtime log in the default pager. -
./battery-guardian --batterylog
Opens the detailed battery discharge tracking log in the default pager. -
./battery-guardian --test
Bypasses capacity checks and forces a simulated trigger to verify notification and audio routing.
WARNING: This simulates a critical state and will suspend your computer after the grace period unless you connect a power supply or you cancel the test (Ctrl+C) within the short grace period.
If the script fails to execute or notifications do not appear, utilize these debugging techniques:
Insert set -x at the top of the bash script (below the shebang). This forces the shell to print every command and its expanded arguments to standard output before execution, allowing you to trace exactly where a logic gate fails.
The script currently allows non-zero exit codes (like a failed cat command if a sysfs node is temporarily busy) to pass quietly without terminating the script.
Uncommenting set -euo pipefail forces the script to abort immediately if any command fails (-e), if an undefined variable is referenced (-u), or if a command within a pipeline fails (-o pipefail). Use this strictly for debugging syntax or pathing errors.
Ensure cron is running and you have the correct permissions. Check the log file: $HOME/.var/log/battery-guardian.log.
Verify that pw-play is installed and the ALSA/PipeWire daemon is active in the user session.
Ensure the script is running within the user's crontab and not sudo crontab. The /usr/bin/systemd-run --user command requires the context of an active desktop seat to bypass Polkit restrictions.
Executing ./battery-guardian --help outputs the embedded, localized Man-page style documentation. It details the script's core purpose, available flags, environment variables, disclaimer, and MIT License information. If invoked with invalid options, the script defaults to printing the standard status output alongside the available command syntax.
Use at your own risk. This script may not prevent battery damage or data loss on all systems due to variations and limitations in hardware and operating system. Test thoroughly; your system may unexpectedly suspend or power-off.
This script is provided “as is”; there is NO WARRANTY at all. This is free software: you are free to modify it to your needs and redistribute it (see MIT Licence).
Due to ongoing development, this documentation might not reflect latest minor code changes.
Copyright (c) 2026 RML Tec Dev
Contributions and feedback are welcome via rmltecdev@pm.me
MIT Licence