diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..7b910f9b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,25 @@ +# Dependabot configuration. +# +# Goals: +# - Stay safe: SHA-pinned third-party Actions get bumped when they +# release new versions, so we don't fall behind for years. +# - Stay quiet: routine bumps land in ONE grouped PR per month, not +# one PR per action per release. (Past experience: ungrouped +# SHA-pinning floods the inbox; the grouping config below avoids it.) +# - Stay reactive on CVEs: security-advisory PRs ALWAYS fire +# immediately regardless of the schedule below. The `interval` only +# affects routine version updates, not the security-update path. +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + groups: + gha-deps: + patterns: + - "*" + # Use a single label so monthly grouped PRs are easy to filter. + labels: + - "dependencies" + - "github-actions" diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 00000000..3cd964bd --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,128 @@ +name: Publish Docker Image + +# Triggers: +# +# - release: published +# Full tag set (:VERSION, :major.minor, :major, :latest) - the +# canonical "ship a new version" path. Same trigger as +# python-publish.yml, so one release ships PyPI + Docker together. +# +# - workflow_dispatch +# Manual trigger. Behavior depends on what ref it's invoked with +# (`gh workflow run ... --ref `): +# +# - With `--ref ` (e.g. `--ref v2.0.0b2`): +# Publishes :VERSION only (no floating aliases). Useful for +# retroactive single-version rebuilds that must NOT move +# :latest / :major / :major.minor. The version tag fires +# because metadata-action's pep440 type matches the tag ref. +# +# - With `--ref master` (default): +# No tag ref to match - publishes :manual- only. +# Useful for smoke-testing the workflow itself. +on: + release: + types: [published] + workflow_dispatch: + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write # required to push to ghcr.io + steps: + - name: Check out source + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + # ref omitted: uses GITHUB_REF, which for both `release` events + # and `workflow_dispatch` is the ref that triggered the workflow. + + # Multi-arch builds need QEMU for cross-compilation under buildx. + - name: Set up QEMU + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 + + # Buildx is the modern docker builder; supports multi-platform output. + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + + # Docker Hub login. Token must be a Hub access token (NOT a password) + # scoped to the proxybroker2 repository for least-privilege. + - name: Log in to Docker Hub + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + # GHCR login uses the workflow's built-in GITHUB_TOKEN (no extra secret + # needed). The `packages: write` permission above is what authorises it. + - name: Log in to GitHub Container Registry + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Tag strategy: + # + # `:VERSION` (e.g. `:2.0.0b2`) - exact version pin. + # Uses type=pep440 (NOT type=semver) because this project + # versions in PEP 440 form (`2.0.0b2`), not SemVer + # (`2.0.0-beta.2`). PyPI mandates PEP 440 - we follow. + # Fires on any tag-shaped GITHUB_REF: release events AND + # workflow_dispatch invoked with `--ref v...`. + # + # `:major.minor` / `:major` / `:latest` - floating aliases. + # Gated on `release` events only via explicit enable=. + # Reasons: + # - Manual workflow_dispatch rebuilds shouldn't accidentally + # move floating tags. + # - Type=match is used instead of {{major}} / {{major.minor}} + # templates because metadata-action's pep440/semver parsers + # deliberately hold floating aliases back for prereleases. + # We want them to advance for prereleases too: this project + # has been in beta for a year, and locking floating tags to + # stable releases would freeze them on `v2.0.0b1` (May 2025) + # until 2.0.0 ships. + # + # `:manual-` - per-build tag for workflow_dispatch. + # Avoids collision with release-published tags. + # + # `flavor: latest=false` disables metadata-action's automatic + # `:latest` setting for the highest stable version. We control + # `:latest` explicitly via the type=raw rule above so it ONLY + # fires on release events, not on manual workflow_dispatch runs + # of stable tags. + # + # Acknowledged side-effect: if a future patch is released for an + # OLDER version line (e.g. 1.5.1 after 2.0.0), the floating tags + # would advance backward. Not maintaining old version lines today; + # restore `flavor: latest=auto` if that ever changes. + - name: Extract image metadata (tags, labels) + id: meta + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 + with: + images: | + bluet/proxybroker2 + ghcr.io/bluet/proxybroker2 + flavor: | + latest=false + tags: | + type=pep440,pattern={{version}} + type=match,pattern=v(\d+\.\d+),group=1,enable=${{ github.event_name == 'release' }} + type=match,pattern=v(\d+),group=1,enable=${{ github.event_name == 'release' }} + type=raw,value=latest,enable=${{ github.event_name == 'release' }} + type=raw,value=manual-{{date 'YYYYMMDDHHmmss'}},enable=${{ github.event_name == 'workflow_dispatch' }} + + # Build once, push to both registries. cache-from/to use the GitHub + # Actions cache backend so a second run on the same code reuses + # layers and finishes in seconds instead of minutes. + - name: Build and push + uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max