diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 301b892..1131f1e 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -3,11 +3,13 @@ name: Build and Publish NUT Server Docker Images on: push: branches: [ "main", "master" ] - tags: [ 'v*.*.*' ] pull_request: branches: [ "main", "master" ] schedule: - # Run automatically on the 1st of every month at 03:00 UTC + # Monthly rebuild (1st, 03:00 UTC): refreshes Debian base security patches + # and re-detects the packaged NUT version. The build also commits a small + # NUT_VERSION stamp, which doubles as the keepalive that stops GitHub from + # auto-disabling this schedule after 60 days of repo inactivity. - cron: '0 3 1 * *' workflow_dispatch: @@ -19,7 +21,7 @@ jobs: build-and-push: runs-on: ubuntu-latest permissions: - contents: read + contents: write packages: write id-token: write @@ -41,6 +43,20 @@ jobs: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + # The image installs nut-server from Debian bookworm, so its version is + # whatever bookworm currently ships. Resolve the candidate here (package + # versions are identical across arches) and use it to tag and label the + # image, so users can pin/inspect by NUT version instead of build date. + - name: Resolve packaged NUT version + id: nut + run: | + full=$(docker run --rm debian:bookworm-slim sh -c \ + 'apt-get update -qq && apt-cache policy nut-server 2>/dev/null | awk "/Candidate:/{print \$2}"') + if [ -z "$full" ]; then echo "Could not resolve nut-server version" >&2; exit 1; fi + echo "full=$full" >> "$GITHUB_OUTPUT" + echo "upstream=${full%%-*}" >> "$GITHUB_OUTPUT" + echo "Resolved NUT: full=$full upstream=${full%%-*}" + - name: Extract Docker metadata id: meta uses: docker/metadata-action@v5 @@ -48,15 +64,23 @@ jobs: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} type=raw,value=latest,enable={{is_default_branch}} + # NUT version tags: full Debian version (e.g. 2.8.0-7) plus the + # upstream pointer (e.g. 2.8.0) that moves to the latest Debian + # revision. This is the primary way to pin a server version. + type=raw,value=${{ steps.nut.outputs.full }},enable={{is_default_branch}} + type=raw,value=${{ steps.nut.outputs.upstream }},enable={{is_default_branch}} + # Date tag: immutable stamp of a specific monthly build. type=raw,value={{date 'YYYY.MM'}},enable={{is_default_branch}} # Legacy aliases kept in sync with `latest` so users who pinned # these 2021-era tags keep receiving the current multi-arch image. type=raw,value=amd64-latest,enable={{is_default_branch}} type=raw,value=arm64v8-latest,enable={{is_default_branch}} type=raw,value=arm32v7-latest,enable={{is_default_branch}} + labels: | + org.opencontainers.image.version=${{ steps.nut.outputs.full }} + org.networkupstools.version=${{ steps.nut.outputs.full }} + org.networkupstools.upstream=${{ steps.nut.outputs.upstream }} - name: Build and push Docker image id: build-and-push @@ -77,4 +101,30 @@ jobs: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} repository: ${{ env.IMAGE_NAME }} - readme-filepath: ./README.md \ No newline at end of file + readme-filepath: ./README.md + + # Record the built version in-repo and commit it. This gives a + # human-readable version log and, because it runs on the monthly + # schedule, keeps the repo active so GitHub does not auto-disable the + # cron after 60 days of inactivity. [skip ci] avoids a redundant run + # (a GITHUB_TOKEN push does not trigger workflows anyway). + - name: Record built NUT version (keepalive) + if: github.ref == 'refs/heads/master' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') + run: | + cat > NUT_VERSION <