ci(server-image): publish to GHCR only by manual dispatch from master

Publishing a GitHub Release, stable or pre-release, now only validates the
server image. A desktop release and a server image release are separate
owner decisions, so a stable desktop release cannot publish an image that
has not been accepted yet.
This commit is contained in:
aj committed 2026-10-27 10:18:44 -04:00
1 parent 242a0d2a1b
commit 5b2fbb8a7b
3 files changed
+11 -14

No files matched your search

+1 -2
View File
@@ -114,8 +114,7 @@ jobs:
publish:
if: >-
github.repository == 'ajisaacs/OpenNest' &&
((github.event_name == 'release' && github.event.release.prerelease == false) ||
(github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master'))
github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
timeout-minutes: 40
permissions:
+7 -10
View File
@@ -72,16 +72,13 @@ or attestations (`--provenance=false --sbom=false`), and runs the real-client
container persistence/backup/restore smoke. A Windows `v*` tag build alone never
publishes a server image.
Publication requires owner-approved release intent: a **published, non-prerelease GitHub Release**
or an explicit `Server image` dispatch **from `master`**, naming an existing strict
`vX.Y.Z` tag (no prerelease suffix, leading zero, or extra suffix). GitHub pre-releases
publish only the Windows assets; they skip the server image publish and verification
jobs. Changing a pre-release to a full release later is a separate publication
decision. The tag must resolve to
a full commit already on `master`. For a published Release, that peeled commit
must also equal the event's full `GITHUB_SHA`; a retargeted tag is refused. Manual
dispatch intentionally resolves the approved existing tag, not the workflow's
`master` SHA. The job checks out that exact source, reruns all
Publication requires an explicit, owner-approved `Server image` dispatch **from
`master`**, naming an existing strict `vX.Y.Z` tag (no prerelease suffix, leading
zero, or extra suffix). Publishing a GitHub Release, stable or pre-release, only
validates the image; it never publishes it, so a desktop release and a server image
release are separate decisions. The tag must resolve to a full commit already on
`master`. The dispatch intentionally resolves the approved existing tag, not the
workflow's `master` SHA. The job checks out that exact source, reruns all
server/image gates, and pushes the *same smoked image* to
`ghcr.io/ajisaacs/opennest-server:X.Y.Z` and `:sha-<full-commit>` only. Both tags must
be absent; retries after even a partial upload stop instead of overwriting. There
+3 -2
View File
@@ -698,11 +698,12 @@ class ImmutableTagCollisions(unittest.TestCase):
class WorkflowBehavior(unittest.TestCase):
def test_prerelease_skips_ghcr_publish_but_stable_release_and_manual_dispatch_remain(self):
def test_only_a_manual_dispatch_from_master_publishes_to_ghcr(self):
workflow = pathlib.Path(__file__).resolve().parents[1] / ".github/workflows/server-image.yml"
publish = workflow.read_text().split("\n publish:\n", 1)[1].split("\n verify-published:\n", 1)[0]
condition = publish.split(" if: >-\n", 1)[1].split(" runs-on:", 1)[0]
self.assertIn("github.event_name == 'release' && github.event.release.prerelease == false", condition)
# Publishing a GitHub Release, stable or pre-release, never publishes the image.
self.assertNotIn("'release'", condition)
self.assertIn("github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master'", condition)
def test_build_log_pipeline_preserves_docker_failure(self):