diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 7c7cc22923..ac4795212b 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -82,3 +82,25 @@ jobs: env: DEPLOY_BRANCH: ${{ gitea.ref_name }} run: bash scripts/ci-deploy.sh + + # Publish only after checks and the production deployment have succeeded. + # Serial execution also avoids two builds competing on the self-hosted runner. + publish-container: + needs: deploy + if: gitea.event_name == 'push' && (gitea.ref_name == 'main' || gitea.ref_name == 'master') + runs-on: self-hosted + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + repository: ${{ gitea.repository }} + token: ${{ gitea.token }} + + - name: Build, verify portability and publish + shell: bash + env: + REGISTRY_SERVER: ${{ gitea.server_url }} + REGISTRY_REPOSITORY: ${{ gitea.repository }} + REGISTRY_USER: ${{ secrets.CONTAINER_REGISTRY_USER }} + REGISTRY_TOKEN: ${{ secrets.CONTAINER_REGISTRY_TOKEN }} + run: bash scripts/publish-container.sh diff --git a/README.md b/README.md index 05c86bf821..7f987750cd 100644 --- a/README.md +++ b/README.md @@ -222,10 +222,14 @@ To publish from Gitea: 1. In the repository's Actions secrets, configure `CONTAINER_REGISTRY_USER` and `CONTAINER_REGISTRY_TOKEN`. Use a Gitea access token with package read/write permission belonging to an account allowed to publish under the repository owner. -2. Run **Publish portable container** manually on the commit/branch to distribute. - The workflow runs checks, builds from committed source only, and verifies the same - application image with two runtime avatar/badge configurations before pushing. - It does not deploy to production or move a `latest` tag. +2. Every push to `main` or `master` automatically builds and publishes the images + after the CI checks and production deployment succeed. Pull requests do not + publish images. The publication job builds from committed source only and checks + the same application image with two runtime configurations before pushing. + Missing registry secrets fail the publication job explicitly; they do not undo + an already successful production deployment. No `latest` tag is moved. + **Publish portable container** remains available for manual retries on the + commit/branch to distribute, without redeploying production. 3. The images are `//:` and `:-migrations`. Only the application image runs the website; the migrations image is used temporarily for the matching database migrations. diff --git a/src/lib/deploy-workflow-contract.test.ts b/src/lib/deploy-workflow-contract.test.ts index 343a733fc1..4de0cb749f 100644 --- a/src/lib/deploy-workflow-contract.test.ts +++ b/src/lib/deploy-workflow-contract.test.ts @@ -39,3 +39,17 @@ it("builds the checked out source without fetching a moving remote branch", () = ); expect(dockerfile).not.toMatch(/^RUN\s+git\s+(?:pull|fetch|clone)\b/m); }); + +it("publishes commit images after successful main deployment and preserves manual retries", () => { + const publish = workflow.slice(workflow.indexOf("\n publish-container:")); + expect(publish).toContain("needs: deploy"); + expect(publish).toContain("gitea.event_name == 'push'"); + expect(publish).toContain("gitea.ref_name == 'main'"); + expect(publish).toContain("gitea.ref_name == 'master'"); + expect(publish).toContain("bash scripts/publish-container.sh"); + expect(publish).toContain("secrets.CONTAINER_REGISTRY_USER"); + expect(publish).toContain("secrets.CONTAINER_REGISTRY_TOKEN"); + const manual = readFileSync(".gitea/workflows/container.yaml", "utf8"); + expect(manual).toContain("workflow_dispatch:"); + expect(manual).not.toMatch(/^ {2}push:/m); +});