Compare commits

...

10 Commits

Author SHA1 Message Date
05a62dfeb9 refactor: consolidate compatibility JSON generation into Python script
Some checks failed
Test Module Build / Nginx 1.22.1 (linux-armv7) (push) Failing after 12m0s
Test Module Build / Nginx 1.22.1 (linux-x86_64) (push) Failing after 9m49s
Test Module Build / Nginx 1.22.1 (linux-arm64) (push) Failing after 13m7s
Test Module Build / Nginx 1.24.0 (linux-arm64) (push) Failing after 12m1s
Test Module Build / Nginx 1.24.0 (linux-armv7) (push) Failing after 11m56s
Test Module Build / Nginx 1.24.0 (linux-x86_64) (push) Failing after 9m46s
Test Module Build / Nginx 1.26.3 (linux-arm64) (push) Failing after 12m1s
Test Module Build / Nginx 1.26.3 (linux-armv7) (push) Failing after 12m1s
Test Module Build / Nginx 1.26.3 (linux-x86_64) (push) Failing after 9m46s
Test Module Build / Nginx 1.28.0 (linux-arm64) (push) Failing after 12m1s
Test Module Build / Nginx 1.28.0 (linux-armv7) (push) Failing after 12m0s
Test Module Build / Nginx 1.28.0 (linux-x86_64) (push) Failing after 9m45s
Test Module Build / Nginx 1.30.0 (linux-arm64) (push) Failing after 12m0s
Test Module Build / Nginx 1.30.0 (linux-armv7) (push) Failing after 12m1s
Test Module Build / Nginx 1.30.0 (linux-x86_64) (push) Failing after 9m45s
Test Module Build / Distro package compatibility (debian:12) (push) Failing after 6s
Test Module Build / Distro package compatibility (debian:13) (push) Failing after 8s
Test Module Build / Distro package compatibility (ubuntu:24.04) (push) Failing after 6s
2026-06-10 02:26:50 +03:30
256b21e25b chore: add error handling and cc-opt injection to build scripts 2026-06-09 22:45:32 +03:30
058d835f07 chore: refactor release workflow for modular distro and upstream builds 2026-06-09 21:20:20 +03:30
f97ac2f7c5 Implemented Bionic/libc compatibility. 2026-05-12 19:12:02 +03:30
d8dfe1559d Fixe action fails. 2026-05-12 16:56:54 +03:30
21edc39155 implement building for ARM platforms 2026-05-12 16:48:41 +03:30
f9e5b65d39 Merge remote-tracking branch 'origin/main' 2026-05-12 16:23:29 +03:30
564d5afbfb Updated skills/ngx-http-monitoring-client/SKILL.md:45 with API response structures. 2026-05-12 16:23:04 +03:30
Meghdad
ef77980d1c Create LICENSE 2026-05-11 17:20:45 +03:30
b6501f14b0 add another version to "nginx_versions" 2026-05-08 01:54:54 +03:30
18 changed files with 2263 additions and 253 deletions

View File

