Repository navigation
feat: auto-sync add-on pins to upstream FTW releases and publish beta - #4
Conversation
Add end-to-end automation so a new upstream FTW Core/Optimizer release flows into a packaged beta add-on without hand-editing pins: - scripts/upstream_sync.py selects the highest published upstream release, resolves the multi-arch digest and source commit from the registry, verifies the version/revision image labels the release gate re-checks, and rewrites compatibility.yaml, ftw/config.yaml, ftw/CHANGELOG.md and a fresh blocked pilot record. It only moves forward and retries later when an image is not fully published (ImageNotReady). - .github/workflows/sync-upstream.yml runs hourly, on repository_dispatch (ftw-release), or manually; it validates + tests, opens a PR, runs check.yml on the branch, merges when checks pass, then nudges publication. - .github/workflows/auto-publish-beta.yml dispatches the existing signed release-beta.yml only when config.yaml carries a pending beta marker, guarding against existing tags/releases and active runs. The automation holds no packages:write and never builds, pushes, or retags images, so the fail-closed gates (human-reviewed main, cosign signing, SBOMs, pilot qualification) remain the only path to a published release. Generalizes version-coupled tests to read the pinned version, and adds unit + workflow-structure coverage for the new automation. Co-authored-by: HuggeK <48095810+HuggeK@users.noreply.github.com>
miravoss26
left a comment
There was a problem hiding this comment.
Solid automation PR. Two scheduled workflows (sync-upstream, auto-publish-beta) plus upstream_sync.py and a real test suite; all five checks green.
Security screen:
- Permissions scoped top-level contents: read, elevated per-job only where needed.
- auto-publish-beta: the VERSION that flows into the shell is regex-validated (x.y.z-beta.N) and the dispatch step is gated on it, so config.yaml is not an injection surface. checkout uses persist-credentials: false.
- sync-upstream: forward-only, resolves image digests from ghcr, and gates the pin PR + auto-merge on validate.py + the unit tests + check.yml.
One thing worth a conscious yes from a human (not a bug): sync-upstream self-merges its own pin PRs to main when checks pass (gh pr merge --squash --auto, contents: write). That is the right shape for a downstream mirror, but it means main can advance without a human in the loop, so it is your call to accept.
Reads safe to me; the self-merge is the only human-decision point. Not on my auto-merge allowlist regardless, so a human takes the merge.
miravoss26
left a comment
There was a problem hiding this comment.
Closes the manual-pin loop: two new workflows (sync-upstream, auto-publish-beta) + scripts/upstream_sync.py + tests auto-pin the add-on to the latest upstream FTW release and publish it on beta.
Security screen, clean:
- Top-level
permissions: contents: read, per-job elevation only where needed. persist-credentials: falseon checkout;GITHUB_TOKENonly, no PATs.- version / title / branch passed through
env:and referenced as${VAR}, not inline${{ }}in run blocks, so no shell-injection surface; heredocs are quoted. upstream_sync.pyuses list-form subprocess (noshell=True) and strict regexes for versions/digests/commits; forward-only, skips not-yet-published images.
One human decision, not a bug: the sync job opens a PR and then auto-merges it (gh pr merge --auto, fallback watch+merge), gated only by check.yml + branch protection, so the add-on self-updates with no human in the merge loop, and the job holds contents: write + pull-requests: write. If that trust model is intended (the description says it is), this is safe to merge from my read. Worth confirming branch protection on main actually requires check.yml to pass, since that gate is now load-bearing.
|
@frahlg Tänkte se om jag kunde testa detta idag om vi ska cutta en ny release i beta eller release? Skulle du kunna kika på detta? |
|
Likaså #5 |
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
|
Final security and release pass on head 525dc03:
The automation will stay idle until the upstream release carries real pilot evidence. With that fail-closed behavior, this is safe to squash-merge. |
|
Merge is intentionally paused for one owner decision. This PR installs standing workflows with contents: write, pull-requests: write, and actions: write; after the hardening they can still self-merge checked pin PRs and dispatch the signed beta release workflow. The technical review is complete and green, but enabling that trust model needs explicit owner approval. No merge was made. |
|
Squash-merged as 3113b3c after explicit owner approval. The post-merge Check passed, including lint, repository checks, and amd64/aarch64 build, restart, and persistence smoke tests: https://github.com/srcfl/home-assistant-addons/actions/runs/31206045862 The Sync upstream FTW and Auto publish beta workflows are active. The merge did not trigger a sync or beta release; their pilot-evidence and exact-SHA guards remain in place. |
What this does
Makes the FTW Home Assistant add-on update itself when a new FTW version is
released upstream and get packaged on the beta channel — no hand-editing of
pins. Today every release is manually dispatched and every upstream digest is
pinned by hand in
compatibility.yaml; this closes that loop while keeping theexisting fail-closed release gates as the only path to a published artifact.
The flow
Sync upstream FTW(.github/workflows/sync-upstream.yml) — runshourly, on
repository_dispatch: ftw-release, or manually. It runsscripts/upstream_sync.py, which picks the highest published upstreamCore/Optimizer release, resolves the multi-arch digest and source commit
from the registry, and rewrites
compatibility.yaml,ftw/config.yaml,ftw/CHANGELOG.md, and a fresh blocked pilot record. It then validates,runs the test suite, opens a bot PR, runs
check.ymlon the exact head,checks that
mainhas not moved, merges that head, deletes its branch,and nudges publication.
Auto publish beta(.github/workflows/auto-publish-beta.yml) — on thepush to
main, dispatches the existingrelease-beta.ymlonly whenconfig.yamlstill carries the pending beta marker, guarding againstalready-existing tags/releases and active runs.
Why it's still safe
The automation holds no
packages: writeand never builds, pushes, orretags an image. It only produces a bot PR and waits for the exact branch check before
merging it. It then dispatches the existing signed
release-beta.yml, whichindependently re-verifies the version match, publisher marker, source-on-
main, cosignsignatures, and SBOMs. The fail-closed gates remain the only way to ship.
Design details & guarantees
live-pilot PR or issue comment, or a live-pilot Actions run. Without one,
the sync writes nothing and retries later. A release page is not evidence.
upstream_sync.pynever downgrades and skips upstreamreleases whose multi-arch image is not fully published yet (
ImageNotReady→ retry on the next schedule tick).
org.opencontainers.image.version/.revisionlabels match the release, soit can't pin a mislabeled or half-published image.
and resets
home_assistant_os_supervisortoblocked+promoted_from_betato nulls, so nothing is auto-promoted to stable.already exists.
Auto publish betafires on the merge push and on aschedule fallback, so a missed dispatch still self-heals.
srcfl/ftwcan sendrepository_dispatchtypeftw-releasefor instant syncs; the hourly cron is the fallback if itdoesn't.
Tests
python -m unittest discover -s tests→ 118 passing. Addstests/test_upstream_sync.py(version ordering, release selection, next-versionlogic, file rewrites, and a compose→YAML round-trip asserted against the real
validators) and
tests/test_automation_workflows.py(structure assertions thatthe workflows can't write packages or build/push images). Generalizes the
version-coupled tests in
test_validate.py/test_release_gate.pyso futureautomated bumps don't break the suite.
🤖 Generated with Claude Code