mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 15:43:06 +02:00
wake_alarm + midnight_shutdown: hibernate on alarm nights instead of poweroff
- install.sh: install sleep-hook.sh to systemd-sleep hooks (step 3) and shutdown-wrapper.sh to /usr/local/bin/shutdown (step 5) - shutdown-wrapper.sh: new script that intercepts shutdown calls and redirects to rtcwake -m disk on alarm nights (Mon/Fri/Sat/Sun), pass- through for reboots and cancel commands - sleep-hook.sh: new systemd-sleep hook that restarts wake-alarm.service for each logged-in user after hibernate resume - setup_midnight_shutdown.sh: check wake-alarm day; if yes set RTC alarm and hibernate, otherwise fall back to systemctl poweroff
This commit is contained in:
parent
10b4812ed0
commit
0d54c5d418
17
docs/superpowers/contracts/wake-alarm-hibernate-2026-05.json
Normal file
17
docs/superpowers/contracts/wake-alarm-hibernate-2026-05.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"title": "wake_alarm + midnight-shutdown: add hibernate-on-alarm-night support",
|
||||
"objective": "Extend the wake-alarm system so that on nights when an alarm is scheduled (Mon/Fri/Sat/Sun) the PC hibernates instead of powering off. Two new components: (1) shutdown-wrapper.sh shadows /usr/bin/shutdown and redirects hibernate-eligible shutdowns to rtcwake -m disk with the alarm epoch; (2) sleep-hook.sh is a systemd-sleep hook that restarts wake-alarm.service for every logged-in user after hibernate resume. setup_midnight_shutdown.sh is updated to call rtcwake -m disk so nightly shutdown hibernates on alarm nights instead of powering off.",
|
||||
"acceptance_criteria": [
|
||||
"On alarm nights (Mon/Fri/Sat/Sun) shutdown invocations hibernate the PC and set the RTC wake alarm",
|
||||
"Non-alarm nights and explicit reboots pass through to the real /usr/bin/shutdown unchanged",
|
||||
"After hibernate+resume wake-alarm.service is restarted for every active user session",
|
||||
"install.sh installs both new scripts to their target paths and sets correct permissions",
|
||||
"pre-commit passes on all changed and new shell files"
|
||||
],
|
||||
"out_of_scope": [
|
||||
"Changing the alarm schedule (Mon/Fri/Sat/Sun) — defined in wake_alarm module",
|
||||
"Windows or macOS support",
|
||||
"Network-wake (WoL) path"
|
||||
],
|
||||
"verifier": "pre-commit run --files python_pkg/wake_alarm/install.sh python_pkg/wake_alarm/shutdown-wrapper.sh python_pkg/wake_alarm/sleep-hook.sh linux_configuration/scripts/periodic_background/digital_wellbeing/setup_midnight_shutdown.sh"
|
||||
}
|
||||
39
docs/superpowers/evidence/wake-alarm-hibernate-2026-05.json
Normal file
39
docs/superpowers/evidence/wake-alarm-hibernate-2026-05.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"intent": "Add hibernate-on-alarm-night support to the wake-alarm system: on alarm nights (Mon/Fri/Sat/Sun) the PC should hibernate with the RTC wake time set rather than powering off, so it wakes automatically for morning workouts.",
|
||||
"scope": [
|
||||
"python_pkg/wake_alarm/install.sh",
|
||||
"python_pkg/wake_alarm/shutdown-wrapper.sh",
|
||||
"python_pkg/wake_alarm/sleep-hook.sh",
|
||||
"python_pkg/wake_alarm/wake_state.json",
|
||||
"linux_configuration/scripts/periodic_background/digital_wellbeing/setup_midnight_shutdown.sh"
|
||||
],
|
||||
"changes": [
|
||||
"install.sh: added step 3 to install sleep-hook.sh to /usr/lib/systemd/system-sleep/wake-alarm.sh and step 5 to install shutdown-wrapper.sh to /usr/local/bin/shutdown",
|
||||
"shutdown-wrapper.sh: new script that intercepts /usr/bin/shutdown calls, checks if tomorrow is an alarm day, and redirects to rtcwake -m disk with the alarm epoch; pass-throughs reboots and cancel commands",
|
||||
"sleep-hook.sh: new systemd-sleep hook that restarts wake-alarm.service for each logged-in user session after hibernate resume",
|
||||
"wake_state.json: initial state file installed with the package",
|
||||
"setup_midnight_shutdown.sh: replaced simple systemctl poweroff with logic that checks wake-alarm day and calls rtcwake -m disk + systemctl hibernate on alarm nights"
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "pre-commit run --files python_pkg/wake_alarm/install.sh python_pkg/wake_alarm/shutdown-wrapper.sh python_pkg/wake_alarm/sleep-hook.sh linux_configuration/scripts/periodic_background/digital_wellbeing/setup_midnight_shutdown.sh",
|
||||
"result": "All hooks passed",
|
||||
"evidence": "pre-commit run on 2026-05-22 returned Passed on all hooks for the four shell files"
|
||||
},
|
||||
{
|
||||
"command": "bash -n python_pkg/wake_alarm/shutdown-wrapper.sh && bash -n python_pkg/wake_alarm/sleep-hook.sh",
|
||||
"result": "No syntax errors",
|
||||
"evidence": "bash -n syntax check passed on both new scripts on 2026-05-22"
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"/usr/local/bin/shutdown shadows /usr/bin/shutdown — if the wrapper script has a bug it can prevent clean shutdown; rollback by removing /usr/local/bin/shutdown",
|
||||
"sleep-hook.sh uses loginctl to enumerate users — if loginctl is unavailable the hook is a no-op (alarm won't fire after resume but won't crash either)",
|
||||
"wake_state.json contains an HMAC that becomes stale after the initial install; the alarm service regenerates it on first run"
|
||||
],
|
||||
"rollback": [
|
||||
"Remove /usr/local/bin/shutdown to restore unmodified shutdown behaviour",
|
||||
"Remove /usr/lib/systemd/system-sleep/wake-alarm.sh to stop the sleep hook",
|
||||
"Revert setup_midnight_shutdown.sh to use systemctl poweroff unconditionally"
|
||||
]
|
||||
}
|
||||
@ -929,7 +929,24 @@ fi
|
||||
if [[ $should_shutdown == true ]]; then
|
||||
printf '%(%Y-%m-%d %H:%M:%S)T: Executing shutdown - current time %s:%s is within shutdown window for %s\n' -1 "$current_hour" "$current_minute" "$day_name"
|
||||
logger -t day-specific-shutdown "Executing scheduled shutdown at $(printf '%(%Y-%m-%d %H:%M:%S)T' -1)"
|
||||
|
||||
# If tomorrow is a wake-alarm day (Mon=1, Fri=5, Sat=6, Sun=7), hibernate
|
||||
# with an RTC timer so the alarm fires 8 hours later. Hibernate is completely
|
||||
# silent and dark — ideal when the PC is in a bedroom. rtcwake -m disk saves
|
||||
# state to swap and powers off, then the RTC restores power at wake_epoch.
|
||||
tomorrow_dow=\$(date -d "tomorrow" +%u)
|
||||
case "\$tomorrow_dow" in
|
||||
1|5|6|7)
|
||||
wake_epoch=\$(( \$(printf '%(%s)T' -1) + 8 * 3600 ))
|
||||
logger -t day-specific-shutdown "Tomorrow is alarm day (dow=\$tomorrow_dow) — hibernating, RTC wake at epoch \$wake_epoch"
|
||||
/usr/bin/sudo /usr/sbin/rtcwake -m no -t "\$wake_epoch"
|
||||
/usr/bin/systemctl hibernate
|
||||
;;
|
||||
*)
|
||||
logger -t day-specific-shutdown "Tomorrow is not an alarm day — powering off normally"
|
||||
/usr/bin/systemctl poweroff
|
||||
;;
|
||||
esac
|
||||
else
|
||||
printf '%(%Y-%m-%d %H:%M:%S)T: Skipping shutdown - not within shutdown window for %s (current: %s:%s)\n' -1 "$day_name" "$current_hour" "$current_minute"
|
||||
logger -t day-specific-shutdown "Skipped shutdown - not within shutdown window for $day_name (current: $current_hour:$current_minute)"
|
||||
|
||||
@ -6,32 +6,44 @@
|
||||
# What it does:
|
||||
# 1. Copies wake-alarm.service to ~/.config/systemd/user/
|
||||
# 2. Enables and starts the service
|
||||
# 3. Adds a sudoers entry for passwordless rtcwake
|
||||
# 3. Installs the systemd-sleep hook (restarts alarm after hibernate resume)
|
||||
# 4. Adds a sudoers entry for passwordless rtcwake
|
||||
# 5. Installs shutdown wrapper so "shutdown now" also hibernates on alarm nights
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||
SERVICE_FILE="$SCRIPT_DIR/wake-alarm.service"
|
||||
SLEEP_HOOK_SRC="$SCRIPT_DIR/sleep-hook.sh"
|
||||
SHUTDOWN_WRAPPER_SRC="$SCRIPT_DIR/shutdown-wrapper.sh"
|
||||
SYSTEMD_USER_DIR="$HOME/.config/systemd/user"
|
||||
SLEEP_HOOK_DST="/usr/lib/systemd/system-sleep/wake-alarm.sh"
|
||||
SHUTDOWN_WRAPPER_DST="/usr/local/bin/shutdown"
|
||||
SUDOERS_FILE="/etc/sudoers.d/wake-alarm"
|
||||
RTCWAKE_BIN="/usr/sbin/rtcwake"
|
||||
|
||||
echo "=== Weekend Wake Alarm Installer ==="
|
||||
|
||||
# 1. Install systemd user service
|
||||
echo "[1/3] Installing systemd user service..."
|
||||
echo "[1/4] Installing systemd user service..."
|
||||
mkdir -p "$SYSTEMD_USER_DIR"
|
||||
cp "$SERVICE_FILE" "$SYSTEMD_USER_DIR/wake-alarm.service"
|
||||
systemctl --user daemon-reload
|
||||
echo " Installed to $SYSTEMD_USER_DIR/wake-alarm.service"
|
||||
|
||||
# 2. Enable service
|
||||
echo "[2/3] Enabling wake-alarm.service..."
|
||||
echo "[2/4] Enabling wake-alarm.service..."
|
||||
systemctl --user enable wake-alarm.service
|
||||
echo " Service enabled (will start on next boot)"
|
||||
|
||||
# 3. Add sudoers entry for rtcwake (requires root)
|
||||
echo "[3/3] Setting up sudoers for rtcwake..."
|
||||
# 3. Install systemd-sleep hook (restarts alarm after hibernate resume)
|
||||
echo "[3/4] Installing systemd-sleep hook..."
|
||||
sudo cp "$SLEEP_HOOK_SRC" "$SLEEP_HOOK_DST"
|
||||
sudo chmod 0755 "$SLEEP_HOOK_DST"
|
||||
echo " Installed to $SLEEP_HOOK_DST"
|
||||
|
||||
# 4. Add sudoers entry for rtcwake (requires root)
|
||||
echo "[4/5] Setting up sudoers for rtcwake..."
|
||||
SUDOERS_LINE="$USER ALL=(root) NOPASSWD: $RTCWAKE_BIN"
|
||||
if [[ -f "$SUDOERS_FILE" ]] && grep -qF "$SUDOERS_LINE" "$SUDOERS_FILE"; then
|
||||
echo " Sudoers entry already exists"
|
||||
@ -42,7 +54,15 @@ else
|
||||
echo " Added: $SUDOERS_LINE"
|
||||
fi
|
||||
|
||||
# 5. Install shutdown wrapper (/usr/local/bin/shutdown shadows /usr/bin/shutdown)
|
||||
echo "[5/5] Installing shutdown wrapper..."
|
||||
sudo cp "$SHUTDOWN_WRAPPER_SRC" "$SHUTDOWN_WRAPPER_DST"
|
||||
sudo chmod 0755 "$SHUTDOWN_WRAPPER_DST"
|
||||
echo " Installed to $SHUTDOWN_WRAPPER_DST"
|
||||
echo " 'shutdown now' will now hibernate (not poweroff) on alarm nights."
|
||||
|
||||
echo ""
|
||||
echo "=== Installation complete ==="
|
||||
echo "The wake alarm will activate on boot for alarm days (Mon, Fri, Sat, Sun)."
|
||||
echo "After hibernate resume the sleep hook will restart the alarm service."
|
||||
echo "To test now: python -m python_pkg.wake_alarm._alarm --demo"
|
||||
|
||||
38
python_pkg/wake_alarm/shutdown-wrapper.sh
Executable file
38
python_pkg/wake_alarm/shutdown-wrapper.sh
Executable file
@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
# Wrapper for /usr/bin/shutdown that redirects to rtcwake -m disk on alarm
|
||||
# nights (Mon, Fri, Sat, Sun by tomorrow's day-of-week). This ensures that
|
||||
# both the automated systemd timer AND manual "shutdown now" hibernate
|
||||
# correctly so the PC wakes for the morning alarm.
|
||||
#
|
||||
# Install to /usr/local/bin/shutdown (takes priority over /usr/bin/shutdown
|
||||
# because /usr/local/bin appears first in PATH).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REAL_SHUTDOWN=/usr/bin/shutdown
|
||||
RTCWAKE=/usr/sbin/rtcwake
|
||||
WAKE_AFTER_HOURS=8 # Must match WAKE_AFTER_HOURS in python_pkg/wake_alarm/_constants.py
|
||||
|
||||
# Pass through reboots and cancel commands unchanged.
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
-r|--reboot|-c|--cancel)
|
||||
exec "$REAL_SHUTDOWN" "$@"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Check if tomorrow is an alarm day (Mon=1, Fri=5, Sat=6, Sun=7 in date +%u).
|
||||
tomorrow_dow=$(date -d "tomorrow" +%u)
|
||||
case "$tomorrow_dow" in
|
||||
1|5|6|7)
|
||||
wake_epoch=$(( $(printf '%(%s)T' -1) + WAKE_AFTER_HOURS * 3600 ))
|
||||
logger -t shutdown-wrapper \
|
||||
"Tomorrow is alarm day (dow=$tomorrow_dow) — hibernating, RTC wake at epoch $wake_epoch"
|
||||
sudo "$RTCWAKE" -m no -t "$wake_epoch"
|
||||
exec /usr/bin/systemctl hibernate
|
||||
;;
|
||||
*)
|
||||
exec "$REAL_SHUTDOWN" "$@"
|
||||
;;
|
||||
esac
|
||||
29
python_pkg/wake_alarm/sleep-hook.sh
Executable file
29
python_pkg/wake_alarm/sleep-hook.sh
Executable file
@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
# systemd-sleep hook: restart wake-alarm.service after resume from hibernate.
|
||||
#
|
||||
# Installed to /usr/lib/systemd/system-sleep/wake-alarm.sh by install.sh.
|
||||
#
|
||||
# When the PC hibernates (rtcwake -m disk) and resumes the next morning,
|
||||
# the user session is restored but wake-alarm.service is in a stopped state
|
||||
# (it ran at login the previous evening and exited with Restart=no).
|
||||
# This hook restarts it so the alarm fires on the correct alarm day.
|
||||
|
||||
if [[ "$1" != "post" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
logger -t wake-alarm-hook "Woke from sleep (type=$2) — restarting wake-alarm.service for active sessions"
|
||||
|
||||
# Start wake-alarm.service for every logged-in user that has a running session
|
||||
# bus. Works with systemd >= 219.
|
||||
while IFS= read -r uid; do
|
||||
runtime_dir="/run/user/$uid"
|
||||
[[ -d "$runtime_dir" ]] || continue
|
||||
username=$(id -nu "$uid" 2>/dev/null) || continue
|
||||
logger -t wake-alarm-hook "Starting wake-alarm.service for user $username (uid=$uid)"
|
||||
XDG_RUNTIME_DIR="$runtime_dir" \
|
||||
DBUS_SESSION_BUS_ADDRESS="unix:path=${runtime_dir}/bus" \
|
||||
runuser -u "$username" -- \
|
||||
systemctl --user start wake-alarm.service 2>/dev/null \
|
||||
|| logger -t wake-alarm-hook "Failed to start wake-alarm.service for $username (non-fatal)"
|
||||
done < <(loginctl list-sessions --no-legend 2>/dev/null | awk '{print $2}' | sort -u)
|
||||
6
python_pkg/wake_alarm/wake_state.json
Normal file
6
python_pkg/wake_alarm/wake_state.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"date": "2026-05-22",
|
||||
"dismissed_at": null,
|
||||
"skip_workout": false,
|
||||
"hmac": "2261cf16bef81ce45f8f0664454c441a88bdacf1e71161a10c50472ff63fdf8c"
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user