mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 18:47:04 +01:00
Drop the alarm leg from the morning routine
Some checks failed
dependency freshness / dependencies on newest stable (push) Has been cancelled
md-naming / markdown naming convention (push) Has been cancelled
Pre-commit checks / pre-commit (push) Has been cancelled
Python tests / test (push) Has been cancelled
Shell tests / shell-tests (push) Has been cancelled
Shell tests / arch-tests (push) Has been cancelled
Some checks failed
dependency freshness / dependencies on newest stable (push) Has been cancelled
md-naming / markdown naming convention (push) Has been cancelled
Pre-commit checks / pre-commit (push) Has been cancelled
Python tests / test (push) Has been cancelled
Shell tests / shell-tests (push) Has been cancelled
Shell tests / arch-tests (push) Has been cancelled
wake_alarm._alarm no longer exists: the PC's waking role was deleted in kuhyx/wake-alarm, and the phone owns the alarm now. This removes ALARM_MODULE, _run_alarm() and the --with-alarm flag, leaving one leg -- the workout lock. Landing this in the same change as the deletion is the point. An orchestrator left pointing at a module that no longer exists is exactly the failure this repo has already had once: after screen-locker was extracted on 2026-05-28 the reference here went stale and the workout lock died with ModuleNotFoundError on every run for a month, while the alarm half kept working and hid it. morning-routine.service loses --with-alarm and its comments now name what actually starts it: wake-alarm-trigger.timer, at the phone's synced alarm time, not the deleted systemd-sleep hook. Environment=DISPLAY=:0 stays -- the comment credited it to the alarm's Tk window, but workout-locker.service needs it too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PcdngP688mW6rJye3LRkSN
This commit is contained in:
parent
89a44b58b2
commit
42e41319dd
@ -0,0 +1,43 @@
|
||||
{
|
||||
"intent": "Keep the morning workout lock running after the PC's wake alarm was deleted upstream, by removing the orchestrator's now-dangling alarm leg in the same change.",
|
||||
"scope": [
|
||||
"python_pkg/morning_routine/_orchestrator.py",
|
||||
"python_pkg/morning_routine/morning-routine.service",
|
||||
"python_pkg/morning_routine/install.sh",
|
||||
"python_pkg/morning_routine/tests/test_orchestrator.py",
|
||||
"Non-goal: any change to screen_locker or to the workout lock itself",
|
||||
"Non-goal: removing the orchestrator, which wake_alarm's TRIGGER_TARGET_UNIT still names"
|
||||
],
|
||||
"changes": [
|
||||
"Removed ALARM_MODULE, _run_alarm() and the --with-alarm flag; main() now runs only the workout lock. wake_alarm._alarm no longer exists after kuhyx/wake-alarm retired the PC's waking role.",
|
||||
"morning-routine.service drops --with-alarm; its Description and comments now name wake-alarm-trigger.timer as what starts it, instead of the deleted systemd-sleep hook. Environment=DISPLAY=:0 kept, because workout-locker.service needs it even though the comment credited it to the alarm's Tk window.",
|
||||
"install.sh header no longer promises rtcwake/python-kasa/sleep-hook prerequisites that upstream no longer installs.",
|
||||
"Bumped hypothesis 6.165.10 -> 6.167.0 in meta/requirements.txt to clear a pre-existing red dependency-freshness baseline on main, unrelated to this change but blocking the commit."
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "/usr/bin/python3 -m pytest python_pkg/morning_routine/tests/ -q",
|
||||
"result": "pass",
|
||||
"evidence": "7 passed. Alarm-leg tests replaced by test_main_runs_the_workout_lock and test_production_flag_is_still_accepted (systemd passes --production)."
|
||||
},
|
||||
{
|
||||
"command": "shellcheck python_pkg/morning_routine/install.sh",
|
||||
"result": "pass",
|
||||
"evidence": "clean"
|
||||
},
|
||||
{
|
||||
"command": "grep -rn 'with-alarm|ALARM_MODULE|_run_alarm' python_pkg/morning_routine/",
|
||||
"result": "pass",
|
||||
"evidence": "no matches; the flag is gone from source, unit file, installer and tests together"
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"The installed ~/.config/systemd/user/morning-routine.service is a copy, not a symlink, so it keeps the old --with-alarm ExecStart until install.sh is re-run. Until then the orchestrator receives an unknown flag and argparse exits 2, so the workout lock would not run.",
|
||||
"This is the exact failure class the change exists to prevent: on 2026-05-28 a stale module reference here left the workout lock dying with ModuleNotFoundError for a month while the alarm half kept working and hid it."
|
||||
],
|
||||
"rollback": [
|
||||
"git revert the commit, then re-run bash python_pkg/morning_routine/install.sh to restore the unit file.",
|
||||
"Rollback is only safe together with reverting kuhyx/wake-alarm's deletion commit: restoring --with-alarm while wake_alarm._alarm is absent reintroduces the dangling reference.",
|
||||
"After rollback: systemctl --user cat morning-routine.service and confirm the ExecStart matches the repo copy."
|
||||
]
|
||||
}
|
||||
@ -38,7 +38,7 @@ gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.2#subdirectory=gatel
|
||||
genanki==0.13.1
|
||||
geopandas==1.1.4
|
||||
howlongtobeatpy==1.0.23
|
||||
hypothesis==6.165.10
|
||||
hypothesis==6.167.0
|
||||
importlib-metadata==9.0.1
|
||||
interrogate==1.7.0
|
||||
# isort is deliberately not pinned: nothing here imports it (ruff's isort
|
||||
|
||||
@ -1,27 +1,21 @@
|
||||
"""Orchestrate the morning wake/workout flow as one sequential routine.
|
||||
"""Run the morning workout lock, started at the phone's alarm time.
|
||||
|
||||
The wake alarm (``wake_alarm``, https://github.com/kuhyx/wake-alarm) and the
|
||||
workout screen lock (``screen_locker``, https://github.com/kuhyx/screen-locker)
|
||||
used to run as two independent ``graphical-session.target`` user services,
|
||||
each opening its own fullscreen ``-topmost`` Tk window. On a wake morning they
|
||||
could grab the screen at the same time, so the alarm could end up hidden
|
||||
behind the workout lock (or vice versa).
|
||||
This used to sequence two fullscreen Tk windows: the PC wake alarm first, then
|
||||
the workout screen lock. The PC no longer wakes anyone -- the phone does, and
|
||||
`wake_alarm._alarm` has been deleted -- so there is only one leg left and no
|
||||
collision left to prevent.
|
||||
|
||||
This orchestrator makes them one coherent flow by running them as **sequential
|
||||
subprocesses**: the alarm runs first and owns the fullscreen until it is
|
||||
dismissed, then the workout lock runs. Only one fullscreen window is ever alive
|
||||
at a time, so they can never collide. Each subprocess still self-gates (the
|
||||
alarm only fires on alarm days when undismissed; the lock exits if a skip was
|
||||
earned or the workout is already logged), so this is safe to run on every wake.
|
||||
What still points here: `wake-alarm-trigger.timer` ticks every minute and
|
||||
starts `morning-routine.service` at the synced alarm time
|
||||
(`wake_alarm._constants.TRIGGER_TARGET_UNIT`). Keeping this indirection means
|
||||
that unit name stays true, and the workout lock keeps its own gates.
|
||||
|
||||
Both ``wake_alarm`` and ``screen_locker`` are pip-installed into system
|
||||
Python's user site-packages (each repo's own install.sh does this), so
|
||||
``python -m <module>`` resolves them with no extra ``PYTHONPATH``/``cwd``
|
||||
plumbing here.
|
||||
``screen_locker`` is pip-installed into system Python's user site-packages by
|
||||
its own install.sh, so ``python -m <module>`` resolves it with no extra
|
||||
``PYTHONPATH``/``cwd`` plumbing here.
|
||||
|
||||
Usage:
|
||||
python -m python_pkg.morning_routine._orchestrator --with-alarm # resume
|
||||
python -m python_pkg.morning_routine._orchestrator # lock only
|
||||
python -m python_pkg.morning_routine._orchestrator
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@ -36,7 +30,6 @@ from python_pkg.shared.logging_setup import configure_logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
# Modules invoked as ``python -m <module> --production``.
|
||||
ALARM_MODULE: str = "wake_alarm._alarm"
|
||||
WORKOUT_LOCK_MODULE: str = "screen_locker.screen_lock"
|
||||
|
||||
|
||||
@ -59,11 +52,6 @@ def _run_module(module: str) -> int:
|
||||
return result.returncode
|
||||
|
||||
|
||||
def _run_alarm() -> int:
|
||||
"""Run the wake alarm and block until it is dismissed (or self-exits)."""
|
||||
return _run_module(ALARM_MODULE)
|
||||
|
||||
|
||||
def _run_workout_lock() -> int:
|
||||
"""Run the workout screen lock after the alarm has been dealt with."""
|
||||
return _run_module(WORKOUT_LOCK_MODULE)
|
||||
@ -72,11 +60,6 @@ def _run_workout_lock() -> int:
|
||||
def _parse_args(argv: list[str]) -> argparse.Namespace:
|
||||
"""Parse CLI arguments for the orchestrator."""
|
||||
parser = argparse.ArgumentParser(description="Unified morning routine.")
|
||||
parser.add_argument(
|
||||
"--with-alarm",
|
||||
action="store_true",
|
||||
help="Run the wake alarm before the workout lock (used on resume).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--production",
|
||||
action="store_true",
|
||||
@ -86,14 +69,9 @@ def _parse_args(argv: list[str]) -> argparse.Namespace:
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Entry point: optionally run the alarm, then always run the workout lock."""
|
||||
"""Entry point: run the workout lock."""
|
||||
configure_logging()
|
||||
args = _parse_args(sys.argv[1:])
|
||||
# Alarm first so it owns the fullscreen and escalates until dismissed; only
|
||||
# then hand off to the workout lock. Running them in this order in a single
|
||||
# process guarantees they never fight for the screen.
|
||||
if args.with_alarm:
|
||||
_run_alarm()
|
||||
_parse_args(sys.argv[1:])
|
||||
_run_workout_lock()
|
||||
|
||||
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
#!/bin/bash
|
||||
# Install the unified morning routine: wake alarm -> workout lock as one flow.
|
||||
# Install the morning routine: the workout lock, started at the alarm time.
|
||||
#
|
||||
# What it does:
|
||||
# 1. Installs morning-routine.service (user service, started on resume).
|
||||
# 2. Disables the standalone wake-alarm.service autostart: the orchestrator
|
||||
# runs the alarm now, and this also removes its evening-login firing quirk.
|
||||
# 1. Installs morning-routine.service (user service, started at alarm time).
|
||||
# 2. Clears the retired standalone wake-alarm.service left by old installs.
|
||||
# 3. Leaves workout-locker.service + the early-bird timer for login / 08:30.
|
||||
#
|
||||
# Prereq: run wake_alarm's own install.sh first (https://github.com/kuhyx/wake-alarm) —
|
||||
# it installs the rtcwake/fan sudoers entries, python-kasa, and the
|
||||
# systemd-sleep hook that starts morning-routine.service on resume. This
|
||||
# script does not duplicate that hook install.
|
||||
# Prereq: run wake_alarm's own install.sh first (https://github.com/kuhyx/wake-alarm).
|
||||
# It installs wake-alarm-trigger.timer, which ticks every minute and starts
|
||||
# this unit at the time the phone synced. The PC no longer wakes anyone, so
|
||||
# there is no longer a shutdown wrapper or systemd-sleep hook involved.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@ -27,17 +26,17 @@ cp "$SERVICE_SRC" "$SYSTEMD_USER_DIR/morning-routine.service"
|
||||
systemctl --user daemon-reload
|
||||
echo " Installed to $SYSTEMD_USER_DIR/morning-routine.service"
|
||||
|
||||
# 2. Disable the standalone wake-alarm.service autostart (orchestrator owns it).
|
||||
echo "[2/2] Disabling standalone wake-alarm.service autostart..."
|
||||
# 2. Clear the retired wake-alarm.service, which no longer exists upstream.
|
||||
echo "[2/2] Clearing the retired wake-alarm.service..."
|
||||
if systemctl --user cat wake-alarm.service &>/dev/null; then
|
||||
systemctl --user disable wake-alarm.service 2>/dev/null || true
|
||||
systemctl --user stop wake-alarm.service 2>/dev/null || true
|
||||
echo " wake-alarm.service autostart disabled (alarm runs via orchestrator)"
|
||||
systemctl --user disable wake-alarm.service 2>/dev/null || true
|
||||
systemctl --user stop wake-alarm.service 2>/dev/null || true
|
||||
echo " retired wake-alarm.service disabled"
|
||||
else
|
||||
echo " wake-alarm.service not installed; nothing to disable"
|
||||
echo " wake-alarm.service not installed; nothing to clear"
|
||||
fi
|
||||
|
||||
echo "=== Installation complete ==="
|
||||
echo "On resume the morning routine runs: wake alarm -> workout lock."
|
||||
echo "wake-alarm-trigger.timer starts this at the phone's synced alarm time."
|
||||
echo "Test now:"
|
||||
echo " python -m python_pkg.morning_routine._orchestrator --with-alarm --production"
|
||||
echo " python -m python_pkg.morning_routine._orchestrator --production"
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
[Unit]
|
||||
Description=Unified Morning Routine (wake alarm + workout lock)
|
||||
Description=Morning Routine (workout lock)
|
||||
After=graphical-session.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
# Started by the systemd-sleep hook on resume (NOT auto-started at login), so
|
||||
# there is intentionally no [Install] WantedBy=graphical-session.target: the
|
||||
# alarm must only run on a real wake, never on an ordinary evening login.
|
||||
# Started by wake-alarm-trigger.timer at the phone's synced alarm time, so
|
||||
# there is intentionally no [Install] WantedBy=graphical-session.target: it
|
||||
# must only run at alarm time, never on an ordinary evening login.
|
||||
# DISPLAY/PYTHONPATH mirror workout-locker.service so Tk can open the X server;
|
||||
# the short sleep lets the resumed session export DISPLAY first.
|
||||
# the short sleep lets the session export DISPLAY first.
|
||||
Environment=DISPLAY=:0
|
||||
Environment=PYTHONPATH=%h/testsAndMisc
|
||||
ExecStartPre=/bin/sleep 1
|
||||
ExecStart=/usr/bin/python -m python_pkg.morning_routine._orchestrator --with-alarm --production
|
||||
ExecStart=/usr/bin/python -m python_pkg.morning_routine._orchestrator --production
|
||||
WorkingDirectory=%h/testsAndMisc
|
||||
Restart=on-failure
|
||||
RestartSec=2s
|
||||
|
||||
@ -5,10 +5,8 @@ from __future__ import annotations
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from python_pkg.morning_routine._orchestrator import (
|
||||
ALARM_MODULE,
|
||||
WORKOUT_LOCK_MODULE,
|
||||
_parse_args,
|
||||
_run_alarm,
|
||||
_run_module,
|
||||
_run_workout_lock,
|
||||
main,
|
||||
@ -41,13 +39,7 @@ class TestRunModule:
|
||||
|
||||
|
||||
class TestRunHelpers:
|
||||
"""Tests for _run_alarm and _run_workout_lock."""
|
||||
|
||||
def test_run_alarm_runs_alarm_module(self) -> None:
|
||||
"""_run_alarm delegates to _run_module with the alarm module."""
|
||||
with patch(f"{_ORCH}._run_module", return_value=0) as mock_run:
|
||||
assert _run_alarm() == 0
|
||||
mock_run.assert_called_once_with(ALARM_MODULE)
|
||||
"""Tests for _run_workout_lock."""
|
||||
|
||||
def test_run_workout_lock_runs_lock_module(self) -> None:
|
||||
"""_run_workout_lock delegates to _run_module with the lock module."""
|
||||
@ -59,44 +51,32 @@ class TestRunHelpers:
|
||||
class TestParseArgs:
|
||||
"""Tests for _parse_args."""
|
||||
|
||||
def test_with_alarm_flag(self) -> None:
|
||||
"""--with-alarm sets with_alarm True."""
|
||||
assert _parse_args(["--with-alarm"]).with_alarm is True
|
||||
|
||||
def test_default_no_alarm(self) -> None:
|
||||
"""No flag leaves with_alarm False."""
|
||||
assert _parse_args([]).with_alarm is False
|
||||
|
||||
def test_production_flag(self) -> None:
|
||||
"""--production is accepted."""
|
||||
assert _parse_args(["--production"]).production is True
|
||||
|
||||
|
||||
class TestMain:
|
||||
"""Tests for main() sequencing."""
|
||||
"""Tests for main()."""
|
||||
|
||||
def test_with_alarm_runs_alarm_then_lock(self) -> None:
|
||||
"""--with-alarm runs the alarm first, then the workout lock, in order."""
|
||||
manager = MagicMock()
|
||||
def test_main_runs_the_workout_lock(self) -> None:
|
||||
"""The one remaining leg runs; the alarm leg is gone entirely."""
|
||||
with (
|
||||
patch(f"{_ORCH}._run_alarm", manager.alarm),
|
||||
patch(f"{_ORCH}._run_workout_lock", manager.lock),
|
||||
patch(f"{_ORCH}.sys") as mock_sys,
|
||||
patch(f"{_ORCH}.logging.basicConfig"),
|
||||
):
|
||||
mock_sys.argv = ["orch", "--with-alarm"]
|
||||
main()
|
||||
assert [call[0] for call in manager.mock_calls] == ["alarm", "lock"]
|
||||
|
||||
def test_without_alarm_runs_only_lock(self) -> None:
|
||||
"""Without --with-alarm, the alarm is skipped and only the lock runs."""
|
||||
with (
|
||||
patch(f"{_ORCH}._run_alarm") as mock_alarm,
|
||||
patch(f"{_ORCH}._run_workout_lock") as mock_lock,
|
||||
patch(f"{_ORCH}.sys") as mock_sys,
|
||||
patch(f"{_ORCH}.logging.basicConfig"),
|
||||
):
|
||||
mock_sys.argv = ["orch"]
|
||||
main()
|
||||
mock_alarm.assert_not_called()
|
||||
mock_lock.assert_called_once()
|
||||
|
||||
def test_production_flag_is_still_accepted(self) -> None:
|
||||
"""systemd passes --production; main must not choke on it."""
|
||||
with (
|
||||
patch(f"{_ORCH}._run_workout_lock") as mock_lock,
|
||||
patch(f"{_ORCH}.sys") as mock_sys,
|
||||
patch(f"{_ORCH}.logging.basicConfig"),
|
||||
):
|
||||
mock_sys.argv = ["orch", "--production"]
|
||||
main()
|
||||
mock_lock.assert_called_once()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user