chore: refactor release workflow for modular distro and upstream builds

This commit is contained in:
2026-06-09 21:20:20 +03:30
parent f97ac2f7c5
commit 058d835f07
10 changed files with 1423 additions and 200 deletions

View File

@@ -3,8 +3,12 @@ name: Build and Release Module
"on":
workflow_dispatch:
inputs:
nginx_versions:
description: "Space-separated Nginx versions to build against"
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:
@@ -21,7 +25,7 @@ name: Build and Release Module
- minor
- major
push_image:
description: "Push multi-platform Docker image to GHCR using the new tag"
description: "Push multi-platform demo Docker image to GHCR using the new tag"
type: boolean
required: true
default: true
@@ -31,9 +35,11 @@ permissions:
packages: write
env:
DEFAULT_NGINX_VERSIONS: "1.22.1 1.24.0 1.26.3 1.28.0 1.30.0"
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: >-
@@ -46,9 +52,9 @@ env:
jobs:
release:
name: Build module and publish release
name: Build distro modules and publish release
runs-on: ubuntu-22.04
timeout-minutes: 120
timeout-minutes: 240
steps:
- name: Check out repository
@@ -186,17 +192,75 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build release packages
- name: Build distro release packages
shell: bash
env:
INPUT_NGINX_VERSIONS: ${{ github.event.inputs.nginx_versions }}
INPUT_DISTRO_TARGETS: ${{ github.event.inputs.distro_targets }}
NORMALIZED_TARGET_ARCHES: ${{ steps.targets.outputs.target_arches }}
run: |
set -euo pipefail
versions="${INPUT_NGINX_VERSIONS:-$DEFAULT_NGINX_VERSIONS}"
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)
@@ -214,10 +278,11 @@ jobs:
;;
esac
echo "::group::Build ${target_arch} release packages"
echo "::group::Build nginx.org ${target_arch} tarballs"
docker run --rm -i \
--platform "$docker_platform" \
-e BUILD_NGINX_VERSIONS="$versions" \
-e BUILD_NGINX_VERSIONS="$upstream_versions" \
-e DIST_DIR=/work/dist \
-e DOCKER_PLATFORM="$docker_platform" \
-e GITHUB_REF \
-e GITHUB_SHA \
@@ -232,149 +297,76 @@ jobs:
-v "$GITHUB_WORKSPACE:/work" \
-w /work \
"$RELEASE_BUILD_IMAGE" \
bash -s <<'BUILD_SCRIPT'
bash /work/scripts/build-upstream-release.sh
echo "::endgroup::"
done
- name: Generate release manifests and notes
shell: bash
run: |
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
cd "$GITHUB_WORKSPACE/dist"
apt-get update
apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
curl \
file \
libpcre2-dev \
libssl-dev \
zlib1g-dev
rm -rf /var/lib/apt/lists/*
shopt -s nullglob
compatibility_files=( *.compatibility.json )
if [[ "${#compatibility_files[@]}" -eq 0 ]]; then
echo "No compatibility metadata files were generated" >&2
exit 1
fi
target_workdir="/work/.release-work/${TARGET_ARCH}"
mkdir -p "$target_workdir" /work/dist
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
for nginx_version in $BUILD_NGINX_VERSIONS; do
echo "::group::Nginx ${nginx_version} (${TARGET_ARCH})"
archive="nginx-${nginx_version}.tar.gz"
archive_path="${target_workdir}/${archive}"
workdir="${target_workdir}/nginx-${nginx_version}"
if [[ ! -f "$archive_path" ]]; then
curl -fsSLo "$archive_path" \
"https://nginx.org/download/${archive}"
fi
rm -rf "$workdir"
tar -xzf "$archive_path" -C "$target_workdir"
cd "$workdir"
./configure \
--prefix=/usr/local/nginx \
--sbin-path=/usr/local/sbin/nginx \
--modules-path=/usr/local/nginx/modules \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--http-client-body-temp-path=/var/cache/nginx/client_temp \
--http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \
$NGINX_CONFIGURE_ARGS \
--add-dynamic-module=/work
make -j"$(nproc)" modules
package="${MODULE_NAME}-${NEXT_TAG}-nginx-${nginx_version}-${TARGET_ARCH}"
package_dir="/work/dist/$package"
mkdir -p "$package_dir/modules" "$package_dir/examples" "$package_dir/docs"
cp "objs/${MODULE_NAME}.so" "$package_dir/modules/${MODULE_NAME}.so"
strip --strip-unneeded "$package_dir/modules/${MODULE_NAME}.so" || true
cp /work/examples/nginx.conf "$package_dir/examples/nginx.conf"
cp /work/examples/monitoring-site.conf "$package_dir/examples/monitoring-site.conf"
cp /work/README.md "$package_dir/README.md"
cp /work/docs/API.md "$package_dir/docs/API.md"
cp /work/docs/CONFIGURATION.md "$package_dir/docs/CONFIGURATION.md"
cp /work/docs/PERFORMANCE.md "$package_dir/docs/PERFORMANCE.md"
{
printf 'module=%s\n' "$MODULE_NAME"
printf 'module_version=%s\n' "$NEXT_VERSION"
printf 'release_tag=%s\n' "$NEXT_TAG"
printf 'previous_tag=%s\n' "$LATEST_TAG"
printf 'nginx_version=%s\n' "$nginx_version"
printf 'target=%s\n' "$TARGET_ARCH"
printf 'docker_platform=%s\n' "$DOCKER_PLATFORM"
printf 'build_image=%s\n' "$RELEASE_BUILD_IMAGE"
printf 'libc_baseline=%s\n' "$RELEASE_LIBC_BASELINE"
printf 'build_machine=%s\n' "$(uname -m)"
printf 'runner=ubuntu-22.04\n'
printf 'git_ref=%s\n' "$GITHUB_REF"
printf 'git_sha=%s\n' "$GITHUB_SHA"
printf 'configure_args=%s\n' "$NGINX_CONFIGURE_ARGS"
printf 'built_at=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
} > "$package_dir/METADATA.txt"
{
printf '# %s for Nginx %s\n\n' "$MODULE_NAME" "$nginx_version"
printf 'Release: `%s`\n\n' "$NEXT_TAG"
printf 'Target: `%s` (`%s`)\n\n' "$TARGET_ARCH" "$DOCKER_PLATFORM"
printf 'Libc baseline: `%s`\n\n' "$RELEASE_LIBC_BASELINE"
printf 'This package contains a prebuilt Linux dynamic module:\n\n'
printf '```text\n'
printf 'modules/%s.so\n' "$MODULE_NAME"
printf '```\n\n'
printf 'Copy it into your Nginx modules directory and add this near the top of `nginx.conf`:\n\n'
printf '```nginx\n'
printf 'load_module modules/%s.so;\n' "$MODULE_NAME"
printf '```\n\n'
printf 'This binary is built with `--with-compat` against Nginx %s on Ubuntu Bionic 18.04 for %s. ' "$nginx_version" "$TARGET_ARCH"
printf 'For safest production use, run the same Nginx version, CPU architecture, and a Linux/glibc environment compatible with `%s`. ' "$RELEASE_LIBC_BASELINE"
printf 'If your Nginx package has a materially different module ABI, rebuild from source with this repository.\n'
} > "$package_dir/INSTALL.md"
(
cd "$package_dir"
sha256sum "modules/${MODULE_NAME}.so" > SHA256SUMS
file "modules/${MODULE_NAME}.so" > FILE.txt
)
tar -czf "/work/dist/$package.tar.gz" -C /work/dist "$package"
rm -rf "$package_dir"
echo "::endgroup::"
done
BUILD_SCRIPT
echo "::endgroup::"
done
(
cd "$GITHUB_WORKSPACE/dist"
sha256sum *.tar.gz > SHA256SUMS.txt
)
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 '# ngx_http_monitoring_module %s\n\n' "$NEXT_TAG"
printf '# %s %s\n\n' "$MODULE_NAME" "$NEXT_TAG"
printf 'Previous tag: `%s`\n\n' "$LATEST_TAG"
printf 'Libc baseline: `%s`\n\n' "$RELEASE_LIBC_BASELINE"
printf 'Prebuilt Linux dynamic module packages are attached for targets: `%s`.\n\n' "$NORMALIZED_TARGET_ARCHES"
printf 'Each tarball is built against the Nginx version and target architecture in its filename and includes:\n\n'
printf -- '- `modules/%s.so`\n' "$MODULE_NAME"
printf -- '- `INSTALL.md`\n'
printf -- '- `METADATA.txt`\n'
printf -- '- `SHA256SUMS`\n'
printf -- '- example configuration and API docs\n\n'
printf 'Nginx dynamic modules are ABI-sensitive. Use the package matching your Nginx version and CPU architecture, and rebuild locally if your Nginx distribution uses incompatible build options.\n'
} > "$GITHUB_WORKSPACE/dist/RELEASE_NOTES.md"
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
@@ -393,7 +385,12 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
gh release create "$NEXT_TAG" dist/*.tar.gz dist/SHA256SUMS.txt \
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
@@ -414,7 +411,7 @@ jobs:
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Build and push Docker image
- name: Build and push demo Docker image
if: github.event.inputs.push_image == 'true'
uses: docker/build-push-action@v6
with:
@@ -423,8 +420,8 @@ jobs:
platforms: ${{ steps.targets.outputs.docker_platforms }}
push: true
build-args: |
BASE_IMAGE=ubuntu:18.04
OPENSSL_RUNTIME_PACKAGE=libssl1.1
BASE_IMAGE=debian:bookworm-slim
OPENSSL_RUNTIME_PACKAGE=libssl3
tags: |
${{ steps.image.outputs.repo }}:${{ steps.version.outputs.next_tag }}
${{ steps.image.outputs.repo }}:latest

View File

@@ -265,3 +265,66 @@ jobs:
.ci-runtime/${{ matrix.target.name }}/nginx-${{ matrix.nginx-version }}/logs/*.log
.ci-runtime/${{ matrix.target.name }}/nginx-${{ matrix.nginx-version }}/nginx.conf
if-no-files-found: ignore
distro-package-compat:
name: Distro package compatibility (${{ matrix.distro-image }})
runs-on: ubuntu-22.04
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
distro-image:
- ubuntu:24.04
- debian:12
- debian:13
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Build package and validate against distro nginx
shell: bash
env:
DEB_PACKAGE_NAME: libnginx-mod-http-monitoring
DISTRO_IMAGE: ${{ matrix.distro-image }}
DOCKER_PLATFORM: linux/amd64
LATEST_TAG: none
MODULE_NAME: ngx_http_monitoring_module
NEXT_TAG: ci
NEXT_VERSION: 0.0.0
TARGET_ARCH: linux-x86_64
run: |
set -euo pipefail
mkdir -p "$GITHUB_WORKSPACE/.ci-distro-dist" "$GITHUB_WORKSPACE/.ci-distro-work"
docker run --rm -i \
--platform "$DOCKER_PLATFORM" \
-e DEB_PACKAGE_NAME \
-e DISTRO_IMAGE \
-e DIST_DIR=/work/.ci-distro-dist \
-e DOCKER_PLATFORM \
-e GITHUB_REF \
-e GITHUB_SHA \
-e LATEST_TAG \
-e MODULE_NAME \
-e NEXT_TAG \
-e NEXT_VERSION \
-e TARGET_ARCH \
-e WORK_ROOT=/work/.ci-distro-work \
-v "$GITHUB_WORKSPACE:/work" \
-w /work \
"$DISTRO_IMAGE" \
bash /work/scripts/build-distro-release.sh
- name: Upload distro compatibility artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: distro-package-compat-${{ strategy.job-index }}
path: |
.ci-distro-dist/*.deb
.ci-distro-dist/*.tar.gz
.ci-distro-dist/*.compatibility.json
if-no-files-found: ignore

View File

@@ -59,11 +59,11 @@ A reusable Codex skill for clients and agents is available at `skills/ngx-http-m
## GitHub Releases
The repository includes a manual GitHub Actions workflow at `.github/workflows/release.yml` that computes the next `vMAJOR.MINOR.PATCH` tag, builds Linux x86_64, ARM64, and ARMv7 dynamic module tarballs, pushes the tag, and publishes the release. See `docs/RELEASES.md` for release asset format and compatibility notes.
The repository includes a manual GitHub Actions workflow at `.github/workflows/release.yml` that computes the next `vMAJOR.MINOR.PATCH` tag, builds Ubuntu/Debian distro-specific module packages, keeps generic nginx.org source-version tarballs, validates the artifacts, pushes the tag, and publishes the release. Production installs on Ubuntu/Debian should prefer the generated `.deb` packages because they depend on the matching distro Nginx ABI and exact Nginx binary package revision. See `docs/RELEASES.md` for release asset format and compatibility notes.
## CI Tests
`.github/workflows/test.yml` runs on pushes and pull requests. It builds Nginx and the dynamic module against common Nginx versions on x86_64, ARM64, and ARMv7, runs `nginx -t`, starts the built server, and checks the dashboard, JSON API, health, Prometheus, and SSE routes.
`.github/workflows/test.yml` runs on pushes and pull requests. It builds Nginx and the dynamic module against common upstream Nginx versions as a broad source sanity check, and also builds distro packages against Ubuntu/Debian packaged Nginx on amd64. Both paths run `nginx -t`, start Nginx, and check the dashboard, JSON API, health, Prometheus, and SSE routes.
## Load The Module

View File

@@ -1,83 +1,165 @@
# Release Builds
The GitHub Actions workflow in `.github/workflows/release.yml` builds ready-to-use Linux dynamic module packages for x86_64 and ARM targets. Release binaries are built in Ubuntu Bionic 18.04 containers, giving the module artifacts a `ubuntu-bionic-glibc-2.27` libc baseline.
The release workflow in `.github/workflows/release.yml` builds two artifact
tiers:
## Manual Release Only
- production-oriented `.deb` packages against Ubuntu and Debian Nginx source
packages
- generic `.tar.gz` packages against upstream `nginx.org` source versions
The workflow runs only through GitHub Actions `workflow_dispatch`. It does not run on pushes, pull requests, or tag pushes.
## Why Distro Builds
Open the workflow in GitHub Actions and choose:
Nginx dynamic modules are ABI-sensitive. `--with-compat` relaxes some module
signature checks, but it does not make a module independent from the Nginx
source package, distro patchset, compiler flags, configure arguments, or module
ABI selected by a Debian or Ubuntu package maintainer.
- `nginx_versions`: space-separated versions, for example `1.24.0 1.26.3 1.28.0`
- `target_arches`: space-separated targets, default `linux-x86_64 linux-arm64 linux-armv7`
An upstream `nginx-1.24.0` build can load into an Ubuntu `nginx` package and
still behave incorrectly at runtime after a package rebuild such as:
```text
1.24.0-2ubuntu7.9 -> 1.24.0-2ubuntu7.10
```
The safer production rule is:
```text
build from the distro source package that produced the nginx binary you run
```
## Manual Release
The workflow runs through GitHub Actions `workflow_dispatch`.
Inputs:
- `distro_targets`: space-separated container images, default
`ubuntu:24.04 debian:12 debian:13`
- `upstream_nginx_versions`: space-separated upstream source versions, default
`1.22.1 1.24.0 1.26.3 1.28.0 1.30.0`
- `target_arches`: `linux-x86_64`, `linux-arm64`, and `linux-armv7`
- `version_bump`: `patch`, `minor`, or `major`
- `push_image`: whether to push the multi-platform Docker image to GHCR
- `push_image`: push the demo Docker image to GHCR
The workflow fetches existing `vMAJOR.MINOR.PATCH` tags, computes the next semantic version, creates an annotated tag, pushes it, and publishes the GitHub release.
The workflow fetches existing `vMAJOR.MINOR.PATCH` tags, computes the next tag,
builds all release artifacts, creates an annotated tag, pushes it, and publishes
the GitHub release.
Examples:
## Distro Build Strategy
For every distro and architecture, the workflow:
1. Starts the target distro container for the target CPU platform.
2. Enables `deb-src` repositories.
3. Installs the distro `nginx` package.
4. Downloads the exact matching `apt source nginx` package version.
5. Installs the Nginx source package build dependencies.
6. Replays the packaged `nginx -V` configure arguments, rewriting distro module
source paths into the downloaded source tree when needed.
7. Adds this repository as a dynamic module and builds with the distro Nginx
source package.
8. Builds a `.deb` package and a manual `.tar.gz` package.
9. Installs the `.deb` package in the same container.
10. Validates the installed module with packaged `nginx -t` and live endpoint
checks for `/monitor`, `/monitor/api`, `/monitor/health`,
`/monitor/metrics`, and `/monitor/live`.
## nginx.org Build Strategy
For every requested upstream Nginx version and architecture, the workflow:
1. Starts the baseline `ubuntu:18.04` build container for the target CPU
platform.
2. Downloads the requested `https://nginx.org/download/nginx-<version>.tar.gz`
source archive.
3. Configures Nginx with the repository's compatibility build flags and this
module as a dynamic module.
4. Builds the Nginx binary and module.
5. Validates the module with the just-built Nginx binary and live endpoint
checks for `/monitor`, `/monitor/api`, `/monitor/health`,
`/monitor/metrics`, and `/monitor/live`.
6. Publishes a tarball plus sidecar compatibility metadata.
These artifacts preserve upstream version coverage for users who run Nginx
built from nginx.org source. They are not the recommended artifact for Ubuntu or
Debian packaged Nginx.
## Release Assets
Recommended production asset:
```text
latest v1.2.3 + patch = v1.2.4
latest v1.2.3 + minor = v1.3.0
latest v1.2.3 + major = v2.0.0
no existing tag + patch = v0.0.1
libnginx-mod-http-monitoring_<module-version>-1+<distro>.nginx<source-version>_<deb-arch>.deb
```
## Built Nginx Versions
By default, the workflow builds packages for:
- Nginx 1.22.1
- Nginx 1.24.0
- Nginx 1.26.3
- Nginx 1.28.0
- Nginx 1.30.0
## Built CPU Targets
By default, the workflow builds module packages for:
- `linux-x86_64`
- `linux-arm64`
- `linux-armv7`
Aliases such as `amd64`, `arm64`, `aarch64`, `armv7`, and `armhf` are normalized by the workflow.
Each release asset is named like:
Manual fallback asset:
```text
ngx_http_monitoring_module-v1.0.0-nginx-1.28.0-linux-x86_64.tar.gz
ngx_http_monitoring_module-v1.0.0-nginx-1.28.0-linux-arm64.tar.gz
ngx_http_monitoring_module-<tag>-<distro>-nginx-<source-version>-<deb-arch>.tar.gz
```
Each package contains:
Upstream nginx.org source asset:
```text
ngx_http_monitoring_module-<tag>-nginxorg-<nginx-version>-<target-arch>.tar.gz
```
Metadata assets:
- `*.compatibility.json`: compatibility metadata for one artifact target
- `COMPATIBILITY-MATRIX.json`: combined release compatibility matrix
- `SHA256SUMS.txt`: checksums for release assets
Each tarball contains:
- `modules/ngx_http_monitoring_module.so`
- `INSTALL.md`
- `COMPATIBILITY.json`
- `METADATA.txt`
- `NGINX-V.txt`
- `SHA256SUMS`
- example config
- API/config/performance docs
- example config and docs
## ABI Guard
The `.deb` package depends on the distro `nginx-abi-*` virtual package when the
distribution exposes it and always pins the exact Nginx binary package version
used for the build. If a later Nginx package update changes either the ABI or
the package revision, apt cannot silently keep the old module installed against
the new Nginx binary.
This is stricter than ABI-only packaging and can require rebuilding the module
for routine distro Nginx updates. That is intentional: it favors explicit
release artifacts over a module that loads and then fails at runtime.
## Tarball Warning
Tarballs are useful for inspection, non-dpkg deployments, upstream nginx.org
source builds, and emergency manual installs. They do not give apt a dependency
edge, so they cannot protect a host from an ABI-changing Nginx update.
Use a distro tarball only when all fields in `COMPATIBILITY.json` match the
target host:
- OS ID and version
- CPU architecture
- Nginx binary package and version
- Nginx source package and version
- Nginx ABI, when present
Use an nginx.org tarball only when your target Nginx binary was built from the
same upstream Nginx source version and materially compatible configure options.
For Ubuntu/Debian packaged Nginx, use the `.deb` artifacts instead.
## Docker Image
When `push_image` is true, the workflow also builds and pushes:
When `push_image` is true, the workflow also builds a self-contained demo image:
```text
ghcr.io/<owner>/<repo>:<tag>
ghcr.io/<owner>/<repo>:latest
```
The image is built with Docker Buildx for the same normalized target platforms requested by `target_arches`.
The release image build passes:
```text
BASE_IMAGE=ubuntu:18.04
OPENSSL_RUNTIME_PACKAGE=libssl1.1
```
## Compatibility Warning
Nginx dynamic modules are ABI-sensitive. The release packages are built with `--with-compat` on Ubuntu Bionic 18.04 against the Nginx version and CPU architecture in the filename. Use the matching Nginx version, CPU architecture, and a Linux/glibc runtime compatible with `ubuntu-bionic-glibc-2.27`. Rebuild locally when using a vendor Nginx package with materially different module ABI or hardening options.
The image is not the recommended artifact for installing the module into
distribution-packaged Nginx. It is a runnable demo image with Nginx and the
module built together.

View File

@@ -0,0 +1,537 @@
#!/usr/bin/env bash
set -euo pipefail
MODULE_NAME="${MODULE_NAME:-ngx_http_monitoring_module}"
DEB_PACKAGE_NAME="${DEB_PACKAGE_NAME:-libnginx-mod-http-monitoring}"
NGINX_INSTALL_PACKAGE="${NGINX_INSTALL_PACKAGE:-nginx}"
NEXT_VERSION="${NEXT_VERSION:-0.0.0}"
NEXT_TAG="${NEXT_TAG:-v${NEXT_VERSION}}"
LATEST_TAG="${LATEST_TAG:-none}"
TARGET_ARCH="${TARGET_ARCH:-$(uname -m)}"
DOCKER_PLATFORM="${DOCKER_PLATFORM:-unknown}"
DISTRO_IMAGE="${DISTRO_IMAGE:-unknown}"
DIST_DIR="${DIST_DIR:-/work/dist}"
WORK_ROOT="${WORK_ROOT:-/work/.release-work}"
VALIDATION_PORT="${VALIDATION_PORT:-18080}"
GITHUB_REF="${GITHUB_REF:-unknown}"
GITHUB_SHA="${GITHUB_SHA:-unknown}"
export DEBIAN_FRONTEND=noninteractive
slugify() {
tr '[:upper:]' '[:lower:]' \
| sed -E 's/[^a-z0-9.+~]+/./g; s/^[.]+//; s/[.]+$//'
}
fail() {
echo "error: $*" >&2
exit 1
}
apt_install() {
apt-get install -y --no-install-recommends "$@"
}
find_nginx_abi() {
local pkg
local abi
for pkg in "$@"; do
abi="$(
apt-cache show "$pkg" 2>/dev/null \
| awk -F': ' '
/^Provides:/ {
n = split($2, provides, ",")
for (i = 1; i <= n; i++) {
gsub(/^[ \t]+|[ \t]+$/, "", provides[i])
if (provides[i] ~ /^nginx-abi-/) {
print provides[i]
exit
}
}
}
' \
| head -n 1 \
|| true
)"
if [[ -n "$abi" ]]; then
printf '%s\n' "$abi"
return 0
fi
done
}
validate_module() {
local module_path="$1"
local runtime_root="$2"
local config_path="$runtime_root/nginx.conf"
local base_url="http://127.0.0.1:${VALIDATION_PORT}"
local status
local dashboard
local sse_output
rm -rf "$runtime_root"
mkdir -p \
"$runtime_root/logs" \
"$runtime_root/temp/client_body" \
"$runtime_root/temp/proxy" \
"$runtime_root/temp/fastcgi" \
"$runtime_root/temp/uwsgi" \
"$runtime_root/temp/scgi"
{
printf 'load_module %s;\n\n' "$module_path"
printf 'worker_processes 1;\n'
printf 'error_log %s/logs/error.log info;\n' "$runtime_root"
printf 'pid %s/logs/nginx.pid;\n\n' "$runtime_root"
printf 'events {\n'
printf ' worker_connections 1024;\n'
printf '}\n\n'
printf 'http {\n'
printf ' access_log off;\n'
printf ' default_type application/octet-stream;\n'
printf ' client_body_temp_path %s/temp/client_body;\n' "$runtime_root"
printf ' proxy_temp_path %s/temp/proxy;\n' "$runtime_root"
printf ' fastcgi_temp_path %s/temp/fastcgi;\n' "$runtime_root"
printf ' uwsgi_temp_path %s/temp/uwsgi;\n' "$runtime_root"
printf ' scgi_temp_path %s/temp/scgi;\n\n' "$runtime_root"
printf ' monitor_refresh_interval 1s;\n'
printf ' monitor_history 1m;\n'
printf ' monitor_resolution 1s;\n'
printf ' monitor_shm_size 8m;\n'
printf ' monitor_collect_system on;\n'
printf ' monitor_collect_nginx on;\n'
printf ' monitor_collect_network on;\n'
printf ' monitor_access_log on;\n'
printf ' monitor_max_top_urls 100;\n\n'
printf ' server {\n'
printf ' listen 127.0.0.1:%s;\n' "$VALIDATION_PORT"
printf ' server_name localhost;\n\n'
printf ' location /monitor {\n'
printf ' monitor on;\n'
printf ' monitor_dashboard on;\n'
printf ' monitor_api on;\n'
printf ' monitor_sse on;\n'
printf ' monitor_allow all;\n'
printf ' monitor_basic_auth off;\n'
printf ' monitor_cors off;\n'
printf ' monitor_rate_limit 120;\n'
printf ' }\n\n'
printf ' location / {\n'
printf ' return 200 "ok\\n";\n'
printf ' }\n'
printf ' }\n'
printf '}\n'
} > "$config_path"
nginx -t -p "$runtime_root" -c "$config_path"
cleanup() {
nginx -p "$runtime_root" -c "$config_path" -s quit >/dev/null 2>&1 || true
}
trap cleanup RETURN
nginx -p "$runtime_root" -c "$config_path"
for attempt in $(seq 1 50); do
if curl -fsS "$base_url/" >/dev/null; then
break
fi
if [[ "$attempt" -eq 50 ]]; then
echo "Nginx did not become ready" >&2
return 1
fi
sleep 0.2
done
curl -fsS "$base_url/" | grep -q '^ok'
status="$(curl -fsS -o "$runtime_root/dashboard.html" -w '%{http_code}' "$base_url/monitor")"
[[ "$status" == "200" ]] || fail "/monitor returned HTTP $status"
dashboard="$(cat "$runtime_root/dashboard.html")"
printf '%s' "$dashboard" | grep -q '<title>Nginx Monitor</title>'
printf '%s' "$dashboard" | grep -q '/monitor/api'
printf '%s' "$dashboard" | grep -q '/monitor/live'
curl -fsS "$base_url/monitor/api" \
| jq -e '.version == 1 and has("system") and has("nginx") and has("requests") and has("history")' >/dev/null
curl -fsS "$base_url/monitor/api/system" \
| jq -e '.version == 1 and .scope == "system" and has("system")' >/dev/null
curl -fsS "$base_url/monitor/api/nginx" \
| jq -e '.version == 1 and .scope == "nginx" and has("nginx")' >/dev/null
curl -fsS "$base_url/monitor/health" \
| jq -e '.version == 1 and .scope == "health" and .status == "ok"' >/dev/null
curl -fsS "$base_url/monitor/metrics" \
| grep -q '^nginx_monitor_requests_total '
sse_output="$(timeout 5s curl -fsS -N "$base_url/monitor/live" || true)"
printf '%s' "$sse_output" | grep -q '^event: metrics'
printf '%s' "$sse_output" | grep -q '^data: {'
cleanup
trap - RETURN
}
mkdir -p "$DIST_DIR" "$WORK_ROOT"
printf '#!/bin/sh\nexit 101\n' > /usr/sbin/policy-rc.d
chmod +x /usr/sbin/policy-rc.d
apt-get update
apt_install ca-certificates curl gnupg
bash /work/scripts/enable-apt-source-repos.sh
apt-get update
apt_install \
bash \
binutils \
build-essential \
ca-certificates \
curl \
dpkg-dev \
file \
findutils \
jq \
libpcre2-dev \
libssl-dev \
python3 \
xz-utils \
zlib1g-dev \
"$NGINX_INSTALL_PACKAGE"
. /etc/os-release
distro_id="${ID:-unknown}"
distro_version="${VERSION_ID:-unknown}"
distro_codename="${VERSION_CODENAME:-${UBUNTU_CODENAME:-unknown}}"
distro_slug="$(printf '%s-%s-%s' "$distro_id" "$distro_version" "$distro_codename" | slugify)"
deb_arch="$(dpkg --print-architecture)"
nginx_bin="$(command -v nginx)"
nginx_binary_package="$(dpkg-query -S "$nginx_bin" | head -n 1 | cut -d: -f1)"
nginx_binary_version="$(dpkg-query -W -f='${Version}' "$nginx_binary_package")"
source_package="$(dpkg-query -W -f='${source:Package}' "$nginx_binary_package" 2>/dev/null || true)"
source_version="$(dpkg-query -W -f='${source:Version}' "$nginx_binary_package" 2>/dev/null || true)"
if [[ -z "$source_package" || "$source_package" == *'{'* ]]; then
source_package="$nginx_binary_package"
fi
if [[ -z "$source_version" || "$source_version" == *'{'* ]]; then
source_version="$nginx_binary_version"
fi
nginx_abi="$(find_nginx_abi "$nginx_binary_package" "$NGINX_INSTALL_PACKAGE" nginx-core nginx-full nginx-light nginx-extras nginx)"
echo "Distro: ${distro_id} ${distro_version} (${distro_codename}), arch ${deb_arch}"
echo "Nginx binary package: ${nginx_binary_package} ${nginx_binary_version}"
echo "Nginx source package: ${source_package} ${source_version}"
echo "Nginx ABI package: ${nginx_abi:-none found}"
src_root="$WORK_ROOT/${distro_slug}/${deb_arch}/source"
build_root="$WORK_ROOT/${distro_slug}/${deb_arch}/build"
rm -rf "$src_root" "$build_root"
mkdir -p "$src_root" "$build_root"
(
cd "$src_root"
apt-get source "${source_package}=${source_version}" \
|| apt-get source "$source_package"
)
dsc_path="$(find "$src_root" -maxdepth 1 -name '*.dsc' | head -n 1)"
[[ -n "$dsc_path" ]] || fail "apt-get source did not download a .dsc file"
dsc_version="$(awk '/^Version:/ { print $2; exit }' "$dsc_path")"
if [[ "$dsc_version" != "$source_version" ]]; then
fail "downloaded source version ${dsc_version}, expected ${source_version}"
fi
nginx_src="$(find "$src_root" -mindepth 1 -maxdepth 1 -type d | head -n 1)"
[[ -n "$nginx_src" ]] || fail "apt-get source did not unpack an nginx source directory"
apt-get build-dep -y --no-install-recommends "${source_package}=${source_version}" \
|| apt-get build-dep -y --no-install-recommends "$source_package"
nginx_build_info="$build_root/nginx-build-info.json"
python3 /work/scripts/nginx-build-info.py --nginx-src "$nginx_src" --json > "$nginx_build_info"
modules_path="$(python3 /work/scripts/nginx-build-info.py --modules-path)"
configure_line="$(python3 /work/scripts/nginx-build-info.py --nginx-src "$nginx_src" --configure-line)"
case "$modules_path" in
/*) ;;
*) modules_path="/${modules_path}" ;;
esac
source_version_slug="$(printf '%s' "$source_version" | slugify)"
deb_revision_slug="$(printf '%s%s.nginx%s' "$distro_id" "$distro_version" "$source_version" | slugify)"
deb_version="${NEXT_VERSION}-1+${deb_revision_slug}"
build_metadata="$(printf '%s.%s.%s' "$distro_slug" "$deb_arch" "$source_version_slug" | slugify)"
module_cflags="-DNGX_HTTP_MONITORING_MODULE_VERSION=\\\"${NEXT_VERSION}\\\" -DNGX_HTTP_MONITORING_BUILD_METADATA=\\\"${build_metadata}\\\""
cd "$nginx_src"
eval "./configure ${configure_line} --with-cc-opt=\"${module_cflags}\" --add-dynamic-module=/work"
build_jobs="$(nproc)"
if [[ "$TARGET_ARCH" != "linux-x86_64" && "$build_jobs" -gt 2 ]]; then
build_jobs=2
fi
make -j"$build_jobs" modules
built_module="$nginx_src/objs/${MODULE_NAME}.so"
[[ -f "$built_module" ]] || fail "module was not built at ${built_module}"
artifact_base="${MODULE_NAME}-${NEXT_TAG}-${distro_slug}-nginx-${source_version_slug}-${deb_arch}"
deb_file="${DEB_PACKAGE_NAME}_${deb_version}_${deb_arch}.deb"
tarball_file="${artifact_base}.tar.gz"
package_root="$build_root/package-root"
doc_dir="$package_root/usr/share/doc/$DEB_PACKAGE_NAME"
module_install_dir="$package_root/${modules_path#/}"
modules_available_dir="$package_root/usr/share/nginx/modules-available"
metadata_dir="$doc_dir/metadata"
rm -rf "$package_root"
mkdir -p "$module_install_dir" "$modules_available_dir" "$metadata_dir" "$package_root/DEBIAN"
cp "$built_module" "$module_install_dir/${MODULE_NAME}.so"
strip --strip-unneeded "$module_install_dir/${MODULE_NAME}.so" || true
cat > "$modules_available_dir/mod-http-monitoring.conf" <<EOF
load_module ${modules_path}/${MODULE_NAME}.so;
EOF
cp /work/README.md "$doc_dir/README.md"
cp /work/LICENSE "$doc_dir/copyright"
cp /work/docs/API.md "$doc_dir/API.md"
cp /work/docs/CONFIGURATION.md "$doc_dir/CONFIGURATION.md"
cp /work/docs/PERFORMANCE.md "$doc_dir/PERFORMANCE.md"
cp /work/examples/monitoring-site.conf "$doc_dir/monitoring-site.conf.example"
cat > "$doc_dir/changelog.Debian" <<EOF
$DEB_PACKAGE_NAME (${deb_version}) unstable; urgency=medium
* Build ${NEXT_TAG} against ${distro_id} ${distro_version} nginx source ${source_version}.
-- GitHub Actions <actions@github.com> $(date -Ru)
EOF
gzip -9n "$doc_dir/changelog.Debian"
module_sha256="$(sha256sum "$module_install_dir/${MODULE_NAME}.so" | awk '{ print $1 }')"
dsc_sha256="$(sha256sum "$dsc_path" | awk '{ print $1 }')"
nginx_v_text="$metadata_dir/nginx-V.txt"
nginx -V > "$nginx_v_text" 2>&1
compatibility_json="$metadata_dir/compatibility.json"
jq -n \
--slurpfile nginxBuild "$nginx_build_info" \
--arg schema "1" \
--arg module "$MODULE_NAME" \
--arg debPackage "$DEB_PACKAGE_NAME" \
--arg debVersion "$deb_version" \
--arg releaseTag "$NEXT_TAG" \
--arg releaseVersion "$NEXT_VERSION" \
--arg previousTag "$LATEST_TAG" \
--arg gitRef "$GITHUB_REF" \
--arg gitSha "$GITHUB_SHA" \
--arg distroImage "$DISTRO_IMAGE" \
--arg dockerPlatform "$DOCKER_PLATFORM" \
--arg targetArch "$TARGET_ARCH" \
--arg osId "$distro_id" \
--arg osVersion "$distro_version" \
--arg osCodename "$distro_codename" \
--arg debArch "$deb_arch" \
--arg nginxBinaryPackage "$nginx_binary_package" \
--arg nginxBinaryVersion "$nginx_binary_version" \
--arg nginxSourcePackage "$source_package" \
--arg nginxSourceVersion "$source_version" \
--arg nginxAbi "$nginx_abi" \
--arg modulesPath "$modules_path" \
--arg dscPath "$(basename "$dsc_path")" \
--arg dscSha256 "$dsc_sha256" \
--arg moduleSha256 "$module_sha256" \
--arg builtAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--arg artifactBase "$artifact_base" \
--arg debFile "$deb_file" \
--arg tarballFile "$tarball_file" \
'{
schema: ($schema | tonumber),
module: $module,
release: {
tag: $releaseTag,
version: $releaseVersion,
previous_tag: $previousTag,
git_ref: $gitRef,
git_sha: $gitSha
},
target: {
os_id: $osId,
os_version: $osVersion,
os_codename: $osCodename,
architecture: $debArch,
requested_target: $targetArch,
docker_image: $distroImage,
docker_platform: $dockerPlatform
},
nginx: {
binary_package: $nginxBinaryPackage,
binary_version: $nginxBinaryVersion,
source_package: $nginxSourcePackage,
source_version: $nginxSourceVersion,
abi: $nginxAbi,
modules_path: $modulesPath,
build: $nginxBuild[0]
},
build: {
method: "distro-source-package",
dsc_file: $dscPath,
dsc_sha256: $dscSha256,
module_sha256: $moduleSha256,
built_at: $builtAt
},
artifacts: {
base_name: $artifactBase,
deb_package: $debPackage,
deb_version: $debVersion,
deb_file: $debFile,
tarball_file: $tarballFile,
compatibility_file: ($artifactBase + ".compatibility.json")
},
packaging: {
deb_depends: (
if $nginxAbi == "" then
[($nginxBinaryPackage + " (= " + $nginxBinaryVersion + ")"), "libc6"]
else
[$nginxAbi, ($nginxBinaryPackage + " (= " + $nginxBinaryVersion + ")"), "libc6"]
end
)
},
validation: {
packaged_nginx_config_test: false,
packaged_nginx_endpoint_smoke: false
}
}' > "$compatibility_json"
cat > "$doc_dir/INSTALL.md" <<EOF
# ${MODULE_NAME} for ${distro_id} ${distro_version} ${deb_arch}
This package was built from the ${source_package} source package version
${source_version} and validated against the packaged ${nginx_binary_package}
binary version ${nginx_binary_version}.
Recommended install:
\`\`\`sh
sudo apt install ./$(printf '%s_%s_%s.deb' "$DEB_PACKAGE_NAME" "$deb_version" "$deb_arch")
\`\`\`
The package installs the module at:
\`\`\`text
${modules_path}/${MODULE_NAME}.so
\`\`\`
Load it near the top of nginx.conf:
\`\`\`nginx
load_module ${modules_path}/${MODULE_NAME}.so;
\`\`\`
Compatibility guard:
\`\`\`text
$(if [[ -n "$nginx_abi" ]]; then printf 'Depends: %s\nDepends: %s (= %s)\n' "$nginx_abi" "$nginx_binary_package" "$nginx_binary_version"; else printf 'Depends: %s (= %s)\n' "$nginx_binary_package" "$nginx_binary_version"; fi)
\`\`\`
If a distro nginx update changes this ABI, apt will refuse to keep this module
installed with the new nginx package. Rebuild and publish a new package for the
new distro nginx package instead of reusing this binary.
EOF
depends=""
if [[ -n "$nginx_abi" ]]; then
depends="$nginx_abi, $nginx_binary_package (= $nginx_binary_version), libc6"
else
depends="$nginx_binary_package (= $nginx_binary_version), libc6"
fi
installed_size="$(du -ks "$package_root" | awk '{ print $1 }')"
cat > "$package_root/DEBIAN/control" <<EOF
Package: $DEB_PACKAGE_NAME
Version: $deb_version
Section: httpd
Priority: optional
Architecture: $deb_arch
Maintainer: ngx_http_monitoring_module maintainers <noreply@github.com>
Depends: $depends
Installed-Size: $installed_size
Description: Nginx HTTP monitoring dynamic module
Distro-built dynamic module for live Nginx monitoring, JSON APIs,
Server-Sent Events, Prometheus metrics, and an embedded dashboard.
This package is built against the target distribution nginx source package
and depends on the matching nginx module ABI.
EOF
deb_path="$DIST_DIR/$deb_file"
dpkg-deb --build --root-owner-group "$package_root" "$deb_path"
apt-get install -y --no-install-recommends "$deb_path"
validate_module "${modules_path}/${MODULE_NAME}.so" "$build_root/runtime"
jq \
--arg debFile "$deb_file" \
--arg validationTime "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'.artifacts.deb_file = $debFile
| .validation.packaged_nginx_config_test = true
| .validation.packaged_nginx_endpoint_smoke = true
| .validation.validated_at = $validationTime' \
"$compatibility_json" > "$compatibility_json.tmp"
mv "$compatibility_json.tmp" "$compatibility_json"
dpkg-deb --build --root-owner-group "$package_root" "$deb_path"
tar_root="$build_root/tar"
tar_dir="$tar_root/$artifact_base"
rm -rf "$tar_root"
mkdir -p "$tar_dir/modules" "$tar_dir/docs" "$tar_dir/examples"
cp "${modules_path}/${MODULE_NAME}.so" "$tar_dir/modules/${MODULE_NAME}.so"
cp "$compatibility_json" "$tar_dir/COMPATIBILITY.json"
cp "$doc_dir/INSTALL.md" "$tar_dir/INSTALL.md"
cp "$doc_dir/README.md" "$tar_dir/README.md"
cp "$doc_dir/API.md" "$tar_dir/docs/API.md"
cp "$doc_dir/CONFIGURATION.md" "$tar_dir/docs/CONFIGURATION.md"
cp "$doc_dir/PERFORMANCE.md" "$tar_dir/docs/PERFORMANCE.md"
cp "$doc_dir/monitoring-site.conf.example" "$tar_dir/examples/monitoring-site.conf"
cp "$nginx_v_text" "$tar_dir/NGINX-V.txt"
{
printf 'module=%s\n' "$MODULE_NAME"
printf 'module_release=%s\n' "$NEXT_TAG"
printf 'module_version=%s\n' "$NEXT_VERSION"
printf 'module_build_metadata=%s\n' "$build_metadata"
printf 'distro=%s\n' "$distro_slug"
printf 'architecture=%s\n' "$deb_arch"
printf 'nginx_binary_package=%s\n' "$nginx_binary_package"
printf 'nginx_binary_version=%s\n' "$nginx_binary_version"
printf 'nginx_source_package=%s\n' "$source_package"
printf 'nginx_source_version=%s\n' "$source_version"
printf 'nginx_abi=%s\n' "$nginx_abi"
printf 'modules_path=%s\n' "$modules_path"
printf 'git_ref=%s\n' "$GITHUB_REF"
printf 'git_sha=%s\n' "$GITHUB_SHA"
printf 'built_at=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
} > "$tar_dir/METADATA.txt"
(
cd "$tar_dir"
sha256sum "modules/${MODULE_NAME}.so" COMPATIBILITY.json > SHA256SUMS
file "modules/${MODULE_NAME}.so" > FILE.txt
)
tar -czf "$DIST_DIR/$tarball_file" -C "$tar_root" "$artifact_base"
cp "$compatibility_json" "$DIST_DIR/${artifact_base}.compatibility.json"
echo "Built $deb_path"
echo "Built $DIST_DIR/$tarball_file"
echo "Built $DIST_DIR/${artifact_base}.compatibility.json"

View File

@@ -0,0 +1,359 @@
#!/usr/bin/env bash
set -euo pipefail
MODULE_NAME="${MODULE_NAME:-ngx_http_monitoring_module}"
BUILD_NGINX_VERSIONS="${BUILD_NGINX_VERSIONS:-}"
NEXT_VERSION="${NEXT_VERSION:-0.0.0}"
NEXT_TAG="${NEXT_TAG:-v${NEXT_VERSION}}"
LATEST_TAG="${LATEST_TAG:-none}"
TARGET_ARCH="${TARGET_ARCH:-$(uname -m)}"
DOCKER_PLATFORM="${DOCKER_PLATFORM:-unknown}"
RELEASE_BUILD_IMAGE="${RELEASE_BUILD_IMAGE:-unknown}"
RELEASE_LIBC_BASELINE="${RELEASE_LIBC_BASELINE:-unknown}"
NGINX_CONFIGURE_ARGS="${NGINX_CONFIGURE_ARGS:---with-compat --with-http_ssl_module --with-http_stub_status_module}"
DIST_DIR="${DIST_DIR:-/work/dist}"
WORK_ROOT="${WORK_ROOT:-/work/.release-work/upstream}"
VALIDATION_PORT="${VALIDATION_PORT:-18080}"
GITHUB_REF="${GITHUB_REF:-unknown}"
GITHUB_SHA="${GITHUB_SHA:-unknown}"
export DEBIAN_FRONTEND=noninteractive
fail() {
echo "error: $*" >&2
exit 1
}
slugify() {
tr '[:upper:]' '[:lower:]' \
| sed -E 's/[^a-z0-9.+~]+/./g; s/^[.]+//; s/[.]+$//'
}
validate_module() {
local nginx_bin="$1"
local module_path="$2"
local runtime_root="$3"
local config_path="$runtime_root/nginx.conf"
local base_url="http://127.0.0.1:${VALIDATION_PORT}"
local status
local dashboard
local sse_output
rm -rf "$runtime_root"
mkdir -p \
"$runtime_root/logs" \
"$runtime_root/temp/client_body" \
"$runtime_root/temp/proxy" \
"$runtime_root/temp/fastcgi" \
"$runtime_root/temp/uwsgi" \
"$runtime_root/temp/scgi"
{
printf 'load_module %s;\n\n' "$module_path"
printf 'worker_processes 1;\n'
printf 'error_log %s/logs/error.log info;\n' "$runtime_root"
printf 'pid %s/logs/nginx.pid;\n\n' "$runtime_root"
printf 'events {\n'
printf ' worker_connections 1024;\n'
printf '}\n\n'
printf 'http {\n'
printf ' access_log off;\n'
printf ' default_type application/octet-stream;\n'
printf ' client_body_temp_path %s/temp/client_body;\n' "$runtime_root"
printf ' proxy_temp_path %s/temp/proxy;\n' "$runtime_root"
printf ' fastcgi_temp_path %s/temp/fastcgi;\n' "$runtime_root"
printf ' uwsgi_temp_path %s/temp/uwsgi;\n' "$runtime_root"
printf ' scgi_temp_path %s/temp/scgi;\n\n' "$runtime_root"
printf ' monitor_refresh_interval 1s;\n'
printf ' monitor_history 1m;\n'
printf ' monitor_resolution 1s;\n'
printf ' monitor_shm_size 8m;\n'
printf ' monitor_collect_system on;\n'
printf ' monitor_collect_nginx on;\n'
printf ' monitor_collect_network on;\n'
printf ' monitor_access_log on;\n'
printf ' monitor_max_top_urls 100;\n\n'
printf ' server {\n'
printf ' listen 127.0.0.1:%s;\n' "$VALIDATION_PORT"
printf ' server_name localhost;\n\n'
printf ' location /monitor {\n'
printf ' monitor on;\n'
printf ' monitor_dashboard on;\n'
printf ' monitor_api on;\n'
printf ' monitor_sse on;\n'
printf ' monitor_allow all;\n'
printf ' monitor_basic_auth off;\n'
printf ' monitor_cors off;\n'
printf ' monitor_rate_limit 120;\n'
printf ' }\n\n'
printf ' location / {\n'
printf ' return 200 "ok\\n";\n'
printf ' }\n'
printf ' }\n'
printf '}\n'
} > "$config_path"
"$nginx_bin" -t -p "$runtime_root" -c "$config_path"
cleanup() {
"$nginx_bin" -p "$runtime_root" -c "$config_path" -s quit >/dev/null 2>&1 || true
}
trap cleanup RETURN
"$nginx_bin" -p "$runtime_root" -c "$config_path"
for attempt in $(seq 1 50); do
if curl -fsS "$base_url/" >/dev/null; then
break
fi
if [[ "$attempt" -eq 50 ]]; then
echo "Nginx did not become ready" >&2
return 1
fi
sleep 0.2
done
curl -fsS "$base_url/" | grep -q '^ok'
status="$(curl -fsS -o "$runtime_root/dashboard.html" -w '%{http_code}' "$base_url/monitor")"
[[ "$status" == "200" ]] || fail "/monitor returned HTTP $status"
dashboard="$(cat "$runtime_root/dashboard.html")"
printf '%s' "$dashboard" | grep -q '<title>Nginx Monitor</title>'
printf '%s' "$dashboard" | grep -q '/monitor/api'
printf '%s' "$dashboard" | grep -q '/monitor/live'
curl -fsS "$base_url/monitor/api" \
| jq -e '.version == 1 and has("system") and has("nginx") and has("requests") and has("history")' >/dev/null
curl -fsS "$base_url/monitor/api/system" \
| jq -e '.version == 1 and .scope == "system" and has("system")' >/dev/null
curl -fsS "$base_url/monitor/api/nginx" \
| jq -e '.version == 1 and .scope == "nginx" and has("nginx")' >/dev/null
curl -fsS "$base_url/monitor/health" \
| jq -e '.version == 1 and .scope == "health" and .status == "ok"' >/dev/null
curl -fsS "$base_url/monitor/metrics" \
| grep -q '^nginx_monitor_requests_total '
sse_output="$(timeout 5s curl -fsS -N "$base_url/monitor/live" || true)"
printf '%s' "$sse_output" | grep -q '^event: metrics'
printf '%s' "$sse_output" | grep -q '^data: {'
cleanup
trap - RETURN
}
[[ -n "$BUILD_NGINX_VERSIONS" ]] || fail "BUILD_NGINX_VERSIONS is required"
apt-get update
apt-get install -y --no-install-recommends \
binutils \
build-essential \
ca-certificates \
curl \
file \
jq \
libpcre2-dev \
libssl-dev \
zlib1g-dev
rm -rf /var/lib/apt/lists/*
mkdir -p "$DIST_DIR" "$WORK_ROOT"
for nginx_version in $BUILD_NGINX_VERSIONS; do
archive="nginx-${nginx_version}.tar.gz"
archive_path="${WORK_ROOT}/${archive}"
workdir="${WORK_ROOT}/nginx-${nginx_version}"
runtime_root="${WORK_ROOT}/runtime/nginx-${nginx_version}"
if [[ ! -f "$archive_path" ]]; then
curl -fsSLo "$archive_path" "https://nginx.org/download/${archive}"
fi
rm -rf "$workdir"
tar -xzf "$archive_path" -C "$WORK_ROOT"
nginx_version_slug="$(printf '%s' "$nginx_version" | slugify)"
build_metadata="$(printf 'nginxorg.%s.%s.%s' "$nginx_version_slug" "$TARGET_ARCH" "$RELEASE_LIBC_BASELINE" | slugify)"
module_cflags="-DNGX_HTTP_MONITORING_MODULE_VERSION=\\\"${NEXT_VERSION}\\\" -DNGX_HTTP_MONITORING_BUILD_METADATA=\\\"${build_metadata}\\\""
cd "$workdir"
./configure \
--prefix=/usr/local/nginx \
--sbin-path=/usr/local/sbin/nginx \
--modules-path=/usr/local/nginx/modules \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--http-client-body-temp-path=/var/cache/nginx/client_temp \
--http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \
--with-cc-opt="$module_cflags" \
$NGINX_CONFIGURE_ARGS \
--add-dynamic-module=/work
build_jobs="$(nproc)"
if [[ "$TARGET_ARCH" != "linux-x86_64" && "$build_jobs" -gt 2 ]]; then
build_jobs=2
fi
make -j"$build_jobs"
make -j"$build_jobs" modules
module_path="$workdir/objs/${MODULE_NAME}.so"
nginx_bin="$workdir/objs/nginx"
[[ -x "$nginx_bin" ]] || fail "nginx binary was not built at ${nginx_bin}"
[[ -f "$module_path" ]] || fail "module was not built at ${module_path}"
strip --strip-unneeded "$module_path" || true
validate_module "$nginx_bin" "$module_path" "$runtime_root"
module_sha256="$(sha256sum "$module_path" | awk '{ print $1 }')"
archive_sha256="$(sha256sum "$archive_path" | awk '{ print $1 }')"
artifact_base="${MODULE_NAME}-${NEXT_TAG}-nginxorg-${nginx_version_slug}-${TARGET_ARCH}"
package_root="${WORK_ROOT}/packages/${artifact_base}"
compatibility_json="${package_root}/COMPATIBILITY.json"
rm -rf "$package_root"
mkdir -p "$package_root/modules" "$package_root/docs" "$package_root/examples"
cp "$module_path" "$package_root/modules/${MODULE_NAME}.so"
cp /work/examples/nginx.conf "$package_root/examples/nginx.conf"
cp /work/examples/monitoring-site.conf "$package_root/examples/monitoring-site.conf"
cp /work/README.md "$package_root/README.md"
cp /work/docs/API.md "$package_root/docs/API.md"
cp /work/docs/CONFIGURATION.md "$package_root/docs/CONFIGURATION.md"
cp /work/docs/PERFORMANCE.md "$package_root/docs/PERFORMANCE.md"
"$nginx_bin" -V > "$package_root/NGINX-V.txt" 2>&1
tarball_file="${artifact_base}.tar.gz"
sidecar_file="${artifact_base}.compatibility.json"
jq -n \
--arg schema "1" \
--arg module "$MODULE_NAME" \
--arg releaseTag "$NEXT_TAG" \
--arg releaseVersion "$NEXT_VERSION" \
--arg previousTag "$LATEST_TAG" \
--arg gitRef "$GITHUB_REF" \
--arg gitSha "$GITHUB_SHA" \
--arg buildImage "$RELEASE_BUILD_IMAGE" \
--arg libcBaseline "$RELEASE_LIBC_BASELINE" \
--arg dockerPlatform "$DOCKER_PLATFORM" \
--arg targetArch "$TARGET_ARCH" \
--arg nginxVersion "$nginx_version" \
--arg configureArgs "$NGINX_CONFIGURE_ARGS" \
--arg archive "$archive" \
--arg archiveSha256 "$archive_sha256" \
--arg moduleSha256 "$module_sha256" \
--arg builtAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--arg artifactBase "$artifact_base" \
--arg tarballFile "$tarball_file" \
--arg sidecarFile "$sidecar_file" \
--arg validationTime "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'{
schema: ($schema | tonumber),
module: $module,
release: {
tag: $releaseTag,
version: $releaseVersion,
previous_tag: $previousTag,
git_ref: $gitRef,
git_sha: $gitSha
},
target: {
os_id: "nginx.org-source",
os_version: $nginxVersion,
os_codename: "upstream",
architecture: $targetArch,
requested_target: $targetArch,
docker_image: $buildImage,
docker_platform: $dockerPlatform,
libc_baseline: $libcBaseline
},
nginx: {
binary_package: "nginx.org-source-build",
binary_version: $nginxVersion,
source_package: "nginx.org",
source_version: $nginxVersion,
abi: "",
modules_path: "/usr/local/nginx/modules",
build: {
version: ("nginx/" + $nginxVersion),
configure_args: ($configureArgs | split(" ")),
configure_line: $configureArgs
}
},
build: {
method: "upstream-nginxorg-source",
source_archive: $archive,
source_archive_sha256: $archiveSha256,
module_sha256: $moduleSha256,
built_at: $builtAt
},
artifacts: {
base_name: $artifactBase,
deb_package: "",
deb_version: "",
deb_file: "",
tarball_file: $tarballFile,
compatibility_file: $sidecarFile
},
packaging: {
deb_depends: []
},
validation: {
packaged_nginx_config_test: false,
packaged_nginx_endpoint_smoke: false,
built_nginx_config_test: true,
built_nginx_endpoint_smoke: true,
validated_at: $validationTime
}
}' > "$compatibility_json"
{
printf '# %s for nginx.org %s\n\n' "$MODULE_NAME" "$nginx_version"
printf 'Release: `%s`\n\n' "$NEXT_TAG"
printf 'Target: `%s` (`%s`)\n\n' "$TARGET_ARCH" "$DOCKER_PLATFORM"
printf 'Build baseline: `%s`\n\n' "$RELEASE_LIBC_BASELINE"
printf 'This tarball is built against upstream nginx.org source, not a Debian or Ubuntu source package.\n\n'
printf 'Use this artifact for Nginx binaries built from the same upstream source version and compatible configure options. For distribution-packaged Nginx, prefer the distro `.deb` release artifacts.\n\n'
printf 'Load it near the top of `nginx.conf`:\n\n'
printf '```nginx\n'
printf 'load_module modules/%s.so;\n' "$MODULE_NAME"
printf '```\n'
} > "$package_root/INSTALL.md"
{
printf 'module=%s\n' "$MODULE_NAME"
printf 'module_release=%s\n' "$NEXT_TAG"
printf 'module_version=%s\n' "$NEXT_VERSION"
printf 'module_build_metadata=%s\n' "$build_metadata"
printf 'nginx_source=nginx.org\n'
printf 'nginx_version=%s\n' "$nginx_version"
printf 'target=%s\n' "$TARGET_ARCH"
printf 'docker_platform=%s\n' "$DOCKER_PLATFORM"
printf 'build_image=%s\n' "$RELEASE_BUILD_IMAGE"
printf 'libc_baseline=%s\n' "$RELEASE_LIBC_BASELINE"
printf 'configure_args=%s\n' "$NGINX_CONFIGURE_ARGS"
printf 'git_ref=%s\n' "$GITHUB_REF"
printf 'git_sha=%s\n' "$GITHUB_SHA"
printf 'built_at=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
} > "$package_root/METADATA.txt"
(
cd "$package_root"
sha256sum "modules/${MODULE_NAME}.so" COMPATIBILITY.json > SHA256SUMS
file "modules/${MODULE_NAME}.so" > FILE.txt
)
tar -czf "$DIST_DIR/$tarball_file" -C "$(dirname "$package_root")" "$(basename "$package_root")"
cp "$compatibility_json" "$DIST_DIR/$sidecar_file"
echo "Built $DIST_DIR/$tarball_file"
echo "Built $DIST_DIR/$sidecar_file"
done

View File

@@ -0,0 +1,53 @@
#!/usr/bin/env bash
set -euo pipefail
enable_deb822_sources() {
local file="$1"
local tmp
tmp="$(mktemp)"
awk '
/^Types:/ {
if ($0 !~ /(^|[[:space:]])deb-src($|[[:space:]])/) {
print $0 " deb-src"
next
}
}
{ print }
' "$file" > "$tmp"
cat "$tmp" > "$file"
rm -f "$tmp"
}
enable_legacy_sources() {
local file="$1"
local output="$2"
awk '
/^[[:space:]]*deb[[:space:]]/ {
line = $0
sub(/^[[:space:]]*deb[[:space:]]+/, "deb-src ", line)
print line
}
' "$file" > "$output"
}
found_sources=0
for sources_file in /etc/apt/sources.list.d/*.sources; do
if [[ -f "$sources_file" ]]; then
found_sources=1
enable_deb822_sources "$sources_file"
fi
done
if [[ -f /etc/apt/sources.list ]]; then
found_sources=1
enable_legacy_sources /etc/apt/sources.list \
/etc/apt/sources.list.d/codex-deb-src.list
fi
if [[ "$found_sources" -eq 0 ]]; then
echo "No apt source list files were found under /etc/apt" >&2
exit 1
fi

124
scripts/nginx-build-info.py Normal file
View File

@@ -0,0 +1,124 @@
#!/usr/bin/env python3
import argparse
import json
import os
import shlex
import subprocess
import sys
def nginx_v_output(nginx_bin):
proc = subprocess.run(
[nginx_bin, "-V"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
check=True,
)
return proc.stdout
def parse_nginx_v(output):
version = ""
configure_line = ""
for line in output.splitlines():
if line.startswith("nginx version:"):
version = line.split(":", 1)[1].strip()
elif line.startswith("configure arguments:"):
configure_line = line.split(":", 1)[1].strip()
if not configure_line:
raise RuntimeError("nginx -V did not include configure arguments")
return version, configure_line, shlex.split(configure_line)
def option_value(args, option):
prefix = option + "="
for arg in args:
if arg.startswith(prefix):
return arg[len(prefix) :]
return ""
def rewrite_module_path(arg, nginx_src):
for option in ("--add-module=", "--add-dynamic-module="):
if not arg.startswith(option):
continue
module_path = arg[len(option) :]
if os.path.exists(module_path):
return arg
marker = "/debian/modules/"
if marker in module_path:
suffix = module_path.split(marker, 1)[1]
candidate = os.path.join(nginx_src, "debian", "modules", suffix)
if os.path.exists(candidate):
return option + candidate
marker = "/debian/"
if marker in module_path:
suffix = module_path.split(marker, 1)[1]
candidate = os.path.join(nginx_src, "debian", suffix)
if os.path.exists(candidate):
return option + candidate
return arg
def rewrite_configure_args(args, nginx_src):
if not nginx_src:
return args
return [rewrite_module_path(arg, nginx_src) for arg in args]
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--nginx-bin", default="nginx")
parser.add_argument("--nginx-src", default="")
parser.add_argument("--json", action="store_true")
parser.add_argument("--configure-line", action="store_true")
parser.add_argument("--modules-path", action="store_true")
args = parser.parse_args()
output = nginx_v_output(args.nginx_bin)
version, configure_line, configure_args = parse_nginx_v(output)
rewritten_args = rewrite_configure_args(configure_args, args.nginx_src)
modules_path = option_value(configure_args, "--modules-path")
if not modules_path:
prefix = option_value(configure_args, "--prefix") or "/usr/share/nginx"
modules_path = os.path.join(prefix, "modules")
if args.configure_line:
print(shlex.join(rewritten_args))
return
if args.modules_path:
print(modules_path)
return
if args.json:
json.dump(
{
"version": version,
"configure_line": configure_line,
"configure_args": configure_args,
"rewritten_configure_args": rewritten_args,
"modules_path": modules_path,
},
sys.stdout,
indent=2,
sort_keys=True,
)
sys.stdout.write("\n")
return
parser.error("choose one of --json, --configure-line, or --modules-path")
if __name__ == "__main__":
main()

View File

@@ -9,7 +9,14 @@
#include <stdint.h>
#define NGX_HTTP_MONITORING_VERSION 1
#define NGX_HTTP_MONITORING_MODULE_VERSION "1.0.0"
#ifndef NGX_HTTP_MONITORING_MODULE_VERSION
#define NGX_HTTP_MONITORING_MODULE_VERSION "0.0.0-dev"
#endif
#ifndef NGX_HTTP_MONITORING_BUILD_METADATA
#define NGX_HTTP_MONITORING_BUILD_METADATA "source"
#endif
#define NGX_HTTP_MONITORING_DEFAULT_SHM_SIZE (8 * 1024 * 1024)
#define NGX_HTTP_MONITORING_MIN_INTERVAL 100

View File

@@ -411,10 +411,11 @@ ngx_http_monitoring_json_header(ngx_http_monitoring_json_writer_t *jw,
tp = ngx_timeofday();
ngx_http_monitoring_json_printf(jw,
"{\"version\":%d,\"module\":\"%s\",\"timestamp\":%T,"
"\"msec\":%M,\"scope\":\"%s\",\"pid\":%P",
"{\"version\":%d,\"module\":\"%s\",\"build\":\"%s\","
"\"timestamp\":%T,\"msec\":%M,\"scope\":\"%s\",\"pid\":%P",
NGX_HTTP_MONITORING_VERSION,
NGX_HTTP_MONITORING_MODULE_VERSION,
NGX_HTTP_MONITORING_BUILD_METADATA,
tp->sec, tp->msec, scope, ngx_pid);
(void) r;