Problem
Scheduled host-side jobs around a stack currently live outside strut as hand-installed systemd timers or cron entries. From the project-horizon infra repo:
stacks/backup/nightly-plane.sh, nightly-sites.sh — nightly DB/site backups, crontab'd manually
stacks/media/systemd/media-port-sync.{service,timer} — 60s Gluetun NAT-PMP → qBittorrent port sync
stacks/immich/immich-db-backup-sync.{service,timer} — backup off-array sync
Each one: hand-written unit pair, manual install → daemon-reload → enable --now runbook, no drift detection, invisible to strut status.
Proposal
A declarative timers: (or schedule:) block in stack config (services.conf or a timers.conf):
# stacks/media/timers.conf
[port-sync]
exec = ./port-sync.sh
on_calendar = *:*:0/60 # or interval = 60s
env_file = /etc/default/media-port-sync
[nightly-backup]
exec = strut media backup
on_calendar = daily 03:00
strut renders these to systemd .service/.timer pairs and installs them during deploy (reusing the proposed hook-helper strut::install_timer semantics: idempotent, only-if-changed, recorded for uninstall). strut <stack> timers lists them with last/next run (systemctl list-timers parse); drift check flags hand-edited units.
Why
- Kills the biggest remaining class of "SSH in and install this timer" runbooks.
- Backup timers can route through
strut backup, so pre_backup/post_backup hooks fire on scheduled runs too — today the out-of-band cron scripts silently bypass the whole hook system.
Composes with: hook helper stdlib (rendering/installing), teardown hooks (removal), host-scoped provisioning (host-level vs stack-level timers).
Problem
Scheduled host-side jobs around a stack currently live outside strut as hand-installed systemd timers or cron entries. From the
project-horizoninfra repo:stacks/backup/nightly-plane.sh,nightly-sites.sh— nightly DB/site backups, crontab'd manuallystacks/media/systemd/media-port-sync.{service,timer}— 60s Gluetun NAT-PMP → qBittorrent port syncstacks/immich/immich-db-backup-sync.{service,timer}— backup off-array syncEach one: hand-written unit pair, manual
install → daemon-reload → enable --nowrunbook, no drift detection, invisible tostrut status.Proposal
A declarative
timers:(orschedule:) block in stack config (services.confor atimers.conf):strut renders these to systemd
.service/.timerpairs and installs them during deploy (reusing the proposed hook-helperstrut::install_timersemantics: idempotent, only-if-changed, recorded for uninstall).strut <stack> timerslists them with last/next run (systemctl list-timersparse); drift check flags hand-edited units.Why
strut backup, sopre_backup/post_backuphooks fire on scheduled runs too — today the out-of-band cron scripts silently bypass the whole hook system.Composes with: hook helper stdlib (rendering/installing), teardown hooks (removal), host-scoped provisioning (host-level vs stack-level timers).