
A serverless weekly pipeline that detects each Sunday's sermon livestream, finds where the sermon starts and ends inside the full service, trims it losslessly, and publishes the clip to YouTube, Google Drive, and an email list. No VPS app, no database, no always-on process. State is per-sermon JSON committed to the repo; git history is the audit log. Every irreversible step waits behind a one-comment human gate.
Data flow
Read left to right. The week runs in three phases, split by the two approval gates (/approve, /publish) where a reviewer signs off before anything irreversible happens. Inside each phase the Runner on the church Mac is the hub, calling out to the boxed services beside it; every numbered arrow is one flow of data, keyed to the legend below. The Runner lives on a residential IP because YouTube blocks video downloads from datacenter addresses. Follow the numbers 1 to 15 in order.
Reference
Live
A heartbeat for each moving part, read from status.json. Grey means the check has not run recently.
State
There is no database. Each sermon is one JSON record in the repo, and git history is the audit log. A job checks the record's status at the top and refuses work not in its expected predecessor state, so re-running tomorrow is always safe.
Rationale
There is no VPS app and no database. State is per-sermon JSON committed to the repo; the multi-GB video never crosses a job boundary. Git history is the audit trail, and a repo clone is the backup. The old FastAPI + SQLite service is retired one slice at a time.
YouTube bot-walls video downloads from datacenter IPs, which takes out both GitHub-hosted runners and the church VPS. The fix is a self-hosted runner on a Mac at the church, on a residential IP. Only the light, API-only jobs stay on cloud runners.
Detection runs with read-only keys. The credentials that can upload, go public, or send email live in a protected production Environment, branch-restricted to main and readable only by approve and publish. A runaway scan cannot touch the channel or the mailing list.
Before every irreversible call, the job checks a per-effect receipt and then probes the destination itself (a marker in the YouTube description, a Drive name lookup, the email flag). A torn or stale commit cannot cause a duplicate upload or a double-send. Re-running tomorrow is always safe.
Free YouTube captions are the primary transcript, so a normal week finishes in minutes. Whisper is off by default and runs only on request, locally on the Mac, so a no-caption week degrades gracefully instead of blowing the time budget.
Nothing goes public without a person. The upload is unlisted first; an owner-only /approve then /publish stands between detection and the congregation. Auto-pilot, off by default, can only ever produce the unlisted preview.
The Telegram bot is a webhook-free dispatcher: it long-polls for updates, so there is no inbound port and nothing to host. An owner message is validated (chat id, then a regex on the video id and timestamps) and turned into a gh workflow run via an argument list, never a shell string, so a chat message cannot inject a command. The same review loop runs from the phone or from a GitHub Issue comment.
YouTube's upload quota is small. When it is exhausted mid-run the record parks at processed_pending_upload instead of erroring; a daily job drains those back into an upload as quota returns. The status gate plus per-effect receipts make every step re-runnable with no duplicate upload or double email.