This project sets up a Bash script on an Ubuntu laptop (acting as a server) to monitor battery status. It sends email alerts when the battery is discharging (not plugged in) and performs a graceful shutdown when the battery drops below 30% to prevent data loss for hosted apps/websites. The script runs periodically via cron and uses Python for email sending via SMTP (e.g., Gmail).
This is useful if you SSH into your Ubuntu server from another machine (e.g., MacBook) and want notifications to avoid unexpected shutdowns.
- Hardware/Setup:
- Ubuntu laptop (server) with a battery, always on and hosting your app/website (e.g., via domain).
- SSH access from another machine (e.g.,
ssh user@server).
- Software:
- Ubuntu 20.04+ (tested on 24.04.3 LTS).
- Python3 pre-installed (default on Ubuntu).
- Gmail account (or other SMTP provider) with 2-Step Verification enabled and an App Password generated (for security; do not use your main password).
- Network:
- Internet access on the Ubuntu server for email sending.
- Permissions:
- User with sudo access to configure cron and passwordless shutdown.
No additional packages needed beyond defaults, but if battery reading fails, install acpi with sudo apt install acpi.
- Go to https://myaccount.google.com/security > "2-Step Verification" > "App passwords."
- Create a new App Password (e.g., name it "Ubuntu Battery Alert") and copy the 16-character code.
- Expected Result: You have a secure App Password for SMTP (not your main Gmail password).
- If Error: If 2-Step Verification isn't enabled, enable it first. If using non-Gmail, adjust SMTP settings accordingly (e.g., Outlook: smtp.office365.com:587).
- SSH into your Ubuntu server:
ssh user@server. - Create the script:
nano ~/battery_monitor.sh. - Paste the script code (see "The Script" section below), customize variables (e.g., EMAIL_TO, SMTP_PASS with your App Password).
- Make executable:
chmod +x ~/battery_monitor.sh. - Expected Result: Script file created and ready to run.
- If Error: If
nanonot found, usevimor installsudo apt install nano. Permission issues? Runchmodagain.
- Run
sudo visudo. - Add at the end:
your_username ALL=(ALL) NOPASSWD: /sbin/shutdown(replaceyour_usernamewith e.g.,tomey-tran). - Save and exit (Ctrl+O, Enter, Ctrl+X in nano).
- Test:
sudo shutdown -h now(should shut down without password prompt; restart after). - Expected Result: Shutdown works without password, ensuring unattended operation.
- If Error: "visudo: command not found"? Install
sudo apt install sudo. Prompt still appears? Check syntax or username withwhoami.
- Run
crontab -e. - Add:
*/5 * * * * ~/battery_monitor.sh >> ~/battery_monitor.log 2>&1(runs every 5 minutes, logs to ~/battery_monitor.log). - Save and exit.
- Verify:
crontab -l. - Expected Result: Script runs automatically every 5 minutes; check logs with
cat ~/battery_monitor.log. - If Error: Cron not running? Check
sudo service cron status. Log permission denied? Runchmod u+rw ~/battery_monitor.log.
- Run manually:
~/battery_monitor.sh. - To test email without unplugging: Uncomment the temporary test block in the script (see comments in script), save, run, then re-comment it out.
- To test shutdown: Temporarily change
-lt 30to-lt 90(if battery >90%, it won't trigger), unplug, run script, observe email and shutdown; revert after. - Expected Result: Log shows status; email arrives if discharging; shutdown at low battery with email.
- If Error: See Troubleshooting section.
#!/bin/bash
# Email configuration (replace with your details)
EMAIL_TO="[email protected]" # Your email to receive alerts
EMAIL_FROM="[email protected]" # Same as SMTP_USER for Gmail
SMTP_SERVER="smtp.gmail.com"
SMTP_PORT=587
SMTP_USER="[email protected]" # Your Gmail address
SMTP_PASS="your_app_password" # 16-character App Password from Google
LOG_FILE="$HOME/battery_monitor.log"
# Check battery status
BATTERY_STATUS=$(cat /sys/class/power_supply/BAT*/status 2>/dev/null)
BATTERY_PERCENT=$(cat /sys/class/power_supply/BAT*/capacity 2>/dev/null)
# Log current time and status
echo "$(date): Battery Status: $BATTERY_STATUS, Level: $BATTERY_PERCENT%" >> "$LOG_FILE"
# Check if battery info is readable
if [ -z "$BATTERY_STATUS" ] || [ -z "$BATTERY_PERCENT" ]; then
echo "$(date): Error: Could not read battery status!" >> "$LOG_FILE"
exit 1
fi
# Alert if discharging
if [ "$BATTERY_STATUS" = "Discharging" ]; then
SUBJECT="ALERT: Ubuntu Server Battery Discharging!"
BODY="Battery is DISCHARGING at $BATTERY_PERCENT%. Server may shut down, risking data loss for your website/app!"
# Send email using Python
python3 - <<EOF 2>> "$LOG_FILE"
import smtplib
from email.mime.text import MIMEText
msg = MIMEText("$BODY")
msg['Subject'] = "$SUBJECT"
msg['From'] = "$EMAIL_FROM"
msg['To'] = "$EMAIL_TO"
try:
server = smtplib.SMTP("$SMTP_SERVER", $SMTP_PORT)
server.starttls()
server.login("$SMTP_USER", "$SMTP_PASS")
server.sendmail("$EMAIL_FROM", "$EMAIL_TO", msg.as_string())
server.quit()
print("$(date): Email sent successfully")
except Exception as e:
print("$(date): Email failed: " + str(e))
EOF
echo "$(date): Alert triggered: Battery discharging at $BATTERY_PERCENT%" >> "$LOG_FILE"
fi
# Graceful shutdown if battery below 30% while discharging
# Suggestion: For testing, temporarily change -lt 30 to -lt 90 (if battery is ~95%), unplug charger, run script, expect shutdown email and system halt; revert after testing.
if [ "$BATTERY_STATUS" = "Discharging" ] && [ "$BATTERY_PERCENT" -lt 30 ]; then
SHUTDOWN_SUBJECT="ALERT: Ubuntu Server Shutting Down!"
SHUTDOWN_BODY="Battery level dropped to $BATTERY_PERCENT%. Server is shutting down now to prevent data loss."
# Send shutdown email
python3 - <<EOF 2>> "$LOG_FILE"
import smtplib
from email.mime.text import MIMEText
msg = MIMEText("$SHUTDOWN_BODY")
msg['Subject'] = "$SHUTDOWN_SUBJECT"
msg['From'] = "$EMAIL_FROM"
msg['To'] = "$EMAIL_TO"
try:
server = smtplib.SMTP("$SMTP_SERVER", $SMTP_PORT)
server.starttls()
server.login("$SMTP_USER", "$SMTP_PASS")
server.sendmail("$EMAIL_FROM", "$EMAIL_TO", msg.as_string())
server.quit()
print("$(date): Shutdown email sent successfully")
except Exception as e:
print("$(date): Shutdown email failed: " + str(e))
EOF
echo "$(date): Initiating shutdown due to low battery ($BATTERY_PERCENT%)" >> "$LOG_FILE"
sudo shutdown -h now
fi
# Temporary email test block (uncomment to test email sending while charged; run script, check inbox/log, then re-comment out)
# SUBJECT="TEST: Email Functionality Check"
# BODY="This is a test email from the battery monitor script at $(date). Battery is $BATTERY_PERCENT% and $BATTERY_STATUS."
#
# python3 - <<EOF 2>> "$LOG_FILE"
# import smtplib
# from email.mime.text import MIMEText
#
# msg = MIMEText("$BODY")
# msg['Subject'] = "$SUBJECT"
# msg['From'] = "$EMAIL_FROM"
# msg['To'] = "$EMAIL_TO"
#
# try:
# server = smtplib.SMTP("$SMTP_SERVER", $SMTP_PORT)
# server.starttls()
# server.login("$SMTP_USER", "$SMTP_PASS")
# server.sendmail("$EMAIL_FROM", "$EMAIL_TO", msg.as_string())
# server.quit()
# print("$(date): Test email sent successfully")
# except Exception as e:
# print("$(date): Test email failed: " + str(e))
# EOF- Manual Run:
~/battery_monitor.sh– Logs status; sends alert if discharging. - Email Test: Uncomment the temporary test block, save/run, check email/log for success; re-comment.
- Shutdown Test: Change
-lt 30to higher value (e.g.,-lt 90), unplug, run; expect email and shutdown. Revert threshold. - Cron Test: Wait 5 minutes, check log for new entry.
- Email Failed (535 Error): Wrong App Password – regenerate and update script. Check spam folder. Non-Gmail? Adjust SMTP_SERVER/PORT.
- Battery Read Error: Install
acpi, replace status lines:BATTERY_STATUS=$(acpi -b | grep -oP '(Charging|Discharging|Full)')andBATTERY_PERCENT=$(acpi -b | grep -oP '\d+%' | tr -d '%'). - Shutdown Fails: Sudo prompt? Re-check
visudoline. Testsudo shutdown -h nowmanually. - No Logs: Permission issue –
chmod u+rw ~/battery_monitor.log. Cron not running?sudo service cron restart. - Script Not Executable: Run
chmod +x ~/battery_monitor.sh. - Data Loss Risk: Always back up your app/website data regularly (e.g., via separate cron job).
For questions, consult Ubuntu docs or adjust script as needed.