From 5d4d3b9e24782cc2d0cd8ae8a276234ca7c3b7de Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Mon, 20 Jul 2026 08:50:45 +0200 Subject: [PATCH] night-lockdown: stop firing the morning alarm on a fixed hour MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The unlock script started morning-routine.service on every staggered trigger (05:00/05:15/05:30/06:00/07:00) on alarm days. That was a workaround for the always-on lockdown removing the hibernate/resume event the alarm used to ride on — reasonable when nothing else could fire it. Something else fires it now. wake-alarm-trigger.timer ticks every minute and starts morning-routine.service at the time the phone synced, with a catch-up window for a suspended or just-booted machine. Keeping both meant two independent triggers, one of which ignores the phone entirely. That is not hypothetical: on 2026-07-20 the phone was set to 08:00 and the alarm went off at 07:00, started here, with no wake-alarm-trigger "Firing" line in the journal at all. The 08:00 fire then landed as a no-op because this 07:00 run had left morning-routine.service still active. Drop maybe_start_morning_alarm() and both call sites, plus the config that went dead with it (ALARM_DAYS_DOW, MORNING_ROUTINE_UNIT, the alarm_started_date marker). MIN_WAKE_AFTER_LOCKDOWN_HOURS stays — the wake-floor trigger uses it. The lockdown's actual job — console/GUI restore, unmute, unmasking the login surface — is untouched. The only starters of morning-routine.service left are wake-alarm-trigger.timer (primary) and the system-sleep hook (resume backstop). Co-Authored-By: Claude Opus 4.8 (1M context) --- ...ockdown-drop-morning-alarm-2026-07-20.json | 47 +++++++++++ .../digital_wellbeing/setup_night_lockdown.sh | 79 ++----------------- 2 files changed, 54 insertions(+), 72 deletions(-) create mode 100644 docs/superpowers/evidence/night-lockdown-drop-morning-alarm-2026-07-20.json diff --git a/docs/superpowers/evidence/night-lockdown-drop-morning-alarm-2026-07-20.json b/docs/superpowers/evidence/night-lockdown-drop-morning-alarm-2026-07-20.json new file mode 100644 index 00000000..1fb50ac9 --- /dev/null +++ b/docs/superpowers/evidence/night-lockdown-drop-morning-alarm-2026-07-20.json @@ -0,0 +1,47 @@ +{ + "intent": "The PC's wake alarm must fire at the time the phone synced, and only that time. night-lockdown-unlock.sh also started morning-routine.service on a fixed 05:00/05:15/05:30/06:00/07:00 ladder, so on 2026-07-20 the alarm went off at 07:00 while the phone said 08:00. Remove that second, phone-independent trigger.", + "scope": [ + "linux_configuration/scripts/periodic_background/digital_wellbeing/setup_night_lockdown.sh (generator for /usr/local/bin/night-lockdown-unlock.sh)", + "Non-goal: the lockdown's own job (console/GUI restore, unmute, login-surface unmask) is untouched", + "Non-goal: wake-alarm and screen-locker repos are unchanged by this commit" + ], + "changes": [ + "Deleted maybe_start_morning_alarm() and both call sites from inside the UNLOCK_EOF heredoc, leaving a NOTE explaining that wake-alarm-trigger.timer now owns alarm firing.", + "Removed the config keys that went dead with it: ALARM_DAYS_DOW, MORNING_ROUTINE_UNIT, and the alarm_started_date dedup marker. MIN_WAKE_AFTER_LOCKDOWN_HOURS is kept — the wake-floor trigger still uses it." + ], + "verification": [ + { + "command": "bash -n setup_night_lockdown.sh && shellcheck setup_night_lockdown.sh", + "result": "pass", + "evidence": "syntax OK; shellcheck reports only the pre-existing SC1091 info for the sourced lib/common.sh" + }, + { + "command": "sudo bash setup_night_lockdown.sh setup", + "result": "pass", + "evidence": "All 7 'present:' checks OK, timer enabled, 'Night lockdown installed.' cmd_setup is install-only (writes config/scripts/units, daemon-reload, enable --now on the already-enabled timer and rgb-off oneshot) — it does not apply lockdown state." + }, + { + "command": "grep -nE 'systemctl.*start.*morning|maybe_start_morning' /usr/local/bin/night-lockdown-unlock.sh", + "result": "pass", + "evidence": "No matches. The single remaining 'morning-routine' hit is the new explanatory comment on line 49." + }, + { + "command": "sudo DRY_RUN=1 bash /usr/local/bin/night-lockdown-unlock.sh", + "result": "pass", + "evidence": "Ends at 'already UNLOCKED — nothing else to do' with no alarm start attempted." + }, + { + "command": "grep -rl morning-routine /usr/local/bin /etc/systemd /usr/lib/systemd ~/.config/systemd/user", + "result": "pass", + "evidence": "Starters are now only wake-alarm-trigger.service (primary, per-minute, phone's time) and /usr/lib/systemd/system-sleep/wake-alarm.sh (resume backstop)." + } + ], + "risks": [ + "If wake-alarm-trigger.timer is ever disabled or wake_alarm stops resolving the phone's alarm, there is no longer a fixed-hour fallback on an always-on PC — the alarm simply will not fire. Mitigated by wake_alarm's own FALLBACK_ALARM_HOUR and its stale-cache path.", + "Not observed end to end: no real lockdown-exit morning has happened since the change. Verified by dry-run and by enumerating starters, not by a live 05:00-07:00 unlock." + ], + "rollback": [ + "git revert this commit, then re-run 'sudo bash setup_night_lockdown.sh setup' to regenerate /usr/local/bin/night-lockdown-unlock.sh with the alarm start restored.", + "After rollback, confirm with: grep -n maybe_start_morning_alarm /usr/local/bin/night-lockdown-unlock.sh (expect the function back), and expect the 07:00 double-fire to return." + ] +} diff --git a/linux_configuration/scripts/periodic_background/digital_wellbeing/setup_night_lockdown.sh b/linux_configuration/scripts/periodic_background/digital_wellbeing/setup_night_lockdown.sh index 8f140b4c..dae7d74f 100755 --- a/linux_configuration/scripts/periodic_background/digital_wellbeing/setup_night_lockdown.sh +++ b/linux_configuration/scripts/periodic_background/digital_wellbeing/setup_night_lockdown.sh @@ -122,14 +122,6 @@ CONSOLE_TTY="/dev/tty1" # whole point, without touching the link. CONSOLE_BLANK_MODE="1" -# Morning wake alarm. The always-on lockdown removes the hibernate/resume event -# that used to start the alarm, so the unlock re-fires it on alarm days. Days are -# date(1) %u values: 1=Mon .. 7=Sun. Mon/Fri/Sat/Sun matches screen_locker's -# ALARM_DAYS. The alarm's own gate fires every day, so the day-gate lives here. -ALARM_DAYS_DOW="1 5 6 7" -# User systemd unit that runs the morning routine (wake alarm + workout lock). -MORNING_ROUTINE_UNIT="morning-routine.service" - # Never start the morning alarm earlier than lockdown-entry + this many hours, # even though the unlock timer itself still fires on its normal staggered # schedule (05:00/05:15/05:30/06:00/07:00). A late lockdown entry (e.g. 23:00 @@ -397,8 +389,6 @@ readonly STATE_FILE="$STATE_DIR/state" ALSA_CARDS="${ALSA_CARDS:-}" LOCK_USER="${LOCK_USER:-}" LOCK_UID="${LOCK_UID:-}" -ALARM_DAYS_DOW="${ALARM_DAYS_DOW:-}" -MORNING_ROUTINE_UNIT="${MORNING_ROUTINE_UNIT:-}" MIN_WAKE_AFTER_LOCKDOWN_HOURS="${MIN_WAKE_AFTER_LOCKDOWN_HOURS:-8}" AUTORANDR_PROFILE="${AUTORANDR_PROFILE:-default}" CONSOLE_TTY="${CONSOLE_TTY:-/dev/tty1}" @@ -416,7 +406,7 @@ if [[ "$DRY_RUN" == "1" ]]; then logg "DRY_RUN NOTE: the real DisplayPort reconnect or graphical-session wait." logg "DRY_RUN NOTE: A clean DRY_RUN run is NOT proof those work — only a real" logg "DRY_RUN NOTE: run (DRY_RUN unset) validates them. The alarm floor/dedup" - logg "DRY_RUN NOTE: logic (locked_at_epoch, alarm_started_date) IS exercised" + logg "DRY_RUN NOTE: logic (locked_at_epoch) IS exercised" logg "DRY_RUN NOTE: faithfully here, since that part is pure calculation." fi @@ -430,67 +420,16 @@ run() { fi } -# Fire the morning wake alarm on alarm days. The always-on lockdown removed the -# hibernate/resume event that used to start morning-routine.service, so we start -# it here once the desktop session is back. Called on every staggered trigger -# (not just the one that performs the console/GUI restore), because -# MIN_WAKE_AFTER_LOCKDOWN_HOURS below may push the actual alarm start past the -# trigger that unlocked the screen — dedup is its own alarm_started_date marker, -# independent of the LOCKED/UNLOCKED state file. -maybe_start_morning_alarm() { - [[ -n "$LOCK_USER" && -n "$LOCK_UID" && -n "$MORNING_ROUTINE_UNIT" ]] || return 0 - local dow today_is_alarm_day=false d today - dow="$(date +%u)" # 1=Mon .. 7=Sun - for d in $ALARM_DAYS_DOW; do - [[ "$d" == "$dow" ]] && today_is_alarm_day=true - done - if [[ "$today_is_alarm_day" != true ]]; then - logg "not an alarm day (dow=$dow) — skipping morning alarm" - return 0 - fi - today="$(date +%F)" - if [[ "$(cat "$STATE_DIR/alarm_started_date" 2>/dev/null || echo '')" == "$today" ]]; then - logg "morning alarm already started today — skipping" - return 0 - fi - # Never wake earlier than lockdown-entry + MIN_WAKE_AFTER_LOCKDOWN_HOURS. A - # late lockdown entry (e.g. 23:00 instead of the usual 21:00) must not still - # fire the alarm at the normal 05:00 trigger; wait for a later staggered - # trigger (05:15/05:30/06:00/07:00) that clears the floor instead. - local locked_at now min_wake - locked_at="$(cat "$STATE_DIR/locked_at_epoch" 2>/dev/null || echo 0)" - now="$(date +%s)" - min_wake=$((locked_at + MIN_WAKE_AFTER_LOCKDOWN_HOURS * 3600)) - if ((locked_at > 0 && now < min_wake)); then - logg "too soon since lockdown entry ($(( (min_wake - now) / 60 ))m remaining) — skipping alarm this trigger" - return 0 - fi - if [[ "$DRY_RUN" == "1" ]]; then - logg "DRY_RUN: would wait for graphical session then start $MORNING_ROUTINE_UNIT" - return 0 - fi - # Bounded wait (≤60s) for the user graphical session to come up after lightdm. - local i - for ((i = 0; i < 60; i++)); do - if sudo -u "$LOCK_USER" env "XDG_RUNTIME_DIR=/run/user/$LOCK_UID" \ - systemctl --user is-active graphical-session.target >/dev/null 2>&1; then - break - fi - sleep 1 - done - logg "alarm day (dow=$dow) — starting $MORNING_ROUTINE_UNIT" - run sudo -u "$LOCK_USER" env "XDG_RUNTIME_DIR=/run/user/$LOCK_UID" \ - systemctl --user start --no-block "$MORNING_ROUTINE_UNIT" - echo "$today" >"$STATE_DIR/alarm_started_date" -} +# NOTE: this script used to also start morning-routine.service here, on a +# fixed 05:00/05:15/05:30/06:00/07:00 ladder. wake-alarm-trigger.timer now +# owns firing the alarm: it ticks every minute and fires at the time the +# phone synced, so the alarm follows the phone instead of a hardcoded hour. +# Keeping both meant a 07:00 alarm on a morning the phone said 08:00. mkdir -p "$STATE_DIR" -# Idempotency — lifting when already unlocked is a safe no-op for the -# console/GUI restore, but a still-pending morning alarm (delayed past this -# trigger by MIN_WAKE_AFTER_LOCKDOWN_HOURS) gets one more chance to fire. +# Idempotency — lifting when already unlocked is a safe no-op. if [[ "$(cat "$STATE_FILE" 2>/dev/null || echo UNLOCKED)" == "UNLOCKED" ]]; then - maybe_start_morning_alarm logg "already UNLOCKED — nothing else to do" exit 0 fi @@ -574,10 +513,6 @@ if [[ "$DRY_RUN" != "1" ]]; then echo UNLOCKED >"$STATE_FILE" fi -# 6. On alarm days, re-fire the morning wake alarm the always-on lockdown -# displaced (it used to start on the hibernate/resume event we removed). -maybe_start_morning_alarm - logg "night lockdown lifted — GUI restored, login surface unmasked" UNLOCK_EOF chmod 0755 "$UNLOCK_SCRIPT"