@@ -3,10 +3,18 @@ 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: "1.24.0 1.26.3 1.28.0"
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
@@ -17,7 +25,7 @@ name: Build and Release Module
- minor
- major
push_image:
description: "Push 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
@@ -27,8 +35,13 @@ permissions:
packages: write
env:
DEFAULT_NGINX_VERSIONS: "1.24.0 1.26.3 1.28.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: >-
--with-compat
--with-http_ssl_module
@@ -39,8 +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: 240
steps:
- name: Check out repository
@@ -112,136 +126,247 @@ jobs:
echo "Latest tag: $latest_tag"
echo "Next tag: $next_tag"
- name: Install build dependencies
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
curl \
file \
libpcre2-dev \
libssl-dev \
zlib1g-dev
- name: Build release packages
- name: Normalize target architectures
id: targets
shell: bash
env:
INPUT_NGINX_VERSIONS: ${{ github.event.inputs.nginx_versions }}
INPUT_TARGET_ARCHES: ${{ github.event.inputs.target_arches }}
run: |
set -euo pipefail
versions="${INPUT_NGINX_VERSIONS:-$DEFAULT_NGINX_VERSIONS}"
mkdir -p "$GITHUB_WORKSPACE/dist" "$GITHUB_WORKSPACE/.release-work"
raw_targets="${INPUT_TARGET_ARCHES:-$DEFAULT_TARGET_ARCHES}"
normalized_targets=""
docker_platforms=""
for nginx_version in $versions; do
workdir="$GITHUB_WORKSPACE/.release-work/nginx-$nginx_version"
archive="nginx-$nginx_version.tar.gz"
add_target() {
local target_arch="$1"
local docker_platform="$2"
curl -fsSLo "$GITHUB_WORKSPACE/.release-work/$archive" \
"https://nginx.org/download/$archive"
tar -xzf "$GITHUB_WORKSPACE/.release-work/$archive" \
-C "$GITHUB_WORKSPACE/.release-work"
case " $normalized_targets " in
*" $target_arch "*)
return
;;
esac
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="$GITHUB_WORKSPACE"
normalized_targets="${normalized_targets}${normalized_targets:+ }${target_arch}"
docker_platforms="${docker_platforms}${docker_platforms:+,}${docker_platform}"
}
make -j"$(nproc)" modules
package="${MODULE_NAME}-${NEXT_VERSION}-${NEXT_TAG}-nginx-${nginx_version}-linux-x86_64"
package_dir="$GITHUB_WORKSPACE/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 "$GITHUB_WORKSPACE/examples/nginx.conf" "$package_dir/examples/nginx.conf"
cp "$GITHUB_WORKSPACE/examples/monitoring-site.conf" "$package_dir/examples/monitoring-site.conf"
cp "$GITHUB_WORKSPACE/README.md" "$package_dir/README.md"
cp "$GITHUB_WORKSPACE/docs/API.md" "$package_dir/docs/API.md"
cp "$GITHUB_WORKSPACE/docs/CONFIGURATION.md" "$package_dir/docs/CONFIGURATION.md"
cp "$GITHUB_WORKSPACE/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=linux-x86_64\n'
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 'This package contains a prebuilt Linux x86_64 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 22.04. ' "$nginx_version"
printf 'For safest production use, run the same Nginx version and a compatible Linux/glibc environment. '
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 "$GITHUB_WORKSPACE/dist/$package.tar.gz" \
-C "$GITHUB_WORKSPACE/dist" "$package"
rm -rf "$package_dir"
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
(
cd "$GITHUB_WORKSPACE/dist"
sha256sum *.tar.gz > SHA256SUMS.txt
)
if [[ -z "$normalized_targets" ]]; then
echo "No release target architectures were selected" >&2
exit 1
fi
{
printf '# ngx_http_monitoring_module %s\n\n' "$NEXT_TAG"
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 'Prebuilt Linux x86_64 dynamic module packages are attached.\n\n'
printf 'Each tarball is built against the Nginx version 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 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
@@ -260,25 +385,43 @@ 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
- name: Build and push Docker image
- name: Compute Docker image name
if: github.event.inputs.push_image == 'true'
env:
GH_TOKEN: ${{ github.token }}
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"
docker build \
-f dockerized/Dockerfile \
-t "$image_repo:$NEXT_TAG" \
-t "$image_repo:latest" \
.
- 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 }}
echo "$GH_TOKEN" | docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin
docker push "$image_repo:$NEXT_TAG"
docker push "$image_repo:latest"
- 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

View File

