diff --git a/.github/workflows/lint-dockerfiles.yml b/.github/workflows/lint-dockerfiles.yml index 467dc117..afdd6558 100644 --- a/.github/workflows/lint-dockerfiles.yml +++ b/.github/workflows/lint-dockerfiles.yml @@ -14,6 +14,7 @@ jobs: matrix: dockerfile: - docker/nginx/Dockerfile + - docker/nginx/testing/Dockerfile - docker/sia/Dockerfile - packages/dashboard/Dockerfile - packages/dashboard-v2/Dockerfile diff --git a/docker-compose.yml b/docker-compose.yml index 5838f136..6450a10f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -54,9 +54,11 @@ services: - ./docker/data/certbot:/etc/letsencrypt nginx: - build: - context: ./docker/nginx - dockerfile: Dockerfile + # uncomment "build" and comment out "image" to build from sources + # build: + # context: https://github.com/SkynetLabs/skynet-webportal.git#master + # dockerfile: ./docker/nginx/Dockerfile + image: skynetlabs/nginx container_name: nginx restart: unless-stopped logging: *default-logging @@ -69,6 +71,10 @@ services: - ./docker/data/nginx/skynet:/data/nginx/skynet:ro - ./docker/data/sia/apipassword:/data/sia/apipassword:ro - ./docker/data/certbot:/etc/letsencrypt + - ./docker/nginx/libs:/etc/nginx/libs + - ./docker/nginx/conf.d:/etc/nginx/conf.d + - ./docker/nginx/conf.d.templates:/etc/nginx/templates + - ./docker/nginx/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf networks: shared: ipv4_address: 10.10.10.30 diff --git a/docker/nginx/Dockerfile b/docker/nginx/Dockerfile index eca1e3d8..f35c0aff 100644 --- a/docker/nginx/Dockerfile +++ b/docker/nginx/Dockerfile @@ -2,25 +2,20 @@ FROM openresty/openresty:1.19.9.1-focal WORKDIR / -RUN luarocks install lua-resty-http && \ - luarocks install hasher && \ - openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \ - -subj '/CN=local-certificate' \ - -keyout /etc/ssl/local-certificate.key \ - -out /etc/ssl/local-certificate.crt +RUN apt-get update && apt-get --no-install-recommends -y install bc=1.07.1-2build1 && \ + apt-get clean && rm -rf /var/lib/apt/lists/* && \ + luarocks install lua-resty-http && \ + luarocks install hasher -COPY mo ./ -COPY libs /etc/nginx/libs -COPY conf.d /etc/nginx/conf.d -COPY conf.d.templates /etc/nginx/conf.d.templates -COPY nginx.conf /usr/local/openresty/nginx/conf/nginx.conf +# reload nginx every 6 hours (for reloading certificates) +ENV NGINX_ENTRYPOINT_RELOAD_EVERY_X_HOURS 6 -CMD [ "bash", "-c", \ - "./mo < /etc/nginx/conf.d.templates/server.account.conf > /etc/nginx/conf.d/server.account.conf ; \ - ./mo < /etc/nginx/conf.d.templates/server.api.conf > /etc/nginx/conf.d/server.api.conf; \ - ./mo < /etc/nginx/conf.d.templates/server.dnslink.conf > /etc/nginx/conf.d/server.dnslink.conf; \ - ./mo < /etc/nginx/conf.d.templates/server.hns.conf > /etc/nginx/conf.d/server.hns.conf; \ - ./mo < /etc/nginx/conf.d.templates/server.skylink.conf > /etc/nginx/conf.d/server.skylink.conf ; \ - while :; do sleep 6h & wait ${!}; /usr/local/openresty/bin/openresty -s reload; done & \ - /usr/local/openresty/bin/openresty '-g daemon off;'" \ - ] +# copy entrypoint and entrypoint scripts +COPY docker/nginx/docker-entrypoint.sh / +COPY docker/nginx/docker-entrypoint.d /docker-entrypoint.d + +ENTRYPOINT ["/docker-entrypoint.sh"] + +STOPSIGNAL SIGQUIT + +CMD ["nginx", "-g", "daemon off;"] diff --git a/docker/nginx/conf.d.templates/server.account.conf b/docker/nginx/conf.d.templates/server.account.conf deleted file mode 100644 index af3b7c4d..00000000 --- a/docker/nginx/conf.d.templates/server.account.conf +++ /dev/null @@ -1,45 +0,0 @@ -{{#ACCOUNTS_ENABLED}} - {{#PORTAL_DOMAIN}} - server { - server_name account.{{PORTAL_DOMAIN}}; # example: account.siasky.net - - include /etc/nginx/conf.d/server/server.http; - } - - server { - server_name account.{{PORTAL_DOMAIN}}; # example: account.siasky.net - - set_by_lua_block $skynet_portal_domain { return "{{PORTAL_DOMAIN}}" } - set_by_lua_block $skynet_server_domain { - -- fall back to portal domain if server domain is not defined - if "{{SERVER_DOMAIN}}" == "" then - return "{{PORTAL_DOMAIN}}" - end - return "{{SERVER_DOMAIN}}" - } - - include /etc/nginx/conf.d/server/server.account; - } - {{/PORTAL_DOMAIN}} - - {{#SERVER_DOMAIN}} - server { - server_name account.{{SERVER_DOMAIN}}; # example: account.eu-ger-1.siasky.net - - include /etc/nginx/conf.d/server/server.http; - - set_by_lua_block $server_alias { return string.match("{{SERVER_DOMAIN}}", "^([^.]+)") } - } - - server { - server_name account.{{SERVER_DOMAIN}}; # example: account.eu-ger-1.siasky.net - - set_by_lua_block $skynet_portal_domain { return "{{SERVER_DOMAIN}}" } - set_by_lua_block $skynet_server_domain { return "{{SERVER_DOMAIN}}" } - - include /etc/nginx/conf.d/server/server.account; - - set_by_lua_block $server_alias { return string.match("{{SERVER_DOMAIN}}", "^([^.]+)") } - } - {{/SERVER_DOMAIN}} -{{/ACCOUNTS_ENABLED}} diff --git a/docker/nginx/conf.d.templates/server.account.conf.template b/docker/nginx/conf.d.templates/server.account.conf.template new file mode 100644 index 00000000..8507c407 --- /dev/null +++ b/docker/nginx/conf.d.templates/server.account.conf.template @@ -0,0 +1,44 @@ +server { + server_name account.${PORTAL_DOMAIN}; # example: account.siasky.net + + include /etc/nginx/conf.d/server/server.http; +} + +server { + server_name account.${PORTAL_DOMAIN}; # example: account.siasky.net + + set_by_lua_block $skynet_portal_domain { return "${PORTAL_DOMAIN}" } + set_by_lua_block $skynet_server_domain { + -- fall back to portal domain if server domain is not defined + if "${SERVER_DOMAIN}" == "" then + return "${PORTAL_DOMAIN}" + end + return "${SERVER_DOMAIN}" + } + + include /etc/nginx/conf.d/server/server.account; +} + +server { + server_name account.${SERVER_DOMAIN}; # example: account.eu-ger-1.siasky.net + + include /etc/nginx/conf.d/server/server.http; + + set_by_lua_block $server_alias { return string.match("${SERVER_DOMAIN}", "^([^.]+)") } +} + +server { + server_name account.${SERVER_DOMAIN}; # example: account.eu-ger-1.siasky.net + + set_by_lua_block $skynet_portal_domain { + -- when accessing portal directly through server domain, portal domain should be set to server domain + -- motivation: skynet-js uses Skynet-Portal-Api header (that is set to $skynet_portal_domain) to detect current + -- portal address and it should be server domain when accessing specific server by its domain address + return "${SERVER_DOMAIN}" + } + set_by_lua_block $skynet_server_domain { return "${SERVER_DOMAIN}" } + + include /etc/nginx/conf.d/server/server.account; + + set_by_lua_block $server_alias { return string.match("${SERVER_DOMAIN}", "^([^.]+)") } +} diff --git a/docker/nginx/conf.d.templates/server.api.conf b/docker/nginx/conf.d.templates/server.api.conf deleted file mode 100644 index 591212ba..00000000 --- a/docker/nginx/conf.d.templates/server.api.conf +++ /dev/null @@ -1,43 +0,0 @@ -{{#PORTAL_DOMAIN}} -server { - server_name {{PORTAL_DOMAIN}}; # example: siasky.net - - include /etc/nginx/conf.d/server/server.http; -} - -server { - server_name {{PORTAL_DOMAIN}}; # example: siasky.net - - set_by_lua_block $skynet_portal_domain { return "{{PORTAL_DOMAIN}}" } - set_by_lua_block $skynet_server_domain { - -- fall back to portal domain if server domain is not defined - if "{{SERVER_DOMAIN}}" == "" then - return "{{PORTAL_DOMAIN}}" - end - return "{{SERVER_DOMAIN}}" - } - - include /etc/nginx/conf.d/server/server.api; -} -{{/PORTAL_DOMAIN}} - -{{#SERVER_DOMAIN}} -server { - server_name {{SERVER_DOMAIN}}; # example: eu-ger-1.siasky.net - - include /etc/nginx/conf.d/server/server.http; - - set_by_lua_block $server_alias { return string.match("{{SERVER_DOMAIN}}", "^([^.]+)") } -} - -server { - server_name {{SERVER_DOMAIN}}; # example: eu-ger-1.siasky.net - - set_by_lua_block $skynet_portal_domain { return "{{SERVER_DOMAIN}}" } - set_by_lua_block $skynet_server_domain { return "{{SERVER_DOMAIN}}" } - - include /etc/nginx/conf.d/server/server.api; - - set_by_lua_block $server_alias { return string.match("{{SERVER_DOMAIN}}", "^([^.]+)") } -} -{{/SERVER_DOMAIN}} diff --git a/docker/nginx/conf.d.templates/server.api.conf.template b/docker/nginx/conf.d.templates/server.api.conf.template new file mode 100644 index 00000000..5f742127 --- /dev/null +++ b/docker/nginx/conf.d.templates/server.api.conf.template @@ -0,0 +1,44 @@ +server { + server_name ${PORTAL_DOMAIN}; # example: siasky.net + + include /etc/nginx/conf.d/server/server.http; +} + +server { + server_name ${PORTAL_DOMAIN}; # example: siasky.net + + set_by_lua_block $skynet_portal_domain { return "${PORTAL_DOMAIN}" } + set_by_lua_block $skynet_server_domain { + -- fall back to portal domain if server domain is not defined + if "${SERVER_DOMAIN}" == "" then + return "${PORTAL_DOMAIN}" + end + return "${SERVER_DOMAIN}" + } + + include /etc/nginx/conf.d/server/server.api; +} + +server { + server_name ${SERVER_DOMAIN}; # example: eu-ger-1.siasky.net + + include /etc/nginx/conf.d/server/server.http; + + set_by_lua_block $server_alias { return string.match("${SERVER_DOMAIN}", "^([^.]+)") } +} + +server { + server_name ${SERVER_DOMAIN}; # example: eu-ger-1.siasky.net + + set_by_lua_block $skynet_portal_domain { + -- when accessing portal directly through server domain, portal domain should be set to server domain + -- motivation: skynet-js uses Skynet-Portal-Api header (that is set to $skynet_portal_domain) to detect current + -- portal address and it should be server domain when accessing specific server by its domain address + return "${SERVER_DOMAIN}" + } + set_by_lua_block $skynet_server_domain { return "${SERVER_DOMAIN}" } + + include /etc/nginx/conf.d/server/server.api; + + set_by_lua_block $server_alias { return string.match("${SERVER_DOMAIN}", "^([^.]+)") } +} diff --git a/docker/nginx/conf.d.templates/server.dnslink.conf b/docker/nginx/conf.d.templates/server.dnslink.conf.template similarity index 71% rename from docker/nginx/conf.d.templates/server.dnslink.conf rename to docker/nginx/conf.d.templates/server.dnslink.conf.template index d42ee245..95c623b6 100644 --- a/docker/nginx/conf.d.templates/server.dnslink.conf +++ b/docker/nginx/conf.d.templates/server.dnslink.conf.template @@ -12,13 +12,13 @@ server { ssl_certificate /etc/ssl/local-certificate.crt; ssl_certificate_key /etc/ssl/local-certificate.key; - set_by_lua_block $skynet_portal_domain { return "{{PORTAL_DOMAIN}}" } + set_by_lua_block $skynet_portal_domain { return "${PORTAL_DOMAIN}" } set_by_lua_block $skynet_server_domain { -- fall back to portal domain if server domain is not defined - if "{{SERVER_DOMAIN}}" == "" then - return "{{PORTAL_DOMAIN}}" + if "${SERVER_DOMAIN}" == "" then + return "${PORTAL_DOMAIN}" end - return "{{SERVER_DOMAIN}}" + return "${SERVER_DOMAIN}" } include /etc/nginx/conf.d/server/server.dnslink; diff --git a/docker/nginx/conf.d.templates/server.hns.conf b/docker/nginx/conf.d.templates/server.hns.conf deleted file mode 100644 index 0e4f21f3..00000000 --- a/docker/nginx/conf.d.templates/server.hns.conf +++ /dev/null @@ -1,45 +0,0 @@ -{{#PORTAL_DOMAIN}} -server { - server_name *.hns.{{PORTAL_DOMAIN}}; # example: *.hns.siasky.net - - include /etc/nginx/conf.d/server/server.http; -} - -server { - server_name *.hns.{{PORTAL_DOMAIN}}; # example: *.hns.siasky.net - - set_by_lua_block $skynet_portal_domain { return "{{PORTAL_DOMAIN}}" } - set_by_lua_block $skynet_server_domain { - -- fall back to portal domain if server domain is not defined - if "{{SERVER_DOMAIN}}" == "" then - return "{{PORTAL_DOMAIN}}" - end - return "{{SERVER_DOMAIN}}" - } - - proxy_set_header Host {{PORTAL_DOMAIN}}; - include /etc/nginx/conf.d/server/server.hns; -} -{{/PORTAL_DOMAIN}} - -{{#SERVER_DOMAIN}} -server { - server_name *.hns.{{SERVER_DOMAIN}}; # example: *.hns.eu-ger-1.siasky.net - - include /etc/nginx/conf.d/server/server.http; - - set_by_lua_block $server_alias { return string.match("{{SERVER_DOMAIN}}", "^([^.]+)") } -} - -server { - server_name *.hns.{{SERVER_DOMAIN}}; # example: *.hns.eu-ger-1.siasky.net - - set_by_lua_block $skynet_portal_domain { return "{{SERVER_DOMAIN}}" } - set_by_lua_block $skynet_server_domain { return "{{SERVER_DOMAIN}}" } - - proxy_set_header Host {{SERVER_DOMAIN}}; - include /etc/nginx/conf.d/server/server.hns; - - set_by_lua_block $server_alias { return string.match("{{SERVER_DOMAIN}}", "^([^.]+)") } -} -{{/SERVER_DOMAIN}} diff --git a/docker/nginx/conf.d.templates/server.hns.conf.template b/docker/nginx/conf.d.templates/server.hns.conf.template new file mode 100644 index 00000000..f7bb43fb --- /dev/null +++ b/docker/nginx/conf.d.templates/server.hns.conf.template @@ -0,0 +1,46 @@ +server { + server_name *.hns.${PORTAL_DOMAIN}; # example: *.hns.siasky.net + + include /etc/nginx/conf.d/server/server.http; +} + +server { + server_name *.hns.${PORTAL_DOMAIN}; # example: *.hns.siasky.net + + set_by_lua_block $skynet_portal_domain { return "${PORTAL_DOMAIN}" } + set_by_lua_block $skynet_server_domain { + -- fall back to portal domain if server domain is not defined + if "${SERVER_DOMAIN}" == "" then + return "${PORTAL_DOMAIN}" + end + return "${SERVER_DOMAIN}" + } + + proxy_set_header Host ${PORTAL_DOMAIN}; + include /etc/nginx/conf.d/server/server.hns; +} + +server { + server_name *.hns.${SERVER_DOMAIN}; # example: *.hns.eu-ger-1.siasky.net + + include /etc/nginx/conf.d/server/server.http; + + set_by_lua_block $server_alias { return string.match("${SERVER_DOMAIN}", "^([^.]+)") } +} + +server { + server_name *.hns.${SERVER_DOMAIN}; # example: *.hns.eu-ger-1.siasky.net + + set_by_lua_block $skynet_portal_domain { + -- when accessing portal directly through server domain, portal domain should be set to server domain + -- motivation: skynet-js uses Skynet-Portal-Api header (that is set to $skynet_portal_domain) to detect current + -- portal address and it should be server domain when accessing specific server by its domain address + return "${SERVER_DOMAIN}" + } + set_by_lua_block $skynet_server_domain { return "${SERVER_DOMAIN}" } + + proxy_set_header Host ${SERVER_DOMAIN}; + include /etc/nginx/conf.d/server/server.hns; + + set_by_lua_block $server_alias { return string.match("${SERVER_DOMAIN}", "^([^.]+)") } +} diff --git a/docker/nginx/conf.d.templates/server.skylink.conf b/docker/nginx/conf.d.templates/server.skylink.conf deleted file mode 100644 index a97e240c..00000000 --- a/docker/nginx/conf.d.templates/server.skylink.conf +++ /dev/null @@ -1,43 +0,0 @@ -{{#PORTAL_DOMAIN}} -server { - server_name *.{{PORTAL_DOMAIN}}; # example: *.siasky.net - - include /etc/nginx/conf.d/server/server.http; -} - -server { - server_name *.{{PORTAL_DOMAIN}}; # example: *.siasky.net - - set_by_lua_block $skynet_portal_domain { return "{{PORTAL_DOMAIN}}" } - set_by_lua_block $skynet_server_domain { - -- fall back to portal domain if server domain is not defined - if "{{SERVER_DOMAIN}}" == "" then - return "{{PORTAL_DOMAIN}}" - end - return "{{SERVER_DOMAIN}}" - } - - include /etc/nginx/conf.d/server/server.skylink; -} -{{/PORTAL_DOMAIN}} - -{{#SERVER_DOMAIN}} -server { - server_name *.{{SERVER_DOMAIN}}; # example: *.eu-ger-1.siasky.net - - include /etc/nginx/conf.d/server/server.http; - - set_by_lua_block $server_alias { return string.match("{{SERVER_DOMAIN}}", "^([^.]+)") } -} - -server { - server_name *.{{SERVER_DOMAIN}}; # example: *.eu-ger-1.siasky.net - - set_by_lua_block $skynet_portal_domain { return "{{SERVER_DOMAIN}}" } - set_by_lua_block $skynet_server_domain { return "{{SERVER_DOMAIN}}" } - - include /etc/nginx/conf.d/server/server.skylink; - - set_by_lua_block $server_alias { return string.match("{{SERVER_DOMAIN}}", "^([^.]+)") } -} -{{/SERVER_DOMAIN}} diff --git a/docker/nginx/conf.d.templates/server.skylink.conf.template b/docker/nginx/conf.d.templates/server.skylink.conf.template new file mode 100644 index 00000000..0d337abb --- /dev/null +++ b/docker/nginx/conf.d.templates/server.skylink.conf.template @@ -0,0 +1,44 @@ +server { + server_name *.${PORTAL_DOMAIN}; # example: *.siasky.net + + include /etc/nginx/conf.d/server/server.http; +} + +server { + server_name *.${PORTAL_DOMAIN}; # example: *.siasky.net + + set_by_lua_block $skynet_portal_domain { return "${PORTAL_DOMAIN}" } + set_by_lua_block $skynet_server_domain { + -- fall back to portal domain if server domain is not defined + if "${SERVER_DOMAIN}" == "" then + return "${PORTAL_DOMAIN}" + end + return "${SERVER_DOMAIN}" + } + + include /etc/nginx/conf.d/server/server.skylink; +} + +server { + server_name *.${SERVER_DOMAIN}; # example: *.eu-ger-1.siasky.net + + include /etc/nginx/conf.d/server/server.http; + + set_by_lua_block $server_alias { return string.match("${SERVER_DOMAIN}", "^([^.]+)") } +} + +server { + server_name *.${SERVER_DOMAIN}; # example: *.eu-ger-1.siasky.net + + set_by_lua_block $skynet_portal_domain { + -- when accessing portal directly through server domain, portal domain should be set to server domain + -- motivation: skynet-js uses Skynet-Portal-Api header (that is set to $skynet_portal_domain) to detect current + -- portal address and it should be server domain when accessing specific server by its domain address + return "${SERVER_DOMAIN}" + } + set_by_lua_block $skynet_server_domain { return "${SERVER_DOMAIN}" } + + include /etc/nginx/conf.d/server/server.skylink; + + set_by_lua_block $server_alias { return string.match("${SERVER_DOMAIN}", "^([^.]+)") } +} diff --git a/docker/nginx/docker-entrypoint.d/20-envsubst-on-templates.sh b/docker/nginx/docker-entrypoint.d/20-envsubst-on-templates.sh new file mode 100755 index 00000000..be9c5f8e --- /dev/null +++ b/docker/nginx/docker-entrypoint.d/20-envsubst-on-templates.sh @@ -0,0 +1,59 @@ +#!/bin/sh + +# https://github.com/nginxinc/docker-nginx/blob/master/entrypoint/20-envsubst-on-templates.sh +# https://github.com/nginxinc/docker-nginx/blob/master/LICENSE + +# Copyright (C) 2011-2016 Nginx, Inc. +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. + +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +set -e + +ME=$(basename $0) + +auto_envsubst() { + local template_dir="${NGINX_ENVSUBST_TEMPLATE_DIR:-/etc/nginx/templates}" + local suffix="${NGINX_ENVSUBST_TEMPLATE_SUFFIX:-.template}" + local output_dir="${NGINX_ENVSUBST_OUTPUT_DIR:-/etc/nginx/conf.d}" + + local template defined_envs relative_path output_path subdir + defined_envs=$(printf '${%s} ' $(env | cut -d= -f1)) + [ -d "$template_dir" ] || return 0 + if [ ! -w "$output_dir" ]; then + echo >&3 "$ME: ERROR: $template_dir exists, but $output_dir is not writable" + return 0 + fi + find "$template_dir" -follow -type f -name "*$suffix" -print | while read -r template; do + relative_path="${template#$template_dir/}" + output_path="$output_dir/${relative_path%$suffix}" + subdir=$(dirname "$relative_path") + # create a subdirectory where the template file exists + mkdir -p "$output_dir/$subdir" + echo >&3 "$ME: Running envsubst on $template to $output_path" + envsubst "$defined_envs" < "$template" > "$output_path" + done +} + +auto_envsubst + +exit 0 diff --git a/docker/nginx/docker-entrypoint.d/40-reload-every-x-hours.sh b/docker/nginx/docker-entrypoint.d/40-reload-every-x-hours.sh new file mode 100755 index 00000000..cdd7d17e --- /dev/null +++ b/docker/nginx/docker-entrypoint.d/40-reload-every-x-hours.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# source: https://github.com/nginxinc/docker-nginx/pull/509 + +set -e + +ME=$(basename $0) + +[ "${NGINX_ENTRYPOINT_RELOAD_EVERY_X_HOURS:-}" ] || exit 0 +if [ $(echo "$NGINX_ENTRYPOINT_RELOAD_EVERY_X_HOURS > 0" | bc) = 0 ]; then + echo >&3 "$ME: Error. Provide integer or floating point number greater that 0. See 'man sleep'." + exit 1 +fi + +start_background_reload() { + echo >&3 "$ME: Reloading Nginx every $NGINX_ENTRYPOINT_RELOAD_EVERY_X_HOURS hour(s)" + while :; do sleep ${NGINX_ENTRYPOINT_RELOAD_EVERY_X_HOURS}h; echo >&3 "$ME: Reloading Nginx ..." && nginx -s reload; done & +} + +start_background_reload diff --git a/docker/nginx/docker-entrypoint.d/50-generate-local-certificate.sh b/docker/nginx/docker-entrypoint.d/50-generate-local-certificate.sh new file mode 100755 index 00000000..f5ecada1 --- /dev/null +++ b/docker/nginx/docker-entrypoint.d/50-generate-local-certificate.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +# Generate locally signed ssl certificate to be used on routes +# that do not require certificate issued by trusted CA + +set -e + +ME=$(basename $0) + +generate_local_certificate() { + echo >&3 "$ME: Generating locally signed ssl certificate" + openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \ + -subj '/CN=local-certificate' \ + -keyout /etc/ssl/local-certificate.key \ + -out /etc/ssl/local-certificate.crt +} + +generate_local_certificate diff --git a/docker/nginx/docker-entrypoint.sh b/docker/nginx/docker-entrypoint.sh new file mode 100755 index 00000000..d45a6d9a --- /dev/null +++ b/docker/nginx/docker-entrypoint.sh @@ -0,0 +1,65 @@ +#!/bin/sh +# vim:sw=4:ts=4:et + +# https://github.com/nginxinc/docker-nginx/blob/master/entrypoint/docker-entrypoint.sh +# https://github.com/nginxinc/docker-nginx/blob/master/LICENSE + +# Copyright (C) 2011-2016 Nginx, Inc. +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. + +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +set -e + +if [ -z "${NGINX_ENTRYPOINT_QUIET_LOGS:-}" ]; then + exec 3>&1 +else + exec 3>/dev/null +fi + +if [ "$1" = "nginx" -o "$1" = "nginx-debug" ]; then + if /usr/bin/find "/docker-entrypoint.d/" -mindepth 1 -maxdepth 1 -type f -print -quit 2>/dev/null | read v; then + echo >&3 "$0: /docker-entrypoint.d/ is not empty, will attempt to perform configuration" + + echo >&3 "$0: Looking for shell scripts in /docker-entrypoint.d/" + find "/docker-entrypoint.d/" -follow -type f -print | sort -V | while read -r f; do + case "$f" in + *.sh) + if [ -x "$f" ]; then + echo >&3 "$0: Launching $f"; + "$f" + else + # warn on shell scripts without exec bit + echo >&3 "$0: Ignoring $f, not executable"; + fi + ;; + *) echo >&3 "$0: Ignoring $f";; + esac + done + + echo >&3 "$0: Configuration complete; ready for start up" + else + echo >&3 "$0: No files found in /docker-entrypoint.d/, skipping configuration" + fi +fi + +exec "$@" diff --git a/docker/nginx/mo b/docker/nginx/mo deleted file mode 100755 index ba8e48d1..00000000 --- a/docker/nginx/mo +++ /dev/null @@ -1,1106 +0,0 @@ -#!/usr/bin/env bash -# -#/ Mo is a mustache template rendering software written in bash. It inserts -#/ environment variables into templates. -#/ -#/ Simply put, mo will change {{VARIABLE}} into the value of that -#/ environment variable. You can use {{#VARIABLE}}content{{/VARIABLE}} to -#/ conditionally display content or iterate over the values of an array. -#/ -#/ Learn more about mustache templates at https://mustache.github.io/ -#/ -#/ Simple usage: -#/ -#/ mo [OPTIONS] filenames... -#/ -#/ Options: -#/ -#/ -u, --fail-not-set -#/ Fail upon expansion of an unset variable. -#/ -x, --fail-on-function -#/ Fail when a function returns a non-zero status code. -#/ -e, --false -#/ Treat the string "false" as empty for conditionals. -#/ -h, --help -#/ This message. -#/ -s=FILE, --source=FILE -#/ Load FILE into the environment before processing templates. -#/ Can be used multiple times. -# -# Mo is under a MIT style licence with an additional non-advertising clause. -# See LICENSE.md for the full text. -# -# This is open source! Please feel free to contribute. -# -# https://github.com/tests-always-included/mo - - -# Public: Template parser function. Writes templates to stdout. -# -# $0 - Name of the mo file, used for getting the help message. -# $@ - Filenames to parse. -# -# Options: -# -# --allow-function-arguments -# -# Permit functions in templates to be called with additional arguments. This -# puts template data directly in to the path of an eval statement. Use with -# caution. Not listed in the help because it only makes sense when mo is -# sourced. -# -# -u, --fail-not-set -# -# Fail upon expansion of an unset variable. Default behavior is to silently -# ignore and expand into empty string. -# -# -x, --fail-on-function -# -# Fail when a function used by a template returns an error status code. -# Alternately, ou may set the MO_FAIL_ON_FUNCTION environment variable to a -# non-empty value to enable this behavior. -# -# -e, --false -# -# Treat "false" as an empty value. You may set the MO_FALSE_IS_EMPTY -# environment variable instead to a non-empty value to enable this behavior. -# -# -h, --help -# -# Display a help message. -# -# -s=FILE, --source=FILE -# -# Source a file into the environment before processing template files. -# This can be used multiple times. -# -# -- -# -# Used to indicate the end of options. You may optionally use this when -# filenames may start with two hyphens. -# -# Mo uses the following environment variables: -# -# MO_ALLOW_FUNCTION_ARGUMENTS - When set to a non-empty value, this allows -# functions referenced in templates to receive additional -# options and arguments. This puts the content from the -# template directly into an eval statement. Use with extreme -# care. -# MO_FUNCTION_ARGS - Arguments passed to the function -# MO_FAIL_ON_FUNCTION - If a function returns a non-zero status code, abort -# with an error. -# MO_FAIL_ON_UNSET - When set to a non-empty value, expansion of an unset env -# variable will be aborted with an error. -# MO_FALSE_IS_EMPTY - When set to a non-empty value, the string "false" will be -# treated as an empty value for the purposes of conditionals. -# MO_ORIGINAL_COMMAND - Used to find the `mo` program in order to generate a -# help message. -# -# Returns nothing. -mo() ( - # This function executes in a subshell so IFS is reset. - # Namespace this variable so we don't conflict with desired values. - local moContent f2source files doubleHyphens - - IFS=$' \n\t' - files=() - doubleHyphens=false - - if [[ $# -gt 0 ]]; then - for arg in "$@"; do - if $doubleHyphens; then - #: After we encounter two hyphens together, all the rest - #: of the arguments are files. - files=("${files[@]}" "$arg") - else - case "$arg" in - -h|--h|--he|--hel|--help|-\?) - moUsage "$0" - exit 0 - ;; - - --allow-function-arguments) - # shellcheck disable=SC2030 - MO_ALLOW_FUNCTION_ARGUMENTS=true - ;; - - -u | --fail-not-set) - # shellcheck disable=SC2030 - MO_FAIL_ON_UNSET=true - ;; - - -x | --fail-on-function) - # shellcheck disable=SC2030 - MO_FAIL_ON_FUNCTION=true - ;; - - -e | --false) - # shellcheck disable=SC2030 - MO_FALSE_IS_EMPTY=true - ;; - - -s=* | --source=*) - if [[ "$arg" == --source=* ]]; then - f2source="${arg#--source=}" - else - f2source="${arg#-s=}" - fi - - if [[ -f "$f2source" ]]; then - # shellcheck disable=SC1090 - . "$f2source" - else - echo "No such file: $f2source" >&2 - exit 1 - fi - ;; - - --) - #: Set a flag indicating we've encountered double hyphens - doubleHyphens=true - ;; - - *) - #: Every arg that is not a flag or a option should be a file - files=(${files[@]+"${files[@]}"} "$arg") - ;; - esac - fi - done - fi - - moGetContent moContent "${files[@]}" || return 1 - moParse "$moContent" "" true -) - - -# Internal: Call a function. -# -# $1 - Variable for output -# $2 - Function to call -# $3 - Content to pass -# $4 - Additional arguments as a single string -# -# This can be dangerous, especially if you are using tags like -# {{someFunction ; rm -rf / }} -# -# Returns nothing. -moCallFunction() { - local moArgs moContent moFunctionArgs moFunctionResult - - moArgs=() - moTrimWhitespace moFunctionArgs "$4" - - # shellcheck disable=SC2031 - if [[ -n "${MO_ALLOW_FUNCTION_ARGUMENTS-}" ]]; then - # Intentionally bad behavior - # shellcheck disable=SC2206 - moArgs=($4) - fi - - moContent=$(echo -n "$3" | MO_FUNCTION_ARGS="$moFunctionArgs" eval "$2" "${moArgs[@]}") || { - moFunctionResult=$? - # shellcheck disable=SC2031 - if [[ -n "${MO_FAIL_ON_FUNCTION-}" && "$moFunctionResult" != 0 ]]; then - echo "Function '$2' with args (${moArgs[*]+"${moArgs[@]}"}) failed with status code $moFunctionResult" - exit "$moFunctionResult" - fi - } - - # shellcheck disable=SC2031 - local "$1" && moIndirect "$1" "$moContent" -} - - -# Internal: Scan content until the right end tag is found. Creates an array -# with the following members: -# -# [0] = Content before end tag -# [1] = End tag (complete tag) -# [2] = Content after end tag -# -# Everything using this function uses the "standalone tags" logic. -# -# $1 - Name of variable for the array -# $2 - Content -# $3 - Name of end tag -# $4 - If -z, do standalone tag processing before finishing -# -# Returns nothing. -moFindEndTag() { - local content remaining scanned standaloneBytes tag - - #: Find open tags - scanned="" - moSplit content "$2" '{{' '}}' - - while [[ "${#content[@]}" -gt 1 ]]; do - moTrimWhitespace tag "${content[1]}" - - #: Restore content[1] before we start using it - content[1]='{{'"${content[1]}"'}}' - - case $tag in - '#'* | '^'*) - #: Start another block - scanned="${scanned}${content[0]}${content[1]}" - moTrimWhitespace tag "${tag:1}" - moFindEndTag content "${content[2]}" "$tag" "loop" - scanned="${scanned}${content[0]}${content[1]}" - remaining=${content[2]} - ;; - - '/'*) - #: End a block - could be ours - moTrimWhitespace tag "${tag:1}" - scanned="$scanned${content[0]}" - - if [[ "$tag" == "$3" ]]; then - #: Found our end tag - if [[ -z "${4-}" ]] && moIsStandalone standaloneBytes "$scanned" "${content[2]}" true; then - #: This is also a standalone tag - clean up whitespace - #: and move those whitespace bytes to the "tag" element - # shellcheck disable=SC2206 - standaloneBytes=( $standaloneBytes ) - content[1]="${scanned:${standaloneBytes[0]}}${content[1]}${content[2]:0:${standaloneBytes[1]}}" - scanned="${scanned:0:${standaloneBytes[0]}}" - content[2]="${content[2]:${standaloneBytes[1]}}" - fi - - local "$1" && moIndirectArray "$1" "$scanned" "${content[1]}" "${content[2]}" - return 0 - fi - - scanned="$scanned${content[1]}" - remaining=${content[2]} - ;; - - *) - #: Ignore all other tags - scanned="${scanned}${content[0]}${content[1]}" - remaining=${content[2]} - ;; - esac - - moSplit content "$remaining" '{{' '}}' - done - - #: Did not find our closing tag - scanned="$scanned${content[0]}" - local "$1" && moIndirectArray "$1" "${scanned}" "" "" -} - - -# Internal: Find the first index of a substring. If not found, sets the -# index to -1. -# -# $1 - Destination variable for the index -# $2 - Haystack -# $3 - Needle -# -# Returns nothing. -moFindString() { - local pos string - - string=${2%%$3*} - [[ "$string" == "$2" ]] && pos=-1 || pos=${#string} - local "$1" && moIndirect "$1" "$pos" -} - - -# Internal: Generate a dotted name based on current context and target name. -# -# $1 - Target variable to store results -# $2 - Context name -# $3 - Desired variable name -# -# Returns nothing. -moFullTagName() { - if [[ -z "${2-}" ]] || [[ "$2" == *.* ]]; then - local "$1" && moIndirect "$1" "$3" - else - local "$1" && moIndirect "$1" "${2}.${3}" - fi -} - - -# Internal: Fetches the content to parse into a variable. Can be a list of -# partials for files or the content from stdin. -# -# $1 - Variable name to assign this content back as -# $2-@ - File names (optional) -# -# Returns nothing. -moGetContent() { - local moContent moFilename moTarget - - moTarget=$1 - shift - if [[ "${#@}" -gt 0 ]]; then - moContent="" - - for moFilename in "$@"; do - #: This is so relative paths work from inside template files - moContent="$moContent"'{{>'"$moFilename"'}}' - done - else - moLoadFile moContent || return 1 - fi - - local "$moTarget" && moIndirect "$moTarget" "$moContent" -} - - -# Internal: Indent a string, placing the indent at the beginning of every -# line that has any content. -# -# $1 - Name of destination variable to get an array of lines -# $2 - The indent string -# $3 - The string to reindent -# -# Returns nothing. -moIndentLines() { - local content fragment len posN posR result trimmed - - result="" - - #: Remove the period from the end of the string. - len=$((${#3} - 1)) - content=${3:0:$len} - - if [[ -z "${2-}" ]]; then - local "$1" && moIndirect "$1" "$content" - - return 0 - fi - - moFindString posN "$content" $'\n' - moFindString posR "$content" $'\r' - - while [[ "$posN" -gt -1 ]] || [[ "$posR" -gt -1 ]]; do - if [[ "$posN" -gt -1 ]]; then - fragment="${content:0:$posN + 1}" - content=${content:$posN + 1} - else - fragment="${content:0:$posR + 1}" - content=${content:$posR + 1} - fi - - moTrimChars trimmed "$fragment" false true " " $'\t' $'\n' $'\r' - - if [[ -n "$trimmed" ]]; then - fragment="$2$fragment" - fi - - result="$result$fragment" - - moFindString posN "$content" $'\n' - moFindString posR "$content" $'\r' - - # If the content ends in a newline, do not indent. - if [[ "$posN" -eq ${#content} ]]; then - # Special clause for \r\n - if [[ "$posR" -eq "$((posN - 1))" ]]; then - posR=-1 - fi - - posN=-1 - fi - - if [[ "$posR" -eq ${#content} ]]; then - posR=-1 - fi - done - - moTrimChars trimmed "$content" false true " " $'\t' - - if [[ -n "$trimmed" ]]; then - content="$2$content" - fi - - result="$result$content" - - local "$1" && moIndirect "$1" "$result" -} - - -# Internal: Send a variable up to the parent of the caller of this function. -# -# $1 - Variable name -# $2 - Value -# -# Examples -# -# callFunc () { -# local "$1" && moIndirect "$1" "the value" -# } -# callFunc dest -# echo "$dest" # writes "the value" -# -# Returns nothing. -moIndirect() { - unset -v "$1" - printf -v "$1" '%s' "$2" -} - - -# Internal: Send an array as a variable up to caller of a function -# -# $1 - Variable name -# $2-@ - Array elements -# -# Examples -# -# callFunc () { -# local myArray=(one two three) -# local "$1" && moIndirectArray "$1" "${myArray[@]}" -# } -# callFunc dest -# echo "${dest[@]}" # writes "one two three" -# -# Returns nothing. -moIndirectArray() { - unset -v "$1" - - # IFS must be set to a string containing space or unset in order for - # the array slicing to work regardless of the current IFS setting on - # bash 3. This is detailed further at - # https://github.com/fidian/gg-core/pull/7 - eval "$(printf "IFS= %s=(\"\${@:2}\") IFS=%q" "$1" "$IFS")" -} - - -# Internal: Determine if a given environment variable exists and if it is -# an array. -# -# $1 - Name of environment variable -# -# Be extremely careful. Even if strict mode is enabled, it is not honored -# in newer versions of Bash. Any errors that crop up here will not be -# caught automatically. -# -# Examples -# -# var=(abc) -# if moIsArray var; then -# echo "This is an array" -# echo "Make sure you don't accidentally use \$var" -# fi -# -# Returns 0 if the name is not empty, 1 otherwise. -moIsArray() { - # Namespace this variable so we don't conflict with what we're testing. - local moTestResult - - moTestResult=$(declare -p "$1" 2>/dev/null) || return 1 - [[ "${moTestResult:0:10}" == "declare -a" ]] && return 0 - [[ "${moTestResult:0:10}" == "declare -A" ]] && return 0 - - return 1 -} - - -# Internal: Determine if the given name is a defined function. -# -# $1 - Function name to check -# -# Be extremely careful. Even if strict mode is enabled, it is not honored -# in newer versions of Bash. Any errors that crop up here will not be -# caught automatically. -# -# Examples -# -# moo () { -# echo "This is a function" -# } -# if moIsFunction moo; then -# echo "moo is a defined function" -# fi -# -# Returns 0 if the name is a function, 1 otherwise. -moIsFunction() { - local functionList functionName - - functionList=$(declare -F) - # shellcheck disable=SC2206 - functionList=( ${functionList//declare -f /} ) - - for functionName in "${functionList[@]}"; do - if [[ "$functionName" == "$1" ]]; then - return 0 - fi - done - - return 1 -} - - -# Internal: Determine if the tag is a standalone tag based on whitespace -# before and after the tag. -# -# Passes back a string containing two numbers in the format "BEFORE AFTER" -# like "27 10". It indicates the number of bytes remaining in the "before" -# string (27) and the number of bytes to trim in the "after" string (10). -# Useful for string manipulation: -# -# $1 - Variable to set for passing data back -# $2 - Content before the tag -# $3 - Content after the tag -# $4 - true/false: is this the beginning of the content? -# -# Examples -# -# moIsStandalone RESULT "$before" "$after" false || return 0 -# RESULT_ARRAY=( $RESULT ) -# echo "${before:0:${RESULT_ARRAY[0]}}...${after:${RESULT_ARRAY[1]}}" -# -# Returns nothing. -moIsStandalone() { - local afterTrimmed beforeTrimmed char - - moTrimChars beforeTrimmed "$2" false true " " $'\t' - moTrimChars afterTrimmed "$3" true false " " $'\t' - char=$((${#beforeTrimmed} - 1)) - char=${beforeTrimmed:$char} - - # If the content before didn't end in a newline - if [[ "$char" != $'\n' ]] && [[ "$char" != $'\r' ]]; then - # and there was content or this didn't start the file - if [[ -n "$char" ]] || ! $4; then - # then this is not a standalone tag. - return 1 - fi - fi - - char=${afterTrimmed:0:1} - - # If the content after doesn't start with a newline and it is something - if [[ "$char" != $'\n' ]] && [[ "$char" != $'\r' ]] && [[ -n "$char" ]]; then - # then this is not a standalone tag. - return 2 - fi - - if [[ "$char" == $'\r' ]] && [[ "${afterTrimmed:1:1}" == $'\n' ]]; then - char="$char"$'\n' - fi - - local "$1" && moIndirect "$1" "$((${#beforeTrimmed})) $((${#3} + ${#char} - ${#afterTrimmed}))" -} - - -# Internal: Join / implode an array -# -# $1 - Variable name to receive the joined content -# $2 - Joiner -# $3-$* - Elements to join -# -# Returns nothing. -moJoin() { - local joiner part result target - - target=$1 - joiner=$2 - result=$3 - shift 3 - - for part in "$@"; do - result="$result$joiner$part" - done - - local "$target" && moIndirect "$target" "$result" -} - - -# Internal: Read a file into a variable. -# -# $1 - Variable name to receive the file's content -# $2 - Filename to load - if empty, defaults to /dev/stdin -# -# Returns nothing. -moLoadFile() { - local content len - - # The subshell removes any trailing newlines. We forcibly add - # a dot to the content to preserve all newlines. - # As a future optimization, it would be worth considering removing - # cat and replacing this with a read loop. - - content=$(cat -- "${2:-/dev/stdin}" && echo '.') || return 1 - len=$((${#content} - 1)) - content=${content:0:$len} # Remove last dot - - local "$1" && moIndirect "$1" "$content" -} - - -# Internal: Process a chunk of content some number of times. Writes output -# to stdout. -# -# $1 - Content to parse repeatedly -# $2 - Tag prefix (context name) -# $3-@ - Names to insert into the parsed content -# -# Returns nothing. -moLoop() { - local content context contextBase - - content=$1 - contextBase=$2 - shift 2 - - while [[ "${#@}" -gt 0 ]]; do - moFullTagName context "$contextBase" "$1" - moParse "$content" "$context" false - shift - done -} - - -# Internal: Parse a block of text, writing the result to stdout. -# -# $1 - Block of text to change -# $2 - Current name (the variable NAME for what {{.}} means) -# $3 - true when no content before this, false otherwise -# -# Returns nothing. -moParse() { - # Keep naming variables mo* here to not overwrite needed variables - # used in the string replacements - local moArgs moBlock moContent moCurrent moIsBeginning moNextIsBeginning moTag - - moCurrent=$2 - moIsBeginning=$3 - - # Find open tags - moSplit moContent "$1" '{{' '}}' - - while [[ "${#moContent[@]}" -gt 1 ]]; do - moTrimWhitespace moTag "${moContent[1]}" - moNextIsBeginning=false - - case $moTag in - '#'*) - # Loop, if/then, or pass content through function - # Sets context - moStandaloneAllowed moContent "${moContent[@]}" "$moIsBeginning" - moTrimWhitespace moTag "${moTag:1}" - - # Split arguments from the tag name. Arguments are passed to - # functions. - moArgs=$moTag - moTag=${moTag%% *} - moTag=${moTag%%$'\t'*} - moArgs=${moArgs:${#moTag}} - moFindEndTag moBlock "$moContent" "$moTag" - moFullTagName moTag "$moCurrent" "$moTag" - - if moTest "$moTag"; then - # Show / loop / pass through function - if moIsFunction "$moTag"; then - moCallFunction moContent "$moTag" "${moBlock[0]}" "$moArgs" - moParse "$moContent" "$moCurrent" false - moContent="${moBlock[2]}" - elif moIsArray "$moTag"; then - eval "moLoop \"\${moBlock[0]}\" \"$moTag\" \"\${!${moTag}[@]}\"" - else - moParse "${moBlock[0]}" "$moCurrent" true - fi - fi - - moContent="${moBlock[2]}" - ;; - - '>'*) - # Load partial - get name of file relative to cwd - moPartial moContent "${moContent[@]}" "$moIsBeginning" "$moCurrent" - moNextIsBeginning=${moContent[1]} - moContent=${moContent[0]} - ;; - - '/'*) - # Closing tag - If hit in this loop, we simply ignore - # Matching tags are found in moFindEndTag - moStandaloneAllowed moContent "${moContent[@]}" "$moIsBeginning" - ;; - - '^'*) - # Display section if named thing does not exist - moStandaloneAllowed moContent "${moContent[@]}" "$moIsBeginning" - moTrimWhitespace moTag "${moTag:1}" - moFindEndTag moBlock "$moContent" "$moTag" - moFullTagName moTag "$moCurrent" "$moTag" - - if ! moTest "$moTag"; then - moParse "${moBlock[0]}" "$moCurrent" false "$moCurrent" - fi - - moContent="${moBlock[2]}" - ;; - - '!'*) - # Comment - ignore the tag content entirely - # Trim spaces/tabs before the comment - moStandaloneAllowed moContent "${moContent[@]}" "$moIsBeginning" - ;; - - .) - # Current content (environment variable or function) - moStandaloneDenied moContent "${moContent[@]}" - moShow "$moCurrent" "$moCurrent" - ;; - - '=') - # Change delimiters - # Any two non-whitespace sequences separated by whitespace. - # This tag is ignored. - moStandaloneAllowed moContent "${moContent[@]}" "$moIsBeginning" - ;; - - '{'*) - # Unescaped - split on }}} not }} - moStandaloneDenied moContent "${moContent[@]}" - moContent="${moTag:1}"'}}'"$moContent" - moSplit moContent "$moContent" '}}}' - moTrimWhitespace moTag "${moContent[0]}" - moArgs=$moTag - moTag=${moTag%% *} - moTag=${moTag%%$'\t'*} - moArgs=${moArgs:${#moTag}} - moFullTagName moTag "$moCurrent" "$moTag" - moContent=${moContent[1]} - - # Now show the value - # Quote moArgs here, do not quote it later. - moShow "$moTag" "$moCurrent" "$moArgs" - ;; - - '&'*) - # Unescaped - moStandaloneDenied moContent "${moContent[@]}" - moTrimWhitespace moTag "${moTag:1}" - moFullTagName moTag "$moCurrent" "$moTag" - moShow "$moTag" "$moCurrent" - ;; - - *) - # Normal environment variable or function call - moStandaloneDenied moContent "${moContent[@]}" - moArgs=$moTag - moTag=${moTag%% *} - moTag=${moTag%%$'\t'*} - moArgs=${moArgs:${#moTag}} - moFullTagName moTag "$moCurrent" "$moTag" - - # Quote moArgs here, do not quote it later. - moShow "$moTag" "$moCurrent" "$moArgs" - ;; - esac - - moIsBeginning=$moNextIsBeginning - moSplit moContent "$moContent" '{{' '}}' - done - - echo -n "${moContent[0]}" -} - - -# Internal: Process a partial. -# -# Indentation should be applied to the entire partial. -# -# This sends back the "is beginning" flag because the newline after a -# standalone partial is consumed. That newline is very important in the middle -# of content. We send back this flag to reset the processing loop's -# `moIsBeginning` variable, so the software thinks we are back at the -# beginning of a file and standalone processing continues to work. -# -# Prefix all variables. -# -# $1 - Name of destination variable. Element [0] is the content, [1] is the -# true/false flag indicating if we are at the beginning of content. -# $2 - Content before the tag that was not yet written -# $3 - Tag content -# $4 - Content after the tag -# $5 - true/false: is this the beginning of the content? -# $6 - Current context name -# -# Returns nothing. -moPartial() { - # Namespace variables here to prevent conflicts. - local moContent moFilename moIndent moIsBeginning moPartial moStandalone moUnindented - - if moIsStandalone moStandalone "$2" "$4" "$5"; then - # shellcheck disable=SC2206 - moStandalone=( $moStandalone ) - echo -n "${2:0:${moStandalone[0]}}" - moIndent=${2:${moStandalone[0]}} - moContent=${4:${moStandalone[1]}} - moIsBeginning=true - else - moIndent="" - echo -n "$2" - moContent=$4 - moIsBeginning=$5 - fi - - moTrimWhitespace moFilename "${3:1}" - - # Execute in subshell to preserve current cwd and environment - ( - # It would be nice to remove `dirname` and use a function instead, - # but that's difficult when you're only given filenames. - cd "$(dirname -- "$moFilename")" || exit 1 - moUnindented="$( - moLoadFile moPartial "${moFilename##*/}" || exit 1 - moParse "${moPartial}" "$6" true - - # Fix bash handling of subshells and keep trailing whitespace. - # This is removed in moIndentLines. - echo -n "." - )" || exit 1 - moIndentLines moPartial "$moIndent" "$moUnindented" - echo -n "$moPartial" - ) || exit 1 - - # If this is a standalone tag, the trailing newline after the tag is - # removed and the contents of the partial are added, which typically - # contain a newline. We need to send a signal back to the processing - # loop that the moIsBeginning flag needs to be turned on again. - # - # [0] is the content, [1] is that flag. - local "$1" && moIndirectArray "$1" "$moContent" "$moIsBeginning" -} - - -# Internal: Show an environment variable or the output of a function to -# stdout. -# -# Limit/prefix any variables used. -# -# $1 - Name of environment variable or function -# $2 - Current context -# $3 - Arguments string if $1 is a function -# -# Returns nothing. -moShow() { - # Namespace these variables - local moJoined moNameParts moContent - - if moIsFunction "$1"; then - moCallFunction moContent "$1" "" "$3" - moParse "$moContent" "$2" false - return 0 - fi - - moSplit moNameParts "$1" "." - - if [[ -z "${moNameParts[1]-}" ]]; then - if moIsArray "$1"; then - eval moJoin moJoined "," "\${$1[@]}" - echo -n "$moJoined" - else - # shellcheck disable=SC2031 - if moTestVarSet "$1"; then - echo -n "${!1}" - elif [[ -n "${MO_FAIL_ON_UNSET-}" ]]; then - echo "Env variable not set: $1" >&2 - exit 1 - fi - fi - else - # Further subindexes are disallowed - eval "echo -n \"\${${moNameParts[0]}[${moNameParts[1]%%.*}]}\"" - fi -} - - -# Internal: Split a larger string into an array. -# -# $1 - Destination variable -# $2 - String to split -# $3 - Starting delimiter -# $4 - Ending delimiter (optional) -# -# Returns nothing. -moSplit() { - local pos result - - result=( "$2" ) - moFindString pos "${result[0]}" "$3" - - if [[ "$pos" -ne -1 ]]; then - # The first delimiter was found - result[1]=${result[0]:$pos + ${#3}} - result[0]=${result[0]:0:$pos} - - if [[ -n "${4-}" ]]; then - moFindString pos "${result[1]}" "$4" - - if [[ "$pos" -ne -1 ]]; then - # The second delimiter was found - result[2]="${result[1]:$pos + ${#4}}" - result[1]="${result[1]:0:$pos}" - fi - fi - fi - - local "$1" && moIndirectArray "$1" "${result[@]}" -} - - -# Internal: Handle the content for a standalone tag. This means removing -# whitespace (not newlines) before a tag and whitespace and a newline after -# a tag. That is, assuming, that the line is otherwise empty. -# -# $1 - Name of destination "content" variable. -# $2 - Content before the tag that was not yet written -# $3 - Tag content (not used) -# $4 - Content after the tag -# $5 - true/false: is this the beginning of the content? -# -# Returns nothing. -moStandaloneAllowed() { - local bytes - - if moIsStandalone bytes "$2" "$4" "$5"; then - # shellcheck disable=SC2206 - bytes=( $bytes ) - echo -n "${2:0:${bytes[0]}}" - local "$1" && moIndirect "$1" "${4:${bytes[1]}}" - else - echo -n "$2" - local "$1" && moIndirect "$1" "$4" - fi -} - - -# Internal: Handle the content for a tag that is never "standalone". No -# adjustments are made for newlines and whitespace. -# -# $1 - Name of destination "content" variable. -# $2 - Content before the tag that was not yet written -# $3 - Tag content (not used) -# $4 - Content after the tag -# -# Returns nothing. -moStandaloneDenied() { - echo -n "$2" - local "$1" && moIndirect "$1" "$4" -} - - -# Internal: Determines if the named thing is a function or if it is a -# non-empty environment variable. When MO_FALSE_IS_EMPTY is set to a -# non-empty value, then "false" is also treated is an empty value. -# -# Do not use variables without prefixes here if possible as this needs to -# check if any name exists in the environment -# -# $1 - Name of environment variable or function -# $2 - Current value (our context) -# MO_FALSE_IS_EMPTY - When set to a non-empty value, this will say the -# string value "false" is empty. -# -# Returns 0 if the name is not empty, 1 otherwise. When MO_FALSE_IS_EMPTY -# is set, this returns 1 if the name is "false". -moTest() { - # Test for functions - moIsFunction "$1" && return 0 - - if moIsArray "$1"; then - # Arrays must have at least 1 element - eval "[[ \"\${#${1}[@]}\" -gt 0 ]]" && return 0 - else - # If MO_FALSE_IS_EMPTY is set, then return 1 if the value of - # the variable is "false". - # shellcheck disable=SC2031 - [[ -n "${MO_FALSE_IS_EMPTY-}" ]] && [[ "${!1-}" == "false" ]] && return 1 - - # Environment variables must not be empty - [[ -n "${!1-}" ]] && return 0 - fi - - return 1 -} - -# Internal: Determine if a variable is assigned, even if it is assigned an empty -# value. -# -# $1 - Variable name to check. -# -# Returns true (0) if the variable is set, 1 if the variable is unset. -moTestVarSet() { - [[ "${!1-a}" == "${!1-b}" ]] -} - - -# Internal: Trim the leading whitespace only. -# -# $1 - Name of destination variable -# $2 - The string -# $3 - true/false - trim front? -# $4 - true/false - trim end? -# $5-@ - Characters to trim -# -# Returns nothing. -moTrimChars() { - local back current front last target varName - - target=$1 - current=$2 - front=$3 - back=$4 - last="" - shift 4 # Remove target, string, trim front flag, trim end flag - - while [[ "$current" != "$last" ]]; do - last=$current - - for varName in "$@"; do - $front && current="${current/#$varName}" - $back && current="${current/%$varName}" - done - done - - local "$target" && moIndirect "$target" "$current" -} - - -# Internal: Trim leading and trailing whitespace from a string. -# -# $1 - Name of variable to store trimmed string -# $2 - The string -# -# Returns nothing. -moTrimWhitespace() { - local result - - moTrimChars result "$2" true true $'\r' $'\n' $'\t' " " - local "$1" && moIndirect "$1" "$result" -} - - -# Internal: Displays the usage for mo. Pulls this from the file that -# contained the `mo` function. Can only work when the right filename -# comes is the one argument, and that only happens when `mo` is called -# with `$0` set to this file. -# -# $1 - Filename that has the help message -# -# Returns nothing. -moUsage() { - grep '^#/' "${MO_ORIGINAL_COMMAND}" | cut -c 4- - echo "" - echo "MO_VERSION=$MO_VERSION" -} - - -# Save the original command's path for usage later -MO_ORIGINAL_COMMAND="$(cd "${BASH_SOURCE[0]%/*}" || exit 1; pwd)/${BASH_SOURCE[0]##*/}" -MO_VERSION="2.2.0" - -# If sourced, load all functions. -# If executed, perform the actions as expected. -if [[ "$0" == "${BASH_SOURCE[0]}" ]] || [[ -z "${BASH_SOURCE[0]}" ]]; then - mo "$@" -fi diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf index 3517a6bc..de55d276 100644 --- a/docker/nginx/nginx.conf +++ b/docker/nginx/nginx.conf @@ -19,6 +19,9 @@ user root; worker_processes auto; +# Enables the use of JIT for regular expressions to speed-up their processing. +pcre_jit on; + #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; diff --git a/docker/nginx/testing/Dockerfile b/docker/nginx/testing/Dockerfile index ae2cd78f..ed7740b6 100644 --- a/docker/nginx/testing/Dockerfile +++ b/docker/nginx/testing/Dockerfile @@ -8,4 +8,4 @@ RUN luarocks install lua-resty-http && \ COPY rbusted /etc/nginx/ -CMD /etc/nginx/rbusted --verbose --pattern=spec /usr/local/openresty/site/lualib +CMD ["/etc/nginx/rbusted", "--verbose", "--pattern=spec", "/usr/local/openresty/site/lualib"]