wake-alarm/wake-alarm.service
Krzysztof kuhy Rudnicki 87d2629e88
Some checks are pending
pre-commit / pre-commit (push) Waiting to run
Tests / test (3.10) (push) Waiting to run
Tests / test (3.11) (push) Waiting to run
Tests / test (3.12) (push) Waiting to run
Fire the alarm at the phone's time via a per-minute timer
The PC is no longer shut down at night, which broke the scheduling model
outright. The wake time used to be decided once, at hibernate, and baked
into the RTC by shutdown-wrapper.sh. With no shutdown there is no resume,
so sleep-hook.sh never ran and nothing fired the alarm at all.

Worse, wake-alarm.service was WantedBy=graphical-session.target and
enabled by install.sh, while _should_run_alarm() gates on day / dismissed
/ workout-logged but has no *time* gate. On an always-on machine that
target is reached on every login, so the alarm fired at login time on any
alarm day — which is every day.

Replace both with a systemd user timer ticking once a minute:

- _sync.py reads the alarm the phone pushed to kuhyx/syncs, mirroring
  shutdown-wrapper.sh's jq parse so the two readers cannot drift. Caches
  each read, then degrades fresh cache -> fetch -> stale cache ->
  FALLBACK_ALARM_HOUR, so a network blip can never cost a wake-up.
  stdlib-only: the units run /usr/bin/python, so a dependency here is a
  production install-path hazard.
- _trigger.py fires morning-routine.service (never wake-alarm.service —
  that skips the workout lock) when local time is inside
  [alarm, alarm + TRIGGER_CATCHUP_MINUTES). The catch-up window covers a
  suspended or just-booted machine; starting an already-active unit is a
  no-op, so an alarm on screen needs no extra dedupe state.
- wake-alarm.service loses its [Install]; install.sh disables it.

A timer rather than a resident scheduler: each tick is a fresh process,
so there is no daemon to die silently and no wall-clock jump to handle
across suspend/resume. Ticks are per-minute for exact-minute firing, but
only hit the network when the cache is older than 5 minutes — ~288
GitHub reads/day, and phone changes land within ~5 minutes.

Degraded reads log at INFO, not WARNING: at one tick a minute a
persistent condition would be 1440 identical journal lines a day. The one
WARNING kept is _trigger firing on a non-phone alarm time.

Token resolution gains a fallback to the sibling apps' repo-scoped
sync_token, in both readers, so one PAT covers every app on kuhyx/syncs.

Note this surfaced that wake-alarm-sync/ does not exist in kuhyx/syncs at
all: the phone has never completed a push since the crdt_sync migration,
so the hibernate path had been silently using the +8h fallback. Fixing
that is a phone-side action (reconnect GitHub with repo scope, then one
"Set Alarm & Sync"). Alarms edited directly in the Clock app still do not
reach the sync — that needs an ACTION_NEXT_ALARM_CLOCK_CHANGED receiver.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 20:21:17 +02:00

32 lines
1.4 KiB
Desktop File

[Unit]
Description=Weekend Wake Alarm
After=graphical-session.target
[Service]
Type=simple
# DISPLAY mirrors workout-locker.service: without DISPLAY the alarm crashes
# on cold boot with "no display name and no $DISPLAY" (Tk can't open the X
# server) before systemd retries. The short sleep lets the X session export
# DISPLAY into the user environment first.
# No PYTHONPATH needed: wake_alarm is pip-installed (see install.sh /
# README), so /usr/bin/python finds it via user site-packages.
Environment=DISPLAY=:0
ExecStartPre=/bin/sleep 1
ExecStart=/usr/bin/python -m wake_alarm._alarm --production
Restart=on-failure
RestartSec=10
# There is intentionally NO [Install] section, and nothing auto-starts this
# unit. The PC is always on now, so graphical-session.target is reached on
# every login — and _should_run_alarm() has no time gate, so a
# WantedBy=graphical-session.target here fires the alarm at *login time* on
# any alarm day (i.e. every day). That was the bug this design removed.
#
# What actually runs the alarm: wake-alarm-trigger.timer ticks once a minute
# and starts morning-routine.service at the phone's alarm time; that
# orchestrator runs `python -m wake_alarm._alarm` as a subprocess (the alarm
# first, the workout lock after). This unit survives only as a manual test
# handle: `systemctl --user start wake-alarm.service`.
#
# Do not "fix" the missing auto-start.