diff --git a/wake_alarm/install.sh b/wake_alarm/install.sh index 1a5878f..9fb8ce8 100755 --- a/wake_alarm/install.sh +++ b/wake_alarm/install.sh @@ -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" diff --git a/wake_alarm/shutdown-wrapper.sh b/wake_alarm/shutdown-wrapper.sh new file mode 100755 index 0000000..3022e5e --- /dev/null +++ b/wake_alarm/shutdown-wrapper.sh @@ -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 diff --git a/wake_alarm/sleep-hook.sh b/wake_alarm/sleep-hook.sh new file mode 100755 index 0000000..29fd334 --- /dev/null +++ b/wake_alarm/sleep-hook.sh @@ -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) diff --git a/wake_alarm/wake_state.json b/wake_alarm/wake_state.json new file mode 100644 index 0000000..1c22a17 --- /dev/null +++ b/wake_alarm/wake_state.json @@ -0,0 +1,6 @@ +{ + "date": "2026-05-22", + "dismissed_at": null, + "skip_workout": false, + "hmac": "2261cf16bef81ce45f8f0664454c441a88bdacf1e71161a10c50472ff63fdf8c" +}