From eb0c2191b77e795cca7884e0690ea51c515527fd Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Mon, 3 Aug 2026 09:39:08 -0500 Subject: fix(calendar-sync): run the sync from a timer instead of the agenda hook The hourly timer armed itself from org-agenda-mode-hook, so a session where I never opened the agenda never synced at all. After a reboot that is every session until the first agenda call. I found it with all three calendar files still frozen at the pre-reboot write, five hours stale. The deferral had a real reason. Feed URLs behind :secret-host live in authinfo.gpg, and starting at load would prompt for a passphrase on a cold gpg-agent. The cost was worse than what it bought. A user timer owns the schedule now, every ten minutes. calendar-sync-auto-start defaults to nil so the editor no longer arms its own. scripts/calendar-sync-run is the batch entry point, and it blocks until every calendar leaves the syncing state. The pipeline is asynchronous end to end, so a batch Emacs that returns early exits zero having written nothing. Installing it on a machine that resolves its feeds through :secret-host surfaced two bugs the inline-url machine could never show: - cj/auth-source-secret-value called auth-source-search behind nothing but a declare-function, which quiets the compiler and loads nothing. An interactive Emacs always has auth-source in by the time anyone calls, so the omission stayed invisible until a batch -Q sync died on a void function. The require is guarded on fboundp. An unconditional one reloads auth-source over whatever is already there, which replaced a caller's stubbed search mid-call and sent an existing test out to the real authinfo. - A synchronous failure in one calendar aborted the whole loop. The async callbacks record their own failures, but they never run when the error lands before a process starts. Resolving a :secret-host feed signals outright on a cold agent. Failures are contained per calendar now, so one bad feed no longer costs the other two. A failed row prints the reason it recorded. Batch Emacs discards the *Messages* buffer the interactive path logs to, so without it the journal shows only "error", with no way to tell a cold agent from a revoked token. --- systemd/calendar-sync.service | 17 +++++++++++++++++ systemd/calendar-sync.timer | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 systemd/calendar-sync.service create mode 100644 systemd/calendar-sync.timer (limited to 'systemd') diff --git a/systemd/calendar-sync.service b/systemd/calendar-sync.service new file mode 100644 index 00000000..0d70de57 --- /dev/null +++ b/systemd/calendar-sync.service @@ -0,0 +1,17 @@ +[Unit] +Description=Sync calendars from their .ics feeds into org +# Deliberately no dependency on emacs.service or graphical-session.target. +# The point of moving this out of the editor is that calendars stay current +# while Emacs is down, so the writer is a batch Emacs that needs neither the +# daemon nor a compositor. +Documentation=file:%h/.emacs.d/modules/calendar-sync.el + +[Service] +Type=oneshot +ExecStart=%h/.emacs.d/scripts/calendar-sync-run +# The script waits for every calendar to settle, bounded by +# calendar-sync-batch-timeout (300s). Cap the unit above that so the script's +# own timeout reports a named failure per calendar, rather than systemd +# killing it first and leaving only "timeout". +TimeoutStartSec=360 +Nice=10 diff --git a/systemd/calendar-sync.timer b/systemd/calendar-sync.timer new file mode 100644 index 00000000..71278f20 --- /dev/null +++ b/systemd/calendar-sync.timer @@ -0,0 +1,18 @@ +[Unit] +Description=Sync calendars into org every 10 minutes + +[Timer] +# OnCalendar rather than OnUnitActiveSec, because Persistent= only has an +# effect on calendar timers (systemd.timer(5)). A monotonic schedule silently +# ignores it, so a machine that slept through several intervals would come +# back to stale calendar files with nothing to trigger a catch-up. +OnCalendar=*:0/10 +Persistent=true +# A cold boot should not wait for the next wall-clock slot. This is the case +# the whole change exists for: before the timer, a reboot meant no calendar +# sync at all until the agenda happened to be opened. +OnBootSec=2min +AccuracySec=30s + +[Install] +WantedBy=timers.target -- cgit v1.2.3