@@ -13,6 +13,7 @@ permissions:
contents: read
env:
TEST_BUILD_IMAGE: ubuntu:18.04
NGINX_CONFIGURE_ARGS: >-
--with-compat
--with-http_ssl_module
@@ -23,27 +24,60 @@ env:
jobs:
build-and-configure:
name: Nginx ${{ matrix.nginx-version }}
name: Nginx ${{ matrix.nginx-version }} (${{ matrix.target.name }})
runs-on: ubuntu-22.04
timeout-minutes: 30
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
nginx-version:
- "1.22.1"
- "1.24.0"
- "1.26.3"
- "1.28.0"
- "1.30.0"
target:
- name: linux-x86_64
platform: linux/amd64
- name: linux-arm64
platform: linux/arm64
- name: linux-armv7
platform: linux/arm/v7
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install build dependencies
- name: Set up QEMU for ARM tests
uses: docker/setup-qemu-action@v3
with:
platforms: arm64,arm
- name: Build, configure, and test routes
shell: bash
env:
NGINX_VERSION: ${{ matrix.nginx-version }}
TARGET_ARCH: ${{ matrix.target.name }}
DOCKER_PLATFORM: ${{ matrix.target.platform }}
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
docker run --rm -i \
--platform "$DOCKER_PLATFORM" \
-e NGINX_VERSION \
-e TARGET_ARCH \
-e NGINX_CONFIGURE_ARGS \
-v "$GITHUB_WORKSPACE:/work" \
-w /work \
"$TEST_BUILD_IMAGE" \
bash -s <<'CI_SCRIPT'
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
curl \
@@ -52,26 +86,32 @@ jobs:
libpcre2-dev \
libssl-dev \
zlib1g-dev
rm -rf /var/lib/apt/lists/*
- name: Download Nginx source
shell: bash
env:
NGINX_VERSION: ${{ matrix.nginx-version }}
run: |
set -euo pipefail
mkdir -p "$GITHUB_WORKSPACE/.ci-nginx"
curl -fsSLo "$GITHUB_WORKSPACE/.ci-nginx/nginx-${NGINX_VERSION}.tar.gz" \
"https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz"
tar -xzf "$GITHUB_WORKSPACE/.ci-nginx/nginx-${NGINX_VERSION}.tar.gz" \
-C "$GITHUB_WORKSPACE/.ci-nginx"
build_jobs="$(nproc)"
if [[ "$TARGET_ARCH" != "linux-x86_64" && "$build_jobs" -gt 2 ]]; then
build_jobs=2
fi
- name: Configure Nginx with module
shell: bash
env:
NGINX_VERSION: ${{ matrix.nginx-version }}
run: |
set -euo pipefail
cd "$GITHUB_WORKSPACE/.ci-nginx/nginx-${NGINX_VERSION}"
nginx_root="/work/.ci-nginx/${TARGET_ARCH}"
runtime_root="/work/.ci-runtime/${TARGET_ARCH}/nginx-${NGINX_VERSION}"
archive="nginx-${NGINX_VERSION}.tar.gz"
archive_path="${nginx_root}/${archive}"
nginx_src="${nginx_root}/nginx-${NGINX_VERSION}"
module_path="${nginx_src}/objs/ngx_http_monitoring_module.so"
config_path="${runtime_root}/nginx.conf"
base_url="http://127.0.0.1:18080"
mkdir -p "$nginx_root"
if [[ ! -f "$archive_path" ]]; then
curl -fsSLo "$archive_path" \
"https://nginx.org/download/${archive}"
fi
rm -rf "$nginx_src" "$runtime_root"
tar -xzf "$archive_path" -C "$nginx_root"
cd "$nginx_src"
./configure \
--prefix=/usr/local/nginx \
--sbin-path=/usr/local/sbin/nginx \
@@ -87,58 +127,39 @@ jobs:
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \
$NGINX_CONFIGURE_ARGS \
--add-dynamic-module="$GITHUB_WORKSPACE"
--add-dynamic-module=/work
- name: Build Nginx binary and dynamic module
shell: bash
env:
NGINX_VERSION: ${{ matrix.nginx-version }}
run: |
set -euo pipefail
cd "$GITHUB_WORKSPACE/.ci-nginx/nginx-${NGINX_VERSION}"
make -j"$(nproc)"
make -j"$(nproc)" modules
make -j"$build_jobs"
make -j"$build_jobs" modules
test -x objs/nginx
test -f objs/ngx_http_monitoring_module.so
test -f "$module_path"
file objs/nginx
file objs/ngx_http_monitoring_module.so
- name: Validate generated Nginx configuration
shell: bash
env:
NGINX_VERSION: ${{ matrix.nginx-version }}
run: |
set -euo pipefail
nginx_src="$GITHUB_WORKSPACE/.ci-nginx/nginx-${NGINX_VERSION}"
run_root="$GITHUB_WORKSPACE/.ci-runtime/nginx-${NGINX_VERSION}"
module_path="$nginx_src/objs/ngx_http_monitoring_module.so"
config_path="$run_root/nginx.conf"
file "$module_path"
mkdir -p \
"$run_root/logs" \
"$run_root/temp/client_body" \
"$run_root/temp/proxy" \
"$run_root/temp/fastcgi" \
"$run_root/temp/uwsgi" \
"$run_root/temp/scgi"
"$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' "$run_root"
printf 'pid %s/logs/nginx.pid;\n\n' "$run_root"
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' "$run_root"
printf ' proxy_temp_path %s/temp/proxy;\n' "$run_root"
printf ' fastcgi_temp_path %s/temp/fastcgi;\n' "$run_root"
printf ' uwsgi_temp_path %s/temp/uwsgi;\n' "$run_root"
printf ' scgi_temp_path %s/temp/scgi;\n\n' "$run_root"
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'
@@ -168,22 +189,14 @@ jobs:
printf '}\n'
} > "$config_path"
"$nginx_src/objs/nginx" -t -p "$run_root" -c "$config_path"
"$nginx_src/objs/nginx" -t -p "$runtime_root" -c "$config_path"
- name: Run Nginx and test monitoring routes
shell: bash
env:
NGINX_VERSION: ${{ matrix.nginx-version }}
run: |
set -euo pipefail
cleanup() {
"$nginx_src/objs/nginx" -p "$runtime_root" -c "$config_path" -s quit || true
}
trap cleanup EXIT
nginx_src="$GITHUB_WORKSPACE/.ci-nginx/nginx-${NGINX_VERSION}"
run_root="$GITHUB_WORKSPACE/.ci-runtime/nginx-${NGINX_VERSION}"
config_path="$run_root/nginx.conf"
base_url="http://127.0.0.1:18080"
"$nginx_src/objs/nginx" -p "$run_root" -c "$config_path"
trap '"$nginx_src/objs/nginx" -p "$run_root" -c "$config_path" -s quit || true' EXIT
"$nginx_src/objs/nginx" -p "$runtime_root" -c "$config_path"
for attempt in $(seq 1 50); do
if curl -fsS "$base_url/" >/dev/null; then
@@ -241,13 +254,77 @@ jobs:
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: {'
CI_SCRIPT
- name: Upload failed nginx logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: nginx-${{ matrix.nginx-version }}-test-logs
name: nginx-${{ matrix.nginx-version }}-${{ matrix.target.name }}-test-logs
path: |
.ci-runtime/nginx-${{ matrix.nginx-version }}/logs/*.log
.ci-runtime/nginx-${{ matrix.nginx-version }}/nginx.conf
.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

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Meghdad
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -36,6 +36,8 @@ The compiled module is written to:
build/ngx_http_monitoring_module.so
```
The source avoids glibc-only mount parsing APIs such as `getmntent()` and parses `/proc/mounts` directly, which keeps the collectors portable across Linux libc implementations that expose the required `/proc`, `statvfs()`, and `getifaddrs()` interfaces.
## Docker Image
A Dockerized Nginx image is available in [dockerized](</dockerized>):
@@ -57,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 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, 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,7 +1,7 @@
ARG DEBIAN_VERSION=bookworm
ARG BASE_IMAGE=debian:bookworm-slim
ARG NGINX_VERSION=1.28.0
FROM debian:${DEBIAN_VERSION}-slim AS builder
FROM ${BASE_IMAGE} AS builder
ARG NGINX_VERSION
ARG 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"
@@ -48,9 +48,10 @@ RUN ./configure \
&& cp objs/ngx_http_monitoring_module.so /usr/local/nginx/modules/
FROM debian:${DEBIAN_VERSION}-slim AS runtime
FROM ${BASE_IMAGE} AS runtime
ARG NGINX_VERSION
ARG OPENSSL_RUNTIME_PACKAGE=libssl3
LABEL org.opencontainers.image.title="ngx_http_monitoring_module nginx image" \
org.opencontainers.image.description="Nginx built with ngx_http_monitoring_module and embedded monitoring dashboard" \
@@ -60,7 +61,7 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
libpcre2-8-0 \
libssl3 \
${OPENSSL_RUNTIME_PACKAGE} \
zlib1g \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd --system --gid 101 nginx \

View File

@@ -22,6 +22,12 @@ To choose a different Nginx version:
NGINX_VERSION=1.28.0 sh dockerized/build.sh
```
To build the image on an Ubuntu Bionic 18.04 libc baseline:
```sh
BASE_IMAGE=ubuntu:18.04 OPENSSL_RUNTIME_PACKAGE=libssl1.1 sh dockerized/build.sh
```
## Run
```sh

View File

@@ -3,11 +3,15 @@ set -eu
IMAGE="${IMAGE:-ngx-http-monitoring-module:local}"
NGINX_VERSION="${NGINX_VERSION:-1.28.0}"
BASE_IMAGE="${BASE_IMAGE:-debian:bookworm-slim}"
OPENSSL_RUNTIME_PACKAGE="${OPENSSL_RUNTIME_PACKAGE:-libssl3}"
cd "$(dirname "$0")/.."
docker build \
--build-arg "NGINX_VERSION=$NGINX_VERSION" \
--build-arg "BASE_IMAGE=$BASE_IMAGE" \
--build-arg "OPENSSL_RUNTIME_PACKAGE=$OPENSSL_RUNTIME_PACKAGE" \
-f dockerized/Dockerfile \
-t "$IMAGE" \
.

View File

@@ -4,7 +4,9 @@ services:
context: ..
dockerfile: dockerized/Dockerfile
args:
BASE_IMAGE: "debian:bookworm-slim"
NGINX_VERSION: "1.28.0"
OPENSSL_RUNTIME_PACKAGE: "libssl3"
image: ngx-http-monitoring-module:local
container_name: ngx-http-monitoring-module
ports:

View File

@@ -58,3 +58,5 @@ Upstream metrics are passively observed from Nginx upstream state recorded on pr
## Compatibility
The module is designed as an Nginx dynamic HTTP module and should be built with `--with-compat` against the target Nginx source tree. Stub-status counters are compiled in when the target Nginx build includes `--with-http_stub_status_module`; otherwise request counters still work but low-level active/reading/writing/waiting counters remain zero.
Collectors avoid glibc-only helpers such as `getmntent()` and parse `/proc/mounts` directly. This keeps filesystem collection compatible with libc implementations that do not ship `<mntent.h>`, while still requiring Linux `/proc`, `statvfs()`, and `getifaddrs()`.

View File

@@ -1,60 +1,165 @@
# Release Builds
The GitHub Actions workflow in `.github/workflows/release.yml` builds ready-to-use Linux x86_64 dynamic module packages.
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`
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 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.24.0
- Nginx 1.26.3
- Nginx 1.28.0
Each release asset is named like:
Manual fallback asset:
```text
ngx_http_monitoring_module-1.0.0-v1.0.0-nginx-1.28.0-linux-x86_64.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
```
## Compatibility Warning
Nginx dynamic modules are ABI-sensitive. The release packages are built with `--with-compat` on Ubuntu 22.04 against the Nginx version in the filename. Use the matching Nginx version and a compatible Linux/glibc runtime. 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,595 @@
#!/usr/bin/env bash
set -Eeuo 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
on_error() {
local status="$?"
local line="${BASH_LINENO[0]:-unknown}"
echo "::error::distro release build failed at line ${line} with exit code ${status}" >&2
if [[ -n "${WORK_ROOT:-}" && -d "$WORK_ROOT" ]]; then
find "$WORK_ROOT" -path '*/objs/autoconf.err' -type f -print -exec sed -n '1,160p' {} \; >&2 || true
find "$WORK_ROOT" -path '*/objs/*.log' -type f -print -exec sed -n '1,160p' {} \; >&2 || true
fi
exit "$status"
}
trap on_error ERR
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"
modules_path="$(python3 /work/scripts/nginx-build-info.py --modules-path)"
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}\\\""
python3 /work/scripts/nginx-build-info.py \
--nginx-src "$nginx_src" \
--extra-cc-opt "$module_cflags" \
--json > "$nginx_build_info"
configure_line="$(python3 /work/scripts/nginx-build-info.py \
--nginx-src "$nginx_src" \
--extra-cc-opt "$module_cflags" \
--configure-line)"
cd "$nginx_src"
eval "./configure ${configure_line} --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
if [[ -n "$nginx_abi" ]]; then
deb_depends_json="$(
python3 -c 'import json, sys; print(json.dumps(sys.argv[1:]))' \
"$nginx_abi" \
"$nginx_binary_package (= $nginx_binary_version)" \
"libc6"
)"
else
deb_depends_json="$(
python3 -c 'import json, sys; print(json.dumps(sys.argv[1:]))' \
"$nginx_binary_package (= $nginx_binary_version)" \
"libc6"
)"
fi
compatibility_json="$metadata_dir/compatibility.json"
COMPATIBILITY_JSON="$compatibility_json" \
NGINX_BUILD_INFO="$nginx_build_info" \
SCHEMA=1 \
MODULE_NAME="$MODULE_NAME" \
DEB_PACKAGE_NAME="$DEB_PACKAGE_NAME" \
DEB_VERSION="$deb_version" \
NEXT_TAG="$NEXT_TAG" \
NEXT_VERSION="$NEXT_VERSION" \
LATEST_TAG="$LATEST_TAG" \
GITHUB_REF="$GITHUB_REF" \
GITHUB_SHA="$GITHUB_SHA" \
DISTRO_IMAGE="$DISTRO_IMAGE" \
DOCKER_PLATFORM="$DOCKER_PLATFORM" \
TARGET_ARCH="$TARGET_ARCH" \
OS_ID="$distro_id" \
OS_VERSION="$distro_version" \
OS_CODENAME="$distro_codename" \
DEB_ARCH="$deb_arch" \
NGINX_BINARY_PACKAGE="$nginx_binary_package" \
NGINX_BINARY_VERSION="$nginx_binary_version" \
NGINX_SOURCE_PACKAGE="$source_package" \
NGINX_SOURCE_VERSION="$source_version" \
NGINX_ABI="$nginx_abi" \
MODULES_PATH="$modules_path" \
DSC_PATH="$(basename "$dsc_path")" \
DSC_SHA256="$dsc_sha256" \
MODULE_SHA256="$module_sha256" \
BUILT_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
ARTIFACT_BASE="$artifact_base" \
DEB_FILE="$deb_file" \
TARBALL_FILE="$tarball_file" \
DEB_DEPENDS_JSON="$deb_depends_json" \
python3 - <<'PY'
import json
import os
with open(os.environ["NGINX_BUILD_INFO"], "r", encoding="utf-8") as f:
nginx_build = json.load(f)
artifact_base = os.environ["ARTIFACT_BASE"]
data = {
"schema": int(os.environ["SCHEMA"]),
"module": os.environ["MODULE_NAME"],
"release": {
"tag": os.environ["NEXT_TAG"],
"version": os.environ["NEXT_VERSION"],
"previous_tag": os.environ["LATEST_TAG"],
"git_ref": os.environ["GITHUB_REF"],
"git_sha": os.environ["GITHUB_SHA"],
},
"target": {
"os_id": os.environ["OS_ID"],
"os_version": os.environ["OS_VERSION"],
"os_codename": os.environ["OS_CODENAME"],
"architecture": os.environ["DEB_ARCH"],
"requested_target": os.environ["TARGET_ARCH"],
"docker_image": os.environ["DISTRO_IMAGE"],
"docker_platform": os.environ["DOCKER_PLATFORM"],
},
"nginx": {
"binary_package": os.environ["NGINX_BINARY_PACKAGE"],
"binary_version": os.environ["NGINX_BINARY_VERSION"],
"source_package": os.environ["NGINX_SOURCE_PACKAGE"],
"source_version": os.environ["NGINX_SOURCE_VERSION"],
"abi": os.environ["NGINX_ABI"],
"modules_path": os.environ["MODULES_PATH"],
"build": nginx_build,
},
"build": {
"method": "distro-source-package",
"dsc_file": os.environ["DSC_PATH"],
"dsc_sha256": os.environ["DSC_SHA256"],
"module_sha256": os.environ["MODULE_SHA256"],
"built_at": os.environ["BUILT_AT"],
},
"artifacts": {
"base_name": artifact_base,
"deb_package": os.environ["DEB_PACKAGE_NAME"],
"deb_version": os.environ["DEB_VERSION"],
"deb_file": os.environ["DEB_FILE"],
"tarball_file": os.environ["TARBALL_FILE"],
"compatibility_file": artifact_base + ".compatibility.json",
},
"packaging": {
"deb_depends": json.loads(os.environ["DEB_DEPENDS_JSON"]),
},
"validation": {
"packaged_nginx_config_test": False,
"packaged_nginx_endpoint_smoke": False,
},
}
with open(os.environ["COMPATIBILITY_JSON"], "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, sort_keys=True)
f.write("\n")
PY
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"
COMPATIBILITY_JSON="$compatibility_json" \
DEB_FILE="$deb_file" \
VALIDATION_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
python3 - <<'PY'
import json
import os
path = os.environ["COMPATIBILITY_JSON"]
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
data["artifacts"]["deb_file"] = os.environ["DEB_FILE"]
data["validation"]["packaged_nginx_config_test"] = True
data["validation"]["packaged_nginx_endpoint_smoke"] = True
data["validation"]["validated_at"] = os.environ["VALIDATION_TIME"]
tmp = path + ".tmp"
with open(tmp, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, sort_keys=True)
f.write("\n")
os.replace(tmp, path)
PY
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

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

@@ -0,0 +1,150 @@
#!/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 add_extra_cc_opt(args, extra_cc_opt):
if not extra_cc_opt:
return args
rewritten = []
found = False
for arg in args:
if arg.startswith("--with-cc-opt="):
found = True
rewritten.append(arg + " " + extra_cc_opt)
else:
rewritten.append(arg)
if not found:
rewritten.append("--with-cc-opt=" + extra_cc_opt)
return rewritten
def rewrite_configure_args(args, nginx_src, extra_cc_opt):
if not nginx_src:
return add_extra_cc_opt(args, extra_cc_opt)
rewritten = [rewrite_module_path(arg, nginx_src) for arg in args]
return add_extra_cc_opt(rewritten, extra_cc_opt)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--nginx-bin", default="nginx")
parser.add_argument("--nginx-src", default="")
parser.add_argument("--extra-cc-opt", 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,
args.extra_cc_opt,
)
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

@@ -1,6 +1,6 @@
---
name: ngx-http-monitoring-client
description: Use this skill when connecting clients or agents to an ngx_http_monitoring_module service, querying its JSON/SSE/Prometheus endpoints, debugging access problems, or integrating dashboards/health checks. Supports deployments protected by Nginx Basic Auth and/or monitor_api_token.
description: Use this skill when connecting clients or agents to an ngx_http_monitoring_module service, querying its JSON/SSE/Prometheus endpoints, reading API response structures, debugging access problems, or integrating dashboards/health checks. Supports deployments protected by Nginx Basic Auth and/or monitor_api_token.
---
# ngx_http_monitoring_module Client
@@ -42,6 +42,400 @@ MONITOR_BASIC_AUTH=user:password
- `GET /monitor/metrics` - Prometheus text format
- `GET /monitor/health` - lightweight JSON health check
## API Structures
All JSON API responses are compact, versioned, and timestamped. Treat unknown fields as forward-compatible additions, and treat missing sections as disabled collectors, unsupported Nginx build options, or unavailable Linux `/proc` data.
Common envelope:
```json
{
"version": 1,
"module": "1.0.0",
"timestamp": 1710000000,
"msec": 123,
"scope": "full",
"pid": 12345
}
```
Endpoint-specific API routes such as `/monitor/api/system` return the common envelope plus one top-level field matching the route. The examples below show that section's value unless explicitly shown as a complete response.
`GET /monitor/api` returns the envelope plus:
```json
{
"system": {},
"nginx": {},
"network": {},
"disk": {},
"processes": {},
"upstreams": [],
"connections": {},
"requests": {},
"history": []
}
```
`GET /monitor/api/system`:
```json
{
"cpu": {
"usage": 12.5,
"cores": 8,
"load": [0.12, 0.20, 0.30]
},
"memory": {
"total": 16777216,
"available": 8388608,
"free": 4194304,
"used": 8388608,
"used_pct": 50.0
},
"swap": {
"total": 2097152,
"free": 1048576,
"used_pct": 50.0
},
"uptime": 123456
}
```
`GET /monitor/api/nginx`:
```json
{
"connections": {
"accepted": 1000,
"handled": 1000,
"active": 20,
"reading": 1,
"writing": 4,
"waiting": 15
},
"requests": {
"total": 50000,
"responses": 49990,
"requests_per_sec": 125.5,
"responses_per_sec": 125.3
},
"ssl": {
"requests": 100,
"handshakes": 20
},
"keepalive": {
"requests": 250
},
"workers": []
}
```
`GET /monitor/api/network`:
```json
{
"rx_bytes": 123456789,
"tx_bytes": 987654321,
"rx_packets": 10000,
"tx_packets": 12000,
"rx_errors": 0,
"tx_errors": 0,
"interfaces": [
{
"name": "eth0",
"rx_bytes": 123456789,
"tx_bytes": 987654321,
"rx_packets": 10000,
"tx_packets": 12000,
"rx_errors": 0,
"tx_errors": 0,
"up": true
}
]
}
```
`GET /monitor/api/disk`:
```json
{
"reads": 1000,
"writes": 2000,
"read_bytes": 4096000,
"write_bytes": 8192000,
"devices": [
{
"name": "sda",
"reads": 1000,
"writes": 2000,
"read_bytes": 4096000,
"write_bytes": 8192000,
"io_ms": 120
}
],
"filesystems": [
{
"path": "/",
"type": "ext4",
"total": 107374182400,
"used": 53687091200,
"free": 53687091200,
"avail": 53687091200,
"files": 1000000,
"files_free": 750000
}
]
}
```
`GET /monitor/api/processes`:
```json
{
"process_count": 140,
"tcp": {
"established": 20,
"listen": 8
},
"sockets": {
"used": 512,
"tcp": 120,
"udp": 16
},
"workers": []
}
```
`GET /monitor/api/connections`:
```json
{
"accepted": 1000,
"handled": 1000,
"active": 20,
"reading": 1,
"writing": 4,
"waiting": 15,
"ssl_requests": 100,
"ssl_handshakes": 20,
"keepalive_requests": 250,
"sse_clients": 2,
"sse_events": 500,
"rate_limited": 0
}
```
`GET /monitor/api/requests`:
```json
{
"total": 50000,
"responses": 49990,
"bytes": 104857600,
"requests_per_sec": 125.5,
"responses_per_sec": 125.3,
"error_rate": 0.0078,
"latency": {
"avg": 8.4,
"p50": 4,
"p90": 15,
"p95": 22,
"p99": 80
},
"status": {
"1xx": 0,
"2xx": 49000,
"3xx": 600,
"4xx": 300,
"5xx": 90
},
"methods": {
"GET": 45000,
"POST": 4000,
"PUT": 100,
"DELETE": 50,
"HEAD": 500,
"OPTIONS": 200,
"PATCH": 50,
"OTHER": 100
},
"latency_histogram": [
{
"le": 1,
"count": 100
},
{
"le": "+Inf",
"count": 50000
}
],
"size_histogram": [
{
"le": 512,
"count": 10000
},
{
"le": "+Inf",
"count": 50000
}
],
"top_urls": [],
"user_agents": []
}
```
`GET /monitor/api/upstreams`:
```json
[
{
"peer": "127.0.0.1:9000",
"requests": 1000,
"failures": 2,
"status_4xx": 10,
"status_5xx": 2,
"avg_latency": 12.5,
"last_seen": 1710000000
}
]
```
History samples in `GET /monitor/api`:
```json
[
{
"timestamp": 1710000000,
"cpu": 12.5,
"memory": 50.0,
"swap": 0.0,
"rps": 125.5,
"responses_per_sec": 125.3,
"network_rx_per_sec": 1024,
"network_tx_per_sec": 2048,
"disk_read_per_sec": 0,
"disk_write_per_sec": 4096,
"latency_p95": 22,
"requests_total": 50000,
"status_4xx": 300,
"status_5xx": 90
}
]
```
Shared nested structures:
```json
{
"worker": {
"slot": 0,
"pid": 12345,
"active": true,
"requests": 10000,
"bytes": 10485760,
"errors": 3,
"vm_size": 102400,
"vm_rss": 20480,
"voluntary_ctxt": 100,
"nonvoluntary_ctxt": 5,
"last_seen": 1710000000
},
"top_url": {
"url": "/api/orders",
"hits": 1000,
"errors": 3,
"bytes": 1048576,
"avg_latency": 7.8,
"last_seen": 1710000000
},
"user_agent": {
"user_agent": "curl/8.0.1",
"hits": 100,
"errors": 0,
"bytes": 20480,
"avg_latency": 2.1,
"last_seen": 1710000000
}
}
```
SSE `GET /monitor/live` sends `metrics` events whose `data` payload has this structure:
```json
{
"version": 1,
"timestamp": 1710000000,
"sequence": 42,
"system": {
"cpu": {
"usage": 12.5,
"cores": 8
},
"memory": {
"used_pct": 50.0,
"total": 16777216,
"available": 8388608
}
},
"requests": {
"total": 50000,
"requests_per_sec": 125.5,
"responses_per_sec": 125.3,
"latency": {
"p95": 22,
"p99": 80
},
"status": {
"4xx": 300,
"5xx": 90
}
},
"connections": {
"active": 20,
"reading": 1,
"writing": 4,
"waiting": 15,
"sse_clients": 2,
"keepalive_requests": 250
},
"network": {
"rx_bytes": 123456789,
"tx_bytes": 987654321
},
"disk": {
"read_bytes": 4096000,
"write_bytes": 8192000
}
}
```
`GET /monitor/metrics` exposes Prometheus text metrics including:
```text
nginx_monitor_requests_total
nginx_monitor_active_connections
nginx_monitor_cpu_usage_ratio
nginx_monitor_memory_used_ratio
nginx_monitor_latency_p95_ms
```
`GET /monitor/health` returns a complete health response:
```json
{
"version": 1,
"module": "1.0.0",
"timestamp": 1710000000,
"msec": 123,
"scope": "health",
"pid": 12345,
"status": "ok",
"generation": 100,
"sse_clients": 2
}
```
## Auth Rules
If the service is behind Nginx Basic Auth, include Basic Auth on every request, including `/monitor`, `/monitor/live`, and `/monitor/metrics`.

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

@@ -5,7 +5,6 @@
#include <errno.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <mntent.h>
#include <net/if.h>
#include <stdio.h>
#include <string.h>
@@ -41,6 +40,9 @@ static ngx_atomic_uint_t ngx_http_monitoring_sum_disk(
static ngx_atomic_uint_t ngx_http_monitoring_delta(ngx_atomic_uint_t now,
ngx_atomic_uint_t prev);
static ngx_uint_t ngx_http_monitoring_skip_fs(const char *type);
static char *ngx_http_monitoring_next_mount_field(char **cursor);
static void ngx_http_monitoring_copy_mount_field(u_char *dst, size_t size,
char *src);
ngx_int_t
@@ -599,25 +601,45 @@ static void
ngx_http_monitoring_collect_filesystems(ngx_http_monitoring_shctx_t *sh)
{
FILE *fp;
struct mntent *mnt;
char line[4096], *cursor, *source, *mount, *type;
u_char mount_path[4096], fs_type[NGX_HTTP_MONITORING_NAME_LEN];
struct statvfs st;
ngx_uint_t count;
uint64_t total, free_bytes, avail, used;
fp = setmntent("/proc/mounts", "r");
fp = fopen("/proc/mounts", "r");
if (fp == NULL) {
return;
}
count = 0;
while ((mnt = getmntent(fp)) != NULL
&& count < NGX_HTTP_MONITORING_FILESYSTEMS_MAX)
while (count < NGX_HTTP_MONITORING_FILESYSTEMS_MAX
&& fgets(line, sizeof(line), fp) != NULL)
{
if (ngx_http_monitoring_skip_fs(mnt->mnt_type)) {
cursor = line;
source = ngx_http_monitoring_next_mount_field(&cursor);
mount = ngx_http_monitoring_next_mount_field(&cursor);
type = ngx_http_monitoring_next_mount_field(&cursor);
if (source == NULL || mount == NULL || type == NULL) {
continue;
}
if (statvfs(mnt->mnt_dir, &st) != 0 || st.f_blocks == 0) {
(void) source;
ngx_http_monitoring_copy_mount_field(mount_path, sizeof(mount_path),
mount);
ngx_http_monitoring_copy_mount_field(fs_type, sizeof(fs_type), type);
if (mount_path[0] == '\0' || fs_type[0] == '\0') {
continue;
}
if (ngx_http_monitoring_skip_fs((char *) fs_type)) {
continue;
}
if (statvfs((char *) mount_path, &st) != 0 || st.f_blocks == 0) {
continue;
}
@@ -626,9 +648,9 @@ ngx_http_monitoring_collect_filesystems(ngx_http_monitoring_shctx_t *sh)
avail = (uint64_t) st.f_bavail * st.f_frsize;
used = total - free_bytes;
ngx_cpystrn(sh->filesystems[count].path, (u_char *) mnt->mnt_dir,
ngx_cpystrn(sh->filesystems[count].path, mount_path,
NGX_HTTP_MONITORING_KEY_LEN);
ngx_cpystrn(sh->filesystems[count].type, (u_char *) mnt->mnt_type,
ngx_cpystrn(sh->filesystems[count].type, fs_type,
NGX_HTTP_MONITORING_NAME_LEN);
sh->filesystems[count].total = (ngx_atomic_uint_t) total;
sh->filesystems[count].used = (ngx_atomic_uint_t) used;
@@ -640,7 +662,7 @@ ngx_http_monitoring_collect_filesystems(ngx_http_monitoring_shctx_t *sh)
count++;
}
endmntent(fp);
fclose(fp);
sh->fs_count = count;
}
@@ -822,6 +844,72 @@ ngx_http_monitoring_delta(ngx_atomic_uint_t now, ngx_atomic_uint_t prev)
}
static char *
ngx_http_monitoring_next_mount_field(char **cursor)
{
char *p, *start;
p = *cursor;
while (*p == ' ' || *p == '\t') {
p++;
}
if (*p == '\0' || *p == '\n') {
*cursor = p;
return NULL;
}
start = p;
while (*p != '\0' && *p != '\n' && *p != ' ' && *p != '\t') {
p++;
}
if (*p != '\0') {
*p++ = '\0';
}
*cursor = p;
return start;
}
static void
ngx_http_monitoring_copy_mount_field(u_char *dst, size_t size, char *src)
{
u_char *p, *last;
ngx_uint_t value;
if (size == 0) {
return;
}
p = dst;
last = dst + size - 1;
while (*src != '\0' && p < last) {
if (src[0] == '\\'
&& src[1] >= '0' && src[1] <= '7'
&& src[2] >= '0' && src[2] <= '7'
&& src[3] >= '0' && src[3] <= '7')
{
value = (ngx_uint_t) ((src[1] - '0') * 64
+ (src[2] - '0') * 8
+ (src[3] - '0'));
*p++ = (u_char) value;
src += 4;
continue;
}
*p++ = (u_char) *src++;
}
*p = '\0';
}
static ngx_uint_t
ngx_http_monitoring_skip_fs(const char *type)
{

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;