commit 5606e66ba4da4d6116f17c12e042aba60d6237ef Author: Konstantin Demin Date: Tue Sep 17 14:11:00 2024 +0300 initial commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4d5ab1c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +j2cfg/__pycache__ +j2cfg/j2cfg/__pycache__ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1a63927 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.mypy_cache +/.vscode +__pycache__ +*.py[co] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c6bfa40 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,224 @@ +FROM docker.io/rockdrilla/angie-conv:v0.0.1-deps AS deps + +## --- + +FROM deps AS certs +SHELL [ "/bin/sh", "-ec" ] + +COPY /scripts/* /usr/local/sbin/ +COPY /extra-scripts/* /usr/local/sbin/ + +## consult https://github.com/certifi/python-certifi/ +ENV CERTIFI_COMMIT=bd8153872e9c6fc98f4023df9c2deaffea2fa463 + +RUN apt-install.sh ca-certificates ; \ + ## process certifi + ca_file='/etc/ssl/certs/ca-certificates.crt' ; \ + openssl-cert-fingerprint.sh "${ca_file}" | sort -uV > "${ca_file}.fp.orig" ; \ + ls -l "${ca_file}" ; \ + certifi-extras.sh ; \ + openssl-cert-fingerprint.sh "${ca_file}" | sort -uV > "${ca_file}.fp" ; \ + chmod 0444 "${ca_file}" "${ca_file}.fp" "${ca_file}.fp.orig" ; \ + ls -l "${ca_file}" "${ca_file}.fp" "${ca_file}.fp.orig" + +## --- + +FROM deps AS pycache +SHELL [ "/bin/sh", "-ec" ] + +COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ + +COPY /scripts/* /usr/local/sbin/ +COPY /extra-scripts/* /usr/local/sbin/ + +COPY /j2cfg/ /usr/local/lib/j2cfg/ + +ENV PYTHONDONTWRITEBYTECODE='' + +## Python cache preseed + +RUN python3 -m compileall -q -j 2 /usr/local/lib/j2cfg/ + +RUN libpython="${PYTHON_SITE_PACKAGES%/*}" ; \ + find "${libpython}/" -mindepth 1 -maxdepth 1 -printf '%P\0' \ + | sed -zEn \ + -e '/^(collections|importlib|json|re)$/p' \ + | sort -zV \ + | env -C "${libpython}" xargs -0r \ + python3 -m compileall -q -j 2 ; \ + find "${PYTHON_SITE_PACKAGES}/" -mindepth 1 -maxdepth 1 -printf '%P\0' \ + | sed -zE \ + -e '/\.(dist-info|pth|txt)$/d' \ + -e '/^(pip|pkg_resources|setuptools|wheel)$/d' \ + | sort -zV \ + | env -C "${PYTHON_SITE_PACKAGES}" xargs -0r \ + python3 -m compileall -q -j 2 + +## Python cache warmup +RUN j2cfg-single /usr/local/lib/j2cfg/test.j2 /tmp/test ; \ + cat /tmp/test ; echo ; echo ; \ + rm -f /tmp/test + +## Python cache adjustments +RUN d="@$(date '+%s')" ; \ + find /usr/local/lib/ -name '*.pyc' -exec touch -m -d "$d" {} + ; \ + find /usr/local/lib/ -name __pycache__ -exec touch -m -d "$d" {} + + +## --- + +FROM deps +SHELL [ "/bin/sh", "-ec" ] + +## NB: NGX_DEBUG is set via build script + +COPY /Dockerfile /usr/local/share/ + +COPY --from=certs /etc/ssl/certs/ca-certificates.* /etc/ssl/certs/ + +## RFC: Python cache +## TODO: reduce load by selecting only __pycache__ directories in either way +COPY --from=pycache /usr/local/lib/ /usr/local/lib/ + +## already copied by statement above +# COPY /j2cfg/ /usr/local/lib/j2cfg/ + +ENV ANGIE_MODULES_DIR=/usr/lib/angie/modules + +COPY /scripts/* /usr/local/bin/ + +RUN _UID=11111 _GID=11111 ; \ + echo "angie:x:${_UID}:${_GID}:Angie:/etc/angie:/bin/false" >> /etc/passwd ; \ + echo "angie:x:${_GID}:" >> /etc/group ; \ + echo 'angie:!:::::::' >> /etc/shadow + +RUN apt-install.sh angie ; \ + apt-clean.sh ; \ + ## verify Angie layout + [ -d "${ANGIE_MODULES_DIR}" ] ; \ + n='/usr/sbin/angie' ; \ + [ -x "$n-debug" ] ; \ + [ -x "$n-nodebug" ] ; \ + ## adjust Angie binaries + rm -fv "$n" ; \ + if [ "${NGX_DEBUG}" = 0 ] ; then \ + rm -fv "$n-debug" ; \ + mv -fv "$n-nodebug" "$n" ; \ + else \ + rm -fv "$n-nodebug" ; \ + mv -fv "$n-debug" "$n" ; \ + fi + +## preserve snippets from Angie config directory +## ref: https://git.angie.software/web-server/angie/src/tag/Angie-1.6.2/conf +RUN d=/etc/angie ; t=$(mktemp -d) ; \ + tar -C "$d" -cf - \ + fastcgi_params \ + fastcgi.conf \ + mime.types \ + prometheus_all.conf \ + scgi_params \ + uwsgi_params \ + | tar -C "$t" -xf - ; \ + rm -rf "$d" ; \ + install -d "$d" "$d/snip.dist" ; \ + tar -C "$t" -cf - . | tar -C "$d/snip.dist" -xf - ; \ + rm -rf "$t" + +## copy directory structure +COPY /angie/ /etc/angie/ + +## produce own layout for Angie >:) +## /angie/ is persistence store +RUN install -d -o angie -g angie -m 03777 /angie /run/angie ; \ + ## adjust paths across filesystem + rm -rfv /var/cache/angie/ /var/lib/angie/ /var/log/angie/ ; \ + ln -sv /run/angie/cache /var/cache/angie ; \ + ln -sv /run/angie/lib /var/lib/angie ; \ + ln -sv /run/angie/log /var/log/angie ; \ + ## adjust paths in config directory + cd /etc/angie || exit 1 ; \ + ln -sv /run/angie run ; \ + ln -sv /run/angie/load load ; \ + ln -sv /run/angie/lock lock ; \ + ln -sv ${ANGIE_MODULES_DIR} modules.dist ; \ + ## hyper-modular paths: + data='autoconf conf j2cfg mod modules site snip static tls' ; \ + vardata='cache lib log' ; \ + for n in ${data} ; do \ + for d in "$n" "$n.dist" ; do \ + [ -e "$d" ] || install -d "$d" ; \ + done ; \ + done ; \ + for n in ${data} ${vardata} ; do \ + ln -sv "/run/angie/$n" "$n.d" ; \ + done + +## special empty directory +RUN d='/var/lib/empty' ; \ + rm -rf "$d" ; \ + if [ -d "$d" ] ; then exit 1 ; fi ; \ + install -d -m 0555 "$d" + +## prepare DH params for TLS +## NB: disabled in pipeline for now +## reason: too slow (and too much effort) +# RUN cd /etc/angie/tls.dist || exit 1 ; \ +# openssl-generate-dh-bundle.sh + +## future quirk for angie-module-modsecurity >:) +RUN n='modsecurity' ; \ + d="/etc/angie/$n" ; \ + ln -sv "/run/angie/$n" "$d.d" ; \ + dpkg-divert --divert "$d.dist" --rename "$d" ; \ + for p in modsecurity.conf unicode.mapping ; do \ + dpkg-divert --divert "$d.dist/$p" --rename "$d/$p" ; \ + done ; \ + p='rules.conf' ; \ + dpkg-divert --divert "$d.dist/$p.dist" --rename "$d/$p" + +VOLUME [ "/run/angie" ] + +## preseed builtin modules list +RUN x='angie-builtin-modules.sh' ; \ + "$x" ; \ + rm -fv "$(which "$x")" + +## relatively lightweight modules +RUN apt-install-angie-mod.sh \ + brotli \ + cache-purge \ + echo \ + geoip2 \ + headers-more \ + subs \ + upload \ + zip \ + zstd \ + ; \ + apt-clean.sh + +## adjust permissions/ownership +RUN d='/etc/angie' ; \ + chown -hR 0:0 "$d" ; \ + find "$d/" -name .gitkeep -type f -delete ; \ + find "$d/" -type d -exec chmod 0755 {} + ; \ + find "$d/" -type f -exec chmod 0644 {} + + +## image-entry.sh is placed into /usr/local/bin/ to allow custom entrypoint/chaining: +## - there's no need to change ENTRYPOINT/CMD +## - custom entrypoint should be placed in /usr/local/sbin/ +## - custom entrypoint should "exec" /usr/local/bin/image-entry.sh +COPY /image-entry.sh /usr/local/bin/ +COPY /image-entry.d/ /image-entry.d/ + +## must be bind-mounted only for local customization/overrides! +# RUN install -d /image-entry + +## misc defaults +ENV DUMB_INIT_SETSID=0 \ + MALLOC_ARENA_MAX=4 + +STOPSIGNAL SIGQUIT + +ENTRYPOINT [ "image-entry.sh" ] +CMD [ "angie" ] diff --git a/Dockerfile.base b/Dockerfile.base new file mode 100644 index 0000000..f8ca9f4 --- /dev/null +++ b/Dockerfile.base @@ -0,0 +1,227 @@ +# FROM docker.io/debian:bookworm-slim as base-upstream +ARG PYTHONTAG=3.11.10-slim-bookworm +FROM docker.io/python:${PYTHONTAG} AS base-upstream + +FROM base-upstream AS base +SHELL [ "/bin/sh", "-ec" ] + +COPY /Dockerfile.base /usr/local/share/ + +COPY /scripts/* /usr/local/sbin/ +COPY /extra-scripts/* /usr/local/sbin/ + +## PATH: remove /sbin and /bin (/usr is merged) +ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin \ + TMPDIR=/tmp \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 \ + TERM=linux \ + TZ=Etc/UTC \ + MALLOC_ARENA_MAX=2 \ + PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 + +## local development +# ENV PIP_INDEX="http://127.0.0.1:8081/repository/proxy_pypi/pypi/" \ +# PIP_INDEX_URL="http://127.0.0.1:8081/repository/proxy_pypi/simple/" \ +# PIP_TRUSTED_HOST="localhost" + +COPY /apt/prefs.backports /etc/apt/preferences.d/backports +COPY /apt/sources.debian /etc/apt/sources.list.d/debian.sources + +## prevent services from auto-starting, part 1 +RUN s='/usr/sbin/policy-rc.d' ; b='/usr/bin/policy-rc.d' ; \ + rm -f "$s" "$b" ; \ + echo '#!/bin/sh' > "$b" ; \ + echo 'exit 101' >> "$b" ; \ + chmod 0755 "$b" ; \ + ln -s "$b" "$s" + +RUN divert_true() { divert-rm.sh "$1" ; ln -sv /bin/true "$1" ; } ; \ + ## prevent services from auto-starting, part 2 + divert_true /sbin/start-stop-daemon ; \ + ## always report that we're in chroot + divert_true /usr/bin/ischroot ; \ + ## hide systemd helpers + divert_true /usr/bin/deb-systemd-helper ; \ + divert_true /usr/bin/deb-systemd-invoke + +RUN apt-env.sh apt-get update ; \ + apt-env.sh apt-get upgrade -y ; \ + apt-clean.sh + +## perl-base: hardlink->symlink +RUN d=/usr/bin ; \ + find "$d/" -wholename "$d/perl5*" -exec ln -fsv perl {} ';' ; \ + ls -li "$d/perl"* + +## remove unwanted binaries +RUN set -f ; \ + for i in \ + addgroup \ + addpart \ + adduser \ + apt-ftparchive \ + agetty \ + badblocks \ + blkdiscard \ + blkid \ + blkzone \ + blockdev \ + bsd-write \ + chage \ + chcpu \ + chfn \ + chgpasswd \ + chmem \ + chpasswd \ + chsh \ + cpgr \ + cppw \ + ctrlaltdel \ + debugfs \ + delgroup \ + delpart \ + deluser \ + dmesg \ + dumpe2fs \ + e2freefrag \ + e2fsck \ + e2image \ + e2label \ + e2mmpstatus \ + e2scrub \ + 'e2scrub*' \ + e2undo \ + e4crypt \ + e4defrag \ + expiry \ + faillock \ + fdformat \ + fincore \ + findfs \ + fsck \ + 'fsck.*' \ + fsfreeze \ + fstrim \ + getty \ + gpasswd \ + groupadd \ + groupdel \ + groupmems \ + groupmod \ + grpck \ + grpconv \ + grpunconv \ + hwclock \ + isosize \ + last \ + lastb \ + ldattach \ + losetup \ + lsblk \ + lsirq \ + lslogins \ + mcookie \ + mesg \ + mke2fs \ + mkfs \ + 'mkfs.*' \ + mkhomedir_helper \ + mklost+found \ + mkswap \ + mount \ + newgrp \ + newusers \ + pam-auth-update \ + pam_getenv \ + pam_namespace_helper \ + pam_timestamp_check \ + partx \ + passwd \ + pivot_root \ + pwck \ + pwconv \ + pwhistory_helper \ + pwunconv \ + raw \ + readprofile \ + resize2fs \ + resizepart \ + rtcwake \ + sg \ + shadowconfig \ + su \ + sulogin \ + swaplabel \ + swapoff \ + swapon \ + switch_root \ + tune2fs \ + umount \ + unix_chkpwd \ + unix_update \ + update-passwd \ + useradd \ + userdel \ + usermod \ + utmpdump \ + vigr \ + vipw \ + wall \ + wdctl \ + wipefs \ + write \ + 'write.*' \ + zramctl \ + ; do \ + for d in /usr/sbin /usr/bin /sbin /bin ; do \ + find "$d/" ! -type d -wholename "$d/$i" \ + | while read -r p ; do \ + [ -n "$p" ] || continue ; \ + [ -e "$p" ] || continue ; \ + dpkg -S "$p" >/dev/null 2>&1 || continue ; \ + divert-rm.sh "$p" ; \ + done ; \ + done ; \ + for d in /usr/sbin /usr/bin /sbin /bin ; do \ + find "$d/" ! -type d -wholename "$d/$i" \ + | while read -r p ; do \ + [ -n "$p" ] || continue ; \ + [ -e "$p" ] || continue ; \ + rm -fv "$p" ; \ + done ; \ + done ; \ + done + +RUN apt-remove.sh \ + ca-certificates \ + e2fsprogs \ + ; \ + apt-clean.sh + +## "docker.io/python"-specific cleanup +RUN rm -f /root/.wget-hsts + +RUN pip-env.sh pip list --format freeze \ + | grep -F '==' | awk -F= '{print $1}' \ + | xargs -r pip-env.sh pip install -U ; \ + python-rm-cache.sh "${PYTHON_SITE_PACKAGES}" + +RUN libpython="${PYTHON_SITE_PACKAGES%/*}" ; \ + rm -rfv \ + /usr/local/bin/idle* \ + "${libpython}/ensurepip/_bundled" \ + "${libpython}/idlelib" \ + "${libpython}/tkinter" \ + "${libpython}/turtle.py" \ + "${libpython}/turtledemo" \ + ; \ + python-rm-cache.sh /usr/local + +RUN find /usr/local/sbin/ ! -type d -ls -delete ; \ + find /run/ -mindepth 1 -ls -delete || : ; \ + install -d -m 01777 /run/lock + +ENTRYPOINT [ ] +CMD [ "bash" ] diff --git a/Dockerfile.deps b/Dockerfile.deps new file mode 100644 index 0000000..546a858 --- /dev/null +++ b/Dockerfile.deps @@ -0,0 +1,100 @@ +FROM docker.io/rockdrilla/angie-conv:v0.0.1-base AS base + +## --- + +FROM base AS setup +SHELL [ "/bin/sh", "-ec" ] + +COPY /scripts/* /usr/local/sbin/ +COPY /extra-scripts/* /usr/local/sbin/ + +ADD https://angie.software/keys/angie-signing.gpg /tmp/angie.gpg.bin +COPY /apt/sources.angie /etc/apt/sources.list.d/angie.txt + +RUN pkg='gnupg' ; \ + apt-install.sh ${pkg} ; \ + ## process Angie GPG keyring / APT sources + gpg-export.sh /tmp/angie.gpg.bin /etc/apt/keyrings/angie.gpg.asc ; \ + rm -f /tmp/angie.gpg.bin ; \ + env -C /etc/apt/sources.list.d mv angie.txt angie.sources ; \ + ## verify sources! + apt-env.sh apt-get update ; \ + apt-remove.sh ${pkg} ; \ + apt-clean.sh + +ENV INSTALL_WHEELS='jinja2 netaddr psutil pyyaml wcmatch' +ENV DEV_PACKAGES='libyaml-dev' +# markupsafe, psutil +ENV CIBUILDWHEEL=1 +# pyyaml +ENV PYYAML_FORCE_CYTHON=1 + +RUN w=$(mktemp -d) ; : "${w:?}" ; \ + { apt-mark showauto ; apt-mark showmanual ; } | sort -uV > "$w/t0" ; \ + printf '%s\n' ${DEV_PACKAGES} | sort -uV > "$w/t1" ; \ + apt-install.sh ${DEV_PACKAGES} ; \ + { apt-mark showauto ; apt-mark showmanual ; } | sort -uV > "$w/t2" ; \ + set +e ; \ + grep -Fxv -f "$w/t0" "$w/t2" > "$w/t3" ; \ + grep -Fxv -f "$w/t1" "$w/t3" > "$w/t4" ; \ + grep -Ev -e '-(dev|doc)$' "$w/t4" > "${PYTHON_SITE_PACKAGES}/apt-deps.txt" ; \ + set -e ; \ + rm -rf "$w/" ; unset w ; \ + apt-install.sh build-essential ; \ + pip-env.sh pip install 'cython' ; \ + pip-env.sh pip install --no-binary :all: ${INSTALL_WHEELS} ; \ + pip-env.sh pip uninstall -y 'cython' ; \ + python-rm-cache.sh "${PYTHON_SITE_PACKAGES}" ; \ + rm -rf \ + "${PYTHON_SITE_PACKAGES}/netaddr/tests" \ + "${PYTHON_SITE_PACKAGES}/psutil/tests" \ + ; \ + find "${PYTHON_SITE_PACKAGES}/" -type f -name '*.so*' -exec ls -l {} + ; \ + echo ; \ + find "${PYTHON_SITE_PACKAGES}/" -type f -name '*.so*' -printf '%p\0' \ + | sed -zE '/rust/d' \ + | xargs -0r strip --verbose --strip-debug ; \ + echo ; \ + find "${PYTHON_SITE_PACKAGES}/" -type f -name '*.so*' -exec ls -l {} + ; \ + apt-remove.sh build-essential ; \ + apt-clean.sh + +## --- + +FROM base AS deps +SHELL [ "/bin/sh", "-ec" ] + +COPY /Dockerfile.deps /usr/local/share/ + +COPY --from=setup /etc/apt/keyrings/angie.gpg.asc /etc/apt/keyrings/ +COPY --from=setup /etc/apt/sources.list.d/angie.sources /etc/apt/sources.list.d/ + +## Python: site-packages +COPY --from=setup /usr/local/bin/ /usr/local/bin/ +COPY --from=setup /${PYTHON_SITE_PACKAGES}/ /${PYTHON_SITE_PACKAGES}/ + +COPY /scripts/* /usr/local/sbin/ + +## install missing dependencies for Python site-packages +RUN f="${PYTHON_SITE_PACKAGES}/apt-deps.txt" ; \ + [ -s "$f" ] || exit 0 ; \ + xargs -a "$f" apt-install.sh ; \ + apt-clean.sh + +## common deps +RUN apt-install.sh \ + brotli \ + curl \ + dumb-init \ + gettext-base \ + jq \ + netbase \ + netcat-openbsd \ + openssl \ + procps \ + psmisc \ + zstd \ + ; \ + apt-clean.sh + +RUN find /usr/local/sbin/ ! -type d -ls -delete diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..67db858 --- /dev/null +++ b/LICENSE @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/TODO b/TODO new file mode 100644 index 0000000..01fe0c4 --- /dev/null +++ b/TODO @@ -0,0 +1,3 @@ +- documentation +- examples +- "light" NJS module (request: https://t.me/angie_support/3315) diff --git a/angie/angie.conf b/angie/angie.conf new file mode 100644 index 0000000..9513111 --- /dev/null +++ b/angie/angie.conf @@ -0,0 +1,22 @@ +daemon off; +pid run/angie.pid; + +## almost useless +include load/mod-core-*.conf; + +# mod-http.conf +# mod-mail.conf +# mod-stream.conf +include run/mod-*.conf; + +events { + include autoconf.d/core_ev-*.conf; + include load/core_ev-*.conf; +} +include autoconf.d/core-*.conf; +include load/core-*.conf; + +# ctx-http.conf +# ctx-mail.conf +# ctx-stream.conf +include run/ctx-*.conf; \ No newline at end of file diff --git a/angie/autoconf.dist/core-error-log.conf b/angie/autoconf.dist/core-error-log.conf new file mode 100644 index 0000000..566c8e7 --- /dev/null +++ b/angie/autoconf.dist/core-error-log.conf @@ -0,0 +1 @@ +error_log log.d/error.log warn; \ No newline at end of file diff --git a/angie/autoconf.dist/core-lock-file.conf b/angie/autoconf.dist/core-lock-file.conf new file mode 100644 index 0000000..c458950 --- /dev/null +++ b/angie/autoconf.dist/core-lock-file.conf @@ -0,0 +1 @@ +lock_file lock/angie.lock; \ No newline at end of file diff --git a/angie/autoconf.dist/core-pcre-jit.conf b/angie/autoconf.dist/core-pcre-jit.conf new file mode 100644 index 0000000..a78c335 --- /dev/null +++ b/angie/autoconf.dist/core-pcre-jit.conf @@ -0,0 +1 @@ +pcre_jit on; \ No newline at end of file diff --git a/angie/autoconf.dist/core-user.conf.in b/angie/autoconf.dist/core-user.conf.in new file mode 100644 index 0000000..86558e3 --- /dev/null +++ b/angie/autoconf.dist/core-user.conf.in @@ -0,0 +1,3 @@ +## if container is running in non-privileged mode, +## then this file is going to be removed by /image-entry.d/76-adjust-core-user.sh +user ${NGX_USER} ${NGX_GROUP}; \ No newline at end of file diff --git a/angie/autoconf.dist/core-worker-env.conf.j2 b/angie/autoconf.dist/core-worker-env.conf.j2 new file mode 100644 index 0000000..61a6ce1 --- /dev/null +++ b/angie/autoconf.dist/core-worker-env.conf.j2 @@ -0,0 +1,31 @@ +{#- prologue -#} +{#- NB: "TZ" is always provided by Angie itself -#} +{%- set c_env = ( j2cfg.core_worker_env or [] ) | any_to_env_dict -%} +{%- set c_vars = c_env | dict_keys -%} +{%- set c_vars_passthrough = c_env | dict_empty_keys -%} +{%- set c_vars_override = c_env | dict_non_empty_keys -%} +{%- set vars_passthrough = (env_passthrough + c_vars_passthrough) | uniq | list_intersect(env | dict_keys) -%} + +{#- main part -#} +## preserve +{%- for k in env_preserve %} +env {{ k }}; +{%- endfor %} + +## passthrough +{%- for k in vars_passthrough %} +env {{ k }}; +{%- endfor %} + +{% if c_vars_override %} +## WARNING! +## explicit environment variables are NOT implemented +## reason: envs are supported only for http_perl but not for http_js/stream_js +## solution: provide environment variables explicitly +## and then list them in "core_worker_env" key in config +## + {%- for k in c_vars_override %} + {#- {%- set v = c_env[k] %} #} +## env {{ k }}={{ c_env[k].__repr__() }}; + {%- endfor %} +{%- endif %} \ No newline at end of file diff --git a/angie/autoconf.dist/core-worker.conf.j2 b/angie/autoconf.dist/core-worker.conf.j2 new file mode 100644 index 0000000..76554c0 --- /dev/null +++ b/angie/autoconf.dist/core-worker.conf.j2 @@ -0,0 +1,10 @@ +worker_processes {{ env.NGX_WORKER_PROCESSES }}; +{%- if env.NGX_WORKER_CPU_AFFINITY %} +worker_cpu_affinity {{ env.NGX_WORKER_CPU_AFFINITY }}; +{%- endif %} +{%- if env.NGX_WORKER_PRIORITY %} +worker_priority {{ env.NGX_WORKER_PRIORITY }}; +{%- endif %} +{%- if env.NGX_WORKER_RLIMIT_NOFILE %} +worker_rlimit_nofile {{ env.NGX_WORKER_RLIMIT_NOFILE }}; +{%- endif %} \ No newline at end of file diff --git a/angie/autoconf.dist/core_ev-worker.conf.j2 b/angie/autoconf.dist/core_ev-worker.conf.j2 new file mode 100644 index 0000000..4a61641 --- /dev/null +++ b/angie/autoconf.dist/core_ev-worker.conf.j2 @@ -0,0 +1,7 @@ +worker_connections {{ env.NGX_WORKER_CONNECTIONS }}; +{%- if env.NGX_WORKER_AIO_REQUESTS %} +worker_aio_requests {{ env.NGX_WORKER_AIO_REQUESTS }}; +{%- endif %} +{%- if env.NGX_WORKER_PRIORITY %} +worker_priority {{ env.NGX_WORKER_PRIORITY }}; +{%- endif %} \ No newline at end of file diff --git a/angie/autoconf.dist/http-access-log.conf b/angie/autoconf.dist/http-access-log.conf new file mode 100644 index 0000000..1465352 --- /dev/null +++ b/angie/autoconf.dist/http-access-log.conf @@ -0,0 +1,12 @@ +log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + +log_format extended '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" rt="$request_time" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + 'h="$host" sn="$server_name" ru="$request_uri" u="$uri" ' + 'ucs="$upstream_cache_status" ua="$upstream_addr" us="$upstream_status" ' + 'uct="$upstream_connect_time" urt="$upstream_response_time"'; + +access_log log.d/access.log main; diff --git a/angie/autoconf.dist/http-alt-svc.conf.j2 b/angie/autoconf.dist/http-alt-svc.conf.j2 new file mode 100644 index 0000000..f764b23 --- /dev/null +++ b/angie/autoconf.dist/http-alt-svc.conf.j2 @@ -0,0 +1,12 @@ +{#- prologue -#} +{%- set extra_proto = ['v3', 'v2'] -%} +{%- set confload = ( env.NGX_HTTP_CONFLOAD or '' ) | str_split_to_list -%} +{%- set proto = confload | list_intersect(extra_proto) -%} +{#- ALPN mapping -#} +{%- set proto = proto | re_sub('^v2$', 'h2=":443"; ma=3600') -%} +{%- set proto = proto | re_sub('^v3$', 'h3=":443"; ma=3600') -%} +{#- main part -#} +{%- if proto %} +{#- TODO: precise quotation #} +add_header Alt-Svc {{ (proto | join(', ')).__repr__() }}; +{%- endif %} \ No newline at end of file diff --git a/angie/autoconf.dist/http-buffers.conf b/angie/autoconf.dist/http-buffers.conf new file mode 100644 index 0000000..2f8939d --- /dev/null +++ b/angie/autoconf.dist/http-buffers.conf @@ -0,0 +1,4 @@ +subrequest_output_buffer_size 16k; +client_body_buffer_size 16k; +client_header_buffer_size 4k; +large_client_header_buffers 8 16k; diff --git a/angie/autoconf.dist/http-max-ranges.conf.j2 b/angie/autoconf.dist/http-max-ranges.conf.j2 new file mode 100644 index 0000000..64b25de --- /dev/null +++ b/angie/autoconf.dist/http-max-ranges.conf.j2 @@ -0,0 +1,3 @@ +{%- if env.NGX_HTTP_MAX_RANGES %} +max_ranges {{ env.NGX_HTTP_MAX_RANGES }}; +{%- endif %} \ No newline at end of file diff --git a/angie/autoconf.dist/http-mime-types.conf b/angie/autoconf.dist/http-mime-types.conf new file mode 100644 index 0000000..6757e54 --- /dev/null +++ b/angie/autoconf.dist/http-mime-types.conf @@ -0,0 +1,8 @@ +include snip.d/mime.types; + +types { + font/ttf ttf; + application/font-sfnt otf; +} + +default_type application/octet-stream; diff --git a/angie/autoconf.dist/http-request-headers-basic.conf.j2 b/angie/autoconf.dist/http-request-headers-basic.conf.j2 new file mode 100644 index 0000000..65a5166 --- /dev/null +++ b/angie/autoconf.dist/http-request-headers-basic.conf.j2 @@ -0,0 +1,26 @@ +map $http_upgrade + $req_connection +{ + default upgrade; + "" ""; +} + +map $http_user_agent + $req_user_agent +{ + default $http_user_agent; +{%- if env.NGX_HTTP_FAKE_UA %} + ## merely fake + "" {{ env.NGX_HTTP_FAKE_UA.__repr__() }}; +{%- else %} + "" "Angie/$angie_version"; +{%- endif %} +} + +map $http_accept + $req_accept +{ + volatile; + default $http_accept; + "" "*/*"; +} \ No newline at end of file diff --git a/angie/autoconf.dist/http-request-headers-forwarded.conf b/angie/autoconf.dist/http-request-headers-forwarded.conf new file mode 100644 index 0000000..afb55c6 --- /dev/null +++ b/angie/autoconf.dist/http-request-headers-forwarded.conf @@ -0,0 +1,27 @@ +## ref: +## - https://www.digitalocean.com/community/tools/nginx?domains.0.reverseProxy.reverseProxy=true +map $remote_addr + $proxy_forwarded_elem +{ + ## IPv4 addresses can be sent as-is + ~^[0-9.]+$ "for=$remote_addr"; + ## IPv6 addresses need to be bracketed and quoted + ~^[0-9A-Fa-f:.]+$ "for=\"[$remote_addr]\""; + ## Unix domain socket names cannot be represented in RFC 7239 syntax + default "for=unknown"; +} + +## ref: +## - https://www.digitalocean.com/community/tools/nginx?domains.0.reverseProxy.reverseProxy=true +## - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded +map $http_forwarded + $proxy_add_forwarded +{ + volatile; + + ## if the incoming Forwarded header is syntactically valid, append to it + "~^(,[ \\t]*)*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*([ \\t]*,([ \\t]*([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?(;([!#$%&'*+.^_`|~0-9A-Za-z-]+=([!#$%&'*+.^_`|~0-9A-Za-z-]+|\"([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\"))?)*)?)*$" "$http_forwarded, $proxy_forwarded_elem"; + + ## otherwise, replace it + default "$proxy_forwarded_elem"; +} \ No newline at end of file diff --git a/angie/autoconf.dist/http-response-headers.conf.j2 b/angie/autoconf.dist/http-response-headers.conf.j2 new file mode 100644 index 0000000..142b081 --- /dev/null +++ b/angie/autoconf.dist/http-response-headers.conf.j2 @@ -0,0 +1,6 @@ +## add response headers +{%- set resp_hdr_dict = j2cfg.response_headers or {} -%} +{%- for h, v in resp_hdr_dict.items() %} +{#- TODO: precise quotation #} +add_header {{ h }} {{ v.__repr__() }}; +{%- endfor %} \ No newline at end of file diff --git a/angie/autoconf.dist/http-webroot.conf.in b/angie/autoconf.dist/http-webroot.conf.in new file mode 100644 index 0000000..51fb841 --- /dev/null +++ b/angie/autoconf.dist/http-webroot.conf.in @@ -0,0 +1 @@ +root ${NGX_HTTP_WEBROOT}; \ No newline at end of file diff --git a/angie/conf.dist/brotli/buffers.conf b/angie/conf.dist/brotli/buffers.conf new file mode 100644 index 0000000..5cf3b78 --- /dev/null +++ b/angie/conf.dist/brotli/buffers.conf @@ -0,0 +1,5 @@ +brotli_comp_level 5; # default: 6 +brotli_window 64k; # default: 512k + +brotli_min_length 1024; +brotli_buffers 32 16k; \ No newline at end of file diff --git a/angie/conf.dist/brotli/types.conf.j2 b/angie/conf.dist/brotli/types.conf.j2 new file mode 100644 index 0000000..d719410 --- /dev/null +++ b/angie/conf.dist/brotli/types.conf.j2 @@ -0,0 +1,9 @@ +{%- set mime_types = j2cfg.compress_types or [] -%} +{%- set mime_types = mime_types | any_to_str_list | uniq_str_list -%} +{%- if mime_types -%} +brotli_types +{%- for t in mime_types %} + {{ t }} +{%- endfor %} +; +{%- endif -%} \ No newline at end of file diff --git a/angie/conf.dist/core-quic-bpf.conf b/angie/conf.dist/core-quic-bpf.conf new file mode 100644 index 0000000..57e3c82 --- /dev/null +++ b/angie/conf.dist/core-quic-bpf.conf @@ -0,0 +1 @@ +quic_bpf on; \ No newline at end of file diff --git a/angie/conf.dist/core_ev-accept-mutex-delay.conf b/angie/conf.dist/core_ev-accept-mutex-delay.conf new file mode 100644 index 0000000..8cf9b15 --- /dev/null +++ b/angie/conf.dist/core_ev-accept-mutex-delay.conf @@ -0,0 +1 @@ +accept_mutex_delay 200ms; \ No newline at end of file diff --git a/angie/conf.dist/core_ev-accept-mutex.conf b/angie/conf.dist/core_ev-accept-mutex.conf new file mode 100644 index 0000000..9b9e7eb --- /dev/null +++ b/angie/conf.dist/core_ev-accept-mutex.conf @@ -0,0 +1 @@ +accept_mutex on; \ No newline at end of file diff --git a/angie/conf.dist/core_ev-multi-accept.conf b/angie/conf.dist/core_ev-multi-accept.conf new file mode 100644 index 0000000..19df249 --- /dev/null +++ b/angie/conf.dist/core_ev-multi-accept.conf @@ -0,0 +1 @@ +multi_accept on; \ No newline at end of file diff --git a/angie/conf.dist/fastcgi/buffers.conf b/angie/conf.dist/fastcgi/buffers.conf new file mode 100644 index 0000000..6198dff --- /dev/null +++ b/angie/conf.dist/fastcgi/buffers.conf @@ -0,0 +1,4 @@ +fastcgi_buffers 16 16k; +fastcgi_buffer_size 16k; +fastcgi_busy_buffers_size 32k; +fastcgi_temp_file_write_size 32k; \ No newline at end of file diff --git a/angie/conf.dist/fastcgi/cache-bypass.conf.j2 b/angie/conf.dist/fastcgi/cache-bypass.conf.j2 new file mode 100644 index 0000000..cb5095c --- /dev/null +++ b/angie/conf.dist/fastcgi/cache-bypass.conf.j2 @@ -0,0 +1,15 @@ +{#- TODO: precise quotation -#} +{%- set cache_bypass = j2cfg.cache_bypass or [] -%} +{%- if cache_bypass -%} +## disable (response) cache under following conditions +fastcgi_cache_bypass +{%- for v in cache_bypass %} + {{ v.__repr__() }} +{%- endfor %} +; +fastcgi_no_cache +{%- for v in cache_bypass %} + {{ v.__repr__() }} +{%- endfor %} +; +{%- endif -%} \ No newline at end of file diff --git a/angie/conf.dist/fastcgi/headers.conf.j2 b/angie/conf.dist/fastcgi/headers.conf.j2 new file mode 100644 index 0000000..a33f6f4 --- /dev/null +++ b/angie/conf.dist/fastcgi/headers.conf.j2 @@ -0,0 +1,13 @@ +## hide/remove request headers +{%- set req_hdr_dict = j2cfg.request_headers or {} -%} +{%- for h, v in req_hdr_dict.items() %} +{#- TODO: precise quotation #} +fastcgi_param {{ h | as_cgi_header }} {{ v.__repr__() }}; +{%- endfor %} + +## hide response headers +{%- set resp_hdr_dict = j2cfg.response_headers or {} -%} +{%- set resp_hdr_list = resp_hdr_dict | dict_keys -%} +{%- for h in resp_hdr_list %} +fastcgi_hide_header {{ h }}; +{%- endfor %} \ No newline at end of file diff --git a/angie/conf.dist/fastcgi/param.conf b/angie/conf.dist/fastcgi/param.conf new file mode 100644 index 0000000..863a37e --- /dev/null +++ b/angie/conf.dist/fastcgi/param.conf @@ -0,0 +1,7 @@ +include snip.d/fastcgi.conf; + +fastcgi_param PATH_INFO $path_info; + +fastcgi_param AUTH_USER $remote_user; +fastcgi_param REMOTE_USER $remote_user; +fastcgi_param HTTP_HOST $host; \ No newline at end of file diff --git a/angie/conf.dist/grpc/buffers.conf b/angie/conf.dist/grpc/buffers.conf new file mode 100644 index 0000000..03efeee --- /dev/null +++ b/angie/conf.dist/grpc/buffers.conf @@ -0,0 +1 @@ +grpc_buffer_size 16k; \ No newline at end of file diff --git a/angie/conf.dist/grpc/headers.conf.j2 b/angie/conf.dist/grpc/headers.conf.j2 new file mode 100644 index 0000000..38de9fb --- /dev/null +++ b/angie/conf.dist/grpc/headers.conf.j2 @@ -0,0 +1,13 @@ +## hide/remove request headers +{%- set req_hdr_dict = j2cfg.request_headers or {} -%} +{%- for h, v in req_hdr_dict.items() %} +{#- TODO: precise quotation #} +grpc_set_header {{ h }} {{ v.__repr__() }}; +{%- endfor %} + +## hide response headers +{%- set resp_hdr_dict = j2cfg.response_headers or {} -%} +{%- set resp_hdr_list = resp_hdr_dict | dict_keys -%} +{%- for h in resp_hdr_list %} +grpc_hide_header {{ h }}; +{%- endfor %} \ No newline at end of file diff --git a/angie/conf.dist/grpc/ssl-cmd.conf.j2 b/angie/conf.dist/grpc/ssl-cmd.conf.j2 new file mode 100644 index 0000000..298a4e1 --- /dev/null +++ b/angie/conf.dist/grpc/ssl-cmd.conf.j2 @@ -0,0 +1,4 @@ +{%- for k, v in j2cfg.tls.conf_cmd.items() %} +{#- TODO: precise quotation #} +grpc_ssl_conf_command {{ k }} {{ v.__repr__() }}; +{%- endfor %} \ No newline at end of file diff --git a/angie/conf.dist/grpc/ssl-verify.conf b/angie/conf.dist/grpc/ssl-verify.conf new file mode 100644 index 0000000..7f5c82f --- /dev/null +++ b/angie/conf.dist/grpc/ssl-verify.conf @@ -0,0 +1 @@ +grpc_ssl_verify on; \ No newline at end of file diff --git a/angie/conf.dist/grpc/tls-ca-file.conf.in b/angie/conf.dist/grpc/tls-ca-file.conf.in new file mode 100644 index 0000000..e0750ff --- /dev/null +++ b/angie/conf.dist/grpc/tls-ca-file.conf.in @@ -0,0 +1 @@ +grpc_ssl_trusted_certificate ${NGX_SSL_CERT_FILE}; \ No newline at end of file diff --git a/angie/conf.dist/gzip/buffers.conf b/angie/conf.dist/gzip/buffers.conf new file mode 100644 index 0000000..8a13413 --- /dev/null +++ b/angie/conf.dist/gzip/buffers.conf @@ -0,0 +1,4 @@ +gzip_comp_level 2; # default: 1 + +gzip_min_length 1024; +gzip_buffers 32 16k; \ No newline at end of file diff --git a/angie/conf.dist/gzip/proxied.conf b/angie/conf.dist/gzip/proxied.conf new file mode 100644 index 0000000..989c9d7 --- /dev/null +++ b/angie/conf.dist/gzip/proxied.conf @@ -0,0 +1 @@ +gzip_proxied any; \ No newline at end of file diff --git a/angie/conf.dist/gzip/types.conf.j2 b/angie/conf.dist/gzip/types.conf.j2 new file mode 100644 index 0000000..6dc5194 --- /dev/null +++ b/angie/conf.dist/gzip/types.conf.j2 @@ -0,0 +1,9 @@ +{%- set mime_types = j2cfg.compress_types or [] -%} +{%- set mime_types = mime_types | any_to_str_list | uniq_str_list -%} +{%- if mime_types -%} +gzip_types +{%- for t in mime_types %} + {{ t }} +{%- endfor %} +; +{%- endif -%} \ No newline at end of file diff --git a/angie/conf.dist/gzip/vary.conf b/angie/conf.dist/gzip/vary.conf new file mode 100644 index 0000000..d157ade --- /dev/null +++ b/angie/conf.dist/gzip/vary.conf @@ -0,0 +1 @@ +gzip_vary on; \ No newline at end of file diff --git a/angie/conf.dist/http-brotli-static.conf b/angie/conf.dist/http-brotli-static.conf new file mode 100644 index 0000000..11a1aaf --- /dev/null +++ b/angie/conf.dist/http-brotli-static.conf @@ -0,0 +1 @@ +brotli_static on; \ No newline at end of file diff --git a/angie/conf.dist/http-brotli.conf b/angie/conf.dist/http-brotli.conf new file mode 100644 index 0000000..f31f7ed --- /dev/null +++ b/angie/conf.dist/http-brotli.conf @@ -0,0 +1,2 @@ +include conf.d/brotli/*.conf; +brotli on; \ No newline at end of file diff --git a/angie/conf.dist/http-fastcgi.conf b/angie/conf.dist/http-fastcgi.conf new file mode 100644 index 0000000..a286a45 --- /dev/null +++ b/angie/conf.dist/http-fastcgi.conf @@ -0,0 +1 @@ +include conf.d/fastcgi/*.conf; \ No newline at end of file diff --git a/angie/conf.dist/http-grpc.conf b/angie/conf.dist/http-grpc.conf new file mode 100644 index 0000000..cd17a8d --- /dev/null +++ b/angie/conf.dist/http-grpc.conf @@ -0,0 +1 @@ +include conf.d/grpc/*.conf; \ No newline at end of file diff --git a/angie/conf.dist/http-gunzip.conf b/angie/conf.dist/http-gunzip.conf new file mode 100644 index 0000000..ca799e2 --- /dev/null +++ b/angie/conf.dist/http-gunzip.conf @@ -0,0 +1,2 @@ +gunzip_buffers 16 16k; +gunzip on; \ No newline at end of file diff --git a/angie/conf.dist/http-gzip-static.conf b/angie/conf.dist/http-gzip-static.conf new file mode 100644 index 0000000..aed8556 --- /dev/null +++ b/angie/conf.dist/http-gzip-static.conf @@ -0,0 +1 @@ +gzip_static on; \ No newline at end of file diff --git a/angie/conf.dist/http-gzip.conf b/angie/conf.dist/http-gzip.conf new file mode 100644 index 0000000..0ebfbfa --- /dev/null +++ b/angie/conf.dist/http-gzip.conf @@ -0,0 +1,2 @@ +include conf.d/gzip/*.conf; +gzip on; \ No newline at end of file diff --git a/angie/conf.dist/http-modsecurity.conf b/angie/conf.dist/http-modsecurity.conf new file mode 100644 index 0000000..0f1725b --- /dev/null +++ b/angie/conf.dist/http-modsecurity.conf @@ -0,0 +1,4 @@ +modsecurity_rules_file /etc/angie/modsecurity.d/rules.conf; + +## NOT enabling ModSecurity by default! +# modsecurity on; \ No newline at end of file diff --git a/angie/conf.dist/http-njs.conf b/angie/conf.dist/http-njs.conf new file mode 100644 index 0000000..fe45f43 --- /dev/null +++ b/angie/conf.dist/http-njs.conf @@ -0,0 +1 @@ +include conf.d/njs/*.conf; \ No newline at end of file diff --git a/angie/conf.dist/http-perl.conf b/angie/conf.dist/http-perl.conf new file mode 100644 index 0000000..b3317bb --- /dev/null +++ b/angie/conf.dist/http-perl.conf @@ -0,0 +1 @@ +perl_modules /etc/angie/site.d; \ No newline at end of file diff --git a/angie/conf.dist/http-proxy.conf b/angie/conf.dist/http-proxy.conf new file mode 100644 index 0000000..c5ae073 --- /dev/null +++ b/angie/conf.dist/http-proxy.conf @@ -0,0 +1,2 @@ +include conf.d/proxy/*.conf; +include conf.d/proxy-http/*.conf; \ No newline at end of file diff --git a/angie/conf.dist/http-quic-gso.conf.j2 b/angie/conf.dist/http-quic-gso.conf.j2 new file mode 100644 index 0000000..78464ea --- /dev/null +++ b/angie/conf.dist/http-quic-gso.conf.j2 @@ -0,0 +1,5 @@ +quic_gso on; + +{%- if env.NGX_HTTP_NO_PROXY == '0' %} +proxy_quic_gso on; +{%- endif %} \ No newline at end of file diff --git a/angie/conf.dist/http-scgi.conf b/angie/conf.dist/http-scgi.conf new file mode 100644 index 0000000..436efe8 --- /dev/null +++ b/angie/conf.dist/http-scgi.conf @@ -0,0 +1 @@ +include conf.d/scgi/*.conf; \ No newline at end of file diff --git a/angie/conf.dist/http-ssl.conf.j2 b/angie/conf.dist/http-ssl.conf.j2 new file mode 100644 index 0000000..6157286 --- /dev/null +++ b/angie/conf.dist/http-ssl.conf.j2 @@ -0,0 +1,27 @@ +include conf.d/ssl/*.conf; + +## lowering from 16k to 4k to improve time-to-first-byte +ssl_buffer_size 4k; + +{%- if env.NGX_HTTP_SSL_PROFILE %} +include snip.d/ssl-{{ env.NGX_HTTP_SSL_PROFILE }}; +{%- endif %} + +{%- if j2cfg.tls.stapling.enable %} +ssl_stapling on; + {%- if j2cfg.tls.stapling.verify %} +ssl_stapling_verify on; + {%- else %} +ssl_stapling_verify off; + {%- endif %} + {%- if j2cfg.tls.stapling.file %} +{#- TODO: precise quotation #} +ssl_stapling_file {{ j2cfg.tls.stapling.file.__repr__() }}; + {%- endif %} + {%- if j2cfg.tls.stapling.responder %} +{#- TODO: precise quotation #} +ssl_stapling_responder {{ j2cfg.tls.stapling.responder.__repr__() }}; + {%- endif %} +{%- else %} +ssl_stapling off; +{%- endif %} diff --git a/angie/conf.dist/http-uwsgi.conf b/angie/conf.dist/http-uwsgi.conf new file mode 100644 index 0000000..a9567c2 --- /dev/null +++ b/angie/conf.dist/http-uwsgi.conf @@ -0,0 +1 @@ +include conf.d/uwsgi/*.conf; \ No newline at end of file diff --git a/angie/conf.dist/http-v2.conf b/angie/conf.dist/http-v2.conf new file mode 100644 index 0000000..2a8de50 --- /dev/null +++ b/angie/conf.dist/http-v2.conf @@ -0,0 +1,2 @@ +include conf.d/http2/*.conf; +http2 on; \ No newline at end of file diff --git a/angie/conf.dist/http-v3.conf b/angie/conf.dist/http-v3.conf new file mode 100644 index 0000000..23a6672 --- /dev/null +++ b/angie/conf.dist/http-v3.conf @@ -0,0 +1,2 @@ +include conf.d/http3/*.conf; +http3 on; \ No newline at end of file diff --git a/angie/conf.dist/http-zstd-static.conf b/angie/conf.dist/http-zstd-static.conf new file mode 100644 index 0000000..1ef178e --- /dev/null +++ b/angie/conf.dist/http-zstd-static.conf @@ -0,0 +1 @@ +zstd_static on; \ No newline at end of file diff --git a/angie/conf.dist/http-zstd.conf b/angie/conf.dist/http-zstd.conf new file mode 100644 index 0000000..34116a1 --- /dev/null +++ b/angie/conf.dist/http-zstd.conf @@ -0,0 +1,2 @@ +include conf.d/zstd/*.conf; +zstd on; \ No newline at end of file diff --git a/angie/conf.dist/http2/param.conf b/angie/conf.dist/http2/param.conf new file mode 100644 index 0000000..2645e78 --- /dev/null +++ b/angie/conf.dist/http2/param.conf @@ -0,0 +1,2 @@ +http2_chunk_size 16k; +http2_body_preread_size 64k; \ No newline at end of file diff --git a/angie/conf.dist/http3/param.conf.j2 b/angie/conf.dist/http3/param.conf.j2 new file mode 100644 index 0000000..a15fac2 --- /dev/null +++ b/angie/conf.dist/http3/param.conf.j2 @@ -0,0 +1,9 @@ +http3_max_concurrent_streams 128; #default +http3_stream_buffer_size 64k; #default +quic_active_connection_id_limit 3; + +{%- if env.NGX_HTTP_NO_PROXY == '0' %} +proxy_http3_max_concurrent_streams 128; #default +proxy_http3_stream_buffer_size 64k; #default +proxy_quic_active_connection_id_limit 3; +{%- endif %} \ No newline at end of file diff --git a/angie/conf.dist/mail-ssl.conf.j2 b/angie/conf.dist/mail-ssl.conf.j2 new file mode 100644 index 0000000..1d778ee --- /dev/null +++ b/angie/conf.dist/mail-ssl.conf.j2 @@ -0,0 +1,5 @@ +include conf.d/ssl/*.conf; + +{%- if env.NGX_MAIL_SSL_PROFILE %} +include snip.d/ssl-{{ env.NGX_MAIL_SSL_PROFILE }}; +{%- endif %} diff --git a/angie/conf.dist/njs/path.conf b/angie/conf.dist/njs/path.conf new file mode 100644 index 0000000..cbed30d --- /dev/null +++ b/angie/conf.dist/njs/path.conf @@ -0,0 +1 @@ +js_path /etc/angie/site.d; \ No newline at end of file diff --git a/angie/conf.dist/njs/tls-ca-file.conf.in b/angie/conf.dist/njs/tls-ca-file.conf.in new file mode 100644 index 0000000..8244737 --- /dev/null +++ b/angie/conf.dist/njs/tls-ca-file.conf.in @@ -0,0 +1 @@ +js_fetch_trusted_certificate ${NGX_SSL_CERT_FILE}; \ No newline at end of file diff --git a/angie/conf.dist/proxy-http/buffers.conf b/angie/conf.dist/proxy-http/buffers.conf new file mode 100644 index 0000000..7e69216 --- /dev/null +++ b/angie/conf.dist/proxy-http/buffers.conf @@ -0,0 +1,4 @@ +proxy_buffers 16 16k; +proxy_buffer_size 16k; +proxy_busy_buffers_size 32k; +proxy_temp_file_write_size 32k; \ No newline at end of file diff --git a/angie/conf.dist/proxy-http/cache-bypass.conf.j2 b/angie/conf.dist/proxy-http/cache-bypass.conf.j2 new file mode 100644 index 0000000..fb6357c --- /dev/null +++ b/angie/conf.dist/proxy-http/cache-bypass.conf.j2 @@ -0,0 +1,15 @@ +{#- TODO: precise quotation -#} +{%- set cache_bypass = j2cfg.cache_bypass or [] -%} +{%- if cache_bypass -%} +## disable (response) cache under following conditions +proxy_cache_bypass +{%- for v in cache_bypass %} + {{ v.__repr__() }} +{%- endfor %} +; +proxy_no_cache +{%- for v in cache_bypass %} + {{ v.__repr__() }} +{%- endfor %} +; +{%- endif -%} \ No newline at end of file diff --git a/angie/conf.dist/proxy-http/headers.conf.j2 b/angie/conf.dist/proxy-http/headers.conf.j2 new file mode 100644 index 0000000..ba2f52e --- /dev/null +++ b/angie/conf.dist/proxy-http/headers.conf.j2 @@ -0,0 +1,13 @@ +## hide/remove request headers +{%- set req_hdr_dict = j2cfg.request_headers or {} -%} +{%- for h, v in req_hdr_dict.items() %} +{#- TODO: precise quotation #} +proxy_set_header {{ h }} {{ v.__repr__() }}; +{%- endfor %} + +## hide response headers +{%- set resp_hdr_dict = j2cfg.response_headers or {} -%} +{%- set resp_hdr_list = resp_hdr_dict | dict_keys -%} +{%- for h in resp_hdr_list %} +proxy_hide_header {{ h }}; +{%- endfor %} \ No newline at end of file diff --git a/angie/conf.dist/proxy-http/version.conf b/angie/conf.dist/proxy-http/version.conf new file mode 100644 index 0000000..8d0948b --- /dev/null +++ b/angie/conf.dist/proxy-http/version.conf @@ -0,0 +1 @@ +proxy_http_version 1.1; \ No newline at end of file diff --git a/angie/conf.dist/proxy-stream/.gitkeep b/angie/conf.dist/proxy-stream/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/angie/conf.dist/proxy/ssl-cmd.conf.j2 b/angie/conf.dist/proxy/ssl-cmd.conf.j2 new file mode 100644 index 0000000..afd8378 --- /dev/null +++ b/angie/conf.dist/proxy/ssl-cmd.conf.j2 @@ -0,0 +1,4 @@ +{%- for k, v in j2cfg.tls.conf_cmd.items() %} +{#- TODO: precise quotation #} +proxy_ssl_conf_command {{ k }} {{ v.__repr__() }}; +{%- endfor %} \ No newline at end of file diff --git a/angie/conf.dist/proxy/ssl-verify.conf b/angie/conf.dist/proxy/ssl-verify.conf new file mode 100644 index 0000000..f5b5ebe --- /dev/null +++ b/angie/conf.dist/proxy/ssl-verify.conf @@ -0,0 +1 @@ +proxy_ssl_verify on; \ No newline at end of file diff --git a/angie/conf.dist/scgi/buffers.conf b/angie/conf.dist/scgi/buffers.conf new file mode 100644 index 0000000..9871886 --- /dev/null +++ b/angie/conf.dist/scgi/buffers.conf @@ -0,0 +1,4 @@ +scgi_buffers 16 16k; +scgi_buffer_size 16k; +scgi_busy_buffers_size 32k; +scgi_temp_file_write_size 32k; \ No newline at end of file diff --git a/angie/conf.dist/scgi/cache-bypass.conf.j2 b/angie/conf.dist/scgi/cache-bypass.conf.j2 new file mode 100644 index 0000000..b08bae2 --- /dev/null +++ b/angie/conf.dist/scgi/cache-bypass.conf.j2 @@ -0,0 +1,15 @@ +{#- TODO: precise quotation -#} +{%- set cache_bypass = j2cfg.cache_bypass or [] -%} +{%- if cache_bypass -%} +## disable (response) cache under following conditions +scgi_cache_bypass +{%- for v in cache_bypass %} + {{ v.__repr__() }} +{%- endfor %} +; +scgi_no_cache +{%- for v in cache_bypass %} + {{ v.__repr__() }} +{%- endfor %} +; +{%- endif -%} \ No newline at end of file diff --git a/angie/conf.dist/scgi/headers.conf.j2 b/angie/conf.dist/scgi/headers.conf.j2 new file mode 100644 index 0000000..dc67c63 --- /dev/null +++ b/angie/conf.dist/scgi/headers.conf.j2 @@ -0,0 +1,13 @@ +## hide/remove request headers +{%- set req_hdr_dict = j2cfg.request_headers or {} -%} +{%- for h, v in req_hdr_dict.items() %} +{#- TODO: precise quotation #} +scgi_param {{ h | as_cgi_header }} {{ v.__repr__() }}; +{%- endfor %} + +## hide response headers +{%- set resp_hdr_dict = j2cfg.response_headers or {} -%} +{%- set resp_hdr_list = resp_hdr_dict | dict_keys -%} +{%- for h in resp_hdr_list %} +scgi_hide_header {{ h }}; +{%- endfor %} \ No newline at end of file diff --git a/angie/conf.dist/scgi/param.conf b/angie/conf.dist/scgi/param.conf new file mode 100644 index 0000000..2e0c45b --- /dev/null +++ b/angie/conf.dist/scgi/param.conf @@ -0,0 +1,7 @@ +include snip.d/scgi_params; + +scgi_param PATH_INFO $path_info; + +scgi_param AUTH_USER $remote_user; +scgi_param REMOTE_USER $remote_user; +scgi_param HTTP_HOST $host; \ No newline at end of file diff --git a/angie/conf.dist/ssl/cmd.conf.j2 b/angie/conf.dist/ssl/cmd.conf.j2 new file mode 100644 index 0000000..2b17c6e --- /dev/null +++ b/angie/conf.dist/ssl/cmd.conf.j2 @@ -0,0 +1,4 @@ +{%- for k, v in j2cfg.tls.conf_cmd.items() %} +{#- TODO: precise quotation #} +ssl_conf_command {{ k }} {{ v.__repr__() }}; +{%- endfor %} \ No newline at end of file diff --git a/angie/conf.dist/stream-njs.conf b/angie/conf.dist/stream-njs.conf new file mode 100644 index 0000000..fe45f43 --- /dev/null +++ b/angie/conf.dist/stream-njs.conf @@ -0,0 +1 @@ +include conf.d/njs/*.conf; \ No newline at end of file diff --git a/angie/conf.dist/stream-proxy.conf b/angie/conf.dist/stream-proxy.conf new file mode 100644 index 0000000..bc68d70 --- /dev/null +++ b/angie/conf.dist/stream-proxy.conf @@ -0,0 +1,2 @@ +include conf.d/proxy/*.conf; +include conf.d/proxy-stream/*.conf; \ No newline at end of file diff --git a/angie/conf.dist/stream-ssl.conf.j2 b/angie/conf.dist/stream-ssl.conf.j2 new file mode 100644 index 0000000..dfeadde --- /dev/null +++ b/angie/conf.dist/stream-ssl.conf.j2 @@ -0,0 +1,5 @@ +include conf.d/ssl/*.conf; + +{%- if env.NGX_STREAM_SSL_PROFILE %} +include snip.d/ssl-{{ env.NGX_STREAM_SSL_PROFILE }}; +{%- endif %} diff --git a/angie/conf.dist/uwsgi/buffers.conf b/angie/conf.dist/uwsgi/buffers.conf new file mode 100644 index 0000000..2ac16d7 --- /dev/null +++ b/angie/conf.dist/uwsgi/buffers.conf @@ -0,0 +1,4 @@ +uwsgi_buffers 16 16k; +uwsgi_buffer_size 16k; +uwsgi_busy_buffers_size 32k; +uwsgi_temp_file_write_size 32k; \ No newline at end of file diff --git a/angie/conf.dist/uwsgi/cache-bypass.conf.j2 b/angie/conf.dist/uwsgi/cache-bypass.conf.j2 new file mode 100644 index 0000000..4571bb0 --- /dev/null +++ b/angie/conf.dist/uwsgi/cache-bypass.conf.j2 @@ -0,0 +1,15 @@ +{#- TODO: precise quotation -#} +{%- set cache_bypass = j2cfg.cache_bypass or [] -%} +{%- if cache_bypass -%} +## disable (response) cache under following conditions +uwsgi_cache_bypass +{%- for v in cache_bypass %} + {{ v.__repr__() }} +{%- endfor %} +; +uwsgi_no_cache +{%- for v in cache_bypass %} + {{ v.__repr__() }} +{%- endfor %} +; +{%- endif -%} \ No newline at end of file diff --git a/angie/conf.dist/uwsgi/headers.conf.j2 b/angie/conf.dist/uwsgi/headers.conf.j2 new file mode 100644 index 0000000..44a41b9 --- /dev/null +++ b/angie/conf.dist/uwsgi/headers.conf.j2 @@ -0,0 +1,13 @@ +## hide/remove request headers +{%- set req_hdr_dict = j2cfg.request_headers or {} -%} +{%- for h, v in req_hdr_dict.items() %} +{#- TODO: precise quotation #} +uwsgi_param {{ h | as_cgi_header }} {{ v.__repr__() }}; +{%- endfor %} + +## hide response headers +{%- set resp_hdr_dict = j2cfg.response_headers or {} -%} +{%- set resp_hdr_list = resp_hdr_dict | dict_keys -%} +{%- for h in resp_hdr_list %} +uwsgi_hide_header {{ h }}; +{%- endfor %} \ No newline at end of file diff --git a/angie/conf.dist/uwsgi/param.conf b/angie/conf.dist/uwsgi/param.conf new file mode 100644 index 0000000..7ac1edc --- /dev/null +++ b/angie/conf.dist/uwsgi/param.conf @@ -0,0 +1,7 @@ +include snip.d/uwsgi_params; + +uwsgi_param PATH_INFO $path_info; + +uwsgi_param AUTH_USER $remote_user; +uwsgi_param REMOTE_USER $remote_user; +uwsgi_param HTTP_HOST $host; \ No newline at end of file diff --git a/angie/conf.dist/uwsgi/ssl-cmd.conf.j2 b/angie/conf.dist/uwsgi/ssl-cmd.conf.j2 new file mode 100644 index 0000000..9b0dda8 --- /dev/null +++ b/angie/conf.dist/uwsgi/ssl-cmd.conf.j2 @@ -0,0 +1,4 @@ +{%- for k, v in j2cfg.tls.conf_cmd.items() %} +{#- TODO: precise quotation #} +uwsgi_ssl_conf_command {{ k }} {{ v.__repr__() }}; +{%- endfor %} \ No newline at end of file diff --git a/angie/conf.dist/uwsgi/tls-ca-file.conf.in b/angie/conf.dist/uwsgi/tls-ca-file.conf.in new file mode 100644 index 0000000..8ebe0d0 --- /dev/null +++ b/angie/conf.dist/uwsgi/tls-ca-file.conf.in @@ -0,0 +1 @@ +uwsgi_ssl_trusted_certificate ${NGX_SSL_CERT_FILE}; \ No newline at end of file diff --git a/angie/conf.dist/zstd/buffers.conf b/angie/conf.dist/zstd/buffers.conf new file mode 100644 index 0000000..c310cc5 --- /dev/null +++ b/angie/conf.dist/zstd/buffers.conf @@ -0,0 +1,4 @@ +zstd_comp_level 2; # default: 1 + +zstd_min_length 1024; +zstd_buffers 32 16k; \ No newline at end of file diff --git a/angie/conf.dist/zstd/types.conf.j2 b/angie/conf.dist/zstd/types.conf.j2 new file mode 100644 index 0000000..924c58a --- /dev/null +++ b/angie/conf.dist/zstd/types.conf.j2 @@ -0,0 +1,9 @@ +{%- set mime_types = j2cfg.compress_types or [] -%} +{%- set mime_types = mime_types | any_to_str_list | uniq_str_list -%} +{%- if mime_types -%} +zstd_types +{%- for t in mime_types %} + {{ t }} +{%- endfor %} +; +{%- endif -%} \ No newline at end of file diff --git a/angie/ctx-http.conf b/angie/ctx-http.conf new file mode 100644 index 0000000..8547637 --- /dev/null +++ b/angie/ctx-http.conf @@ -0,0 +1,5 @@ +http { + include autoconf.d/http-*.conf; + include load/http-*.conf; + include site.d/http-*.conf; +} \ No newline at end of file diff --git a/angie/ctx-mail.conf b/angie/ctx-mail.conf new file mode 100644 index 0000000..bd21fcb --- /dev/null +++ b/angie/ctx-mail.conf @@ -0,0 +1,5 @@ +mail { + include autoconf.d/mail-*.conf; + include load/mail-*.conf; + include site.d/mail-*.conf; +} \ No newline at end of file diff --git a/angie/ctx-stream.conf b/angie/ctx-stream.conf new file mode 100644 index 0000000..176574d --- /dev/null +++ b/angie/ctx-stream.conf @@ -0,0 +1,5 @@ +stream { + include autoconf.d/stream-*.conf; + include load/stream-*.conf; + include site.d/stream-*.conf; +} \ No newline at end of file diff --git a/angie/j2cfg.dist/00-defaults.yml.j2 b/angie/j2cfg.dist/00-defaults.yml.j2 new file mode 100644 index 0000000..f7dc496 --- /dev/null +++ b/angie/j2cfg.dist/00-defaults.yml.j2 @@ -0,0 +1,93 @@ +cache_bypass: +- '$http_authorization' +- '$http_pragma' +- '$http_upgrade' + +compress_types: +- application/atom+xml +- application/javascript +- application/json +- application/vnd.api+json +- application/rss+xml +- application/x-javascript +- application/xhtml+xml +- application/xml +- image/svg+xml +- image/x-icon +- text/css +- text/javascript +- text/plain +- text/xml + +request_headers: +{% if env.NGX_HTTP_TRANSPARENT_PROXY == '0' %} + Host: '$proxy_host' + X-Real-IP: '$remote_addr' + ## '$proxy_add_forwarded' is defined in /angie/autoconf.dist/http-request-headers-forwarded.conf + Forwarded: '$proxy_add_forwarded' +{% elif env.NGX_HTTP_TRANSPARENT_PROXY == '1' %} + Host: '$host' + X-Real-IP: '' + Forwarded: '' +{% endif %} + +request_headers: + ## do not pass Accept-Encoding to backend + Accept-Encoding: "" + ## '$req_accept' is defined in /angie/autoconf.dist/http-request-headers-basic.conf.j2 + Accept: '$req_accept' + ## '$req_connection' is defined in /angie/autoconf.dist/http-request-headers-basic.conf.j2 + Connection: '$req_connection' + Upgrade: '$http_upgrade' + Early-Data: '$ssl_early_data' + ## '$req_user_agent' is defined in /angie/autoconf.dist/http-request-headers-basic.conf.j2 + User-Agent: '$req_user_agent' +{% if env.NGX_HTTP_X_FORWARDED == 'pass' %} + X-Forwarded-Proto: '$scheme' + X-Forwarded-Host: '$host' + X-Forwarded-Port: '$server_port' + X-Forwarded-For: '$proxy_add_x_forwarded_for' +{% elif env.NGX_HTTP_X_FORWARDED == 'remove' %} + X-Forwarded-Proto: '' + X-Forwarded-Host: '' + X-Forwarded-Port: '' + X-Forwarded-For: '' +{% endif %} + +response_headers: +{% if env.NGX_HTTP_TRANSPARENT_PROXY == '0' %} + Permissions-Policy: "accelerometer=(), autoplay=(), browsing-topics=(), camera=(), clipboard-read=(), clipboard-write=(), geolocation=(), gyroscope=(), hid=(), interest-cohort=(), magnetometer=(), microphone=(), payment=(), publickey-credentials-get=(), screen-wake-lock=(), serial=(), sync-xhr=(), usb=()" + Referrer-Policy: "no-referrer-when-downgrade" + Strict-Transport-Security: "max-age=15724800; includeSubDomains; preload" + X-Content-Type-Options: "nosniff" + X-Frame-Options: "SAMEORIGIN" + X-XSS-Protection: "1; mode=block" +{% endif %} + +tls: + ## https://docs.openssl.org/3.0/man3/SSL_CONF_cmd/#supported-configuration-file-commands + conf_cmd: + Options: PrioritizeChaCha + stapling: + enable: false + verify: true + profiles: + modern: + protocols: TLSv1.3 + #prefer_server_ciphers: false + session_tickets: false + session_timeout: 1d + intermediate: + protocols: TLSv1.2 TLSv1.3 + #prefer_server_ciphers: false + ciphers: ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305 + dhparam: /etc/angie/tls.d/ffdhe2048.pem + session_tickets: false + session_timeout: 1d + old: + protocols: TLSv1 TLSv1.1 TLSv1.2 TLSv1.3 + prefer_server_ciphers: true + ciphers: ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA + dhparam: /etc/angie/tls.d/dh1024.pem + session_tickets: false + session_timeout: 1d diff --git a/angie/mod-http.conf b/angie/mod-http.conf new file mode 100644 index 0000000..e1f2f3a --- /dev/null +++ b/angie/mod-http.conf @@ -0,0 +1 @@ +include load/mod-http-*.conf; \ No newline at end of file diff --git a/angie/mod-mail.conf b/angie/mod-mail.conf new file mode 100644 index 0000000..67c6775 --- /dev/null +++ b/angie/mod-mail.conf @@ -0,0 +1 @@ +include load/mod-mail-*.conf; \ No newline at end of file diff --git a/angie/mod-stream.conf b/angie/mod-stream.conf new file mode 100644 index 0000000..2807adb --- /dev/null +++ b/angie/mod-stream.conf @@ -0,0 +1 @@ +include load/mod-stream-*.conf; \ No newline at end of file diff --git a/angie/mod.dist/.brotli.preseed b/angie/mod.dist/.brotli.preseed new file mode 100644 index 0000000..e69de29 diff --git a/angie/mod.dist/.otel.preseed b/angie/mod.dist/.otel.preseed new file mode 100644 index 0000000..e69de29 diff --git a/angie/mod.dist/.postgres.preseed b/angie/mod.dist/.postgres.preseed new file mode 100644 index 0000000..e69de29 diff --git a/angie/mod.dist/.rtmp.preseed b/angie/mod.dist/.rtmp.preseed new file mode 100644 index 0000000..e69de29 diff --git a/angie/mod.dist/.vts.preseed b/angie/mod.dist/.vts.preseed new file mode 100644 index 0000000..e69de29 diff --git a/angie/mod.dist/.zstd.preseed b/angie/mod.dist/.zstd.preseed new file mode 100644 index 0000000..e69de29 diff --git a/angie/mod.dist/http-brotli-static.conf b/angie/mod.dist/http-brotli-static.conf new file mode 100644 index 0000000..5d02905 --- /dev/null +++ b/angie/mod.dist/http-brotli-static.conf @@ -0,0 +1 @@ +load_module modules.d/ngx_http_brotli_static_module.so; \ No newline at end of file diff --git a/angie/mod.dist/http-brotli.conf b/angie/mod.dist/http-brotli.conf new file mode 100644 index 0000000..2acbe61 --- /dev/null +++ b/angie/mod.dist/http-brotli.conf @@ -0,0 +1 @@ +load_module modules.d/ngx_http_brotli_filter_module.so; \ No newline at end of file diff --git a/angie/mod.dist/http-otel.conf b/angie/mod.dist/http-otel.conf new file mode 100644 index 0000000..1cdafc5 --- /dev/null +++ b/angie/mod.dist/http-otel.conf @@ -0,0 +1 @@ +load_module modules.d/ngx_otel_module.so; \ No newline at end of file diff --git a/angie/mod.dist/http-postgres.conf b/angie/mod.dist/http-postgres.conf new file mode 100644 index 0000000..5048d82 --- /dev/null +++ b/angie/mod.dist/http-postgres.conf @@ -0,0 +1 @@ +load_module modules.d/ngx_postgres_module.so; \ No newline at end of file diff --git a/angie/mod.dist/http-rtmp.conf b/angie/mod.dist/http-rtmp.conf new file mode 100644 index 0000000..a4bc030 --- /dev/null +++ b/angie/mod.dist/http-rtmp.conf @@ -0,0 +1 @@ +load_module modules.d/ngx_rtmp_module.so; \ No newline at end of file diff --git a/angie/mod.dist/http-sts.conf b/angie/mod.dist/http-sts.conf new file mode 100644 index 0000000..01e70c2 --- /dev/null +++ b/angie/mod.dist/http-sts.conf @@ -0,0 +1 @@ +load_module modules.d/ngx_http_stream_server_traffic_status_module.so; \ No newline at end of file diff --git a/angie/mod.dist/http-vts.conf b/angie/mod.dist/http-vts.conf new file mode 100644 index 0000000..0dfb96f --- /dev/null +++ b/angie/mod.dist/http-vts.conf @@ -0,0 +1 @@ +load_module modules.d/ngx_http_vhost_traffic_status_module.so; \ No newline at end of file diff --git a/angie/mod.dist/http-zstd-static.conf b/angie/mod.dist/http-zstd-static.conf new file mode 100644 index 0000000..e1f6190 --- /dev/null +++ b/angie/mod.dist/http-zstd-static.conf @@ -0,0 +1 @@ +load_module modules.d/ngx_http_zstd_static_module.so; \ No newline at end of file diff --git a/angie/mod.dist/http-zstd.conf b/angie/mod.dist/http-zstd.conf new file mode 100644 index 0000000..cb586bc --- /dev/null +++ b/angie/mod.dist/http-zstd.conf @@ -0,0 +1 @@ +load_module modules.d/ngx_http_zstd_filter_module.so; \ No newline at end of file diff --git a/angie/mod.dist/stream-sts.conf b/angie/mod.dist/stream-sts.conf new file mode 100644 index 0000000..f0807d9 --- /dev/null +++ b/angie/mod.dist/stream-sts.conf @@ -0,0 +1 @@ +load_module modules.d/ngx_stream_server_traffic_status_module.so; \ No newline at end of file diff --git a/angie/modsecurity.dist/rules.conf b/angie/modsecurity.dist/rules.conf new file mode 100644 index 0000000..d0e004a --- /dev/null +++ b/angie/modsecurity.dist/rules.conf @@ -0,0 +1,33 @@ +Include modsecurity.conf + +# To enable the OWASP CRS, please perform the following steps: +# +# 1. Checkout Core Rule Set from GitHub and create config files as shown below: +# +# version='v4.6.0' +# uri="https://github.com/coreruleset/coreruleset/archive/refs/tags/${version}.tar.gz" +# dst_dir='/etc/angie/modsecurity/coreruleset' +# w=$(mktemp -d) ; : "${w:?}" +# cd "$w/" +# tarball="coreruleset.tar.gz" +# curl -Lo "${tarball}" "${uri}" +# mkdir coreruleset +# tar -C ./coreruleset --strip-components=1 -xf "${tarball}" +# rm -f "${tarball}" ; unset tarball +# for p in \ +# crs-setup.conf \ +# rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf \ +# rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf \ +# ; do +# src="coreruleset/$p.example" +# dst="${dst_dir}/$p" +# [ -f "${src}" ] || continue +# [ -d "${dst%/*}" ] || mkdir -p "${dst%/*}" +# cp -nv "${src}" "${dst}" +# done +# rm -rf "${w:?}/" ; unset w +# +# 2. Uncomment both 'Include' directives below + +#Include coreruleset/crs-setup.conf +#Include coreruleset/rules/*.conf \ No newline at end of file diff --git a/angie/snip.dist/deny-dotfiles b/angie/snip.dist/deny-dotfiles new file mode 100644 index 0000000..5f11f8d --- /dev/null +++ b/angie/snip.dist/deny-dotfiles @@ -0,0 +1,3 @@ +location ~ /\. { + include snip.d/internal-area; +} \ No newline at end of file diff --git a/angie/snip.dist/disable-compression.j2 b/angie/snip.dist/disable-compression.j2 new file mode 100644 index 0000000..3df37eb --- /dev/null +++ b/angie/snip.dist/disable-compression.j2 @@ -0,0 +1,8 @@ +{#- safe to specify all the time -#} +gzip off; +{%- set extra_comp_modules = ['brotli', 'zstd'] -%} +{%- set modules = ( env.NGX_HTTP_MODULES or '' ) | str_split_to_list -%} +{%- set comp_modules = modules | list_intersect(extra_comp_modules) | sort -%} +{%- for comp in comp_modules %} +{{ comp }} off; +{%- endfor %} \ No newline at end of file diff --git a/angie/snip.dist/empty-favicon b/angie/snip.dist/empty-favicon new file mode 100644 index 0000000..bc796c3 --- /dev/null +++ b/angie/snip.dist/empty-favicon @@ -0,0 +1,4 @@ +location = /favicon.ico { + empty_gif; + expires 1d; +} \ No newline at end of file diff --git a/angie/snip.dist/fastcgi-location b/angie/snip.dist/fastcgi-location new file mode 100644 index 0000000..9e99f1e --- /dev/null +++ b/angie/snip.dist/fastcgi-location @@ -0,0 +1,5 @@ +try_files $fastcgi_script_name =444; + +## bypass the fact that try_files resets $fastcgi_path_info +## see: https://trac.nginx.org/nginx/ticket/321 +set $path_info $fastcgi_path_info; \ No newline at end of file diff --git a/angie/snip.dist/internal-area b/angie/snip.dist/internal-area new file mode 100644 index 0000000..08bc53d --- /dev/null +++ b/angie/snip.dist/internal-area @@ -0,0 +1,5 @@ +## always sourced by snip.d/deny-dotfiles + +access_log off; +log_not_found off; +internal; \ No newline at end of file diff --git a/angie/snip.dist/ssl-intermediate.j2 b/angie/snip.dist/ssl-intermediate.j2 new file mode 100644 index 0000000..59411f9 --- /dev/null +++ b/angie/snip.dist/ssl-intermediate.j2 @@ -0,0 +1,2 @@ +{%- set ssl_profile = j2cfg.tls.profiles.intermediate -%} +{% include 'ssl-profile.j2m' %} diff --git a/angie/snip.dist/ssl-modern.j2 b/angie/snip.dist/ssl-modern.j2 new file mode 100644 index 0000000..d676aa0 --- /dev/null +++ b/angie/snip.dist/ssl-modern.j2 @@ -0,0 +1,2 @@ +{%- set ssl_profile = j2cfg.tls.profiles.modern -%} +{% include 'ssl-profile.j2m' %} diff --git a/angie/snip.dist/ssl-old.j2 b/angie/snip.dist/ssl-old.j2 new file mode 100644 index 0000000..172552e --- /dev/null +++ b/angie/snip.dist/ssl-old.j2 @@ -0,0 +1,2 @@ +{%- set ssl_profile = j2cfg.tls.profiles.old -%} +{% include 'ssl-profile.j2m' %} diff --git a/angie/snip.dist/ssl-profile.j2m b/angie/snip.dist/ssl-profile.j2m new file mode 100644 index 0000000..22539a8 --- /dev/null +++ b/angie/snip.dist/ssl-profile.j2m @@ -0,0 +1,30 @@ +{%- if ssl_profile.protocols %} +ssl_protocols {{ ssl_profile.protocols }}; +{%- endif %} +{%- if ssl_profile.prefer_server_ciphers %} +ssl_prefer_server_ciphers on; +{%- else %} +ssl_prefer_server_ciphers off; +{%- endif %} +{%- if ssl_profile.ciphers %} +ssl_ciphers {{ ssl_profile.ciphers }}; +{%- endif %} +{%- if ssl_profile.dhparam %} +ssl_dhparam {{ ssl_profile.dhparam }}; +{%- endif %} +{%- if ssl_profile.ecdh_curve %} +ssl_ecdh_curve {{ ssl_profile.ecdh_curve }}; +{%- endif %} + +{%- if ssl_profile.session_cache %} +ssl_session_cache {{ ssl_profile.session_cache }}; +{%- endif %} +{%- if ssl_profile.session_timeout %} +ssl_session_timeout {{ ssl_profile.session_timeout }}; +{%- endif %} +{%- if ssl_profile.session_tickets %} +ssl_session_tickets {{ ssl_profile.session_tickets }}; +{%- endif %} +{%- if ssl_profile.session_ticket_key %} +ssl_session_ticket_key {{ ssl_profile.session_ticket_key }}; +{%- endif %} diff --git a/angie/static.dist/robots.txt b/angie/static.dist/robots.txt new file mode 100644 index 0000000..1f53798 --- /dev/null +++ b/angie/static.dist/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/angie/tls.dist/dh1024.pem b/angie/tls.dist/dh1024.pem new file mode 100644 index 0000000..c363bca --- /dev/null +++ b/angie/tls.dist/dh1024.pem @@ -0,0 +1,5 @@ +-----BEGIN DH PARAMETERS----- +MIGLAoGBALcJ4VP7JatjdktQgjKHMzDPOgRKWMv0ByLCIoBKSFHzqiIVB6kHt/yw +/O5S/OsCt7RYlWIEuxgpus+1r3rVLQX9QdyRyqF70EVtX+5CeoU9xmz9Lxav9r9x +9tG2TYQbkTBke1K8wS+MZefPVDGIjPFCQPvQdhs9krKmQoTzmtPvAgECAgIArw== +-----END DH PARAMETERS----- diff --git a/angie/tls.dist/dh2048.pem b/angie/tls.dist/dh2048.pem new file mode 100644 index 0000000..9e51c6f --- /dev/null +++ b/angie/tls.dist/dh2048.pem @@ -0,0 +1,8 @@ +-----BEGIN DH PARAMETERS----- +MIIBDAKCAQEAq4G33yzu2B72KyBCcq8rj9fEMozlqvirKEEPbI6h3vZBsA9vxMeB +y5Wlgd9SpQrgXZZDKIfVUYOKiD2bOjxPMurI7mVLOi8YmSbJsf6f29rbUxtSiasH +ekhFcxHgUHyggKKgZ8JY2PpH6AADDDs36yQVQDAGk4ioKjYs60Z+o1a1cFAxxRqc +BWteSws+tH7d5BsS3GVkUwSokuMtIKNhsQ8zn/fQZ8WFtc3fvc3uFy61snJPGI77 +YM8rtkP5JOdWO2bAHiuLbllOZecynrM20B2XchErJahoIoTlnEs9aJLIQrYQmJ/R +NRopooR4cbCpTljlZKurSk9CZzE3zzRg3wIBAgICAOE= +-----END DH PARAMETERS----- diff --git a/angie/tls.dist/ffdhe2048.pem b/angie/tls.dist/ffdhe2048.pem new file mode 100644 index 0000000..9b182b7 --- /dev/null +++ b/angie/tls.dist/ffdhe2048.pem @@ -0,0 +1,8 @@ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz ++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a +87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 +YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi +7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD +ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== +-----END DH PARAMETERS----- diff --git a/angie/tls.dist/ffdhe3072.pem b/angie/tls.dist/ffdhe3072.pem new file mode 100644 index 0000000..fb31ccd --- /dev/null +++ b/angie/tls.dist/ffdhe3072.pem @@ -0,0 +1,11 @@ +-----BEGIN DH PARAMETERS----- +MIIBiAKCAYEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz ++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a +87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 +YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi +7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD +ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3 +7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32 +nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZsYu +N///////////AgEC +-----END DH PARAMETERS----- diff --git a/angie/tls.dist/ffdhe4096.pem b/angie/tls.dist/ffdhe4096.pem new file mode 100644 index 0000000..3cf0fcb --- /dev/null +++ b/angie/tls.dist/ffdhe4096.pem @@ -0,0 +1,13 @@ +-----BEGIN DH PARAMETERS----- +MIICCAKCAgEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz ++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a +87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 +YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi +7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD +ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3 +7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32 +nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZp4e +8W5vUsMWTfT7eTDp5OWIV7asfV9C1p9tGHdjzx1VA0AEh/VbpX4xzHpxNciG77Qx +iu1qHgEtnmgyqQdgCpGBMMRtx3j5ca0AOAkpmaMzy4t6Gh25PXFAADwqTs6p+Y0K +zAqCkc3OyX3Pjsm1Wn+IpGtNtahR9EGC4caKAH5eZV9q//////////8CAQI= +-----END DH PARAMETERS----- diff --git a/apt/prefs.backports b/apt/prefs.backports new file mode 100644 index 0000000..8658903 --- /dev/null +++ b/apt/prefs.backports @@ -0,0 +1,4 @@ +## example: +# Package: src:curl +# Pin: release n=bookworm-backports +# Pin-Priority: 600 diff --git a/apt/sources.angie b/apt/sources.angie new file mode 100644 index 0000000..cd6c48c --- /dev/null +++ b/apt/sources.angie @@ -0,0 +1,5 @@ +Types: deb +URIs: http://download.angie.software/angie/debian/12 +Suites: bookworm +Components: main +Signed-By: /etc/apt/keyrings/angie.gpg.asc diff --git a/apt/sources.debian b/apt/sources.debian new file mode 100644 index 0000000..75936f9 --- /dev/null +++ b/apt/sources.debian @@ -0,0 +1,15 @@ +Types: deb +URIs: http://deb.debian.org/debian +## local development +# URIs: http://127.0.0.1:8081/repository/proxy_apt_debian +Suites: bookworm bookworm-updates bookworm-proposed-updates bookworm-backports +Components: main +Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg + +Types: deb +## local development +# URIs: http://127.0.0.1:8081/repository/proxy_apt_debian-security +URIs: http://deb.debian.org/debian-security +Suites: bookworm-security +Components: main +Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg diff --git a/build-scripts/image-base.sh b/build-scripts/image-base.sh new file mode 100755 index 0000000..ca6988b --- /dev/null +++ b/build-scripts/image-base.sh @@ -0,0 +1,51 @@ +#!/bin/sh +set -ef +cd "$(dirname "$0")/.." + +set -a +BUILDAH_FORMAT="${BUILDAH_FORMAT:-docker}" +BUILDAH_ISOLATION="${BUILDAH_ISOLATION:-chroot}" +BUILDAH_NETWORK="${BUILDAH_NETWORK:-host}" +set +a + +PYTHONTAG="${PYTHONTAG:-3.11.10-slim-bookworm}" + +grab_site_packages() { + podman run \ + --pull=always --rm \ + --entrypoint='[]' \ + --user=nobody:nogroup \ + -e LANG=C.UTF-8 \ + -e LC_ALL=C.UTF-8 \ + -e MALLOC_ARENA_MAX=2 \ + -e PYTHONUNBUFFERED=1 \ + -e PYTHONDONTWRITEBYTECODE=1 \ + "$1" \ + python3 -c 'import site;print(site.getsitepackages()[0])' +} + +PYTHON_SITE_PACKAGES=$(grab_site_packages "docker.io/python:${PYTHONTAG}") +[ -n "${PYTHON_SITE_PACKAGES:?}" ] + +img="docker.io/rockdrilla/angie-conv:v0.0.1-base" + +buildah bud --network="${BUILDAH_NETWORK}" \ + -f ./Dockerfile.base \ + -t "${img}" \ + --pull=missing --no-cache --squash \ + --build-arg "PYTHONTAG=${PYTHONTAG}" \ + --env "PYTHON_SITE_PACKAGES=${PYTHON_SITE_PACKAGES}" \ + --unsetenv GPG_KEY \ + --unsetenv PYTHON_PIP_VERSION \ + --unsetenv PYTHON_SETUPTOOLS_VERSION \ + --unsetenv PYTHON_GET_PIP_SHA256 \ + --unsetenv PYTHON_GET_PIP_URL \ + + +c=$(buildah from --pull=never "${img}") || true +if [ -z "$c" ] ; then + buildah rmi -f "${img}" + exit 1 +fi +buildah config --created-by /usr/local/share/Dockerfile.base "$c" +buildah commit --rm --squash "$c" "${img}" diff --git a/build-scripts/image-deps.sh b/build-scripts/image-deps.sh new file mode 100755 index 0000000..53da228 --- /dev/null +++ b/build-scripts/image-deps.sh @@ -0,0 +1,16 @@ +#!/bin/sh +set -ef +cd "$(dirname "$0")/.." + +set -a +BUILDAH_FORMAT="${BUILDAH_FORMAT:-docker}" +BUILDAH_ISOLATION="${BUILDAH_ISOLATION:-chroot}" +BUILDAH_NETWORK="${BUILDAH_NETWORK:-host}" +set +a + +img="docker.io/rockdrilla/angie-conv:v0.0.1-deps" + +exec buildah bud \ + -f ./Dockerfile.deps \ + -t "${img}" \ + --pull=missing --no-cache diff --git a/build-scripts/image.sh b/build-scripts/image.sh new file mode 100755 index 0000000..7623552 --- /dev/null +++ b/build-scripts/image.sh @@ -0,0 +1,35 @@ +#!/bin/sh +set -ef +cd "$(dirname "$0")/.." + +set -a +BUILDAH_FORMAT="${BUILDAH_FORMAT:-docker}" +BUILDAH_ISOLATION="${BUILDAH_ISOLATION:-chroot}" +BUILDAH_NETWORK="${BUILDAH_NETWORK:-host}" +set +a + +ANGIE_VERSION="${ANGIE_VERSION:-1.6.2}" + +## likely the same as in https://pkg.go.dev/strconv#ParseBool +gobool_to_int() { + ## local value=$1 + ## local default=$2 + case "${1:-_}" in + 1 | [Tt] | [Tt][Rr][Uu][Ee] ) echo 1 ;; + 0 | [Ff] | [Ff][Aa][Ll][Ss][Ee] ) echo 0 ;; + * ) echo "${2:-error}" ;; + esac +} + +NGX_DEBUG=$(gobool_to_int "${1:-0}" 0) +case "${NGX_DEBUG}" in +0 ) img="docker.io/rockdrilla/angie-conv:v0.0.1-${ANGIE_VERSION}" ;; +1 ) img="docker.io/rockdrilla/angie-conv:v0.0.1-${ANGIE_VERSION}-debug" ;; +esac + +exec buildah bud \ + -f ./Dockerfile \ + -t "${img}" \ + --env "ANGIE_VERSION=${ANGIE_VERSION}" \ + --env "NGX_DEBUG=${NGX_DEBUG}" \ + --pull=missing --no-cache diff --git a/extra-scripts/certifi-extras.sh b/extra-scripts/certifi-extras.sh new file mode 100755 index 0000000..2e509f7 --- /dev/null +++ b/extra-scripts/certifi-extras.sh @@ -0,0 +1,91 @@ +#!/bin/sh +set -ef + +certifi_uri="https://raw.githubusercontent.com/certifi/python-certifi/${CERTIFI_COMMIT:?}/certifi/cacert.pem" +dst_dir=/usr/local/share/ca-certificates + +w=$(mktemp -d) ; : "${w:?}" +w_cleanup() { + [ -z "$w" ] || ls -lA "$w/" + [ -z "$w" ] || rm -rf "$w" + unset w + exit "${1:-0}" +} + +curl -sSL "${certifi_uri}" > "$w/certifi.crt" + +def_bundle='/etc/ssl/certs/ca-certificates.crt' + +openssl-cert-auto-pem.sh "${def_bundle}" > "$w/cacert.pem" +openssl-cert-auto-pem.sh "$w/certifi.crt" > "$w/certifi.pem" +[ -s "$w/cacert.pem" ] || w_cleanup 1 +[ -s "$w/certifi.pem" ] || w_cleanup 1 + +bundle_offsets() { + awk ' + BEGIN { + OFS = "," + m_begin="-----BEGIN CERTIFICATE-----" + m_end="-----END CERTIFICATE-----" + i_begin = 0 + } + $0 == m_begin { i_begin = NR ; } + $0 == m_end { + if (i_begin > 0) { + print i_begin,NR + i_begin = 0 + } + } + ' "$1" +} + +bundle_offsets "$w/cacert.pem" > "$w/cacert.off" +bundle_offsets "$w/certifi.pem" > "$w/certifi.off" +[ -s "$w/cacert.off" ] || w_cleanup 1 +[ -s "$w/certifi.off" ] || w_cleanup 1 + +bundle_fingerprints() { + local a + while read -r a ; do + [ -n "$a" ] || continue + + { + sed -ne "${a}p" "$1" | openssl x509 -noout -fingerprint -sha256 \ + || \ + sed -ne "${a}p" "$1" | openssl x509 -noout -fingerprint + } | tr '[:upper:]' '[:lower:]' + done < "$2" +} + +bundle_fingerprints "$w/cacert.pem" "$w/cacert.off" | sort -uV > "$w/cacert.fp" +bundle_fingerprints "$w/certifi.pem" "$w/certifi.off" | sort -uV > "$w/certifi.fp" +[ -s "$w/cacert.fp" ] || w_cleanup 1 +[ -s "$w/certifi.fp" ] || w_cleanup 1 + +set +e +grep -Fxv -f "$w/cacert.fp" "$w/certifi.fp" > "$w/diff.fp" +set -e + +if [ -s "$w/diff.fp" ] ; then + set +e + grep -Fxn -f "$w/diff.fp" "$w/certifi.fp" | cut -d : -f 1 > "$w/records.diff" + set -e + + terse_fingerprint() { + cut -d = -f 2- | tr -cd '[:alnum:]' + } + + mkdir "$w/extras" + + while read -r n ; do + [ -n "$n" ] || continue + + fp=$(sed -ne "${n}p" "$w/certifi.fp" | terse_fingerprint) + off=$(sed -ne "${n}p" "$w/certifi.off") + sed -ne "${off}p" "$w/certifi.pem" | openssl x509 > "${dst_dir}/certifi-${fp}.crt" + done < "$w/records.diff" +fi + +rm -rf "$w" ; unset w + +exec update-ca-certificates --fresh diff --git a/extra-scripts/gpg-batch.sh b/extra-scripts/gpg-batch.sh new file mode 100755 index 0000000..244ea54 --- /dev/null +++ b/extra-scripts/gpg-batch.sh @@ -0,0 +1,45 @@ +#!/bin/sh +set -ef + +: "${GPG_KEYSERVER:=hkps://keyserver.ubuntu.com}" + +[ $# != 0 ] || exit 1 + +case "$1" in +1 | start ) + [ -n "${GNUPGHOME}" ] || exit 1 + [ -d "${GNUPGHOME}" ] || exit 1 + + cd "${GNUPGHOME}" + cat > gpg.conf <<-EOF + quiet + batch + trust-model always + no-auto-check-trustdb + ignore-time-conflict + keyid-format 0xlong + keyserver ${GPG_KEYSERVER} + EOF + cat > dirmngr.conf <<-EOF + quiet + batch + keyserver ${GPG_KEYSERVER} + EOF + gpg --update-trustdb >/dev/null 2>&1 + gpg --list-keys >/dev/null 2>&1 + dirmngr >/dev/null 2>&1 +;; +0 | stop ) + [ -n "${GNUPGHOME}" ] || exit 0 + [ -d "${GNUPGHOME}" ] || exit 1 + + cd "${GNUPGHOME}" + gpgconf --kill all + cd / + rm -rf "${GNUPGHOME}" +;; +* ) + exit 1 +;; +esac +exit 0 diff --git a/extra-scripts/gpg-export.sh b/extra-scripts/gpg-export.sh new file mode 100755 index 0000000..dda0678 --- /dev/null +++ b/extra-scripts/gpg-export.sh @@ -0,0 +1,28 @@ +#!/bin/sh +set -ef + +: "${1:?}" "${2:?}" + +w=$(mktemp -d) ; : "${w:?}" + +gpg_on() { gpg-batch.sh start ; } +gpg_off() { + cd / + gpg-batch.sh stop + unset GNUPGHOME + rm -rf "$w" + exit "${1:-0}" +} + +( + export GNUPGHOME="$w/.gnupg" + mkdir -m 0700 "${GNUPGHOME}" + gpg_on + + gpg --import "$1" + gpg --armor --export > "$w/export" + cat < "$w/export" > "$2" + gpg --show-keys "$2" + + gpg_off +) || gpg_off 1 diff --git a/image-entry.d/00-common.envsh b/image-entry.d/00-common.envsh new file mode 100644 index 0000000..72574e9 --- /dev/null +++ b/image-entry.d/00-common.envsh @@ -0,0 +1,238 @@ +#!/bin/sh + +volume_root='/run/angie' +tmp_dir="${volume_root}/tmp" +merged_root="${volume_root}/merged" +target_root="${volume_root}" +empty_dir='/var/lib/empty' + +have_envvar() { + [ -n "$1" ] || return 1 + grep -Ezq "^$1=" /proc/self/environ || return +} + +## unexporting variable in (POSIX) sh is PITA =/ +unexport() { + local ___k ___v + for ___k ; do + [ -n "${___k}" ] || continue + have_envvar "${___k}" || continue + + ___v=$(eval printf '%s' "\"\${${___k}}\"") + eval "unset ${___k}" + eval "${___k}=$(env printf '%s' \"\${___v}\")" + done +} + +## likely the same as in https://pkg.go.dev/strconv#ParseBool +gobool_to_int() { + ## local value=$1 + ## local default=$2 + case "${1:-_}" in + 1 | [Tt] | [Tt][Rr][Uu][Ee] ) echo 1 ;; + 0 | [Ff] | [Ff][Aa][Ll][Ss][Ee] ) echo 0 ;; + * ) echo "${2:-error}" ;; + esac +} + +[ -n "${__IEP_SRC:-}" ] || __IEP_SRC="$0" + +log_always() { + if [ "${IEP_DEBUG}" = 1 ] ; then + echo "# $(date +'%Y-%m-%d %H:%M:%S.%03N %z'): ${__IEP_SRC}${*:+: $*}" + else + echo "# ${__IEP_SRC}${*:+: $*}" + fi >&2 +} + +log() { + [ "${IEP_VERBOSE}" = 0 ] || log_always "$@" +} + +ln_s() { + if [ "${IEP_VERBOSE}" = 0 ] ; then + ln -s "$@" || return + else + ln -sv "$@" || return + fi +} +cp_a() { + if [ "${IEP_VERBOSE}" = 0 ] ; then + cp -a "$@" || return + else + cp -av "$@" || return + fi +} + +ln_cp() { + if [ -h "$1" ] ; then + ln_s "$(readlink -e "$1")" "$2" + else + cp_a "$1" "$2" + fi +} + +have_cmd() { command -v "$1" >/dev/null 2>&1 || return ; } + +strip_suffix() { printf '%s' "${1%"$2"}" | tr -s '/' ; } + +install_userdir() { + if [ "${IEP_ROOT}" = 1 ] ; then + install -d -o "${NGX_USER}" -g "${NGX_GROUP}" "$@" + else + install -d "$@" + fi +} + +expand_file_envsubst() { + local __ret __src __dst + + __ret=0 + for __src ; do + [ -n "${__src}" ] || continue + + if ! [ -f "${__src}" ] ; then + __ret=1 + log_always "file not found: ${__src}" + continue + fi + + case "${__src}" in + *.in ) ;; + * ) + __ret=1 + log "expand_file_envsubst: file name extension mismatch: ${__src}" + continue + ;; + esac + + __dst=$(strip_suffix "${__src}" '.in') + if [ -e "${__dst}" ] ; then + __ret=1 + log "expand_file_envsubst: destination file already exists: ${__dst}" + continue + fi + + log "Running envsubst: ${__src} -> ${__dst}" + envsubst.sh < "${__src}" > "${__dst}" || __ret=1 + done + return ${__ret} +} + +expand_file_j2cfg() { + j2cfg-single "$@" || return $? +} + +expand_dir_envsubst() { + local __template_list __have_args __ret __orig_file + + __template_list=$(mktemp) || return + + find "$@" -follow -type f -name '*.in' \ + | sort -uV > "${__template_list}" + + __have_args="${ENVSUBST_ARGS:+1}" + if [ -z "${__have_args}" ] ; then + ## optimize envsubst.sh invocation by caching argument list + ## ref: envsubst.sh + ENVSUBST_ARGS=$(mktemp) || return + envsubst-args.sh > "${ENVSUBST_ARGS}" + export ENVSUBST_ARGS + fi + + __ret=0 + while read -r __orig_file ; do + [ -n "${__orig_file}" ] || continue + expand_file_envsubst "${__orig_file}" || __ret=1 + done < "${__template_list}" + + if [ -z "${__have_args}" ] ; then + rm -f "${ENVSUBST_ARGS}" ; unset ENVSUBST_ARGS + fi + unset __have_args + + rm -f "${__template_list}" ; unset __template_list + + return ${__ret} +} + +expand_dir_j2cfg() { + local __template_list __ret + + __template_list=$(mktemp) || return + + find "$@" -follow -type f -name '*.j2' -printf '%p\0' \ + | sort -zuV > "${__template_list}" + + __ret=0 + if [ -s "${__template_list}" ] ; then + xargs -0r -n 1000 -a "${__template_list}" \ + j2cfg-multi < /dev/null || __ret=1 + fi + + rm -f "${__template_list}" ; unset __template_list + + return ${__ret} +} + +is_builtin_module() { + [ -n "${1:-}" ] || return 1 + [ -n "${2:-}" ] || return 1 + + [ -f "/etc/angie/builtin.$1" ] || return 1 + [ -s "/etc/angie/builtin.$1" ] || return 1 + + grep -Fxq -e "$2" "/etc/angie/builtin.$1" || return 1 +} + +append_list() { + if [ -n "$2" ] ; then + printf '%s' "${1:-}${1:+ }$2" + else + printf '%s' "$1" + fi +} + +prepend_list() { + if [ -n "$2" ] ; then + printf '%s' "$2${1:+ }${1:-}" + else + printf '%s' "$1" + fi +} + +list_have_item() { + [ -n "$2" ] || return 1 + case " $1 " in + *" $2 "* ) return 0 ;; + * ) return 1 ;; + esac +} + +normalize_list() { + [ -n "$1" ] || return 0 + + printf '%s' "$1" \ + | tr -s '[:space:]' ' ' \ + | sed -zE 's/^ //;s/ $//' +} + +sort_dedup_list() { + [ -n "$1" ] || return 0 + + printf '%s' "$1" \ + | tr -s '[:space:]' '\n' | sort -uV | paste -sd ' ' \ + | sed -zE 's/^\s+//;s/\s+$//' +} + +float_div() { + mawk -v "a=$1" -v "b=$2" 'BEGIN{print a/b;exit;}' &2 + + if [ ${nofile_hard} -lt ${NGX_WORKER_RLIMIT_NOFILE} ] ; then + ulimit -Hn "${NGX_WORKER_RLIMIT_NOFILE}" + nofile_hard=$(ulimit -Hn) + fi + if [ ${nofile_hard} -lt ${NGX_WORKER_RLIMIT_NOFILE} ] ; then + log_always "lowering NGX_WORKER_RLIMIT_NOFILE to ${nofile_hard} due to hard limit" + NGX_WORKER_RLIMIT_NOFILE=${nofile_hard} + fi + + if [ ${nofile_soft} -lt ${NGX_WORKER_RLIMIT_NOFILE} ] ; then + ulimit -Sn "${NGX_WORKER_RLIMIT_NOFILE}" + fi + + log_always "Limits after:" + sed -En '1p;/open files/p' < /proc/$$/limits >&2 + fi + unset nofile_soft nofile_hard nofile_ok + + export NGX_WORKER_RLIMIT_NOFILE +fi + +if [ -z "${NGX_WORKER_RLIMIT_NOFILE:-}" ] ; then + nofile_limit=$(ulimit -Hn) + nofile_kind="'ulimit:nofile'" +else + nofile_limit=${NGX_WORKER_RLIMIT_NOFILE} + nofile_kind='NGX_WORKER_RLIMIT_NOFILE' +fi +if [ ${nofile_limit} -lt ${NGX_WORKER_CONNECTIONS} ] ; then + log_always "WARNING: ${nofile_kind} is less than NGX_WORKER_CONNECTIONS (${nofile_limit} < ${NGX_WORKER_CONNECTIONS})" +else + ratio=$(float_div "${nofile_limit}" "${NGX_WORKER_CONNECTIONS}") + case "${ratio}" in + 1 | 1.* ) + log_always "WARNING: \"${nofile_kind}/NGX_WORKER_CONNECTIONS\" ratio is too low (=${ratio})" + ;; + esac + unset ratio +fi +unset nofile_limit nofile_kind + +unset _NGX_WORKER_PROCESSES _NGX_WORKER_PRIORITY _NGX_WORKER_RLIMIT_NOFILE _NGX_WORKER_CONNECTIONS _NGX_WORKER_AIO_REQUESTS diff --git a/image-entry.d/20-http.envsh b/image-entry.d/20-http.envsh new file mode 100755 index 0000000..60379b3 --- /dev/null +++ b/image-entry.d/20-http.envsh @@ -0,0 +1,17 @@ +#!/bin/sh + +if [ "${NGX_HTTP}" = 0 ] ; then + unset NGX_HTTP_MODULES NGX_HTTP_CONFLOAD NGX_HTTP_CACHES NGX_HTTP_WEBROOT +else + unset default_caches + default_caches='client_temp fastcgi_temp proxy_temp scgi_temp uwsgi_temp' + + set -a + NGX_HTTP_MODULES="${NGX_HTTP_MODULES:-}" + NGX_HTTP_CONFLOAD="${NGX_HTTP_CONFLOAD:-}" + NGX_HTTP_CACHES=$(sort_dedup_list "${default_caches} ${NGX_HTTP_CACHES:-}") + NGX_HTTP_WEBROOT="${NGX_HTTP_WEBROOT:-/etc/angie/static.d}" + set +a + + unset default_caches +fi diff --git a/image-entry.d/21-http-modules.envsh b/image-entry.d/21-http-modules.envsh new file mode 100755 index 0000000..c02de83 --- /dev/null +++ b/image-entry.d/21-http-modules.envsh @@ -0,0 +1,109 @@ +#!/bin/sh + +if [ "${NGX_HTTP}" = 0 ] ; then + unset NGX_HTTP_NO_PROXY NGX_HTTP_WITH_MODSECURITY +else + NGX_HTTP_NO_PROXY=$(gobool_to_int "${NGX_HTTP_NO_PROXY:-0}" 0) + export NGX_HTTP_NO_PROXY + if [ "${NGX_HTTP_NO_PROXY}" = 0 ] ; then + NGX_HTTP_CONFLOAD=$(append_list "${NGX_HTTP_CONFLOAD}" proxy) + fi + + unset http_modules http_confload + http_modules= + http_confload="${NGX_HTTP_CONFLOAD:-}" + + if [ -n "${NGX_HTTP_MODULES}" ] ; then + ## angie-module-lua: depends on angie-module-ndk + ## angie-module-set-misc: depends on angie-module-ndk + + # unset want_ndk + # want_ndk=0 + # if list_have_item "${NGX_HTTP_MODULES}" lua ; then + # want_ndk=1 + # elif list_have_item "${NGX_HTTP_MODULES}" set-misc ; then + # want_ndk=1 + # fi + # if [ ${want_ndk} = 1 ] ; then + # NGX_HTTP_MODULES=$(prepend_list "${NGX_HTTP_MODULES}" ndk) + # fi + # unset want_ndk + NGX_HTTP_MODULES=$( + printf '%s' "${NGX_HTTP_MODULES}" \ + | sed -zE 's/(\s|^)(lua|set-misc)(\s|$)/\1ndk \2\3/g' + ) + fi + + ## filter out builtin http modules + unset i + for i in ${NGX_HTTP_MODULES:-} ; do + [ -n "$i" ] || continue + + case "$i" in + */* | *\** | *\?* ) + log_always "module '$i' is not legal, skipping" + continue + ;; + esac + + if is_builtin_module http "$i" ; then + log "$i is builtin module, moving to NGX_HTTP_CONFLOAD" + http_confload=$(append_list "${http_confload}" "$i") + continue + fi + + ## naive deduplication + if list_have_item "${http_modules}" "$i" ; then + log "$i is already specified" + continue + fi + + http_modules=$(append_list "${http_modules}" "$i") + done + unset i + + ## grpc depends on http/2 + if list_have_item "${NGX_HTTP_CONFLOAD}" grpc ; then + unset want_http2 + want_http2=0 + if ! list_have_item "${NGX_HTTP_CONFLOAD}" v2 ; then + want_http2=1 + fi + if [ "${want_http2}" = 1 ] ; then + NGX_HTTP_CONFLOAD=$(append_list "${NGX_HTTP_CONFLOAD}" v2) + fi + unset want_http2 + fi + + set -a + NGX_HTTP_MODULES="${http_modules}" + NGX_HTTP_CONFLOAD=$(sort_dedup_list "${http_confload}") + set +a + + unset http_modules http_confload + + ## quirk: angie-module-modsecurity + unset NGX_HTTP_WITH_MODSECURITY + NGX_HTTP_WITH_MODSECURITY=0 + while : ; do + if ! list_have_item "${NGX_HTTP_MODULES}" modsecurity ; then + break + fi + + for d in /angie/modules /etc/angie/modules /etc/angie/modules.dist ; do + [ -d "$d" ] || continue + [ -f "$d/ngx_http_modsecurity_module.so" ] || continue + if ! [ -h "$d/ngx_http_modsecurity_module.so" ] ; then + NGX_HTTP_WITH_MODSECURITY=1 + break + fi + done ; unset d + break ; done + export NGX_HTTP_WITH_MODSECURITY + + if [ "${NGX_HTTP_WITH_MODSECURITY}" = 1 ] ; then + set -a + NGX_DIRS_MERGE=$(sort_dedup_list "${NGX_DIRS_MERGE} modsecurity") + set +a + fi +fi diff --git a/image-entry.d/22-http-ssl.envsh b/image-entry.d/22-http-ssl.envsh new file mode 100755 index 0000000..6fc45ad --- /dev/null +++ b/image-entry.d/22-http-ssl.envsh @@ -0,0 +1,9 @@ +#!/bin/sh + +if [ "${NGX_HTTP}" = 0 ] ; then + unset NGX_HTTP_SSL_PROFILE +else + ## here should be SANE defaults (!) + NGX_HTTP_SSL_PROFILE="${NGX_HTTP_SSL_PROFILE:-intermediate}" + export NGX_HTTP_SSL_PROFILE +fi diff --git a/image-entry.d/23-http-max-ranges.envsh b/image-entry.d/23-http-max-ranges.envsh new file mode 100755 index 0000000..a8e2fc8 --- /dev/null +++ b/image-entry.d/23-http-max-ranges.envsh @@ -0,0 +1,30 @@ +#!/bin/sh + +if [ "${NGX_HTTP}" = 0 ] ; then + unset NGX_HTTP_MAX_RANGES +else + unset _NGX_HTTP_MAX_RANGES + ## here should be SANE defaults (!) + _NGX_HTTP_MAX_RANGES=16 + + if [ -z "${NGX_HTTP_MAX_RANGES:-}" ] ; then + unset NGX_HTTP_MAX_RANGES + else + case "${NGX_HTTP_MAX_RANGES}" in + ## allow values within [1;999] + [1-9] | [1-9][0-9] | [1-9][0-9][0-9] ) ;; + 0 ) + log_always "HTTP: Range/If-Range/Accept-Ranges support is disabled by NGX_HTTP_MAX_RANGES=0" + ;; + * ) + log_always "NGX_HTTP_MAX_RANGES: unrecognized value: ${NGX_HTTP_MAX_RANGES}" + log_always "setting NGX_HTTP_MAX_RANGES=${_NGX_HTTP_MAX_RANGES}" + NGX_HTTP_MAX_RANGES=${_NGX_HTTP_MAX_RANGES} + ;; + esac + + export NGX_HTTP_MAX_RANGES + fi + + unset _NGX_HTTP_MAX_RANGES +fi diff --git a/image-entry.d/24-http-forward-headers.envsh b/image-entry.d/24-http-forward-headers.envsh new file mode 100755 index 0000000..c44410f --- /dev/null +++ b/image-entry.d/24-http-forward-headers.envsh @@ -0,0 +1,56 @@ +#!/bin/sh + +if [ "${NGX_HTTP}" = 0 ] ; then + unset NGX_HTTP_TRANSPARENT_PROXY NGX_HTTP_FAKE_UA NGX_HTTP_X_FORWARDED +else + unset _NGX_HTTP_FAKE_UA _NGX_HTTP_X_FORWARDED + ## here should be SANE defaults (!) + _NGX_HTTP_FAKE_UA='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36' + _NGX_HTTP_X_FORWARDED=pass + + NGX_HTTP_TRANSPARENT_PROXY=$(gobool_to_int "${NGX_HTTP_TRANSPARENT_PROXY:-0}" 0) + export NGX_HTTP_TRANSPARENT_PROXY + if [ "${NGX_HTTP_TRANSPARENT_PROXY}" = 1 ] ; then + [ -n "${NGX_HTTP_FAKE_UA:-}" ] || NGX_HTTP_FAKE_UA=${_NGX_HTTP_FAKE_UA} + export NGX_HTTP_FAKE_UA + + if [ -n "${NGX_HTTP_X_FORWARDED:-}" ] ; then + case "${NGX_HTTP_X_FORWARDED}" in + [Rr][Ee][Mm][Oo][Vv][Ee] ) ;; + * ) + log_always "NGX_HTTP_X_FORWARDED: overridden to 'remove' due to NGX_HTTP_TRANSPARENT_PROXY=1" + ;; + esac + fi + NGX_HTTP_X_FORWARDED=remove + fi + + [ -n "${NGX_HTTP_X_FORWARDED:-}" ] || NGX_HTTP_X_FORWARDED=${_NGX_HTTP_X_FORWARDED} + case "${NGX_HTTP_X_FORWARDED}" in + [Pp][Aa][Ss][Ss] ) + ## adjust + NGX_HTTP_X_FORWARDED=pass + ;; + [Rr][Ee][Mm][Oo][Vv][Ee] ) + ## adjust + NGX_HTTP_X_FORWARDED=remove + ;; + * ) + unset x + x=$(gobool_to_int "${NGX_HTTP_X_FORWARDED}") + case "$x" in + 0 ) NGX_HTTP_X_FORWARDED=remove ;; + 1 ) NGX_HTTP_X_FORWARDED=pass ;; + * ) + log_always "NGX_HTTP_X_FORWARDED: unrecognized value: ${NGX_HTTP_X_FORWARDED}" + log_always "setting NGX_HTTP_X_FORWARDED=${_NGX_HTTP_X_FORWARDED}" + NGX_HTTP_X_FORWARDED=${_NGX_HTTP_X_FORWARDED} + ;; + esac + unset x + ;; + esac + export NGX_HTTP_X_FORWARDED + + unset _NGX_HTTP_FAKE_UA _NGX_HTTP_X_FORWARDED +fi diff --git a/image-entry.d/30-mail.envsh b/image-entry.d/30-mail.envsh new file mode 100755 index 0000000..9f84dc6 --- /dev/null +++ b/image-entry.d/30-mail.envsh @@ -0,0 +1,10 @@ +#!/bin/sh + +if [ "${NGX_MAIL}" = 0 ] ; then + unset NGX_MAIL_MODULES NGX_MAIL_CONFLOAD +else + set -a + NGX_MAIL_MODULES="${NGX_MAIL_MODULES:-}" + NGX_MAIL_CONFLOAD="${NGX_MAIL_CONFLOAD:-}" + set +a +fi diff --git a/image-entry.d/31-mail-modules.envsh b/image-entry.d/31-mail-modules.envsh new file mode 100755 index 0000000..483258e --- /dev/null +++ b/image-entry.d/31-mail-modules.envsh @@ -0,0 +1,42 @@ +#!/bin/sh + +if [ "${NGX_MAIL}" = 1 ] ; then + unset mail_modules mail_confload + mail_modules= + mail_confload="${NGX_MAIL_CONFLOAD:-}" + + ## filter out builtin mail modules + unset i + for i in ${NGX_MAIL_MODULES:-} ; do + [ -n "$i" ] || continue + + case "$i" in + */* | *\** | *\?* ) + log_always "module '$i' is not legal, skipping" + continue + ;; + esac + + if is_builtin_module mail "$i" ; then + log "$i is builtin module, moving to NGX_MAIL_CONFLOAD" + mail_confload=$(append_list "${mail_confload}" "$i") + continue + fi + + ## naive deduplication + if list_have_item "${mail_modules}" "$i" ; then + log "$i is already specified" + continue + fi + + mail_modules=$(append_list "${mail_modules}" "$i") + done + unset i + + set -a + NGX_MAIL_MODULES="${mail_modules}" + NGX_MAIL_CONFLOAD=$(sort_dedup_list "${mail_confload}") + set +a + + unset mail_modules mail_confload +fi diff --git a/image-entry.d/32-mail-ssl.envsh b/image-entry.d/32-mail-ssl.envsh new file mode 100755 index 0000000..170155b --- /dev/null +++ b/image-entry.d/32-mail-ssl.envsh @@ -0,0 +1,9 @@ +#!/bin/sh + +if [ "${NGX_MAIL}" = 0 ] ; then + unset NGX_MAIL_SSL_PROFILE +else + ## here should be SANE defaults (!) + NGX_MAIL_SSL_PROFILE="${NGX_MAIL_SSL_PROFILE:-intermediate}" + export NGX_MAIL_SSL_PROFILE +fi diff --git a/image-entry.d/40-stream.envsh b/image-entry.d/40-stream.envsh new file mode 100755 index 0000000..e1e90e7 --- /dev/null +++ b/image-entry.d/40-stream.envsh @@ -0,0 +1,10 @@ +#!/bin/sh + +if [ "${NGX_STREAM}" = 0 ] ; then + unset NGX_STREAM_MODULES NGX_STREAM_CONFLOAD +else + set -a + NGX_STREAM_MODULES="${NGX_STREAM_MODULES:-}" + NGX_STREAM_CONFLOAD="${NGX_STREAM_CONFLOAD:-}" + set +a +fi diff --git a/image-entry.d/41-stream-modules.envsh b/image-entry.d/41-stream-modules.envsh new file mode 100755 index 0000000..0e5bdc9 --- /dev/null +++ b/image-entry.d/41-stream-modules.envsh @@ -0,0 +1,42 @@ +#!/bin/sh + +if [ "${NGX_STREAM}" = 1 ] ; then + unset stream_modules stream_confload + stream_modules= + stream_confload="${NGX_STREAM_CONFLOAD:-}" + + ## filter out builtin stream modules + unset i + for i in ${NGX_STREAM_MODULES:-} ; do + [ -n "$i" ] || continue + + case "$i" in + */* | *\** | *\?* ) + log_always "module '$i' is not legal, skipping" + continue + ;; + esac + + if is_builtin_module stream "$i" ; then + log "$i is builtin module, moving to NGX_STREAM_CONFLOAD" + stream_confload=$(append_list "${stream_confload}" "$i") + continue + fi + + ## naive deduplication + if list_have_item "${stream_modules}" "$i" ; then + log "$i is already specified" + continue + fi + + stream_modules=$(append_list "${stream_modules}" "$i") + done + unset i + + set -a + NGX_STREAM_MODULES="${stream_modules}" + NGX_STREAM_CONFLOAD=$(sort_dedup_list "${stream_confload}") + set +a + + unset stream_modules stream_confload +fi diff --git a/image-entry.d/42-stream-ssl.envsh b/image-entry.d/42-stream-ssl.envsh new file mode 100755 index 0000000..3c660cb --- /dev/null +++ b/image-entry.d/42-stream-ssl.envsh @@ -0,0 +1,9 @@ +#!/bin/sh + +if [ "${NGX_STREAM}" = 0 ] ; then + unset NGX_STREAM_SSL_PROFILE +else + ## here should be SANE defaults (!) + NGX_STREAM_SSL_PROFILE="${NGX_STREAM_SSL_PROFILE:-intermediate}" + export NGX_STREAM_SSL_PROFILE +fi diff --git a/image-entry.d/70-flush-run-volume.sh b/image-entry.d/70-flush-run-volume.sh new file mode 100755 index 0000000..b4cf016 --- /dev/null +++ b/image-entry.d/70-flush-run-volume.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -f + +. /image-entry.d/00-common.envsh + +find "${volume_root}/" -mindepth 1 -exec rm -rf {} + + +[ -d "${tmp_dir}" ] || install -d -m 03777 "${tmp_dir}" +[ -d "${volume_root}/lock" ] || install_userdir "${volume_root}/lock" + +exit 0 diff --git a/image-entry.d/71-topmost-configs.sh b/image-entry.d/71-topmost-configs.sh new file mode 100755 index 0000000..cd71f89 --- /dev/null +++ b/image-entry.d/71-topmost-configs.sh @@ -0,0 +1,19 @@ +#!/bin/sh +set -f + +. /image-entry.d/00-common.envsh + +s=/etc/angie +d=${target_root} + +comps='' +[ "${NGX_HTTP}" = 0 ] || comps="${comps} http" +[ "${NGX_MAIL}" = 0 ] || comps="${comps} mail" +[ "${NGX_STREAM}" = 0 ] || comps="${comps} stream" + +for n in ${comps} ; do + ln_s "$s/ctx-$n.conf" "$d/" + ln_s "$s/mod-$n.conf" "$d/" +done + +exit 0 diff --git a/image-entry.d/72-merge-tree.sh b/image-entry.d/72-merge-tree.sh new file mode 100755 index 0000000..01cbc1a --- /dev/null +++ b/image-entry.d/72-merge-tree.sh @@ -0,0 +1,43 @@ +#!/bin/sh +set -ef + +. /image-entry.d/00-common.envsh + +[ -d "${merged_root}" ] || install -d "${merged_root}" + +remap_path_int() { + printf '%s' "$2/${1#*/}" +} + +remap_path() { + case "$1" in + /etc/angie/* ) remap_path_int "${1#/etc/angie/}" "$2" ;; + /angie/* ) remap_path_int "${1#/angie/}" "$2" ;; + ## misbehavior! + * ) + log_always "remap_path() doesn't know how to handle this path: '$1'" + return 1 + ;; + esac +} + +for n in j2cfg ${NGX_DIRS_MERGE} ; do + [ -n "$n" ] || continue + + merged_dir="${merged_root}/$n" + while read -r old_path ; do + [ -n "${old_path}" ] || continue + + new_path=$(remap_path "${old_path}" "${merged_dir}") + [ -n "${new_path}" ] + + new_dir="${new_path%/*}" + [ -d "${new_dir}" ] || mkdir -p "${new_dir}" + + ln_s "${old_path}" "${new_path}" + done <<-EOF + $(overlay-dir-list.sh "/etc/angie/$n.dist" "/etc/angie/$n" "/angie/$n") + EOF +done + +exit 0 diff --git a/image-entry.d/73-expand-templates.sh b/image-entry.d/73-expand-templates.sh new file mode 100755 index 0000000..016f040 --- /dev/null +++ b/image-entry.d/73-expand-templates.sh @@ -0,0 +1,63 @@ +#!/bin/sh +set -f + +. /image-entry.d/00-common.envsh + +## Angie: unset core variable +unset ANGIE ANGIE_BPF_MAPS + +[ "${NGX_STRICT_LOAD}" = 0 ] || set -e + +cd "${merged_root}/" + +expand_error_delim() { + IEP_DEBUG=0 log_always ' ----------------------------------- ' +} +unset expand_error_seen +expand_error() { + [ "${expand_error_seen:-}" != 1 ] || return + expand_error_seen=1 + expand_error_delim + log_always 'template expansion has failed' + if [ "${NGX_STRICT_LOAD}" = 1 ] ; then + t=15 + log_always "injecting delay for $t seconds" + expand_error_delim + sleep $t + exit 1 + fi + expand_error_delim +} + +merge_dirs= +for n in ${NGX_DIRS_MERGE} ; do + [ -n "$n" ] || continue + [ -d "$n" ] || continue + + merge_dirs="${merge_dirs} $n/" +done + +set -a +J2CFG_PATH="${merged_root}/j2cfg" +J2CFG_SEARCH_PATH="${merged_root}" +set +a + +## expand j2cfg templates first + +expand_dir_envsubst j2cfg/ || expand_error +expand_dir_j2cfg j2cfg/ || expand_error + +## expand other templates + +expand_dir_envsubst ${merge_dirs} || expand_error + +unset j2cfg_dump +j2cfg_dump="${volume_root}/diag.j2cfg.yml" + +j2cfg-dump > "${j2cfg_dump}" || expand_error + +export J2CFG_CONFIG="${j2cfg_dump}" + +expand_dir_j2cfg ${merge_dirs} || expand_error + +exit 0 diff --git a/image-entry.d/74-combine-tree.sh b/image-entry.d/74-combine-tree.sh new file mode 100755 index 0000000..efb4613 --- /dev/null +++ b/image-entry.d/74-combine-tree.sh @@ -0,0 +1,236 @@ +#!/bin/sh +set -f + +. /image-entry.d/00-common.envsh + +[ "${NGX_STRICT_LOAD}" = 0 ] || set -e + +load_error_delim() { + IEP_DEBUG=0 log_always ' ----------------------------------- ' +} +unset load_error_seen +load_error() { + [ "${load_error_seen:-}" != 1 ] || return + load_error_seen=1 + load_error_delim + log_always 'tree combine has failed' + if [ "${NGX_STRICT_LOAD}" = 1 ] ; then + t=15 + log_always "injecting delay for $t seconds" + load_error_delim + sleep $t + exit 1 + fi + load_error_delim +} + +dirs='cache lib log' +for n in ${dirs} ; do + s="/angie/$n" + d="${target_root}/$n" + + if [ -d "$s" ] ; then + ln_s "$s" "$d" + else + [ -d "$d" ] || install_userdir "$d" + fi +done + +d="${target_root}/cache" +for p in ${NGX_HTTP_CACHES:-} ; do + [ -d "$d/$p" ] || install_userdir "$d/$p" || load_error +done + +d="${target_root}/lib" +dirs='acme' +[ "${NGX_HTTP_WITH_MODSECURITY:-}" != 1 ] || dirs="${dirs} modsecurity" +for p in ${dirs} ; do + [ -d "$d/$p" ] || install_userdir "$d/$p" || load_error +done + +if [ "${NGX_HTTP_WITH_MODSECURITY:-}" = 1 ] ; then + d="${target_root}/log" + for p in modsecurity modsecurity/concurrent ; do + [ -d "$d/$p" ] || install_userdir "$d/$p" || load_error + done +fi + +## provide same symlinks as upstream (both Angie and nginx) docker images do +d="${target_root}/log" +[ -e "$d/access.log" ] || ln_s /dev/stdout "$d/access.log" || load_error +[ -e "$d/error.log" ] || ln_s /dev/stderr "$d/error.log" || load_error + +## NB: if any error occurs above then configuration is merely empty and/or broken + +remap_path() { + [ -n "$1" ] || return + + case "$1" in + "${merged_root}"/* ) echo "${target_root}${1#"${merged_root}"}" ;; + ## misbehavior! + * ) + log_always "remap_path() doesn't know how to handle this path: $1" + return 1 + ;; + esac +} + +while read -r old_path ; do + [ -n "${old_path}" ] || continue + + new_path=$(remap_path "${old_path}") + [ -n "${new_path}" ] + + new_dir="${new_path%/*}" + [ -d "${new_dir}" ] || mkdir -p "${new_dir}" + + ln_cp "${old_path}" "${new_path}" +done <<-EOF +$( + set +e + for n in j2cfg ${NGX_DIRS_MERGE} ; do + [ -n "$n" ] || continue + + [ -d "${merged_root}/$n" ] || continue + find "${merged_root}/$n/" ! -type d + done \ + | grep -Ev \ + -e "^${merged_root}/mod/[^/]+\.preseed\$" \ + | sort -V +) +EOF + +for n in ${NGX_DIRS_LINK} ; do + [ -n "$n" ] || continue + + if [ -e "${target_root}/$n" ] ; then continue ; fi + for d in "/angie/$n" "/etc/angie/$n" "/etc/angie/$n.dist" ; do + [ -d "$d" ] || continue + ln_s "$d" "${target_root}/$n" + break + done + + [ -d "${target_root}/$n" ] || { + log "missing required directory: ${target_root}/$n" + } +done + +## Angie modules are loaded in [strict] order! +combine_modules() { + [ -n "$1" ] || return 1 + local n + n="$1" ; shift + + [ $# -ne 0 ] || return 0 + + local i m src_dir dst_dir src_name dst_name src_path dst_path dst_dir + src_dir="${merged_root}/mod" + dst_dir="${volume_root}/load" + + i=0 + for m ; do + [ -n "$m" ] || continue + + case "$m" in + /* | */../* | *\** | *\?* ) + log_always "module config filename '$m' is not legal, skipping" + continue + ;; + esac + + case "$m" in + */* ) src_name="$m.conf" ;; + * ) src_name="$n-$m.conf" ;; + esac + dst_name=$(printf 'mod-%s-%02d-%s.conf' "$n" "$i" "$m" | tr -s '/_' '_') + + src_path="${src_dir}/${src_name}" + if ! [ -f "${src_path}" ] ; then + log_always "file ${src_name} is not found in ${src_dir}/" + load_error + log "file ${src_name} is skipped" + continue + fi + + dst_path="${dst_dir}/${dst_name}" + + ln_cp "${src_path}" "${dst_path}" + + i=$((i+1)) + done +} + +combine_confload() { + [ -n "$1" ] || return 1 + local n + n="$1" ; shift + + [ $# -ne 0 ] || return 0 + + local s src_dir dst_dir src_name dst_name src_path dst_path + src_dir="${merged_root}/conf" + dst_dir="${volume_root}/load" + + for s ; do + [ -n "$s" ] || continue + + case "$s" in + /* | */../* | *\** | *\?* ) + log_always "config filename '$s' is not legal, skipping" + continue + ;; + esac + + case "$s" in + */* ) src_name="$s.conf" ;; + * ) src_name="$n-$s.conf" ;; + esac + dst_name=$(printf '%s-%s.conf' "$n" "$s" | tr -s '/_' '_') + + dst_path="${dst_dir}/${dst_name}" + if [ -e "${dst_path}" ] ; then + log "${dst_path} already exists, skipping" + continue + fi + + src_path="${src_dir}/${src_name}" + if ! [ -f "${src_path}" ] ; then + log_always "file ${src_name} is not found in ${src_dir}/" + if [ "${NGX_ALLOW_MISSING_CONFLOAD:-}" != 1 ] ; then + load_error + log "file ${src_name} is skipped" + fi + continue + fi + + ln_cp "${src_path}" "${dst_path}" + done +} + +[ -d "${volume_root}/load" ] || install -d "${volume_root}/load" +find "${volume_root}/load/" -mindepth 1 -exec rm -rf {} + + +combine_modules core ${NGX_CORE_MODULES:-} +combine_modules http ${NGX_HTTP_MODULES:-} +combine_modules mail ${NGX_MAIL_MODULES:-} +combine_modules stream ${NGX_STREAM_MODULES:-} + +loose=$(( 1 - NGX_STRICT_LOAD )) +NGX_ALLOW_MISSING_CONFLOAD=$(gobool_to_int "${NGX_ALLOW_MISSING_CONFLOAD:-${loose}}" ${loose}) +unset loose + +combine_confload core ${NGX_CORE_CONFLOAD:-} +combine_confload core_ev ${NGX_CORE_EVENTS_CONFLOAD:-} +combine_confload http ${NGX_HTTP_CONFLOAD:-} +combine_confload mail ${NGX_MAIL_CONFLOAD:-} +combine_confload stream ${NGX_STREAM_CONFLOAD:-} + +## some modules doesn't have configuration at all +NGX_ALLOW_MISSING_CONFLOAD=1 + +combine_confload core ${NGX_CORE_MODULES:-} +combine_confload http ${NGX_HTTP_MODULES:-} +combine_confload mail ${NGX_MAIL_MODULES:-} +combine_confload stream ${NGX_STREAM_MODULES:-} + +exit 0 diff --git a/image-entry.d/75-remove-merged-tree.sh b/image-entry.d/75-remove-merged-tree.sh new file mode 100755 index 0000000..640de27 --- /dev/null +++ b/image-entry.d/75-remove-merged-tree.sh @@ -0,0 +1,13 @@ +#!/bin/sh +set -f + +. /image-entry.d/00-common.envsh + +IEP_RETAIN_MERGED_TREE=$(gobool_to_int "${IEP_RETAIN_MERGED_TREE:-0}" 0) + +if [ "${IEP_RETAIN_MERGED_TREE}" = 1 ] ; then + log_always "NOT removing merged tree: ${merged_root}/" +else + log "removing merged tree: ${merged_root}/" + rm -rf "${merged_root}" +fi diff --git a/image-entry.d/76-adjust-core-user.sh b/image-entry.d/76-adjust-core-user.sh new file mode 100755 index 0000000..2e49e59 --- /dev/null +++ b/image-entry.d/76-adjust-core-user.sh @@ -0,0 +1,16 @@ +#!/bin/sh +set -f + +. /image-entry.d/00-common.envsh + +conf=/etc/angie/conf.d/core-user.conf + +if [ "${IEP_ROOT}" = 1 ] ; then + log "Running as root, no need to adjust configuration" + exit 0 +fi + +log_always "Running as non-root, adjusting configuration" +rm -fv "${conf}" + +exit 0 diff --git a/image-entry.d/77-openssl-ca-certs.envsh b/image-entry.d/77-openssl-ca-certs.envsh new file mode 100755 index 0000000..2b4333b --- /dev/null +++ b/image-entry.d/77-openssl-ca-certs.envsh @@ -0,0 +1,130 @@ +#!/bin/sh + +unset def_bundle def_bundle_fp +def_bundle='/etc/ssl/certs/ca-certificates.crt' +def_bundle_fp="${def_bundle}.fp" + +while : ; do + if [ -n "${SSL_CERT_FILE:-}" ] ; then + log_always "NOT merging CA certificates (if any): SSL_CERT_FILE is already set (=${SSL_CERT_FILE})" + break + fi + + [ -d "${target_root}/tls/ca" ] || break + + unset w + w=$(mktemp -d) || break + + find "${target_root}/tls/ca/" -follow -type f | sort -V > "$w/all.list" + [ -s "$w/all.list" ] || break + + ## entering processing section + touch "$w/processing" + + unset orig_ca_file + while read -r orig_ca_file ; do + [ -n "${orig_ca_file}" ] || continue + + openssl-cert-auto-pem.sh "${orig_ca_file}" + done < "$w/all.list" > "$w/all.pem" + unset orig_ca_file + [ -s "$w/all.pem" ] || break + + openssl-cert-fingerprint.sh "$w/all.pem" | sort -uV > "$w/all.fp" + [ -s "$w/all.fp" ] || break + + ## leaving processing section + rm -f "$w/processing" + + unset dev_root dev_bundle dev_bundle_fp + dev_root=$(env stat -c '%d' / ) + dev_bundle=$(env stat -L -c '%d' "${def_bundle}") + dev_bundle_fp=$(env stat -L -c '%d' "${def_bundle_fp}") + + unset def_bundle_bind_mount + def_bundle_bind_mount=1 + while : ; do + [ "${dev_root}" = "${dev_bundle}" ] || break + [ "${dev_root}" = "${dev_bundle_fp}" ] || break + [ "${dev_bundle}" = "${dev_bundle_fp}" ] || break + + def_bundle_bind_mount=0 + break ; done + unset dev_root dev_bundle dev_bundle_fp + + if [ "${def_bundle_bind_mount}" = 1 ] ; then + log_always "detected bind-mount inside ${def_bundle%/*}/" + log_always "this is merely misuse!" + + openssl-cert-auto-pem.sh "${def_bundle}" > "$w/cacert.pem" + openssl-cert-fingerprint.sh "$w/cacert.pem" | sort -uV > "$w/cacert.fp" + else + ln -s "${def_bundle}" "$w/cacert.pem" + ln -s "${def_bundle_fp}" "$w/cacert.fp" + fi + + unset with_def_bundle + with_def_bundle=0 + while : ; do + [ -s "$w/cacert.pem" ] || break + [ -s "$w/cacert.fp" ] || break + + with_def_bundle=1 + break ; done + + if [ "${with_def_bundle}" = 1 ] ; then + grep -Fxv -f "$w/cacert.fp" "$w/all.fp" > "$w/diff.fp" + [ -s "$w/diff.fp" ] || break + + ## entering processing section + touch "$w/processing" + + grep -Fxn -f "$w/diff.fp" "$w/all.fp" | cut -d : -f 1 > "$w/diff.lineno" + [ -s "$w/diff.lineno" ] || break + + ## leaving processing section + rm -f "$w/processing" + else + : > "$w/diff.lineno" + fi + + : > "${volume_root}/ca.pem" + if [ "${with_def_bundle}" = 1 ] ; then + cat < "$w/cacert.pem" > "${volume_root}/ca.pem" + else + log_always "NOT using ${def_bundle} - empty or missing" + fi + + unset n + while read -r n ; do + [ -n "$n" ] || continue + + off=$(sed -ne "${n}p" "$w/all.off") + sed -ne "${off}p" "$w/all.pem" | openssl x509 + done < "$w/diff.lineno" >> "${volume_root}/ca.pem" + unset n off + + set -a + SSL_CERT_FILE="${volume_root}/ca.pem" + ## merely a quirk + SSL_CERT_DIR="${empty_dir}" + set +a +break ; done +unset def_bundle_bind_mount with_def_bundle + +[ -f "${volume_root}/ca.pem" ] || ln -s "${def_bundle}" "${volume_root}/ca.pem" +unset def_bundle def_bundle_fp + +if [ -n "${w:-}" ] ; then + if [ -f "$w/processing" ] ; then + rm -f "$w/processing" + log_always "unable to merge CA certificates (see below for details):" + log_always "directory listing:" + env -C "$w" ls -lA >&2 + log_always "directory listing (following symlinks):" + env -C "$w" ls -L -lA >&2 + log_always "consider reading source code and contacting developers" + fi + rm -rf "$w" +fi +unset w diff --git a/image-entry.d/90-angie-config-test.sh b/image-entry.d/90-angie-config-test.sh new file mode 100755 index 0000000..ba93009 --- /dev/null +++ b/image-entry.d/90-angie-config-test.sh @@ -0,0 +1,33 @@ +#!/bin/sh +set -f + +. /image-entry.d/00-common.envsh + +## Angie: unset core variable +unset ANGIE ANGIE_BPF_MAPS + +_angie() { + angie -e stderr -g 'error_log /dev/stderr warn;' "$@" +} + +## merely debug test +log_always 'test Angie configuration:' +log_always '=========================' +_angie -t +r=$? +log_always '=========================' + +if [ $r = 0 ] ; then + log_always 'ready to run Angie' + _angie -T 2>&1 | cat > "${volume_root}/diag.angie.conf" +else + log_always 'configuration test has failed, see above' + t=15 + log_always "injecting delay for $t seconds" + sleep $t +fi + +## cleanup after test +rm -f "${volume_root}/angie.pid" + +exit 0 diff --git a/image-entry.d/99-cleanup-env.envsh b/image-entry.d/99-cleanup-env.envsh new file mode 100755 index 0000000..94a5963 --- /dev/null +++ b/image-entry.d/99-cleanup-env.envsh @@ -0,0 +1,66 @@ +#!/bin/sh + +## Angie: unset core variable +unset ANGIE ANGIE_BPF_MAPS + +IEP_RETAIN_ENV=$(gobool_to_int "${IEP_RETAIN_ENV:-0}" 0) + +if [ "${IEP_RETAIN_ENV}" = 1 ] ; then + log_always "NOT removing following variables:" + sed -E '/^./s,^, ,' >&2 + echo >&2 +else + __set="$-" + set +e + + unset __env __env_print + while read -r __env ; do + [ -n "${__env}" ] || continue + + case "${__env}" in + \'* | \"* ) + log "skipping variable (malformed): ${__env}" >&2 + continue + ;; + esac + + if [ "${IEP_DEBUG}" = 1 ] ; then + __env_print="${__env}="$(printenv "${__env}") + __env_print=$(env printf '%q' "${__env_print}") + log_always "unsetting variable: ${__env_print}" + else + log "unsetting variable: ${__env}" + fi + + unset "${__env}" + done + unset __env __env_print + + [ -z "${__set}" ] || set -"${__set}" + unset __set +fi <<-EOF +$( + set +e + cat /proc/self/environ \ + | sed -zEn '/^([^=]+).*$/s//\1/p' \ + | xargs -0r printf '%q\n' \ + | { + ## retain variables defined in ".core_worker_env" configuration key + ## (if it was specified somewhere in dictionaries - either yaml or json) + f="${target_root}/j2cfg/core-worker-env.txt" + [ -s "$f" ] || exec cat + grep -Fxv -f "$f" + } \ + | { + ## remove environment variables: + ## 1. variables starting with "NGX" as they are used by configuration templates + ## 2. variables containing "_SERVICE" or "_PORT" as they are came from + ## container orchestration + grep -E \ + -e '^NGX' \ + -e '_(SERVICE|PORT)' \ + + } \ + | sort -uV +) +EOF diff --git a/image-entry.sh b/image-entry.sh new file mode 100755 index 0000000..9f928de --- /dev/null +++ b/image-entry.sh @@ -0,0 +1,166 @@ +#!/bin/sh +set -f + +[ -n "${IEP_TRACE}" ] || IEP_TRACE=0 +[ "${IEP_TRACE}" = 1 ] || IEP_TRACE=0 +[ "${IEP_TRACE}" = 0 ] || echo "# trace: $(date +'%Y-%m-%d %H:%M:%S.%03N %z'): start" >&2 + +iep_preserve_env() { + ## preserve LD_PRELOAD + unset __IEP_LD_PRELOAD + __IEP_LD_PRELOAD="${LD_PRELOAD:-}" + unset LD_PRELOAD + + ## glibc: preserve GLIBC_TUNABLES + unset __IEP_GLIBC_TUNABLES + __IEP_GLIBC_TUNABLES="${GLIBC_TUNABLES:-}" + unset GLIBC_TUNABLES + + ## glibc: preserve MALLOC_ARENA_MAX + unset __IEP_MALLOC_ARENA_MAX + __IEP_MALLOC_ARENA_MAX="${MALLOC_ARENA_MAX:-2}" + export MALLOC_ARENA_MAX=2 + + ## jemalloc: preserve MALLOC_CONF + unset __IEP_MALLOC_CONF + __IEP_MALLOC_CONF="${MALLOC_CONF:-}" + unset MALLOC_CONF +} + +iep_prepare_env() { + ## Angie: unset core variable + unset ANGIE ANGIE_BPF_MAPS + + ## dumb-init: preserve args + unset IEP_DUMB_INIT_ARGS + IEP_DUMB_INIT_ARGS="${DUMB_INIT_ARGS:-}" + unset DUMB_INIT_ARGS + if [ "${DUMB_INIT_SETSID:-}" = 0 ] ; then + IEP_DUMB_INIT_ARGS="-c${IEP_DUMB_INIT_ARGS:+ }${IEP_DUMB_INIT_ARGS}" + fi + unset DUMB_INIT_SETSID +} + +iep_restore_env() { + unset IEP_DEBUG IEP_VERBOSE IEP_TRACE IEP_ROOT + unset IEP_LOCAL_OVERRIDE IEP_RETAIN_MERGED_TREE IEP_RETAIN_ENV + + ## restore LD_PRELOAD + if [ -n "${__IEP_LD_PRELOAD:-}" ] ; then + export LD_PRELOAD="${__IEP_LD_PRELOAD}" + fi + unset __IEP_LD_PRELOAD + + ## glibc: restore GLIBC_TUNABLES + if [ -n "${__IEP_GLIBC_TUNABLES:-}" ] ; then + export GLIBC_TUNABLES="${__IEP_GLIBC_TUNABLES}" + fi + unset __IEP_GLIBC_TUNABLES + + ## glibc: restore MALLOC_ARENA_MAX + if [ -n "${__IEP_MALLOC_ARENA_MAX:-}" ] ; then + export MALLOC_ARENA_MAX="${__IEP_MALLOC_ARENA_MAX}" + fi + unset __IEP_MALLOC_ARENA_MAX + + ## jemalloc: restore MALLOC_CONF + if [ -n "${__IEP_MALLOC_CONF:-}" ] ; then + export MALLOC_CONF="${__IEP_MALLOC_CONF}" + fi + unset __IEP_MALLOC_CONF +} + +iep_preserve_env +iep_prepare_env + +## early setup TMPDIR (affects "mktemp") +export TMPDIR=/run/angie/tmp +[ -d "${TMPDIR}" ] || install -d -m 03777 "${TMPDIR}" + +## RFC: no need to run entire entrypoint for custom command +# case "$1" in +# angie | */angie ) ;; +# * ) +# iep_restore_env +# exec "$@" +# ;; +# esac + +unset __IEP_SRC ; __IEP_SRC="${0##*/}" +. /image-entry.d/00-common.envsh + +IEP_INIT=$(gobool_to_int "${IEP_INIT:-0}" 0) +# unexport IEP_INIT +unset x ; x="${IEP_INIT}" ; unset IEP_INIT ; IEP_INIT="$x" ; unset x + +# IEP_TRACE=$(gobool_to_int "${IEP_TRACE:-0}" 0) +IEP_DEBUG=$(gobool_to_int "${IEP_DEBUG:-0}" 0) +IEP_VERBOSE=$(gobool_to_int "${IEP_VERBOSE:-${IEP_DEBUG}}" "${IEP_DEBUG}") +export IEP_TRACE IEP_DEBUG IEP_VERBOSE + +## run parts (if any) +unset __IEP_SCRIPT +while read -r __IEP_SCRIPT ; do + [ -n "${__IEP_SCRIPT}" ] || continue + [ -f "${__IEP_SCRIPT}" ] || continue + + + case "${__IEP_SCRIPT}" in + *.envsh ) + if ! [ -x "${__IEP_SCRIPT}" ] ; then + log "NOT sourcing ${__IEP_SCRIPT} - not executable" + continue + fi + [ "${IEP_TRACE}" = 0 ] || echo "# trace: $(date +'%Y-%m-%d %H:%M:%S.%03N %z'): source ${__IEP_SCRIPT}" >&2 + log "sourcing ${__IEP_SCRIPT}" + __IEP_SRC="${__IEP_SCRIPT}" + . "${__IEP_SCRIPT}" + __IEP_SRC="${0##*/}" + ;; + * ) + if ! [ -x "${__IEP_SCRIPT}" ] ; then + log "NOT running ${__IEP_SCRIPT} - not executable" + continue + fi + [ "${IEP_TRACE}" = 0 ] || echo "# trace: $(date +'%Y-%m-%d %H:%M:%S.%03N %z'): run ${__IEP_SCRIPT}" >&2 + log "running ${__IEP_SCRIPT}" + "${__IEP_SCRIPT}" + ;; + esac +done <&2 + +if [ "${IEP_DEBUG}" = 1 ] ; then + log_always "ready to run application: $*" +else + log_always "ready to run application" +fi +echo >&2 + +iep_restore_env + +## variables that are not so easily unsettable +unset __IEP_ENV +for i in '_' 'SHLVL' ; do + __IEP_ENV="${__IEP_ENV:-}${__IEP_ENV:+ }-u $i" +done + +if [ "${IEP_INIT}" = 0 ] ; then + exec \ + ${__IEP_ENV:+ env ${__IEP_ENV} } \ + "$@" +else + exec \ + ${__IEP_ENV:+ env ${__IEP_ENV} } \ + dumb-init ${IEP_DUMB_INIT_ARGS} \ + "$@" +fi diff --git a/j2cfg/j2cfg-dump.py b/j2cfg/j2cfg-dump.py new file mode 100755 index 0000000..aaec791 --- /dev/null +++ b/j2cfg/j2cfg-dump.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +import os.path +import sys + + +def main(): + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + import j2cfg + + j = j2cfg.J2cfg(dump_only=True) + print(j.dump_config()) + + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/j2cfg/j2cfg-multi.py b/j2cfg/j2cfg-multi.py new file mode 100755 index 0000000..b86050d --- /dev/null +++ b/j2cfg/j2cfg-multi.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 + +import os.path +import sys + + +def main(): + if len(sys.argv) < 2: + raise ValueError('not enough arguments (min: 1)') + + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + import j2cfg + + r = j2cfg.J2cfg(strict=False) + ret = 0 + for f in sys.argv[1:]: + if not r.render_file(f, None): + ret = 1 + + sys.exit(ret) + + +if __name__ == "__main__": + main() diff --git a/j2cfg/j2cfg-single.py b/j2cfg/j2cfg-single.py new file mode 100755 index 0000000..09f4e02 --- /dev/null +++ b/j2cfg/j2cfg-single.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +import os.path +import sys + + +def main(): + if len(sys.argv) < 2: + raise ValueError('not enough arguments (min: 1)') + if len(sys.argv) > 3: + raise ValueError('too many arguments (max: 2)') + + if not sys.argv[1]: + raise ValueError('specify input file') + + if (len(sys.argv) == 3) and (not sys.argv[2]): + raise ValueError('specify output file') + + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + import j2cfg + + r = j2cfg.J2cfg() + r.render_file(*sys.argv[1:]) + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/j2cfg/j2cfg/__init__.py b/j2cfg/j2cfg/__init__.py new file mode 100644 index 0000000..720fb01 --- /dev/null +++ b/j2cfg/j2cfg/__init__.py @@ -0,0 +1,266 @@ +import importlib +import json +import os +import os.path +import sys + +import jinja2 +import wcmatch.wcmatch +import yaml + +from .functions import * +from .settings import * + + +J2CFG_CONFIG_EXT = ['yml', 'yaml', 'json'] + + +class J2cfg: + def __init__(self, strict=True, config_file=None, config_path=None, + modules=None, search_path=None, template_suffix=None, + dump_only=False): + + if dump_only is None: + self.dump_only = False + else: + self.dump_only = bool(dump_only) + + self.config_file = config_file or os.getenv('J2CFG_CONFIG') + if self.config_file is not None: + self.config_file = str(self.config_file) + + self.config_path = config_path + if self.config_path is None: + self.config_path = os.getenv('J2CFG_PATH') + if self.config_path is not None: + self.config_path = str_split_to_list(self.config_path, ':') + if self.config_path is None: + self.config_path = J2CFG_PATH.copy() + else: + self.config_path = any_to_str_list(self.config_path) + self.config_path = uniq_str_list(self.config_path) + + self.kwargs = {'j2cfg': {}} + + def merge_dict_from_file(filename): + if filename is None: + return False + f = str(filename) + if f == '': + return False + if not os.path.exists(f): + return False + if not os.path.isfile(f): + print( + f'J2cfg: not a file, skipping: {filename}', + file=sys.stderr) + return False + + if f.endswith('.yml') or f.endswith('.yaml'): + with open(f, mode='r', encoding='utf-8') as fx: + for x in yaml.safe_load_all(fx): + if not x: + continue + self.kwargs['j2cfg'] = merge_dict_recurse( + self.kwargs['j2cfg'], x + ) + return True + + if f.endswith('.json'): + with open(f, mode='r', encoding='utf-8') as fx: + self.kwargs['j2cfg'] = merge_dict_recurse( + self.kwargs['j2cfg'], json.load(fx) + ) + return True + + print( + f'J2cfg: non-recognized name extension: {filename}', + file=sys.stderr) + return False + + def merge_dict_default(): + search_pattern = '|'.join(['*.' + ext for ext in J2CFG_CONFIG_EXT]) + search_flags = wcmatch.wcmatch.SYMLINKS + + for d in self.config_path: + if not os.path.isdir(d): + continue + m = wcmatch.wcmatch.WcMatch(d, search_pattern, + flags=search_flags) + for f in sorted(m.match()): + if self.dump_only: + real_f = os.path.realpath(f) + if f == real_f: + print( + f'J2cfg: try loading {f}', + file=sys.stderr + ) + else: + print( + f'J2cfg: try loading {f} <- {real_f}', + file=sys.stderr + ) + merge_dict_from_file(f) + + if self.config_file is None: + merge_dict_default() + else: + if os.path.isfile(self.config_file): + merge_dict_from_file(self.config_file) + else: + print( + 'J2cfg: J2cfg config file does not exist, skipping: ' + + f'{self.config_file}', + file=sys.stderr + ) + + if self.dump_only: + return + + self.strict = strict + if not isinstance(self.strict, bool): + self.strict = True + + self.search_path = search_path + if self.search_path is None: + self.search_path = os.getenv('J2CFG_SEARCH_PATH') + if self.search_path is not None: + self.search_path = str_split_to_list(self.search_path, ':') + if self.search_path is None: + self.search_path = self.config_path.copy() + else: + self.search_path = any_to_str_list(self.search_path) + self.search_path = uniq_str_list(self.search_path) + # RFC: should we use the current working directory early? + for d in [os.getcwd()]: + if d not in self.search_path: + self.search_path.insert(0, d) + + self.modules = modules or os.getenv('J2CFG_MODULES') + if self.modules is None: + self.modules = J2CFG_PYTHON_MODULES.copy() + else: + if isinstance(self.modules, str): + self.modules = str_split_to_list(self.modules) + else: + self.modules = any_to_str_list(self.modules) + self.modules = uniq_str_list(self.modules) + + self.template_suffix = template_suffix or os.getenv('J2CFG_SUFFIX') + if self.template_suffix is None: + self.template_suffix = '' + J2CFG_TEMPLATE_EXT + else: + self.template_suffix = str(self.template_suffix) + if self.template_suffix == '': + self.template_suffix = '' + J2CFG_TEMPLATE_EXT + if not self.template_suffix.startswith('.'): + self.template_suffix = '.' + self.template_suffix + + self.kwargs.update({ + 'env': os.environ, + 'env_preserve': J2CFG_PRESERVE_ENVS.copy(), + 'env_passthrough': J2CFG_PASSTHROUGH_ENVS.copy(), + }) + for m in self.modules: + if m in self.kwargs: + print(f'J2cfg: kwargs already has {m} key', + file=sys.stderr) + continue + self.kwargs[m] = importlib.import_module(m) + + self.j2fs_loaders = { + d: jinja2.FileSystemLoader( + d, encoding='utf-8', followlinks=True, + ) for d in self.search_path + } + self.j2env = jinja2.Environment( + extensions=J2CFG_JINJA_EXTENSIONS, + loader=jinja2.ChoiceLoader([ + self.j2fs_loaders[d] for d in self.search_path + ]), + ) + + def init_env(e: jinja2.Environment): + for s in J2CFG_FILTERS: + n = s.__name__ + if n in e.filters: + print(f'J2cfg: filters already has {n} key', + file=sys.stderr) + continue + e.filters[n] = s + + init_env(self.j2env) + + def dump_config(self): + return yaml.safe_dump(self.kwargs['j2cfg']) + + def ensure_fs_loader_for(self, directory: str): + if self.dump_only: + raise ValueError('dump_only is True') + + if directory in self.j2fs_loaders: + return + self.j2fs_loaders[directory] = jinja2.FileSystemLoader( + directory, encoding='utf-8', followlinks=True, + ) + + def render_file(self, file_in, file_out=None) -> bool: + if self.dump_only: + raise ValueError('dump_only is True') + + def render_error(msg) -> bool: + if self.strict: + raise ValueError(msg) + print(f'J2cfg: {msg}', file=sys.stderr) + return False + + if file_in is None: + return render_error( + 'argument "file_in" is None') + f_in = str(file_in) + if f_in == '': + return render_error( + 'argument "file_in" is empty') + if not os.path.exists(f_in): + return render_error( + f'file is missing: {file_in}') + if not os.path.isfile(f_in): + return render_error( + f'not a file: {file_in}') + + f_out = file_out + if f_out is None: + if not f_in.endswith(self.template_suffix): + return render_error( + f'input file name extension mismatch: {file_in}') + f_out = os.path.splitext(f_in)[0] + + dirs = self.search_path.copy() + for d in [os.getcwd(), os.path.dirname(f_in)]: + if d in dirs: + continue + self.ensure_fs_loader_for(d) + dirs.insert(0, d) + if f_in.startswith('/'): + self.ensure_fs_loader_for('/') + dirs.append('/') + + j2_environ = self.j2env.overlay(loader=jinja2.ChoiceLoader([ + self.j2fs_loaders[d] for d in dirs + ])) + j2_template = j2_environ.get_template(f_in) + rendered = j2_template.render(**self.kwargs) + + if os.path.lexists(f_out): + if os.path.islink(f_out) or (not os.path.isfile(f_out)): + return render_error( + f'output file is not safely writable: {f_out}') + if os.path.exists(f_out): + if os.path.samefile(f_in, f_out): + return render_error( + f'unable to process template inplace: {file_in}') + + with open(f_out, mode='w', encoding='utf-8') as f: + f.write(rendered) + + return True diff --git a/j2cfg/j2cfg/functions.py b/j2cfg/j2cfg/functions.py new file mode 100644 index 0000000..7645c74 --- /dev/null +++ b/j2cfg/j2cfg/functions.py @@ -0,0 +1,304 @@ +import collections.abc +import itertools +import pathlib +import re +import sys + +import jinja2 + +from .settings import is_env_banned + + +def is_sequence(x) -> bool: + return isinstance(x, collections.abc.Sequence) + + +def is_mapping(x) -> bool: + return isinstance(x, collections.abc.Mapping) + + +def uniq(a: list | set) -> list: + return sorted(set(a)) + + +def remove_non_str(a: list | set) -> list: + return list(filter(lambda x: isinstance(x, str), a)) + + +def remove_empty_str(a: list | set) -> list: + return list(filter(None, a)) + + +def uniq_str_list(a: list | set) -> list: + return remove_empty_str(uniq(a)) + + +def str_split_to_list(s: str, sep=r'\s+') -> list: + return remove_empty_str(re.split(sep, s)) + + +def dict_to_env_str_list(x: dict) -> list: + r = [] + for k in sorted(x.keys()): + if x[k] is None: + r.append(f'{k}') + else: + r.append(f'{k}={str(x[k])}') + return r + + +def any_to_str_list(x) -> list: + if x is None: + return [] + if isinstance(x, str): + return [x] + if is_sequence(x): + return [str(e) for e in x] + if is_mapping(x): + return dict_to_env_str_list(x) + return [str(x)] + + +def is_re_match(x, pattern, flags=0) -> bool: + if isinstance(x, str): + return bool(re.match(pattern, x, flags)) + if is_sequence(x): + return any(is_re_match(v, pattern, flags) for v in x) + if is_mapping(x): + return any(is_re_match(v, pattern, flags) for v in x.keys()) + return False + + +def is_re_fullmatch(x, pattern, flags=0) -> bool: + if isinstance(x, str): + return bool(re.fullmatch(pattern, x, flags)) + if is_sequence(x): + return any(is_re_fullmatch(v, pattern, flags) for v in x) + if is_mapping(x): + return any(is_re_fullmatch(v, pattern, flags) for v in x.keys()) + return False + + +def re_match(x, pattern, flags=0): + if isinstance(x, str): + return re.match(pattern, x, flags) + if is_sequence(x): + return [v for v in x + if re_match(v, pattern, flags)] + if is_mapping(x): + return {k: v for k, v in x.items() + if re_match(k, pattern, flags)} + return None + + +def re_fullmatch(x, pattern, flags=0): + if isinstance(x, str): + return re.fullmatch(pattern, x, flags) + if is_sequence(x): + return [v for v in x + if re_fullmatch(v, pattern, flags)] + if is_mapping(x): + return {k: v for k, v in x.items() + if re_fullmatch(k, pattern, flags)} + return None + + +def re_match_negate(x, pattern, flags=0): + if isinstance(x, str): + return not bool(re.match(pattern, x, flags)) + if is_sequence(x): + return [v for v in x + if re_match_negate(v, pattern, flags)] + if is_mapping(x): + return {k: v for k, v in x.items() + if re_match_negate(k, pattern, flags)} + return x + + +def re_fullmatch_negate(x, pattern, flags=0): + if isinstance(x, str): + return not bool(re.fullmatch(pattern, x, flags)) + if is_sequence(x): + return [v for v in x + if re_fullmatch_negate(v, pattern, flags)] + if is_mapping(x): + return {k: v for k, v in x.items() + if re_fullmatch_negate(k, pattern, flags)} + return x + + +def dict_remap_keys(x: dict, key_map) -> dict: + if key_map is None: + return x + p = set(x.keys()) + m = {} + for k in x: + v = key_map(k) + if v == k: + continue + m[k] = v + p.discard(k) + p.discard(v) + return {k: x[k] for k in p} | {v: x[k] for k, v in m.items()} + + +def re_sub(x, pattern, repl, count=0, flags=0): + if isinstance(x, str): + return re.sub(pattern, repl, x, count, flags) + if is_sequence(x): + return [ + re_sub(v, pattern, repl, count, flags) + for v in x + ] + if is_mapping(x): + return dict_remap_keys( + x, lambda k: + re_sub(k, pattern, repl, count, flags) + ) + return x + + +def as_cgi_header(x): + if isinstance(x, str): + return 'HTTP_' + re.sub('[^A-Z0-9]+', '_', x.upper()).strip('_') + if is_sequence(x): + return uniq([ + as_cgi_header(v) + for v in x + ]) + if is_mapping(x): + return dict_remap_keys( + x, as_cgi_header + ) + return x + + +def any_to_env_dict(x) -> dict: + if x is None: + return {} + + h = {} + + def feed(k, parse=False, v=None): + if v is None: + return + k = str(k) + if parse: + k2, m, v2 = k.partition('=') + if m == '=': + k = k2 + v = v2 + if not re.fullmatch(r'[a-zA-Z_][a-zA-Z0-9_]*', k): + return + if is_env_banned(k): + return + if k in h: + return + h[k] = v if v is None else str(v) + + if isinstance(x, str): + feed(x, True) + elif is_sequence(x): + for e in x: + feed(e, True) + elif is_mapping(x): + for k in x: + feed(k, False, x[k]) + else: + return {} + + return h + + +def dict_keys(x: dict) -> list: + return sorted([k for k in x.keys()]) + + +def dict_empty_keys(x: dict) -> list: + return sorted([k for k in x.keys() if x[k] is None]) + + +def dict_non_empty_keys(x: dict) -> list: + return sorted([k for k in x.keys() if x[k] is not None]) + + +def list_diff(a: list | set, b: list | set) -> list: + return list(set(a) - set(b)) + + +def list_intersect(a: list | set, b: list | set) -> list: + return list(set(a) & set(b)) + + +@jinja2.pass_environment +def sh_like_file_to_list(j2env, file_in: str) -> list: + tpl = j2env.get_template(file_in) + text = pathlib.Path(tpl.filename).read_text(encoding='utf-8') + lines = re.split(r'[\r\n]', text) + return list(itertools.filterfalse( + lambda x: re.match(r'\s*#', x), lines + )) + + +def merge_dict_recurse(d1, d2: dict) -> dict: + x = {} | d1 + + keys1 = set(x.keys()) + keys2 = set(d2.keys()) + common = keys1 & keys2 + missing = keys2 - common + + map1 = {k for k in common if is_mapping(x.get(k))} + seq1 = {k for k in common if is_sequence(x.get(k))} + misc1 = common - seq1 - map1 + + merge_safe = missing | misc1 + x.update({k: d2.get(k) for k in merge_safe}) + + map_common = {k for k in map1 if is_mapping(d2.get(k))} + for k in map_common: + x[k] = merge_dict_recurse(x.get(k), d2.get(k)) + + seq_common = {k for k in seq1 if is_sequence(d2.get(k))} + for k in seq_common: + x[k] = uniq(list(x.get(k)) + list(d2.get(k))) + + unmerged = (map1 - map_common) | (seq1 - seq_common) + for k in unmerged: + t1 = type(x.get(k)) + t2 = type(d2.get(k)) + print( + f'merge_dict_recurse(): skipping key {k}' + + f' due to type mismatch: {t1} vs. {t2}', + file=sys.stderr) + + return x + + +J2CFG_FILTERS = [ + any_to_env_dict, + any_to_str_list, + as_cgi_header, + dict_empty_keys, + dict_keys, + dict_non_empty_keys, + dict_remap_keys, + dict_to_env_str_list, + is_mapping, + is_re_fullmatch, + is_re_match, + is_sequence, + list_diff, + list_intersect, + re_fullmatch, + re_fullmatch_negate, + re_match, + re_match_negate, + re_sub, + remove_empty_str, + remove_non_str, + sh_like_file_to_list, + str_split_to_list, + uniq, + uniq_str_list, +] diff --git a/j2cfg/j2cfg/settings.py b/j2cfg/j2cfg/settings.py new file mode 100644 index 0000000..03e1481 --- /dev/null +++ b/j2cfg/j2cfg/settings.py @@ -0,0 +1,73 @@ +import re + + +J2CFG_TEMPLATE_EXT = '.j2' + +J2CFG_PATH = [ + '/angie/j2cfg', + '/etc/angie/j2cfg', + '/etc/angie/j2cfg.dist', +] + +J2CFG_PYTHON_MODULES = [ + 'itertools', + 'json', + 'os', + 'os.path', + 'pathlib', + 're', + 'sys', + # installed through pip + 'psutil', + 'netaddr', + 'wcmatch', +] + +J2CFG_JINJA_EXTENSIONS = [ + 'jinja2.ext.do', + 'jinja2.ext.loopcontrols', +] + +J2CFG_BANNED_ENVS = [ + r'ANGIE(=|$)', + r'ANGIE_BPF_MAPS(=|$)' +] + +J2CFG_PRESERVE_ENVS = [ + # glibc + 'GLIBC_TUNABLES', + 'MALLOC_ARENA_MAX', + # jemalloc + 'MALLOC_CONF', +] + +J2CFG_PASSTHROUGH_ENVS = [ + # openssl (man 7 openssl-env) + 'SSL_CERT_DIR', + 'SSL_CERT_FILE', + 'OPENSSL_CONF', + 'OPENSSL_CONF_INCLUDE', + 'OPENSSL_CONFIG', + 'OPENSSL_ENGINES', + 'OPENSSL_MODULES', + 'RANDFILE', + 'CTLOG_FILE', + # openssl: processor capabilities + 'OPENSSL_armcap', + 'OPENSSL_ia32cap', + 'OPENSSL_ppccap', + 'OPENSSL_riscvcap', + 'OPENSSL_s390xcap', + 'OPENSSL_sparcv9cap', + # generic proxy settings + 'NO_PROXY', + 'HTTPS_PROXY', + 'HTTP_PROXY', +] + + +def is_env_banned(k: str) -> bool: + for r in J2CFG_BANNED_ENVS: + if re.match(r, k): + return True + return False diff --git a/j2cfg/test.j2 b/j2cfg/test.j2 new file mode 100644 index 0000000..f07b7bc --- /dev/null +++ b/j2cfg/test.j2 @@ -0,0 +1,173 @@ +j2cfg: +{{ j2cfg }} + +{% set x = [1,2,3,4] %} +x = {{ x }} +is_sequence: +{{ x | is_sequence }} + +{% set x = {1:2,3:4} %} +x = {{ x }} +is_sequence: +{{ x | is_sequence }} + +{% set x = [1,2,3,4] %} +x = {{ x }} +is_mapping: +{{ x | is_mapping }} + +{% set x = {1:2,3:4} %} +x = {{ x }} +is_mapping: +{{ x | is_mapping }} + +{% set x = [2,3,1,2] %} +x = {{ x }} +uniq: +{{ x | uniq }} + +{% set x = ['2',3,'1','2'] %} +x = {{ x }} +remove_non_str: +{{ x | remove_non_str }} + +{% set x = ['2','','1','2'] %} +x = {{ x }} +remove_empty_str: +{{ x | remove_empty_str }} + +{% set x = ['2','3','1','2'] %} +x = {{ x }} +uniq_str_list: +{{ x | uniq_str_list }} + +{% set x = '2 3 1 2 ' %} +x = {{ x.__repr__() }} +str_split_to_list: +{{ x | str_split_to_list }} + +{% set x = '2:3::1:2:' %} +x = {{ x.__repr__() }} +str_split_to_list(':'): +{{ x | str_split_to_list(':') }} + +{% set x = { 'VAR1': 'Etc/UTC', 'VAR2': '', 'VAR3': None, '4VAR4': 'yeah', 'VAR5=not': 'yeah', 'VAR5=real yeah': None, 'VAR6': {'pi': 3.1415926}, 'VAR7': ['pi', 3.1415926] } %} +x = {{ x }} +dict_to_env_str_list: +{{ x | dict_to_env_str_list }} + +{% set x = '1 2 3 4' %} +x = {{ x.__repr__() }} +any_to_str_list: +{{ x | any_to_str_list }} + +{% set x = [1,2,3,4] %} +x = {{ x }} +any_to_str_list: +{{ x | any_to_str_list }} + +{% set x = 3.1415926 %} +x = {{ x }} +any_to_str_list: +{{ x | any_to_str_list }} + +{% set x = ['a2','b3','c1','d2'] %} +x = {{ x }} +is_re_match('[ab]'): +{{ x | is_re_match('[ab]') }} +is_re_match('[mn]'): +{{ x | is_re_match('[mn]') }} + +{% set x = ['a2','b3','c1','d2'] %} +x = {{ x }} +is_re_fullmatch('[ab]'): +{{ x | is_re_fullmatch('[ab]') }} +is_re_fullmatch('[ab][12]'): +{{ x | is_re_fullmatch('[ab][12]') }} + +{% set x = ['a2','b3','c1','d2'] %} +x = {{ x }} +re_match('[ab]'): +{{ x | re_match('[ab]') }} +re_match('[mn]'): +{{ x | re_match('[mn]') }} + +{% set x = ['a2','b3','c1','d2'] %} +x = {{ x }} +re_fullmatch('[ab]'): +{{ x | re_fullmatch('[ab]') }} +re_fullmatch('[ab][12]'): +{{ x | re_fullmatch('[ab][12]') }} + +{% set x = ['a2','b3','c1','d2'] %} +x = {{ x }} +re_match_negate('[ab]'): +{{ x | re_match_negate('[ab]') }} +re_match_negate('[mn]'): +{{ x | re_match_negate('[mn]') }} + +{% set x = ['a2','b3','c1','d2'] %} +x = {{ x }} +re_fullmatch_negate('[ab]'): +{{ x | re_fullmatch_negate('[ab]') }} +re_fullmatch_negate('[ab][12]'): +{{ x | re_fullmatch_negate('[ab][12]') }} + +{% set x = ['a2b','b3b','c1f','d2g'] %} +x = {{ x }} +re_sub('[ab]', '_'): +{{ x | re_sub('[ab]', '_') }} +re_sub('[mn]', '_'): +{{ x | re_sub('[mn]', '_') }} + +{% set x = 'j2cfg-multi.py' %} +x = {{ x.__repr__() }} +sh_like_file_to_list: +{{ 'j2cfg-multi.py' | sh_like_file_to_list }} + +{% set x = 'Accept-Encoding' %} +x = {{ x.__repr__() }} +as_cgi_header: +{{ x | as_cgi_header }} + +{% set x = '_Permissions-Policy--' %} +x = {{ x.__repr__() }} +as_cgi_header: +{{ x | as_cgi_header }} + +{% set x = 'VAR1=Etc/UTC' %} +x = {{ x.__repr__() }} +any_to_env_dict: +{{ x | any_to_env_dict }} + +{% set x = ['VAR1=Etc/UTC', 'VAR2=', 'VAR3', '4VAR4=yeah', 'VAR5=yeah', 'VAR5=not-yeah'] %} +x = {{ x }} +any_to_env_dict: +{{ x | any_to_env_dict }} + +{% set x = { 'VAR1': 'Etc/UTC', 'VAR2': '', 'VAR3': None, '4VAR4': 'yeah', 'VAR5=not': 'yeah', 'VAR5=real yeah': None, 'VAR6': {'pi': 3.1415926}, 'VAR7': ['pi', 3.1415926] } %} +x = {{ x }} +any_to_env_dict: +{{ x | any_to_env_dict }} + +{% set x = { 'VAR1': 'Etc/UTC', 'VAR2': '', 'VAR3': None, '4VAR4': 'yeah', 'VAR5=not': 'yeah', 'VAR5=real yeah': None, 'VAR6': {'pi': 3.1415926}, 'VAR7': ['pi', 3.1415926] } %} +x = {{ x }} +dict_keys: +{{ x | dict_keys }} +dict_empty_keys: +{{ x | dict_empty_keys }} +dict_non_empty_keys: +{{ x | dict_non_empty_keys }} + +{% set x = [1,2,3,4] %} +{% set y = [3,4,5,6] %} +x = {{ x }} +y = {{ y }} +list_diff(x, y): +{{ x | list_diff(y) }} +list_diff(y, x): +{{ y | list_diff(x) }} +list_intersect(x, y): +{{ x | list_intersect(y) }} +list_intersect(y, x): +{{ y | list_intersect(x) }} diff --git a/scripts/angie-builtin-modules.sh b/scripts/angie-builtin-modules.sh new file mode 100755 index 0000000..9321c93 --- /dev/null +++ b/scripts/angie-builtin-modules.sh @@ -0,0 +1,30 @@ +#!/bin/sh +set -f + +conf_dir='/etc/angie' + +## Angie: unset core variable +unset ANGIE ANGIE_BPF_MAPS + +_angie() { + angie -e stderr -g 'error_log /dev/stderr warn;' "$@" +} + +t=$(mktemp) || exit $? + +_angie -m 2>&1 | tee "$t" >/dev/null + +sed -En '/^ngx_(http|mail|stream)/d;/^ngx_(.+)_module$/{s//\1/;s/_filter$//;s/_/-/g;p}' < "$t" \ +| sort -uV > "${conf_dir}/builtin.core" + +for m in http mail stream ; do + sed -En '/^ngx_'"${m}"'_(.+)_module$/{s//\1/;s/_filter$//;s/_/-/g;p}' < "$t" \ + | sort -uV > "${conf_dir}/builtin.$m" +done + +rm -f "$t" ; unset t + +for m in core http mail stream ; do + echo "${conf_dir}/builtin.$m" +done \ +| xargs -r ls -ld diff --git a/scripts/angie-reload.sh b/scripts/angie-reload.sh new file mode 100755 index 0000000..810f204 --- /dev/null +++ b/scripts/angie-reload.sh @@ -0,0 +1,15 @@ +#!/bin/sh +set -ef + +pid_file="${1:-/run/angie/angie.pid}" +[ -f "${pid_file}" ] || { + echo "Angie is not running? (${pid_file} not found)" + exit 0 +} +[ -s "${pid_file}" ] || { + echo "Angie is not running? (${pid_file} is empty)" + exit 1 +} +pid=$(cat "${pid_file}") || exit 1 + +exec env kill -SIGHUP "${pid}" \ No newline at end of file diff --git a/scripts/apt-clean.sh b/scripts/apt-clean.sh new file mode 100755 index 0000000..4365a7c --- /dev/null +++ b/scripts/apt-clean.sh @@ -0,0 +1,52 @@ +#!/bin/sh +set -f + +## apt +find /var/cache/apt/ ! -type d ! -name 'lock' -delete +find /var/lib/apt/ ! -type d -wholename '/var/lib/apt/listchanges*' -delete +find /var/lib/apt/lists/ ! -type d ! -name 'lock' -delete +find /var/log/ ! -type d -wholename '/var/log/apt/*' -delete +find /var/log/ ! -type d -wholename '/var/log/aptitude*' -delete + +## dpkg +: "${DPKG_ADMINDIR:=/var/lib/dpkg}" +truncate -s 0 "${DPKG_ADMINDIR}/available" +find "${DPKG_ADMINDIR}/" ! -type d -wholename "${DPKG_ADMINDIR}/*-old" -delete +find /var/log/ ! -type d -wholename '/var/log/alternatives.log' -delete +find /var/log/ ! -type d -wholename '/var/log/dpkg.log' -delete + +## DONT DO THIS AT HOME! +find "${DPKG_ADMINDIR}/" ! -type d -wholename "${DPKG_ADMINDIR}/info/*.symbols" -delete + +## debconf +find /var/cache/debconf/ ! -type d -wholename '/var/cache/debconf/*-old' -delete + +__t=$(mktemp) ; : "${__t:?}" +debconf_trim_i18n() { + mawk 'BEGIN { m = 0 } + $0 == "" { print } + /^[^[:space:]]/ { + if ($1 ~ "\.[Uu][Tt][Ff]-?8:") { m = 1; next; } + m = 0; print $0; + } + /^[[:space:]]/ { + if (m == 1) next; + print $0; + }' < "$1" > "${__t}" + cat < "${__t}" > "$1" +} + +debconf_trim_i18n /var/cache/debconf/templates.dat +while read -r tmpl ; do + [ -n "${tmpl}" ] || continue + [ -s "${tmpl}" ] || continue + debconf_trim_i18n "${tmpl}" +done <&2 <<-EOF + + $0: + NGX_DEBUG is not set - defaulting to NGX_DEBUG=0 + + EOF + NGX_DEBUG=0 +fi + +set -a +ANGIE_MODULES_DIR="${ANGIE_MODULES_DIR:-/usr/lib/angie/modules}" +set +a + +## produce package list +pkgs= +for i ; do + [ -n "$i" ] || continue + + i="angie-module-$i" + printf '%s' "$i" | grep -zEq '^[a-z0-9.+-]+$' || { + env printf 'package name %q is not legal, quitting!\n' "$i" >&2 + exit 1 + } + + pkgs="${pkgs}${pkgs:+ }$i" +done + +[ -n "${pkgs}" ] || exit 0 + +normalize_list() { + [ -n "$1" ] || return 0 + + printf '%s' "$1" \ + | tr -s '[:space:]' ' ' \ + | sed -zE 's/^ //;s/ $//' +} + +sort_dedup_list() { + [ -n "$1" ] || return 0 + + printf '%s' "$1" \ + | tr -s '[:space:]' '\n' | sort -uV | paste -sd ' ' \ + | sed -zE 's/^\s+//;s/\s+$//' +} + +pkgs=$(sort_dedup_list "${pkgs}") + +dirs='cache lib log' +for n in ${dirs} ; do + d="/run/angie/$n" + [ -d "$d" ] || install -d "$d" +done + +apt-install.sh ${pkgs} + +ANGIE_MODCONF_DIR=/etc/angie/mod.dist +[ -d "${ANGIE_MODCONF_DIR}" ] || install -d "${ANGIE_MODCONF_DIR}" + +list_ngx_modules() { + set +e + dpkg-query -L "$1" \ + | grep -F -e "${ANGIE_MODULES_DIR}/" \ + | grep -E -e '/[^/]+_module(-debug)?\.so$' \ + | sed -E '\,^(.+)-debug\.so$,{p;s//\1.so/;p;d}' \ + | sort -uV \ + | xargs -r ls -U1d 2>/dev/null + set -e +} + +is_same_file() { + find -L "$1" -samefile "$2" -printf . -quit 2>/dev/null | grep -Fq . || return 1 +} + +gen_mod_config() { + if [ -s "$2" ] ; then + printf '%s: configuration already exists: %s\n' "$1" "$2" >&2 + return + fi + + [ -n "$3" ] || return + + local __m + for __m in $3 ; do + echo "load_module ${__m};" + done > "$2" +} + +for p in ${pkgs} ; do + [ -n "$p" ] || continue + i="${p#angie-module-}" + + ## adjust modules: + ## - remove debug module if not in debug image + ## - move debug module to usual location otherwise + while read -r fmod_debug ; do + # [ -n "${fmod_debug}" ] || continue + case "${fmod_debug}" in + *-debug.so ) ;; + * ) continue ;; + esac + fmod="${fmod_debug%-debug.so}.so" + + fmod_tmp=$(mktemp -u "${fmod}.XXXXXXXXXX") + if [ "${NGX_DEBUG}" = 0 ] ; then + if [ -f "${fmod}" ] ; then + fmod_real=$(readlink -f "${fmod}") + else + env printf 'missing (non-debug) file: %q\n' "${fmod}" >&2 + env printf 'falling back to (debug) file: %q\n' "${fmod_debug}" >&2 + fmod_real=$(readlink -f "${fmod_debug}") + fi + else + fmod_real=$(readlink -f "${fmod_debug}") + fi + [ -n "${fmod_real}" ] || exit 1 + ln -v "${fmod_real}" "${fmod_tmp}" + rm -fv "${fmod}" "${fmod_debug}" + mv -fv "${fmod_tmp}" "${fmod}" + done <<-EOF + $(list_ngx_modules "$p") + EOF + + if [ -e "${ANGIE_MODCONF_DIR}/.$i.preseed" ] ; then + printf '%s: skipping generation of module load config (preseed is in effect)\n' "$p" >&2 + continue + fi + + ## produce attachable module configs + http_modules= + mail_modules= + stream_modules= + while read -r fmod ; do + [ -n "${fmod}" ] || continue + + fmod_short="modules.d/${fmod#"${ANGIE_MODULES_DIR}/"}" + fname=${fmod##*/} + case "${fname}" in + ngx_http_* ) + http_modules="${http_modules}${http_modules:+ }${fmod_short}" + ;; + ngx_mail_* ) + mail_modules="${mail_modules}${mail_modules:+ }${fmod_short}" + ;; + ngx_stream_* ) + stream_modules="${stream_modules}${stream_modules:+ }${fmod_short}" + ;; + ## damn NDK + ndk_http_* ) + http_modules="${http_modules}${http_modules:+ }${fmod_short}" + ;; + * ) + env printf '%s: unable to determine module type for file (skipping): %q\n' "$p" "${fmod}" >&2 + continue + ;; + esac + done <<-EOF + $(list_ngx_modules "$p") + EOF + + [ -z "${http_modules}" ] || gen_mod_config "$p" "${ANGIE_MODCONF_DIR}/http-$i.conf" "${http_modules}" + [ -z "${mail_modules}" ] || gen_mod_config "$p" "${ANGIE_MODCONF_DIR}/mail-$i.conf" "${mail_modules}" + [ -z "${stream_modules}" ] || gen_mod_config "$p" "${ANGIE_MODCONF_DIR}/stream-$i.conf" "${stream_modules}" +done diff --git a/scripts/apt-install.sh b/scripts/apt-install.sh new file mode 100755 index 0000000..7859e80 --- /dev/null +++ b/scripts/apt-install.sh @@ -0,0 +1,44 @@ +#!/bin/sh +set -ef + +find_fresh_ts() { + { + find "$@" -exec stat -c '%Y' '{}' '+' 2>/dev/null || : + # duck and cover! + echo 1 + } | sort -rn | head -n 1 +} + +_apt_update() { + # update package lists; may fail sometimes, + # e.g. soon-to-release channels like Debian "bullseye" @ 22.04.2021 + + # (wannabe) smart package list update + ts_sources=$(find_fresh_ts /etc/apt/ -follow -regextype egrep -regex '.+\.(list|sources)$' -type f) + ts_lists=$(find_fresh_ts /var/lib/apt/lists/ -maxdepth 1 -regextype egrep -regex '.+_Packages(\.(bz2|gz|lz[4o]|xz|zstd?))?$' -type f) + if [ ${ts_sources} -gt ${ts_lists} ] ; then + apt-env.sh apt-get update + fi +} + +_dpkg_avail_hack() { + VERSION_CODENAME=$(. /etc/os-release ; printf '%s' "${VERSION_CODENAME}") || : + f="${DPKG_ADMINDIR:-/var/lib/dpkg}/available" + # if ${VERSION_CODENAME} is empty then we're on Debian sid or so :) + case "${VERSION_CODENAME}" in + stretch | buster | bionic | focal ) + # ref: https://unix.stackexchange.com/a/271387/49297 + if [ -s "$f" ] ; then + return + fi + /usr/lib/dpkg/methods/apt/update "${DPKG_ADMINDIR:-/var/lib/dpkg}" apt apt + ;; + * ) + touch "$f" + ;; + esac +} + +_apt_update +_dpkg_avail_hack +exec apt-env.sh apt-get install -y --no-install-recommends --no-install-suggests "$@" diff --git a/scripts/apt-remove.sh b/scripts/apt-remove.sh new file mode 100755 index 0000000..dc032b6 --- /dev/null +++ b/scripts/apt-remove.sh @@ -0,0 +1,5 @@ +#!/bin/sh +set -ef + +apt-env.sh apt-get purge -y --allow-remove-essential "$@" +exec apt-env.sh apt-get autopurge -y diff --git a/scripts/divert-rm.sh b/scripts/divert-rm.sh new file mode 100755 index 0000000..79742d5 --- /dev/null +++ b/scripts/divert-rm.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -ef +: "${1:?}" +d=$(printf '%s' "/run/angie/divert/$1" | tr -s '/') +mkdir -p "${d%/*}" +dpkg-divert --divert "$d" --rename "$1" 2>/dev/null +rm -f "$d" diff --git a/scripts/envsubst-args.sh b/scripts/envsubst-args.sh new file mode 100755 index 0000000..27dbaef --- /dev/null +++ b/scripts/envsubst-args.sh @@ -0,0 +1,21 @@ +#!/bin/sh +set -f + +sed -znE '/^([^=]+)=.*$/s,,\1,p' /proc/self/environ \ +| sed -zE \ + -e '/^_$/d;/^ENVSUBST_/d;' \ + -e '/^__IEP_/d;/^IEP_$/d' \ +| { + if [ -n "${ENVSUBST_EXCLUDE_REGEX:-}" ] ; then + grep -zEv -e "${ENVSUBST_EXCLUDE_REGEX}" + else + if [ -n "${ENVSUBST_INCLUDE_REGEX:-}" ] ; then + grep -zE -e "${ENVSUBST_INCLUDE_REGEX}" + else + cat + fi + fi +} \ +| sort -zV \ +| xargs -0 -r printf '${%s} ' \ +| sed -zE 's/ $//' diff --git a/scripts/envsubst.sh b/scripts/envsubst.sh new file mode 100755 index 0000000..c446305 --- /dev/null +++ b/scripts/envsubst.sh @@ -0,0 +1,12 @@ +#!/bin/sh +set -f + +while [ -n "${ENVSUBST_ARGS}" ] ; do + [ -f "${ENVSUBST_ARGS}" ] || break + [ -s "${ENVSUBST_ARGS}" ] || break + + exec envsubst "$(cat "${ENVSUBST_ARGS}" &2 + printf ' - %s\n' "$@" >&2 +} +exec python3 "/usr/local/lib/j2cfg/${0##*/}.py" "$@" \ No newline at end of file diff --git a/scripts/j2cfg-single b/scripts/j2cfg-single new file mode 100755 index 0000000..eacddd0 --- /dev/null +++ b/scripts/j2cfg-single @@ -0,0 +1,7 @@ +#!/bin/sh +[ "${IEP_VERBOSE:-}" = 0 ] || { + pfx= + [ "${IEP_DEBUG:-}" = 0 ] || pfx="$(date +'%Y-%m-%d %H:%M:%S.%03N %z'): " + echo "# ${pfx}${0##*/}:${*:+ $*}" >&2 +} +exec python3 "/usr/local/lib/j2cfg/${0##*/}.py" "$@" \ No newline at end of file diff --git a/scripts/openssl-cert-auto-pem.sh b/scripts/openssl-cert-auto-pem.sh new file mode 100755 index 0000000..2ffff27 --- /dev/null +++ b/scripts/openssl-cert-auto-pem.sh @@ -0,0 +1,50 @@ +#!/bin/sh +set -f + +[ $# -gt 0 ] || exit 0 +me=${0##*/} + +[ -n "$1" ] || exit 1 +[ -f "$1" ] || { + env printf '%s: not a file or does not exist: %q\n' "${me}" "$1" >&2 + exit 1 +} +[ -s "$1" ] || exit 0 + +w=$(mktemp -d) || exit 1 +w_cleanup() { + [ -z "$w" ] || ls -lA "$w/" + [ -z "$w" ] || rm -rf "$w" + unset w + exit "${1:-0}" +} + +openssl storeutl -certs "$1" > "$w/cert.pem" || w_cleanup 1 +[ -s "$w/cert.pem" ] || w_cleanup 1 +tr -s '\r\n' '\n' < "$w/cert.pem" > "$w/cert.txt" +[ -s "$w/cert.txt" ] || w_cleanup 1 + +awk ' +BEGIN { + OFS = "," + m_begin="-----BEGIN CERTIFICATE-----" + m_end="-----END CERTIFICATE-----" + i_begin = 0 +} +$0 == m_begin { i_begin = NR ; } +$0 == m_end { + if (i_begin > 0) { + print i_begin,NR + i_begin = 0 + } +} +' "$w/cert.txt" > "$w/cert.offsets" +[ -s "$w/cert.offsets" ] || w_cleanup 1 + +while read -r a ; do + [ -n "$a" ] || continue + + sed -ne "${a}p" "$w/cert.txt" +done < "$w/cert.offsets" + +rm -rf "$w" ; unset w diff --git a/scripts/openssl-cert-fingerprint.sh b/scripts/openssl-cert-fingerprint.sh new file mode 100755 index 0000000..f41ad1d --- /dev/null +++ b/scripts/openssl-cert-fingerprint.sh @@ -0,0 +1,52 @@ +#!/bin/sh +set -f + +[ $# -gt 0 ] || exit 0 +me=${0##*/} + +[ -n "$1" ] || exit 1 +[ -f "$1" ] || { + env printf '%s: not a file or does not exist: %q\n' "${me}" "$1" >&2 + exit 1 +} +[ -s "$1" ] || exit 0 + +w=$(mktemp -d) || exit 1 +w_cleanup() { + [ -z "$w" ] || ls -lA "$w/" + [ -z "$w" ] || rm -rf "$w" + unset w + exit "${1:-0}" +} + +openssl-cert-auto-pem.sh "$1" > "$w/cert.pem" || w_cleanup 1 +[ -s "$w/cert.pem" ] || w_cleanup 1 + +awk ' +BEGIN { + OFS = "," + m_begin="-----BEGIN CERTIFICATE-----" + m_end="-----END CERTIFICATE-----" + i_begin = 0 +} +$0 == m_begin { i_begin = NR ; } +$0 == m_end { + if (i_begin > 0) { + print i_begin,NR + i_begin = 0 + } +} +' "$w/cert.pem" > "$w/cert.off" +[ -s "$w/cert.off" ] || w_cleanup 1 + +while read -r a ; do + [ -n "$a" ] || continue + + { + sed -ne "${a}p" "$w/cert.pem" | openssl x509 -noout -fingerprint -sha256 \ + || \ + sed -ne "${a}p" "$w/cert.pem" | openssl x509 -noout -fingerprint + } | tr '[:upper:]' '[:lower:]' +done < "$w/cert.off" + +w_cleanup 0 diff --git a/scripts/openssl-generate-dh-bundle.sh b/scripts/openssl-generate-dh-bundle.sh new file mode 100644 index 0000000..285452e --- /dev/null +++ b/scripts/openssl-generate-dh-bundle.sh @@ -0,0 +1,16 @@ +#!/bin/sh +set -ef + +for k in 1024 2048 ; do + f="dh${k}.pem" + echo "# openssl genpkey: $f" >&2 + timeout --kill-after=32s 30s \ + openssl genpkey -quiet -genparam -algorithm DH -out "./$f" -pkeyopt "dh_paramgen_prime_len:${k}" +done + +for k in 2048 3072 4096 ; do + f="ffdhe${k}.pem" + echo "# openssl genpkey: $f" >&2 + timeout --kill-after=32s 30s \ + openssl genpkey -quiet -genparam -algorithm DH -out "./$f" -pkeyopt "group:ffdhe${k}" +done diff --git a/scripts/openssl-ocsp.sh b/scripts/openssl-ocsp.sh new file mode 100755 index 0000000..4cf745e --- /dev/null +++ b/scripts/openssl-ocsp.sh @@ -0,0 +1,206 @@ +#!/bin/sh +set -ef + +ocsp_fetch_timeout=15 +ocsp_fetch_retries=3 +ocsp_fetch_retry_delay=5 +ocsp_valid_threshold=86400 + +usage() { + cat >&2 <<-EOF + # usage: ${0##*/} [args...] + # ${0##*/} get-uri + # ${0##*/} is-valid + # ${0##*/} is-expiring + # ${0##*/} fetch + EOF + exit "${1:-0}" +} +[ $# != 0 ] || usage + +## $1 - X509 in PEM format +ossl_x509_verify_fmt() { + openssl x509 -in "$1" -noout >/dev/null +} + +## $1 - cert +ossl_ocsp_uri() { + openssl x509 -in "$1" -noout -ocsp_uri \ + | head -n 1 +} + +## $1 - chain +## $2 - cert +## $3 - ocsp uri +## $4 - ocsp response +ossl_ocsp_fetch() { + openssl ocsp \ + -timeout "${ocsp_fetch_timeout}" \ + -nonce \ + -issuer "$1" -cert "$2" -url "$3" -respout "$4" +} + +## $1 - ocsp response +ossl_ocsp_verify_fmt() { + openssl ocsp \ + -noverify -respin "$1" +} + +## $1 - chain +## $2 - cert +## $3 - ocsp response +ossl_ocsp_read() { + openssl ocsp \ + -issuer "$1" -cert "$2" -respin "$3" -resp_text +} + +## $1 - chain +## $2 - cert +## $3 - ocsp response +ossl_ocsp_verify() { + ossl_ocsp_read "$@" >/dev/null +} + +## stdin - output of ossl_ocsp_read() +ossl_ocsp_next_update() { + sed -En '/^\s*[Nn]ext [Uu]pdate:\s*(\S.+\S)\s*$/{s//\1/;p;q}' +} + +unset arg_ok cmd chain cert ocsp_uri ocsp_resp +arg_ok= +while : ; do + [ -n "$1" ] || break + cmd="$1" + case "$1" in + get-uri ) + [ -n "$2" ] || break + [ -s "$2" ] || break + ossl_x509_verify_fmt "$2" || break + cert="$2" + ;; + is-valid | is-expiring | fetch ) + [ -n "$2" ] || break + [ -s "$2" ] || break + ossl_x509_verify_fmt "$2" || break + chain="$2" + [ -n "$3" ] || break + [ -s "$3" ] || break + ossl_x509_verify_fmt "$3" || break + cert="$3" + [ -n "$4" ] || break + ocsp_resp="$4" + ## OCSP response validation is handled later and in various ways (!) + ## e.g. "is-valid" (cmd_is_valid) validates OCSP response as expected + ## but "is-expiring" (cmd_is_expiring) returns success for invalid OCSP response + ## which means OCSP response should be updated ASAP + ;; + *) break ;; + esac + arg_ok=1 +break ; done +[ -n "${arg_ok}" ] || usage 1 +unset arg_ok + +## OCSP URI is used only in "get-uri" and "fetch" commands +## but implicitly required for all actions +ocsp_uri=$(ossl_ocsp_uri "${cert}") || exit 1 +if [ -z "${ocsp_uri}" ] ; then + env printf '%s: unable to extract OCSP URI from %q\n' "${0##*/}" "${cert}" >&2 + exit 1 +fi +## early command handling +if [ "${cmd}" = 'get-uri' ] ; then + printf '%s\n' "${ocsp_uri}" + exit 0 +fi + +## $1 - chain +## $2 - cert +## $3 - ocsp response +cmd_is_valid() { + ossl_ocsp_verify_fmt "$3" || return 1 + ossl_ocsp_verify "$1" "$2" "$3" || return 1 +} + +## $1 - chain +## $2 - cert +## $3 - ocsp response +cmd_is_expiring() { + cmd_is_valid "$1" "$2" "$3" || return 0 + + local need_update next ts_now ts_next ts_diff + + need_update=1 + while : ; do + next=$(ossl_ocsp_read "$1" "$2" "$3" 2>/dev/null | ossl_ocsp_next_update) + [ -n "${next}" ] || break + + ts_now=$(date '+%s') + ts_next=$(date -d "${next}" '+%s') + [ -n "${ts_next}" ] || break + [ ${ts_now} -lt ${ts_next} ] || break + + ts_diff=$((ts_next - ts_now)) + [ ${ts_diff} -le ${ocsp_valid_threshold} ] || need_update=0 + break ; done + + if [ "${need_update}" = 0 ] ; then + env printf '%q has valid and fresh OCSP response\n' "$2" >&2 + return 1 + fi + + return 0 +} + +## $1 - chain +## $2 - cert +## $3 - ocsp uri +## $4 - ocsp response +cmd_fetch() { + local t i r + + t=$(mktemp) ; : "${t:?}" + + for i in $(seq 1 "${ocsp_fetch_retries}") ; do + i= ## no-op + + if ossl_ocsp_fetch "$1" "$2" "$3" "$t" ; then + break + fi + : > "$t" + sleep "${ocsp_fetch_retry_delay}" + done + + r= + while : ; do + [ -s "$t" ] || break + cmd_is_valid "$1" "$2" "$t" || break + r=1 + break ; done + if [ -z "$r" ] ; then + env printf 'unable to fetch OCSP response for %q via %q\n' "$2" "$3" >&2 + rm -rf "$t" + return 1 + fi + + r= + while : ; do + touch "$4" || break + tee "$4" < "$t" >/dev/null || break + chmod 0644 "$4" || break + r=1 + break ; done + if [ -z "$r" ] ; then + env printf 'unable to save OCSP response for %q into %q\n' "$2" "$4" >&2 + rm -rf "$t" + return 1 + fi + + return 0 +} + +case "${cmd}" in +is-valid ) cmd_is_valid "${chain}" "${cert}" "${ocsp_resp}" ;; +is-expiring ) cmd_is_expiring "${chain}" "${cert}" "${ocsp_resp}" ;; +fetch ) cmd_fetch "${chain}" "${cert}" "${ocsp_uri}" "${ocsp_resp}" ;; +esac diff --git a/scripts/overlay-dir-list.sh b/scripts/overlay-dir-list.sh new file mode 100755 index 0000000..c8bc4cb --- /dev/null +++ b/scripts/overlay-dir-list.sh @@ -0,0 +1,278 @@ +#!/bin/sh +set -f + +## ephemeral limit, keep in sync with verify_depth() +max_depth=99 +default_depth=20 + +me="${0##*/}" +usage() { + cat >&2 <<-EOF + # usage: ${me} [options] [ ..] + # options: + # -d , --depth , --depth= + # limit search depth to (default: ${default_depth}, max: ${max_depth}) + # -z, --zero + # separate entries with NUL instead of LF + # -s, --split-path + # separate directory and file name parts with "|" instead of "/" + EOF + exit "${1:-0}" +} +[ $# != 0 ] || usage + +msg() { echo ${1:+"# ${me}: $*"} >&2 ; } +msgf() { + _____fmt="$1" ; shift + env printf "# ${me}: ${_____fmt}\\n" ${@+"$@"} >&2 + unset _____fmt +} + +verify_depth() { + case "$1" in + [1-9] | [1-9][0-9] ) return 0 ;; + esac + msgf 'error: wrong depth specifier: %q' "$1" + usage 1 +} + +o_depth= +f_zero_eol= +f_split_path= + +## process options +want_value= +n_opt=0 +for i ; do + if [ -n "${want_value}" ] ; then + case "${want_value}" in + depth ) + o_depth="$i" + verify_depth "${o_depth}" + ;; + esac + + want_value= + n_opt=$((n_opt+1)) + continue + fi + + case "$i" in + -d | --depth | --depth=* ) + if [ -n "${o_depth}" ] ; then + msg 'error: "depth" option already set' + usage 1 + fi + case "$i" in + *=* ) + o_depth="${i#*=}" + verify_depth "${o_depth}" + ;; + * ) want_value=depth ;; + esac + ;; + -z | --zero ) + if [ -n "${f_zero_eol}" ] ; then + msg 'error: "zero" flag already set' + usage 1 + fi + f_zero_eol=1 + ;; + -s | --split-path ) + if [ -n "${f_split_path}" ] ; then + msg 'error: "split-path" flag already set' + usage 1 + fi + f_split_path=1 + ;; + -* ) + msgf 'unknown option: %q' "$i" + usage 1 + ;; + * ) break ;; + esac + + n_opt=$((n_opt+1)) +done + +[ ${n_opt} = 0 ] || shift ${n_opt} + +[ $# != 0 ] || usage 1 + +: "${o_depth:=${default_depth}}" + +path_sep='/' +[ -z "${f_split_path}" ] || path_sep='|' + +entry_sep='\n' +[ -z "${f_zero_eol}" ] || entry_sep='\0' + +entry_fmt="%s${path_sep}%s${entry_sep}" + +## work directory +w=$(mktemp -d) ; : "${w:?}" + +_cleanup() { + cd / + rm -rf -- "$w" +} + +adjust_with_skiplist() { + [ -s "$1" ] || return 0 + [ -s "$2" ] || return 0 + + __skiptype="$3" + while read -r __skipname ; do + [ -n "${__skipname}" ] || continue + + mawk \ + -v "n=${__skipname}" \ + -v "t=${__skiptype}" \ + ' + BEGIN { + RS = ORS = "\0"; + FS = OFS = "|"; + } + { + if ($3 != n) { print; next; } + if (t == "") { next; } + if ($1 ~ t) { next; } + print; + } + ' < "$2" > "$2.t" + mv -f "$2.t" "$2" + done < "$1" + unset __skipname __skiptype +} + +: > "$w/all" + +## process arguments +for i ; do + ## arguments with '|' are skipped - try naming paths simplier + case "$i" in + *\|* ) + msgf 'argument with "|" is IGNORED: %q' "$i" + continue + ;; + esac + + ## non-existent directories are silently skipped + [ -d "$i" ] || continue + + ## generate directory listing + ## paths with '\n' and '|' are silently skipped - try naming paths simplier + find "$i/" -follow -mindepth 1 -maxdepth 1 ! -name '*|*' -printf '%y|%h|%P\0' \ + | sed -zE '/\n/d' > "$w/current" + + ## filter out uncommon entry types + ## allowed types: + ## d - directory + ## f - file + ## less common types (but also allowed): + ## b - block device + ## c - character device + ## p - pipe + ## s - socket + sed -i -zE '/^[^bcdfps]/d' "$w/current" + + ## empty directory - continue with next argument + [ -s "$w/current" ] || continue + + ## generate skiplist + grep -zE '\.-$' < "$w/current" \ + | cut -z -d '|' -f 3 \ + | sed -zE 's/\.-$//' \ + | tr '\0' '\n' > "$w/skip" + ## adjust current list: remove entries from skiplist + sed -i -zE '/\.-$/d' "$w/current" + + ## adjust accumulated list: remove "skip" entries + adjust_with_skiplist "$w/skip" "$w/all" + rm -f "$w/skip" + + ## adjust accumulated list: override all entries with "non-dir" entries from current list + ## NB: entry type from current list overrides entry from previously accumulated list + sed -zEn '/^[^d]/p' < "$w/current" \ + | cut -z -d '|' -f 3 \ + | tr '\0' '\n' > "$w/nondir" + adjust_with_skiplist "$w/nondir" "$w/all" + rm -f "$w/nondir" + + ## adjust accumulated list: override "non-dir" entries with "dir" entries from current list + ## NB: entry type from current list overrides entry from previously accumulated list + sed -zEn '/^d/p' < "$w/current" \ + | cut -z -d '|' -f 3 \ + | tr '\0' '\n' > "$w/dir" + adjust_with_skiplist "$w/dir" "$w/all" '[^d]' + rm -f "$w/dir" + + ## merge lists + sort -zV -t '|' -k 3 < "$w/current" >> "$w/all" + rm -f "$w/current" +done + +# nothing to do? +if ! [ -s "$w/all" ] ; then + _cleanup + exit 0 +fi + +cut -z -d '|' -f '3' < "$w/all" \ +| sort -zuV \ +| tr '\0' '\n' > "$w/names" + +: > "$w/dirnames" + +sub_depth=$(( o_depth - 1 )) + +while read -r name ; do + mawk \ + -v "n=${name}" \ + ' + BEGIN { + RS = "\0"; ORS = "\n"; + FS = OFS = "|"; + } + + $3 == n { print; } + ' < "$w/all" > "$w/list" + + while IFS='|' read -r dtype dir _name ; do + [ -n "${dtype}" ] || continue + + # ${_name} is unused + case "${dtype}" in + d ) + [ "${sub_depth}" != 0 ] || continue + + if grep -Fxq -e "${name}" "$w/dirnames" ; then + continue + fi + + printf '%s\n' "${name}" >> "$w/dirnames" + + mawk \ + -v "n=${name}" \ + ' + BEGIN { + ORS = "\0"; + FS = "|"; OFS = "/"; + } + + $3 == n { print $2,$3; } + ' < "$w/list" > "$w/dir.args" + + xargs -0 -a "$w/dir.args" "$0" ${f_zero_eol:+-z} ${f_split_path:+-s} -d "${sub_depth}" + rm -f "$w/dir.args" + ;; + * ) + printf "${entry_fmt}" "${dir}" "${name}" + ;; + esac + done < "$w/list" + +done < "$w/names" + +_cleanup +exit 0 diff --git a/scripts/pip-env.sh b/scripts/pip-env.sh new file mode 100755 index 0000000..8a182f8 --- /dev/null +++ b/scripts/pip-env.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -a +PIP_DISABLE_PIP_VERSION_CHECK=1 +PIP_NO_CACHE_DIR=1 +PIP_ROOT_USER_ACTION=ignore +PIP_NO_COMPILE=1 +set +a +exec "$@" diff --git a/scripts/python-rm-cache.sh b/scripts/python-rm-cache.sh new file mode 100755 index 0000000..039b846 --- /dev/null +++ b/scripts/python-rm-cache.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -f +for i ; do + find "$i/" -name __pycache__ -exec rm -rf {} + + find "$i/" ! -type d -name '*.py[co]' -exec rm -f {} + +done +exit 0 diff --git a/scripts/static-compress.sh b/scripts/static-compress.sh new file mode 100755 index 0000000..5b8c829 --- /dev/null +++ b/scripts/static-compress.sh @@ -0,0 +1,65 @@ +#!/bin/sh +set -f + +COMPRESS_MIN_RATIO=90 + +if command -V gzip >/dev/null ; then has_gzip=1 ; fi +if command -V brotli >/dev/null ; then has_brotli=1 ; fi +if command -V zstd >/dev/null ; then has_zstd=1 ; fi + +do_gzip() { [ -s "$1.gz" ] || gzip -1kf "$1" ; comp_fixup "$1" "$1.gz" ; } +do_brotli() { [ -s "$1.br" ] || brotli -1kf "$1" ; comp_fixup "$1" "$1.br" ; } +do_zstd() { [ -s "$1.zst" ] || zstd -q1kf "$1" ; comp_fixup "$1" "$1.zst" ; } + +comp_fixup() { + size1=$(env stat -c '%s' "$1") || return + + [ -f "$2" ] || return + [ -s "$2" ] || { rm -f "$2" ; return ; } + size2=$(env stat -c '%s' "$2") || return + + pow1=${#size1} ; pow2=${#size2} + + ## if size2 is _longer_ than size1 - compression did something wrong (file is bigger) + if [ ${pow2} -gt ${pow1} ] ; then + rm -f "$2" ; return + fi + + ## if size1 is _longer_ size2 more than 2 digits - compression was done very successful + ## doubtful but okay (c) Oleg Tinkov + if [ $(( pow1 - pow2 )) -gt 2 ] ; then + return + fi + + ## math hack! + if [ ${pow1} -gt 7 ] ; then + skew=$(( pow1 - 4 )) + pow1=$(( pow1 - skew )) + pow2=$(( pow2 - skew )) + size1=$(printf '%s' "${size1}" | cut -c 1-${pow1}) + size2=$(printf '%s' "${size2}" | cut -c 1-${pow2}) + fi + + ratio=$(( (100 * size2) / size1 )) + if [ ${ratio} -ge ${COMPRESS_MIN_RATIO} ] ; then + rm -f "$2" + else + ## seems to be excessive + : touch -r "$1" -m "$2" + fi +} + +for i ; do + [ -n "$i" ] || continue + + case "$i" in + *.br | *.gz | *.zst ) continue ;; + esac + + [ -f "$i" ] || continue + [ -s "$i" ] || continue + + [ -z "${has_gzip}" ] || do_gzip "$i" + [ -z "${has_brotli}" ] || do_brotli "$i" + [ -z "${has_zstd}" ] || do_zstd "$i" +done