#!/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 'Nginx Monitor' 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