diff --git a/.github/workflows/docker-compose.yml b/.github/workflows/docker-compose.yml new file mode 100644 index 0000000..b7e6ca7 --- /dev/null +++ b/.github/workflows/docker-compose.yml @@ -0,0 +1,57 @@ +--- + +# ------------------------------------------------------------------------------------------------- +# Job Name +# ------------------------------------------------------------------------------------------------- +name: docker-compose + + +# ------------------------------------------------------------------------------------------------- +# When to run +# ------------------------------------------------------------------------------------------------- +on: + pull_request: + paths: + - '.github/workflows/docker-compose.yml' + - 'Dockerfiles/**' + - 'examples/**/docker-compose.yml' + - 'examples/**/integration-test.sh' + - 'examples/integration-test.sh' + + +jobs: + docker-compose: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: 'docker-compose: main-vhost Static Files' + run: | + cd ./examples/default-vhost__static-files/ + ./integration-test.sh + + - name: 'docker-compose: main-vhost PHP-FPM' + run: | + cd ./examples/default-vhost__php-fpm/ + ./integration-test.sh + + - name: 'docker-compose: main-vhost PHP-FPM (SSL)' + run: | + cd ./examples/default-vhost__php-fpm__ssl/ + ./integration-test.sh + + - name: 'docker-compose: main-vhost Reverse Proxy (NodeJS)' + run: | + cd ./examples/default-vhost__reverse-proxy__node/ + ./integration-test.sh + + - name: 'docker-compose: mass-vhost PHP-FPM (SSL)' + run: | + cd ./examples/mass-vhost__php-fpm__ssl/ + ./integration-test.sh + + - name: 'docker-compose: mass-vhost Reverse Proxy (SSL)' + run: | + cd ./examples/mass-vhost__reverse-proxy__ssl/ + ./integration-test.sh diff --git a/.github/workflows/params.yml b/.github/workflows/params.yml index 9a97258..65870e5 100644 --- a/.github/workflows/params.yml +++ b/.github/workflows/params.yml @@ -15,13 +15,13 @@ env: { "NAME": "Apache", "VERSION": ["2.2"], - "FLAVOUR": ["latest", "debian"], + "FLAVOUR": ["debian"], "ARCH": ["linux/amd64", "linux/386", "linux/arm64", "linux/arm/v7", "linux/arm/v6"] }, { "NAME": "Apache", "VERSION": ["2.2"], - "FLAVOUR": ["alpine"], + "FLAVOUR": ["latest", "alpine"], "ARCH": ["linux/amd64"] } ] diff --git a/.gitignore b/.gitignore index 7457dff..271c3c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ Makefile.docker Makefile.lint +devilbox-ca.crt +devilbox-ca.key +devilbox-ca.srl diff --git a/Dockerfiles/Dockerfile.alpine b/Dockerfiles/Dockerfile.alpine index 1a9ff7a..b2bb5ae 100644 --- a/Dockerfiles/Dockerfile.alpine +++ b/Dockerfiles/Dockerfile.alpine @@ -1,3 +1,4 @@ +# vi: ft=dockerfile FROM alpine:3.5 as builder RUN set -eux \ @@ -17,10 +18,10 @@ LABEL \ ### ### Build arguments ### -ARG VHOST_GEN_GIT_REF=1.0.3 -ARG WATCHERD_GIT_REF=v1.0.2 -ARG CERT_GEN_GIT_REF=0.7 -ARG ARCH +ARG VHOST_GEN_GIT_REF=1.0.8 +ARG WATCHERD_GIT_REF=v1.0.7 +ARG CERT_GEN_GIT_REF=0.10 +ARG ARCH=linux/amd64 ENV BUILD_DEPS \ autoconf \ @@ -39,14 +40,6 @@ ENV RUN_DEPS \ tzdata -### -### Runtime arguments -### -ENV MY_USER=daemon -ENV MY_GROUP=daemon -ENV HTTPD_START="httpd-foreground" -ENV HTTPD_RELOAD="/usr/local/apache2/bin/httpd -k stop" - ### ### Install required packages ### @@ -87,7 +80,7 @@ RUN set -eux \ && chmod +x /usr/bin/cert-gen \ \ # Install watcherd - && wget --no-check-certificate -O /usr/bin/watcherd https://raw.githubusercontent.com/devilbox/watcherd/${WATCHERD_GIT_REF}/watcherd \ + && wget --no-check-certificate -O /usr/bin/watcherd https://raw.githubusercontent.com/devilbox/watcherd/${WATCHERD_GIT_REF}/bin/watcherd \ && chmod +x /usr/bin/watcherd \ \ # Clean-up @@ -125,6 +118,17 @@ RUN set -eux \ ) >> /usr/local/apache2/conf/httpd.conf +### +### Runtime arguments +### +ENV MY_USER=daemon +ENV MY_GROUP=daemon +ENV HTTPD_START="httpd-foreground" +ENV HTTPD_RELOAD="/usr/local/apache2/bin/httpd -k stop" +ENV HTTPD_VERSION="httpd -V 2>&1 | head -1 | awk '{print \$3}'" +ENV VHOSTGEN_HTTPD_SERVER="apache22" + + ### ### Create directories ### @@ -133,21 +137,38 @@ RUN set -eux \ && mkdir -p /etc/httpd/conf.d \ && mkdir -p /etc/httpd/vhost.d \ && mkdir -p /var/www/default/htdocs \ + && mkdir -p /var/log/httpd \ && mkdir -p /shared/httpd \ && chmod 0775 /shared/httpd \ && chown ${MY_USER}:${MY_GROUP} /shared/httpd +### +### Symlink Python3 to Python +### +RUN set -eux \ + && ln -sf /usr/bin/python2 /usr/bin/python + + +### +### Set timezone +### +RUN set -eux \ + && if [ -f /etc/localtime ]; then rm /etc/localtime; fi \ + && ln -s /usr/share/zoneinfo/UTC /etc/localtime + + ### ### Copy files ### -COPY ./data/vhost-gen/main.yml /etc/vhost-gen/main.yml -COPY ./data/vhost-gen/mass.yml /etc/vhost-gen/mass.yml +COPY ./data/vhost-gen/templates-main /etc/vhost-gen/templates-main COPY ./data/create-vhost.sh /usr/local/bin/create-vhost.sh + COPY ./data/docker-entrypoint.d /docker-entrypoint.d COPY ./data/docker-entrypoint.sh /docker-entrypoint.sh + ### ### Backporting from Alpine 3.5 ### diff --git a/Dockerfiles/Dockerfile.debian b/Dockerfiles/Dockerfile.debian index 2f17183..6b584f1 100644 --- a/Dockerfiles/Dockerfile.debian +++ b/Dockerfiles/Dockerfile.debian @@ -1,3 +1,4 @@ +# vi: ft=dockerfile FROM httpd:2.2 MAINTAINER "cytopia" @@ -11,10 +12,10 @@ LABEL \ ### ### Build arguments ### -ARG VHOST_GEN_GIT_REF=1.0.3 -ARG WATCHERD_GIT_REF=v1.0.2 -ARG CERT_GEN_GIT_REF=0.7 -ARG ARCH +ARG VHOST_GEN_GIT_REF=1.0.8 +ARG WATCHERD_GIT_REF=v1.0.7 +ARG CERT_GEN_GIT_REF=0.10 +ARG ARCH=linux/amd64 ENV BUILD_DEPS \ autoconf \ @@ -28,14 +29,6 @@ ENV RUN_DEPS \ supervisor -### -### Runtime arguments -### -ENV MY_USER=daemon -ENV MY_GROUP=daemon -ENV HTTPD_START="httpd-foreground" -ENV HTTPD_RELOAD="/usr/local/apache2/bin/httpd -k stop" - ### ### Install required packages ### @@ -82,7 +75,7 @@ RUN set -eux \ && chmod +x /usr/bin/cert-gen \ \ # Install watcherd - && wget --no-check-certificate -O /usr/bin/watcherd https://raw.githubusercontent.com/devilbox/watcherd/${WATCHERD_GIT_REF}/watcherd \ + && wget --no-check-certificate -O /usr/bin/watcherd https://raw.githubusercontent.com/devilbox/watcherd/${WATCHERD_GIT_REF}/bin/watcherd \ && chmod +x /usr/bin/watcherd \ \ # Clean-up @@ -121,6 +114,17 @@ RUN set -eux \ ) >> /usr/local/apache2/conf/httpd.conf +### +### Runtime arguments +### +ENV MY_USER=daemon +ENV MY_GROUP=daemon +ENV HTTPD_START="httpd-foreground" +ENV HTTPD_RELOAD="/usr/local/apache2/bin/httpd -k stop" +ENV HTTPD_VERSION="httpd -V 2>&1 | head -1 | awk '{print \$3}'" +ENV VHOSTGEN_HTTPD_SERVER="apache22" + + ### ### Create directories ### @@ -129,17 +133,33 @@ RUN set -eux \ && mkdir -p /etc/httpd/conf.d \ && mkdir -p /etc/httpd/vhost.d \ && mkdir -p /var/www/default/htdocs \ + && mkdir -p /var/log/httpd \ && mkdir -p /shared/httpd \ && chmod 0775 /shared/httpd \ && chown ${MY_USER}:${MY_GROUP} /shared/httpd +### +### Symlink Python3 to Python +### +RUN set -eux \ + && ln -sf /usr/bin/python2 /usr/bin/python + + +### +### Set timezone +### +RUN set -eux \ + && if [ -f /etc/localtime ]; then rm /etc/localtime; fi \ + && ln -s /usr/share/zoneinfo/UTC /etc/localtime + + ### ### Copy files ### -COPY ./data/vhost-gen/main.yml /etc/vhost-gen/main.yml -COPY ./data/vhost-gen/mass.yml /etc/vhost-gen/mass.yml +COPY ./data/vhost-gen/templates-main /etc/vhost-gen/templates-main COPY ./data/create-vhost.sh /usr/local/bin/create-vhost.sh + COPY ./data/docker-entrypoint.d /docker-entrypoint.d COPY ./data/docker-entrypoint.sh /docker-entrypoint.sh diff --git a/Dockerfiles/Dockerfile.latest b/Dockerfiles/Dockerfile.latest index d537b9a..45cc04d 120000 --- a/Dockerfiles/Dockerfile.latest +++ b/Dockerfiles/Dockerfile.latest @@ -1 +1 @@ -Dockerfile.debian \ No newline at end of file +Dockerfile.alpine \ No newline at end of file diff --git a/Dockerfiles/data/create-vhost.sh b/Dockerfiles/data/create-vhost.sh index 7c8d0bb..92c4341 100755 --- a/Dockerfiles/data/create-vhost.sh +++ b/Dockerfiles/data/create-vhost.sh @@ -4,38 +4,215 @@ set -e set -u set -o pipefail -VHOST_PATH="${1}" -VHOST_NAME="${2}" -VHOST_TLD="${3}" -VHOST_TPL="${4}" -CA_KEY="${5}" -CA_CRT="${6}" -GENERATE_SSL="${7}" -GEN_MODE="${8}" -VERBOSE="${9:-}" - -if [ "${GENERATE_SSL}" = "1" ]; then + +### +### Inputs (watcherd will call this script) +### +VHOST_NAME="${1}" # vhost project directory name (via watcherd: "%n") +VHOST_PATH="${2}" # vhost project directory path (via watcherd: "%p") +VHOST_DOCROOT_NAME="${3}" # Document root subdir inside VHOST_PATH +VHOST_TLD_SUFFIX="${4}" # TLD_SUFFIX to append to VHOST_NAME +VHOST_ALIASES_ALLOW="${5}" # Additional allow aliases to generate (path:, url: cors:) +VHOST_ALIASES_DENY="${6}" # Additional deny aliases to generate +VHOST_SSL_TYPE="${7}" # SSL_TYPE: "plain", "ssl", "both", "redir" +VHOST_BACKEND="${8}" # Backend string: file:* or cfg:* +VHOST_BACKEND_REWRITE="${9}" # Backend Rewrite string: file:* +VHOST_BACKEND_TIMEOUT="${10}" # Timeout for backend in seconds +HTTP2_ENABLE="${11}" # Enable HTTP2? +DOCKER_LOGS="${12}" # Enable Docker logs? +CA_KEY_FILE="${13}" # Path to CA key file +CA_CRT_FILE="${14}" # Path to CA crt file +VHOSTGEN_TEMPLATE_DIR="${15}" # vhost-gen template dir (via watcherd: "%p/${MASS_VHOST_TPL_DIR}") +VHOSTGEN_HTTPD_SERVER="${16}" # nginx, apache22 or apache24 (determines the template to choose) + + + +# ------------------------------------------------------------------------------------------------- +# BOOTSTRAP +# ------------------------------------------------------------------------------------------------- + +### +### Bootstrap (Debug level and source .lib/ and .httpd/ functions) +### +# shellcheck disable=SC1090,SC1091 +. "/docker-entrypoint.d/bootstrap/bootstrap.sh" + + + +# ------------------------------------------------------------------------------------------------- +# GENERATE SSL CERTIFICATES? +# ------------------------------------------------------------------------------------------------- + +### +### Generate vhost SSL certificate +### +if [ "${VHOST_SSL_TYPE}" != "plain" ]; then if [ ! -d "/etc/httpd/cert/mass" ]; then - mkdir -p "/etc/httpd/cert/mass" + runtime "mkdir -p /etc/httpd/cert/mass" fi - _email="admin@${VHOST_NAME}${VHOST_TLD}" - _domain="${VHOST_NAME}${VHOST_TLD}" - _domains="*.${VHOST_NAME}${VHOST_TLD}" - _out_key="/etc/httpd/cert/mass/${VHOST_NAME}${VHOST_TLD}.key" - _out_csr="/etc/httpd/cert/mass/${VHOST_NAME}${VHOST_TLD}.csr" - _out_crt="/etc/httpd/cert/mass/${VHOST_NAME}${VHOST_TLD}.crt" - if ! cert-gen -v -c DE -s Berlin -l Berlin -o Devilbox -u Devilbox -n "${_domain}" -e "${_email}" -a "${_domains}" "${CA_KEY}" "${CA_CRT}" "${_out_key}" "${_out_csr}" "${_out_crt}"; then - echo "[FAILED] Failed to add SSL certificate for ${VHOST_NAME}${VHOST_TLD}" + _email="admin@${VHOST_NAME}${VHOST_TLD_SUFFIX}" + _domain="${VHOST_NAME}${VHOST_TLD_SUFFIX}" + _domains="*.${VHOST_NAME}${VHOST_TLD_SUFFIX}" + _out_key="/etc/httpd/cert/mass/${VHOST_NAME}${VHOST_TLD_SUFFIX}.key" + _out_csr="/etc/httpd/cert/mass/${VHOST_NAME}${VHOST_TLD_SUFFIX}.csr" + _out_crt="/etc/httpd/cert/mass/${VHOST_NAME}${VHOST_TLD_SUFFIX}.crt" + if ! runtime \ + "cert-gen -v -c DE -s Berlin -l Berlin -o Devilbox -u Devilbox -n \"${_domain}\" -e \"${_email}\" -a \"${_domains}\" \"${CA_KEY_FILE}\" \"${CA_CRT_FILE}\" \"${_out_key}\" \"${_out_csr}\" \"${_out_crt}\"" \ + "Failed to add SSL certificate for ${VHOST_NAME}${VHOST_TLD_SUFFIX}"; then exit 1 fi fi -cmd="vhost-gen -p \"${VHOST_PATH}\" -n \"${VHOST_NAME}\" -c /etc/vhost-gen/mass.yml -o \"${VHOST_TPL}\" -s ${VERBOSE} -m ${GEN_MODE}" -if [ -n "${VERBOSE}" ]; then - echo "\$ ${cmd}" + + +# ------------------------------------------------------------------------------------------------- +# BACKEND string +# ------------------------------------------------------------------------------------------------- + +### +### Validate Backend +### +if [ -n "${VHOST_BACKEND}" ]; then + ### + ### Check if BACKEND_REWRITE is set + ### + if [ -n "${VHOST_BACKEND_REWRITE}" ]; then + # No need to validate backend string, has been done already in entrypoint + BACKEND_REWRITE_FILE_NAME="$( echo "${VHOST_BACKEND_REWRITE}" | awk -F':' '{print $2}' )" + BACKEND_REWRITE_FILE_PATH="${VHOSTGEN_TEMPLATE_DIR}${BACKEND_REWRITE_FILE_NAME}" + + # Backend file exists + if [ -f "${BACKEND_REWRITE_FILE_PATH}" ]; then + BACKEND_REWRITE_CONFIG="$( cat "${BACKEND_REWRITE_FILE_PATH}" )" + log "info" "[${VHOST_NAME}${VHOST_TLD_SUFFIX}] Backend rewrite found: ${BACKEND_REWRITE_FILE_PATH}" + log "info" "[${VHOST_NAME}${VHOST_TLD_SUFFIX}] Backend rewrite config: ${BACKEND_REWRITE_CONFIG}" + # Rewrite config is invalid + if ! BACKEND_REWRITE_ERROR="$( backend_conf_is_valid "${BACKEND_REWRITE_CONFIG}" )"; then + log "warn" "[${VHOST_NAME}${VHOST_TLD_SUFFIX}] Backend rewrite config is invalid: ${BACKEND_REWRITE_ERROR}" + log "warn" "[${VHOST_NAME}${VHOST_TLD_SUFFIX}] Backend rewrite: skipping" + else + # Apply the overwrite + log "info" "[${VHOST_NAME}${VHOST_TLD_SUFFIX}] Overwriting MASS_VHOST_BACKEND config" + VHOST_BACKEND="${BACKEND_REWRITE_CONFIG}" # Use config from file + fi + fi + fi + + ### + ### BACKEND=file: + ### + if echo "${VHOST_BACKEND}" | grep -E '^file:' >/dev/null; then + # No need to validate backend string, has been done already in entrypoint + BACKEND_FILE_NAME="$( echo "${VHOST_BACKEND}" | awk -F':' '{print $2}' )" + BACKEND_FILE_PATH="${VHOSTGEN_TEMPLATE_DIR}${BACKEND_FILE_NAME}" + log "info" "[${VHOST_NAME}${VHOST_TLD_SUFFIX}] Backend config specified via file: ${VHOSTGEN_TEMPLATE_DIR}${BACKEND_FILE_NAME}" + + # [1/2] Backend file does not exist + if [ ! -f "${BACKEND_FILE_PATH}" ]; then + log "info" "[${VHOST_NAME}${VHOST_TLD_SUFFIX}] Backend file does not exist: ${VHOSTGEN_TEMPLATE_DIR}${BACKEND_FILE_NAME}" + log "info" "[${VHOST_NAME}${VHOST_TLD_SUFFIX}] Backend defaulting to: serve static files only" + VHOST_BACKEND="" # Empty the backend + + # [2/2] Backend exists (need to validate it) + else + BACKEND_CONFIG="$( cat "${BACKEND_FILE_PATH}" )" + log "info" "[${VHOST_NAME}${VHOST_TLD_SUFFIX}] Backend config file contents: ${BACKEND_CONFIG}" + if ! BACKEND_ERROR="$( backend_conf_is_valid "${BACKEND_CONFIG}" )"; then + log "warn" "[${VHOST_NAME}${VHOST_TLD_SUFFIX}] Backend config is invalid: ${BACKEND_ERROR}" + log "warn" "[${VHOST_NAME}${VHOST_TLD_SUFFIX}] Backend defaulting to: serve static files only" + VHOST_BACKEND="" # Empty the backend + else + VHOST_BACKEND="${BACKEND_CONFIG}" # Use config from file + fi + fi + ### + ### Backend=conf:::: + ### + else + log "info" "[${VHOST_NAME}${VHOST_TLD_SUFFIX}] Backend config specified via env: ${VHOST_BACKEND}" + # No need to validate backend string, has been done already in entrypoint + fi +else + log "info" "[${VHOST_NAME}${VHOST_TLD_SUFFIX}] No Backend specified: Serving static files only" +fi + + +### +### Evaluate Backend +### +be_type="" +be_prot="" +be_host="" +be_port="" +if [ -n "${VHOST_BACKEND}" ]; then + be_type="$( get_backend_conf_type "${VHOST_BACKEND}" )" # phpfpm or rproxy + be_prot="$( get_backend_conf_prot "${VHOST_BACKEND}" )" # tpc, http, https + be_host="$( get_backend_conf_host "${VHOST_BACKEND}" )" # + be_port="$( get_backend_conf_port "${VHOST_BACKEND}" )" # + if [ "${be_type}" = "phpfpm" ]; then + log "info" "[${VHOST_NAME}${VHOST_TLD_SUFFIX}] Backend PHP-FPM Remote: ${be_prot}://${be_host}:${be_port}" + elif [ "${be_type}" = "rproxy" ]; then + log "info" "[${VHOST_NAME}${VHOST_TLD_SUFFIX}] Backend Reverse Proxy: ${be_prot}://${be_host}:${be_port}" + fi +fi + +INDICES="index.html, index.htm" +PHP_FPM_ENABLE=0 +if [ "${be_type}" = "phpfpm" ]; then + INDICES="index.php, index.html, index.htm" + PHP_FPM_ENABLE=1 fi -if ! eval "${cmd}"; then - echo "[FAILED] Failed to add vhost for ${VHOST_NAME}${VHOST_TLD}" - exit 1 + + +# ------------------------------------------------------------------------------------------------- +# VHOSTGEN +# ------------------------------------------------------------------------------------------------- + +VHOSTGEN_CONFIG_NAME="mass-${VHOST_NAME}.yml" +VHOSTGEN_CONFIG_PATH="/etc/vhost-gen/${VHOSTGEN_CONFIG_NAME}" + +### +### Generate vhost-gen config file (not template) +### +VHOSTGEN_TEMPLATE="$( \ + generate_vhostgen_conf \ + "${VHOSTGEN_HTTPD_SERVER}" \ + "/etc/httpd/vhost.d" \ + "${VHOST_TLD_SUFFIX}" \ + "${VHOST_DOCROOT_NAME}" \ + "${INDICES}" \ + "$( to_python_bool "${HTTP2_ENABLE}" )" \ + "/etc/httpd/cert/mass" \ + "/etc/httpd/cert/mass" \ + "" \ + "$( to_python_bool "${DOCKER_LOGS}" )" \ + "$( to_python_bool "${PHP_FPM_ENABLE}" )" \ + "${be_host}" \ + "${be_port}" \ + "${VHOST_BACKEND_TIMEOUT}" \ + "${VHOST_ALIASES_ALLOW}" \ + "${VHOST_ALIASES_DENY}" \ + "no" \ + "/httpd-status" \ +)" +echo "${VHOSTGEN_TEMPLATE}" > "${VHOSTGEN_CONFIG_PATH}" +log "trace" "${VHOSTGEN_TEMPLATE}" + +### +### Execute vhost-gen command +### +if [ "${be_type}" = "rproxy" ]; then + if ! runtime \ + "vhost-gen -v -r \"${be_prot}://${be_host}:${be_port}\" -l / -n \"${VHOST_NAME}\" -c \"${VHOSTGEN_CONFIG_PATH}\" -o \"${VHOSTGEN_TEMPLATE_DIR}\" -s -m ${VHOST_SSL_TYPE}" \ + "Failed to add vhost for ${VHOST_NAME}${VHOST_TLD_SUFFIX}"; then + exit 1 + fi +else + if ! runtime \ + "vhost-gen -v -p \"${VHOST_PATH}\" -n \"${VHOST_NAME}\" -c \"${VHOSTGEN_CONFIG_PATH}\" -o \"${VHOSTGEN_TEMPLATE_DIR}\" -s -m ${VHOST_SSL_TYPE}" \ + "Failed to add vhost for ${VHOST_NAME}${VHOST_TLD_SUFFIX}"; then + exit 1 + fi fi +log "trace" "$( grep -v '^[[:blank:]]*$' "/etc/httpd/vhost.d/${VHOST_NAME}.conf" )" diff --git a/Dockerfiles/data/docker-entrypoint.d/.httpd/README.md b/Dockerfiles/data/docker-entrypoint.d/.httpd/README.md new file mode 100644 index 0000000..864773a --- /dev/null +++ b/Dockerfiles/data/docker-entrypoint.d/.httpd/README.md @@ -0,0 +1,3 @@ +# Functions `.httpd` + +This directory contains functions and validator specifically for the HTTPD server in this project. diff --git a/Dockerfiles/data/docker-entrypoint.d/.httpd/func-backend.sh b/Dockerfiles/data/docker-entrypoint.d/.httpd/func-backend.sh new file mode 100755 index 0000000..aad0dd8 --- /dev/null +++ b/Dockerfiles/data/docker-entrypoint.d/.httpd/func-backend.sh @@ -0,0 +1,283 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +### +### This file defines how the *_VHOST_BACKEND variable evaluates and validates. +### +### Supported backends formats: +### ------------------------------------------- +### Format-1: conf:::: +### Format-2: file: +### +### +### Format-1: conf:::: +### ------------------------------------------- +### Requirement: +### 1. type == "rproxy" is only supported for $MAIN_VHOST_BACKEND +### +### Valid formats: +### conf:phpfpm:tcp:: # Remote PHP-FPM server at : +### conf:rproxy:http:: # Reverse Proxy server at http://: +### conf:rproxy:https:: # Reverse Proxy server at https://: +### +### +### Format-2: file: +### ------------------------------------------- +### Requirement: +### 1. Only supported for $MASS_VHOST_BACKEND +### 2. Only supported for "rproxy" type +### +### It must be a file in the project directory, as each project will probably use +### a different backend host/port: +### File path: ${$MASS_VHOST_DOCROOT_DIR}/${$MASS_VHOST_TPL_DIR}/ +### Default: /shared/httpd//cfg/ +### +### The file must have the following content format: +### conf:rproxy::: +### Examples: +### conf:rproxy:http:10.0.0.1:3000 +### conf:rproxy:https:mydomain.com:8080 +### +### Note: If no file is found, a warning will be logged and no Reverse proxy will be created. +### + + + +# ------------------------------------------------------------------------------------------------- +# *_VHOST_BACKEND FUNCTIONS +# ------------------------------------------------------------------------------------------------- + +### +### Check if a backend is specified +### +backend_has_backend() { + # If the backend string is empty, we do not have a backend + if [ -z "${1}" ]; then + return 1 + fi +} + + +### +### This is a generick backend_string validator for 'conf', which also returns an error message. +### +backend_conf_is_valid() { + # Do we have a backend defined? + if ! backend_has_backend "${1}"; then + return 0 + fi + backend_prefix="$( get_backend_prefix "${1}" )" # file or conf + backend_conf_type="$( get_backend_conf_type "${1}" )" # phpfpm or rproxy + backend_conf_prot="$( get_backend_conf_prot "${1}" )" # tpc, http, https + #backend_conf_host="$( get_backend_conf_host "${1}" )" # + #backend_conf_port="$( get_backend_conf_port "${1}" )" # + + ### + ### Generic validation + ### + # 1. Prefix: only 'conf' is allowed + if [ "${backend_prefix}" != "conf" ]; then + echo "Invalid backend string '${1}'. It must start with 'conf:'" + return 1 + fi + # 2. Type: 'phpfpm' or 'rproxy' + if ! backend_is_valid_conf_type "${1}"; then + echo "Invalid backend conf: in: '${1}'. It must be 'phpfpm' or 'rproxy'" + return 1 + fi + # 3. Protocol: 'tcp', 'http' or 'https' + if ! backend_is_valid_conf_prot "${1}"; then + echo "Invalid backend conf: in: '${1}'. It must be 'tcp', 'http' or 'https'" + return 1 + fi + # 4. Host + if ! backend_is_valid_conf_host "${1}"; then + echo "Invalid backend conf: in: '${1}'. It must be valid hostname, IPv4 or IPv6 addr" + return 1 + fi + # 5. Port + if ! backend_is_valid_conf_port "${1}"; then + echo "Invalid backend conf: in: '${1}'. It must be a valid port" + return 1 + fi + + ### + ### Specific validation + ### + # 6. Validate conf phpfpm == tcp + if [ "${backend_conf_type}" = "phpfpm" ]; then + if [ "${backend_conf_prot}" != "tcp" ]; then + echo "Invalid backend conf: in: '${1}'. 'phpfpm' only supports 'tcp'" + return 1 + fi + fi + # 7. Validate conf rproxy == http(s)? + if [ "${backend_conf_type}" = "rproxy" ]; then + if [ "${backend_conf_prot}" != "http" ] && [ "${backend_conf_prot}" != "https" ]; then + echo "Invalid backend conf: in: '${1}'. 'rproxy' only supports 'http' or 'https'" + return 1 + fi + fi +} + + +### +### Check if the backend prefix inside the backend string is valid ('conf' or 'file') +### +backend_is_valid_prefix() { + local value + value="$( get_backend_prefix "${1}" )" + + if [ "${value}" != "conf" ] && [ "${value}" != "file" ]; then + return 1 + fi + return 0 +} + + +### +### Check if the backend file is a valid filename +### +backend_is_valid_file_file() { + local value + value="$( get_backend_file_file "${1}" )" + + # Is valid filename? + if ! is_file "${value}"; then + return 1 + fi + # No spaces allowed in filename allowed + if echo "${value}" | grep -E '\s' >/dev/null; then + return 1 + fi + # No weired characters in filename allowed + if echo "${value}" | grep -E '!|\$|\(|\)\[|\]' >/dev/null; then + return 1 + fi +} + + +### +### Check if the backend type inside the backend string is valid. +### +backend_is_valid_conf_type() { + local value + value="$( get_backend_conf_type "${1}" )" + + if [ "${value}" != "phpfpm" ] && [ "${value}" != "rproxy" ]; then + return 1 + fi + return 0 +} + + +### +### Check if the backend protocol inside the backend string is valid. +### +backend_is_valid_conf_prot() { + local value + value="$( get_backend_conf_prot "${1}" )" + + if [ "${value}" != "tcp" ] && [ "${value}" != "http" ] && [ "${value}" != "https" ]; then + return 1 + fi + return 0 +} + + +### +### Check if the backend host inside the backend string is valid. +### +backend_is_valid_conf_host() { + local value + value="$( get_backend_conf_host "${1}" )" + + if ! is_ip_addr "${value}" && ! is_hostname "${value}"; then + return 1 + fi + return 0 +} + + +### +### Check if the backend port inside the backend string is valid. +### +backend_is_valid_conf_port() { + local value + value="$( get_backend_conf_port "${1}" )" + + if ! is_port "${value}"; then + return 1 + fi + return 0 +} + + + +# ------------------------------------------------------------------------------------------------- +# GETTER FUNCTIONS +# ------------------------------------------------------------------------------------------------- + +### +### Get Backend prefix (conf or file) +### +### Returns the first element from either: +### conf:::: +### file: +### +get_backend_prefix() { + echo "${1}" | awk -F':' '{print $1}' +} + + +### +### Get backend file name +### +### Returns the second element from file: +### +get_backend_file_file() { + echo "${1}" | awk -F':' '{print $2}' +} + + +### +### Get Backend type (phpfpm or rproxy) +### +### Returns the second element from conf:::: +### +get_backend_conf_type() { + echo "${1}" | awk -F':' '{print $2}' +} + + +### +### Get Backend protocol (tcp, http or https) +### +### Returns the third element from conf:::: +### +get_backend_conf_prot() { + echo "${1}" | awk -F':' '{print $3}' +} + + +### +### Get Backend host +### +### Returns the 4th to 2nd last element from conf:::: +### +get_backend_conf_host() { + echo "${1}" | awk -F ':' -v OFS=':' '{$1="";$2="";$3="";$NF="";print}' | sed -e 's/^::://g' -e 's/:$//g' +} + + +### +### Get Backend port +### +### Returns the last element from conf:::: +### +get_backend_conf_port() { + echo "${1}" | awk -F':' '{print $NF}' +} diff --git a/Dockerfiles/data/docker-entrypoint.d/.httpd/func-vhostgen.sh b/Dockerfiles/data/docker-entrypoint.d/.httpd/func-vhostgen.sh new file mode 100755 index 0000000..b0e8f38 --- /dev/null +++ b/Dockerfiles/data/docker-entrypoint.d/.httpd/func-vhostgen.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +### +### This file defines vhost-gen generator functions. +### + + +# ------------------------------------------------------------------------------------------------- +# vhost-gen +# ------------------------------------------------------------------------------------------------- + +### +### Generate vhost-gen config file (not template) +### +generate_vhostgen_conf() { + local httpd_server="${1}" # nginx, apache22, apache24 + local conf_dir="${2}" # Store generated httpd.conf in this directory + local tld_suffix="${3}" + local docroot_subdir="${4}" + local index="${5}" # comma separated list of index files, e.g.: "index.html, index.php" + local http2_enable="${6}" # "yes" or "no" + local dir_crt="${7}" + local dir_key="${8}" + local log_prefix="${9}" # "yes" or "no" + local docker_logs="${10}" # "yes" or "no" + local php_fpm_enable="${11}" + local php_fpm_addr="${12}" + local php_fpm_port="${13}" + local timeout="${14}" + local alias_allow="${15}" # alias1:path1[:cors], alias2:path2[:cors] + local alias_deny="${16}" # alias1[,alias2] + local server_status_enable="${17}" + local server_status_alias="${18}" + + alias_allow_block="alias: []" + if [ -n "${alias_allow}" ]; then + alias_allow_block="alias:\n" + # Ensure to convert ',' to space, to have items to iterate over + for item in ${alias_allow//,/ }; do + item_alias="$( echo "${item}" | awk -F':' '{print $1}' )" + item_path="$( echo "${item}" | awk -F':' '{print $2}' )" + item_cors="$( echo "${item}" | awk -F':' -v OFS=':' '{$1="";$2="";print}' | sed -e 's/^://g' -e 's/^://g' )" + alias_allow_block+=" - alias: ${item_alias}\n" + alias_allow_block+=" path: ${item_path}\n" + if [ -n "${item_cors}" ]; then + alias_allow_block+=" xdomain_request:\n" + alias_allow_block+=" enable: yes\n" + alias_allow_block+=" origin: ${item_cors}\n" + fi + done + fi + + alias_deny_block="deny: []" + if [ -n "${alias_deny}" ]; then + alias_deny_block="deny:\n" + # Ensure to convert ',' to space, to have items to iterate over + for item_alias in ${alias_deny//,/ }; do + alias_deny_block+=" - alias: '${item_alias}'\n" + done + fi + + + # https://github.com/devilbox/vhost-gen/blob/master/etc/conf.yml + OUT=$(cat </dev/null 2>&1 +} + + +### +### Get env variable by name +### +### This function allows an optional second parameter, which sets +### the default value, if the environment variable was not set. +### +### ${1}: name of the environment variable +### ${2}: (optional) default value, if not set +### +env_get() { + # Did we have a default value set? + if [ "${#}" -gt "1" ]; then + if ! env_set "${1}"; then + echo "${2}" + return 0 + fi + fi + # Just output the env value + printenv "${1}" +} + + + +# ------------------------------------------------------------------------------------------------- +# SANITY CHECKS +# ------------------------------------------------------------------------------------------------- + +### +### The following commands are required and used in the current script. +### +if ! command -v printenv >/dev/null 2>&1; then + >&2 echo "Error, printenv not found, but required." + exit 1 +fi diff --git a/Dockerfiles/data/docker-entrypoint.d/.lib/func-logger.sh b/Dockerfiles/data/docker-entrypoint.d/.lib/func-logger.sh new file mode 100755 index 0000000..a9fdb1e --- /dev/null +++ b/Dockerfiles/data/docker-entrypoint.d/.lib/func-logger.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +### +### This file holds the global logger function. +### + + +# ------------------------------------------------------------------------------------------------- +# LOGGER +# ------------------------------------------------------------------------------------------------- + +### +### Log to stdout/stderr +### +### Internally used logger function to log 'ok', 'warn' and 'err' messages to stdout/stderr. +### +### DEBUG_ENTRYPOINT=0 done, err +### DEBUG_ENTRYPOINT=1 done, err, warn +### DEBUG_ENTRYPOINT=2 done, err, warn, ok, info +### DEBUG_ENTRYPOINT=3 done, err, warn, ok, info, debug +### DEBUG_ENTRYPOINT=4 done, err, warn, ok, info, debug, trace +### +log() { + local type="${1}" + local message="${2}" + local disable_format="${3:-0}" # disable colors and prefix + + # https://unix.stackexchange.com/questions/124407/what-color-codes-can-i-use-in-my-bash-ps1-prompt + local clr_trace="\033[38;5;244m" # gray + local clr_debug="\033[38;5;244m" # gray + local clr_info="\033[0;34m" # blue + local clr_ok="\033[0;32m" # green + local clr_warn="\033[0;33m" # yellow + local clr_err="\033[0;31m" # red + local clr_rst="\033[0m" # reset color + + # Always show ready messages + if [ "${type}" = "done" ]; then + if [ "${disable_format}" = "1" ]; then + echo "${message}" 1>&1 # stdout -> stderr + else + printf "${clr_ok}[DONE] %s${clr_rst}\n" "${message}" 1>&2 # stdout -> stderr + fi + fi + + # Always show errors + if [ "${type}" = "err" ]; then + if [ "${disable_format}" = "1" ]; then + echo "${message}" 1>&1 # stdout -> stderr + else + printf "${clr_err}[ERR] %s${clr_rst}\n" "${message}" 1>&2 # stdout -> stderr + fi + fi + + # >= 1 (Errors & Warnings) + if [ "${DEBUG_ENTRYPOINT}" -ge "1" ]; then + if [ "${type}" = "warn" ]; then + if [ "${disable_format}" = "1" ]; then + echo "${message}" 1>&1 # stdout -> stderr + else + printf "${clr_warn}[WARN] %s${clr_rst}\n" "${message}" 1>&2 # stdout -> stderr + fi + fi + fi + # >= 2 (Errors, Warnings, OK & Info) + if [ "${DEBUG_ENTRYPOINT}" -ge "2" ]; then + if [ "${type}" = "ok" ]; then + if [ "${disable_format}" = "1" ]; then + echo "${message}" + else + printf "${clr_ok}[OK] %s${clr_rst}\n" "${message}" + fi + fi + if [ "${type}" = "info" ]; then + if [ "${disable_format}" = "1" ]; then + echo "${message}" + else + printf "${clr_info}[INFO] %s${clr_rst}\n" "${message}" + fi + fi + fi + # >= 3 (Errors, Warnings, OK, Info, Debug) + if [ "${DEBUG_ENTRYPOINT}" -ge "3" ]; then + if [ "${type}" = "debug" ]; then + if [ "${disable_format}" = "1" ]; then + echo "${message}" + else + printf "${clr_debug}[DBG] %s${clr_rst}\n" "${message}" + fi + fi + fi + # >= 4 (Errors, Warnings, OK, Info, Debug, Trace) + if [ "${DEBUG_ENTRYPOINT}" -ge "4" ]; then + if [ "${type}" = "trace" ]; then + if [ "${disable_format}" = "1" ]; then + echo "${message}" + else + printf "${clr_trace}[TRC] %s${clr_rst}\n" "${message}" + fi + fi + fi +} diff --git a/Dockerfiles/data/docker-entrypoint.d/.lib/func-misc.sh b/Dockerfiles/data/docker-entrypoint.d/.lib/func-misc.sh new file mode 100755 index 0000000..9a72f35 --- /dev/null +++ b/Dockerfiles/data/docker-entrypoint.d/.lib/func-misc.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +### +### This file holds misc functions. +### + + +# ------------------------------------------------------------------------------------------------- +# ENV FUNCTIONS +# ------------------------------------------------------------------------------------------------- + +### +### Cast a bash bool ("0" or "1") to Python bool ("yes" or "no") +### +to_python_bool() { + if [ "${1}" = "0" ]; then + echo "no" + elif [ "${1}" = "1" ]; then + echo "yes" + fi +} + + +### +### Get Random alphanumeric string +### +get_random_alphanum() { + local len="${1:-15}" # length defaults to 15 + tr -dc A-Za-z0-9 < /dev/urandom | head -c "${len}" | xargs || true +} + + + +# ------------------------------------------------------------------------------------------------- +# SANITY CHECKS +# ------------------------------------------------------------------------------------------------- + +### +### The following commands are required and used in the current script. +### +if ! command -v tr >/dev/null 2>&1; then + >&2 echo "Error, tr not found, but required." + exit 1 +fi +if ! command -v xargs >/dev/null 2>&1; then + >&2 echo "Error, xargs not found, but required." + exit 1 +fi diff --git a/Dockerfiles/data/docker-entrypoint.d/.lib/func-run.sh b/Dockerfiles/data/docker-entrypoint.d/.lib/func-run.sh new file mode 100755 index 0000000..f37ae2f --- /dev/null +++ b/Dockerfiles/data/docker-entrypoint.d/.lib/func-run.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +### +### This file holds the global run function to wrap all executed commands. +### + + +# ------------------------------------------------------------------------------------------------- +# RUN +# ------------------------------------------------------------------------------------------------- + +### +### Wrapper to run simple commands. +### +### Internally used function to execute commands and also be able to show +### these commands to stdout/stderr prior executing. +### +### DEBUG_ENTRYPOINT=0 - +### DEBUG_ENTRYPOINT=1 - +### DEBUG_ENTRYPOINT=2 show output +### DEBUG_ENTRYPOINT=3 show output and its command +### DEBUG_ENTRYPOINT=4 show output and its command +### +run() { + local cmd="${1}" + local fail_msg="${2:-}" + + # https://unix.stackexchange.com/questions/124407/what-color-codes-can-i-use-in-my-bash-ps1-prompt + local clr_gray="\033[38;5;240m" + local crl_cmd="\033[38;5;236m" # dark gray + #local clr_blue="\033[0;34m" + #local clr_green="\033[0;32m" + #local clr_yellow="\033[0;33m" + local clr_red="\033[0;31m" + local clr_rst="\033[0m" + + ### + ### Failure + ### + if ! OUTPUT="$( /bin/sh -c "LANG=C LC_ALL=C ${cmd}" 2>&1 )"; then + if [ -n "${fail_msg}" ]; then + printf "${clr_red}[FAIL] %s${clr_rst}\n" "${fail_msg}" 1>&2 # (opt) msg: stdout -> stderr + fi + printf "${clr_red}[FAIL] %s${clr_rst}\n" "${cmd}" 1>&2 # command: stdout -> stderr + if [ -n "${OUTPUT}" ]; then + printf "${clr_red}%s${clr_rst}\n" "${OUTPUT}" 1>&2 # output: stdout -> stderr + fi + return 1 + fi + + ### + ### Success + ### + if [ "${DEBUG_ENTRYPOINT}" -gt "2" ]; then + printf "${crl_cmd}[CMD] %s${clr_rst}\n" "${cmd}" + if [ -n "${OUTPUT}" ]; then + printf "${clr_gray}%s${clr_rst}\n" "${OUTPUT}" + fi + elif [ "${DEBUG_ENTRYPOINT}" -gt "1" ]; then + if [ -n "${OUTPUT}" ]; then + printf "${clr_gray}%s${clr_rst}\n" "${OUTPUT}" + fi + fi +} + + +### +### Wrapper to run commands during runtime. +### This includes all commands triggered once the main entrypoint service is running. +### +### Internally used function to execute commands and also be able to show +### these commands to stdout/stderr prior executing. +### +### DEBUG_RUNTIME=0 - +### DEBUG_RUNTIME=1 show output +### DEBUG_RUNTIME=2 show output and its command +### +runtime() { + local cmd="${1}" + local fail_msg="${2:-}" + + # https://unix.stackexchange.com/questions/124407/what-color-codes-can-i-use-in-my-bash-ps1-prompt + local clr_gray="\033[38;5;240m" + local crl_cmd="\033[38;5;236m" # dark gray + #local clr_blue="\033[0;34m" + #local clr_green="\033[0;32m" + #local clr_yellow="\033[0;33m" + local clr_red="\033[0;31m" + local clr_rst="\033[0m" + + ### + ### Failure + ### + if ! OUTPUT="$( /bin/sh -c "LANG=C LC_ALL=C ${cmd}" 2>&1 )"; then + if [ -n "${fail_msg}" ]; then + printf "${clr_red}[FAIL] %s${clr_rst}\n" "${fail_msg}" 1>&2 # (opt) msg: stdout -> stderr + fi + printf "${clr_red}[FAIL] %s${clr_rst}\n" "${cmd}" 1>&2 # command: stdout -> stderr + if [ -n "${OUTPUT}" ]; then + printf "${clr_red}%s${clr_rst}\n" "${OUTPUT}" 1>&2 # output: stdout -> stderr + fi + return 1 + fi + + ### + ### Success + ### + if [ "${DEBUG_RUNTIME}" -gt "1" ]; then + printf "${crl_cmd}[CMD] %s${clr_rst}\n" "${cmd}" + if [ -n "${OUTPUT}" ]; then + printf "${clr_gray}%s${clr_rst}\n" "${OUTPUT}" + fi + elif [ "${DEBUG_RUNTIME}" -gt "0" ]; then + if [ -n "${OUTPUT}" ]; then + printf "${clr_gray}%s${clr_rst}\n" "${OUTPUT}" + fi + fi +} diff --git a/Dockerfiles/data/docker-entrypoint.d/.lib/func-validator.sh b/Dockerfiles/data/docker-entrypoint.d/.lib/func-validator.sh new file mode 100755 index 0000000..0e95a04 --- /dev/null +++ b/Dockerfiles/data/docker-entrypoint.d/.lib/func-validator.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +### +### This file holds useful validator functions +### + + +# ------------------------------------------------------------------------------------------------- +# HELPER FUNCTIONS +# ------------------------------------------------------------------------------------------------- + +### +### Check if given value is a positive integer +### +is_int() { + if [ -z "${1}" ]; then + return 1 + fi + test -n "${1##*[!0-9]*}" +} + + +### +### Check if given value is a bool ('0' or '1') +### +is_bool() { + if [ "${1}" != "0" ] && [ "${1}" != "1" ]; then + return 1 + fi +} + + +### +### Check if given value is valid uid +### +is_uid() { + is_int "${1}" +} + + +### +### Check if given value is valid gid +### +is_gid() { + is_int "${1}" +} + + +### +### Check if given value is a valid port (1-65535) +### +is_port() { + if ! is_int "${1}"; then + return 1 + fi + if [ "${1}" -lt "1" ]; then + return 1 + fi + if [ "${1}" -gt "65535" ]; then + return 1 + fi +} + + +### +### Check if given string is a valid domain +### +is_domain() { + # Cannot be empty + if [ -z "${1}" ]; then + return 1 + fi + # Leading . (dot) + if echo "${1}" | grep -E '^\.' > /dev/null; then + return 1 + fi + # Trailing . (dot) + if echo "${1}" | grep -E '\.$' > /dev/null; then + return 1 + fi + # Space + if echo "${1}" | grep -E '\s' > /dev/null; then + return 1 + fi + # Some common-sense characters + if echo "${1}" | grep -E '&|@|\*|\(|\)|,|\?|_|#|\$|:|;|\\|/|%|\+|=|a<|>' > /dev/null; then + return 1 + fi +} + + +### +### Check if given value is valid hostname +### +is_hostname() { + # Cannot be empty + if [ -z "${1}" ]; then + return 1 + fi + # TODO: Add some hostname regex + return 0 +} + + +### +### Check if given value is valid IPv4 or IPv6 address +### +is_ip_addr() { + if is_ipv4_addr "${1}"; then + return 0 + fi + if is_ipv6_addr "${1}"; then + return 0 + fi + return 1 +} + + +### +### Check if given value is valid IPv4 address +### +is_ipv4_addr() { + # Cannot be empty + if [ -z "${1}" ]; then + return 1 + fi + + # This is only a very basic check to prevent typos during startup + echo "${1}" | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' >/dev/null +} + + +### +### Check if given value is valid IPv6 address +### +is_ipv6_addr() { + # Cannot be empty + if [ -z "${1}" ]; then + return 1 + fi + # This is only a very basic check to prevent typos during startup + echo "${1}" | grep -E '^([A-Fa-f0-9:]+:+)+[A-Fa-f0-9]+$' >/dev/null +} + + +### +### Check if given value is a valid filename (no sub-/parent dir) +### +is_file() { + # Cannot be empty + if [ -z "${1}" ]; then + return 1 + fi + + # Not a sub-directory prefix + if [ "$( basename "${1}" )" != "${1}" ]; then + return 1 + fi + + # Not a parent directory suffix + if [ "$( dirname "${1}" )" != "." ]; then + return 1 + fi +} diff --git a/Dockerfiles/data/docker-entrypoint.d/00-base-libs.sh b/Dockerfiles/data/docker-entrypoint.d/00-base-libs.sh deleted file mode 100755 index 361ae34..0000000 --- a/Dockerfiles/data/docker-entrypoint.d/00-base-libs.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - - -############################################################ -# Functions -############################################################ - -### -### Log to stdout/stderr -### -log() { - local type="${1}" # ok, warn or err - local message="${2}" # msg to print - local debug="${3}" # 0: only warn and error, >0: ok and info - - local clr_ok="\033[0;32m" - local clr_info="\033[0;34m" - local clr_warn="\033[0;33m" - local clr_err="\033[0;31m" - local clr_rst="\033[0m" - - if [ "${type}" = "ok" ]; then - if [ "${debug}" -gt "0" ]; then - printf "${clr_ok}[OK] %s${clr_rst}\n" "${message}" - fi - elif [ "${type}" = "info" ]; then - if [ "${debug}" -gt "0" ]; then - printf "${clr_info}[INFO] %s${clr_rst}\n" "${message}" - fi - elif [ "${type}" = "warn" ]; then - printf "${clr_warn}[WARN] %s${clr_rst}\n" "${message}" 1>&2 # stdout -> stderr - elif [ "${type}" = "err" ]; then - printf "${clr_err}[ERR] %s${clr_rst}\n" "${message}" 1>&2 # stdout -> stderr - else - printf "${clr_err}[???] %s${clr_rst}\n" "${message}" 1>&2 # stdout -> stderr - fi -} - - -### -### Wrapper for run_run command -### -run() { - local cmd="${1}" # command to execute - local debug="${2}" # show commands if debug level > 1 - - local clr_red="\033[0;31m" - local clr_green="\033[0;32m" - local clr_reset="\033[0m" - - if [ "${debug}" -gt "1" ]; then - printf "${clr_red}%s \$ ${clr_green}${cmd}${clr_reset}\n" "$( whoami )" - fi - /bin/sh -c "LANG=C LC_ALL=C ${cmd}" -} - - -### -### Is argument a positive integer? -### -isint() { - test -n "${1##*[!0-9]*}" -} - - -### -### Is env variable set? -### -env_set() { - printenv "${1}" >/dev/null 2>&1 -} - - -### -### Get env variable by name -### -env_get() { - local env_name="${1}" - - # Did we have a default value specified? - if [ "${#}" -gt "1" ]; then - if ! env_set "${env_name}"; then - echo "${2}" - return 0 - fi - fi - # Just output the env value - printenv "${1}" -} - - -############################################################ -# Sanity Checks -############################################################ - -if ! command -v printenv >/dev/null 2>&1; then - log "err" "printenv not found, but required." "1" - exit 1 -fi diff --git a/Dockerfiles/data/docker-entrypoint.d/01-env-vars-export.sh b/Dockerfiles/data/docker-entrypoint.d/01-env-vars-export.sh new file mode 100755 index 0000000..dbacd41 --- /dev/null +++ b/Dockerfiles/data/docker-entrypoint.d/01-env-vars-export.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +### +### This file holds functions to ensures all environment variables are set or defaulted +### + + +# ------------------------------------------------------------------------------------------------- +# EXPORT FUNCTIONS +# ------------------------------------------------------------------------------------------------- + +### +### Ensure that env variables are exported. +### +### In case an environment variable was not specified, assign +### a default value and export it to the environment. +### +env_var_export() { + local env_varname="${1}" + local default="${2:-}" + + if ! env_set "${env_varname}"; then + _log_env_export "unset" "${env_varname}" "${default}" + else + default="$( env_get "${env_varname}" )" + _log_env_export "set" "${env_varname}" "${default}" + fi + export "${env_varname}=${default}" +} + + + +# ------------------------------------------------------------------------------------------------- +# HELPER FUNCTIONS +# ------------------------------------------------------------------------------------------------- + +### +### Use custom logger to log env variable status +### +_log_env_export() { + local state="${1}" # 'set' or 'unset' + local name="${2}" # Variable name + local value="${3}" # Variable value (either set value or default value) + + local clr_set="\033[0;32m" # green + #local clr_unset="\033[0;34m" # blue + local clr_value="\033[0;32m" # green + local clr_info="\033[0;34m" # blue + local clr_rst="\033[0m" + + if [ "${state}" = "set" ]; then + log "info" "$( \ + printf "${clr_info}%-11s${clr_rst}%-8s${clr_set}\$%-27s${clr_rst}%-9s${clr_value}%s${clr_rst}\n" \ + "[INFO]" \ + "Set" \ + "${name}" \ + "Value:" \ + "${value}" \ + )" "1" + elif [ "${state}" = "unset" ]; then + log "info" "$( \ + printf "${clr_info}%-11s${clr_rst}%-8s${clr_rst}\$%-27s${clr_rst}%-9s${clr_value}%s${clr_rst}\n" \ + "[INFO]" \ + "Unset" \ + "${name}" \ + "Default:" \ + "${value}" \ + )" "1" + else + log "????" "Internal: Wrong value given to _log_env_export" + exit 1 + fi +} diff --git a/Dockerfiles/data/docker-entrypoint.d/01-uid-gid.sh b/Dockerfiles/data/docker-entrypoint.d/01-uid-gid.sh deleted file mode 100755 index d95aeef..0000000 --- a/Dockerfiles/data/docker-entrypoint.d/01-uid-gid.sh +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - - -############################################################ -# Functions -############################################################ - -### -### Helper -### -_get_username_by_uid() { - if getent="$( getent passwd "${1}" )"; then - echo "${getent//:*}" - return 0 - fi - return 1 -} - -_get_groupname_by_gid() { - if getent="$( getent group "${1}" )"; then - echo "${getent//:*}" - return 0 - fi - return 1 -} - -_get_homedir_by_username() { - getent passwd "${1}" | cut -d: -f6 -} - -_get_homedir_by_groupname() { - grep -E ".*:x:[0-9]+:[0-9]+:$( _get_groupname_by_gid "${1}" ).*" /etc/passwd | cut -d: -f6 -} - - -### -### Change UID -### -set_uid() { - local uid_varname="${1}" - local username="${2}" - local debug="${3}" - #local homedir - #homedir="$( _get_homedir_by_username "${username}" )" - - local uid= # new uid - local spare_uid=9876 # spare uid to change another user to - - if ! env_set "${uid_varname}"; then - log "info" "\$${uid_varname} not set. Keeping default uid for '${username}'." "${debug}" - else - uid="$( env_get "${uid_varname}" )" - - if ! isint "${uid}"; then - log "err" "\$${uid_varname} is not an integer: '${uid}'" "${debug}" - exit 1 - else - # Username with this uid already exists - if target_username="$( _get_username_by_uid "${uid}" )"; then - # It is not our user, so we need to changes his/her uid to something else first - if [ "${target_username}" != "${username}" ]; then - log "warn" "User with ${uid} already exists: ${target_username}" "${debug}" - log "info" "Changing UID of ${target_username} to ${spare_uid}" "${debug}" - run "usermod -u ${spare_uid} ${target_username}" "${debug}" - fi - fi - # Change uid and fix homedir permissions - log "info" "Changing user '${username}' uid to: ${uid}" "${debug}" - run "usermod -u ${uid} ${username}" "${debug}" - fi - fi -} - - -### -### Change GID -### -set_gid() { - local gid_varname="${1}" - local groupname="${2}" - local debug="${3}" - #local homedir - #homedir="$( _get_homedir_by_groupname "${groupname}" )" - - local gid= # new gid - local spare_gid=9876 # spare gid to change another group to - - if ! env_set "${gid_varname}"; then - log "info" "\$${gid_varname} not set. Keeping default gid for '${groupname}'." "${debug}" - else - # Retrieve the value from env - gid="$( env_get "${gid_varname}" )" - - if ! isint "${gid}"; then - log "err" "\$${gid_varname} is not an integer: '${gid}'" "${debug}" - exit 1 - else - # Groupname with this gid already exists - if target_groupname="$( _get_groupname_by_gid "${gid}" )"; then - # It is not our group, so we need to changes his/her gid to something else first - if [ "${target_groupname}" != "${groupname}" ]; then - log "warn" "Group with ${gid} already exists: ${target_groupname}" "${debug}" - log "info" "Changing GID of ${target_groupname} to ${spare_gid}" "${debug}" - run "groupmod -g ${spare_gid} ${target_groupname}" "${debug}" - fi - fi - # Change ugd and fix homedir permissions - log "info" "Changing group '${groupname}' gid to: ${gid}" "${debug}" - run "groupmod -g ${gid} ${groupname}" "${debug}" - fi - fi -} - - -############################################################ -# Sanity Checks -############################################################ - -if ! command -v usermod >/dev/null 2>&1; then - log "err" "usermod not found, but required." "1" - exit 1 -fi -if ! command -v groupmod >/dev/null 2>&1; then - log "err" "groupmod not found, but required." "1" - exit 1 -fi -if ! command -v getent >/dev/null 2>&1; then - log "err" "getent not found, but required." "1" - exit 1 -fi diff --git a/Dockerfiles/data/docker-entrypoint.d/02-env-vars-validate.sh b/Dockerfiles/data/docker-entrypoint.d/02-env-vars-validate.sh new file mode 100755 index 0000000..02e8f5c --- /dev/null +++ b/Dockerfiles/data/docker-entrypoint.d/02-env-vars-validate.sh @@ -0,0 +1,1075 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +### +### This file holds functions to validate the given environment variables +### + + +# ------------------------------------------------------------------------------------------------- +# MAIN VALIDATOR +# ------------------------------------------------------------------------------------------------- + +### +### Validate environment variables +### +### This function is just a gate-keeper and calls the validate_() +### function for each environment variable to ensure the assigned +### value is correct. +### +env_var_validate() { + local name="${1}" + local value + + value="$( env_get "${name}" )" + func="validate_$( echo "${name}" | awk '{print tolower($0)}' )" + + # Call specific validator function: validate_() + $func "${name}" "${value}" +} + + + +# ------------------------------------------------------------------------------------------------- +# VALIDATE FUNCTIONS: GENERAL +# ------------------------------------------------------------------------------------------------- + +### +### Validate NEW_UID +### +validate_new_uid() { + local name="${1}" + local value="${2}" + + # Ignore if empty (no change) + if [ -z "${value}" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(not specified)" + return 0 + fi + if ! is_uid "${value}"; then + _log_env_valid "invalid" "${name}" "${value}" "Must be positive integer" + exit 1 + fi + _log_env_valid "valid" "${name}" "${value}" "User ID (uid)" "${value}" +} + + +### +### Validate NEW_GID +### +validate_new_gid() { + local name="${1}" + local value="${2}" + + # Ignore if empty (no change) + if [ -z "${value}" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(not specified)" + return 0 + fi + if ! is_gid "${value}"; then + _log_env_valid "invalid" "${name}" "${value}" "Must be positive integer" + exit 1 + fi + _log_env_valid "valid" "${name}" "${value}" "Group ID (gid)" "${value}" +} + + +### +### Validate TIMEZONE +### +validate_timezone() { + local name="${1}" + local value="${2}" + + # Show ignored + if [ "${value}" = "UTC" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(not specified)" + return 0 + fi + if [ ! -f "/usr/share/zoneinfo/${value}" ]; then + _log_env_valid "invalid" "${name}" "${value}" "File '${value}' must exist in: " "/usr/share/zoneinfo/" + exit 1 + fi + _log_env_valid "valid" "${name}" "${value}" "Timezone" "${value}" +} + + + +# ------------------------------------------------------------------------------------------------- +# VALIDATE FUNCTIONS: MAIN VHOST +# ------------------------------------------------------------------------------------------------- + + +### +### Validate MAIN_VHOST_ENABLE +### +validate_main_vhost_enable() { + local name="${1}" + local value="${2}" + _validate_bool "${name}" "${value}" "Default vhost" +} + + +### +### Validate MAIN_VHOST_ALIASES_ALLOW: :[:] +### +validate_main_vhost_aliases_allow() { + local name="${1}" + local value="${2}" + _validate_vhost_aliases_allow "${name}" "${value}" "${MAIN_VHOST_ENABLE}" +} + + +### +### Validate MAIN_VHOST_ALIASES_DENY: [,] +### +validate_main_vhost_aliases_deny() { + local name="${1}" + local value="${2}" + _validate_vhost_aliases_deny "${name}" "${value}" "${MAIN_VHOST_ENABLE}" +} + + +### +### Validate MAIN_VHOST_BACKEND: :::: +### +validate_main_vhost_backend() { + local name="${1}" + local value="${2}" + _validate_vhost_backend "${name}" "${value}" "main" "${MAIN_VHOST_ENABLE}" +} + + +### +### Validate MAIN_VHOST_BACKEND_TIMEOUT +### +validate_main_vhost_backend_timeout() { + local name="${1}" + local value="${2}" + _validate_vhost_backend_timeout "${name}" "${value}" "${MAIN_VHOST_ENABLE}" +} + + +### +### Validate MAIN_VHOST_DOCROOT_DIR +### +validate_main_vhost_docroot_dir() { + local name="${1}" + local value="${2}" + local base_path="${MAIN_DOCROOT_BASE}" + # shellcheck disable=SC2153 + _validate_vhost_docroot_dir "${name}" "${value}" "${MAIN_VHOST_ENABLE}" "${MAIN_VHOST_BACKEND}" "${base_path}/${value}" +} + + +### +### Validate MAIN_VHOST_SSL_TYPE +### +validate_main_vhost_ssl_type() { + local name="${1}" + local value="${2}" + _validate_vhost_ssl_type "${name}" "${value}" "${MAIN_VHOST_ENABLE}" +} + + +### +### Validate MAIN_VHOST_SSL_CN +### +validate_main_vhost_ssl_cn() { + local name="${1}" + local value="${2}" + + # Show ignored + if [ "${MAIN_VHOST_ENABLE}" = "0" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(vhost disabled)" + return + fi + if [ "${MAIN_VHOST_SSL_TYPE}" = "plain" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(no ssl)" + return + fi + _log_env_valid "valid" "${name}" "${value}" "SSL cert subject" "CN = ${value}" +} + + +### +### Validate MAIN_VHOST_TEMPLATE_DIR: vhost-gen template directory +### +validate_main_vhost_template_dir() { + local name="${1}" + local value="${2}" + local base_path="${MAIN_DOCROOT_BASE}" + _validate_vhost_template_dir "${name}" "${value}" "${MAIN_VHOST_ENABLE}" "${base_path}/${value}" +} + + +### +### Validate MAIN_VHOST_STATUS_ENABLE: Status page enable/disable +### +validate_main_vhost_status_enable() { + local name="${1}" + local value="${2}" + + if ! is_bool "${value}"; then + _log_env_valid "invalid" "${name}" "${value}" "Must be 0 or 1" "" + exit 1 + fi + # Show ignored + if [ "${MAIN_VHOST_ENABLE}" = "0" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(vhost disabled)" + return + fi + if [ "${value}" = "0" ]; then + _log_env_valid "valid" "${name}" "${value}" "Status page" "Disabled" + else + _log_env_valid "valid" "${name}" "${value}" "Status page" "Enabled" + fi +} + + +### +### Validate MAIN_VHOST_STATUS_ALIAS: Status page URL +### +validate_main_vhost_status_alias() { + local name="${1}" + local value="${2}" + + # Show ignored + if [ "${MAIN_VHOST_ENABLE}" = "0" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(vhost disabled)" + return + fi + if [ "${MAIN_VHOST_STATUS_ENABLE}" = "0" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(status disabled)" + return + fi + _log_env_valid "valid" "${name}" "${value}" "Status page URL" "${value}" +} + + + +# ------------------------------------------------------------------------------------------------- +# VALIDATE FUNCTIONS: MASS VHOST +# ------------------------------------------------------------------------------------------------- + +### +### Validate MASS_VHOST_ENABLE +### +validate_mass_vhost_enable() { + local name="${1}" + local value="${2}" + _validate_bool "${name}" "${value}" "Mass vhost" + + # Ensure either of MASS or MAIN is actually enabled + if [ "${value}" = "0" ] && [ "${MAIN_VHOST_ENABLE}" = "0" ]; then + _log_env_valid "invalid" "${name}" "${value}" "MAIN_VHOST and MASS_HOST are both disabled" "" + exit 1 + fi +} + + +### +### Validate MASS_VHOST_ALIASES_ALLOW: :[:] +### +validate_mass_vhost_aliases_allow() { + local name="${1}" + local value="${2}" + _validate_vhost_aliases_allow "${name}" "${value}" "${MASS_VHOST_ENABLE}" +} + + +### +### Validate MASS_VHOST_ALIASES_DENY: [,] +### +validate_mass_vhost_aliases_deny() { + local name="${1}" + local value="${2}" + _validate_vhost_aliases_deny "${name}" "${value}" "${MASS_VHOST_ENABLE}" +} + + +### +### Validate MASS_VHOST_BACKEND :::: +### +validate_mass_vhost_backend() { + local name="${1}" + local value="${2}" + _validate_vhost_backend "${name}" "${value}" "mass" "${MASS_VHOST_ENABLE}" +} + + +### +### Validate MASS_VHOST_BACKEND_REWRITE file: +### +validate_mass_vhost_backend_rewrite() { + local name="${1}" + local value="${2}" + _validate_vhost_backend_rewrite "${name}" "${value}" "mass" "${MASS_VHOST_ENABLE}" +} + + +### +### Validate MASS_VHOST_BACKEND_TIMEOUT +### +validate_mass_vhost_backend_timeout() { + local name="${1}" + local value="${2}" + _validate_vhost_backend_timeout "${name}" "${value}" "${MASS_VHOST_ENABLE}" +} + + +### +### Validate MASS_VHOST_DOCROOT_DIR +### +validate_mass_vhost_docroot_dir() { + local name="${1}" + local value="${2}" + local base_path="${MASS_DOCROOT_BASE}" + _validate_vhost_docroot_dir "${name}" "${value}" "${MASS_VHOST_ENABLE}" "${MASS_VHOST_BACKEND}" "${base_path}//${value}" +} + + +### +### Validate MASS_VHOST_TLD_SUFFIX (top-level domain suffix) +### +validate_mass_vhost_tld_suffix() { + local name="${1}" + local value="${2}" + + # If value is not empty + if [ -n "${value}" ]; then + if ! echo "${value}" | grep -E '^\.' > /dev/null; then + _log_env_valid "invalid" "${name}" "${value}" "Must start with a leading '.' when set" "" + _log_env_valid "invalid" "${name}" "${value}" "Not a valid project name" "" + fi + # Note: ${value:1} means it starts at the second character, + # when handing it over to the is_domain() check function. + if ! is_domain "${value:1}"; then + _log_env_valid "invalid" "${name}" "${value}" "Must be a valid domain name or empty" "" + exit 1 + fi + fi + # Show ignored + if [ "${MASS_VHOST_ENABLE}" = "0" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(vhost disabled)" + return + fi + if [ -z "${value}" ]; then + _log_env_valid "valid" "${name}" "${value}" "vHost domain" "" + else + _log_env_valid "valid" "${name}" "${value}" "Vhost domain" "${value}" + fi +} + + +### +### Validate MASS_VHOST_SSL_TYPE +### +validate_mass_vhost_ssl_type() { + local name="${1}" + local value="${2}" + _validate_vhost_ssl_type "${name}" "${value}" "${MASS_VHOST_ENABLE}" +} + + +### +### Validate MASS_VHOST_TEMPLATE_DIR: vhost-gen template directory +### +validate_mass_vhost_template_dir() { + local name="${1}" + local value="${2}" + local base_path="${MASS_DOCROOT_BASE}" + _validate_vhost_template_dir "${name}" "${value}" "${MASS_VHOST_ENABLE}" "${base_path}//${value}" +} + + + +# ------------------------------------------------------------------------------------------------- +# VALIDATE FUNCTIONS: MISC VALIDATION +# ------------------------------------------------------------------------------------------------- + +### +### Validate WORKER_CONNECTIONS +### +validate_worker_connections() { + local name="${1}" + local value="${2}" + _log_env_valid "valid" "${name}" "${value}" "worker_connections" "${value}" +} + + +### +### Validate WORKER_PROCESSES +### +validate_worker_processes() { + local name="${1}" + local value="${2}" + _log_env_valid "valid" "${name}" "${value}" "worker_processes" "${value}" +} + + +### +### Validate HTTPD2_ENABLE +### +validate_http2_enable() { + local name="${1}" + local value="${2}" + _validate_bool "${name}" "${value}" "HTTP/2" +} + + +### +### Validate DOCKER_LOGS +### +validate_docker_logs() { + local name="${1}" + local value="${2}" + _validate_bool "${name}" "${value}" "Log to" "0" "stdout and stderr" "/var/log/" +} + + + +# ------------------------------------------------------------------------------------------------- +# HELPER FUNCTIONS +# ------------------------------------------------------------------------------------------------- + +### +### Generic validator for bool (Enabled/Disabled) +### +_validate_bool() { + local name="${1}" + local value="${2}" + local message="${3}" + local ignore="${4:-0}" + local on="${5:-Enabled}" + local off="${5:-Disabled}" + + # Validate + if ! is_bool "${value}"; then + _log_env_valid "invalid" "${name}" "${value}" "Must be 0 or 1" "" + exit 1 + fi + + # Check if we ignore the value + if [ "${ignore}" = "1" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(disabled)" + return + fi + + # Show status + if [ "${value}" = "0" ]; then + _log_env_valid "valid" "${name}" "${value}" "${message}" "${off}" + else + _log_env_valid "valid" "${name}" "${value}" "${message}" "${on}" + fi +} + + +### +### Validate *_VHOST_ALIASES_ALLOW :[:] +### +_validate_vhost_aliases_allow() { + local name="${1}" + local value="${2}" + local vhost_enabled="${3}" + + # Empty value means no alias configuration + if [ -z "${value}" ]; then + # Check if vhost is disabled + if [ "${vhost_enabled}" = "0" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(vhost disabled)" + return + fi + _log_env_valid "valid" "${name}" "${value}" "No Aliases defined" + return + fi + + # Aliases can be comma separated + alias_urls= # This is used to show valid output + for item in ${value//,/ }; do + item_alias="$( echo "${item}" | awk -F':' '{print $1}' )" + item_path="$( echo "${item}" | awk -F':' '{print $2}' )" + item_cors="$( echo "${item}" | awk -F':' -v OFS=':' '{$1="";$2="";print}' | sed -e 's/^://g' -e 's/^://g' )" + + # Validate part + if ! echo "${item_alias}" | grep -E '^/(.+)/$' >/dev/null; then + _log_env_valid "invalid" "${name}" "${value}" "Invalid format" + _log_env_valid "invalid" "${name}" "${item}" "Invalid item" + _log_env_valid "invalid" "${name}" "${item_alias}" "Invalid part" + log "err" "The definition is invalid. It must start and end with a '/'" + log "err" "I.e., it must pass the following regex check: ^/(.+)/\$" + _log_aliases_allow_examples + exit 1 + fi + # Validate part + if ! echo "${item_path}" | grep -E '^/(.*)[^/]$' >/dev/null; then + _log_env_valid "invalid" "${name}" "${value}" "Invalid format" + _log_env_valid "invalid" "${name}" "${item}" "Invalid item" + _log_env_valid "invalid" "${name}" "${item_path}" "Invalid part" + log "err" "The definition is invalid. It must start with a '/' and can't have a trailing '/'" + log "err" "I.e., it must pass the following regex check: ^/(.*)[^/]\$" + _log_aliases_allow_examples + exit 1 + fi + # Validate part + if [ -n "${item_cors}" ]; then + if ! echo "${item_cors}" | grep -E '(http|https|http\(s\)\?):\/\/(.+)$' >/dev/null; then + _log_env_valid "invalid" "${name}" "${value}" "Invalid format" + _log_env_valid "invalid" "${name}" "${item}" "Invalid item" + _log_env_valid "invalid" "${name}" "${item_cors}" "Invalid part" + log "err" "The definition is invalid. It must be a valid regex in the form of:" + log "err" " http://\$" + log "err" " https://\$" + log "err" " http(s)?://\$" + log "err" "I.e., it must pass the following regex check: (http|https|http\(s\)\?):\/\/(.+)\$" + _log_aliases_allow_examples + exit 1 + fi + fi + alias_urls="${alias_urls}, ${item_alias}" + done + alias_urls="${alias_urls:2}" # Remove leading comma and leading space + + # Check if vhost is disabled + if [ "${vhost_enabled}" = "0" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(vhost disabled)" + return + fi + + # Display settings + _log_env_valid "valid" "${name}" "${value}" "Defined Aliases" "${alias_urls}" +} + + +### +### Validate *_VHOST_ALIASES_DENY [,] +### +_validate_vhost_aliases_deny() { + local name="${1}" + local value="${2}" + local vhost_enabled="${3}" + + # Empty value means no alias configuration + if [ -z "${value}" ]; then + # Check if vhost is disabled + if [ "${vhost_enabled}" = "0" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(vhost disabled)" + return + fi + _log_env_valid "valid" "${name}" "${value}" "No Aliases defined" + return + fi + + # Aliases can be comma separated + for item_alias in ${value//,/ }; do + + # Validate part + if ! echo "${item_alias}" | grep -E '^/[^:]*$' >/dev/null; then + _log_env_valid "invalid" "${name}" "${value}" "Invalid format" + _log_env_valid "invalid" "${name}" "${item_alias}" "Invalid " + log "err" "The definition is invalid. It must start with a '/'" + log "err" "I.e., it must pass the following regex check: ^/[^:]*\$" + _log_aliases_deny_examples + exit 1 + fi + done + + # Check if vhost is disabled + if [ "${vhost_enabled}" = "0" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(vhost disabled)" + return + fi + + # Display settings + _log_env_valid "valid" "${name}" "${value}" "Defined Aliases" "${value}" +} + + +### +### Validate *_VHOST_BACKEND +### string: conf:::: +### string: file: +### +_validate_vhost_backend() { + local name="${1}" + local value="${2}" + local vhost="${3}" # either "main" or "mass" + local vhost_enabled="${4}" # either "0" or "1" + + backend_prefix="$( get_backend_prefix "${value}" )" # file or conf + backend_file_name="$( get_backend_file_file "${value}" )" # filename + backend_conf_type="$( get_backend_conf_type "${value}" )" # phpfpm or rproxy + backend_conf_prot="$( get_backend_conf_prot "${value}" )" # tpc, http, https + backend_conf_host="$( get_backend_conf_host "${value}" )" # + backend_conf_port="$( get_backend_conf_port "${value}" )" # + + # 1. If no backend is specified + if ! backend_has_backend "${value}"; then + # Check if vhost is disabled + if [ "${vhost_enabled}" = "0" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(vhost disabled)" + else + _log_env_valid "valid" "${name}" "${value}" "No remote backend" "Serving static files only" + fi + return + fi + + # 2. Validate prefix + if [ "${backend_prefix}" != "file" ] && [ "${backend_prefix}" != "conf" ]; then + _log_env_valid "invalid" "${name}" "${value}" "Invalid format" + _log_env_valid "invalid" "${name}" "${backend_prefix}" "Valid backend prefix: " "'conf' or 'file'" + _log_backend_examples "all" + exit 1 + fi + + # 3. Validate file: - the filename + if [ "${backend_prefix}" = "file" ]; then + if ! backend_is_valid_file_file "${value}"; then + _log_env_valid "invalid" "${name}" "${value}" "Invalid format" + _log_env_valid "invalid" "${name}" "${backend_file_name}" "filename is invalid" + _log_backend_examples "file" + exit 1 + fi + fi + + if [ "${backend_prefix}" = "conf" ]; then + + # 4. Validate conf + if ! backend_is_valid_conf_type "${value}"; then + _log_env_valid "invalid" "${name}" "${value}" "Invalid format" + _log_env_valid "invalid" "${name}" "${backend_conf_type}" " is invalid. Must be: " "'phpfpm' or 'rproxy'" + _log_backend_examples "conf" + exit 1 + fi + # 5. Validate conf + if ! backend_is_valid_conf_prot "${value}"; then + _log_env_valid "invalid" "${name}" "${value}" "Invalid format" + _log_env_valid "invalid" "${name}" "${backend_conf_prot}" " is invalid. Must be: " "'tcp', 'http' or 'https'" + _log_backend_examples "conf" + exit 1 + fi + # 6. Validate conf phpfpm == tcp + if [ "${backend_conf_type}" = "phpfpm" ]; then + if [ "${backend_conf_prot}" != "tcp" ]; then + _log_env_valid "invalid" "${name}" "${value}" "Invalid format" + _log_env_valid "invalid" "${name}" "${backend_conf_prot}" "phpfpm only supports protocol " "'tcp'" + _log_backend_examples "conf" + exit 1 + fi + fi + # 7. Validate conf rproxy == http(s)? + if [ "${backend_conf_type}" = "rproxy" ]; then + if [ "${backend_conf_prot}" != "http" ] && [ "${backend_conf_prot}" != "https" ]; then + _log_env_valid "invalid" "${name}" "${value}" "Invalid format" + _log_env_valid "invalid" "${name}" "${backend_conf_prot}" "rproxy only supports protocol " "'http' or 'https'" + _log_backend_examples "conf" + exit 1 + fi + fi + # 8. Validate conf + if ! backend_is_valid_conf_host "${value}"; then + _log_env_valid "invalid" "${name}" "${value}" "Invalid format" + _log_env_valid "invalid" "${name}" "${backend_conf_host}" " is invalid. Must be: " "hostname, IPv4 or IPv6 addr" + _log_backend_examples "conf" + exit 1 + fi + # 8. Validate conf + if ! backend_is_valid_conf_port "${value}"; then + _log_env_valid "invalid" "${name}" "${value}" "Invalid format" + _log_env_valid "invalid" "${name}" "${backend_conf_port}" " is invalid. Must be valid port" + _log_backend_examples "conf" + exit 1 + fi + + fi + + # 9. Validate MAIN_VHOST_BACKEND (does not YET support file:) + # TODO: implement file: support for MAIN_VHOST + if [ "${vhost}" = "main" ]; then + if [ "${backend_prefix}" = "file" ]; then + _log_env_valid "invalid" "${name}" "${value}" "Unsupported" + _log_env_valid "invalid" "${name}" "${backend_prefix}" "\$MAIN_VHOST_BACKEND does not support 'file'. Use: " "'conf'" + _log_backend_examples "conf" + exit 1 + fi + fi + + # 10. MASS_VHOST_BACKEND cannot use rproxy, otherwise all autogenerated mass vhosts + # would reverse proxy to the same :, which does not make any sense at all. + # Instead, it only supports file:, so that each project can define it's own reverse + # proxy definition in a file and each can have different hosts and ports. + if [ "${vhost}" = "mass" ]; then + if [ "${backend_prefix}" = "conf" ] && [ "${backend_conf_type}" = "rproxy" ]; then + _log_env_valid "invalid" "${name}" "${value}" "Unsupported" + _log_env_valid "invalid" "${name}" "\$MASS_VHOST_BACKEND' does not support 'conf' with type 'rproxy" + log "err" "" + log "err" "Why is this?" + log "err" " The MASS_VHOST automatically creates a vhost for each directory present in: ${MASS_DOCROOT_BASE}/" + log "err" " Now imagine you specify a reverse proxy at http://example:3000." + log "err" " Then every automatically created virtual host would point to that address." + log "err" " You will end up with many virtual hosts all pointing the the same backend." + log "err" " This makes only sense with 'phpfpm'." + log "err" "" + log "err" "What should I do?" + log "err" " Use 'MASS_VHOST_BACKEND=file:config.txt' instead!" + log "err" " This allows you to add a config file to every project at: ${MASS_DOCROOT_BASE}//${MASS_VHOST_TEMPLATE_DIR}/config.txt" + log "err" " Then every project can define its own reverse proxy backend." + log "err" "" + log "err" "What is in that file?" + log "err" " The file contains a single line with the same config string you supplied for MASS_VHOST_BACKEND:" + log "err" "" + log "err" " ${value}" + log "err" "" + log "err" " Each project config file can also have different protocol/host/port values to make sense." + exit 1 + fi + fi + + # 11. Check if vhost is disabled + if [ "${vhost_enabled}" = "0" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(vhost disabled)" + return + fi + + # 12. Show settings (file) + if [ "${backend_prefix}" = "file" ]; then + if [ "${vhost}" = "main" ]; then + _log_env_valid "valid" "${name}" "${value}" "Backend set in file" "${MAIN_DOCROOT_BASE}/${MAIN_VHOST_TEMPLATE_DIR}/${backend_file_name}" + else + _log_env_valid "valid" "${name}" "${value}" "Backend set in file" "${MASS_DOCROOT_BASE}//${MASS_VHOST_TEMPLATE_DIR}/${backend_file_name}" + fi + # 13. Show settings (conf) + elif [ "${backend_prefix}" = "conf" ]; then + if [ "${backend_conf_type}" = "phpfpm" ]; then + _log_env_valid "valid" "${name}" "${value}" "PHP via PHP-FPM" "Remote: ${backend_conf_prot}://${backend_conf_host}:${backend_conf_port}" + elif [ "${backend_conf_type}" = "rproxy" ]; then + _log_env_valid "valid" "${name}" "${value}" "Reverse Proxy" "Remote: ${backend_conf_prot}://${backend_conf_host}:${backend_conf_port}" + fi + fi +} + + +### +### Validate *_VHOST_BACKEND +### string: file: +### +### Only file: is supported +### Only MASS_VHOST is supported +### Only applies, whenn MASS_VHOST_BACKEND is set +### Only applies, whenn MASS_VHOST_BACKEND does not use file: +### +_validate_vhost_backend_rewrite() { + local name="${1}" + local value="${2}" + local vhost="${3}" # either "main" or "mass" + local vhost_enabled="${4}" # either "0" or "1" + + # MASS_VHOST_BACKEND + backend_prefix="$( get_backend_prefix "${MASS_VHOST_BACKEND}" )" # file or conf + backend_file_name="$( get_backend_file_file "${MASS_VHOST_BACKEND}" )" # filename + + # MASS_VHOST_BACKEND_REWRITE + backend_rewrite_prefix="$( get_backend_prefix "${value}" )" # file or conf + backend_rewrite_file_name="$( get_backend_file_file "${value}" )" # filename + + # 1. If no backend is specified + if ! backend_has_backend "${value}"; then + # Check if vhost is disabled + if [ "${vhost_enabled}" = "0" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(vhost disabled)" + else + _log_env_valid "ignore" "${name}" "${value}" "Applying MASS_VHOST_BACKEND settings" + fi + return + fi + + # 1. Only accept if MASS_VHOST_BACKEND was set + if [ -z "${MASS_VHOST_BACKEND}" ]; then + _log_env_valid "invalid" "${name}" "${value}" "Overwrite makes no sense" + log "err" "" + log "err" "You can only 'overwrite' a backend that has been set." + log "err" "MASS_VHOST_BACKEND is unset, so the overwrite does not make sense." + log "err" "" + log "err" "Use MASS_VHOST_BACKEND=file: to keep 'serve static files only' and place a config file" + log "err" "for specific projects that require a backend." + log "err" "This variable is intended to overwrite an already specified backend that applies to all projects." + log "err" "" + log "err" "A common use case is to specify PHP-FPM backend globally via MASS_VHOST_BACKEND." + log "err" "And then use MASS_VHOST_BACKEND_REWRITE to adjust specific projects to Reverse Proxy." + exit 1 + fi + # 2. Only accept if MASS_VHOST_BACKEND is not equal to 'file:' + if [ "${backend_prefix}" = "file" ]; then + _log_env_valid "invalid" "${name}" "${value}" "Overwrite makes no sense" + log "err" "" + log "err" "You have set MASS_VHOST_BACKEND to 'file:${backend_file_name}'" + log "err" "This means you can already set a different value for each project, so no need to overwrite." + log "err" "" + log "err" "Overwriting a backend makes sense, when you set MASS_VHOST_BACKEND to 'conf:...'" + log "err" "Then the 'conf:..' settings are applied to every single project and you can then" + log "err" "use a file in MASS_VHOST_BACKEND_REWRITE to overwrite a setting for individual projects." + log "err" "" + log "err" "A common use case is to specify PHP-FPM backend globally via MASS_VHOST_BACKEND." + log "err" "And then use MASS_VHOST_BACKEND_REWRITE to adjust specific projects to Reverse Proxy." + exit 1 + fi + # 3. Only allow file: as a prefix + if [ "${backend_rewrite_prefix}" != "file" ]; then + _log_env_valid "invalid" "${name}" "${backend_rewrite_prefix}" "Invalid type" + log "err" "" + log "err" "You can only use file: for an individual overwrite." + log "err" "How else would you want to overwrite on a per project base?" + log "err" "The specified file in 'file:' will be placed in your project dirs conf dir." + log "err" "This way you can overwrite that specific project." + log "err" "" + log "err" "A common use case is to specify PHP-FPM backend globally via MASS_VHOST_BACKEND." + log "err" "And then use MASS_VHOST_BACKEND_REWRITE to adjust specific projects to Reverse Proxy." + log "err" "" + _log_backend_examples "file" + exit 1 + fi + # 4. Validate file: - the filename + if [ "${backend_rewrite_prefix}" = "file" ]; then + if ! backend_is_valid_file_file "${value}"; then + _log_env_valid "invalid" "${name}" "${value}" "Invalid format" + _log_env_valid "invalid" "${name}" "${backend_rewrite_file_name}" "filename is invalid" + _log_backend_examples "file" + exit 1 + fi + fi + # 5. Check if vhost is disabled + if [ "${vhost_enabled}" = "0" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(vhost disabled)" + return + fi + + # 6. Show settings (file) + if [ "${vhost}" = "main" ]; then + _log_env_valid "valid" "${name}" "${value}" "Backend overwrite" "${MAIN_DOCROOT_BASE}/${MAIN_VHOST_TEMPLATE_DIR}/${backend_rewrite_file_name}" + else + _log_env_valid "valid" "${name}" "${value}" "Backend overwrite" "${MASS_DOCROOT_BASE}//${MASS_VHOST_TEMPLATE_DIR}/${backend_rewrite_file_name}" + fi +} + + +### +### Validate *_VHOST_BACKEND_TIMEOUT +### +_validate_vhost_backend_timeout() { + local name="${1}" + local value="${2}" + local vhost_enabled="${3}" + + if ! is_int "${value}"; then + _log_env_valid "invalid" "${name}" "${value}" "Invalid timeout. Must be positive integer" + exit 1 + fi + # Check if vhost is disabled + if [ "${vhost_enabled}" = "0" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(vhost disabled)" + return + fi + _log_env_valid "valid" "${name}" "${value}" "Timeout: " "${value}sec" +} + + +### +### Validate *_VHOST_DOCROOT_DIR +### +_validate_vhost_docroot_dir() { + local name="${1}" + local value="${2}" + local vhost_enabled="${3}" + local vhost_backend="${4}" + local docroot_path="${5}" + + # Show ignored + if [ "${vhost_enabled}" = "0" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(vhost disabled)" + return + fi + # Check if we have a backend defined + if backend_has_backend "${value}"; then + if [ "$( get_backend_conf_type "${vhost_backend}" )" = "rproxy" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(using rproxy)" + return + fi + fi + _log_env_valid "valid" "${name}" "${value}" "Document root: " "${docroot_path}" +} + + +### +### Validate *_VHOST_SSL_TYPE +### +_validate_vhost_ssl_type() { + local name="${1}" + local value="${2}" + local vhost_enabled="${3}" + + if [ "${value}" != "plain" ] && [ "${value}" != "ssl" ] && [ "${value}" != "both" ] && [ "${value}" != "redir" ]; then + _log_env_valid "invalid" "${name}" "${value}" "Invalid type. Must be one of: " "plain, ssl, both, redir" + exit 1 + fi + + # Show ignored + if [ "${vhost_enabled}" = "0" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(vhost disabled)" + return + fi + + if [ "${value}" = "plain" ]; then + _log_env_valid "valid" "${name}" "${value}" "Vhost protocol" "HTTP only" + elif [ "${value}" = "ssl" ]; then + _log_env_valid "valid" "${name}" "${value}" "Vhost protocol" "HTTPS only" + elif [ "${value}" = "both" ]; then + _log_env_valid "valid" "${name}" "${value}" "Vhost protocol" "HTTP and HTTPS" + elif [ "${value}" = "redir" ]; then + _log_env_valid "valid" "${name}" "${value}" "Vhost protocol" "Redirect HTTP -> HTTPS" + fi +} + + +### +### Validate *_VHOST_TEMPLATE_DIR: vhost-gen template directory +### +_validate_vhost_template_dir() { + local name="${1}" + local value="${2}" + local vhost_enabled="${3}" + local template_path="${4}" + + # Show ignored + if [ "${vhost_enabled}" = "0" ]; then + _log_env_valid "ignore" "${name}" "${value}" "(vhost disabled)" + return + fi + _log_env_valid "valid" "${name}" "${value}" "Template dir" "${template_path}" +} + + +# ------------------------------------------------------------------------------------------------- +# Logger +# ------------------------------------------------------------------------------------------------- + +### +### Use custom logger to log env variable validity +### +_log_env_valid() { + local state="${1}" # 'valid', `ignore` or 'invalid' + local name="${2}" # Variable name + local value="${3}" # Variable value + local message="${4:-}" # Message: what will happen (valid) or expected format (invalid) + local message_val="${5:-}" # value for message + + local clr_valid="\033[0;32m" # green + local clr_invalid="\033[0;31m" # red + + local clr_expect="\033[0;31m" # red + local clr_ignore="\033[0;34m" # red + + local clr_ok="\033[0;32m" # green + local clr_fail="\033[0;31m" # red + local clr_rst="\033[0m" + + if [ "${state}" = "valid" ]; then + log "ok" "$( \ + printf "${clr_ok}%-11s${clr_rst}%-8s${clr_valid}\$%-27s${clr_rst}%-20s${clr_valid}%s${clr_rst}\n" \ + "[OK]" \ + "Valid" \ + "${name}" \ + "${message}" \ + "${message_val}" \ + )" "1" + elif [ "${state}" = "ignore" ]; then + log "ok" "$( \ + printf "${clr_ok}%-11s${clr_rst}%-8s${clr_rst}\$%-27s${clr_ignore}%-20s${clr_rst}%s\n" \ + "[OK]" \ + "Valid" \ + "${name}" \ + "ignored" \ + "${message}" \ + )" "1" + elif [ "${state}" = "invalid" ]; then + log "err" "$( \ + printf "${clr_fail}%-11s${clr_rst}%-8s${clr_invalid}\$%-27s${clr_rst}${clr_invalid}'%s'${clr_rst}. %s${clr_expect}%s${clr_rst}\n" \ + "[ERR]" \ + "Invalid" \ + "${name}" \ + "${value}" \ + "${message}" \ + "${message_val}" \ + )" "1" + else + log "????" "Internal: Wrong value given to _log_env_valid" + exit 1 + fi +} + + +### +### Log aliases examples as error messages +### +_log_aliases_allow_examples() { + log "err" "" + log "err" "Format (single): :" + log "err" "Format (single): ::" + log "err" "" + log "err" "Format (multi): :[:] [,:[:]]" + log "err" "" + + log "err" "" + log "err" "Example: /my-api-url/:/var/www/default/api" + log "err" "Example: /my-api-url/:/var/www/default/api:http(s)?://(.*)$" + log "err" "" + log "err" "Example: /img/:/var/www/img, /css/:/var/www/css, /js/:/var/www/js" +} + + +### +### Log aliases examples as error messages +### +_log_aliases_deny_examples() { + log "err" "" + log "err" "Format (single): " + log "err" "Format (multi): [,]" + + log "err" "" + log "err" "Example: /secret.*" + log "err" "Example: /\\.git, /secret.*" +} + + +### +### Log backend examples as error messages +### +_log_backend_examples() { + local show="${1}" # "all", "file" or "conf" + + log "err" "" + if [ "${show}" = "all" ] || [ "${show}" = "conf" ]; then + log "err" "Format: conf::::" + fi + if [ "${show}" = "all" ] || [ "${show}" = "file" ]; then + log "err" "Format: file:" + fi + + if [ "${show}" = "all" ] || [ "${show}" = "conf" ]; then + log "err" "" + log "err" "Example: conf:phpfpm:tcp:10.0.0.100:9000" + log "err" "Example: conf:phpfpm:tcp:domain.com:9000" + log "err" "" + log "err" "Example: conf:rproxy:http:10.0.0.100:3000" + log "err" "Example: conf:rproxy:http:domain.com:443" + log "err" "" + log "err" "Example: conf:rproxy:https:10.0.0.100:8080" + log "err" "Example: conf:rproxy:https:domain.com:8443" + fi + if [ "${show}" = "all" ] || [ "${show}" = "file" ]; then + log "err" "" + log "err" "Example: file:config.txt" + fi +} diff --git a/Dockerfiles/data/docker-entrypoint.d/02-timezone.sh b/Dockerfiles/data/docker-entrypoint.d/02-timezone.sh deleted file mode 100755 index 19b31c0..0000000 --- a/Dockerfiles/data/docker-entrypoint.d/02-timezone.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - - -############################################################ -# Functions -############################################################ - -### -### Change Timezone -### -set_timezone() { - local env_varname="${1}" - local debug="${2}" - local timezone= - - if ! env_set "${env_varname}"; then - log "info" "\$${env_varname} not set." "${debug}" - # Unix Time - log "info" "Setting container timezone to: UTC" "${debug}" - run "ln -sf /usr/share/zoneinfo/UTC /etc/localtime" "${debug}" - else - timezone="$( env_get "${env_varname}" )" - if [ -f "/usr/share/zoneinfo/${timezone}" ]; then - # Unix Time - log "info" "Setting container timezone to: ${timezone}" "${debug}" - run "ln -sf /usr/share/zoneinfo/${timezone} /etc/localtime" "${debug}" - else - log "err" "Invalid timezone for \$${env_varname}." "${debug}" - log "err" "Timezone '${timezone}' does not exist." "${debug}" - exit 1 - fi - fi - log "info" "Docker date set to: $(date)" "${debug}" -} diff --git a/Dockerfiles/data/docker-entrypoint.d/03-docker-logs.sh b/Dockerfiles/data/docker-entrypoint.d/03-docker-logs.sh deleted file mode 100755 index 408832f..0000000 --- a/Dockerfiles/data/docker-entrypoint.d/03-docker-logs.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - - -############################################################ -# Functions -############################################################ - -### -### Set docker logs -### -export_docker_logs() { - local varname="${1}" - local debug="${2}" - local value="0" - - if ! env_set "${varname}"; then - log "info" "\$${varname} not set. Logging errors and access to log files inside container." "${debug}" - else - value="$( env_get "${varname}" )" - if [ "${value}" = "0" ]; then - log "info" "\$${varname} disabled. Logging errors and access to log files inside container." "${debug}" - elif [ "${value}" = "1" ]; then - log "info" "\$${varname} enabled. Logging errors and access to Docker log (stderr and stdout)" "${debug}" - else - log "err" "Invalid value for \$${varname}: ${value}" - log "err" "Must be '1' (for On) or '0' (for Off)" - exit 1 - fi - fi - - # Set docker logs variable - eval "export ${varname}=${value}" -} diff --git a/Dockerfiles/data/docker-entrypoint.d/04-php-fpm.sh b/Dockerfiles/data/docker-entrypoint.d/04-php-fpm.sh deleted file mode 100755 index 406ad98..0000000 --- a/Dockerfiles/data/docker-entrypoint.d/04-php-fpm.sh +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - - -############################################################ -# Functions -############################################################ - -### -### Ensure PHP_FPM_ENABLE is set -### -export_php_fpm_enable() { - local varname="${1}" - local debug="${2}" - local value="0" - - if ! env_set "${varname}"; then - log "info" "\$${varname} not set. Disabling PHP-FPM." "${debug}" - else - value="$( env_get "${varname}" )" - if [ "${value}" = "0" ]; then - log "info" "PHP-FPM: Disabled" "${debug}" - elif [ "${value}" = "1" ]; then - log "info" "PHP-FPM: Enabled" "${debug}" - else - log "err" "Invalid value for \$${varname}: ${value}" - log "err" "Must be '1' (for On) or '0' (for Off)" - exit 1 - fi - fi - - # Ensure variable is exported - eval "export ${varname}=${value}" -} - - -### -### Ensure PHP_FPM_SERVER_ADDR is set (if needed) -### -export_php_fpm_server_addr() { - local varname="${1}" - local debug="${2}" - local value= - - if [ "${PHP_FPM_ENABLE}" = "1" ]; then - if ! env_set "${varname}"; then - log "err" "PHP-FPM is enabled, but \$${varname} not specified, but required." "${debug}" - exit 1 - fi - value="$( env_get "${varname}" )" - if [ -z "${value}" ]; then - log "err" "PHP-FPM enabled, but \$${varname} is empty." "${debug}" - exit 1 - fi - log "info" "PHP-FPM: Server address: ${value}" "${debug}" - fi - - # Ensure variable is exported - eval "export ${varname}=${value}" -} - - -### -### Ensure PHP_FPM_SERVER_PORT is set (if needed) -### -export_php_fpm_server_port() { - local varname="${1}" - local debug="${2}" - local value="9000" - - if [ "${PHP_FPM_ENABLE}" = "1" ]; then - if ! env_set "${varname}"; then - log "info" "\$${varname} not specified, keeping default: ${value}" "${debug}" - else - value="$( env_get "${varname}" )" - - if [ -z "${value}" ]; then - log "err" "\$${varname} is empty." "${debug}" - exit 1 - fi - if ! isint "${value}"; then - log "err" "\$${varname} is not a valid integer: ${value}" "${debug}" - exit 1 - fi - if [ "${value}" -lt "1" ] || [ "${value}" -gt "65535" ]; then - log "err" "\$${varname} is not in a valid port range: ${value}" "${debug}" - exit 1 - fi - log "info" "PHP-FPM: Server port: ${value}" "${debug}" - fi - fi - - # Ensure variable is exported if not set - eval "export ${varname}=${value}" -} - - -### -### Ensure PHP_FPM_TIMEOUT is set (if needed) -### -export_php_fpm_timeout() { - local varname="${1}" - local debug="${2}" - local value="180" - - if [ "${PHP_FPM_ENABLE}" = "1" ]; then - if ! env_set "${varname}"; then - log "info" "\$${varname} not specified, keeping default: ${value}" "${debug}" - else - value="$( env_get "${varname}" )" - - if [ -z "${value}" ]; then - log "err" "\$${varname} is empty." "${debug}" - exit 1 - fi - if ! isint "${value}"; then - log "err" "\$${varname} is not a valid integer: ${value}" "${debug}" - exit 1 - fi - if [ "${value}" -lt "0" ]; then - log "err" "\$${varname} must be greater than 0: ${value}" "${debug}" - exit 1 - fi - log "info" "PHP-FPM: Timeout: ${value}" "${debug}" - fi - fi - - # Ensure variable is exported if not set - eval "export ${varname}=${value}" -} diff --git a/Dockerfiles/data/docker-entrypoint.d/05-main-vhost.sh b/Dockerfiles/data/docker-entrypoint.d/05-main-vhost.sh deleted file mode 100755 index a6d27b0..0000000 --- a/Dockerfiles/data/docker-entrypoint.d/05-main-vhost.sh +++ /dev/null @@ -1,214 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - - -############################################################ -# Functions -############################################################ - -### -### Ensure MAIN_VHOST_ENABLE is exported -### -export_main_vhost_enable() { - local varname="${1}" - local debug="${2}" - local value="1" - - if ! env_set "${varname}"; then - log "info" "\$${varname} not set. Enabling default vhost." "${debug}" - else - value="$( env_get "${varname}" )" - if [ "${value}" = "0" ]; then - log "info" "Main vhost: Disabled" "${debug}" - elif [ "${value}" = "1" ]; then - log "info" "Main vhost: Enabled" "${debug}" - else - log "err" "Invalid value for \$${varname}: ${value}" - log "err" "Must be '1' (for On) or '0' (for Off)" - exit 1 - fi - fi - - # Ensure variable is exported - eval "export ${varname}=${value}" -} - - -### -### Ensure MAIN_VHOST_SSL_TYPE is set (if needed) -### -export_main_vhost_ssl_type() { - local varname="${1}" - local debug="${2}" - local value="plain" - - if [ "${MAIN_VHOST_ENABLE}" = "1" ]; then - if ! env_set "${varname}"; then - log "info" "\$${varname} not specified, defaulting to: plain" "${debug}" - else - value="$( env_get "${varname}" )" - if [ "${value}" = "plain" ]; then - log "info" "Main vhost: Setting SSL type to: http only" "${debug}" - elif [ "${value}" = "ssl" ]; then - log "info" "Main vhost: Setting SSL type to: https only" "${debug}" - elif [ "${value}" = "both" ]; then - log "info" "Main vhost: Setting SSL type to: http and https" "${debug}" - elif [ "${value}" = "redir" ]; then - log "info" "Main vhost: Setting SSL type to: redirect http to https" "${debug}" - else - log "err" "Invalid value for \$${varname}: '${value}'. Allowed: plain, ssl, both or redir" "${debug}" - exit 1 - fi - fi - # Ensure variable is exported - eval "export ${varname}=${value}" - fi -} - - -### -### Ensure MAIN_VHOST_SSL_GEN is set (if needed) -### -export_main_vhost_ssl_gen() { - local varname="${1}" - local debug="${2}" - local value="0" - - if [ "${MAIN_VHOST_ENABLE}" = "1" ]; then - if ! env_set "${varname}"; then - log "info" "\$${varname} not specified, defaulting to not generate SSL certificates" "${debug}" - else - value="$( env_get "${varname}" )" - if [ "${value}" = "0" ]; then - log "info" "Main vhost: Disable automatic generation of SSL certificates" "${debug}" - elif [ "${value}" = "1" ]; then - log "info" "Main vhost: Enable automatic generation of SSL certificates" "${debug}" - else - log "err" "Invalid value for \$${varname}: '${value}'. Allowed: 0 or 1" "${debug}" - exit 1 - fi - fi - # Ensure variable is exported - eval "export ${varname}=${value}" - fi -} - - -### -### Ensure MAIN_VHOST_SSL_CN is set (if needed) -### -export_main_vhost_ssl_cn() { - local varname="${1}" - local debug="${2}" - local value="localhost" - - if [ "${MAIN_VHOST_ENABLE}" = "1" ]; then - if ! env_set "${varname}"; then - log "info" "\$${varname} not specified. Keeping default: ${value}" "${debug}" - else - value="$( env_get "${varname}" )" - if [ -z "${value}" ]; then - log "err" "\$${varname} set but empty. Cannot determine CN name for SSL certificate generation." "${debug}" - exit 1 - else - log "info" "Main vhost: SSL CN: ${value}" "${debug}" - fi - fi - # Ensure variable is exported - eval "export ${varname}=${value}" - fi -} - - -### -### Ensure MAIN_VHOST_DOCROOT is set (if needed) -### -export_main_vhost_docroot() { - local varname="${1}" - local debug="${2}" - local value="htdocs" - - if [ "${MAIN_VHOST_ENABLE}" = "1" ]; then - if ! env_set "${varname}"; then - log "info" "\$${varname} not specified. Keeping default: ${value}" "${debug}" - else - value="$( env_get "${varname}" )" - log "info" "Main vhost: changing document root to: ${value}" "${debug}" - fi - # Ensure variable is exported - eval "export ${varname}=${value}" - fi -} - - -### -### Ensure MAIN_VHOST_TPL is set (if needed) -### -export_main_vhost_tpl() { - local varname="${1}" - local debug="${2}" - local value="cfg" - - if [ "${MAIN_VHOST_ENABLE}" = "1" ]; then - if ! env_set "${varname}"; then - log "info" "\$${varname} not specified. Keeping default: ${value}" "${debug}" - else - value="$( env_get "${varname}" )" - log "info" "Main vhost: changing template dir to: ${value}" "${debug}" - fi - # Ensure variable is exported - eval "export ${varname}=${value}" - fi -} - - -### -### Ensure MAIN_VHOST_STATUS_ENABLE is set (if needed) -### -export_main_vhost_status_enable() { - local varname="${1}" - local debug="${2}" - local value="0" - - if [ "${MAIN_VHOST_ENABLE}" = "1" ]; then - if ! env_set "${varname}"; then - log "info" "\$${varname} not specified, defaulting to disable httpd status page" "${debug}" - else - value="$( env_get "${varname}" )" - if [ "${value}" = "0" ]; then - log "info" "Main vhost: Disabling httpd status page" "${debug}" - elif [ "${value}" = "1" ]; then - log "info" "Main vhost: Enabling httpd status page" "${debug}" - else - log "err" "Invalid value for \$${varname}: '${value}'. Allowed: 0 or 1" "${debug}" - exit 1 - fi - fi - # Ensure variable is exported - eval "export ${varname}=${value}" - fi -} - - -### -### Ensure MAIN_VHOST_STATUS_ALIAS is set (if needed) -### -export_main_vhost_status_alias() { - local varname="${1}" - local debug="${2}" - local value="/httpd-status" - - if [ "${MAIN_VHOST_ENABLE}" = "1" ]; then - if ! env_set "${varname}"; then - log "info" "\$${varname} not specified. Keeping default: ${value}" "${debug}" - else - value="$( env_get "${varname}" )" - log "info" "Main vhost: Changing status page alias to: ${value}" "${debug}" - fi - # Ensure variable is exported - eval "export ${varname}=${value}" - fi -} diff --git a/Dockerfiles/data/docker-entrypoint.d/06-mass-vhost.sh b/Dockerfiles/data/docker-entrypoint.d/06-mass-vhost.sh deleted file mode 100755 index 0c52504..0000000 --- a/Dockerfiles/data/docker-entrypoint.d/06-mass-vhost.sh +++ /dev/null @@ -1,165 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - - -############################################################ -# Functions -############################################################ - -### -### Ensure MASS_VHOST_ENABLE is exported -### -export_mass_vhost_enable() { - local varname="${1}" - local debug="${2}" - local value="0" - - if ! env_set "${varname}"; then - log "info" "\$${varname} not set. Enabling default vhost." "${debug}" - else - value="$( env_get "${varname}" )" - if [ "${value}" = "0" ]; then - log "info" "Mass vhost: Disabled" "${debug}" - elif [ "${value}" = "1" ]; then - log "info" "Mass vhost: Enabled" "${debug}" - else - log "err" "Invalid value for \$${varname}: ${value}" - log "err" "Must be '1' (for On) or '0' (for Off)" - exit 1 - fi - fi - - # Ensure variable is exported - eval "export ${varname}=${value}" -} - - -### -### Ensure MASS_VHOST_SSL_TYPE is set (if needed) -### -export_mass_vhost_ssl_type() { - local varname="${1}" - local debug="${2}" - local value="plain" - - if [ "${MASS_VHOST_ENABLE}" = "1" ]; then - if ! env_set "${varname}"; then - log "info" "\$${varname} not specified, defaulting to: plain" "${debug}" - else - value="$( env_get "${varname}" )" - if [ "${value}" = "plain" ]; then - log "info" "Mass vhost: Setting SSL type to: http only" "${debug}" - elif [ "${value}" = "ssl" ]; then - log "info" "Mass vhost: Setting SSL type to: https only" "${debug}" - elif [ "${value}" = "both" ]; then - log "info" "Mass vhost: Setting SSL type to: http and https" "${debug}" - elif [ "${value}" = "redir" ]; then - log "info" "Mass vhost: Setting SSL type to: redirect http to https" "${debug}" - else - log "err" "Invalid value for \$${varname}: '${value}'. Allowed: plain, ssl, both or redir" "${debug}" - exit 1 - fi - fi - fi - - # Ensure variable is exported - eval "export ${varname}=${value}" -} - - -### -### Ensure MASS_VHOST_SSL_GEN is set (if needed) -### -export_mass_vhost_ssl_gen() { - local varname="${1}" - local debug="${2}" - local value="0" - - if [ "${MASS_VHOST_ENABLE}" = "1" ]; then - if ! env_set "${varname}"; then - log "info" "\$${varname} not specified, defaulting to not generate SSL certificates" "${debug}" - else - value="$( env_get "${varname}" )" - if [ "${value}" = "0" ]; then - log "info" "Mass vhost: Disable automatic generation of SSL certificates" "${debug}" - elif [ "${value}" = "1" ]; then - log "info" "Mass vhost: Enable automatic generation of SSL certificates" "${debug}" - else - log "err" "Invalid value for \$${varname}: '${value}'. Allowed: 0 or 1" "${debug}" - exit 1 - fi - fi - fi - - # Ensure variable is exported - eval "export ${varname}=${value}" -} - - -### -### Ensure MASS_VHOST_TLD is set (if needed) -### -export_mass_vhost_tld() { - local varname="${1}" - local debug="${2}" - local value="loc" - - if [ "${MASS_VHOST_ENABLE}" = "1" ]; then - if ! env_set "${varname}"; then - log "info" "\$${varname} not specified. Keeping default: ${value}" "${debug}" - else - value="$( env_get "${varname}" )" - log "info" "Mass vhost: changing tld to: ${value}" "${debug}" - fi - fi - - # Ensure variable is exported - eval "export ${varname}=${value}" -} - - -### -### Ensure MASS_VHOST_DOCROOT is set (if needed) -### -export_mass_vhost_docroot() { - local varname="${1}" - local debug="${2}" - local value="htdocs" - - if [ "${MASS_VHOST_ENABLE}" = "1" ]; then - if ! env_set "${varname}"; then - log "info" "\$${varname} not specified. Keeping default: ${value}" "${debug}" - else - value="$( env_get "${varname}" )" - log "info" "Mass vhost: changing document root to: ${value}" "${debug}" - fi - fi - - # Ensure variable is exported - eval "export ${varname}=${value}" -} - - -### -### Ensure MASS_VHOST_TPL is set (if needed) -### -export_mass_vhost_tpl() { - local varname="${1}" - local debug="${2}" - local value="cfg" - - if [ "${MASS_VHOST_ENABLE}" = "1" ]; then - if ! env_set "${varname}"; then - log "info" "\$${varname} not specified. Keeping default: ${value}" "${debug}" - else - value="$( env_get "${varname}" )" - log "info" "Mass vhost: changing template dir to: ${value}" "${debug}" - fi - fi - - # Ensure variable is exported - eval "export ${varname}=${value}" -} diff --git a/Dockerfiles/data/docker-entrypoint.d/07-vhost-gen.sh b/Dockerfiles/data/docker-entrypoint.d/07-vhost-gen.sh deleted file mode 100755 index 06d8b92..0000000 --- a/Dockerfiles/data/docker-entrypoint.d/07-vhost-gen.sh +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - - -############################################################ -# Functions -############################################################ - - -### -### Ensure HTTP2_ENABLE is exported -### -export_http2_enable() { - local varname="${1}" - local debug="${2}" - local value="1" - - if ! env_set "${varname}"; then - log "info" "\$${varname} not set. Enabling http2." "${debug}" - else - value="$( env_get "${varname}" )" - if [ "${value}" = "0" ]; then - log "info" "http2: Disabled" "${debug}" - elif [ "${value}" = "1" ]; then - log "info" "http2: Enabled" "${debug}" - else - log "err" "Invalid value for \$${varname}: ${value}" - log "err" "Must be '1' (for On) or '0' (for Off)" - exit 1 - fi - fi - - # Ensure variable is exported - eval "export ${varname}=${value}" -} - - -### -### Copy custom vhost-gen template -### -vhost_gen_copy_custom_template() { - local input_dir="${1}" - local output_dir="${2}" - local template_name="${3}" - local debug="${4}" - - if [ ! -d "${input_dir}" ]; then - run "mkdir -p ${input_dir}" "${debug}" - fi - - if [ -f "${input_dir}/${template_name}" ]; then - log "info" "vhost-gen: applying customized global template: ${template_name}" "${debug}" - run "cp ${input_dir}/${template_name} ${output_dir}/${template_name}" "${debug}" - else - log "info" "vhost-gen: no customized template found" "${debug}" - fi -} - - -### -### Set PHP_FPM -### -vhost_gen_php_fpm() { - local enable="${1}" - local addr="${2}" - local port="${3}" - local timeout="${4}" - local config="${5}" - local debug="${6}" - - if [ "${enable}" -eq "1" ]; then - run "sed -i'' 's/__PHP_ENABLE__/yes/g' ${config}" "${debug}" - run "sed -i'' 's/__PHP_ADDR__/${addr}/g' ${config}" "${debug}" - run "sed -i'' 's/__PHP_PORT__/${port}/g' ${config}" "${debug}" - run "sed -i'' 's/__PHP_TIMEOUT__/${timeout}/g' ${config}" "${debug}" - else - run "sed -i'' 's/__PHP_ENABLE__/no/g' ${config}" "${debug}" - fi -} - - -### -### Configure Docker logs -### -vhost_gen_docker_logs() { - local enable="${1}" - local config="${2}" - local debug="${3}" - - if [ "${enable}" -eq "1" ]; then - run "sed -i'' 's/__DOCKER_LOGS_ERROR__/yes/g' ${config}" "${debug}" - run "sed -i'' 's/__DOCKER_LOGS_ACCESS__/yes/g' ${config}" "${debug}" - else - run "sed -i'' 's/__DOCKER_LOGS_ERROR__/no/g' ${config}" "${debug}" - run "sed -i'' 's/__DOCKER_LOGS_ACCESS__/no/g' ${config}" "${debug}" - fi -} - - - -### -### Generate Main vhost? -### -vhost_gen_generate_main_vhost() { - local enable="${1}" - local docroot="${2}" - local config="${3}" - local template="${4}" - local ssl_type="${5}" - local verbose="${6}" - local debug="${7}" - - if [ "${enable}" -eq "1" ]; then - - # vhost-gen verbosity - if [ "${verbose}" -gt "0" ]; then - verbose="-v" - else - verbose="" - fi - run "vhost-gen -n localhost -p ${docroot} -c ${config} -o ${template} ${verbose} -d -s -m ${ssl_type}" "${debug}" - fi -} - - - -### -### Enable HTTPD status page? -### -vhost_gen_main_vhost_httpd_status() { - local enable="${1}" - local alias="${2}" - local config="${3}" - local debug="${4}" - - if [ "${enable}" -eq "1" ]; then - run "sed -i'' 's|__ENABLE_STATUS__|yes|g' ${config}" "${debug}" - run "sed -i'' 's|__STATUS_ALIAS__|${alias}|g' ${config}" "${debug}" - else - run "sed -i'' 's|__ENABLE_STATUS__|no|g' ${config}" "${debug}" - fi -} - - - -### -### Set DOCROOT_SUFFIX -### -vhost_gen_mass_vhost_docroot() { - local enable="${1}" - local docroot="${2}" - local config="${3}" - local debug="${4}" - - if [ "${enable}" -eq "1" ]; then - run "sed -i'' 's|__DOCROOT_SUFFIX__|${docroot}|g' ${config}" "${debug}" - fi -} - - -### -### Set TLD -### -vhost_gen_mass_vhost_tld() { - local enable="${1}" - local tld="${2}" - local config="${3}" - local debug="${4}" - - if [ "${enable}" -eq "1" ]; then - run "sed -i'' 's/__TLD__/${tld}/g' ${config}" "${debug}" - fi -} - - -### -### Set HTTP2_ENABLE -### -vhost_gen_http2() { - local enable="${1}" - local config="${2}" - local debug="${3}" - - if [ "${enable}" -eq "1" ]; then - run "sed -i'' 's/__HTTP2_ENABLE__/True/g' ${config}" "${debug}" - else - run "sed -i'' 's/__HTTP2_ENABLE__/False/g' ${config}" "${debug}" - fi -} diff --git a/Dockerfiles/data/docker-entrypoint.d/09-fix-permissions.sh b/Dockerfiles/data/docker-entrypoint.d/09-fix-permissions.sh deleted file mode 100755 index b2772da..0000000 --- a/Dockerfiles/data/docker-entrypoint.d/09-fix-permissions.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - - -############################################################ -# Functions -############################################################ - - -### -### Change UID -### -fix_perm() { - local uid_varname="${1}" - local gid_varname="${2}" - local directory="${3}" - local recursive="${4}" - local debug="${5}" - - local perm= - - # Get uid - if env_set "${uid_varname}"; then - perm="$( env_get "${uid_varname}" )" - fi - - # Get gid - if env_set "${gid_varname}"; then - perm="${perm}:$( env_get "${gid_varname}" )" - fi - - if [ -n "${perm}" ]; then - if [ "${recursive}" = "1" ]; then - run "chown -R ${perm} ${directory}" "${debug}" - else - run "chown ${perm} ${directory}" "${debug}" - fi - fi -} diff --git a/Dockerfiles/data/docker-entrypoint.d/10-supervisord.sh b/Dockerfiles/data/docker-entrypoint.d/10-supervisord.sh deleted file mode 100755 index abfc66a..0000000 --- a/Dockerfiles/data/docker-entrypoint.d/10-supervisord.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - - -############################################################ -# Functions -############################################################ - - -### -### Create supervisord.conf -### -supervisord_create() { - local httpd_command="${1}" - local watcherd_command="${2}" - local config="${3}" - - if [ -d "$( basename "${config}" )" ]; then - mkdir -p "$( basename "${config}" )" - fi - - { - echo "[supervisord]" - echo "user=root" - echo "nodaemon=true" - echo - echo "[program:httpd]" - echo "command=${httpd_command}" - echo "priority=1" - echo "autostart=true" - echo "startretries=100" - echo "startsecs=1" - echo "autorestart=true" - echo "stdout_logfile=/dev/stdout" - echo "stdout_logfile_maxbytes=0" - echo "stderr_logfile=/dev/stderr" - echo "stderr_logfile_maxbytes=0" - echo "stdout_events_enabled=true" - echo "stderr_events_enabled=true" - echo - echo "[program:watcherd]" - echo "command=${watcherd_command}" - echo "priority=999" - echo "autostart=true" - echo "autorestart=false" - echo "stdout_logfile=/dev/stdout" - echo "stdout_logfile_maxbytes=0" - echo "stderr_logfile=/dev/stderr" - echo "stderr_logfile_maxbytes=0" - echo "stdout_events_enabled=true" - echo "stderr_events_enabled=true" - } > "${config}" -} diff --git a/Dockerfiles/data/docker-entrypoint.d/10-uid-gid.sh b/Dockerfiles/data/docker-entrypoint.d/10-uid-gid.sh new file mode 100755 index 0000000..171cf25 --- /dev/null +++ b/Dockerfiles/data/docker-entrypoint.d/10-uid-gid.sh @@ -0,0 +1,139 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +### +### This file holds functions to change user id/gid +### + + +# ------------------------------------------------------------------------------------------------- +# [SET] FUNCTIONS +# ------------------------------------------------------------------------------------------------- + +### +### Change UID +### +set_uid() { + local uid="${1}" + local username="${2}" + local spare_uid=9876 # spare uid to change another user to + + # If uid is empty, end this function + if [ -z "${uid}" ]; then + return + fi + + # Check if username with given uid already exists + if target_username="$( _get_username_by_uid "${uid}" )"; then + # It is not our user, so we need to changes his/her uid to something else first + if [ "${target_username}" != "${username}" ]; then + log "info" "User with ${uid} already exists: ${target_username}" + log "info" "Changing UID of ${target_username} to ${spare_uid}" + run "usermod -u ${spare_uid} ${target_username}" + fi + fi + log "info" "Setting uid to ${uid} (user: ${username})" + run "usermod -u ${uid} ${username}" + run "id ${username}" +} + + +### +### Change GID +### +set_gid() { + local gid="${1}" + local username="${2}" + local groupname="${3}" + + local spare_gid=9876 # spare gid to change another group to + + # If gid is empty, end this function + if [ -z "${gid}" ]; then + return + fi + + # Groupname with this gid already exists + if target_groupname="$( _get_groupname_by_gid "${gid}" )"; then + # It is not our group, so we need to changes his/her gid to something else first + if [ "${target_groupname}" != "${groupname}" ]; then + log "info" "Group with ${gid} already exists: ${target_groupname}" + log "info" "Changing GID of ${target_groupname} to ${spare_gid}" + run "groupmod -g ${spare_gid} ${target_groupname}" + fi + fi + # Change ugd and fix homedir permissions + log "info" "Setting gid to ${gid} (group: ${groupname})" + run "groupmod -g ${gid} ${groupname}" + run "id ${username}" +} + + + +# ------------------------------------------------------------------------------------------------- +# HELPER FUNCTIONS +# ------------------------------------------------------------------------------------------------- + +### +### Get username by its uid +### +_get_username_by_uid() { + if getent="$( getent passwd "${1}" )"; then + echo "${getent//:*}" + return 0 + fi + return 1 +} + + +### +### Get groupname by its gid +### +_get_groupname_by_gid() { + if getent="$( getent group "${1}" )"; then + echo "${getent//:*}" + return 0 + fi + return 1 +} + + +### +### Get home directory by username +### +_get_homedir_by_username() { + getent passwd "${1}" | cut -d: -f6 +} + + +### +### Get home directory by groupname +### +_get_homedir_by_groupname() { + grep -E ".*:x:[0-9]+:[0-9]+:$( _get_groupname_by_gid "${1}" ).*" /etc/passwd | cut -d: -f6 +} + + + +# ------------------------------------------------------------------------------------------------- +# SANITY CHECKS +# ------------------------------------------------------------------------------------------------- + +### +### The following commands are required and used in the current script. +### +if ! command -v usermod >/dev/null 2>&1; then + log "err" "usermod not found, but required." + exit 1 +fi +if ! command -v groupmod >/dev/null 2>&1; then + log "err" "groupmod not found, but required." + exit 1 +fi +if ! command -v getent >/dev/null 2>&1; then + log "err" "getent not found, but required." + exit 1 +fi diff --git a/Dockerfiles/data/docker-entrypoint.d/11-timezone.sh b/Dockerfiles/data/docker-entrypoint.d/11-timezone.sh new file mode 100755 index 0000000..5592891 --- /dev/null +++ b/Dockerfiles/data/docker-entrypoint.d/11-timezone.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +### +### This file holds functions to change the timezone +### + + +# ------------------------------------------------------------------------------------------------- +# [SET] FUNCTIONS +# ------------------------------------------------------------------------------------------------- + +### +### Set Timezone +### +set_timezone() { + local timezone="${1}" + + # If uid is empty, end this function + if [ "${timezone}" = "UTC" ]; then + log "info" "Skipping timezone. Already set to UTC: $(date)" + return + fi + + # Unix Time + log "info" "Setting timezone to ${timezone}" + run "ln -sf /usr/share/zoneinfo/${timezone} /etc/localtime" + log "info" "Current date: $(date)" +} diff --git a/Dockerfiles/data/docker-entrypoint.d/12-vhost-gen.sh b/Dockerfiles/data/docker-entrypoint.d/12-vhost-gen.sh new file mode 100755 index 0000000..311268e --- /dev/null +++ b/Dockerfiles/data/docker-entrypoint.d/12-vhost-gen.sh @@ -0,0 +1,162 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +### +### This file holds functions to manipiate vhosts +### + + +# ------------------------------------------------------------------------------------------------- +# ALL VHOST FUNCTIONS +# ------------------------------------------------------------------------------------------------- + +### +### Copy custom vhost-gen override template (user-mounted) +### +vhostgen_copy_custom_template() { + local input_dir="${1}" + local output_dir="${2}" + local template_name="${3}" + + if [ ! -d "${input_dir}" ]; then + run "mkdir -p ${input_dir}" + fi + + if [ -f "${input_dir}/${template_name}" ]; then + log "info" "vhost-gen: applying custom global template: ${input_dir}/${template_name}" + run "cp ${input_dir}/${template_name} ${output_dir}/${template_name}" + else + log "info" "vhost-gen: no custom global template found in: ${input_dir}/${template_name}" + fi +} + + + +# ------------------------------------------------------------------------------------------------- +# MAIN VHOST FUNCTIONS +# ------------------------------------------------------------------------------------------------- + +### +### Generate config for MAIN_VHOST +### +vhostgen_main_generate_config() { + local httpd_server="${1}" # nginx, apache22 or apache24 + local backend_string="${2}" + local http2_enable="${3}" + local aliases_allow="${4}" + local aliases_deny="${5}" + local status_enable="${6}" + local status_alias="${7}" + local docker_logs="${8}" + local timeout="${9}" + local outpath="${10}" + + be_conf_type="$( get_backend_conf_type "${backend_string}" )" + be_conf_host="$( get_backend_conf_host "${backend_string}" )" + be_conf_port="$( get_backend_conf_port "${backend_string}" )" + + # Defaults + directory_index="index.html, index.htm" + php_fpm_enable="no" + + # PHP-FPM specific + if [ "${be_conf_type}" = "phpfpm" ]; then + php_fpm_enable="yes" + directory_index="index.php, index.html, index.htm" + fi + generate_vhostgen_conf \ + "${httpd_server}" \ + "/etc/httpd/conf.d" \ + "" \ + "" \ + "${directory_index}" \ + "$( to_python_bool "${http2_enable}" )" \ + "/etc/httpd/cert/main" \ + "/etc/httpd/cert/main" \ + "'default'" \ + "$( to_python_bool "${docker_logs}" )" \ + "${php_fpm_enable}" \ + "${be_conf_host}" \ + "${be_conf_port}" \ + "${timeout}" \ + "${aliases_allow}" \ + "${aliases_deny}" \ + "$( to_python_bool "${status_enable}" )" \ + "${status_alias}" \ + > "${outpath}" +} + + +### +### Generate vhost for MAIN_VHOST (if enabled) +### +vhostgen_main_generate() { + local enable="${1}" + local docroot="${2}" + local backend="${3}" + local config="${4}" + local template="${5}" + local ssl_type="${6}" + local custom_template_dir="${7:-}" # Specifies a different vhost-gen template dir (Dockerfile might copy custom vhost-gen templates) + + # Not using main virtual host, so no need to generate it + if [ "${enable}" -eq "0" ]; then + return + fi + + # vhost-gen always runs with minimum INFO verbosity level + local verbose="-v" + local reverse=0 + + # Check if reverse proxy or not + if [ -n "${backend}" ]; then + be_type="$( get_backend_conf_type "${backend}" )" # phpfpm or rproxy + be_prot="$( get_backend_conf_prot "${backend}" )" # tcp, http, https + be_host="$( get_backend_conf_host "${backend}" )" # + be_port="$( get_backend_conf_port "${backend}" )" # + if [ "${be_type}" = "rproxy" ]; then + reverse=1 + fi + fi + + # increase vhost-gen verbosity? + if [ "${DEBUG_RUNTIME}" -gt "1" ]; then + verbose="-vv" + elif [ "${DEBUG_RUNTIME}" -gt "0" ]; then + verbose="-v" + fi + + if [ "${reverse}" = "1" ]; then + if [ -n "${custom_template_dir}" ]; then + if ! run \ + "vhost-gen ${verbose} -d -n \"localhost\" -r \"${be_prot}://${be_host}:${be_port}\" -l / -c \"${config}\" -o \"${template}\" -s -m \"${ssl_type}\" -t \"${custom_template_dir}\"" \ + "Failed to create default vhost"; then + exit 1 + fi + else + if ! run \ + "vhost-gen ${verbose} -d -n \"localhost\" -r \"${be_prot}://${be_host}:${be_port}\" -l / -c \"${config}\" -o \"${template}\" -s -m \"${ssl_type}\"" \ + "Failed to create default vhost"; then + exit 1 + fi + fi + else + if [ -n "${custom_template_dir}" ]; then + if ! run \ + "vhost-gen ${verbose} -d -n \"localhost\" -p \"${docroot}\" -c \"${config}\" -o \"${template}\" -s -m \"${ssl_type}\" -t \"${custom_template_dir}\"" \ + "Failed to create default vhost"; then + exit 1 + fi + else + if ! run \ + "vhost-gen ${verbose} -d -n \"localhost\" -p \"${docroot}\" -c \"${config}\" -o \"${template}\" -s -m \"${ssl_type}\"" \ + "Failed to create default vhost"; then + exit 1 + fi + fi + fi + log "trace" "$( grep -v '^[[:blank:]]*$' "/etc/httpd/conf.d/localhost.conf" )" +} diff --git a/Dockerfiles/data/docker-entrypoint.d/08-cert-gen.sh b/Dockerfiles/data/docker-entrypoint.d/13-cert-gen.sh similarity index 54% rename from Dockerfiles/data/docker-entrypoint.d/08-cert-gen.sh rename to Dockerfiles/data/docker-entrypoint.d/13-cert-gen.sh index 65e1095..aeafa78 100755 --- a/Dockerfiles/data/docker-entrypoint.d/08-cert-gen.sh +++ b/Dockerfiles/data/docker-entrypoint.d/13-cert-gen.sh @@ -4,10 +4,14 @@ set -e set -u set -o pipefail +### +### This file holds functions to create CA and Certs +### + -############################################################ -# Functions -############################################################ +# ------------------------------------------------------------------------------------------------- +# ACTION FUNCTIONS +# ------------------------------------------------------------------------------------------------- ### ### Generate CA @@ -15,27 +19,25 @@ set -o pipefail cert_gen_generate_ca() { local key="${1}" local crt="${2}" - local verbose="${3}" - local debug="${4}" # Create directories if [ ! -d "$( dirname "${key}" )" ]; then - run "mkdir -p $( dirname "${key}" )" "${debug}" + run "mkdir -p $( dirname "${key}" )" fi if [ ! -d "$( dirname "${crt}" )" ]; then - run "mkdir -p $( dirname "${crt}" )" "${debug}" - fi - - # cert-gen verbosity - if [ "${verbose}" -gt "0" ]; then - verbose="-v" - else - verbose="" + run "mkdir -p $( dirname "${crt}" )" fi # Generate CA if it does not exist yet if [ ! -f "${key}" ] || [ ! -f "${crt}" ]; then - run "ca-gen ${verbose} -c DE -s Berlin -l Berlin -o Devilbox -u Devilbox -n 'Devilbox Root CA' -e 'cytopia@devilbox.org' ${key} ${crt}" "${DEBUG_LEVEL}" + log "warn" "(Re)creating Certificate Authority. You may need to (re)import it into your browser." + if ! run \ + "ca-gen -v -c DE -s Berlin -l Berlin -o Devilbox -u Devilbox -n 'Devilbox Root CA' -e 'cytopia@devilbox.org' \"${key}\" \"${crt}\"" \ + "Failed to create Certificate Authority."; then + exit 1 + fi + else + log "info" "Existing Certificate Authority files found in: $(dirname "${key}")" fi } @@ -52,8 +54,6 @@ cert_gen_generate_cert() { local csr="${6}" local crt="${7}" local domains="${8}" - local verbose="${9}" - local debug="${10}" # If not enabled, skip SSL certificate eneration if [ "${enable}" != "1" ]; then @@ -67,20 +67,13 @@ cert_gen_generate_cert() { # Create directories if [ ! -d "$( dirname "${key}" )" ]; then - run "mkdir -p $( dirname "${key}" )" "${debug}" + run "mkdir -p $( dirname "${key}" )" fi if [ ! -d "$( dirname "${csr}" )" ]; then - run "mkdir -p $( dirname "${csr}" )" "${debug}" + run "mkdir -p $( dirname "${csr}" )" fi if [ ! -d "$( dirname "${crt}" )" ]; then - run "mkdir -p $( dirname "${crt}" )" "${debug}" - fi - - # cert-gen verbosity - if [ "${verbose}" -gt "0" ]; then - verbose="-v" - else - verbose="" + run "mkdir -p $( dirname "${crt}" )" fi # Get domain name and alt_names @@ -98,5 +91,9 @@ cert_gen_generate_cert() { done alt_names="$( echo "${alt_names}" | xargs )" # tim - run "cert-gen ${verbose} -c DE -s Berlin -l Berlin -o Devilbox -u Devilbox -n '${cn}' -e 'admin@${cn}' -a '${alt_names}' ${ca_key} ${ca_crt} ${key} ${csr} ${crt}" "${debug}" + if ! run \ + "cert-gen -v -c DE -s Berlin -l Berlin -o Devilbox -u Devilbox -n '${cn}' -e 'admin@${cn}' -a '${alt_names}' \"${ca_key}\" \"${ca_crt}\" \"${key}\" \"${csr}\" \"${crt}\"" \ + "Failed to create SSL certificate"; then + exit 1 + fi } diff --git a/Dockerfiles/data/docker-entrypoint.d/14-file-permissions.sh b/Dockerfiles/data/docker-entrypoint.d/14-file-permissions.sh new file mode 100755 index 0000000..90ffbea --- /dev/null +++ b/Dockerfiles/data/docker-entrypoint.d/14-file-permissions.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +### +### This file holds functions to work on file system permissions +### + + +# ------------------------------------------------------------------------------------------------- +# ACTION FUNCTIONS +# ------------------------------------------------------------------------------------------------- + +### +### Fix permissions for MY_USER:MY_GROUP +### +fix_perm() { + local directory="${1}" + local recursive="${2}" + + uid="$( id -u "${MY_USER}" )" + gid="$( id -g "${MY_USER}" )" + + # These are set in the Dockerfile + local perm="${uid}:${gid}" + + if [ "${recursive}" = "1" ]; then + log "info" "Fixing ownership (recursively) in: ${directory}" + run "chown -R ${perm} ${directory}" + else + log "info" "Fixing ownership in: ${directory}" + run "chown ${perm} ${directory}" + fi +} + + +# ------------------------------------------------------------------------------------------------- +# SANITY CHECKS +# ------------------------------------------------------------------------------------------------- + +### +### The following commands are required and used in the current script. +### +if ! command -v id >/dev/null 2>&1; then + log "err" "id not found, but required." + exit 1 +fi diff --git a/Dockerfiles/data/docker-entrypoint.d/15-supervisord.sh b/Dockerfiles/data/docker-entrypoint.d/15-supervisord.sh new file mode 100755 index 0000000..f01f874 --- /dev/null +++ b/Dockerfiles/data/docker-entrypoint.d/15-supervisord.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +### +### This file holds functions to conigure supervisord +### + + +# ------------------------------------------------------------------------------------------------- +# ACTION FUNCTIONS +# ------------------------------------------------------------------------------------------------- + +### +### Create supervisord.conf +### +supervisord_create() { + local httpd_command="${1}" + local watcherd_command="${2}" + local config="${3}" + + if [ -d "$( basename "${config}" )" ]; then + mkdir -p "$( basename "${config}" )" + fi + + # Enable supervisorctl (default: disabled) + SVCTL_ENABLE="${SVCTL_ENABLE:-0}" + SVCTL_LISTEN_ADDR="0.0.0.0" + SVCTL_LISTEN_PORT=9001 + if [ -z "${SVCTL_USER:-}" ]; then + SVCTL_USER="$( get_random_alphanum "10" )" + fi + if [ -z "${SVCTL_PASS:-}" ]; then + SVCTL_PASS="$( get_random_alphanum "10" )" + fi + if [ "${SVCTL_LISTEN_ADDR}" = "0.0.0.0" ] || [ "${SVCTL_LISTEN_ADDR}" = "*" ]; then + SVCTL_CONNECT_ADDR="127.0.0.1" + fi + + # Allow tailing remote logs from supervisord via supervisorctl + SVCTL_REMOTE_LOGS_ENABLE="${SVCTL_REMOTE_LOGS_ENABLE:-0}" + + # This allows for 'supvervisorctl restart watcherd + { + # Use 'echo_supervisord_conf' to generate an example config + if [ "${SVCTL_ENABLE}" = "1" ]; then + echo "[rpcinterface:supervisor]" + echo "supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface" + echo + + echo "[inet_http_server] ; inet (TCP) server disabled by default" + echo "port=${SVCTL_LISTEN_ADDR}:${SVCTL_LISTEN_PORT} ; ip_address:port specifier, *:port for all iface" + echo "username=${SVCTL_USER} ; default is no username (open server)" + echo "password=${SVCTL_PASS} ; default is no password (open server)" + echo + + echo "[supervisorctl]" + echo ";serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket" + echo "serverurl=http://${SVCTL_CONNECT_ADDR}:${SVCTL_LISTEN_PORT} ; http:// url to specify an inet socket" + echo "username=${SVCTL_USER} ; should be same as in [*_http_server] if set" + echo "password=${SVCTL_PASS} ; should be same as in [*_http_server] if set" + echo ";prompt=mysupervisor ; cmd line prompt (default 'supervisor')" + echo ";history_file=~/.sc_history ; use readline history if available" + echo + fi + + echo "[supervisord]" + echo "user=root" + echo "nodaemon=true" + echo "loglevel=warn" + echo "strip_ansi=true" # Required to fix tail logs for watcherd + echo + + echo "[program:httpd]" + echo "command=${httpd_command}" + echo "priority=1" + echo "autostart=true" + echo "startretries=100" + echo "startsecs=1" + echo "autorestart=true" + if [ "${SVCTL_REMOTE_LOGS_ENABLE}" = "1" ]; then + echo "stdout_logfile=/var/log/supervisord-httpd.log" + echo "stderr_logfile=/var/log/supervisord-httpd.err" + else + echo "stdout_logfile=/dev/stdout" + echo "stderr_logfile=/dev/stderr" + fi + echo "stdout_logfile_maxbytes=0" + echo "stderr_logfile_maxbytes=0" + echo "stdout_events_enabled=true" + echo "stderr_events_enabled=true" + echo + + echo "[program:watcherd]" + echo "command=${watcherd_command}" + echo "priority=999" + echo "autostart=true" + echo "autorestart=true" + if [ "${SVCTL_REMOTE_LOGS_ENABLE}" = "1" ]; then + echo "stdout_logfile=/var/log/supervisord-watcherd.log" + echo "stderr_logfile=/var/log/supervisord-watcherd.err" + else + echo "stdout_logfile=/dev/stdout" + echo "stderr_logfile=/dev/stderr" + fi + echo "stdout_logfile_maxbytes=0" + echo "stderr_logfile_maxbytes=0" + echo "stdout_events_enabled=true" + echo "stderr_events_enabled=true" + } > "${config}" +} diff --git a/Dockerfiles/data/docker-entrypoint.d/99-nginx.sh b/Dockerfiles/data/docker-entrypoint.d/99-nginx.sh new file mode 100755 index 0000000..4faf7d6 --- /dev/null +++ b/Dockerfiles/data/docker-entrypoint.d/99-nginx.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + + +############################################################ +# Functions +############################################################ + +### +### Change worker_processes +### +nginx_set_worker_processess() { + local value="${1}" + local config="/etc/nginx/nginx.conf" + + log "info" "Setting Nginx worker_processes to: ${value}" + run "sed -i'' 's/__WORKER_PROCESSES__/${value}/g' ${config}" +} + + +### +### Change worker_connections +### +nginx_set_worker_connections() { + local value="${1}" + local config="/etc/nginx/nginx.conf" + + log "info" "Setting Nginx worker_connections to: ${value}" + run "sed -i'' 's/__WORKER_CONNECTIONS__/${value}/g' ${config}" +} diff --git a/Dockerfiles/data/docker-entrypoint.d/bootstrap/bootstrap.sh b/Dockerfiles/data/docker-entrypoint.d/bootstrap/bootstrap.sh new file mode 100755 index 0000000..07519f0 --- /dev/null +++ b/Dockerfiles/data/docker-entrypoint.d/bootstrap/bootstrap.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +### +### This is the bootstrap file - it must be called first. +### +### It will boostrap the entrypoint (or any other scripts which wants to use +### .http/ and .lib/ scripts. +### +### 1. Bootstrap debug level (set and export) +### 2. Source .lib/ (basic generic functions) +### 3. Source .httpd/ (basic httpd functions) +### + + +# ------------------------------------------------------------------------------------------------- +# BOOTSTRAP DEBUG LEVEL +# ------------------------------------------------------------------------------------------------- + +### +### Default Debug Levels +### +DEFAULT_DEBUG_ENTRYPOINT="2" +DEFAULT_DEBUG_RUNTIME="1" + + +### +### DEBUG_ENTRYPOINT +### +# 1. Not set (gets default value) +if [ -z "${DEBUG_ENTRYPOINT:-}" ]; then + DEBUG_ENTRYPOINT="${DEFAULT_DEBUG_ENTRYPOINT}" +fi +# 2. Not an integer (gets default value) +if [ -z "${DEBUG_ENTRYPOINT##*[!0-9]*}" ]; then + DEBUG_ENTRYPOINT="${DEFAULT_DEBUG_ENTRYPOINT}" +fi +# 3. Not between 0 and 4 (gets highest value) +if [ "${DEBUG_ENTRYPOINT}" != "0" ] \ + && [ "${DEBUG_ENTRYPOINT}" != "1" ] \ + && [ "${DEBUG_ENTRYPOINT}" != "2" ] \ + && [ "${DEBUG_ENTRYPOINT}" != "3" ] \ + && [ "${DEBUG_ENTRYPOINT}" != "4" ]; then + DEBUG_ENTRYPOINT=4 +fi + +### +### DEBUG_RUNTIME +### +# 1. Not set (gets default value) +if [ -z "${DEBUG_RUNTIME:-}" ]; then + DEBUG_RUNTIME="${DEFAULT_DEBUG_RUNTIME}" +fi +# 2. Not an integer (gets default value) +if [ -z "${DEBUG_RUNTIME##*[!0-9]*}" ]; then + DEBUG_RUNTIME="${DEFAULT_DEBUG_RUNTIME}" +fi +# 3. Not between 0 and 4 (gets highest value) +if [ "${DEBUG_RUNTIME}" != "0" ] \ + && [ "${DEBUG_RUNTIME}" != "1" ] \ + && [ "${DEBUG_RUNTIME}" != "2" ]; then + DEBUG_RUNTIME=2 +fi + +### +### Export +### +export "DEBUG_ENTRYPOINT" +export "DEBUG_RUNTIME" + + + +# ------------------------------------------------------------------------------------------------- +# SOURCE LIBRARIES +# ------------------------------------------------------------------------------------------------- + +### +### Full path to this script +### +SCRIPTPATH="$( cd "$(dirname "${BASH_SOURCE[0]}")" && pwd )" + +### +### Source available library functions +### +# shellcheck disable=SC2012 +for f in $( ls -1 "${SCRIPTPATH}/../.lib/"*.sh | sort -u ); do + # shellcheck disable=SC1090 + . "${f}" +done + +### +### Source available HTTPD functions +### +# shellcheck disable=SC2012 +for f in $( ls -1 "${SCRIPTPATH}/../.httpd/"*.sh | sort -u ); do + # shellcheck disable=SC1090 + . "${f}" +done diff --git a/Dockerfiles/data/docker-entrypoint.sh b/Dockerfiles/data/docker-entrypoint.sh index 2da4217..d3f00d0 100755 --- a/Dockerfiles/data/docker-entrypoint.sh +++ b/Dockerfiles/data/docker-entrypoint.sh @@ -4,257 +4,368 @@ set -e set -u set -o pipefail +################################################################################################### +################################################################################################### ### -### Globals +### GLOBAL VARIABLES ### +################################################################################################### +################################################################################################### -# Set via Dockerfile -# MY_USER -# MY_GROUP -# HTTPD_START -# HTTPD_RELOAD - -# OpenSSL Certificate Authority file to generate -CA_KEY=/ca/devilbox-ca.key -CA_CRT=/ca/devilbox-ca.crt - - -# Path to scripts to source -CONFIG_DIR="/docker-entrypoint.d" -VHOST_GEN_DIR="/etc/vhost-gen/templates" -VHOST_GEN_CUST_DIR="/etc/vhost-gen.d" - - -# Wait this many seconds to start watcherd after httpd has been started -WATCHERD_STARTUP_DELAY="3" +### The following env variables are set inside the Dockerfiles +### MY_USER +### MY_GROUP +### HTTPD_START +### HTTPD_RELOAD +### VHOSTGEN_HTTPD_SERVER # 'nginx', 'apache22' or 'apache24' +### +### Can be any of 'nginx', 'apache22' or 'apache24' +### +# VHOSTGEN_HTTPD_SERVER is set via Dockerfile +VHOSTGEN_HTTPD_SERVER_TEMPLATE="${VHOSTGEN_HTTPD_SERVER}.yml" ### -### Source libs +### Base path for main (default) document root ### -init="$( find "${CONFIG_DIR}" -name '*.sh' -type f | sort -u )" -for f in ${init}; do - # shellcheck disable=SC1090 - . "${f}" -done +MAIN_DOCROOT_BASE="/var/www/default" +MASS_DOCROOT_BASE="/shared/httpd" +### +### OpenSSL Certificate Authority file to generate +### +### If the /ca directory is mounted and those files already exist +### a new ca will not be generated, but reused. +### +CA_KEY_FILE=/ca/devilbox-ca.key +CA_CRT_FILE=/ca/devilbox-ca.crt +### +### Path to scripts to source +### +ENTRYPOINT_DIR="/docker-entrypoint.d" # All entrypoint scripts +VHOSTGEN_TEMPLATE_DIR="/etc/vhost-gen/templates" # vhost-gen default templates +VHOSTGEN_CUST_TEMPLATE_DIR="/etc/vhost-gen.d" # vhost-gen custom templates (must be mounted to add) -############################################################# -## Basic Settings -############################################################# +### +### Defailt aliases copied from previous images, just for the record +### +#MAIN_VHOST_ALIASES_ALLOW='/devilbox-api/:/var/www/default/api, /vhost.d/:/etc/httpd' +#MASS_VHOST_ALIASES_ALLOW='/devilbox-api/:/var/www/default/api:http(s)?://(.*)$' ### -### Set Debug level +### Wait this many seconds to start watcherd after httpd has been started ### -DEBUG_LEVEL="$( env_get "DEBUG_ENTRYPOINT" "0" )" -log "info" "Debug level: ${DEBUG_LEVEL}" "${DEBUG_LEVEL}" +WATCHERD_STARTUP_DELAY="3" -DEBUG_RUNTIME="$( env_get "DEBUG_RUNTIME" "0" )" -log "info" "Runtime debug: ${DEBUG_RUNTIME}" "${DEBUG_LEVEL}" +################################################################################################### +################################################################################################### ### -### Change uid/gid +### INCLUDES ### -set_uid "NEW_UID" "${MY_USER}" "${DEBUG_LEVEL}" -set_gid "NEW_GID" "${MY_GROUP}" "${DEBUG_LEVEL}" - +################################################################################################### +################################################################################################### ### -### Set timezone +### Bootstrap ### -set_timezone "TIMEZONE" "${DEBUG_LEVEL}" - +# shellcheck disable=SC1090,SC1091 +. "${ENTRYPOINT_DIR}/bootstrap/bootstrap.sh" -############################################################# -## Variable exports -############################################################# ### -### Ensure Docker_LOGS is exported +### Source available entrypoint scripts ### -export_docker_logs "DOCKER_LOGS" "${DEBUG_LEVEL}" +# shellcheck disable=SC2012 +for f in $( ls -1 "${ENTRYPOINT_DIR}/"*.sh | sort -u ); do + # shellcheck disable=SC1090 + . "${f}" +done -### -### Ensure PHP-FPM variables are exported -### -export_php_fpm_enable "PHP_FPM_ENABLE" "${DEBUG_LEVEL}" -export_php_fpm_server_addr "PHP_FPM_SERVER_ADDR" "${DEBUG_LEVEL}" -export_php_fpm_server_port "PHP_FPM_SERVER_PORT" "${DEBUG_LEVEL}" -export_php_fpm_timeout "PHP_FPM_TIMEOUT" "${DEBUG_LEVEL}" +################################################################################################### +################################################################################################### ### -### Ensure global main/mass variables are eported +### MAIN ENTRYPOINT ### -export_http2_enable "HTTP2_ENABLE" "${DEBUG_LEVEL}" +################################################################################################### +################################################################################################### +# ------------------------------------------------------------------------------------------------- +# SET ENVIRONMENT VARIABLES AND DEFAULT VALUES +# ------------------------------------------------------------------------------------------------- ### -### Ensure MAIN_VHOST variables are exported +### Show Debug level ### -export_main_vhost_enable "MAIN_VHOST_ENABLE" "${DEBUG_LEVEL}" -export_main_vhost_ssl_type "MAIN_VHOST_SSL_TYPE" "${DEBUG_LEVEL}" -export_main_vhost_ssl_gen "MAIN_VHOST_SSL_GEN" "${DEBUG_LEVEL}" -export_main_vhost_ssl_cn "MAIN_VHOST_SSL_CN" "${DEBUG_LEVEL}" -export_main_vhost_docroot "MAIN_VHOST_DOCROOT" "${DEBUG_LEVEL}" -export_main_vhost_tpl "MAIN_VHOST_TPL" "${DEBUG_LEVEL}" -export_main_vhost_status_enable "MAIN_VHOST_STATUS_ENABLE" "${DEBUG_LEVEL}" -export_main_vhost_status_alias "MAIN_VHOST_STATUS_ALIAS" "${DEBUG_LEVEL}" +log "info" "Entrypoint debug: $( env_get "DEBUG_ENTRYPOINT" )" +log "info" "Runtime debug: $( env_get "DEBUG_RUNTIME" )" ### -### Ensure MASS_VHOST variables are exported +### Show environment vars ### -export_mass_vhost_enable "MASS_VHOST_ENABLE" "${DEBUG_LEVEL}" -export_mass_vhost_ssl_type "MASS_VHOST_SSL_TYPE" "${DEBUG_LEVEL}" -export_mass_vhost_ssl_gen "MASS_VHOST_SSL_GEN" "${DEBUG_LEVEL}" -export_mass_vhost_tld "MASS_VHOST_TLD" "${DEBUG_LEVEL}" -export_mass_vhost_docroot "MASS_VHOST_DOCROOT" "${DEBUG_LEVEL}" -export_mass_vhost_tpl "MASS_VHOST_TPL" "${DEBUG_LEVEL}" +log "info" "-------------------------------------------------------------------------" +log "info" "Environment Variables (set/default)" +log "info" "-------------------------------------------------------------------------" +log "info" "Variables: General:" +env_var_export "NEW_UID" +env_var_export "NEW_GID" +env_var_export "TIMEZONE" "UTC" -### -### Default and/or mass vhost must be enabled (at least one of them) -### -if [ "${MAIN_VHOST_ENABLE}" -eq "0" ] && [ "${MASS_VHOST_ENABLE}" -eq "0" ]; then - log "err" "Default vhost and mass vhosts are disabled." "${DEBUG_LEVEL}" - exit 1 -fi +log "info" "Variables: Main Vhost:" +env_var_export "MAIN_VHOST_ENABLE" "1" +env_var_export "MAIN_VHOST_DOCROOT_DIR" "htdocs" +env_var_export "MAIN_VHOST_TEMPLATE_DIR" "cfg" +env_var_export "MAIN_VHOST_ALIASES_ALLOW" "" +env_var_export "MAIN_VHOST_ALIASES_DENY" '/\.git, /\.ht.*' +env_var_export "MAIN_VHOST_BACKEND" +env_var_export "MAIN_VHOST_BACKEND_TIMEOUT" "180" +env_var_export "MAIN_VHOST_SSL_TYPE" "plain" +env_var_export "MAIN_VHOST_SSL_CN" "localhost" +env_var_export "MAIN_VHOST_STATUS_ENABLE" "0" +env_var_export "MAIN_VHOST_STATUS_ALIAS" "/httpd-status" + +log "info" "Variables: Mass Vhost:" +env_var_export "MASS_VHOST_ENABLE" "0" +env_var_export "MASS_VHOST_DOCROOT_DIR" "htdocs" +env_var_export "MASS_VHOST_TEMPLATE_DIR" "cfg" +env_var_export "MASS_VHOST_ALIASES_ALLOW" "" +env_var_export "MASS_VHOST_ALIASES_DENY" '/\.git, /\.ht.*' +env_var_export "MASS_VHOST_BACKEND" +env_var_export "MASS_VHOST_BACKEND_REWRITE" +env_var_export "MASS_VHOST_BACKEND_TIMEOUT" "180" +env_var_export "MASS_VHOST_SSL_TYPE" "plain" +env_var_export "MASS_VHOST_TLD_SUFFIX" ".loc" +log "info" "Variables: Misc:" +if [ "${VHOSTGEN_HTTPD_SERVER}" = "nginx" ]; then + env_var_export "WORKER_CONNECTIONS" "1024" + env_var_export "WORKER_PROCESSES" "auto" +fi +# Apache 2.2 does not have HTTP/2 support +if [ "${VHOSTGEN_HTTPD_SERVER}" != "apache22" ]; then + env_var_export "HTTP2_ENABLE" "1" +else + export HTTP2_ENABLE=0 +fi +env_var_export "DOCKER_LOGS" "1" + + + +# ------------------------------------------------------------------------------------------------- +# VERIFY ENVIRONMENT VARIABLES +# ------------------------------------------------------------------------------------------------- + +log "info" "-------------------------------------------------------------------------" +log "info" "Validate Settings" +log "info" "-------------------------------------------------------------------------" + +log "info" "Settings: General:" +env_var_validate "NEW_UID" +env_var_validate "NEW_GID" +env_var_validate "TIMEZONE" + +log "info" "Settings: Main Vhost:" +env_var_validate "MAIN_VHOST_ENABLE" +env_var_validate "MAIN_VHOST_DOCROOT_DIR" +env_var_validate "MAIN_VHOST_TEMPLATE_DIR" +env_var_validate "MAIN_VHOST_ALIASES_ALLOW" +env_var_validate "MAIN_VHOST_ALIASES_DENY" +env_var_validate "MAIN_VHOST_BACKEND" +env_var_validate "MAIN_VHOST_BACKEND_TIMEOUT" +env_var_validate "MAIN_VHOST_SSL_TYPE" +env_var_validate "MAIN_VHOST_SSL_CN" +env_var_validate "MAIN_VHOST_STATUS_ENABLE" +env_var_validate "MAIN_VHOST_STATUS_ALIAS" + +log "info" "Settings: Mass Vhost:" +env_var_validate "MASS_VHOST_ENABLE" +env_var_validate "MASS_VHOST_DOCROOT_DIR" +env_var_validate "MASS_VHOST_TEMPLATE_DIR" +env_var_validate "MASS_VHOST_ALIASES_ALLOW" +env_var_validate "MASS_VHOST_ALIASES_DENY" +env_var_validate "MASS_VHOST_BACKEND" +env_var_validate "MASS_VHOST_BACKEND_REWRITE" +env_var_validate "MASS_VHOST_BACKEND_TIMEOUT" +env_var_validate "MASS_VHOST_SSL_TYPE" +env_var_validate "MASS_VHOST_TLD_SUFFIX" + +log "info" "Settings: Misc:" +if [ "${VHOSTGEN_HTTPD_SERVER}" = "nginx" ]; then + env_var_validate "WORKER_CONNECTIONS" + env_var_validate "WORKER_PROCESSES" +fi +# Apache 2.2 does not have HTTP/2 support +if [ "${VHOSTGEN_HTTPD_SERVER}" != "apache22" ]; then + env_var_validate "HTTP2_ENABLE" +fi +env_var_validate "DOCKER_LOGS" -############################################################# -## vhost-gen Configuration -############################################################# -### -### Copy custom vhost-gen template -### -vhost_gen_copy_custom_template "${VHOST_GEN_CUST_DIR}" "${VHOST_GEN_DIR}" "apache22.yml" "${DEBUG_LEVEL}" +# ------------------------------------------------------------------------------------------------- +# APPLY SETTINGS +# ------------------------------------------------------------------------------------------------- +log "info" "-------------------------------------------------------------------------" +log "info" "Apply Settings" +log "info" "-------------------------------------------------------------------------" ### -### Enable and configure PHP-FPM +### Change uid/gid ### -vhost_gen_php_fpm "${PHP_FPM_ENABLE}" "${PHP_FPM_SERVER_ADDR}" "${PHP_FPM_SERVER_PORT}" "${PHP_FPM_TIMEOUT}" "/etc/vhost-gen/main.yml" "${DEBUG_LEVEL}" -vhost_gen_php_fpm "${PHP_FPM_ENABLE}" "${PHP_FPM_SERVER_ADDR}" "${PHP_FPM_SERVER_PORT}" "${PHP_FPM_TIMEOUT}" "/etc/vhost-gen/mass.yml" "${DEBUG_LEVEL}" - +set_uid "${NEW_UID}" "${MY_USER}" +set_gid "${NEW_GID}" "${MY_USER}" "${MY_GROUP}" ### -### Configure Docker logs +### Set timezone ### -vhost_gen_docker_logs "${DOCKER_LOGS}" "/etc/vhost-gen/main.yml" "${DEBUG_LEVEL}" -vhost_gen_docker_logs "${DOCKER_LOGS}" "/etc/vhost-gen/mass.yml" "${DEBUG_LEVEL}" - +set_timezone "${TIMEZONE}" ### -### Set HTTP2 support +### Copy custom user-mounted vhost-gen template (if they are mounted and exist) ### -vhost_gen_http2 "${HTTP2_ENABLE}" "/etc/vhost-gen/main.yml" "${DEBUG_LEVEL}" -vhost_gen_http2 "${HTTP2_ENABLE}" "/etc/vhost-gen/mass.yml" "${DEBUG_LEVEL}" - +vhostgen_copy_custom_template \ + "${VHOSTGEN_CUST_TEMPLATE_DIR}" \ + "${VHOSTGEN_TEMPLATE_DIR}" \ + "${VHOSTGEN_HTTPD_SERVER_TEMPLATE}" ### -### Main vhost settings +### Generate vhost-gen config file (MAIN_VHOST) ### -vhost_gen_main_vhost_httpd_status \ +vhostgen_main_generate_config \ + "${VHOSTGEN_HTTPD_SERVER}" \ + "${MAIN_VHOST_BACKEND}" \ + "${HTTP2_ENABLE}" \ + "${MAIN_VHOST_ALIASES_ALLOW}" \ + "${MAIN_VHOST_ALIASES_DENY}" \ "${MAIN_VHOST_STATUS_ENABLE}" \ "${MAIN_VHOST_STATUS_ALIAS}" \ - "/etc/vhost-gen/main.yml" \ - "${DEBUG_LEVEL}" - -vhost_gen_generate_main_vhost \ - "${MAIN_VHOST_ENABLE}" \ - "/var/www/default/${MAIN_VHOST_DOCROOT}" \ - "/etc/vhost-gen/main.yml" \ - "/var/www/default/${MAIN_VHOST_TPL}" \ - "${MAIN_VHOST_SSL_TYPE}" \ - "${DEBUG_RUNTIME}" \ - "${DEBUG_LEVEL}" - - -### -### Mass vhost settings -### -vhost_gen_mass_vhost_docroot \ - "${MASS_VHOST_ENABLE}" \ - "${MASS_VHOST_DOCROOT}" \ - "/etc/vhost-gen/mass.yml" \ - "${DEBUG_LEVEL}" - -vhost_gen_mass_vhost_tld \ - "${MASS_VHOST_ENABLE}" \ - "${MASS_VHOST_TLD}" \ - "/etc/vhost-gen/mass.yml" \ - "${DEBUG_LEVEL}" - - - -################################################################################ -# cert-getn Configuration -################################################################################ + "${DOCKER_LOGS}" \ + "${MAIN_VHOST_BACKEND_TIMEOUT}" \ + "/etc/vhost-gen/main.yml" + +### +### Generate vhost (MAIN_VHOST) +### +if [ "${VHOSTGEN_HTTPD_SERVER}" = "nginx" ]; then + # Adding custom nginx vhost template to ensure paths like: + # /vendor/index.php/arg1/arg2 will also work (just like Apache) + # https://www.reddit.com/r/nginx/comments/a6pw31/phpfpm_does_not_handle_subpathindexphparg1arg2/ + vhostgen_main_generate \ + "${MAIN_VHOST_ENABLE}" \ + "${MAIN_DOCROOT_BASE}/${MAIN_VHOST_DOCROOT_DIR}" \ + "${MAIN_VHOST_BACKEND}" \ + "/etc/vhost-gen/main.yml" \ + "${MAIN_DOCROOT_BASE}/${MAIN_VHOST_TEMPLATE_DIR}" \ + "${MAIN_VHOST_SSL_TYPE}" \ + "/etc/vhost-gen/templates-main/" +else + vhostgen_main_generate \ + "${MAIN_VHOST_ENABLE}" \ + "${MAIN_DOCROOT_BASE}/${MAIN_VHOST_DOCROOT_DIR}" \ + "${MAIN_VHOST_BACKEND}" \ + "/etc/vhost-gen/main.yml" \ + "${MAIN_DOCROOT_BASE}/${MAIN_VHOST_TEMPLATE_DIR}" \ + "${MAIN_VHOST_SSL_TYPE}" +fi ### ### Create Certificate Signing request ### -cert_gen_generate_ca "${CA_KEY}" "${CA_CRT}" "${DEBUG_RUNTIME}" "${DEBUG_LEVEL}" - +cert_gen_generate_ca "${CA_KEY_FILE}" "${CA_CRT_FILE}" ### -### Generate main vhost ssl certificate +### Generate main vhost ssl certificate (MAIN_VHOST) ### +# shellcheck disable=SC2153 cert_gen_generate_cert \ "${MAIN_VHOST_ENABLE}" \ "${MAIN_VHOST_SSL_TYPE}" \ - "${CA_KEY}" \ - "${CA_CRT}" \ + "${CA_KEY_FILE}" \ + "${CA_CRT_FILE}" \ "/etc/httpd/cert/main/localhost.key" \ "/etc/httpd/cert/main/localhost.csr" \ "/etc/httpd/cert/main/localhost.crt" \ - "${MAIN_VHOST_SSL_CN}" \ - "${DEBUG_RUNTIME}" \ - "${DEBUG_LEVEL}" + "${MAIN_VHOST_SSL_CN}" + +### +### Fix CA directory/file permissions (in case it is mounted) +### +fix_perm "/ca" "1" + + +# ------------------------------------------------------------------------------------------------- +# NGINX-SPECIFIC BASIC SETTINGS +# ------------------------------------------------------------------------------------------------- +### +### Nginx settings +### +if [ "${VHOSTGEN_HTTPD_SERVER}" = "nginx" ]; then + nginx_set_worker_processess "${WORKER_PROCESSES}" + nginx_set_worker_connections "${WORKER_CONNECTIONS}" +fi -################################################################################ -# Fix directory permissions -################################################################################ -fix_perm "NEW_UID" "NEW_GID" "/ca" "1" "${DEBUG_LEVEL}" +# ------------------------------------------------------------------------------------------------- +# MAIN ENTRYPOINT +# ------------------------------------------------------------------------------------------------- +# shellcheck disable=SC2153 +_HTTPD_VERSION="$( eval "${HTTPD_VERSION}" || true )" # Set via Dockerfile +_SUPVD_VERSION="$( supervisord -v )" -################################################################################ -# RUN -################################################################################ +log "info" "-------------------------------------------------------------------------" +log "info" "Main Entrypoint" +log "info" "-------------------------------------------------------------------------" ### -### Supervisor or plain +### MASS_VHOST requires supervisor to run (watcherd) ### if [ "${MASS_VHOST_ENABLE}" -eq "1" ]; then - - verbose="" - if [ "${DEBUG_RUNTIME}" -gt "0" ]; then - verbose="-v" - fi - # Create watcherd sub commands - watcherd_add="create-vhost.sh '%%p' '%%n' '${MASS_VHOST_TLD}' '%%p/${MASS_VHOST_TPL}/' '${CA_KEY}' '${CA_CRT}' '${MASS_VHOST_SSL_GEN}' '${MASS_VHOST_SSL_TYPE}' '${verbose}'" + watcherd_add="create-vhost.sh" + watcherd_add+=" \\\"%%n\\\"" # vhost project directory name + watcherd_add+=" \\\"%%p\\\"" # vhost project directory path (absolute) + watcherd_add+=" \\\"${MASS_VHOST_DOCROOT_DIR}\\\"" + watcherd_add+=" \\\"${MASS_VHOST_TLD_SUFFIX}\\\"" + watcherd_add+=" \\\"${MASS_VHOST_ALIASES_ALLOW}\\\"" + watcherd_add+=" \\\"${MASS_VHOST_ALIASES_DENY}\\\"" + watcherd_add+=" \\\"${MASS_VHOST_SSL_TYPE}\\\"" + watcherd_add+=" \\\"${MASS_VHOST_BACKEND}\\\"" + watcherd_add+=" \\\"${MASS_VHOST_BACKEND_REWRITE}\\\"" + watcherd_add+=" \\\"${MASS_VHOST_BACKEND_TIMEOUT}\\\"" + watcherd_add+=" \\\"${HTTP2_ENABLE}\\\"" + watcherd_add+=" \\\"${DOCKER_LOGS}\\\"" + watcherd_add+=" \\\"${CA_KEY_FILE}\\\"" + watcherd_add+=" \\\"${CA_CRT_FILE}\\\"" + watcherd_add+=" \\\"%%p/${MASS_VHOST_TEMPLATE_DIR}/\\\"" + watcherd_add+=" \\\"${VHOSTGEN_HTTPD_SERVER}\\\"" + watcherd_del="rm /etc/httpd/vhost.d/%%n.conf" watcherd_tri="${HTTPD_RELOAD}" supervisord_create \ "${HTTPD_START}" \ - "bash -c 'sleep ${WATCHERD_STARTUP_DELAY} && exec watcherd -v -p /shared/httpd -a \"${watcherd_add}\" -d \"${watcherd_del}\" -t \"${watcherd_tri}\"'" \ + "bash -c 'sleep ${WATCHERD_STARTUP_DELAY} && exec watcherd -c -v -p ${MASS_DOCROOT_BASE} -a \"${watcherd_add}\" -d \"${watcherd_del}\" -t \"${watcherd_tri}\"'" \ "/etc/supervisord.conf" - log "info" "Starting supervisord: $(supervisord -v)" "${DEBUG_LEVEL}" + log "done" "Starting supervisord: ${_SUPVD_VERSION} [HTTPD: ${_HTTPD_VERSION}]" exec /usr/bin/supervisord -c /etc/supervisord.conf + +### +### No MASS_VHOST: just start HTTPD as process 1. +### else - log "info" "Starting webserver" "${DEBUG_LEVEL}" - exec ${HTTPD_START} + log "done" "Starting webserver: ${_HTTPD_VERSION}" + exec "${HTTPD_START}" fi diff --git a/Dockerfiles/data/nginx/nginx.conf b/Dockerfiles/data/nginx/nginx.conf new file mode 100644 index 0000000..720c677 --- /dev/null +++ b/Dockerfiles/data/nginx/nginx.conf @@ -0,0 +1,88 @@ +# ================================================================================================= +# MAIN SETTINGS +# ================================================================================================= + +worker_processes __WORKER_PROCESSES__; + +user nginx; +daemon off; + +error_log /var/log/httpd/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections __WORKER_CONNECTIONS__; +} + + +# ================================================================================================= +# HTTPS SETTINGS +# ================================================================================================= + +http { + + # ------------------------------------------------------------------------------- + # NGINX DEFAULTS + # ------------------------------------------------------------------------------- + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/httpd/access.log main; + + # [emerg] could not build server_names_hash, you should increase server_names_hash_bucket_size: 32 + # https://stackoverflow.com/questions/26357487/ + server_names_hash_bucket_size 64; + + + # ------------------------------------------------------------------------------- + # NGINX HEADER + # ------------------------------------------------------------------------------- + + # Set valid Via header: (/) + map $server_protocol $my_proto { + "~^HTTP\/(?[.0-9]+)$" $version; + default $server_protocol; + } + add_header Via '$my_proto $hostname (nginx/$nginx_version)' always; + + + # ------------------------------------------------------------------------------- + # NGINX PERFORMANCE + # ------------------------------------------------------------------------------- + + sendfile on; + #tcp_nopush on; + #aio on; + #gzip on; + + + # ------------------------------------------------------------------------------- + # BUFFER SIZES + # ------------------------------------------------------------------------------- + + # The maximum allowed size for a client request. If the maximum size is exceeded, + # then Nginx will spit out a 413 error or Request Entity Too Large. + # Setting size to 0 disables checking of client request body size. + client_max_body_size 0; + + + # ------------------------------------------------------------------------------- + # TIMEOUTS + # ------------------------------------------------------------------------------- + + keepalive_timeout 65; + + + # ------------------------------------------------------------------------------- + # INCLUDES + # ------------------------------------------------------------------------------- + + include /etc/httpd-custom.d/*.conf; + include /etc/httpd/conf.d/*.conf; + include /etc/httpd/vhost.d/*.conf; +} diff --git a/Dockerfiles/data/vhost-gen/main.yml b/Dockerfiles/data/vhost-gen/main.yml deleted file mode 100644 index e9f1aa4..0000000 --- a/Dockerfiles/data/vhost-gen/main.yml +++ /dev/null @@ -1,162 +0,0 @@ ---- -# Generic vhost generator configuration file. -# Location: /etc/vhost-gen/main.yml -# -# See: https://github.com/devilbox/vhost-gen -# -# If not specified or file is missing the following -# default values will be merged to your current (if any) -# configuration: -# -# server: nginx -# conf_dir: /etc/nginx/conf.d -# custom: -# vhost: -# port: -# name: -# prefix: -# suffix: -# docroot: -# suffix: -# log: -# access: -# prefix: -# stdout: no -# error: -# prefix: -# stderr: no -# dir: -# create: no -# path: /var/log/nginx -# listen: -# enable: no -# php_fpm: -# enable: no -# address: php -# port: 9000 -# alias: [] -# deny: [] -# server_status: -# enable: no -# alias: /server-status - - -# The server type determines which template -# from etc/templates/ will be chosen. -# Allowed server types: -# server: apache22 -# server: apache24 -# server: nginx -server: apache22 - - -# Where to store the generated configuration files. -# This must be a directory the web server will read -# configuration files from. -conf_dir: /etc/httpd/conf.d - - -# Custom directive -# Everything specified here will be directly replaced -# into the corresponding vhost directive: -# nginx: server { HERE } -# apache: HERE -# -# How to add multiline strings? -# -# custom: | -# custom statement 1 -# custom statement 2 -custom: - - -# Vhost definition -vhost: - # What port should this virtual host listen on - port: 80 - ssl_port: 443 - - # The virtual host name is specified as an command line argument - # to vhost-gen via '-n', however it is possible - # to prepend and/or append additional name strings. - name: - prefix: - suffix: - # The document root directory is specified as an command line argument - # to vhost-gen via '-p', however it is possible - # to prepend another subdirectory here. - docroot: - suffix: - # Array of indecies to serve as default files (e.g.: index.php, index.html, etc) - index: - - index.php - - index.html - - index.htm - # SSL Definition - ssl: - http2: __HTTP2_ENABLE__ - dir_crt: /etc/httpd/cert/main - dir_key: /etc/httpd/cert/main - protocols: 'TLSv1 TLSv1.1 TLSv1.2' - honor_cipher_order: 'on' - ciphers: 'HIGH:!aNULL:!MD5' - - # Log definition - log: - # Log file settings (error/access log) - access: - # By default the vhost name is used for log file names. - # You can also prepand an additional string to the access log - # as shown here: - # -access.log - prefix: 'default' - # For use inside a docker container, enable this in order - # to redirect the access log to stdout instead of to file. - # NOTE: When enabling this, the prefix will have no effect and the access - # log will be stored under /tmp/www-access.log which will be a symlink of - # /dev/stdout - stdout: __DOCKER_LOGS_ACCESS__ - error: - # By default the vhost name is used for log file names. - # You can also prepand an additional string to the error log - # as shown here: - # -error.log - prefix: 'default' - # For use inside a docker container, enable this in order - # to redirect the error log to stderr instead of to file. - # NOTE: When enabling this, the prefix will have no effect and the error - # log will be stored under /tmp/www-error.log which will be a symlink of - # /dev/stderr - stderr: __DOCKER_LOGS_ERROR__ - # Directory to store log files in. - # Also define if the directory should be created or not. - dir: - create: yes - path: /var/log/apache-2.2 - # Enable PHP-FPM - php_fpm: - enable: __PHP_ENABLE__ - # Hostname or IP address - address: __PHP_ADDR__ - port: __PHP_PORT__ - # Timeout to upstream FPM service - timeout: __PHP_TIMEOUT__ - # Create additional aliases - alias: - - alias: /devilbox-api/ - path: /var/www/default/api - # Allow cross-domain-request to this alias from the hosts/origin - # specified by the below defined regex - xdomain_request: - enable: no - origin: 'http(s)?://(.*)$' - - alias: /vhost.d/ - path: /etc/httpd - # Denies locations - deny: - - alias: '/\.git' - - alias: '/\.ht.*' - # Enable server status on the following alias - server_status: - enable: __ENABLE_STATUS__ - alias: __STATUS_ALIAS__ diff --git a/Dockerfiles/data/vhost-gen/mass.yml b/Dockerfiles/data/vhost-gen/mass.yml deleted file mode 100644 index 03075a0..0000000 --- a/Dockerfiles/data/vhost-gen/mass.yml +++ /dev/null @@ -1,160 +0,0 @@ ---- -# Generic vhost generator configuration file. -# Location: /etc/vhost-gen/mass.yml -# -# See: https://github.com/devilbox/vhost-gen -# -# If not specified or file is missing the following -# default values will be merged to your current (if any) -# configuration: -# -# server: nginx -# conf_dir: /etc/nginx/conf.d -# custom: -# vhost: -# port: -# name: -# prefix: -# suffix: -# docroot: -# suffix: -# log: -# access: -# prefix: -# stdout: no -# error: -# prefix: -# stderr: no -# dir: -# create: no -# path: /var/log/nginx -# listen: -# enable: no -# php_fpm: -# enable: no -# address: php -# port: 9000 -# alias: [] -# deny: [] -# server_status: -# enable: no -# alias: /server-status - - -# The server type determines which template -# from etc/templates/ will be chosen. -# Allowed server types: -# server: apache22 -# server: apache24 -# server: nginx -server: apache22 - - -# Where to store the generated configuration files. -# This must be a directory the web server will read -# configuration files from. -conf_dir: /etc/httpd/vhost.d - - -# Custom directive -# Everything specified here will be directly replaced -# into the corresponding vhost directive: -# nginx: server { HERE } -# apache: HERE -# -# How to add multiline strings? -# -# custom: | -# custom statement 1 -# custom statement 2 -custom: - - -# Vhost definition -vhost: - # What port should this virtual host listen on - port: 80 - ssl_port: 443 - - # The virtual host name is specified as an command line argument - # to vhost-gen via '-n', however it is possible - # to prepend and/or append additional name strings. - name: - prefix: - suffix: __TLD__ - # The document root directory is specified as an command line argument - # to vhost-gen via '-p', however it is possible - # to prepend another subdirectory here. - docroot: - suffix: __DOCROOT_SUFFIX__ - # Array of indecies to serve as default files (e.g.: index.php, index.html, etc) - index: - - index.php - - index.html - - index.htm - # SSL Definition - ssl: - http2: __HTTP2_ENABLE__ - dir_crt: /etc/httpd/cert/mass - dir_key: /etc/httpd/cert/mass - protocols: 'TLSv1 TLSv1.1 TLSv1.2' - honor_cipher_order: 'on' - ciphers: 'HIGH:!aNULL:!MD5' - - # Log definition - log: - # Log file settings (error/access log) - access: - # By default the vhost name is used for log file names. - # You can also prepand an additional string to the access log - # as shown here: - # -access.log - prefix: '' - # For use inside a docker container, enable this in order - # to redirect the access log to stdout instead of to file. - # NOTE: When enabling this, the prefix will have no effect and the access - # log will be stored under /tmp/www-access.log which will be a symlink of - # /dev/stdout - stdout: __DOCKER_LOGS_ACCESS__ - error: - # By default the vhost name is used for log file names. - # You can also prepand an additional string to the error log - # as shown here: - # -error.log - prefix: '' - # For use inside a docker container, enable this in order - # to redirect the error log to stderr instead of to file. - # NOTE: When enabling this, the prefix will have no effect and the error - # log will be stored under /tmp/www-error.log which will be a symlink of - # /dev/stderr - stderr: __DOCKER_LOGS_ERROR__ - # Directory to store log files in. - # Also define if the directory should be created or not. - dir: - create: yes - path: /var/log/apache-2.2 - # Enable PHP-FPM - php_fpm: - enable: __PHP_ENABLE__ - # Hostname or IP address - address: __PHP_ADDR__ - port: __PHP_PORT__ - # Timeout to upstream FPM service - timeout: __PHP_TIMEOUT__ - # Create additional aliases - alias: - - alias: /devilbox-api/ - path: /var/www/default/api - # Allow cross-domain-request to this alias from the hosts/origin - # specified by the below defined regex - xdomain_request: - enable: yes - origin: 'http(s)?://(.*)$' - # Denies locations - deny: - - alias: '/\.git' - - alias: '/\.ht.*' - # Enable server status on the following alias - server_status: - enable: no - alias: /httpd-status diff --git a/Dockerfiles/data/vhost-gen/templates-main/apache22.yml b/Dockerfiles/data/vhost-gen/templates-main/apache22.yml new file mode 100644 index 0000000..e69de29 diff --git a/Dockerfiles/data/vhost-gen/templates-main/apache24.yml b/Dockerfiles/data/vhost-gen/templates-main/apache24.yml new file mode 100644 index 0000000..e69de29 diff --git a/Dockerfiles/data/vhost-gen/templates-main/nginx.yml b/Dockerfiles/data/vhost-gen/templates-main/nginx.yml new file mode 100644 index 0000000..8d5f50e --- /dev/null +++ b/Dockerfiles/data/vhost-gen/templates-main/nginx.yml @@ -0,0 +1,173 @@ +--- + +# +# Slightly modified version of the nginx template for mass vhost +# This mitigates the following issue: +# https://www.reddit.com/r/nginx/comments/a6pw31/phpfpm_does_not_handle_subpathindexphparg1arg2/ +# +# Search this file for 'CHANGED:' +# + + +# Nginx vHost Template defintion for vhost-gen.py +# +# The 'feature' section contains optional features that can be enabled via +# conf.yml and will then be replaced into the main vhost ('structure' section) +# into their corresponding position: +# +# __XDOMAIN_REQ__ +# __PHP_FPM__ +# __ALIASES__ +# __DENIES__ +# __STATUS__ +# +# The features itself also contain variables to be adjusted in conf.yml +# and will then be replaced in their corresponding feature section +# before being replaced into the vhost section (if enabled): +# +# PHP-FPM: +# __PHP_ADDR__ +# __PHP_PORT__ +# XDomain: +# __REGEX__ +# Alias: +# __REGEX__ +# __PATH__ +# Deny: +# __REGEX__ +# Status: +# __REGEX__ +# +# Variables to be replaced directly in the vhost configuration can also be set +# in conf.yml and include: +# __VHOST_NAME__ +# __DOCUMENT_ROOT__ +# __INDEX__ +# __ACCESS_LOG__ +# __ERROR_LOG__ +# __PHP_ADDR__ +# __PHP_PORT__ +# + + +### +### Basic vHost skeleton +### +vhost: | + server { + listen __PORT____HTTP_PROTO____DEFAULT_VHOST__; + server_name __VHOST_NAME__; + + access_log "__ACCESS_LOG__" combined; + error_log "__ERROR_LOG__" warn; + + __REDIRECT__ + __SSL__ + __VHOST_DOCROOT__ + __VHOST_RPROXY__ + __PHP_FPM__ + __ALIASES__ + __DENIES__ + __SERVER_STATUS__ + # Custom directives + __CUSTOM__ + } + + +### +### vHost Type (normal or reverse proxy) +### +vhost_type: + # Normal vHost (-p) + docroot: | + # Define the vhost to serve files + root "__DOCUMENT_ROOT__"; + index __INDEX__; + + # Reverse Proxy (-r) + rproxy: | + # Define the vhost to reverse proxy + location __LOCATION__ { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_pass __PROXY_PROTO__://__PROXY_ADDR__:__PROXY_PORT__; + } + + +### +### Optional features to be enabled in vHost +### +features: + + # SSL Configuration + ssl: | + ssl_certificate __SSL_PATH_CRT__; + ssl_certificate_key __SSL_PATH_KEY__; + ssl_protocols __SSL_PROTOCOLS__; + ssl_prefer_server_ciphers __SSL_HONOR_CIPHER_ORDER__; + ssl_ciphers __SSL_CIPHERS__; + + # Redirect to SSL directive + redirect: | + return 301 https://__VHOST_NAME__:__SSL_PORT__$request_uri; + + # PHP-FPM will not be applied to a reverse proxy! + php_fpm: | + # PHP-FPM Definition + location / { + try_files $uri $uri/ /index.php$is_args$args; + } + # CHANGED: + #location ~ \.php?$ { + location ~ \.php($|/) { + set $script $uri; + if ($uri ~ "^(.+\.php)(/.+)") { + set $script $1; + } + # end of CHANGED: + try_files $uri = 404; + include fastcgi_params; + + # https://stackoverflow.com/questions/1733306/nginx-errors-readv-and-recv-failed/51457613#51457613 + fastcgi_keep_conn off; + + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_split_path_info ^(.+\.php)(.*)$; + + fastcgi_pass __PHP_ADDR__:__PHP_PORT__; + fastcgi_read_timeout __PHP_TIMEOUT__; + + fastcgi_index index.php; + fastcgi_intercept_errors on; + } + + alias: | + # Alias Definition + location ~ __ALIAS__ { + root __PATH__; + __XDOMAIN_REQ__ + } + + deny: | + # Deny Definition + location ~ __REGEX__ { + deny all; + } + + server_status: | + # Status Page + location ~ __REGEX__ { + stub_status on; + access_log off; + } + + xdomain_request: | + # Allow cross domain request from these hosts + if ( $http_origin ~* (__REGEX__) ) { + add_header "Access-Control-Allow-Origin" "$http_origin"; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; + add_header 'Access-Control-Expose-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; + add_header 'Access-Control-Max-Age' 0; + return 200; + } diff --git a/Makefile b/Makefile index 5f2a9c2..c56d357 100644 --- a/Makefile +++ b/Makefile @@ -90,4 +90,66 @@ manifest-push: docker-manifest-push # ------------------------------------------------------------------------------------------------- .PHONY: test test: - ./tests/start-ci.sh $(IMAGE) $(NAME) $(VERSION) $(DOCKER_TAG) $(ARCH) + ./tests/start-ci.sh $(IMAGE) $(DOCKER_TAG) $(ARCH) + + +# ------------------------------------------------------------------------------------------------- +# Internal Repository Targets +# ------------------------------------------------------------------------------------------------- +.PHONY: _repo_fix +_repo_fix: __repo_fix_examples +_repo_fix: __repo_fix_doc +_repo_fix: __repo_fix_readme + +### +### In case I've copied the examples/ from any repo, ensure to replace images with current +### +.PHONY: __repo_fix_examples +__repo_fix_examples: + find examples/ -type f -print0 | xargs -0 -n1 sh -c \ + 'if grep "nginx-stable" "$${1}">/dev/null; then sed -i"" "s|devilbox/nginx-stable|$(IMAGE)|g" "$${1}";fi' -- + find examples/ -type f -print0 | xargs -0 -n1 sh -c \ + 'if grep "nginx-mainline" "$${1}">/dev/null; then sed -i"" "s|devilbox/nginx-mainline|$(IMAGE)|g" "$${1}";fi' -- + find examples/ -type f -print0 | xargs -0 -n1 sh -c \ + 'if grep "apache-2.2" "$${1}">/dev/null; then sed -i"" "s|devilbox/apache-2.2|$(IMAGE)|g" "$${1}";fi' -- + find examples/ -type f -print0 | xargs -0 -n1 sh -c \ + 'if grep "apache-2.4" "$${1}">/dev/null; then sed -i"" "s|devilbox/apache-2.4|$(IMAGE)|g" "$${1}";fi' -- + +### +### In case I've copied the doc/ from any repo, ensure to replace images with current +### +.PHONY: __repo_fix_doc +__repo_fix_doc: + find doc/ -name '*.md' -type f -print0 | xargs -0 -n1 sh -c \ + 'if grep "nginx-stable" "$${1}">/dev/null; then sed -i"" "s|devilbox/nginx-stable|$(IMAGE)|g" "$${1}";fi' -- + find doc/ -name '*.md' -type f -print0 | xargs -0 -n1 sh -c \ + 'if grep "nginx-mainline" "$${1}">/dev/null; then sed -i"" "s|devilbox/nginx-mainline|$(IMAGE)|g" "$${1}";fi' -- + find doc/ -name '*.md' -type f -print0 | xargs -0 -n1 sh -c \ + 'if grep "apache-2.2" "$${1}">/dev/null; then sed -i"" "s|devilbox/apache-2.2|$(IMAGE)|g" "$${1}";fi' -- + find doc/ -name '*.md' -type f -print0 | xargs -0 -n1 sh -c \ + 'if grep "apache-2.4" "$${1}">/dev/null; then sed -i"" "s|devilbox/apache-2.4|$(IMAGE)|g" "$${1}";fi' -- + +### +### In case I've copied the doc/ from any repo, ensure to replace images with current +### +.PHONY: __repo_fix_readme +__repo_fix_readme: + sed -i'' "s/^# Nginx stable$$/# $(NAME) $(VERSION)/g" README.md + sed -i'' "s/^# Nginx mainline$$/# $(NAME) $(VERSION)/g" README.md + sed -i'' "s/^# Apache 2.2$$/# $(NAME) $(VERSION)/g" README.md + sed -i'' "s/^# Apache 2.4$$/# $(NAME) $(VERSION)/g" README.md + @# + sed -i'' "s|docker--nginx--stable|$$( echo "docker/$(IMAGE)" | awk -F'/' '{print $$1"-"$$3}' | awk -F'-' '{print $$1"--"$$2"--"$$3}' )|g" README.md + sed -i'' "s|docker--nginx--mainline|$$( echo "docker/$(IMAGE)" | awk -F'/' '{print $$1"-"$$3}' | awk -F'-' '{print $$1"--"$$2"--"$$3}' )|g" README.md + sed -i'' "s|docker--apache--2.2|$$( echo "docker/$(IMAGE)" | awk -F'/' '{print $$1"-"$$3}' | awk -F'-' '{print $$1"--"$$2"--"$$3}' )|g" README.md + sed -i'' "s|docker--apache--2.4|$$( echo "docker/$(IMAGE)" | awk -F'/' '{print $$1"-"$$3}' | awk -F'-' '{print $$1"--"$$2"--"$$3}' )|g" README.md + @# + sed -i'' "s|docker-nginx-stable|$$( echo "docker-/$(IMAGE)" | awk -F'/' '{print $$1$$3}')|g" README.md + sed -i'' "s|docker-nginx-mainline|$$( echo "docker-/$(IMAGE)" | awk -F'/' '{print $$1$$3}')|g" README.md + sed -i'' "s|docker-apache-2.2|$$( echo "docker-/$(IMAGE)" | awk -F'/' '{print $$1$$3}')|g" README.md + sed -i'' "s|docker-apache-2.4|$$( echo "docker-/$(IMAGE)" | awk -F'/' '{print $$1$$3}')|g" README.md + @# + sed -i'' 's|devilbox/nginx-stable|$(IMAGE)|g' README.md + sed -i'' 's|devilbox/nginx-mainline|$(IMAGE)|g' README.md + sed -i'' 's|devilbox/apache-2.2|$(IMAGE)|g' README.md + sed -i'' 's|devilbox/apache-2.4|$(IMAGE)|g' README.md diff --git a/README.md b/README.md index 3d2d6d0..cd80729 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,13 @@ This image is based on the official **[Apache 2.2](https://hub.docker.com/_/httpd)** Docker image and extends it with the ability to have **virtual hosts created automatically**, as well as **adding SSL certificates** when creating new directories. For that to work, it integrates two tools that will take care about the whole process: **[watcherd](https://github.com/devilbox/watcherd)** and **[vhost-gen](https://github.com/devilbox/vhost-gen)**. -From a users perspective, you mount your local project directory into the container under `/shared/httpd`. Any directory then created in your local project directory wil spawn a new virtual host by the same name. Additional settings such as custom server names, PHP-FPM or even different Apache templates per project are supported as well. +From a users perspective, you mount your local project directory into the container under `/shared/httpd`. Any directory then created in your local project directory wil spawn a new virtual host by the same name. Each virtual host optionally supports a generic or custom backend configuration (**static files**, **PHP-FPM** or **reverse proxy**). + +For convenience the entrypoint script during `docker run` provides a pretty decent **validation and documentation** about wrong user input and suggests steps to fix it. + +| | | | | +|:----------------------:|:------------------:|:-------------:|:--------:| +| Invalid backend string | Backend Suggestion | Invalid Alias | Verified | > ##### 🐱 GitHub: [devilbox/docker-apache-2.2](https://github.com/devilbox/docker-apache-2.2) @@ -54,9 +60,9 @@ The following Docker image tags are rolling releases and are built and updated e | Docker Tag | Git Ref | Available Architectures | |----------------------------------|--------------|-----------------------------------------------| -| **[`latest`][tag_latest]** | master | `amd64`, `i386`, `arm64`, `arm/v7`, `arm/v6` | +| **[`latest`][tag_latest]** | master | `amd64` | | [`debian`][tag_debian] | master | `amd64`, `i386`, `arm64`, `arm/v7`, `arm/v6` | -| [`alpine`][tag_alpine] | master | `amd64`, `i386`, `arm64`, `arm/v7`, `arm/v6` | +| [`alpine`][tag_alpine] | master | `amd64`, `i386` | #### Point in time releases @@ -67,49 +73,48 @@ The following Docker image tags are built once and can be used for reproducible | Docker Tag | Git Ref | Available Architectures | |----------------------------------|--------------|-----------------------------------------------| -| **[``][tag_latest]** | git: `` | `amd64`, `i386`, `arm64`, `arm/v7`, `arm/v6` | +| **[``][tag_latest]** | git: `` | `amd64` | | [`-debian`][tag_debian] | git: `` | `amd64`, `i386`, `arm64`, `arm/v7`, `arm/v6` | -| [`-alpine`][tag_alpine] | git: `` | `amd64`, `i386`, `arm64`, `arm/v7`, `arm/v6` | +| [`-alpine`][tag_alpine] | git: `` | `amd64`, `i386` | > 🛈 Where `` refers to the chosen git tag from this repository.
> ⚠ **Warning:** The latest available git tag is also build every night and considered a rolling tag. -## ✰ Features - -> 🛈 For details see **[Documentation: Features](doc/features.md)** - -### Automated virtual hosts - -Virtual hosts are created automatically, simply by creating a new project directory (inside or outside of the container). This allows you to quickly create new projects and work on them in your IDE without the hassle of configuring the web server. -### Automated PHP-FPM setup +## ✰ Features -PHP is not included in the provided images, but you can link the Docker container to a PHP-FPM image with any PHP version. This allows you to easily switch PHP versions and choose one which is currently required. +This repository uses official httpd Docker images and adds a lot of features, logic and autmomation op top. This allows you to feature-toggle certain functionality simply by setting environment variables. -### Automated SSL certificate generation +Below is a brief overview about most outstanding features, but I would still advice you to read up on available [environment variables](#-environment-variables), as well as the [architecture](#-architecture) to get the whole picture. -SSL certificates are generated automatically for each virtual host to allow you to develop over HTTP and HTTPS. -### Automatically trusted HTTPS +> 🛈 For details see **[Documentation: Features](doc/features.md)** -SSL certificates are signed by a certificate authority (which is also being generated). The CA file can be mounted locally and imported into your browser, which allows you to automatically treat all generated virtual host certificates as trusted. +#### Automated mass virtual hosts +* Virtual hosts are created automatically, simply by creating a new project directory (inside or outside of the container). This allows you to quickly create new projects and work on them in your IDE without the hassle of configuring the web server. -### Customization per virtual host +#### Automated PHP-FPM setup +* PHP is not included in the provided images, but you can enable a remote backend and link it to a PHP-FPM image. This allows you to easily switch PHP versions and choose one which is currently required. -Each virtual host can individually be fully customized via `vhost-gen` templates. +#### Automated Reverse Proxy setup +* In reverse proxy mode, you can choose any http or https backend of your likings. This way you can proxy NodeJS, Python, etc. and use the webserver to add SSL in front. -### Customization for the default virtual host +#### Automated SSL certificate generation +* SSL certificates are generated automatically for each virtual host if you choose to enable it -The default virtual host is also treated differently from the auto-generated mass virtual hosts. You can choose to disable it or use it for a generic overview page for all of your created projects. +#### Trusted HTTPS in all vhosts +* Virtual host SSL certificates are signed by an internal Certificate Authority (or one you provide to the image). That makes it possible to set the CA to trusted and all generated vhosts will automatically have trusted SSL. -### Reverse Proxy integration +#### Customization per virtual host +* Each virtual host can individually be fully customized via [`vhost-gen`](https://github.com/devilbox/vhost-gen) templates. -Through virtual host customization, any project can also be served with a reverse proxy. This is useful if you want to run NodeJS or Python projects which require a reverse proxy and still want to benefit with a custom domain and auto-generated SSL certificates. +#### Local file system permission sync +* File system permission/ownership of files/dirs inside the running container can be synced with the permission on your host system. This is accomplished by specifying a user- and group-id to the `docker run` command. -### Local file system permission sync +#### Tested with common Frameworks +* Wordpress, Drupal, Laravel, CakePHP, PhalconPHP, Magento, Shopware, Typo3, Yii, Zend and many others. -File system permissions of files/dirs inside the running Docker container are synced with the permission on your host system. This is accomplished by specifying a user- and group-id to the `docker run` command. ## ∑ Environment Variables @@ -117,61 +122,68 @@ File system permissions of files/dirs inside the running Docker container are sy The provided Docker images add a lot of injectables in order to customize it to your needs. See the table below for a brief overview. > 🛈 For details see **[Documentation: Environment variables](doc/environment-variables.md)** +> +> If you don't feel like reading the documentation, simply try out your `docker run` command and add +> any environment variables specified below. The validation will tell you what you might have done wrong, +> how to fix it and what the meaning is. - - - - - - - - - -
ApacheLoggingFeatures
+ Verbosity
+ DEBUG_ENTRYPOINT
+ DEBUG_RUNTIME
- DEBUG_ENTRYPOINT
- DEBUG_RUNTIME
- DOCKER_LOGS
+ System
+ NEW_UID
+ NEW_GID
+ TIMEZONE
- TIMEZONE
- NEW_UID
- NEW_GID
+ Apache
+ -
Main vHostMass vHostPHP
- MAIN_VHOST_ENABLE
- MAIN_VHOST_SSL_TYPE
- MAIN_VHOST_SSL_GEN
- MAIN_VHOST_SSL_CN
- MAIN_VHOST_DOCROOT
- MAIN_VHOST_TPL
- MAIN_VHOST_STATUS_ENABLE
- MAIN_VHOST_STATUS_ALIAS
+ Main Vhost
+ MAIN_VHOST_ENABLE
+ MAIN_VHOST_ALIASES_ALLOW
+ MAIN_VHOST_ALIASES_DENY
+ MAIN_VHOST_BACKEND
+ MAIN_VHOST_BACKEND_TIMEOUT
+ MAIN_VHOST_DOCROOT_DIR
+ MAIN_VHOST_TEMPLATE_DIR
+ MAIN_VHOST_SSL_TYPE
+
+ MAIN_VHOST_SSL_CN
+ MAIN_VHOST_STATUS_ENABLE
+ MAIN_VHOST_STATUS_ALIAS
- MASS_VHOST_ENABLE
- MASS_VHOST_SSL_TYPE
- MASS_VHOST_SSL_GEN
- MASS_VHOST_TLD
- MASS_VHOST_DOCROOT
- MASS_VHOST_TPL
+ Mass Vhost
+ MASS_VHOST_ENABLE
+ MASS_VHOST_ALIASES_ALLOW
+ MASS_VHOST_ALIASES_DENY
+ MASS_VHOST_BACKEND
+ MASS_VHOST_BACKEND_TIMEOUT
+ MASS_VHOST_DOCROOT_DIR
+ MASS_VHOST_TEMPLATE_DIR
+ MASS_VHOST_SSL_TYPE
+
+ MASS_VHOST_BACKEND_REWRITE
+ MASS_VHOST_TLD_SUFFIX
- PHP_FPM_ENABLE
- PHP_FPM_SERVER_ADDR
- PHP_FPM_SERVER_PORT
- PHP_FPM_TIMEOUT
+ All Vhosts
+ DOCKER_LOGS
+ ## 📂 Volumes The provided Docker images offer the following internal paths to be mounted to your local file system. @@ -187,6 +199,7 @@ The provided Docker images offer the following internal paths to be mounted to y /var/www/default/
/shared/httpd/
+ /ca/
/etc/httpd-custom.d/
@@ -197,9 +210,10 @@ The provided Docker images offer the following internal paths to be mounted to y + ## 🖧 Exposed Ports -When you plan on using `443` you should enable automated SSL certificate generation. +When you plan on using `443` you must enable SSL via environment variables, otherwise nothing will be listening on that port. | Docker | Description | |--------|-------------| @@ -207,84 +221,89 @@ When you plan on using `443` you should enable automated SSL certificate generat | 443 | HTTPS listening Port | + ## 💡 Examples -### Serve static files +The documentation provides many copy/paste examples about common use-cases including dummy projects. -Mount your local directort `~/my-host-www` into the container and server those files. +The given examples distinguish between two different kinds of setup: The default vhost, which only allows to serve a single project and the mass vhost setup, which allows unlimited vhosts that are created automtically. Both types offer the same set of features and are configured in a similar way, so If you find an example in one kind it is easily applyable to the other kind as well. -**Note:** Files will be server from `~/my-host-www/htdocs`. -```bash -docker run -d -it \ - -p 80:80 \ - -v ~/my-host-www:/var/www/default \ - devilbox/apache-2.2 -``` +> 🛈 For details see **[Documentation: Examples](doc/examples.md)**
+> 🛈 For details see **[Docker Compose: Examples](examples/)** -### Serve PHP files with PHP-FPM +#### Docker -| PHP-FPM Reference Images | -|--------------------------| -| | + + + + + +
+ Default vhost
+    💡 Serve static files
+    💡 Serve PHP files
+    💡 Sync local filestem permission
+    💡 Serve PHP files over HTTPS
+    💡 Reverse Proxy NodeJS
+
+ Unlimited vhosts
+    💡 Custom vhost-gen template
+    💡 LEMP stack with PHP-FPM and MariaDB
+    💡 Wordpress setup
+
-Note, for this to work, the `~/my-host-www` dir must be mounted into the Apache container as well as into the php-fpm container. -Each PHP-FPM container also has the option to enable Xdebug and more, see their respective Readme files for futher settings. +#### Docker Compose -```bash -# Start the PHP-FPM container, mounting the same diectory -docker run -d -it \ - --name php \ - -p 9000 \ - -v ~/my-host-www:/var/www/default \ - devilbox/php-fpm:5.6-prod - -# Start the Apache Docker, linking it to the PHP-FPM container -docker run -d -it \ - -p 80:80 \ - -v ~/my-host-www:/var/www/default \ - -e PHP_FPM_ENABLE=1 \ - -e PHP_FPM_SERVER_ADDR=php \ - -e PHP_FPM_SERVER_PORT=9000 \ - --link php \ - devilbox/apache-2.2 -``` + + + + + +
+ Default vhost
+    💡 Serve static files
+    💡 Serve PHP files
+    💡 Serve PHP files over HTTPS
+    💡 Reverse Proxy NodeJS
+    💡 Reverse Proxy Python
+
+ Unlimited vhosts
+    💡 Serve PHP files over HTTPS
+    💡 Reverse Proxy and PHP-FPM
+
-### Fully functional LEMP stack -Same as above, but also add a MySQL container and link it into Apache. + +## 👷 Architecture + +The following diagram shows the basic architecture of this docker image. + + +> 🛈 For details see **[Documentation: Architecture](doc/architecture.md)** + ```bash -# Start the MySQL container -docker run -d -it \ - --name mysql \ - -p 3306:3306 \ - -e MYSQL_ROOT_PASSWORD=my-secret-pw \ - devilbox/mysql:mysql-5.5 - -# Start the PHP-FPM container, mounting the same diectory. -# Also make sure to -# forward the remote MySQL port 3306 to 127.0.0.1:3306 within the -# PHP-FPM container in order to be able to use `127.0.0.1` for mysql -# connections from within the php container. -docker run -d -it \ - --name php \ - -p 9000:9000 \ - -v ~/my-host-www:/var/www/default \ - -e FORWARD_PORTS_TO_LOCALHOST=3306:mysql:3306 \ - devilbox/php-fpm:5.6-prod - -# Start the Apache Docker, linking it to the PHP-FPM container -docker run -d -it \ - -p 80:80 \ - -v ~/my-host-www:/var/www/default \ - -e PHP_FPM_ENABLE=1 \ - -e PHP_FPM_SERVER_ADDR=php \ - -e PHP_FPM_SERVER_PORT=9000 \ - --link php \ - --link mysql \ - devilbox/apache-2.2 + # mass-vhost # main-vhost only + docker-entrypoint.sh docker-entrypoint.sh + | | + ↓ ↓ + supervisord (pid 1) httpd (pid 1) + / | + / | + ↙ ↓ + start start + httpd watcherd + / | \ + / | \ + ↓ ↓ ↘ + sgn rm create-vhost.sh + httpd vhost | | + | | + ↓ ↓ + cert-gen vhost-gen ⭢ generate vhost ``` + ## 🖤 Sister Projects Show some love for the following sister projects. @@ -329,10 +348,16 @@ Show some love for the following sister projects. devilbox/nginx-stable
devilbox/nginx-mainline + + + docker-bind + cytopia/bind + + ## 👫 Community In case you seek help, go and visit the community pages. @@ -372,6 +397,7 @@ In case you seek help, go and visit the community pages. + ## 🧘 Maintainer **[@cytopia](https://github.com/cytopia)** @@ -394,6 +420,7 @@ Terraform: [cytopia](https://registry.terraform.io/namespaces/cytopia) **·** Ansible: [cytopia](https://galaxy.ansible.com/cytopia) + ## 🗎 License **[MIT License](LICENSE)** diff --git a/doc/architecture.md b/doc/architecture.md new file mode 100644 index 0000000..1671cb2 --- /dev/null +++ b/doc/architecture.md @@ -0,0 +1,110 @@ +**Architecture** | +[Features](features.md) | +[Examples](examples.md) | +[Environment variables](environment-variables.md) | +[Volumes](volumes.md) + +--- + +# Documentation: Architecture + +1. [Tools](#-tools) +2. [Execution Chain](#-execution-chain) +3. [Directories and files](#-directories-and-files) + + +## 👷 Tools + +This project is using four core tools that interact with each other in order to achieve automated project-based mass virtual hosting with HTTPS support from SSL certificates signed by an internal CA. + +| Tool | Usage | +|------|-------| +| [`vhost-gen`](https://github.com/devilbox/vhost-gen) | An arbitrary vhost generator for Nginx (mainline and stable), Apache 2.2 and Apache 2.4 to ensure one config generates the same vhost functionality independently of underlying webserver | +| [`cert-gen`](https://github.com/devilbox/cert-gen) | A tool to generate and validate Certificate Authorities and SSL certificates which are signed by a Certificate Authority | +| [`watcherd`](https://github.com/devilbox/watcherd) | A file system change detecter (`inotify`-based or `bash`-based), which acts on changes (`add` or `delete` of directories in this case) with custom commands and offers a trigger command on change. (in this configuration, it will call `vhost-gen`, when a new directory is added in order to make the mass vhost possible. It will call a generic `rm ...` commad for a `delete` and restarts the webserver as its trigger command. | +| [`supervisord`](http://supervisord.org/) | A daemon that manages the run-time of multiple other daemons. In this case it ensures that `watcherd` and the webserver are up and running. | + + + +## 👷 Execution Chain + +This is the execution chain for how the mass virtual hosting or single vhost is achieved: +```bash + # mass-vhost # main-vhost only + docker-entrypoint.sh docker-entrypoint.sh + | | + ↓ ↓ + supervisord (pid 1) httpd (pid 1) + / | + / | + ↙ ↓ + start start + httpd watcherd + / | \ + / | \ + ↓ ↓ ↘ + sgn rm create-vhost.sh + httpd vhost | | + | | + ↓ ↓ + cert-gen vhost-gen ⭢ generate vhost +``` + +### The basics + +1. The `docker-entrypoint.sh` script sets and validates given options +2. It then passes over to `supervisord` via `exec` +3. `supervisord` ensures the web server is running +4. `supervisord` ensures `watcherd` is running +5. `watcherd` listens for file system changed (directory created or directory removed)\[1\] + +> **\[1\]** A renamed directory is: directory removed and directory created + +### What does `watcherd` do? + +* `watcherd` is setup with two events: + * event: directory created + * event: directory removed +* `watcherd` is setup with two event actions (one for each event): + * directory created: call `create-vhost.sh` + * directory removed: remove webserver vhost config for this project +* `watcherd` is setup with one *trigger* that acts after any event action has been executed: + * send a reload or stop signal to webserver + +So in simple terms, when `watcherd` detects that a new directory was created, it calls `create-vhost.sh` and sends a reload or stop signal to the webserver. In case the webserver will shutdown gracefully, it will immediately be started by `supervisord`. In both cases, the new webserver configuration will be applied.
+When `watcherd` detects that a directory was removed, it will remove the corresponding webserver vhost configuration file and send a reload or stop signal to the webserver (In case of a stop signal, `supervisord` will again ensure the webserver will come up). + +### What does `create-vhost.sh` do? + +`create-vhost.sh` is a minimalistic run-time version of the entrypoint script and does thorough validation on anything that could not be validated during startup-time. Additionally it does the following: + +* `create-vhost.sh` will generate SSL certificates (signed by internal CA) via `cert-gen` +* `create-vhost.sh` will generate a customized `vhost-gen` configuration file +* `create-vhost.sh` will move any custom `vhost-gen` templates into place +* `create-vhost.sh` will passes over to `vhost-gen`, which will then generate a virtual host configuration file. + +Once `vhost-gen` is done, the execution cycle is returned to `watcherd`, which will apply its trigger. + + + + +## 👷 Directories and files + +To get some insights on the internals, here is an overview about all directory paths and files that are being used: + +| Directories / Files | Description | +|----------------------------------|-------------| +| `/var/www/default/` | Main Vhost base directory | +| `/shared/httpd/` | Mass Vhost base directory | +| `/ca/` | Directory where generated Certificate Authoriy will be placed (You can mount this and place your own, if you prefer to use another one) | +| `/etc/httpd/cert/` | Directory where Vhost SSL certificates and keys are stored | +| `/etc/httpd/conf.d/` | Webserer configuration directory: Stores main vhost configuration file | +| `/etc/httpd/vhost.d/` | Webserver configuration directory: Stores mass vhost configuration files | +| `/etc/httpd-custom.d/` | Webserver configuration directory: Mount this and place your custom webserver configuration files in here | +| `/var/logs/httpd/` | Webserver log directory | +| `/etc/vhost-gen/` | Directory for [vhost-gen](https://github.com/devilbox/vhost-gen/): contains its default configuration (placed during install time) | +| `/etc/vhost-gen.d/` | Directory for [vhost-gen](https://github.com/devilbox/vhost-gen/): mount this and place custom `vhost-gen` templates to override `vhost-gen`'s behaviour. Templates can be found: [here](https://github.com/devilbox/vhost-gen/tree/master/etc/templates) | +| [`/docker-entrypoint.sh`](../Dockerfiles/data/docker-entrypoint.sh) | Entrypoint script that will be executed by the container during startup | +| `/docker-entrypoint.d/` | Entrypoint validators and functions that are used by `/docker-entrypoint.sh` | +| [`/etc/supervisord.conf`](../Dockerfiles/data/docker-entrypoint.d/15-supervisord.sh) | Supervisord coniguration file. Supervisord will only be started, whenn `MASS_VHOST_ENABLE` is set to `1` | +| [`/usr/local/bin/create-vhost.sh`](../Dockerfiles/data/create-vhost.sh) | A wrapper script to create a vhost (validation, ssl certificates and calls `vhost-gen` | diff --git a/doc/environment-variables.md b/doc/environment-variables.md index 838db73..9dd214f 100644 --- a/doc/environment-variables.md +++ b/doc/environment-variables.md @@ -1,4 +1,6 @@ +[Architecture](architecture.md) | [Features](features.md) | +[Examples](examples.md) | **Environment variables** | [Volumes](volumes.md) @@ -7,51 +9,589 @@ # Documentation: Environment Variables -This Docker container adds a lot of injectables in order to customize it to your needs. See the table below for a detailed description. +The provided Docker images have a lot of injectables in order to customize it to your needs. Click an environment variable in the table below to jump to the corresponding section. -## Required environmental variables + + + + + + + + + + + +
+ Verbosity
+ DEBUG_ENTRYPOINT
+ DEBUG_RUNTIME
+
+ System
+ NEW_UID
+ NEW_GID
+ TIMEZONE
+
+ Apache 2.2
+ - +
+ Main Vhost
+ MAIN_VHOST_ENABLE
+ MAIN_VHOST_ALIASES_ALLOW
+ MAIN_VHOST_ALIASES_DENY
+ MAIN_VHOST_BACKEND
+ MAIN_VHOST_BACKEND_TIMEOUT
+ MAIN_VHOST_DOCROOT_DIR
+ MAIN_VHOST_TEMPLATE_DIR
+ MAIN_VHOST_SSL_TYPE
+
+ MAIN_VHOST_SSL_CN
+ MAIN_VHOST_STATUS_ENABLE
+ MAIN_VHOST_STATUS_ALIAS
+
+ Mass Vhost
+ MASS_VHOST_ENABLE
+ MASS_VHOST_ALIASES_ALLOW
+ MASS_VHOST_ALIASES_DENY
+ MASS_VHOST_BACKEND
+ MASS_VHOST_BACKEND_TIMEOUT
+ MASS_VHOST_DOCROOT_DIR
+ MASS_VHOST_TEMPLATE_DIR
+ MASS_VHOST_SSL_TYPE
+
+ MASS_VHOST_BACKEND_REWRITE
+ MASS_VHOST_TLD_SUFFIX
+
+ All Vhosts
+ DOCKER_LOGS
+
-`PHP_FPM_SERVER_ADDR` is required when enabling PHP FPM. -## Optional environmental variables (general) +## ∑ `DEBUG_ENTRYPOINT` -| Variable | Type | Default | Description | -|----------|------|---------|-------------| -| `DEBUG_ENTRYPOINT` | int | `0` | Show settings and shell commands executed during startup.
Values:
`0`: Off
`1`: Show settings
`2`: Show settings and commands | -| `DEBUG_RUNTIME` | bool | `0` | Be verbose during runtime.
Value: `0` or `1` | -| `DOCKER_LOGS` | bool | `0` | When set to `1` will redirect error and access logs to Docker logs (`stderr` and `stdout`) instead of file inside container.
Value: `0` or `1` | -| `TIMEZONE` | string | `UTC` | Set docker OS timezone.
(Example: `Europe/Berlin`) | -| `NEW_UID` | int | `101` | Assign the default Apache user a new UID. This is useful if you you mount your document root and want to match the file permissions to the one of your local user. Set it to your host users uid (see `id` for your uid). | -| `NEW_GID` | int | `101` | This is useful if you you mount your document root and want to match the file permissions to the one of your local user group. Set it to your host user groups gid (see `id` for your gid). | -| `PHP_FPM_ENABLE` | bool | `0` | Enable PHP-FPM for the default vhost and the mass virtual hosts. | -| `PHP_FPM_SERVER_ADDR` | string | `` | IP address or hostname of remote PHP-FPM server.
Required when enabling PHP. | -| `PHP_FPM_SERVER_PORT` | int | `9000` | Port of remote PHP-FPM server | -| `PHP_FPM_TIMEOUT` | int | `180` | Timeout in seconds to upstream PHP-FPM server | -| `HTTP2_ENABLE` | int | `1` | Enabled or disabled HTTP2 support.
Values:
`0`: Disabled
`1`: Enabled
Defaults to Enabled | +This variable controls the debug level (verbosity) for the container startup. The more verbose, the more information is shown via docker logs during startup. +* **Default:** `2` +* **Allowed:** `0`, `1`, `2`, `3`, `4` +* **Var type:** `int` -## Optional environmental variables (default vhost) +When set to `0`, the following events are shown: `error`
+When set to `1`, the following events are shown: `error`,`warning`
+When set to `2`, the following events are shown: `error`, `warning`, `ok`, `info`
+When set to `3`, the following events are shown: `error`, `warning`, `ok`, `info`, `debug`
+When set to `4`, the following events are shown: `error`, `warning`, `ok`, `info`, `debug` and `trace` -| Variable | Type | Default | Description | -|----------|------|---------|-------------| -| `MAIN_VHOST_ENABLE` | bool | `1` | By default there is a standard (catch-all) vhost configured to accept requests served from `/var/www/default/htdocs`. If you want to disable it, set the value to `0`.
Note:The `htdocs` dir name can be changed with `MAIN_VHOST_DOCROOT`. See below. | -| `MAIN_VHOST_SSL_TYPE` | string | `plain` |
  • plain - only serve via http
  • ssl - only serve via https
  • both - serve via http and https
  • redir - serve via https and redirect http to https
| -| `MAIN_VHOST_SSL_GEN` | bool | `0` | `0`: Do not generate an ssl certificate
`1`: Generate self-signed certificate automatically | -| `MAIN_VHOST_SSL_CN` | string | `localhost` | Comma separated list of CN names for SSL certificate generation (The domain names by which you want to reach the default server) | -| `MAIN_VHOST_DOCROOT` | string | `htdocs`| This is the directory name appended to `/var/www/default/` from which the default virtual host will serve its files.
Default:
`/var/www/default/htdocs`
Example:
`MAIN_VHOST_DOCROOT=www`
Doc root: `/var/www/default/www` | -| `MAIN_VHOST_TPL` | string | `cfg` | Directory within th default vhost base path (`/var/www/default`) to look for templates to overwrite virtual host settings. See [vhost-gen](https://github.com/devilbox/vhost-gen/tree/master/etc/templates) for available template files.
Resulting default path:
`/var/www/default/cfg` | -| `MAIN_VHOST_STATUS_ENABLE` | bool | `0` | Enable httpd status page. | -| `MAIN_VHOST_STATUS_ALIAS` | string | `/httpd-status` | Set the alias under which the httpd server should serve its status page. | -## Optional environmental variables (mass vhosts) +## ∑ `DEBUG_RUNTIME` -| Variable | Type | Default | Description | -|----------|------|---------|-------------| -| `MASS_VHOST_ENABLE` | bool | `0` | You can enable mass virtual hosts by setting this value to `1`. Mass virtual hosts will be created for each directory present in `/shared/httpd` by the same name including a top-level domain suffix (which could also be a domain+tld). See `MASS_VHOST_TLD` for how to set it. | -| `MASS_VHOST_SSL_TYPE` | string | `plain` |
  • plain - only serve via http
  • ssl - only serve via https
  • both - serve via http and https
  • redir - serve via https and redirect http to https
| -| `MASS_VHOST_SSL_GEN` | bool | `0` | `0`: Do not generate an ssl certificate
`1`: Generate self-signed certificate automatically | -| `MASS_VHOST_TLD` | string | `.loc`| This string will be appended to the server name (which is built by its directory name) for mass virtual hosts and together build the final domain.
Default:`.loc`
Example:
Path: `/shared/httpd/temp`
`MASS_VHOST_TLD=.lan`
Server name: `temp.lan`
Example:
Path:`/shared/httpd/api`
`MASS_VHOST_TLD=.example.com`
Server name: `api.example.com` | -| `MASS_VHOST_DOCROOT` | string | `htdocs`| This is a subdirectory within your project dir under each project from which the web server will serve its files.
`/shared/httpd//$MASS_VHOST_DOCROOT/`
Default:
`/shared/httpd//htdocs/` | -| `MASS_VHOST_TPL` | string | `cfg` | Directory within your new virtual host to look for templates to overwrite virtual host settings. See [vhost-gen](https://github.com/devilbox/vhost-gen/tree/master/etc/templates) for available template files.
`/shared/httpd//$MASS_VHOST_TPL/`
Resulting default path:
`/shared/httpd//cfg/` | +This variable controls the debug level (verbosity) during run-time (after validation and after the main webserver process has started). + +* **Default:** `1` +* **Allowed:** `0`, `1`, `2` +* **Var type:** `int` + +When set to `0`, the following events are shown: none
+When set to `1`, the following events are shown: `command outputs`
+When set to `2`, the following events are shown: `command outputs` and `commands` + +If you are using dynamic vhost creation with `MASS_VHOST_ENABLE=1`, it is recommended to keep this value at `1`, in order to observe the correct creation/deletion and view any custom templates being picked up. + + + +## ∑ `NEW_UID` + +This variable controls the user id of the webserver process. + +* **Default:** `` +* **Allowed:** any valid user id (use your local users' `uid`) +* **Var type:** `int` + +> **Backgrund:** The webserver docker image has a non-root user (and group) that the webserver process runs with. When one of your PHP scripts creates a file (cache, uploads, etc), it is being created with the user id and group id, the webserver process runs with. In order to make sure this is the same user id as your normal user locally on the host system, this env variable can be used to change the user id inside the container (during startup). +**Why can't the webserver process run as root?** It would then create files with root permissions and as those files are actually on your host system, you would require root permission to access/edit them again. +> +> You can read more about this topic here: [Syncronize file and folder Permissions](https://github.com/devilbox/docker-php-fpm/blob/master/doc/syncronize-file-permissions.md). + +What value should I set this to? Open up a terminal on your host system and type **`id -u`** to find out the user id of your local user. + + + +## ∑ `NEW_GID` + +This variable controls the group id of the webserver process. + +* **Default:** `` +* **Allowed:** any valid group id (use your local users' `gid`) +* **Var type:** `int` + +> See **`NEW_UID`** for background information. + +What value should I set this to? Open up a terminal on your host system and type **`id -g`** to find out the group id of your local user. + + + +## ∑ `TIMEZONE` + +This variable sets the timezone for the container as well as for the webserver process. + +* **Default:** `UTC` +* **Allowed:** any valid timezone (e.g.: `Europe/Berlin`) +* **Var type:** `string` + + + +## ∑ `MAIN_VHOST_ENABLE` + +This variable controls whether the main (default) virtual host is enabled or disabled. + +* **Default:** `1` +* **Allowed:** `0` or `1` +* **Var type:** `bool` + + + +## ∑ `MAIN_VHOST_DOCROOT_DIR` + +The given directory name (not path) is appended to the vhost base path. This is the final location from where the main (default) vhost will serve files from. + +* **Default:** `htdocs` +* **Allowed:** valid directory name (not path) +* **Var type:** `string` +* **Requires:** `MAIN_VHOST_ENABLE=1` + +The given directory name will be appended to the current web server base path (`/var/www/default/`) and thus creating the full path from where the main (default) vhost will serve files from (`DocumentRoot` in _Apache speak_ or `root` in _Nginx speak_). + +**Example:** + +| `MAIN_VHOST_DOCROOT_DIR` value | Full path where the main (default) vhost serves files from | +|--------------------------------|------------------------------------------------------------| +| `htdocs` | `/var/www/default/htdocs/` | +| `www` | `/var/www/default/www/` | + +The following Docker logs output shows settings for `*_VHOST_DOCROOT_DIR` set to `htdocs` + + + + + +## ∑ `MAIN_VHOST_TEMPLATE_DIR` + +The given directory name (not path) is appended to the vhost base path. This is the final location in which [vhost-gen](https://github.com/devilbox/vhost-gen/) will look for custom templates for the main (default) vhost creation. + +* **Default:** `cfg` +* **Allowed:** valid directory name +* **Var type:** `string` +* **Requires:** `MAIN_VHOST_ENABLE=1` + +The given directory name will be appended to the current web server base path (`/var/www/default/`) and thus creating the full path in which `vhost-gen` will look for custom templates. + + +**Example:** + +| `MAIN_VHOST_TEMPLATE_DIR` value | Full path where `vhost-gen` will look for templates | +|----------------------------|------------------------------------------------------------| +| `cfg` | `/var/www/default/cfg/` | +| `.conf` | `/var/www/default/.conf/` | + +The following Docker logs output shows settings for `*_VHOST_TEMPLATE_DIR` set to `cfg`. See how it affects the `MASS_VHOST_BACKEND` as well. + + + +**Note:** In most cases it is not required to mess with the `vhost-gen` templates, but if you want some major customizations, download the **[vhost-gen templates](https://github.com/devilbox/vhost-gen/tree/master/etc/templates)**, adjust it and put it into the directory prior startup (via mount). + + + +## ∑ `MAIN_VHOST_ALIASES_ALLOW` + +This variable defines one or more URL aliases pointing to a file system path. Optional CORS settings can be set as well. + +* **Default:** `` _(no aliases)_ +* **Allowed:** valid aliases string +* **Var type:** `string` +* **Requires:** `MAIN_VHOST_ENABLE=1` + +### Format string +```bash +# Single alias +:[:] + +# Multiple aliases are comma separated +:[:][,:[:]] +``` + +### Format string examples +```bash +# Single alias +# Ensures that http://server/url/path/ points to filesystem /var/www/default/img +/url/path/:/var/www/default/img + +# Single alias with CORS +# Ensures that http://server/url/path/ points to filesystem /var/www/default/img +# And allows cross domain request from these hosts: http(s)?://(.+)? +/url/path/:/var/www/default/img:http(s)?://(.+)$ + +# Mutiple aliases +# Ensures that http://server/devilbox-api/ points to filesystem /var/www/default/api +# Ensures that http://server/vhost.d/ points to filesystem /etc/httpd +/devilbox-api/:/var/www/default/api, /vhost.d/:/etc/httpd + +# Mutiple aliases with CORS +# Ensures that http://server/devilbox-api/ points to filesystem /var/www/default/api +# Ensures that http://server/vhost.d/ points to filesystem /etc/httpd +# And allows cross domain request from these hosts: http(s)?://(.+)? on http://server/devilbox-api/ +/devilbox-api/:/var/www/default/api:http(s)?://(.+)$, /vhost.d/:/etc/httpd +``` + +### Generation example + +Let's assume you have provided the following environment variable to the docker image: +```bash +MAIN_VHOST_ALIASES_ALLOW='/vhost.d/:/etc/httpd' +``` +In _Nginx Speak_ it would generate the following configuration block: +```conf +location ~ /vhost.d/ { + root /etc/httpd; +} +``` +In _Apache Speak_ it would generate the following configuration block: +```conf +Alias "/vhost.d/" "/etc/httpd/vhost.d/" + + + + Order allow,deny + Allow from all + Require all granted + +``` + +### Validation + +During docker startup, the entrypoint validator tries the best guess on what has gone wrong and gives valid examples. + + + + + +## ∑ `MAIN_VHOST_ALIASES_DENY` + +This variable defines one or more URL aliases to deny access to. + +* **Default:** `/\.git, /\.ht.*` +* **Allowed:** valid aliases string +* **Var type:** `string` +* **Requires:** `MAIN_VHOST_ENABLE=1` + +### Format string +```bash +# Single alias + + +# Multiple aliases +, +``` +### Format string examples +```bash +# Single alias +# Ensures that http://server/url/secret is denied +/url/secret* + +# Mutiple aliases +# Ensures that http://server/.git is denied +# Ensures that http://server/.svn is denied +/\.git, /\.svn +``` + +### Generation example + +Let's assume you have provided the following environment variable to the docker image: +```bash +MAIN_VHOST_ALIASES_DENY='/\.git, /\.ht.*' +``` +In _Nginx Speak_ it would generate the following configuration block: +```conf +# Deny Definition +location ~ /\.git { + deny all; +} +# Deny Definition +location ~ /\.ht.* { + deny all; +} +``` +In _Apache Speak_ it would generate the following configuration block: +```conf +# Deny Definition + + Order allow,deny + Deny from all + + +# Deny Definition + + Order allow,deny + Deny from all + +``` + + + +## ∑ `MAIN_VHOST_BACKEND` + +The given value determines the backend (potentia remote/reveres hosts) for the main (default vhost). + +* **Default:** `` _(serving static files only)_ +* **Allowed:** valid backend string +* **Var type:** `string` +* **Requires:** `MAIN_VHOST_ENABLE=1` + +You can configure a remote backend via this environment variable. Either a remote PHP-FPM server or any kind of service via `http` or `https` reverse proxy. + +### String format + +The backend environment variable supports two different formats. + +1. Direct configuration + ```bash + conf:::: + ``` +2. Configuration via file + ```bash + file: + ``` + +### 1. Direct configuration + +With the direct configuration you set everything explicitly via this environment variable and nothing else is required. + +* **``**: `phpfpm` or `rproxy` +* **``**: `tcp`, `http` or `https` +* **``**: the address of upstream host to send requests to (`hostname`, `IPv4` or `IPv6`). +* **``**: the port of the upstream host to send requests to + +**Examples** +```bash +MAIN_VHOST_BACKEND=conf:phpfpm:tcp:10.0.0.1:9000 +MAIN_VHOST_BACKEND=conf:rproxy:http:10.0.0.1:3000 +MAIN_VHOST_BACKEND=conf:rproxy:https:10.0.0.1:3000 +``` + +When specifying `phpfpm`, the vhost will also automatically be configured for serving PHP files. (including `index.php` for its directory index). + +### 2. Configuration via file + +With the file configuration, you assign the configuration for the vhost a file instead of its environment variable. +The file will be located in the vhost base path under `$MAIN_VHOST_TEMPLATE_DIR`. + +The main (default) vhost base path is `/var/www/default`. Now let's assume the following configuration: +* `MAIN_VHOST_TEMPLATE_DIR=cfg` +* `MAIN_VHOST_BACKEND=file:backend.cfg` + +The configuration file can then be found in `/var/www/default/cfg/backend.cfg`. + +The file expects the same string as you would give to the format in `Direct configuration`. + +**Example:** +```bash +$ cat /var/www/default/cfg/backend.cfg +conf:phpfpm:tcp:10.0.0.1:9000 +``` + +See the following Docker logs output for how `*_VHOST_TEMPLATE_DIR` affects the configuration file specified in `*_VHOST_BACKEND`
+ + +### Validation + +During docker startup, the entrypoint validator tries the best guess on what has gone wrong and gives valid examples. + +| | | +|:-----------:|:-----------:| +| Invalid `` | Unsupported backend | + + + +## ∑ `MAIN_VHOST_BACKEND_TIMEOUT` + +Set a timeout for the backend server (if any was specified). + +* **Default:** `180` +* **Allowed:** valid integer +* **Var type:** `integer` +* **Requires:** `MAIN_VHOST_ENABLE=1` and `MAIN_VHOST_BACKEND=...` + +The given value determines the timeout (in seconds) for the backend, if a backend was set via `MAIN_VHOST_BACKEND` + + + +## ∑ `MAIN_VHOST_SSL_TYPE` + +The SSL_TYPE determines if HTTPS vhosts are created and how they behave. + +* **Default:** `plain` +* **Allowed:** `plain`, `ssl`, `both`, `redir` +* **Var type:** `string` +* **Requires:** `MAIN_VHOST_ENABLE=1` + +By default only a HTTP vhost is created, you can change this via the SSL type: + +* `MAIN_VHOST_SSL_TYPE=plain`: Only a HTTP vhost is created +* `MAIN_VHOST_SSL_TYPE=ssl`: Only a HTTPS vhost is created +* `MAIN_VHOST_SSL_TYPE=both`: A HTTP and a HTTPS vhost is created +* `MAIN_VHOST_SSL_TYPE=redir`: Any HTTP request redirects to HTTPS (where the vhost resides) + + + +## ∑ `MAIN_VHOST_SSL_CN` + +Set the Common name for the SSL certificate of the vhost. + +* **Default:** `localhost` +* **Allowed:** valid common name +* **Var type:** `string` +* **Requires:** `MAIN_VHOST_ENABLE=1` and `MAIN_VHOST_SSL_TYPE != plain` + +If you want to set the common name of the SSL certificate for this vhost to something else than `localhost`, then adjust it here. + + + +## ∑ `MAIN_VHOST_STATUS_ENABLE` + +You can enable the webserver status page with this variable + +* **Default:** `0` +* **Allowed:** `0` or `1` +* **Var type:** `bool` +* **Requires:** `MAIN_VHOST_ENABLE=1` + +When setting to `1`, the webserver status page will be available under the URL specified in `MAIN_VHOST_STATUS_ALIAS`. + + + +## ∑ `MAIN_VHOST_STATUS_ALIAS` + +Change the location of the webserver status URL. + +* **Default:** `/httpd-status` +* **Allowed:** valid alias string +* **Var type:** `string` +* **Requires:** `MAIN_VHOST_ENABLE=1` and `MAIN_VHOST_STATUS_ENABLE` + +By default, you can access the webserver status page via `http(s)?://:/httpd-status`. +If you would like a different URL, change it here. + + + +## ∑ `MASS_VHOST_ENABLE` + +See [`MAIN_VHOST_ENABLE`](#-main_vhost_enable). It is the same concept. + + + +## ∑ `MASS_VHOST_DOCROOT_DIR` + +See [`MAIN_VHOST_DOCROOT_DIR`](#-main_vhost_docroot_dir). It is the same concept. + + + +## ∑ `MASS_VHOST_TEMPLATE_DIR` + +See [`MAIN_VHOST_TEMPLATE_DIR`](#-main_vhost_template_dir). It is the same concept. + + + +## ∑ `MASS_VHOST_ALIASES_ALLOW` + +See [`MAIN_VHOST_ALIASES_ALLOW`](#-main_vhost_aliases_allow). It is the same concept. + + + +## ∑ `MASS_VHOST_ALIASES_DENY` + +See [`MAIN_VHOST_ALIASES_DENY`](#-main_vhost_aliases_deny). It is the same concept. + + + +## ∑ `MASS_VHOST_BACKEND` + +See [`MAIN_VHOST_BACKEND`](#-main_vhost_backend). It is the same concept. + + + +## ∑ `MASS_VHOST_BACKEND_REWRITE` + +This is a per project backend overwrite that is accomplished through a configuration file inside the project directory. + +* **Default:** `` +* **Allowed:** `file:` +* **Var type:** `string` +* **Requires:** `MASS_VHOST_ENABLE=1` and a value in `MASS_VHOST_BACKEND` + +This environment variable works in the same way as [`MAIN_VHOST_BACKEND`](#-main_vhost_backend) with the exception that it only supports the `file:` format. + +### Why would I use this? + +You can first set a generic backend for mass virtual hosting via `MASS_VHOST_BACKEND`. That means that every created mass virtual host will use the defined backend as its default (e.g. PHP-FPM). +Now if you have a single (or more) project, which requires a different configuration (e.g. needs to be a reverse proxy), then you can place a configuration file (as specified in `MASS_VHOST_BACKEND_REWRITE`) into that project dir and its backend will behave in this way. + +You can see an example setup here, with `MASS_VHOST_BACKEND=conf:phpfpm:tcp:10.0.0.1:9000` and `MASS_VHOST_BACKEND_REWRITE=file:config.txt`, where the `config.txt` contains: `conf:rproxy:http:127.0.0.1:300` + + + +### What are the limitations? + +`MASS_VHOST_BACKEND_REWRITE` only works, if you are also using `MASS_VHOST_BACKEND`. I.e, you must have something that you can rewrite, otherwise there is no sense in using it and you can just fall back to using `MASS_VHOST_BACKEND` alone. Don't worry too much about it, the entrypoint validator will tell you in case you got this wrong. + +Additionally there is no such thing as `MAIN_VHOST_BACKEND_REWRITE`, as the main vhost is only a single vhost and therefore has no need to overwrite it, as you can simply use `MAIN_VHOST_BACKEND`. + + + +## ∑ `MASS_VHOST_BACKEND_TIMEOUT` + +See [`MAIN_VHOST_BACKEND_TIMEOUT`](#-main_vhost_backend-timeout). It is the same concept. + + + +## ∑ `MASS_VHOST_SSL_TYPE` + +See [`MASS_VHOST_SSL_TYPE`](#-main_vhost_ssl-type). It is the same concept. + + + +## ∑ `MASS_VHOST_TLD_SUFFIX` + +Set the domain suffix for all virtual hosts created via the `MASS_VHOST_ENABLE`. + +* **Default:** `.loc` +* **Allowed:** empty or domain with leading dot +* **Var type:** `str` +* **Requires:** `MASS_VHOST_ENABLE=1` + + +**Background:** When `MASS_VHOST_ENABLE` is set to `1`. [watcherd](https://github.com/devilbox/watcherd) will listen for directory changes (creations, deletions, renamings) in `/shared/httpd`. As soon as a project directory is created below that path, `watcherd` will trigger [vhost-gen](https://github.com/devilbox/vhost-gen/) to create a new virtual host and reloads the webserver. + +Each project virtual host created will have a name of `${MASS_VHOST_TLD_SUFFIX}`. See the table below for details: + +| Base path | Project dir | MASS_VHOST_TLD_SUFFIX | Final domain name | +|------------------|-------------|-----------------------|-----------------------| +| `/shared/httpd/` | `test-1` | `` | `test-1` | +| `/shared/httpd/` | `test-1` | `.loc` | `test-1.loc` | +| `/shared/httpd/` | `drupal` | `.com` | `drupal.com` | +| `/shared/httpd/` | `nodeapp` | `example.com` | `nodeapp.example.com` | + +If you have SSL enabled, then the final domain name will also be used as the common name for SSL certificate generation for the project. + + + +## ∑ `DOCKER_LOGS` + +This variable controls whether webserver access and error logs are written to a log file inside the container or shown via docker logs. + +* **Default:** `1` +* **Allowed:** `0` or `1` +* **Var type:** `bool` + +By default (value: `1`) all Docker images are configured to output their webserver access and error logs to stdout and stderr, which means it is shown by `docker logs` (or `docker-compose logs`). + +If you want to log into files inside the container instead, change it to `0`. The respective log files are available in `/var/log/httpd/` and can be mounted to your local file system so you can `cat`, `tail` or `grep` them for anything interesting. diff --git a/doc/examples.md b/doc/examples.md new file mode 100644 index 0000000..99d76ee --- /dev/null +++ b/doc/examples.md @@ -0,0 +1,313 @@ +[Architecture](architecture.md) | +[Features](features.md) | +**Examples]** | +[Environment variables](environment-variables.md) | +[Volumes](volumes.md) + +--- + +# Documentation: Examples + + +1. [Serve static files](#-serve-staticfiles) +2. [Serve PHP files with PHP-FPM](#-serve-php-files-with-php-fpm) +3. [Serve PHP files with PHP-FPM and sync local permissions](#-serve-php-files-with-php-fpm-and-sync-local-permissions) +4. [Serve PHP files with PHP-FPM over HTTPS](#-serve-php-files-with-php-fpm-over-https) +5. [Act as a Reverse Proxy for NodeJS](#-act-as-a-reverse-proxy-for-nodejs) +6. [Fully functional LEMP stack with Mass vhosts](#-fully-functional-lemp-stack-with-mass-vhosts) +7. [Docker Compose](#-docker-compose) + + + +## 💡 Serve static files + +This example creates the main (default) vhost, which only serves static files. + +* **Vhost:** main (default) +* **Backend:** none + +> 🛈 With no further configuration, the webserver expects files to be served by the main vhost in: `/var/www/default/htdocs`. + +1. Create a static page + ```bash + mkdir -p www/htdocs + echo '

It works

' > www/htdocs/index.html + ``` +2. Start the webserver + ```bash + docker run -d -it \ + -p 9090:80 \ + -v $(pwd)/www:/var/www/default \ + devilbox/apache-2.2 + ``` +3. Verify + ```bash + curl http://localhost:9090 + ``` + + + +## 💡 Serve PHP files with PHP-FPM + +This example creates the main (default) vhost, which contacts a remote PHP-FPM host to serve PHP files. + +* **Vhost:** main (default) +* **Backend:** PHP-FPM + +| PHP-FPM Reference Images | +|--------------------------| +| | + +> 🛈 For this to work, the `$(pwd)/www` directory must be mounted into the webserver container as well as into the php-fpm container.
+> 🛈 With no further configuration, the webserver expects files to be served by the main vhost in: `/var/www/default/htdocs`. + +1. Create a helo world page + ```bash + mkdir -p www/htdocs + echo ' www/htdocs/index.php + ```` +2. Start the PHP-FPM container + ```bash + docker run -d -it \ + --name php \ + -v $(pwd)/www:/var/www/default \ + devilbox/php-fpm:8.2-base + ``` +3. Start the webserve, linking it to the PHP-FPM container + ```bash + docker run -d -it \ + -p 9090:80 \ + -v $(pwd)/www:/var/www/default \ + -e MAIN_VHOST_BACKEND='conf:phpfpm:tcp:php:9000' \ + --link php \ + devilbox/apache-2.2 + ``` +4. Verify + ```bash + curl http://localhost:9090 + ``` + + + +## 💡 Serve PHP files with PHP-FPM and sync local permissions + +The same as the previous example, but also ensures that you can edit files locally and have file ownerships synced with webserver and PHP-FPM container. + +> See **[Syncronize File System Permissions](https://github.com/devilbox/docker-php-fpm/blob/master/doc/syncronize-file-permissions.md)** for details + +* **Vhost:** main (default) +* **Backend:** PHP-FPM +* **Feature:** `uid` and `gid` are synced + +> 🛈 For this to work, the `$(pwd)/www` directory must be mounted into the webserver container as well as into the php-fpm container.
+> 🛈 With no further configuration, the webserver expects files to be served by the main vhost in: `/var/www/default/htdocs`.
+> 🛈 `NEW_UID` and `NEW_GID` are set to your local users' value + +1. Create a helo world page + ```bash + mkdir -p www/htdocs + echo ' www/htdocs/index.php + ```` +2. Start the PHP-FPM container + ```bash + docker run -d -it \ + --name php \ + -v $(pwd)/www:/var/www/default \ + -e NEW_UID=$(id -u) \ + -e NEW_GID=$(id -g) \ + devilbox/php-fpm:8.2-base + ``` +3. Start the webserve, linking it to the PHP-FPM container + ```bash + docker run -d -it \ + -p 9090:80 \ + -v $(pwd)/www:/var/www/default \ + -e NEW_UID=$(id -u) \ + -e NEW_GID=$(id -g) \ + -e MAIN_VHOST_BACKEND='conf:phpfpm:tcp:php:9000' \ + --link php \ + devilbox/apache-2.2 + ``` +4. Verify + ```bash + curl http://localhost:9090 + ``` +5. **Explanation:** Whenever a file is created by the webserver (e.g.: file uploads) or the PHP-FPM process (e.g.: php creates a file on the filesystem), it is done with the same permissions as your local operating system user. This means you can easily edit files in your IDE/editor and do not come accross permission issues. + + + + +## 💡 Serve PHP files with PHP-FPM over HTTPS + +The same as the previous example, just with the addition of enabling SSL (HTTPS). + +This example shows the SSL type `redir`, which makes the webserver redirect any HTTP requests to HTTPS. + +Additionally we are mounting the `./ca` directory into the container under `/ca`. After startup you will find generated Certificate Authority files in there, which you could import into your browser. + +* **Vhost:** main (default) +* **Backend:** Reverse Proxy +* **Features:** `uid` and `gid` are synced and SSL (redirect) + +> 🛈 For this to work, the `$(pwd)/www` directory must be mounted into the webserver container as well as into the php-fpm container.
+> 🛈 With no further configuration, the webserver expects files to be served by the main vhost in: `/var/www/default/htdocs`. + +1. Create a helo world page + ```bash + mkdir -p www/htdocs + echo ' www/htdocs/index.php + ```` +2. Start the PHP-FPM container + ```bash + docker run -d -it \ + --name php \ + -e NEW_UID=$(id -u) \ + -e NEW_GID=$(id -g) \ + -v $(pwd)/www:/var/www/default \ + devilbox/php-fpm:8.2-base + ``` +3. Start the webserve, linking it to the PHP-FPM container + ```bash + docker run -d -it \ + -p 80:80 \ + -p 443:443 \ + -v $(pwd)/www:/var/www/default \ + -v $(pwd)/ca:/ca \ + -e NEW_UID=$(id -u) \ + -e NEW_GID=$(id -g) \ + -e MAIN_VHOST_BACKEND='conf:phpfpm:tcp:php:9000' \ + -e MAIN_VHOST_SSL_TYPE='redir' \ + --link php \ + devilbox/apache-2.2 + ``` +4. Verify redirect + ```bash + curl -I http://localhost + ``` +5. Verify HTTPS + ```bash + curl -k https://localhost + ``` + + + +## 💡 Act as a Reverse Proxy for NodeJS + +The following example proxies all HTTP requests to a NodeJS remote backend. You could also enable SSL on the webserver in order to access NodeJS via HTTPS. + +* **Vhost:** main (default) +* **Backend:** Reverse Proxy + +> 🛈 No files need to be mounted into the webserver, as content is coming from the NodeJS server. + +1. Create a NodeJS application + ```bash + mkdir -p src + cat << EOF > src/app.js + const http = require('http'); + const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.write('[OK]\n'); + res.write('NodeJS is running\n'); + res.end(); + }); + server.listen(3000, '0.0.0.0'); + EOF + ``` +2. Start the NodeJS container + ```bash + docker run -d -it \ + --name nodejs \ + -v $(pwd)/src:/app \ + node:19-alpine node /app/app.js + ``` +3. Start Reverse Proxy + ```bash + docker run -d -it \ + -p 80:80 \ + -e MAIN_VHOST_BACKEND='conf:rproxy:http:nodejs:3000' \ + --link nodejs \ + devilbox/apache-2.2 + ``` +4. Verify + ```bash + curl http://localhost + ``` + + + +## 💡 Fully functional LEMP stack with Mass vhosts + +The following example creates a dynamic setup. Each time you create a new project directory below `www/`, a new virtual host is being created. +Additionally all projects will have the `.com` suffix added to their domain name, which results in `.com` as the final domain. + +* **Vhost:** mass (unlimited vhosts) +* **Backend:** PHP-FPM + +> 🛈 For this to work, the `$(pwd)/www` directory must be mounted into the webserver container as well as into the php-fpm container.
+> 🛈 With no further configuration, the webserver expects files to be served by the **mass** vhost in: **`/shared/httpd//htdocs`**, where `` is a placeholder for any directory. + +1. Create the project base directory + ```bash + mkdir -p www + ``` +2. Start the MySQL container _(only for demonstration purposes)_ + ```bash + docker run -d -it \ + --name mysql \ + -e MYSQL_ROOT_PASSWORD=my-secret-pw \ + devilbox/mysql:mariadb-10.10 + ``` +3. Start the PHP-FPM container + ```bash + docker run -d -it \ + --name php \ + -v $(pwd)/www:/shared/httpd \ + devilbox/php-fpm:8.2-base + ``` +4. Start the webserver container, linking it to the two above + ```bash + docker run -d -it \ + -p 8080:80 \ + -v $(pwd)/www:/shared/httpd \ + -e MAIN_VHOST_ENABLE=0 \ + -e MASS_VHOST_ENABLE=1 \ + -e MASS_VHOST_TLD_SUFFIX=.com \ + -e MASS_VHOST_BACKEND='conf:phpfpm:tcp:php:9000' \ + --link php \ + --link mysql \ + devilbox/apache-2.2 + ``` +5. Create `project-1` + ```bash + mkdir -p www/project-1/htdocs + echo ' www/project-1/htdocs/index.php + ``` +6. Verify `project-1` + ```bash + curl -H 'Host: project-1.com' http://localhost:8080 + ``` +7. Create `another` + ```bash + mkdir -p www/another/htdocs + echo ' www/another/htdocs/index.php + ``` +8. Verify `another` + ```bash + curl -H 'Host: another.com' http://localhost:8080 + ``` +9. Add more projects as you wish... + + + + +## 💡 Docker Compose + +Have a look at the **[examples](../examples/)** directory. It is packed with all kinds of `Docker Compose` examples: + +* SSL +* PHP-FPM remote server +* Python and NodeJS Reverse Proxy +* Mass virtual hosts +* Mass virtual hosts with PHP-FPM, Python and NodeJS as backends diff --git a/doc/features.md b/doc/features.md index 0e97090..2ba1be2 100644 --- a/doc/features.md +++ b/doc/features.md @@ -1,4 +1,6 @@ +[Architecture](architecture.md) | **Features** | +[Examples](examples.md) | [Environment variables](environment-variables.md) | [Volumes](volumes.md) @@ -7,72 +9,102 @@ # Documentation: Features -## Automated virtual hosts +## ☆ Automated mass Virtual hosts -1. Automated virtual hosts can be enabled by providing `-e MASS_VHOST_ENABLE=1`. +1. Automated virtual hosts can be enabled by providing the following environment variable to the docker container `MASS_VHOST_ENABLE=1`. 2. You should mount a local project directory into the Docker under `/shared/httpd` (`-v /local/path:/shared/httpd`). -3. You can optionally specify a global server name suffix via e.g.: `-e MASS_VHOST_TLD=.loc` -4. You can optionally specify a global subdirectory from which the virtual host will servve the documents via e.g.: `-e MASS_VHOST_DOCROOT=www` +3. You can optionally specify a global server name suffix via e.g.: `-e MASS_VHOST_TLD_SUFFIX=.loc` +4. You can optionally specify a global subdirectory from which the virtual host will servve the documents via e.g.: `-e MASS_VHOST_DOCROOT_DIR=www` 5. Allow the Docker to expose its port via `-p 80:80`. 6. Have DNS names point to the IP address the container runs on (e.g. via `/etc/hosts`) With the above described settings, whenever you create a local directory under your projects dir such as `/local/path/mydir`, there will be a new virtual host created by the same name `http://mydir`. You can also specify a global suffix for the vhost names via -`-e MASS_VHOST_TLD=.loc`, afterwards your above created vhost would be reachable via +`-e MASS_VHOST_TLD_SUFFIX=.loc`, afterwards your above created vhost would be reachable via `http://mydir.loc`. Just to give you a few examples: **Assumption:** `/local/path` is mounted to `/shared/httpd` -| Directory | `MASS_VHOST_DOCROOT` | `MASS_VHOST_TLD` | Serving from (*) | Via | -|-----------|----------------------|------------------|--------------------------|----------------------| -| work1/ | htdocs/ | | /local/path/work1/htdocs | http://work1 | -| work1/ | www/ | | /local/path/work1/www | http://work1 | -| work1/ | htdocs/ | .loc | /local/path/work1/htdocs | http://work1.loc | -| work1/ | www/ | .loc | /local/path/work1/www | http://work1.loc | +| Directory | `MASS_VHOST_DOCROOT_DIR` | `MASS_VHOST_TLD_SUFFIX` | Serving from (*) | Via | +|-----------|--------------------------|-------------------------|--------------------------|----------------------| +| work1/ | htdocs/ | | /local/path/work1/htdocs | http://work1 | +| work1/ | www/ | | /local/path/work1/www | http://work1 | +| work1/ | htdocs/ | .loc | /local/path/work1/htdocs | http://work1.loc | +| work1/ | www/ | .loc | /local/path/work1/www | http://work1.loc | (*) This refers to the directory on your host computer **Assumption:** `/tmp` is mounted to `/shared/httpd` -| Directory | `MASS_VHOST_DOCROOT` | `MASS_VHOST_TLD` | Serving from (*) | Via | -|-----------|----------------------|------------------|--------------------------|----------------------| -| api/ | htdocs/ | | /tmp/api/htdocs | http://api | -| api/ | www/ | | /tmp/api/www | http://api | -| api/ | htdocs/ | .test.com | /tmp/api/htdocs | http://api.test.com | -| api/ | www/ | .test.com | /tmp/api/www | http://api.test.com | +| Directory | `MASS_VHOST_DOCROOT_DIR` | `MASS_VHOST_TLD_SUFFIX` | Serving from (*) | Via | +|-----------|--------------------------|-------------------------|--------------------------|----------------------| +| api/ | htdocs/ | | /tmp/api/htdocs | http://api | +| api/ | www/ | | /tmp/api/www | http://api | +| api/ | htdocs/ | .test.com | /tmp/api/htdocs | http://api.test.com | +| api/ | www/ | .test.com | /tmp/api/www | http://api.test.com | (*) This refers to the directory on your host computer You would start it as follows: -```shell +```bash docker run -it \ -p 80:80 \ -e MASS_VHOST_ENABLE=1 \ - -e MASS_VHOST_DOCROOT=www \ - -e MASS_VHOST_TLD=.loc \ + -e MASS_VHOST_DOCROOT_DIR=www \ + -e MASS_VHOST_TLD_SUFFIX=.loc \ -v /local/path:/shared/httpd \ devilbox/apache-2.2 ``` -## Automated PHP-FPM setup -PHP-FPM is not included inside this Docker container, but can be enabled to contact a remote PHP-FPM server. To do so, you must enable it and at least specify the remote PHP-FPM server address (hostname or IP address). Additionally you must mount the data dir under the same path into the PHP-FPM docker container as it is mounted into the web server. +## ☆ Automated PHP-FPM setup -**Note:** When PHP-FPM is enabled, it is enabled for the default virtual host as well as for all other automatically created mass virtual hosts. +PHP-FPM is not included inside this Docker image, but can be enabled to contact a remote PHP-FPM server. To do so, you need to configure one of the two backends (main or mass vhost). +```bash +# Create a test script +mkdir -p www/htdocs +echo ' www/htdocs/index.php +# Start a PHP-FPM server +docker run -d -it \ + --name phpserver \ + -v $(pwd)/www:/var/www/default \ + devilbox/php-fpm:8.2-base -## Customization per virtual host +# Start the webserver +# Where 'phpserver' is the hostname or IP address of the PHP-FPM server +docker run -it \ + -p 80:80 \ + -v $(pwd)/www:/var/www/default \ + -e MAIN_VHOST_BACKEND='conf:phpfpm:tcp:phpserver:9000' \ + --link phpserver \ + devilbox/apache-2.2 +``` + + + +## ☆ Automated Reverse Proxy Setup -Each virtual host is generated from templates by **[vhost-gen](https://github.com/devilbox/vhost-gen/tree/master/etc/templates)**. As `vhost-gen` is really flexible and allows combining multiple templates, you can copy and alter an existing template and then place it in a subdirectory of your project folder. The subdirectory is specified by `MASS_VHOST_TPL`. +Reverse Proxies are configured in a similar way to how PHP-FPM is setup as a remote backend. All you have to do is to specify the backend in the following form: +```bash +MAIN_VHOST_BACKEND:conf:rproxy::: +``` +Where `` can by one of `http` or `https` (depending what your backend provides. `` and `` specify the hostname, IPv4 or IPv6 address of your upstream server, followed by its TCP port. + + + +## ☆ Customization per virtual host + +Each virtual host is generated from templates by **[vhost-gen](https://github.com/devilbox/vhost-gen/tree/master/etc/templates)**. As `vhost-gen` is really flexible and allows combining multiple templates, you can copy and alter an existing template and then place it in a subdirectory of your project folder. The subdirectory is specified by `MASS_VHOST_TEMPLATE_DIR`. **Assumption:** `/local/path` is mounted to `/shared/httpd` -| Directory | `MASS_VHOST_TPL` | Templates are then read from (*) | +| Directory | `MASS_VHOST_TEMPLATE_DIR` | Templates are then read from (*) | |-----------|------------------|------------------------------| | work1/ | cfg/ | /local/path/work1/cfg/ | | api/ | cfg/ | /local/path/api/cfg/ | @@ -82,11 +114,13 @@ Each virtual host is generated from templates by **[vhost-gen](https://github.co (*) This refers to the directory on your host computer -## Customization for the default virtual host -The default virtual host can also be overwritten with a custom template. Use `MAIN_VHOST_TPL` variable in order to set the subdirectory to look for template files. +## ☆ Customization for the default virtual host + +The default virtual host can also be overwritten with a custom template. Use `MAIN_VHOST_TEMPLATE_DIR` variable in order to set the subdirectory to look for template files. + -## Disabling the default virtual host +## ☆ Disabling the default virtual host If you only want to server you custom projects and don't need the default virtual host, you can disable it by `-e MAIN_VHOST_ENABLE=0`. diff --git a/doc/img/httpd-alias-validation.png b/doc/img/httpd-alias-validation.png new file mode 100644 index 0000000..77ca841 Binary files /dev/null and b/doc/img/httpd-alias-validation.png differ diff --git a/doc/img/httpd-backend-invalid-type.png b/doc/img/httpd-backend-invalid-type.png new file mode 100644 index 0000000..ed3e25b Binary files /dev/null and b/doc/img/httpd-backend-invalid-type.png differ diff --git a/doc/img/httpd-backend-rewrite.png b/doc/img/httpd-backend-rewrite.png new file mode 100644 index 0000000..8ff6042 Binary files /dev/null and b/doc/img/httpd-backend-rewrite.png differ diff --git a/doc/img/httpd-backend-unsupported.png b/doc/img/httpd-backend-unsupported.png new file mode 100644 index 0000000..75721e2 Binary files /dev/null and b/doc/img/httpd-backend-unsupported.png differ diff --git a/doc/img/httpd-dir-docroot-template.png b/doc/img/httpd-dir-docroot-template.png new file mode 100644 index 0000000..c2fd92e Binary files /dev/null and b/doc/img/httpd-dir-docroot-template.png differ diff --git a/doc/img/httpd-valid.png b/doc/img/httpd-valid.png new file mode 100644 index 0000000..c8ab2e0 Binary files /dev/null and b/doc/img/httpd-valid.png differ diff --git a/doc/volumes.md b/doc/volumes.md index fbcab57..d8873a7 100644 --- a/doc/volumes.md +++ b/doc/volumes.md @@ -1,4 +1,6 @@ +[Architecture](architecture.md) | [Features](features.md) | +[Examples](examples.md) | [Environment variables](environment-variables.md) | **Volumes** @@ -7,7 +9,7 @@ # Documentation: Volumes -## `/var/www/default/` +## 📂 `/var/www/default/` * **type:** data directory * **purpose:** website files for the default virtual host @@ -26,7 +28,7 @@ docker run -d -it \ ``` -## `/shared/httpd/` +## 📂 `/shared/httpd/` * **type:** data directory * **purpose:** website files for the mass virtual hosts (your projects) @@ -45,7 +47,17 @@ docker run -d -it \ ``` -## `/etc/httpd-custom.d/` +## 📂 `/ca/` + +* **type:** data directory +* **purpose:** populated with CA certificate files + +This directory will be populated by a Certificate Authority, which signs every vhost SSL certificate. If you want to have valid SSL in your browser for every current and future project, simply import the CA files into your browser and/or system. + +**Note:** CA files are not being regenerated if they already exist. You could also place your own CA files in here. + + +## 📂 `/etc/httpd-custom.d/` * **type:** config directory * **purpose:** Add `*.conf` files to alter the webserver behaviour @@ -53,7 +65,7 @@ docker run -d -it \ Mount this directory to your local file system and add any valid `*.conf` files to alter the web server behaviour. -## `/etc/vhost-gen.d/` +## 📂 `/etc/vhost-gen.d/` * **type:** config directory * **purpose:** Add [vhost-gen](https://github.com/devilbox/vhost-gen) templates to alter the webserver behaviour diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..734bcb6 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,14 @@ +# Examples + + +A Quick overview about available examples + +| Complexity | Virtual Host | Backend | Description | Link | +|------------|--------------|---------|-------------|------| +| simple | Default | None | Serve static files | [default-vhost__static-files](default-vhost__static-files) | +| simple | Default | NodsJS | Reverse Proxy to Node app | [default-vhost__reverse-proxy__node](default-vhost__reverse-proxy__node) | +| simple | Default | Python | Reverse Proxy to Python app | [default-vhost__reverse-proxy__python](default-vhost__reverse-proxy__python) | +| simple | Default | PHP-FPM | Serve PHP files | [default-vhost__php-fpm](default-vhost__php-fpm) | +| medium | Default | PHP-FPM | Serve PHP files over HTTPS (SSL) | [default-vhost__php-fpm__ssl](default-vhost__php-fpm__ssl) | +| complex | Mass vhost | PHP-FPM | Mass vhosting with auto-generated SSL for each host | [mass-vhost__php-fpm__ssl](mass-vhost__php-fpm__ssl) | +| complex | Mass vhost | Multi | Mass vhosting with auto-generated SSL for each host (PHP-FPM and NodeJS reverse Proxy) | [mass-vhost__reverse-proxy__ssl/](mass-vhost__reverse-proxy__ssl/) | diff --git a/examples/default-vhost__php-fpm/README.md b/examples/default-vhost__php-fpm/README.md new file mode 100644 index 0000000..26f47bf --- /dev/null +++ b/examples/default-vhost__php-fpm/README.md @@ -0,0 +1,13 @@ +# Example: PHP_FPM + +Docker Compose example with a remote PHP-FPM server. + +## Run +```bash +docker-compose up +``` + +## View +```bash +curl http://localhost:8000 +``` diff --git a/examples/default-vhost__php-fpm/docker-compose.yml b/examples/default-vhost__php-fpm/docker-compose.yml new file mode 100644 index 0000000..7930745 --- /dev/null +++ b/examples/default-vhost__php-fpm/docker-compose.yml @@ -0,0 +1,32 @@ +--- +version: '2.3' + +services: + + # HTTPD Server + httpd: + image: devilbox/apache-2.2:alpine + build: + context: ../../Dockerfiles + dockerfile: Dockerfile.alpine + hostname: httpd + environment: + - NEW_UID=1000 + - NEW_GID=1000 + - MAIN_VHOST_BACKEND=conf:phpfpm:tcp:php:9000 + ports: + - "8000:80" + volumes: + - ./www:/var/www/default/htdocs + depends_on: + - php + + # PHP-FPM Server + php: + image: devilbox/php-fpm:8.2-base + hostname: php + environment: + - NEW_UID=1000 + - NEW_GID=1000 + volumes: + - ./www:/var/www/default/htdocs diff --git a/examples/default-vhost__php-fpm/integration-test.sh b/examples/default-vhost__php-fpm/integration-test.sh new file mode 100755 index 0000000..13a9bd4 --- /dev/null +++ b/examples/default-vhost__php-fpm/integration-test.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +docker-compose build +docker-compose up -d +sleep 10 + +if ! curl http://localhost:8000 | grep '[OK]'; then + docker-compose logs || true + docker-compose stop || true + docker-compose rm -f || true + exit 1 +fi + +docker-compose logs || true +docker-compose stop || true +docker-compose rm -f || true diff --git a/examples/default-vhost__php-fpm/www/index.php b/examples/default-vhost__php-fpm/www/index.php new file mode 100644 index 0000000..df33e4e --- /dev/null +++ b/examples/default-vhost__php-fpm/www/index.php @@ -0,0 +1,2 @@ +[OK] +

PHP version:

diff --git a/examples/default-vhost__php-fpm__ssl/README.md b/examples/default-vhost__php-fpm__ssl/README.md new file mode 100644 index 0000000..42c36d7 --- /dev/null +++ b/examples/default-vhost__php-fpm__ssl/README.md @@ -0,0 +1,17 @@ +# Example: PHP_FPM + +Docker Compose example with a remote PHP-FPM server and serving HTTPS + +## Run +```bash +docker-compose up +``` + +## View +```bash +# HTTP +curl http://localhost:8000 + +# HTTPS +curl -k https://localhost:8443 +``` diff --git a/examples/default-vhost__php-fpm__ssl/docker-compose.yml b/examples/default-vhost__php-fpm__ssl/docker-compose.yml new file mode 100644 index 0000000..77845f2 --- /dev/null +++ b/examples/default-vhost__php-fpm__ssl/docker-compose.yml @@ -0,0 +1,34 @@ +--- +version: '2.3' + +services: + + # HTTPD Server + httpd: + image: devilbox/apache-2.2:alpine + build: + context: ../../Dockerfiles + dockerfile: Dockerfile.alpine + hostname: httpd + environment: + - NEW_UID=1000 + - NEW_GID=1000 + - MAIN_VHOST_SSL_TYPE=both + - MAIN_VHOST_BACKEND=conf:phpfpm:tcp:php:9000 + ports: + - "8000:80" + - "8443:443" + volumes: + - ./www:/var/www/default/htdocs + depends_on: + - php + + # PHP-FPM Server + php: + image: devilbox/php-fpm:8.2-base + hostname: php + environment: + - NEW_UID=1000 + - NEW_GID=1000 + volumes: + - ./www:/var/www/default/htdocs diff --git a/examples/default-vhost__php-fpm__ssl/integration-test.sh b/examples/default-vhost__php-fpm__ssl/integration-test.sh new file mode 100755 index 0000000..dd41bcd --- /dev/null +++ b/examples/default-vhost__php-fpm__ssl/integration-test.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +docker-compose build +docker-compose up -d +sleep 10 + +if ! curl http://localhost:8000 | grep '[OK]'; then + docker-compose logs || true + docker-compose stop || true + docker-compose rm -f || true + exit 1 +fi +if ! curl -k https://localhost:8443 | grep '[OK]'; then + docker-compose logs || true + docker-compose stop || true + docker-compose rm -f || true + exit 1 +fi + +docker-compose logs || true +docker-compose stop || true +docker-compose rm -f || true diff --git a/examples/default-vhost__php-fpm__ssl/www/index.php b/examples/default-vhost__php-fpm__ssl/www/index.php new file mode 100644 index 0000000..df33e4e --- /dev/null +++ b/examples/default-vhost__php-fpm__ssl/www/index.php @@ -0,0 +1,2 @@ +[OK] +

PHP version:

diff --git a/examples/default-vhost__reverse-proxy__node/README.md b/examples/default-vhost__reverse-proxy__node/README.md new file mode 100644 index 0000000..4343c12 --- /dev/null +++ b/examples/default-vhost__reverse-proxy__node/README.md @@ -0,0 +1,17 @@ +# Example: Reverse Proxy (NodeJS) + +Docker Compose example with HTTPD acting as a Reverse Proxy and a remote NodeJS server. + +## Run +```bash +docker-compose up +``` + +## View +```bash +# HTTP +curl http://localhost:8000 + +# HTTPS +curl -k https://localhost:8443 +``` diff --git a/examples/default-vhost__reverse-proxy__node/docker-compose.yml b/examples/default-vhost__reverse-proxy__node/docker-compose.yml new file mode 100644 index 0000000..7c1798c --- /dev/null +++ b/examples/default-vhost__reverse-proxy__node/docker-compose.yml @@ -0,0 +1,28 @@ +--- +version: '2.3' + +services: + + # HTTPD Server + httpd: + image: devilbox/apache-2.2:alpine + build: + context: ../../Dockerfiles + dockerfile: Dockerfile.alpine + hostname: httpd + environment: + - MAIN_VHOST_BACKEND=conf:rproxy:http:node:3000 + - MAIN_VHOST_SSL_TYPE=both + ports: + - "8000:80" + - "8443:443" + depends_on: + - node + + # NodeJS Server + node: + image: node:19-alpine + hostname: node + command: node /app/app.js + volumes: + - ./www:/app diff --git a/examples/default-vhost__reverse-proxy__node/integration-test.sh b/examples/default-vhost__reverse-proxy__node/integration-test.sh new file mode 100755 index 0000000..dd41bcd --- /dev/null +++ b/examples/default-vhost__reverse-proxy__node/integration-test.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +docker-compose build +docker-compose up -d +sleep 10 + +if ! curl http://localhost:8000 | grep '[OK]'; then + docker-compose logs || true + docker-compose stop || true + docker-compose rm -f || true + exit 1 +fi +if ! curl -k https://localhost:8443 | grep '[OK]'; then + docker-compose logs || true + docker-compose stop || true + docker-compose rm -f || true + exit 1 +fi + +docker-compose logs || true +docker-compose stop || true +docker-compose rm -f || true diff --git a/examples/default-vhost__reverse-proxy__node/www/app.js b/examples/default-vhost__reverse-proxy__node/www/app.js new file mode 100644 index 0000000..3e01719 --- /dev/null +++ b/examples/default-vhost__reverse-proxy__node/www/app.js @@ -0,0 +1,9 @@ +const http = require('http'); +const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.write('[OK]\n'); + res.write('NodeJS is running\n'); + res.end(); +}); +server.listen(3000, '0.0.0.0'); diff --git a/examples/default-vhost__reverse-proxy__python/README.md b/examples/default-vhost__reverse-proxy__python/README.md new file mode 100644 index 0000000..bf7dde5 --- /dev/null +++ b/examples/default-vhost__reverse-proxy__python/README.md @@ -0,0 +1,17 @@ +# Example: Reverse Proxy (Python) + +Docker Compose example with HTTPD acting as a Reverse Proxy and a remote Python server. + +## Run +```bash +docker-compose up +``` + +## View +```bash +# HTTP +curl http://localhost:8000 + +# HTTPS +curl -k https://localhost:8443 +``` diff --git a/examples/default-vhost__reverse-proxy__python/docker-compose.yml b/examples/default-vhost__reverse-proxy__python/docker-compose.yml new file mode 100644 index 0000000..cc29329 --- /dev/null +++ b/examples/default-vhost__reverse-proxy__python/docker-compose.yml @@ -0,0 +1,28 @@ +--- +version: '2.3' + +services: + + # HTTPD Server + httpd: + image: devilbox/apache-2.2:alpine + build: + context: ../../Dockerfiles + dockerfile: Dockerfile.alpine + hostname: httpd + environment: + - MAIN_VHOST_BACKEND=conf:rproxy:http:python:3000 + - MAIN_VHOST_SSL_TYPE=both + ports: + - "8000:80" + - "8443:443" + depends_on: + - python + + # Python Server + python: + image: python:3-alpine + hostname: python + command: sh -c "pip install aiohttp==3.8.3; python -u /app/server.py" + volumes: + - ./www:/app diff --git a/examples/default-vhost__reverse-proxy__python/integration-test.sh b/examples/default-vhost__reverse-proxy__python/integration-test.sh new file mode 100755 index 0000000..dd41bcd --- /dev/null +++ b/examples/default-vhost__reverse-proxy__python/integration-test.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +docker-compose build +docker-compose up -d +sleep 10 + +if ! curl http://localhost:8000 | grep '[OK]'; then + docker-compose logs || true + docker-compose stop || true + docker-compose rm -f || true + exit 1 +fi +if ! curl -k https://localhost:8443 | grep '[OK]'; then + docker-compose logs || true + docker-compose stop || true + docker-compose rm -f || true + exit 1 +fi + +docker-compose logs || true +docker-compose stop || true +docker-compose rm -f || true diff --git a/examples/default-vhost__reverse-proxy__python/www/server.py b/examples/default-vhost__reverse-proxy__python/www/server.py new file mode 100644 index 0000000..87cf4cd --- /dev/null +++ b/examples/default-vhost__reverse-proxy__python/www/server.py @@ -0,0 +1,11 @@ +from aiohttp import web + +async def handle(request): + response = '[OK]\nHello from Python\n' + return web.Response(text=response) + +app = web.Application() +app.router.add_get('/', handle) +app.router.add_get('/{name}', handle) + +web.run_app(app, port=3000) diff --git a/examples/default-vhost__static-files/README.md b/examples/default-vhost__static-files/README.md new file mode 100644 index 0000000..44b8cf0 --- /dev/null +++ b/examples/default-vhost__static-files/README.md @@ -0,0 +1,13 @@ +# Example: Static files + +Docker Compose example with only serving static files. + +## Run +```bash +docker-compose up +``` + +## View +```bash +curl http://localhost:8000 +``` diff --git a/examples/default-vhost__static-files/docker-compose.yml b/examples/default-vhost__static-files/docker-compose.yml new file mode 100644 index 0000000..3cbb320 --- /dev/null +++ b/examples/default-vhost__static-files/docker-compose.yml @@ -0,0 +1,19 @@ +--- +version: '2.3' + +services: + + # HTTPD Server + httpd: + image: devilbox/apache-2.2:alpine + build: + context: ../../Dockerfiles + dockerfile: Dockerfile.alpine + hostname: httpd + environment: + - NEW_UID=1000 + - NEW_GID=1000 + ports: + - "8000:80" + volumes: + - ./www:/var/www/default/htdocs diff --git a/examples/default-vhost__static-files/integration-test.sh b/examples/default-vhost__static-files/integration-test.sh new file mode 100755 index 0000000..13a9bd4 --- /dev/null +++ b/examples/default-vhost__static-files/integration-test.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +docker-compose build +docker-compose up -d +sleep 10 + +if ! curl http://localhost:8000 | grep '[OK]'; then + docker-compose logs || true + docker-compose stop || true + docker-compose rm -f || true + exit 1 +fi + +docker-compose logs || true +docker-compose stop || true +docker-compose rm -f || true diff --git a/examples/default-vhost__static-files/www/index.html b/examples/default-vhost__static-files/www/index.html new file mode 100644 index 0000000..a18243f --- /dev/null +++ b/examples/default-vhost__static-files/www/index.html @@ -0,0 +1,2 @@ +[OK] +

It works!

diff --git a/examples/integration-test.sh b/examples/integration-test.sh new file mode 100755 index 0000000..a41cd16 --- /dev/null +++ b/examples/integration-test.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +SCRIPTPATH="$( cd "$(dirname "${BASH_SOURCE[0]}")" && pwd )" + +cd "${SCRIPTPATH}" +# shellcheck disable=SC2035,SC2045 +for test_dir in $(ls -1 -d */);do + echo "################################################################################" + echo "${test_dir}" + echo "################################################################################" + cd "${SCRIPTPATH}/${test_dir}" + if ! ./integration-test.sh; then + exit 1 + fi +done diff --git a/examples/mass-vhost__php-fpm__ssl/README.md b/examples/mass-vhost__php-fpm__ssl/README.md new file mode 100644 index 0000000..b2a3cb1 --- /dev/null +++ b/examples/mass-vhost__php-fpm__ssl/README.md @@ -0,0 +1,81 @@ +# Example: PHP_FPM + +Docker Compose example with a remote PHP-FPM server. + +This example uses mass virtual hosting, i.e.: it creates **as many virtual hosts automatically as directories exist**. +This happens either during startup (initial setup) and also during run-time, whenever directories are created, renamed or removed. +It will also provide **SSL capable vhosts** that you can view in your browser **without SSL certificate warnings.** + + +Try it out yourself and add a directory into the [projects](projects/) directory. As soon as you create one, a new virtual host will be created. Keep in mind that files are being served from the `htdocs` directory within your newly created project. (The `htdocs` directory can also be a symlink). + +The `MASS_VHOST_TLD_SUFFIX` is set to `.loc`, so the project vhost name (its domain) will be: `.loc` + +## Example 1: During Startup + +The [projects](projects/) directory already contains two projects: + * `sample` + * `test` + +That means that during startup, two vhosts will be created: + * `sample.loc` + * `test.loc` + +The files for each vhost are being served from: + * `projects/sample/htdocs` (where `htdocs` symlinks to `src/`) + * `projects/test/htdocs` + +You can reach those two projects via: +```bash +# Ensure docker-compose is running +docker-compose up + +# Now verify +curl http://localhost:8000 -H 'host: sample.loc' +curl http://localhost:8000 -H 'host: test.loc' +``` + +## Example 2: During Run-time + +In this example we add more projects during run-time +```bash +# Ensure docker-compose is running +docker-compose up +``` + +Now as the HTTP and PHP container are up and running, we can add more projects: +``` +# Create project directory +# This will auto-create a new vhost 'it-works.tld' +mkdir projects/it-works/ + +# Add some code +mkdir projects/it-works/htdocs +echo '' > projects/it-works/htdocs/index.php +``` +Now you can access it via: +```bash +curl http://localhost:8000 -H 'host: it-works.loc' +``` + +**Note:** The other two vhosts from Example 1 are still available. + + +## Example 3: SSL and Browser access + +You migth have noticed the `ca/` directory. The HTTPD container also creates SSL certificates for all of the above described vhosts (and any you will create during startup- or run-time). +This is done via a certificate authority, so that each vhost certificate was signed by a CA. + +**What is the benefit?** + +1. You can import the CA files in the `ca/` directory into your browser +2. Add `/etc/hosts` entries: + ```bash + 127.0.0.1 sample.loc + 127.0.0.1 test.loc + 127.0.0.1 it-works.loc + ``` +3. Access them through your browser via valid https + * `https://sample.loc:8443 + * `https://test.loc:8443 + * `https://it-works.loc:8443 diff --git a/examples/mass-vhost__php-fpm__ssl/ca/.keep b/examples/mass-vhost__php-fpm__ssl/ca/.keep new file mode 100644 index 0000000..e69de29 diff --git a/examples/mass-vhost__php-fpm__ssl/docker-compose.yml b/examples/mass-vhost__php-fpm__ssl/docker-compose.yml new file mode 100644 index 0000000..fd2c7a4 --- /dev/null +++ b/examples/mass-vhost__php-fpm__ssl/docker-compose.yml @@ -0,0 +1,39 @@ +--- +version: '2.3' + +services: + + # HTTPD Server + httpd: + image: devilbox/apache-2.2:alpine + build: + context: ../../Dockerfiles + dockerfile: Dockerfile.alpine + hostname: httpd + environment: + - NEW_UID=1000 + - NEW_GID=1000 + - DOCKER_LOGS=1 + - MAIN_VHOST_ENABLE=0 + - MASS_VHOST_ENABLE=1 + - MASS_VHOST_BACKEND=conf:phpfpm:tcp:php:9000 + - MASS_VHOST_TLD_SUFFIX=.loc + - MASS_VHOST_SSL_TYPE=both + ports: + - "8000:80" + - "8443:443" + volumes: + - ./projects:/shared/httpd + - ./ca:/ca + depends_on: + - php + + # PHP-FPM Server + php: + image: devilbox/php-fpm:8.2-base + hostname: php + environment: + - NEW_UID=1000 + - NEW_GID=1000 + volumes: + - ./projects:/shared/httpd diff --git a/examples/mass-vhost__php-fpm__ssl/integration-test.sh b/examples/mass-vhost__php-fpm__ssl/integration-test.sh new file mode 100755 index 0000000..5b098c1 --- /dev/null +++ b/examples/mass-vhost__php-fpm__ssl/integration-test.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +docker-compose build +docker-compose up -d +sleep 10 + +if ! curl http://localhost:8000 -H 'Host:sample.loc' | grep '[OK]'; then + docker-compose logs || true + docker-compose stop || true + docker-compose rm -f || true + exit 1 +fi +if ! curl -k https://localhost:8443 -H 'Host:sample.loc' | grep '[OK]'; then + docker-compose logs || true + docker-compose stop || true + docker-compose rm -f || true + exit 1 +fi + +if ! curl http://localhost:8000 -H 'Host:test.loc' | grep '[OK]'; then + docker-compose logs || true + docker-compose stop || true + docker-compose rm -f || true + exit 1 +fi +if ! curl -k https://localhost:8443 -H 'Host:test.loc' | grep '[OK]'; then + docker-compose logs || true + docker-compose stop || true + docker-compose rm -f || true + exit 1 +fi + +docker-compose logs || true +docker-compose stop || true +docker-compose rm -f || true diff --git a/examples/mass-vhost__php-fpm__ssl/projects/sample/htdocs b/examples/mass-vhost__php-fpm__ssl/projects/sample/htdocs new file mode 120000 index 0000000..e831038 --- /dev/null +++ b/examples/mass-vhost__php-fpm__ssl/projects/sample/htdocs @@ -0,0 +1 @@ +src \ No newline at end of file diff --git a/examples/mass-vhost__php-fpm__ssl/projects/sample/src/index.php b/examples/mass-vhost__php-fpm__ssl/projects/sample/src/index.php new file mode 100644 index 0000000..1962a1f --- /dev/null +++ b/examples/mass-vhost__php-fpm__ssl/projects/sample/src/index.php @@ -0,0 +1,4 @@ +[OK] +[PROJECT: sample] +[DOMAIN: sample.loc] +

PHP version:

diff --git a/examples/mass-vhost__php-fpm__ssl/projects/test/htdocs/index.php b/examples/mass-vhost__php-fpm__ssl/projects/test/htdocs/index.php new file mode 100644 index 0000000..9959559 --- /dev/null +++ b/examples/mass-vhost__php-fpm__ssl/projects/test/htdocs/index.php @@ -0,0 +1,4 @@ +[OK] +[PROJECT: test] +[DOMAIN: test.loc] +

PHP version:

diff --git a/examples/mass-vhost__reverse-proxy__ssl/README.md b/examples/mass-vhost__reverse-proxy__ssl/README.md new file mode 100644 index 0000000..da8af20 --- /dev/null +++ b/examples/mass-vhost__reverse-proxy__ssl/README.md @@ -0,0 +1,8 @@ +# Example: Multi + +Docker Compose example with different backends defined in each respective project directory. + +Show-cased backends: + +* PHP-FPM +* NodeJS diff --git a/examples/mass-vhost__reverse-proxy__ssl/ca/.keep b/examples/mass-vhost__reverse-proxy__ssl/ca/.keep new file mode 100644 index 0000000..e69de29 diff --git a/examples/mass-vhost__reverse-proxy__ssl/docker-compose.yml b/examples/mass-vhost__reverse-proxy__ssl/docker-compose.yml new file mode 100644 index 0000000..c2ec191 --- /dev/null +++ b/examples/mass-vhost__reverse-proxy__ssl/docker-compose.yml @@ -0,0 +1,49 @@ +--- +version: '2.3' + +services: + + # HTTPD Server + httpd: + image: devilbox/apache-2.2:alpine + build: + context: ../../Dockerfiles + dockerfile: Dockerfile.alpine + hostname: httpd + environment: + - NEW_UID=1000 + - NEW_GID=1000 + - MAIN_VHOST_ENABLE=1 + - MAIN_VHOST_SSL_TYPE=both + - MASS_VHOST_ENABLE=1 + - MASS_VHOST_BACKEND=file:backend.txt + - MASS_VHOST_TLD_SUFFIX=.loc + - MASS_VHOST_SSL_TYPE=both + ports: + - "8000:80" + - "8443:443" + volumes: + - ./intranet:/var/www/default/htdocs + - ./projects:/shared/httpd + - ./ca:/ca + depends_on: + - node + - php + + # NodeJS Server + node: + image: node:19-alpine + hostname: node + command: node /app/app.js + volumes: + - ./projects/node/src:/app + + # PHP-FPM Server + php: + image: devilbox/php-fpm:8.2-base + hostname: php + environment: + - NEW_UID=1000 + - NEW_GID=1000 + volumes: + - ./projects:/shared/httpd diff --git a/examples/mass-vhost__reverse-proxy__ssl/integration-test.sh b/examples/mass-vhost__reverse-proxy__ssl/integration-test.sh new file mode 100755 index 0000000..634efc4 --- /dev/null +++ b/examples/mass-vhost__reverse-proxy__ssl/integration-test.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +docker-compose build +docker-compose up -d +sleep 10 + +if ! curl http://localhost:8000 -H 'Host:node.loc' | grep '[OK]'; then + docker-compose logs || true + docker-compose stop || true + docker-compose rm -f || true + exit 1 +fi +if ! curl -k https://localhost:8443 -H 'Host:node.loc' | grep '[OK]'; then + docker-compose logs || true + docker-compose stop || true + docker-compose rm -f || true + exit 1 +fi + +if ! curl http://localhost:8000 -H 'Host:php.loc' | grep '[OK]'; then + docker-compose logs || true + docker-compose stop || true + docker-compose rm -f || true + exit 1 +fi +if ! curl -k https://localhost:8443 -H 'Host:php.loc' | grep '[OK]'; then + docker-compose logs || true + docker-compose stop || true + docker-compose rm -f || true + exit 1 +fi + +docker-compose logs || true +docker-compose stop || true +docker-compose rm -f || true diff --git a/examples/mass-vhost__reverse-proxy__ssl/intranet/index.html b/examples/mass-vhost__reverse-proxy__ssl/intranet/index.html new file mode 100644 index 0000000..4667ed5 --- /dev/null +++ b/examples/mass-vhost__reverse-proxy__ssl/intranet/index.html @@ -0,0 +1,14 @@ +[OK] +

Intranet

+ +

This is the default virtual host.

+ +

See NodeJS example via reverse Proxy over SSL

+
+curl -k https://localhost -H 'Host: node.loc'
+
+ +

See PHP example via PHP-FPM over SSL

+
+curl -k https://localhost -H 'Host: php.loc'
+
diff --git a/examples/mass-vhost__reverse-proxy__ssl/projects/node/REAMDE.md b/examples/mass-vhost__reverse-proxy__ssl/projects/node/REAMDE.md new file mode 100644 index 0000000..e69de29 diff --git a/examples/mass-vhost__reverse-proxy__ssl/projects/node/cfg/backend.txt b/examples/mass-vhost__reverse-proxy__ssl/projects/node/cfg/backend.txt new file mode 100644 index 0000000..69b12e3 --- /dev/null +++ b/examples/mass-vhost__reverse-proxy__ssl/projects/node/cfg/backend.txt @@ -0,0 +1 @@ +conf:rproxy:http:node:3000 diff --git a/examples/mass-vhost__reverse-proxy__ssl/projects/node/src/app.js b/examples/mass-vhost__reverse-proxy__ssl/projects/node/src/app.js new file mode 100644 index 0000000..3e01719 --- /dev/null +++ b/examples/mass-vhost__reverse-proxy__ssl/projects/node/src/app.js @@ -0,0 +1,9 @@ +const http = require('http'); +const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.write('[OK]\n'); + res.write('NodeJS is running\n'); + res.end(); +}); +server.listen(3000, '0.0.0.0'); diff --git a/examples/mass-vhost__reverse-proxy__ssl/projects/php/README.md b/examples/mass-vhost__reverse-proxy__ssl/projects/php/README.md new file mode 100644 index 0000000..e69de29 diff --git a/examples/mass-vhost__reverse-proxy__ssl/projects/php/cfg/backend.txt b/examples/mass-vhost__reverse-proxy__ssl/projects/php/cfg/backend.txt new file mode 100644 index 0000000..b07e9ab --- /dev/null +++ b/examples/mass-vhost__reverse-proxy__ssl/projects/php/cfg/backend.txt @@ -0,0 +1 @@ +conf:phpfpm:tcp:php:9000 diff --git a/examples/mass-vhost__reverse-proxy__ssl/projects/php/htdocs/index.php b/examples/mass-vhost__reverse-proxy__ssl/projects/php/htdocs/index.php new file mode 100644 index 0000000..2c57e96 --- /dev/null +++ b/examples/mass-vhost__reverse-proxy__ssl/projects/php/htdocs/index.php @@ -0,0 +1,2 @@ +[OK] +PHP-FPM Host with PHP ' . phpversion() . ''; ?> diff --git a/tests/.lib.sh b/tests/.lib.sh index 42138a4..c8793d3 100755 --- a/tests/.lib.sh +++ b/tests/.lib.sh @@ -11,17 +11,47 @@ set -o pipefail run() { _cmd="${1}" - _red="\033[0;31m" - _green="\033[0;32m" - _yellow="\033[0;33m" + #_red="\033[0;31m" + _gray="\033[38;5;244m" + #_green="\033[0;32m" + #_yellow="\033[0;33m" _reset="\033[0m" - _user="$(whoami)" + #_user="$(whoami)" - printf "${_yellow}[%s] ${_red}%s \$ ${_green}${_cmd}${_reset}\n" "$(hostname)" "${_user}" + printf "${_gray}%s${_cmd}${_reset}\n" "\$ " sh -c "LANG=C LC_ALL=C ${_cmd}" } +log() { + local type="${1}" + local message="${2}" + + local clr_gray="\033[38;5;244m" + local clr_green="\033[0;32m" + local clr_red="\033[0;31m" + local clr_rst="\033[0m" + + if [ "${type}" = "fail" ]; then + printf "${clr_red}%s${clr_rst}\n" "--------------------------------------------------------------------------------" 1>&2 + printf "${clr_red}[FAIL] %s${clr_rst}\n" "${message}" 1>&2 + printf "${clr_red}%s${clr_rst}\n" "--------------------------------------------------------------------------------" 1>&2 + elif [ "${type}" = "ok" ]; then + #printf "${clr_green}%s${clr_rst}\n" "--------------------------------------------------------------------------------" 1>&2 + printf "${clr_green}[SUCC] %s${clr_rst}\n" "${message}" + #printf "${clr_green}%s${clr_rst}\n" "--------------------------------------------------------------------------------" 1>&2 + elif [ "${type}" = "loop" ]; then + printf "${clr_gray}%s${clr_rst}\n" "--------------------------------------------------------------------------------" 1>&2 + printf "${clr_gray}[LOOP] %s${clr_rst}\n" "${message}" + printf "${clr_gray}%s${clr_rst}\n" "--------------------------------------------------------------------------------" 1>&2 + else + printf "${clr_red}%s${clr_rst}\n" "--------------------------------------------------------------------------------" 1>&2 + printf "${clr_red}[SYS] %s${clr_rst}\n" "Unknown log type: ${type}" 1>&1 + printf "${clr_red}%s${clr_rst}\n" "--------------------------------------------------------------------------------" 1>&2 + return 1 + fi +} + ### ### Get 15 character random word @@ -39,3 +69,124 @@ function get_random_name() { done echo "${name}" } + +tmp_dir() { + local tmp_dir= + tmp_dir="$( mktemp -d )" + chmod 755 "${tmp_dir}" + echo "${tmp_dir}" +} + + +docker_logs() { + local container_name="${1}" + docker logs "${container_name}" || true +} + +docker_stop() { + local container_name="${1}" + docker stop "${container_name}" >/dev/null 2>&1 || true + docker rm -f "${container_name}" >/dev/null 2>&1 || true +} + + +# -------------------------------------------------------------------------------------------------- +# TESTS +# -------------------------------------------------------------------------------------------------- + +### +### Application 1 +### +create_app() { + local path="${1}" + local docr="${2}" + local name="${3}" + local file="${4}" + local cont="${5}" + + run "mkdir -p ${path}/${name}/${docr}" + run "echo \"${cont}\" > ${path}/${name}/${docr}/${file}" +} + + + +### +### Find expected string in URL +### +test_vhost_response() { + local expect="${1}" + local url="${2}" + local header="${3:-}" + + #local clr_gray="\033[38;5;244m" + local clr_rst="\033[0m" + local clr_test="\033[0;34m" # blue + + + printf "${clr_test}%s${clr_rst}\n" "--------------------------------------------------------------------------------" 1>&2 + printf "${clr_test}[TEST] %s${clr_rst}\n" "Exec: curl -sS -k -L '${url}' -H '${header}'" + printf "${clr_test}[TEST] %s${clr_rst}" "Find: '${expect}' " + + count=0 + retry=30 + output="" + while ! output="$( sh -c "LANG=C LC_ALL=C curl --fail -sS -k -L '${url}' -H '${header}' 2>/dev/null | grep '^${expect}$'" )"; do + printf "." + if [ "${count}" = "${retry}" ]; then + printf "\\n" + sh -c "LANG=C LC_ALL=C curl -v --fail -sS -k -L '${url}' -H '${header}'" || true + return 1 + fi + count=$(( count + 1 )) + sleep 1 + done + + # Print success + printf "\\n" + log "ok" "Resp: '${output}'" + echo +} + + +### +### Check docker logs for Errors +### +test_docker_logs_err() { + local container_name="${1}" + + local re_internal_upper='(\[(FAILURE|FAILED|FAIL|FATAL|ERROR|ERR|WARNING|WARN)\])' + local re_internal_lower='(\[(failure|failed|fail|fatal|error|err|warning|warn)\])' + local re_upper='(FAULT|FAIL|FATAL|ERROR|WARN)' + local re_lower='(segfault|fail|fatal|error|warn)' + local re_mixed='([Ss]egfault|[Ff]ail|[Ff]atal|[Ww]arn)' + local regex="${re_internal_upper}|${re_internal_lower}|${re_upper}|${re_lower}|${re_mixed}" + + # Ignore this pattern + local ignore1='creating Certificate Authority' + local ignore2='error_log' # nginx error log directive + local ignore3="fastcgi_intercept_errors" # nginx + local ignore4='LogLevel' # Apache logging directive + local ignore5='ErrorLog' # Apache error log directive + local ignore6='# Possible values' # Apache httpd.conf contains a comment with verbosity levels + local ignore7='# consult the online' # Apache httpd.conf contains a comment with warning + local ignore8='stackoverflow' # contains a link comment with 'error' in url + local ignore9='\[warn\] NameVirtualHost' # Apache specific, when massvhost projects are not yet loaded + local ignore="${ignore1}|${ignore2}|${ignore3}|${ignore4}|${ignore5}|${ignore6}|${ignore7}|${ignore8}|${ignore9}" + + #local clr_gray="\033[38;5;244m" + local clr_test="\033[0;34m" # blue + local clr_rst="\033[0m" + + printf "${clr_test}%s${clr_rst}\n" "--------------------------------------------------------------------------------" 1>&2 + printf "${clr_test}[TEST] %s${clr_rst}\n" "Exec: docker logs ${container_name}" + printf "${clr_test}[TEST] %s${clr_rst}\n" "Find: '${regex}'" + + if docker logs "${container_name}" 2>&1 | grep -Ev "${ignore}" | grep -E "${regex}" >/dev/null; then + log "fail" "Found: $( docker logs "${container_name}" 2>&1 | grep -Ev "${ignore}" | grep -E "${regex}" )" + return 1 + fi + + # Print success + log "ok" "Resp: Nothing found in docker logs" + echo +} diff --git a/tests/00-test-html.sh b/tests/00-test-html.sh deleted file mode 100755 index 116353f..0000000 --- a/tests/00-test-html.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - -CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" - -IMAGE="${1}" -#NAME="${2}" -#VERSION="${3}" -TAG="${4}" -ARCH="${5}" - - -HOST_PORT="8093" - -### -### Load Library -### -# shellcheck disable=SC1091 -. "${CWD}/.lib.sh" - - - -### -### Preparation -### -RAND_DIR="$( mktemp -d )" -RAND_NAME="$( get_random_name )" -run "echo \"hello world via html\" > ${RAND_DIR}/index.html" - - -### -### Startup container -### -run "docker run --rm --platform ${ARCH} \ - -v ${RAND_DIR}:/var/www/default/htdocs \ - -p 127.0.0.1:${HOST_PORT}:80 \ - -e DEBUG_ENTRYPOINT=2 \ - -e DEBUG_RUNTIME=1 \ - -e NEW_UID=$( id -u ) \ - -e NEW_GID=$( id -g ) \ - --name ${RAND_NAME} ${IMAGE}:${TAG} &" - - -### -### Tests -### -WAIT=120 -INDEX=0 -printf "Testing connectivity" -while ! curl -sS "http://localhost:${HOST_PORT}" 2>/dev/null | grep 'hello world via html'; do - printf "." - if [ "${INDEX}" = "${WAIT}" ]; then - printf "\\n" - run "docker logs ${RAND_NAME}" || true - run "docker stop ${RAND_NAME}" || true - echo "Error" - exit 1 - fi - INDEX=$(( INDEX + 1 )) - sleep 1 -done -printf "\\n[OK] Test success\\n" - -### -### Cleanup -### -run "docker stop ${RAND_NAME}" diff --git a/tests/001-test-xargs.sh b/tests/001-test-xargs.sh new file mode 100755 index 0000000..0dbf8e8 --- /dev/null +++ b/tests/001-test-xargs.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" + +IMAGE="${1}" +TAG="${2}" +ARCH="${3}" + + +### +### Load Library +### +# shellcheck disable=SC1090,SC1091 +. "${CWD}/.lib.sh" + + + +#--------------------------------------------------------------------------------------------------- +# MAIN ENTRYPOINT +#--------------------------------------------------------------------------------------------------- + +### +### Globals +### +NAME_HTTPD="$( get_random_name )" + + +### +### Start HTTPD Container +### +if ! run "docker run --platform ${ARCH} --name ${NAME_HTTPD} \ +-e DEBUG_ENTRYPOINT=4 \ +-e DEBUG_RUNTIME=1 \ +--entrypoint=bash \ +${IMAGE}:${TAG} -c 'command -v xargs >/dev/null'"; then + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_HTTPD}" + log "fail" "command 'xargs' not found inside image, but required" + exit 1 +fi +echo "command 'xargs' found" + + +### +### Cleanup +### +docker_stop "${NAME_HTTPD}" +log "ok" "Test succeeded" diff --git a/tests/01-test-php.sh b/tests/01-test-php.sh deleted file mode 100755 index 3a34141..0000000 --- a/tests/01-test-php.sh +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - -CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" - -IMAGE="${1}" -#NAME="${2}" -#VERSION="${3}" -TAG="${4}" -ARCH="${5}" - - -HOST_PORT="8093" - -### -### Load Library -### -# shellcheck disable=SC1091 -. "${CWD}/.lib.sh" - - - -### -### Preparation -### -RAND_DIR="$( mktemp -d )" -RAND_NAME1="$( get_random_name )" -RAND_NAME2="$( get_random_name )" -run "chmod 0755 ${RAND_DIR}" -run "echo \" ${RAND_DIR}/index.php" - - -### -### Startup container -### -run "docker run -d --rm --platform ${ARCH} \ - -v ${RAND_DIR}:/var/www/default/htdocs \ - --name ${RAND_NAME1} \ - devilbox/php-fpm-8.1" - -run "docker run --rm --platform ${ARCH} \ - -v ${RAND_DIR}:/var/www/default/htdocs \ - -p 127.0.0.1:${HOST_PORT}:80 \ - -e DEBUG_ENTRYPOINT=2 \ - -e DEBUG_RUNTIME=1 \ - -e NEW_UID=$( id -u ) \ - -e NEW_GID=$( id -g ) \ - -e PHP_FPM_ENABLE=1 \ - -e PHP_FPM_SERVER_ADDR=${RAND_NAME1} \ - -e PHP_FPM_SERVER_PORT=9000 \ - --link ${RAND_NAME1} \ - --name ${RAND_NAME2} \ - ${IMAGE}:${TAG} &" - - -### -### Tests -### -WAIT=120 -INDEX=0 -printf "Testing connectivity" -while ! curl -sS "http://localhost:${HOST_PORT}" 2>/dev/null | grep 'hello world php'; do - printf "." - if [ "${INDEX}" = "${WAIT}" ]; then - printf "\\n" - run "docker logs ${RAND_NAME1}" || true - run "docker logs ${RAND_NAME2}" || true - run "docker stop ${RAND_NAME1}" || true - run "docker stop ${RAND_NAME2}" || true - echo "Error" - exit 1 - fi - INDEX=$(( INDEX + 1 )) - sleep 1 -done -printf "\\n[OK] Test success\\n" - -### -### Cleanup -### -run "docker stop ${RAND_NAME1}" -run "docker stop ${RAND_NAME2}" diff --git a/tests/02-timezone.sh b/tests/02-timezone.sh deleted file mode 100755 index 6dbc280..0000000 --- a/tests/02-timezone.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - -CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" - -IMAGE="${1}" -#NAME="${2}" -#VERSION="${3}" -TAG="${4}" -ARCH="${5}" - - -HOST_PORT="8093" - -### -### Load Library -### -# shellcheck disable=SC1091 -. "${CWD}/.lib.sh" - - - -### -### Preparation -### -RAND_DIR="$( mktemp -d )" -RAND_NAME1="$( get_random_name )" -RAND_NAME2="$( get_random_name )" -run "chmod 0755 ${RAND_DIR}" -run "echo \" ${RAND_DIR}/index.php" - - -### -### Startup container -### -run "docker run -d --rm --platform ${ARCH} \ - -v ${RAND_DIR}:/var/www/default/htdocs \ - --name ${RAND_NAME1} devilbox/php-fpm-8.1" - -run "docker run --rm --platform ${ARCH} \ - -v ${RAND_DIR}:/var/www/default/htdocs \ - -p 127.0.0.1:${HOST_PORT}:80 \ - -e DEBUG_ENTRYPOINT=2 \ - -e DEBUG_RUNTIME=1 \ - -e TIMEZONE=Europe/Berlin \ - -e NEW_UID=$( id -u ) \ - -e NEW_GID=$( id -g ) \ - -e PHP_FPM_ENABLE=1 \ - -e PHP_FPM_SERVER_ADDR=${RAND_NAME1} \ - -e PHP_FPM_SERVER_PORT=9000 \ - --link ${RAND_NAME1} \ - --name ${RAND_NAME2} ${IMAGE}:${TAG} &" - - -### -### Tests -### -WAIT=120 -INDEX=0 -printf "Testing connectivity" -while ! curl -sS "http://localhost:${HOST_PORT}" 2>/dev/null | grep 'hello world php'; do - printf "." - if [ "${INDEX}" = "${WAIT}" ]; then - printf "\\n" - run "docker logs ${RAND_NAME1}" || true - run "docker logs ${RAND_NAME2}" || true - run "docker stop ${RAND_NAME1}" || true - run "docker stop ${RAND_NAME2}" || true - echo "Error" - exit 1 - fi - INDEX=$(( INDEX + 1 )) - sleep 1 -done -printf "\\n[OK] Test success\\n" - -### -### Cleanup -### -run "docker stop ${RAND_NAME1}" -run "docker stop ${RAND_NAME2}" diff --git a/tests/03-test-xargs.sh b/tests/03-test-xargs.sh deleted file mode 100755 index 5e7fada..0000000 --- a/tests/03-test-xargs.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -u -set -o pipefail - -CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" - -IMAGE="${1}" -#NAME="${2}" -#VERSION="${3}" -TAG="${4}" -ARCH="${5}" - - -### -### Load Library -### -# shellcheck disable=SC1091 -. "${CWD}/.lib.sh" - - -RAND_NAME="$( get_random_name )" - -### -### Startup container -### -FILES="$( \ -run "docker run --rm --platform ${ARCH} \ - -e DEBUG_ENTRYPOINT=2 \ - -e DEBUG_RUNTIME=1 \ - -e NEW_UID=$( id -u ) \ - -e NEW_GID=$( id -g ) \ - --entrypoint=bash \ - --name ${RAND_NAME} ${IMAGE}:${TAG} -c ' - find /lib -print0 | xargs -n1 -0 -P 2 - '" -)" - -if [ -z "${FILES}" ]; then - >&2 echo "Error, no files found with 'find' and 'xargs'" - exit 1 -fi - -echo "[OK] xargs works" diff --git a/tests/100-main-vhost__static-page.sh b/tests/100-main-vhost__static-page.sh new file mode 100755 index 0000000..692444b --- /dev/null +++ b/tests/100-main-vhost__static-page.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" + +IMAGE="${1}" +TAG="${2}" +ARCH="${3}" + + +### +### Load Library +### +# shellcheck disable=SC1090,SC1091 +. "${CWD}/.lib.sh" + + +### +### Universal ports +### +# shellcheck disable=SC2034 +HOST_PORT_HTTP="8093" +# shellcheck disable=SC2034 +HOST_PORT_HTTPS="8493" + +### +### Universal container names +### +# shellcheck disable=SC2034 +NAME_HTTPD="$( get_random_name )" +# shellcheck disable=SC2034 +NAME_PHPFPM="$( get_random_name )" +# shellcheck disable=SC2034 +NAME_RPROXY="$( get_random_name )" + + + +#--------------------------------------------------------------------------------------------------- +# DEFINES +#--------------------------------------------------------------------------------------------------- + +### +### GLOBALS +### +DOCROOT="htdocs" +MOUNT_CONT="/var/www/default" +MOUNT_HOST="$( tmp_dir )" + + + +#--------------------------------------------------------------------------------------------------- +# APPS +#--------------------------------------------------------------------------------------------------- + +### +### Application 1 +### +APP1_URL="http://localhost:${HOST_PORT_HTTP}" +APP1_EXT="html" +APP1_HDR="" +APP1_TXT="hello via httpd with ${APP1_EXT}" +create_app "${MOUNT_HOST}" "${DOCROOT}" "" "index.${APP1_EXT}" "${APP1_TXT}" + + + +#--------------------------------------------------------------------------------------------------- +# START +#--------------------------------------------------------------------------------------------------- + +### +### Start HTTPD Container +### +run "docker run -d --platform ${ARCH} --name ${NAME_HTTPD} \ +-v ${MOUNT_HOST}:${MOUNT_CONT} \ +-p 127.0.0.1:${HOST_PORT_HTTP}:80 \ +-p 127.0.0.1:${HOST_PORT_HTTPS}:443 \ +-e DEBUG_ENTRYPOINT=3 \ +-e DEBUG_RUNTIME=2 \ +${IMAGE}:${TAG} >/dev/null" + + + +#--------------------------------------------------------------------------------------------------- +# TESTS +#--------------------------------------------------------------------------------------------------- + +### +### Test: APP1 +### +if ! test_vhost_response "${APP1_TXT}" "${APP1_URL}" "${APP1_HDR}"; then + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_HTTPD}" + log "fail" "'${APP1_TXT}' not found in ${APP1_URL}" + exit 1 +fi + + + +#--------------------------------------------------------------------------------------------------- +# GENERIC +#--------------------------------------------------------------------------------------------------- + +### +### Test: Errors +### +if ! test_docker_logs_err "${NAME_HTTPD}"; then + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_HTTPD}" + log "fail" "Found errors in docker logs" + exit 1 +fi + + +### +### Cleanup +### +docker_stop "${NAME_HTTPD}" +log "ok" "Test succeeded" diff --git a/tests/101-main-vhost__static-page__ssl-both.sh b/tests/101-main-vhost__static-page__ssl-both.sh new file mode 100755 index 0000000..d9ed676 --- /dev/null +++ b/tests/101-main-vhost__static-page__ssl-both.sh @@ -0,0 +1,141 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" + +IMAGE="${1}" +TAG="${2}" +ARCH="${3}" + + +### +### Load Library +### +# shellcheck disable=SC1090,SC1091 +. "${CWD}/.lib.sh" + + +### +### Universal ports +### +# shellcheck disable=SC2034 +HOST_PORT_HTTP="8093" +# shellcheck disable=SC2034 +HOST_PORT_HTTPS="8493" + +### +### Universal container names +### +# shellcheck disable=SC2034 +NAME_HTTPD="$( get_random_name )" +# shellcheck disable=SC2034 +NAME_PHPFPM="$( get_random_name )" +# shellcheck disable=SC2034 +NAME_RPROXY="$( get_random_name )" + + + +#--------------------------------------------------------------------------------------------------- +# DEFINES +#--------------------------------------------------------------------------------------------------- + +### +### GLOBALS +### +DOCROOT="htdocs" +MOUNT_CONT="/var/www/default" +MOUNT_HOST="$( tmp_dir )" + + + +#--------------------------------------------------------------------------------------------------- +# APPS +#--------------------------------------------------------------------------------------------------- + +### +### Application 1 +### +APP1_URL="http://localhost:${HOST_PORT_HTTP}" +APP1_EXT="html" +APP1_HDR="" +APP1_TXT="hello via httpd with ${APP1_EXT}" +create_app "${MOUNT_HOST}" "${DOCROOT}" "" "index.${APP1_EXT}" "${APP1_TXT}" + +### +### Application 2 +### +APP2_URL="https://localhost:${HOST_PORT_HTTPS}" +#APP2_EXT="${APP1_EXT}" +APP2_HDR="${APP1_HDR}" +APP2_TXT="${APP1_TXT}" + + + +#--------------------------------------------------------------------------------------------------- +# START +#--------------------------------------------------------------------------------------------------- + +### +### Start HTTPD Container +### +run "docker run -d --platform ${ARCH} --name ${NAME_HTTPD} \ +-v ${MOUNT_HOST}:${MOUNT_CONT} \ +-p 127.0.0.1:${HOST_PORT_HTTP}:80 \ +-p 127.0.0.1:${HOST_PORT_HTTPS}:443 \ +-e DEBUG_ENTRYPOINT=3 \ +-e DEBUG_RUNTIME=2 \ +-e MAIN_VHOST_SSL_TYPE=both \ +${IMAGE}:${TAG} >/dev/null" + + + +#--------------------------------------------------------------------------------------------------- +# TESTS +#--------------------------------------------------------------------------------------------------- + +### +### Test: APP1 +### +if ! test_vhost_response "${APP1_TXT}" "${APP1_URL}" "${APP1_HDR}"; then + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_HTTPD}" + log "fail" "'${APP1_TXT}' not found in ${APP1_URL}" + exit 1 +fi + + +### +### Test: APP2 +### +if ! test_vhost_response "${APP2_TXT}" "${APP2_URL}" "${APP2_HDR}"; then + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_HTTPD}" + log "fail" "'${APP2_TXT}' not found in ${APP2_URL}" + exit 1 +fi + + + +#--------------------------------------------------------------------------------------------------- +# GENERIC +#--------------------------------------------------------------------------------------------------- + +### +### Test: Errors +### +if ! test_docker_logs_err "${NAME_HTTPD}"; then + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_HTTPD}" + log "fail" "Found errors in docker logs" + exit 1 +fi + + +### +### Cleanup +### +docker_stop "${NAME_HTTPD}" +log "ok" "Test succeeded" diff --git a/tests/110-main-vhost__php-fpm.sh b/tests/110-main-vhost__php-fpm.sh new file mode 100755 index 0000000..59053cc --- /dev/null +++ b/tests/110-main-vhost__php-fpm.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" + +IMAGE="${1}" +TAG="${2}" +ARCH="${3}" + + +### +### Load Library +### +# shellcheck disable=SC1090,SC1091 +. "${CWD}/.lib.sh" + + +### +### Universal ports +### +# shellcheck disable=SC2034 +HOST_PORT_HTTP="8093" +# shellcheck disable=SC2034 +HOST_PORT_HTTPS="8493" + +### +### Universal container names +### +# shellcheck disable=SC2034 +NAME_HTTPD="$( get_random_name )" +# shellcheck disable=SC2034 +NAME_PHPFPM="$( get_random_name )" +# shellcheck disable=SC2034 +NAME_RPROXY="$( get_random_name )" + + + +#--------------------------------------------------------------------------------------------------- +# DEFINES +#--------------------------------------------------------------------------------------------------- + +### +### GLOBALS +### +DOCROOT="htdocs" +MOUNT_CONT="/var/www/default" +MOUNT_HOST="$( tmp_dir )" + + + +#--------------------------------------------------------------------------------------------------- +# APPS +#--------------------------------------------------------------------------------------------------- + +### +### Application 1 +### +APP1_URL="http://localhost:${HOST_PORT_HTTP}" +APP1_EXT="php" +APP1_HDR="" +APP1_TXT="hello via httpd with ${APP1_EXT}" +create_app "${MOUNT_HOST}" "${DOCROOT}" "" "index.${APP1_EXT}" "/dev/null" + + +### +### Start HTTPD Container +### +run "docker run -d --platform ${ARCH} --name ${NAME_HTTPD} \ +-v ${MOUNT_HOST}:${MOUNT_CONT} \ +-p 127.0.0.1:${HOST_PORT_HTTP}:80 \ +-p 127.0.0.1:${HOST_PORT_HTTPS}:443 \ +-e DEBUG_ENTRYPOINT=3 \ +-e DEBUG_RUNTIME=2 \ +-e MAIN_VHOST_BACKEND=conf:phpfpm:tcp:${NAME_PHPFPM}:9000 \ +--link ${NAME_PHPFPM} \ +${IMAGE}:${TAG} >/dev/null" + + + +#--------------------------------------------------------------------------------------------------- +# TESTS +#--------------------------------------------------------------------------------------------------- + +### +### Test: APP1 +### +if ! test_vhost_response "${APP1_TXT}" "${APP1_URL}" "${APP1_HDR}"; then + docker_logs "${NAME_PHPFPM}" + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_PHPFPM}" + docker_stop "${NAME_HTTPD}" + log "fail" "'${APP1_TXT}' not found in ${APP1_URL}" + exit 1 +fi + + + +#--------------------------------------------------------------------------------------------------- +# GENERIC +#--------------------------------------------------------------------------------------------------- + +### +### Test: Errors +### +if ! test_docker_logs_err "${NAME_HTTPD}"; then + docker_logs "${NAME_PHPFPM}" + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_PHPFPM}" + docker_stop "${NAME_HTTPD}" + log "fail" "Found errors in docker logs" + exit 1 +fi + + +### +### Cleanup +### +docker_stop "${NAME_PHPFPM}" +docker_stop "${NAME_HTTPD}" +log "ok" "Test succeeded" diff --git a/tests/111-main-vhost__php-fpm__ssl-both.sh b/tests/111-main-vhost__php-fpm__ssl-both.sh new file mode 100755 index 0000000..a02c1e5 --- /dev/null +++ b/tests/111-main-vhost__php-fpm__ssl-both.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" + +IMAGE="${1}" +TAG="${2}" +ARCH="${3}" + + +### +### Load Library +### +# shellcheck disable=SC1090,SC1091 +. "${CWD}/.lib.sh" + + +### +### Universal ports +### +# shellcheck disable=SC2034 +HOST_PORT_HTTP="8093" +# shellcheck disable=SC2034 +HOST_PORT_HTTPS="8493" + +### +### Universal container names +### +# shellcheck disable=SC2034 +NAME_HTTPD="$( get_random_name )" +# shellcheck disable=SC2034 +NAME_PHPFPM="$( get_random_name )" +# shellcheck disable=SC2034 +NAME_RPROXY="$( get_random_name )" + + + +#--------------------------------------------------------------------------------------------------- +# DEFINES +#--------------------------------------------------------------------------------------------------- + +### +### GLOBALS +### +DOCROOT="htdocs" +MOUNT_CONT="/var/www/default" +MOUNT_HOST="$( tmp_dir )" + + + +#--------------------------------------------------------------------------------------------------- +# APPS +#--------------------------------------------------------------------------------------------------- + +### +### Application 1 +### +APP1_URL="http://localhost:${HOST_PORT_HTTP}" +APP1_EXT="php" +APP1_HDR="" +APP1_TXT="hello via httpd with ${APP1_EXT}" +create_app "${MOUNT_HOST}" "${DOCROOT}" "" "index.${APP1_EXT}" "/dev/null" + + +### +### Start HTTPD Container +### +run "docker run -d --platform ${ARCH} --name ${NAME_HTTPD} \ +-v ${MOUNT_HOST}:${MOUNT_CONT} \ +-p 127.0.0.1:${HOST_PORT_HTTP}:80 \ +-p 127.0.0.1:${HOST_PORT_HTTPS}:443 \ +-e DEBUG_ENTRYPOINT=3 \ +-e DEBUG_RUNTIME=2 \ +-e MAIN_VHOST_SSL_TYPE=both \ +-e MAIN_VHOST_BACKEND=conf:phpfpm:tcp:${NAME_PHPFPM}:9000 \ +--link ${NAME_PHPFPM} \ +${IMAGE}:${TAG} >/dev/null" + + + +#--------------------------------------------------------------------------------------------------- +# TESTS +#--------------------------------------------------------------------------------------------------- + +### +### Test: APP1 +### +if ! test_vhost_response "${APP1_TXT}" "${APP1_URL}" "${APP1_HDR}"; then + docker_logs "${NAME_PHPFPM}" + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_PHPFPM}" + docker_stop "${NAME_HTTPD}" + log "fail" "'${APP1_TXT}' not found in ${APP1_URL}" + exit 1 +fi + + +### +### Test: APP2 +### +if ! test_vhost_response "${APP2_TXT}" "${APP2_URL}" "${APP2_HDR}"; then + docker_logs "${NAME_PHPFPM}" + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_PHPFPM}" + docker_stop "${NAME_HTTPD}" + log "fail" "'${APP2_TXT}' not found in ${APP2_URL}" + exit 1 +fi + + + +#--------------------------------------------------------------------------------------------------- +# GENERIC +#--------------------------------------------------------------------------------------------------- + +### +### Test: Errors +### +if ! test_docker_logs_err "${NAME_HTTPD}"; then + docker_logs "${NAME_PHPFPM}" + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_PHPFPM}" + docker_stop "${NAME_HTTPD}" + log "fail" "Found errors in docker logs" + exit 1 +fi + + +### +### Cleanup +### +docker_stop "${NAME_PHPFPM}" +docker_stop "${NAME_HTTPD}" +log "ok" "Test succeeded" diff --git a/tests/120-main-vhost__reverse-proxy.sh b/tests/120-main-vhost__reverse-proxy.sh new file mode 100755 index 0000000..7b3c003 --- /dev/null +++ b/tests/120-main-vhost__reverse-proxy.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" + +IMAGE="${1}" +TAG="${2}" +ARCH="${3}" + + +### +### Load Library +### +# shellcheck disable=SC1090,SC1091 +. "${CWD}/.lib.sh" + + +### +### Universal ports +### +# shellcheck disable=SC2034 +HOST_PORT_HTTP="8093" +# shellcheck disable=SC2034 +HOST_PORT_HTTPS="8493" + +### +### Universal container names +### +# shellcheck disable=SC2034 +NAME_HTTPD="$( get_random_name )" +# shellcheck disable=SC2034 +NAME_PHPFPM="$( get_random_name )" +# shellcheck disable=SC2034 +NAME_RPROXY="$( get_random_name )" + + + +#--------------------------------------------------------------------------------------------------- +# DEFINES +#--------------------------------------------------------------------------------------------------- + +### +### GLOBALS +### +#DOCROOT="htdocs" +MOUNT_CONT="/var/www/default" +MOUNT_HOST="$( tmp_dir )" + + + +#--------------------------------------------------------------------------------------------------- +# APPS +#--------------------------------------------------------------------------------------------------- + +### +### Application 1 +### +APP1_URL="http://localhost:${HOST_PORT_HTTP}" +#APP1_EXT="nodejs" +APP1_HDR="" +APP1_TXT="hello via httpd with NodeJS" +#create_app "${MOUNT_HOST}" "${DOCROOT}" "" "index.${APP1_EXT}" " "${MOUNT_HOST}/app.js" +const http = require('http'); +const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.write('[OK]\n'); + res.write('${APP1_TXT}\n'); + res.end(); +}); +server.listen(3000, '0.0.0.0'); +EOF + + + +#--------------------------------------------------------------------------------------------------- +# START +#--------------------------------------------------------------------------------------------------- + +### +### Start NodeJS Container +### +run "docker run -d --name ${NAME_RPROXY} \ +-v ${MOUNT_HOST}:${MOUNT_CONT} \ +node:19-alpine node /var/www/default/app.js >/dev/null" + + +### +### Start HTTPD Container +### +run "docker run -d --platform ${ARCH} --name ${NAME_HTTPD} \ +-v ${MOUNT_HOST}:${MOUNT_CONT} \ +-p 127.0.0.1:${HOST_PORT_HTTP}:80 \ +-p 127.0.0.1:${HOST_PORT_HTTPS}:443 \ +-e DEBUG_ENTRYPOINT=3 \ +-e DEBUG_RUNTIME=2 \ +-e MAIN_VHOST_BACKEND=conf:rproxy:http:${NAME_RPROXY}:3000 \ +--link ${NAME_RPROXY} \ +${IMAGE}:${TAG} >/dev/null" + + + +#--------------------------------------------------------------------------------------------------- +# TESTS +#--------------------------------------------------------------------------------------------------- + +### +### Test: APP1 +### +if ! test_vhost_response "${APP1_TXT}" "${APP1_URL}" "${APP1_HDR}"; then + docker_logs "${NAME_RPROXY}" + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_RPROXY}" + docker_stop "${NAME_HTTPD}" + log "fail" "'${APP1_TXT}' not found in ${APP1_URL}" + exit 1 +fi + + + +#--------------------------------------------------------------------------------------------------- +# GENERIC +#--------------------------------------------------------------------------------------------------- + +### +### Test: Errors +### +if ! test_docker_logs_err "${NAME_HTTPD}"; then + docker_logs "${NAME_RPROXY}" + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_RPROXY}" + docker_stop "${NAME_HTTPD}" + log "fail" "Found errors in docker logs" + exit 1 +fi + + +### +### Cleanup +### +docker_stop "${NAME_RPROXY}" +docker_stop "${NAME_HTTPD}" +log "ok" "Test succeeded" diff --git a/tests/200-mass-vhost__static-page.sh b/tests/200-mass-vhost__static-page.sh new file mode 100755 index 0000000..866f26e --- /dev/null +++ b/tests/200-mass-vhost__static-page.sh @@ -0,0 +1,179 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" + +IMAGE="${1}" +TAG="${2}" +ARCH="${3}" + + +### +### Load Library +### +# shellcheck disable=SC1090,SC1091 +. "${CWD}/.lib.sh" + + +### +### Universal ports +### +# shellcheck disable=SC2034 +HOST_PORT_HTTP="8093" +# shellcheck disable=SC2034 +HOST_PORT_HTTPS="8493" + +### +### Universal container names +### +# shellcheck disable=SC2034 +NAME_HTTPD="$( get_random_name )" +# shellcheck disable=SC2034 +NAME_PHPFPM="$( get_random_name )" +# shellcheck disable=SC2034 +NAME_RPROXY="$( get_random_name )" + + + +#--------------------------------------------------------------------------------------------------- +# DEFINES +#--------------------------------------------------------------------------------------------------- + +### +### GLOBALS +### +DOCROOT="htdocs" +TLD=".loc" +MOUNT_CONT="/shared/httpd" +MOUNT_HOST="$( tmp_dir )" + + + +#--------------------------------------------------------------------------------------------------- +# APPS +#--------------------------------------------------------------------------------------------------- + +### +### Application 1 +### +APP1_NAME="my-project-1" +APP1_URL="http://localhost:${HOST_PORT_HTTP}" +APP1_EXT="html" +APP1_HDR="Host: ${APP1_NAME}${TLD}" +APP1_TXT="hello from ${APP1_NAME} via httpd with ${APP1_EXT}" + +### +### Application 2 +### +APP2_NAME="another-example" +APP2_URL="http://localhost:${HOST_PORT_HTTP}" +APP2_EXT="html" +APP2_HDR="Host: ${APP2_NAME}${TLD}" +APP2_TXT="hello from ${APP2_NAME} via httpd with ${APP2_EXT}" + +### +### Application 2 +### +APP3_NAME="app-after-startup" +APP3_URL="http://localhost:${HOST_PORT_HTTP}" +APP3_EXT="html" +APP3_HDR="Host: ${APP3_NAME}${TLD}" +APP3_TXT="hello from ${APP3_NAME} via httpd with ${APP3_EXT}" + + +### +### Create Applications (before startup) +### +create_app "${MOUNT_HOST}" "${DOCROOT}" "${APP1_NAME}" "index.${APP1_EXT}" "${APP1_TXT}" +create_app "${MOUNT_HOST}" "${DOCROOT}" "${APP2_NAME}" "index.${APP2_EXT}" "${APP2_TXT}" + + + +#--------------------------------------------------------------------------------------------------- +# START +#--------------------------------------------------------------------------------------------------- + +### +### Start HTTPD Container +### +run "docker run -d --platform ${ARCH} --name ${NAME_HTTPD} \ +-v ${MOUNT_HOST}:${MOUNT_CONT} \ +-p 127.0.0.1:${HOST_PORT_HTTP}:80 \ +-p 127.0.0.1:${HOST_PORT_HTTPS}:443 \ +-e DEBUG_ENTRYPOINT=3 \ +-e DEBUG_RUNTIME=2 \ +-e MAIN_VHOST_ENABLE=0 \ +-e MASS_VHOST_ENABLE=1 \ +${IMAGE}:${TAG} >/dev/null" + + + +#--------------------------------------------------------------------------------------------------- +# TESTS +#--------------------------------------------------------------------------------------------------- + +### +### Test: APP1 +### +if ! test_vhost_response "${APP1_TXT}" "${APP1_URL}" "${APP1_HDR}"; then + docker_logs "${NAME_PHPFPM}" + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_PHPFPM}" + docker_stop "${NAME_HTTPD}" + log "fail" "'${APP1_TXT}' not found in ${APP1_URL}" + exit 1 +fi + +### +### Test: APP2 +### +if ! test_vhost_response "${APP2_TXT}" "${APP2_URL}" "${APP2_HDR}"; then + docker_logs "${NAME_PHPFPM}" + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_PHPFPM}" + docker_stop "${NAME_HTTPD}" + log "fail" "'${APP2_TXT}' not found in ${APP2_URL}" + exit 1 +fi + +### +### Test: APP3 +### +create_app "${MOUNT_HOST}" "${DOCROOT}" "${APP3_NAME}" "index.${APP3_EXT}" "${APP3_TXT}" +if ! test_vhost_response "${APP3_TXT}" "${APP3_URL}" "${APP3_HDR}"; then + docker_logs "${NAME_PHPFPM}" + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_PHPFPM}" + docker_stop "${NAME_HTTPD}" + log "fail" "'${APP3_TXT}' not found in ${APP3_URL}" + exit 1 +fi + + + +#--------------------------------------------------------------------------------------------------- +# GENERIC +#--------------------------------------------------------------------------------------------------- + +### +### Test: Errors +### +if ! test_docker_logs_err "${NAME_HTTPD}"; then + docker_logs "${NAME_PHPFPM}" + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_PHPFPM}" + docker_stop "${NAME_HTTPD}" + log "fail" "Found errors in docker logs" + exit 1 +fi + + +### +### Cleanup +### +docker_stop "${NAME_PHPFPM}" +docker_stop "${NAME_HTTPD}" +log "ok" "Test succeeded" diff --git a/tests/210-mass-vhost__php-fpm.sh b/tests/210-mass-vhost__php-fpm.sh new file mode 100755 index 0000000..7e98ff6 --- /dev/null +++ b/tests/210-mass-vhost__php-fpm.sh @@ -0,0 +1,189 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" + +IMAGE="${1}" +TAG="${2}" +ARCH="${3}" + + +### +### Load Library +### +# shellcheck disable=SC1090,SC1091 +. "${CWD}/.lib.sh" + + +### +### Universal ports +### +# shellcheck disable=SC2034 +HOST_PORT_HTTP="8093" +# shellcheck disable=SC2034 +HOST_PORT_HTTPS="8493" + +### +### Universal container names +### +# shellcheck disable=SC2034 +NAME_HTTPD="$( get_random_name )" +# shellcheck disable=SC2034 +NAME_PHPFPM="$( get_random_name )" +# shellcheck disable=SC2034 +NAME_RPROXY="$( get_random_name )" + + + +#--------------------------------------------------------------------------------------------------- +# DEFINES +#--------------------------------------------------------------------------------------------------- + +### +### GLOBALS +### +DOCROOT="htdocs" +TLD=".loc" +MOUNT_CONT="/shared/httpd" +MOUNT_HOST="$( tmp_dir )" + + + +#--------------------------------------------------------------------------------------------------- +# APPS +#--------------------------------------------------------------------------------------------------- + +### +### Application 1 +### +APP1_NAME="my-project-1" +APP1_URL="http://localhost:${HOST_PORT_HTTP}" +APP1_EXT="php" +APP1_HDR="Host: ${APP1_NAME}${TLD}" +APP1_TXT="hello from ${APP1_NAME} via httpd with ${APP1_EXT}" + +### +### Application 2 +### +APP2_NAME="another-example" +APP2_URL="http://localhost:${HOST_PORT_HTTP}" +APP2_EXT="php" +APP2_HDR="Host: ${APP2_NAME}${TLD}" +APP2_TXT="hello from ${APP2_NAME} via httpd with ${APP2_EXT}" + +### +### Application 2 +### +APP3_NAME="app-after-startup" +APP3_URL="http://localhost:${HOST_PORT_HTTP}" +APP3_EXT="php" +APP3_HDR="Host: ${APP3_NAME}${TLD}" +APP3_TXT="hello from ${APP3_NAME} via httpd with ${APP3_EXT}" + + +### +### Create Applications (before startup) +### +create_app "${MOUNT_HOST}" "${DOCROOT}" "${APP1_NAME}" "index.${APP1_EXT}" "/dev/null" + + +### +### Start HTTPD Container +### +run "docker run -d --platform ${ARCH} --name ${NAME_HTTPD} \ +-v ${MOUNT_HOST}:${MOUNT_CONT} \ +-p 127.0.0.1:${HOST_PORT_HTTP}:80 \ +-p 127.0.0.1:${HOST_PORT_HTTPS}:443 \ +-e DEBUG_ENTRYPOINT=3 \ +-e DEBUG_RUNTIME=2 \ +-e MAIN_VHOST_ENABLE=0 \ +-e MASS_VHOST_ENABLE=1 \ +-e MASS_VHOST_BACKEND=conf:phpfpm:tcp:${NAME_PHPFPM}:9000 \ +--link ${NAME_PHPFPM} \ +${IMAGE}:${TAG} >/dev/null" + + + +#--------------------------------------------------------------------------------------------------- +# TESTS +#--------------------------------------------------------------------------------------------------- + +### +### Test: APP1 +### +if ! test_vhost_response "${APP1_TXT}" "${APP1_URL}" "${APP1_HDR}"; then + docker_logs "${NAME_PHPFPM}" + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_PHPFPM}" + docker_stop "${NAME_HTTPD}" + log "fail" "'${APP1_TXT}' not found in ${APP1_URL}" + exit 1 +fi + +### +### Test: APP2 +### +if ! test_vhost_response "${APP2_TXT}" "${APP2_URL}" "${APP2_HDR}"; then + docker_logs "${NAME_PHPFPM}" + docker_logs "${NAME_HTTPD}" + docker_stop "${NAME_PHPFPM}" + docker_stop "${NAME_HTTPD}" + log "fail" "'${APP2_TXT}' not found in ${APP2_URL}" + exit 1 +fi + +### +### Test: APP3 +### +create_app "${MOUNT_HOST}" "${DOCROOT}" "${APP3_NAME}" "index.${APP3_EXT}" " "${APP1_DIR}/app.js" +const http = require('http'); +const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.write('[OK]\n'); + res.write('${APP1_TXT}\n'); + res.end(); +}); +server.listen(${APP1_PORT}, '0.0.0.0'); +EOF + +# Create Project for Application 1 +mkdir -p "${MOUNT_HOST}/${APP1_NAME}/cfg" +echo "conf:rproxy:http:${NAME_RPROXY1}:${APP1_PORT}" > "${MOUNT_HOST}/${APP1_NAME}/cfg/backend.txt" + + + + +### +### Application 2 +### +APP2_NAME="another-nodejs-app" +APP2_TXT="hello hello from ${APP2_NAME} via httpd with NodeJS" +APP2_URL="http://localhost:${HOST_PORT_HTTP}" +APP2_HDR="Host: ${APP2_NAME}${TLD}" +APP2_PORT=4000 + +APP2_DIR="$( tmp_dir )" +cat << EOF > "${APP2_DIR}/app.js" +const http = require('http'); +const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.write('[OK]\n'); + res.write('${APP2_TXT}\n'); + res.end(); +}); +server.listen(${APP2_PORT}, '0.0.0.0'); +EOF + + + + + + +#--------------------------------------------------------------------------------------------------- +# START +#--------------------------------------------------------------------------------------------------- + +### +### Pull node image +### +run "docker pull --platform linux/amd64 node:19-alpine" + +### +### Start Node-1 Container (tcp 3000) +### +run "docker run -d --name ${NAME_RPROXY1} \ +-v ${APP1_DIR}:/app \ +node:19-alpine node /app/app.js >/dev/null" + + +### +### Start Node-2 Container (tcp 4000) +### +run "docker run -d --name ${NAME_RPROXY2} \ +-v ${APP2_DIR}:/app \ +node:19-alpine node /app/app.js >/dev/null" + + +### +### Start HTTPD Container +### +run "docker run -d --platform ${ARCH} --name ${NAME_HTTPD} \ +-v ${MOUNT_HOST}:${MOUNT_CONT} \ +-p 127.0.0.1:${HOST_PORT_HTTP}:80 \ +-p 127.0.0.1:${HOST_PORT_HTTPS}:443 \ +-e DEBUG_ENTRYPOINT=3 \ +-e DEBUG_RUNTIME=1 \ +-e MAIN_VHOST_ENABLE=0 \ +-e MASS_VHOST_ENABLE=1 \ +-e MASS_VHOST_BACKEND=file:backend.txt \ +-e DOCKER_LOGS=0 \ +--link ${NAME_RPROXY1} \ +--link ${NAME_RPROXY2} \ +${IMAGE}:${TAG} >/dev/null" + + + +#--------------------------------------------------------------------------------------------------- +# TESTS +#--------------------------------------------------------------------------------------------------- + +### +### Test: APP1 +### +if ! test_vhost_response "${APP1_TXT}" "${APP1_URL}" "${APP1_HDR}"; then + docker_logs "${NAME_RPROXY1}" + docker_logs "${NAME_HTTPD}" + + docker_stop "${NAME_RPROXY2}" + docker_stop "${NAME_RPROXY1}" + docker_stop "${NAME_HTTPD}" + log "fail" "'${APP1_TXT}' not found in ${APP1_URL}" + exit 1 +fi + + +### +### Test: APP2 +### +# Create Project for Application 2 +mkdir -p "${MOUNT_HOST}/${APP2_NAME}/cfg" +echo "conf:rproxy:http:${NAME_RPROXY2}:${APP2_PORT}" > "${MOUNT_HOST}/${APP2_NAME}/cfg/backend.txt" + +if ! test_vhost_response "${APP2_TXT}" "${APP2_URL}" "${APP2_HDR}"; then + docker_logs "${NAME_RPROXY2}" + docker_logs "${NAME_HTTPD}" + + docker_stop "${NAME_RPROXY2}" + docker_stop "${NAME_RPROXY1}" + docker_stop "${NAME_HTTPD}" + log "fail" "'${APP2_TXT}' not found in ${APP2_URL}" + exit 1 +fi + + + +#--------------------------------------------------------------------------------------------------- +# GENERIC +#--------------------------------------------------------------------------------------------------- + +### +### Test: Errors +### +if ! test_docker_logs_err "${NAME_HTTPD}"; then + docker_logs "${NAME_RPROXY2}" + docker_logs "${NAME_RPROXY1}" + docker_logs "${NAME_HTTPD}" + + docker_stop "${NAME_RPROXY2}" + docker_stop "${NAME_RPROXY1}" + docker_stop "${NAME_HTTPD}" + log "fail" "Found errors in docker logs" + exit 1 +fi + + +### +### Cleanup +### +docker_stop "${NAME_RPROXY2}" +docker_stop "${NAME_RPROXY1}" +docker_stop "${NAME_HTTPD}" +log "ok" "Test succeeded" diff --git a/tests/start-ci.sh b/tests/start-ci.sh index bdd8a48..137c24d 100755 --- a/tests/start-ci.sh +++ b/tests/start-ci.sh @@ -13,10 +13,8 @@ IFS=$'\n' # Current directory CWD="$( dirname "${0}" )" IMAGE="${1}" -NAME="${2}" -VERSION="${3}" -TAG="${4}" -ARCH="${5}" +TAG="${2}" +ARCH="${3}" declare -a TESTS=() @@ -33,18 +31,22 @@ for f in ${FILES}; do TESTS+=("${f}") done -# Start a single test -if [ "${#}" -eq "3" ]; then - sh -c "${TESTS[${2}]} ${IMAGE} ${NAME} ${VERSION} ${TAG} ${ARCH}" - -# Run all tests -else - for i in "${TESTS[@]}"; do - echo "################################################################################" - echo "# [${CWD}/${i}] ${IMAGE}:${TAG} ${NAME}-${VERSION} (${ARCH})" - echo "################################################################################" - if ! sh -c "${i} ${IMAGE} ${NAME} ${VERSION} ${TAG} ${ARCH}"; then - exit 1 - fi - done -fi +for i in "${TESTS[@]}"; do + FILE="$( basename "${i}" | sed 's/.sh$//g' )" + NUMBER="${FILE/[-_a-z]*/}" + VHOST="$( echo "${FILE}" | sed 's/[0-9]*-//' | awk -F'__' '{print $1}' )" + TYPE="$( echo "${FILE}" | sed 's/[0-9]*-//' | awk -F'__' '{print $2}' )" + FEATURE="$( echo "${FILE}" | sed 's/[0-9]*-//' | awk -F'__' '{print $3}' )" + echo "####################################################################################################" + echo "####################################################################################################" + echo "###" + echo "### [${NUMBER}] ${VHOST} (${TYPE} - ${FEATURE}) ${IMAGE}:${TAG} (${ARCH})" + echo "###" + echo "####################################################################################################" + echo "####################################################################################################" + if ! sh -c "${i} ${IMAGE} ${TAG} ${ARCH}"; then + exit 1 + fi + echo + echo +done