mirror of
https://github.com/kuhyx/wake-alarm.git
synced 2026-11-11 18:14:04 +01:00
phone_app: the alarm now syncs through the shared crdt_sync transport
(v0.2.0) into the fixed private repo kuhyx/syncs, replacing the bespoke
Gist. AlarmSyncService is the sole writer: it holds the alarm locally in a
crdt_sync LogStore (one HLC-stamped Record) and pushes it to
wake-alarm-sync/devices/phone/alarm.json. gist_service.dart is deleted;
SyncSettings drops the Gist id and keeps only a token; the device-flow scope
moves from `gist` to `repo` (the Contents API needs private-repo access).
shutdown-wrapper.sh: the PC reader now fetches that alarm from the Contents
API (Accept: application/vnd.github.raw, so no base64) and parses the
crdt_sync Record Log with jq (.alarm.fields.alarm_hour[0]). Single-file read
of the sole "phone" device; unchanged fallback to WAKE_AFTER_HOURS on any
miss so a parse failure can never brick the wake. Config moves from
gist_sync.json {token, gist_id} to sync.json {token}.
Verified: flutter analyze clean + tests pass; shellcheck clean and the full
get_synced_alarm_epoch verified against the real Log format; on-device the
app builds, installs with adb install -r (no data loss), launches and
renders. Live round-trip needs a one-time repo-scoped re-auth on the phone
and ~/.config/wake_alarm/sync.json on the PC.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019KwUCPESvE5CjCtmUFz8xQ
92 lines
3.6 KiB
Bash
Executable File
92 lines
3.6 KiB
Bash
Executable File
#!/bin/bash
|
|
# Wrapper for /usr/bin/shutdown that redirects to rtcwake -m disk on alarm
|
|
# nights (every day of the 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).
|
|
#
|
|
# Wake time is taken from the alarm the phone app pushes to the private
|
|
# kuhyx/syncs repo via the shared crdt_sync transport (see phone_app/). Falls
|
|
# back to WAKE_AFTER_HOURS if sync is not configured or unreachable. Config:
|
|
# ~/.config/wake_alarm/sync.json with key "token" (GitHub PAT/token with
|
|
# "repo" scope, so the Contents API can read the private repo).
|
|
|
|
set -euo pipefail
|
|
|
|
REAL_SHUTDOWN=/usr/bin/shutdown
|
|
RTCWAKE=/usr/sbin/rtcwake
|
|
# Fallback: how many hours after shutdown to wake when Gist is unavailable.
|
|
# Must match WAKE_AFTER_HOURS in wake_alarm/_constants.py.
|
|
WAKE_AFTER_HOURS=8
|
|
|
|
# Pass through reboots and cancel commands unchanged.
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
-r|--reboot|-c|--cancel)
|
|
exec "$REAL_SHUTDOWN" "$@"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Read the alarm time the phone pushed to kuhyx/syncs via crdt_sync.
|
|
# Returns the Unix epoch (seconds) of the next occurrence of the stored H:M.
|
|
# Exits non-zero if sync is not configured, unreachable, or malformed.
|
|
#
|
|
# The phone is the sole writer, under the fixed device id "phone", so a single
|
|
# Contents-API read of its alarm.json is enough (no device discovery / HLC
|
|
# ordering needed here — that would only matter with multiple writers). The
|
|
# stored payload is a crdt_sync Log: {"alarm": {"fields": {"alarm_hour":
|
|
# [H, "<hlc>"], "alarm_minute": [M, "<hlc>"]}, ...}}. The raw media type makes
|
|
# the Contents API return that JSON directly, so no base64 decode is needed.
|
|
get_synced_alarm_epoch() {
|
|
local cfg="$HOME/.config/wake_alarm/sync.json"
|
|
[[ -f "$cfg" ]] || return 1
|
|
|
|
local token log_json hour minute target_epoch now_epoch
|
|
token=$(jq -r .token "$cfg" 2>/dev/null) || return 1
|
|
[[ -n "$token" && "$token" != "null" ]] || return 1
|
|
|
|
local url="https://api.github.com/repos/kuhyx/syncs/contents"
|
|
url="$url/wake-alarm-sync/devices/phone/alarm.json"
|
|
log_json=$(curl -sf --max-time 10 \
|
|
-H "Authorization: token $token" \
|
|
-H "Accept: application/vnd.github.raw" \
|
|
"$url") || return 1
|
|
[[ -n "$log_json" ]] || return 1
|
|
|
|
hour=$(printf '%s' "$log_json" \
|
|
| jq -r '.alarm.fields.alarm_hour[0]' 2>/dev/null) || return 1
|
|
minute=$(printf '%s' "$log_json" \
|
|
| jq -r '.alarm.fields.alarm_minute[0]' 2>/dev/null) || return 1
|
|
[[ "$hour" =~ ^[0-9]+$ && "$minute" =~ ^[0-9]+$ ]] || return 1
|
|
|
|
printf -v now_epoch '%(%s)T' -1
|
|
target_epoch=$(date -d "today $hour:$minute" +%s)
|
|
if (( target_epoch <= now_epoch )); then
|
|
target_epoch=$(date -d "tomorrow $hour:$minute" +%s)
|
|
fi
|
|
echo "$target_epoch"
|
|
}
|
|
|
|
# Every day of the week is an alarm day (date +%u: Mon=1 … Sun=7).
|
|
tomorrow_dow=$(date -d "tomorrow" +%u)
|
|
case "$tomorrow_dow" in
|
|
1|2|3|4|5|6|7)
|
|
if wake_epoch=$(get_synced_alarm_epoch 2>/dev/null); then
|
|
logger -t shutdown-wrapper \
|
|
"crdt_sync: waking at $(date -d "@$wake_epoch") (epoch $wake_epoch)"
|
|
else
|
|
wake_epoch=$(( $(printf '%(%s)T' -1) + WAKE_AFTER_HOURS * 3600 ))
|
|
logger -t shutdown-wrapper \
|
|
"sync unavailable — fallback +${WAKE_AFTER_HOURS}h (epoch $wake_epoch)"
|
|
fi
|
|
sudo "$RTCWAKE" -m no -t "$wake_epoch"
|
|
exec /usr/bin/systemctl hibernate
|
|
;;
|
|
*)
|
|
exec "$REAL_SHUTDOWN" "$@"
|
|
;;
|
|
esac
|