name: Build and Release Module "on": workflow_dispatch: inputs: distro_targets: description: "Space-separated distro images to build in, for example: ubuntu:24.04 debian:12 debian:13" required: true default: "ubuntu:24.04 debian:12 debian:13" upstream_nginx_versions: description: "Space-separated upstream nginx.org versions for generic tarball builds" required: true default: "1.22.1 1.24.0 1.26.3 1.28.0 1.30.0" target_arches: description: "Space-separated targets: linux-x86_64 linux-arm64 linux-armv7" required: true default: "linux-x86_64 linux-arm64 linux-armv7" version_bump: description: "Semantic version part to increment from the latest v* tag" required: true type: choice default: patch options: - patch - minor - major push_image: description: "Push multi-platform demo Docker image to GHCR using the new tag" type: boolean required: true default: true permissions: contents: write packages: write env: DEFAULT_DISTRO_TARGETS: "ubuntu:24.04 debian:12 debian:13" DEFAULT_UPSTREAM_NGINX_VERSIONS: "1.22.1 1.24.0 1.26.3 1.28.0 1.30.0" DEFAULT_TARGET_ARCHES: "linux-x86_64 linux-arm64 linux-armv7" MODULE_NAME: ngx_http_monitoring_module DEB_PACKAGE_NAME: libnginx-mod-http-monitoring RELEASE_BUILD_IMAGE: ubuntu:18.04 RELEASE_LIBC_BASELINE: ubuntu-bionic-glibc-2.27 NGINX_CONFIGURE_ARGS: >- --with-compat --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_v2_module --with-http_gzip_static_module jobs: release: name: Build distro modules and publish release runs-on: ubuntu-22.04 timeout-minutes: 240 steps: - name: Check out repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Compute next release tag id: version shell: bash env: INPUT_VERSION_BUMP: ${{ github.event.inputs.version_bump }} run: | set -euo pipefail git fetch --tags --force latest_tag="$( git tag -l 'v*' \ | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \ | sort -V \ | tail -n 1 \ || true )" if [[ -z "$latest_tag" ]]; then latest_tag="v0.0.0" fi version="${latest_tag#v}" IFS='.' read -r major minor patch <<< "$version" case "$INPUT_VERSION_BUMP" in major) major=$((major + 1)) minor=0 patch=0 ;; minor) minor=$((minor + 1)) patch=0 ;; patch) patch=$((patch + 1)) ;; *) echo "Unsupported version_bump: $INPUT_VERSION_BUMP" >&2 exit 1 ;; esac next_tag="v${major}.${minor}.${patch}" while git rev-parse -q --verify "refs/tags/${next_tag}" >/dev/null; do patch=$((patch + 1)) next_tag="v${major}.${minor}.${patch}" done { printf 'NEXT_TAG=%s\n' "$next_tag" printf 'NEXT_VERSION=%s\n' "${next_tag#v}" printf 'LATEST_TAG=%s\n' "$latest_tag" } >> "$GITHUB_ENV" { printf 'next_tag=%s\n' "$next_tag" printf 'next_version=%s\n' "${next_tag#v}" printf 'latest_tag=%s\n' "$latest_tag" } >> "$GITHUB_OUTPUT" echo "Latest tag: $latest_tag" echo "Next tag: $next_tag" - name: Normalize target architectures id: targets shell: bash env: INPUT_TARGET_ARCHES: ${{ github.event.inputs.target_arches }} run: | set -euo pipefail raw_targets="${INPUT_TARGET_ARCHES:-$DEFAULT_TARGET_ARCHES}" normalized_targets="" docker_platforms="" add_target() { local target_arch="$1" local docker_platform="$2" case " $normalized_targets " in *" $target_arch "*) return ;; esac normalized_targets="${normalized_targets}${normalized_targets:+ }${target_arch}" docker_platforms="${docker_platforms}${docker_platforms:+,}${docker_platform}" } for raw_target in $raw_targets; do case "$raw_target" in linux-x86_64|x86_64|amd64|linux-amd64) add_target "linux-x86_64" "linux/amd64" ;; linux-arm64|linux-aarch64|arm64|aarch64) add_target "linux-arm64" "linux/arm64" ;; linux-armv7|linux-armhf|armv7|armhf) add_target "linux-armv7" "linux/arm/v7" ;; *) echo "Unsupported target_arches entry: $raw_target" >&2 echo "Supported entries: linux-x86_64 linux-arm64 linux-armv7" >&2 exit 1 ;; esac done if [[ -z "$normalized_targets" ]]; then echo "No release target architectures were selected" >&2 exit 1 fi { printf 'target_arches=%s\n' "$normalized_targets" printf 'docker_platforms=%s\n' "$docker_platforms" } >> "$GITHUB_OUTPUT" echo "Release targets: $normalized_targets" echo "Docker platforms: $docker_platforms" - name: Set up QEMU for ARM builds uses: docker/setup-qemu-action@v3 with: platforms: arm64,arm - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build distro release packages shell: bash env: INPUT_DISTRO_TARGETS: ${{ github.event.inputs.distro_targets }} NORMALIZED_TARGET_ARCHES: ${{ steps.targets.outputs.target_arches }} run: | set -euo pipefail distro_targets="${INPUT_DISTRO_TARGETS:-$DEFAULT_DISTRO_TARGETS}" mkdir -p "$GITHUB_WORKSPACE/dist" "$GITHUB_WORKSPACE/.release-work" if [[ -z "$distro_targets" ]]; then echo "No distro_targets were selected" >&2 exit 1 fi for distro_image in $distro_targets; do for target_arch in $NORMALIZED_TARGET_ARCHES; do case "$target_arch" in linux-x86_64) docker_platform="linux/amd64" ;; linux-arm64) docker_platform="linux/arm64" ;; linux-armv7) docker_platform="linux/arm/v7" ;; *) echo "Unexpected normalized target: $target_arch" >&2 exit 1 ;; esac echo "::group::Build ${distro_image} ${target_arch}" docker run --rm -i \ --platform "$docker_platform" \ -e DEB_PACKAGE_NAME \ -e DISTRO_IMAGE="$distro_image" \ -e DOCKER_PLATFORM="$docker_platform" \ -e GITHUB_REF \ -e GITHUB_SHA \ -e LATEST_TAG \ -e MODULE_NAME \ -e NEXT_TAG \ -e NEXT_VERSION \ -e TARGET_ARCH="$target_arch" \ -v "$GITHUB_WORKSPACE:/work" \ -w /work \ "$distro_image" \ bash /work/scripts/build-distro-release.sh echo "::endgroup::" done done - name: Build upstream nginx.org release tarballs shell: bash env: INPUT_UPSTREAM_NGINX_VERSIONS: ${{ github.event.inputs.upstream_nginx_versions }} NORMALIZED_TARGET_ARCHES: ${{ steps.targets.outputs.target_arches }} run: | set -euo pipefail upstream_versions="${INPUT_UPSTREAM_NGINX_VERSIONS:-$DEFAULT_UPSTREAM_NGINX_VERSIONS}" if [[ -z "$upstream_versions" ]]; then echo "No upstream_nginx_versions were selected" >&2 exit 1 fi for target_arch in $NORMALIZED_TARGET_ARCHES; do case "$target_arch" in linux-x86_64) docker_platform="linux/amd64" ;; linux-arm64) docker_platform="linux/arm64" ;; linux-armv7) docker_platform="linux/arm/v7" ;; *) echo "Unexpected normalized target: $target_arch" >&2 exit 1 ;; esac echo "::group::Build nginx.org ${target_arch} tarballs" docker run --rm -i \ --platform "$docker_platform" \ -e BUILD_NGINX_VERSIONS="$upstream_versions" \ -e DIST_DIR=/work/dist \ -e DOCKER_PLATFORM="$docker_platform" \ -e GITHUB_REF \ -e GITHUB_SHA \ -e LATEST_TAG \ -e MODULE_NAME \ -e NEXT_TAG \ -e NEXT_VERSION \ -e NGINX_CONFIGURE_ARGS \ -e RELEASE_BUILD_IMAGE \ -e RELEASE_LIBC_BASELINE \ -e TARGET_ARCH="$target_arch" \ -v "$GITHUB_WORKSPACE:/work" \ -w /work \ "$RELEASE_BUILD_IMAGE" \ bash /work/scripts/build-upstream-release.sh echo "::endgroup::" done - name: Generate release manifests and notes shell: bash run: | set -euo pipefail cd "$GITHUB_WORKSPACE/dist" shopt -s nullglob compatibility_files=( *.compatibility.json ) if [[ "${#compatibility_files[@]}" -eq 0 ]]; then echo "No compatibility metadata files were generated" >&2 exit 1 fi jq -s \ --arg releaseTag "$NEXT_TAG" \ --arg generatedAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ '{ schema: 1, release_tag: $releaseTag, generated_at: $generatedAt, artifacts: . }' \ "${compatibility_files[@]}" > COMPATIBILITY-MATRIX.json find . -maxdepth 1 -type f \ \( -name '*.deb' -o -name '*.tar.gz' -o -name '*.compatibility.json' -o -name 'COMPATIBILITY-MATRIX.json' \) \ -print0 \ | sort -z \ | xargs -0 sha256sum > SHA256SUMS.txt { printf '# %s %s\n\n' "$MODULE_NAME" "$NEXT_TAG" printf 'Previous tag: `%s`\n\n' "$LATEST_TAG" printf 'This release includes two artifact tiers:\n\n' printf -- '- distro `.deb` packages built from Ubuntu/Debian Nginx source packages and pinned to the matching packaged Nginx revision\n' printf -- '- nginx.org upstream-version tarballs for users who build or run Nginx from upstream source versions\n\n' printf 'Recommended production artifacts are the `.deb` packages. They depend on the exact distro `nginx-abi-*` virtual package when the distribution exposes one and on the exact Nginx binary package version used for the build. That strict pin prevents Nginx package revisions from silently reusing an unrebuilt module.\n\n' printf 'The nginx.org `.tar.gz` packages preserve upstream-version coverage but are not a safe substitute for distro packages on Ubuntu/Debian packaged Nginx.\n\n' printf '## Distro Package Matrix\n\n' jq -r ' .artifacts[] | select(.build.method == "distro-source-package") | "- `\(.artifacts.deb_file)`: \(.target.os_id) \(.target.os_version) \(.target.architecture), " + "\(.nginx.binary_package) \(.nginx.binary_version), source \(.nginx.source_package) \(.nginx.source_version), " + "ABI `\(if .nginx.abi == "" then "package-version-pinned" else .nginx.abi end)`" ' COMPATIBILITY-MATRIX.json printf '\n## nginx.org Tarballs\n\n' jq -r ' .artifacts[] | select(.build.method == "upstream-nginxorg-source") | "- `\(.artifacts.tarball_file)`: nginx.org \(.nginx.source_version), \(.target.architecture), baseline `\(.target.libc_baseline)`" ' COMPATIBILITY-MATRIX.json printf '\n## Validation\n\n' printf 'Distro `.deb` artifacts are installed into their target distro containers and validated with packaged `nginx -t` plus live endpoint checks. nginx.org tarballs are validated with the Nginx binary built from the same upstream source during the tarball build.\n' } > RELEASE_NOTES.md - name: Upload build artifacts uses: actions/upload-artifact@v4 with: name: ngx-http-monitoring-module-${{ steps.version.outputs.next_tag }}-release-assets path: | dist/*.deb dist/*.tar.gz dist/*.compatibility.json dist/COMPATIBILITY-MATRIX.json dist/SHA256SUMS.txt dist/RELEASE_NOTES.md if-no-files-found: error - name: Create incremented tag shell: bash run: | set -euo pipefail git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git tag -a "$NEXT_TAG" -m "Release $NEXT_TAG" git push origin "$NEXT_TAG" - name: Publish GitHub release env: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail gh release create "$NEXT_TAG" \ dist/*.deb \ dist/*.tar.gz \ dist/*.compatibility.json \ dist/COMPATIBILITY-MATRIX.json \ dist/SHA256SUMS.txt \ --title "$NEXT_TAG" \ --notes-file dist/RELEASE_NOTES.md - name: Compute Docker image name if: github.event.inputs.push_image == 'true' id: image shell: bash run: | set -euo pipefail image_repo="$(echo "ghcr.io/${GITHUB_REPOSITORY}" | tr '[:upper:]' '[:lower:]')" printf 'repo=%s\n' "$image_repo" >> "$GITHUB_OUTPUT" - name: Log in to GHCR if: github.event.inputs.push_image == 'true' uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ github.token }} - name: Build and push demo Docker image if: github.event.inputs.push_image == 'true' uses: docker/build-push-action@v6 with: context: . file: dockerized/Dockerfile platforms: ${{ steps.targets.outputs.docker_platforms }} push: true build-args: | BASE_IMAGE=debian:bookworm-slim OPENSSL_RUNTIME_PACKAGE=libssl3 tags: | ${{ steps.image.outputs.repo }}:${{ steps.version.outputs.next_tag }} ${{ steps.image.outputs.repo }}:latest