|
| 1 | +#!/usr/bin/env bash |
| 2 | +# |
| 3 | +# morning_timer.sh — schedule bin/morning.sh to run overnight on THIS machine, |
| 4 | +# so the sync/clean/publish leg is already done when the human wakes up and |
| 5 | +# the board reads "observed this morning" from the first glance. |
| 6 | +# |
| 7 | +# Every leg of morning.sh is local (it syncs and cleans YOUR checkouts and |
| 8 | +# publishes YOUR dev-box observation), so the schedule must live on the dev |
| 9 | +# box — the board's own cloud refresh (brain_board.yml, 05:30 UTC) is separate |
| 10 | +# and unaffected. The publish push re-triggers a board render anyway, so the |
| 11 | +# board updates minutes after the local run regardless of ordering. |
| 12 | +# |
| 13 | +# bash PyAutoBrain/bin/morning_timer.sh install [HH:MM] # default 05:30 local |
| 14 | +# bash PyAutoBrain/bin/morning_timer.sh uninstall |
| 15 | +# bash PyAutoBrain/bin/morning_timer.sh status |
| 16 | +# bash PyAutoBrain/bin/morning_timer.sh print [HH:MM] # show what install |
| 17 | +# # would write |
| 18 | +# |
| 19 | +# Backend: systemd user units (pyauto-morning.service/.timer, Persistent=true |
| 20 | +# so a night the machine slept through is caught up at next wake/boot) when |
| 21 | +# `systemctl --user` works; otherwise a marked crontab line. Override with |
| 22 | +# MORNING_TIMER_MODE=systemd|cron. |
| 23 | +# |
| 24 | +# Prerequisites on the dev box: non-interactive git credentials (the publish |
| 25 | +# leg pushes to the Brain's main), and for the systemd path a user session at |
| 26 | +# the fire time — enable lingering once (`loginctl enable-linger $USER`) so |
| 27 | +# the timer fires before first login; install says so if it is off. |
| 28 | +# |
| 29 | +# Caveat: clean_slate deletes untracked REGENERABLE datasets. If something |
| 30 | +# else runs on this machine overnight and writes datasets it still needs, |
| 31 | +# schedule after it finishes, or point the timer at |
| 32 | +# `morning.sh --no-publish`-style variants by editing the unit. |
| 33 | + |
| 34 | +set -u |
| 35 | + |
| 36 | +HERE="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)" |
| 37 | +MORNING="$HERE/morning.sh" |
| 38 | +UNIT_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user" |
| 39 | +LOG_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/pyauto" |
| 40 | +MARKER="# pyauto-morning (installed by morning_timer.sh)" |
| 41 | + |
| 42 | +when="${2:-05:30}" |
| 43 | +case "$when" in |
| 44 | + [0-2][0-9]:[0-5][0-9]) ;; |
| 45 | + *) echo "morning_timer: time must be HH:MM (got '$when')" >&2; exit 2 ;; |
| 46 | +esac |
| 47 | +hh="${when%%:*}"; mm="${when##*:}" |
| 48 | +if [ "$((10#$hh))" -gt 23 ]; then |
| 49 | + echo "morning_timer: hour must be 00-23 (got '$hh')" >&2; exit 2 |
| 50 | +fi |
| 51 | + |
| 52 | +mode="${MORNING_TIMER_MODE:-}" |
| 53 | +if [ -z "$mode" ]; then |
| 54 | + if command -v systemctl >/dev/null 2>&1 && systemctl --user show-environment >/dev/null 2>&1; then |
| 55 | + mode=systemd |
| 56 | + elif command -v crontab >/dev/null 2>&1; then |
| 57 | + mode=cron |
| 58 | + else |
| 59 | + echo "morning_timer: neither a systemd user session nor crontab is available" >&2 |
| 60 | + exit 1 |
| 61 | + fi |
| 62 | +fi |
| 63 | + |
| 64 | +service_unit() { |
| 65 | + cat <<EOF |
| 66 | +[Unit] |
| 67 | +Description=PyAuto morning routine (sync + clean + dev-box publish) |
| 68 | +
|
| 69 | +[Service] |
| 70 | +Type=oneshot |
| 71 | +ExecStart=/bin/bash $MORNING |
| 72 | +EOF |
| 73 | +} |
| 74 | + |
| 75 | +timer_unit() { |
| 76 | + cat <<EOF |
| 77 | +[Unit] |
| 78 | +Description=Run the PyAuto morning routine overnight |
| 79 | +
|
| 80 | +[Timer] |
| 81 | +OnCalendar=*-*-* $hh:$mm:00 |
| 82 | +Persistent=true |
| 83 | +
|
| 84 | +[Install] |
| 85 | +WantedBy=timers.target |
| 86 | +EOF |
| 87 | +} |
| 88 | + |
| 89 | +cron_line() { |
| 90 | + # Strip a leading zero so cron never sees an octal-looking field. |
| 91 | + printf '%d %d * * * /bin/bash %s >> %s/morning.log 2>&1 %s\n' \ |
| 92 | + "$((10#$mm))" "$((10#$hh))" "$MORNING" "$LOG_DIR" "$MARKER" |
| 93 | +} |
| 94 | + |
| 95 | +case "${1:-status}" in |
| 96 | + print) |
| 97 | + if [ "$mode" = systemd ]; then |
| 98 | + echo "# $UNIT_DIR/pyauto-morning.service"; service_unit |
| 99 | + echo; echo "# $UNIT_DIR/pyauto-morning.timer"; timer_unit |
| 100 | + else |
| 101 | + echo "# crontab line"; cron_line |
| 102 | + fi |
| 103 | + ;; |
| 104 | + install) |
| 105 | + if [ "$mode" = systemd ]; then |
| 106 | + mkdir -p "$UNIT_DIR" |
| 107 | + service_unit > "$UNIT_DIR/pyauto-morning.service" |
| 108 | + timer_unit > "$UNIT_DIR/pyauto-morning.timer" |
| 109 | + systemctl --user daemon-reload |
| 110 | + systemctl --user enable --now pyauto-morning.timer |
| 111 | + echo "installed: pyauto-morning.timer fires daily at $when local (Persistent=true" |
| 112 | + echo "catches up a night the machine slept through). Logs: journalctl --user -u pyauto-morning" |
| 113 | + if command -v loginctl >/dev/null 2>&1 \ |
| 114 | + && [ "$(loginctl show-user "$USER" -p Linger --value 2>/dev/null)" != "yes" ]; then |
| 115 | + echo "note: lingering is OFF — before first login the timer cannot fire." |
| 116 | + echo " enable once with: loginctl enable-linger $USER" |
| 117 | + fi |
| 118 | + else |
| 119 | + mkdir -p "$LOG_DIR" |
| 120 | + { crontab -l 2>/dev/null | grep -vF "$MARKER"; cron_line; } | crontab - |
| 121 | + echo "installed: crontab entry fires daily at $when local. Log: $LOG_DIR/morning.log" |
| 122 | + echo "note: plain cron does not catch up runs the machine slept through." |
| 123 | + fi |
| 124 | + ;; |
| 125 | + uninstall) |
| 126 | + if [ "$mode" = systemd ]; then |
| 127 | + systemctl --user disable --now pyauto-morning.timer 2>/dev/null |
| 128 | + rm -f "$UNIT_DIR/pyauto-morning.service" "$UNIT_DIR/pyauto-morning.timer" |
| 129 | + systemctl --user daemon-reload |
| 130 | + echo "uninstalled pyauto-morning.timer" |
| 131 | + else |
| 132 | + crontab -l 2>/dev/null | grep -vF "$MARKER" | crontab - |
| 133 | + echo "removed the pyauto-morning crontab entry" |
| 134 | + fi |
| 135 | + ;; |
| 136 | + status) |
| 137 | + if [ "$mode" = systemd ]; then |
| 138 | + systemctl --user list-timers pyauto-morning.timer --no-pager 2>/dev/null \ |
| 139 | + || echo "pyauto-morning.timer is not installed" |
| 140 | + else |
| 141 | + crontab -l 2>/dev/null | grep -F "$MARKER" \ |
| 142 | + || echo "no pyauto-morning crontab entry" |
| 143 | + fi |
| 144 | + ;; |
| 145 | + *) |
| 146 | + echo "usage: morning_timer.sh install [HH:MM] | uninstall | status | print [HH:MM]" >&2 |
| 147 | + exit 2 |
| 148 | + ;; |
| 149 | +esac |
0 commit comments