Files
nginx-custom/freenginx.sh
T
2026-10-29 23:39:29 -05:00

272 lines
9.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# Build freenginx from source with bundled zlib/PCRE2/OpenSSL and third-party modules.
# Installing is left to you: cd into the freenginx source dir and run `sudo make install`.
#
# Every setting below can be overridden from the environment, e.g.
# V_NGINX=1.31.6 BUILDROOT=/tmp/free ./freenginx.sh -v
set -Eeuo pipefail
### Versions
: "${V_NGINX:=1.31.5}"
: "${V_ZLIB:=1.3.2}"
: "${V_PCRE:=10.49}"
: "${V_QSSL:=3.6.5}"
: "${V_MAXM:=1.14.1}"
: "${V_HEAD:=0.41}"
### Build settings
: "${BUILDROOT:=$HOME/Gits/free}"
: "${JOBS:=$(nproc)}"
: "${LOG:=}" # default: $BUILDROOT/build-<version>-<timestamp>.log
: "${VERBOSITY:=1}" # 0 = quiet, 1 = steps only, 2 = everything
: "${CLEAN:=0}"
### Optional features (1 = on, 0 = off)
: "${USE_BROTLI:=1}"
: "${USE_ZSTD:=1}"
: "${USE_GEOIP2:=1}"
: "${USE_HEADERS:=1}"
: "${USE_KTLS:=1}"
: "${USE_DEBUG:=1}"
usage() {
cat <<EOF
Usage: ${0##*/} [options]
-r DIR build root (default: $BUILDROOT)
-j N parallel jobs (default: $JOBS)
-l FILE log file (default: <build root>/build-$V_NGINX-<timestamp>.log)
-q quiet: print only errors and the final summary
-v verbose: stream all build output to the terminal (still logged)
-c run 'make clean' in the freenginx tree before configuring
--purge don't build; delete this version's source and dependency folders
(run after 'sudo make install'; logs are kept)
--no-brotli | --no-zstd | --no-geoip2 | --no-headers | --no-ktls | --no-debug
disable an optional feature
-h show this help
Versions come from the top of the script or the environment:
V_NGINX V_ZLIB V_PCRE V_QSSL V_MAXM V_HEAD
EOF
}
while [ $# -gt 0 ]; do
case $1 in
-r) BUILDROOT=${2:?-r needs a directory}; shift ;;
-j) JOBS=${2:?-j needs a number}; shift ;;
-l) LOG=${2:?-l needs a file}; shift ;;
-q) VERBOSITY=0 ;;
-v) VERBOSITY=2 ;;
-c) CLEAN=1 ;;
--purge) PURGE=1 ;;
--no-brotli) USE_BROTLI=0 ;;
--no-zstd) USE_ZSTD=0 ;;
--no-geoip2) USE_GEOIP2=0 ;;
--no-headers) USE_HEADERS=0 ;;
--no-ktls) USE_KTLS=0 ;;
--no-debug) USE_DEBUG=0 ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown option: $1" >&2; usage >&2; exit 1 ;;
esac
shift
done
### Logging: everything goes to $LOG; the terminal gets steps (1) or everything (2).
log() { printf '%s\n' "$*" >>"$LOG"; }
step() {
if [ "$VERBOSITY" -ge 1 ]; then printf '==> %s\n' "$*"; fi
printf '\n==> [%s] %s\n' "$(date +%T)" "$*" >>"$LOG"
}
run() {
log "+ $*"
if [ "$VERBOSITY" -ge 2 ]; then "$@" 2>&1 | tee -a "$LOG"; else "$@" >>"$LOG" 2>&1; fi
}
on_error() {
local rc=$?
echo "!! Build failed (exit $rc, line $1). Full log: $LOG" >&2
if [ "$VERBOSITY" -lt 2 ]; then tail -n 30 "$LOG" >&2; fi
exit "$rc"
}
### Helpers
fetch() { # url dir: download and extract into $BUILDROOT unless dir already exists
local url=$1 dir=$2 file=${1##*/}
if [ -d "$dir" ]; then step "$dir (cached)"; return 0; fi
step "Downloading $dir"
run curl -fsSL -o "$file" "$url"
case $file in
*.zip) run unzip -q "$file" ;;
*) run tar -xzf "$file" ;;
esac
rm -f "$file"
}
sync_git() { # url dir: clone, or fast-forward an existing clone
if [ -d "$2/.git" ]; then
step "Updating $2"
run git -C "$2" pull --ff-only
else
step "Cloning $2"
run git clone "$1" "$2"
fi
run git -C "$2" submodule update --init --recursive --jobs "$JOBS"
}
ktls_supported() { # kernel >= 5.10
local maj min
IFS=. read -r maj min _ <<<"$(uname -r)"
[ "$maj" -gt 5 ] || { [ "$maj" -eq 5 ] && [ "$min" -ge 10 ]; }
}
build_brotli() {
local src=ngx-brotli/deps/brotli
local flags="-Ofast -m64 -march=native -mtune=native -flto -funroll-loops -ffunction-sections -fdata-sections -Wl,--gc-sections"
step "Building brotli"
run cmake -S "$src" -B "$src/out" -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF \
-DCMAKE_C_FLAGS="$flags" -DCMAKE_CXX_FLAGS="$flags" -DCMAKE_INSTALL_PREFIX=./installed
run cmake --build "$src/out" --config Release --target brotlienc -j "$JOBS"
}
install_maxmind() { # system-wide, skipped when pkg-config already reports $V_MAXM
local dir=libmaxminddb-$V_MAXM have
have=$(pkg-config --modversion libmaxminddb 2>/dev/null || true)
if [ "$have" = "$V_MAXM" ]; then step "libmaxminddb $have already installed"; return 0; fi
log "pkg-config reports libmaxminddb '${have:-none}', want $V_MAXM"
fetch "https://github.com/maxmind/libmaxminddb/releases/download/$V_MAXM/$dir.tar.gz" "$dir"
step "Installing $dir system-wide (sudo)"
cd "$dir"
run ./configure
run make -j "$JOBS"
run sudo make install
run sudo ldconfig
cd "$BUILDROOT"
}
### Setup
case $BUILDROOT in /*) ;; *) BUILDROOT=$PWD/$BUILDROOT ;; esac
if [ "${PURGE:-0}" = 1 ]; then
built=$BUILDROOT/nginx-release-$V_NGINX/objs/nginx
if [ -f "$built" ] && ! cmp -s "$built" /usr/sbin/nginx; then
echo "!! $built is not the installed /usr/sbin/nginx. Run 'sudo make install' first." >&2
exit 1
fi
echo "==> Removing freenginx $V_NGINX build folders from $BUILDROOT"
rm -rf -- "$BUILDROOT/nginx-release-$V_NGINX" "$BUILDROOT/zlib-$V_ZLIB" "$BUILDROOT/pcre2-$V_PCRE" \
"$BUILDROOT/openssl-$V_QSSL" "$BUILDROOT/libmaxminddb-$V_MAXM" "$BUILDROOT/nginx-geoip2" \
"$BUILDROOT/ngx-brotli" "$BUILDROOT/ngx-zstd" "$BUILDROOT/headers-more-nginx-module-$V_HEAD"
exit 0
fi
mkdir -p "$BUILDROOT"
: "${LOG:=$BUILDROOT/build-$V_NGINX-$(date +%Y%m%d-%H%M%S).log}"
case $LOG in /*) ;; *) LOG=$PWD/$LOG ;; esac
mkdir -p "$(dirname "$LOG")"
: >"$LOG"
trap 'on_error $LINENO' ERR
cd "$BUILDROOT"
missing=()
for t in curl tar unzip git cmake make cc pkg-config; do command -v "$t" >/dev/null || missing+=("$t"); done
if [ ${#missing[@]} -gt 0 ]; then echo "Missing required tools: ${missing[*]}" >&2; exit 1; fi
step "freenginx $V_NGINX in $BUILDROOT (log: $LOG)"
set | grep -E '^(V_[A-Z]+|USE_[A-Z0-9]+|JOBS|BUILDROOT|VERBOSITY|CLEAN)=' >>"$LOG"
### Sources
fetch "https://www.zlib.net/zlib-$V_ZLIB.tar.gz" "zlib-$V_ZLIB"
fetch "https://github.com/PCRE2Project/pcre2/releases/download/pcre2-$V_PCRE/pcre2-$V_PCRE.tar.gz" "pcre2-$V_PCRE"
fetch "https://github.com/openssl/openssl/releases/download/openssl-$V_QSSL/openssl-$V_QSSL.tar.gz" "openssl-$V_QSSL"
fetch "https://github.com/freenginx/nginx/archive/refs/tags/release-$V_NGINX.tar.gz" "nginx-release-$V_NGINX"
### Optional modules (BUILD_INFO order matches the old hardcoded string)
info=() mods=()
if [ "$USE_GEOIP2" = 1 ]; then
install_maxmind
sync_git https://github.com/leev/ngx_http_geoip2_module nginx-geoip2
info+=(GeoIP2); mods+=(--add-module=../nginx-geoip2)
fi
if [ "$USE_BROTLI" = 1 ]; then
sync_git https://github.com/google/ngx_brotli ngx-brotli
build_brotli
info+=(Brotli); mods+=(--add-module=../ngx-brotli)
fi
if [ "$USE_ZSTD" = 1 ]; then
sync_git https://github.com/HanadaLee/ngx_http_zstd_module.git ngx-zstd
info+=(Zstd); mods+=(--add-module=../ngx-zstd)
fi
info+=(H3)
if [ "$USE_HEADERS" = 1 ]; then
fetch "https://github.com/openresty/headers-more-nginx-module/archive/refs/tags/v$V_HEAD.zip" "headers-more-nginx-module-$V_HEAD"
info+=(Headers-More); mods+=(--add-module=../headers-more-nginx-module-$V_HEAD)
fi
info+=(Quantum)
if [ "$USE_DEBUG" = 1 ]; then info+=(debug); mods+=(--with-debug); fi
ktls_opt=""
if [ "$USE_KTLS" = 1 ]; then
if ktls_supported; then
step "Enabling kTLS (sudo)"
run sudo modprobe tls
ktls_opt=" enable-ktls"
info+=(KTLS)
else
step "Kernel $(uname -r) is older than 5.10, skipping kTLS"
fi
fi
build_info="w/$(IFS=,; echo "${info[*]}")"
### Configure and build
SRC=$BUILDROOT/nginx-release-$V_NGINX
cd "$SRC"
if [ "$CLEAN" = 1 ] && [ -f Makefile ]; then
step "Cleaning previous build"
run make clean
fi
step "Configuring freenginx ($build_info)"
run ./auto/configure \
--build="$build_info" \
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--modules-path=/usr/lib/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 \
--user=nginx --group=nginx \
--with-compat --with-file-aio --with-threads \
--with-http_addition_module --with-http_auth_request_module --with-http_dav_module \
--with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module \
--with-http_realip_module --with-http_secure_link_module --with-http_slice_module \
--with-http_ssl_module --with-http_stub_status_module --with-http_sub_module \
--with-http_v2_module --with-http_v3_module \
--with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module \
--with-select_module --with-poll_module \
--with-zlib="../zlib-$V_ZLIB" \
--with-pcre="../pcre2-$V_PCRE" \
--with-openssl="../openssl-$V_QSSL" \
--with-openssl-opt="no-asm no-tests enable-tls1_3$ktls_opt" \
"${mods[@]}" \
--with-cc-opt="-g -O2 -ffile-prefix-map=/data/builder/debuild/nginx-$V_NGINX/debian/debuild-base/nginx-$V_NGINX=. -flto=auto -ffat-lto-objects -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC -I../openssl-$V_QSSL/build/include" \
--with-ld-opt="-Wl,-Bsymbolic-functions -flto=auto -ffat-lto-objects -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie -L../openssl-$V_QSSL/build/lib"
step "Compiling with $JOBS jobs (this takes a while)"
run make -j "$JOBS"
### Summary (always shown, even with -q)
{
echo
"$SRC/objs/nginx" -V 2>&1
ls -lh "$SRC/objs/nginx"
echo
echo "Log: $LOG"
echo "Install: cd $SRC && sudo make install"
printf 'Total compile time: %dm %02ds\n' $((SECONDS / 60)) $((SECONDS % 60))
} | tee -a "$LOG"