#!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/certs/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" source "$PLUGIN_AVAILABLE_PATH/domains/functions" source "$PLUGIN_AVAILABLE_PATH/proxy/functions" source "$PLUGIN_AVAILABLE_PATH/ps/functions" get_nginx_location() { declare desc="check that nginx is at the expected location and return it" local NGINX_LOCATION NGINX_LOCATION=$(which nginx 2>/dev/null) if [[ -z "$NGINX_LOCATION" ]]; then NGINX_LOCATION="/usr/sbin/nginx" fi if [[ ! -x "$NGINX_LOCATION" ]]; then dokku_log_fail "Could not find nginx binary in \$PATH or at '${NGINX_LOCATION}'." fi echo "$NGINX_LOCATION" } validate_nginx() { declare desc="validate entire nginx config" local NGINX_LOCATION NGINX_LOCATION=$(get_nginx_location) if [[ -z "$NGINX_LOCATION" ]]; then exit 1; fi set +e sudo "$NGINX_LOCATION" -t > /dev/null 2>&1 local exit_code=$? set -e if [[ "$exit_code" -ne "0" ]]; then sudo "$NGINX_LOCATION" -t shopt -s nullglob local conf_file for conf_file in $DOKKU_ROOT/*/nginx.conf; do dokku_log_verbose "validate_nginx failed. contents of $conf_file below..." cat "$conf_file" done exit "$exit_code" fi } restart_nginx() { declare desc="restart nginx for given distros" case "$DOKKU_DISTRO" in debian) sudo /usr/sbin/invoke-rc.d nginx reload > /dev/null ;; ubuntu) sudo /etc/init.d/nginx reload > /dev/null ;; opensuse) sudo /sbin/service nginx reload > /dev/null ;; arch|centos) sudo /usr/bin/systemctl reload nginx ;; esac } nginx_logs() { declare desc="display app nginx logs" local APP="$2"; verify_app_name "$APP" local NGINX_LOGS_TYPE=${1#nginx:} local NGINX_LOGS_TYPE=${NGINX_LOGS_TYPE%-logs} local NGINX_LOGS_PATH="/var/log/nginx/$APP-$NGINX_LOGS_TYPE.log" if [[ $3 == "-t" ]]; then local NGINX_LOGS_ARGS="-F" else local NGINX_LOGS_ARGS="-n 20" fi tail "$NGINX_LOGS_ARGS" "$NGINX_LOGS_PATH" } configure_nginx_ports() { declare desc="configure nginx listening ports" local APP=$1; verify_app_name "$APP" local RAW_TCP_PORTS="$(get_app_raw_tcp_ports "$APP")" local DOKKU_NGINX_PORT=$(config_get "$APP" DOKKU_NGINX_PORT) local DOKKU_NGINX_SSL_PORT=$(config_get "$APP" DOKKU_NGINX_SSL_PORT) local DOKKU_PROXY_PORT_MAP=$(config_get "$APP" DOKKU_PROXY_PORT_MAP) local IS_APP_VHOST_ENABLED="$(is_app_vhost_enabled "$APP")" local UPSTREAM_PORT="5000" if [[ -z "$DOKKU_NGINX_PORT" ]] && [[ -z "$RAW_TCP_PORTS" ]]; then if [[ "$IS_APP_VHOST_ENABLED" == "false" ]]; then dokku_log_info1 "no nginx port set. setting to random open high port" local NGINX_PORT=$(get_available_port) else local NGINX_PORT=80 fi config_set --no-restart "$APP" DOKKU_NGINX_PORT="$NGINX_PORT" fi if [[ -z "$DOKKU_NGINX_SSL_PORT" ]]; then if (is_ssl_enabled "$APP"); then local NGINX_SSL_PORT=443 if [[ -z "$RAW_TCP_PORTS" ]] && [[ "$IS_APP_VHOST_ENABLED" == "false" ]]; then dokku_log_info1 "no nginx ssl port set. setting to random open high port" NGINX_SSL_PORT=$(get_available_port) fi config_set --no-restart "$APP" DOKKU_NGINX_SSL_PORT="$NGINX_SSL_PORT" fi fi if [[ -z "$DOKKU_PROXY_PORT_MAP" ]]; then if [[ -n "$RAW_TCP_PORTS" ]]; then local RAW_TCP_PORT for RAW_TCP_PORT in $RAW_TCP_PORTS; do local PROXY_PORT_MAP+=" http:${RAW_TCP_PORT}:${RAW_TCP_PORT} " done else local NGINX_PORT=${NGINX_PORT:-$DOKKU_NGINX_PORT} local NGINX_SSL_PORT=${NGINX_SSL_PORT:-$DOKKU_NGINX_SSL_PORT} [[ -f "$DOKKU_ROOT/$APP/PORT.web.1" ]] && local UPSTREAM_PORT="$(< "$DOKKU_ROOT/$APP/PORT.web.1")" if [[ -n "$NGINX_PORT" ]] && [[ -n "$NGINX_SSL_PORT" ]]; then local PROXY_PORT_MAP+=" http:${NGINX_PORT}:$UPSTREAM_PORT https:${NGINX_SSL_PORT}:$UPSTREAM_PORT " elif [[ -n "$NGINX_PORT" ]]; then local PROXY_PORT_MAP+=" http:${NGINX_PORT}:$UPSTREAM_PORT " fi fi if [[ -n "$PROXY_PORT_MAP" ]]; then local PROXY_PORT_MAP="$(echo "$PROXY_PORT_MAP" | xargs)" local PROXY_PORT_MAP+=" $(merge_dedupe_list "$(remove_val_from_list "$PORT_MAP" "$DOKKU_PROXY_PORT_MAP" " ")" " ") " config_set --no-restart "$APP" DOKKU_PROXY_PORT_MAP="$PROXY_PORT_MAP" fi fi } validate_ssl_domains() { declare desc="check configured domains against SSL cert contents and show warning if mismatched" local APP=$1; verify_app_name "$APP" local SSL_HOSTNAME=$(get_ssl_hostnames "$APP") local SSL_HOSTNAME_REGEX=$(echo "$SSL_HOSTNAME" | xargs | sed 's|\.|\\.|g' | sed 's/\*/\[^\.\]\*/g' | sed 's/ /|/g') if ! (egrep -q "^${SSL_HOSTNAME_REGEX}$" "$VHOST_PATH" &> /dev/null); then dokku_log_info1 "No matching configured domains for $APP found in SSL certificate. Your app will show as insecure in a browser if accessed via SSL" dokku_log_info1 "Please add appropriate domains via the dokku domains command" [[ -n "$NONSSL_VHOSTS" ]] && dokku_log_info1 "Configured domains for app:" local domain for domain in $(echo "$NONSSL_VHOSTS"| xargs); do dokku_log_info2 "$domain" done [[ -n "$SSL_HOSTNAME" ]] && dokku_log_info1 "Domains found in SSL certificate:" for domain in $(echo "$SSL_HOSTNAME" | xargs); do dokku_log_info2 "$domain" done fi } get_custom_nginx_template() { declare desc="attempts to copy custom nginx template from app image" local APP="$1"; verify_app_name "$APP" local DESTINATION="$2" local IMAGE_TAG="$(get_running_image_tag "$APP")" local IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG") local NGINX_TEMPLATE_NAME="nginx.conf.sigil" copy_from_image "$IMAGE" "$NGINX_TEMPLATE_NAME" "$DESTINATION" 2>/dev/null || true } is_spdy_enabled() { declare desc="detects whether the installed nginx version has spdy support" local NGINX_VERSION="$1" local MAJOR_VERSION MINOR_VERSION PATCH_VERSION local HAS_SUPPORT=true MAJOR_VERSION=$(echo "$NGINX_VERSION" | awk '{split($0,a,"."); print a[1]}') MINOR_VERSION=$(echo "$NGINX_VERSION" | awk '{split($0,a,"."); print a[2]}') PATCH_VERSION=$(echo "$NGINX_VERSION" | awk '{split($0,a,"."); print a[3]}') if [[ "$MAJOR_VERSION" -ge "2" ]]; then HAS_SUPPORT=false elif [[ "$MAJOR_VERSION" -eq "1" ]]; then if [[ "$MINOR_VERSION" -ge "10" ]]; then HAS_SUPPORT=false elif [[ "$MINOR_VERSION" -ge "9" ]] && [[ "$PATCH_VERSION" -ge "5" ]]; then HAS_SUPPORT=false fi fi echo $HAS_SUPPORT } is_http2_enabled() { declare desc="detects whether the installed nginx version has http2 support" local NGINX_VERSION="$1" local MAJOR_VERSION MINOR_VERSION PATCH_VERSION local HAS_SUPPORT=false MAJOR_VERSION=$(echo "$NGINX_VERSION" | awk '{split($0,a,"."); print a[1]}') MINOR_VERSION=$(echo "$NGINX_VERSION" | awk '{split($0,a,"."); print a[2]}') PATCH_VERSION=$(echo "$NGINX_VERSION" | awk '{split($0,a,"."); print a[3]}') if [[ "$MAJOR_VERSION" -ge "2" ]]; then HAS_SUPPORT=true elif [[ "$MAJOR_VERSION" -eq "1" ]]; then if [[ "$MINOR_VERSION" -eq "11" ]] && [[ "$PATCH_VERSION" -ge "5" ]]; then HAS_SUPPORT=true elif [[ "$MINOR_VERSION" -ge "12" ]]; then HAS_SUPPORT=true fi fi echo $HAS_SUPPORT } nginx_build_config() { declare desc="build nginx config to proxy app containers using sigil" local APP="$1"; verify_app_name "$APP" local DOKKU_APP_LISTEN_PORT="$2"; local DOKKU_APP_LISTEN_IP="$3" local VHOST_PATH="$DOKKU_ROOT/$APP/VHOST"; local URLS_PATH="$DOKKU_ROOT/$APP/URLS" local NGINX_TEMPLATE_NAME="nginx.conf.sigil" local DEFAULT_NGINX_TEMPLATE="$PLUGIN_AVAILABLE_PATH/nginx-vhosts/templates/$NGINX_TEMPLATE_NAME" local NGINX_TEMPLATE="$DEFAULT_NGINX_TEMPLATE"; local SCHEME=http local NGINX_TEMPLATE_SOURCE="built-in"; local APP_SSL_PATH="$DOKKU_ROOT/$APP/tls" local RAW_TCP_PORTS="$(get_app_raw_tcp_ports "$APP")" local DOKKU_APP_LISTENERS local IS_APP_VHOST_ENABLED=$(is_app_vhost_enabled "$APP") if [[ "$(is_app_proxy_enabled "$APP")" == "true" ]]; then if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -z "$DOKKU_APP_LISTEN_IP" ]]; then DOKKU_APP_LISTENERS="$(plugn trigger network-get-listeners "$APP" | xargs)" elif [[ -n "$DOKKU_APP_LISTEN_PORT" ]] && [[ -n "$DOKKU_APP_LISTEN_IP" ]]; then local PASSED_LISTEN_IP_PORT=true fi # setup nginx listen ports configure_nginx_ports "$APP" local NGINX_PORT=$(config_get "$APP" DOKKU_NGINX_PORT) local NGINX_SSL_PORT=$(config_get "$APP" DOKKU_NGINX_SSL_PORT) local PROXY_PORT_MAP=$(config_get "$APP" DOKKU_PROXY_PORT_MAP) local PORT_MAP for PORT_MAP in $PROXY_PORT_MAP; do local PROXY_UPSTREAM_PORT="$(awk -F ':' '{ print $3 }' <<< "$PORT_MAP")" if [[ "$(is_val_in_list "$PROXY_UPSTREAM_PORT" "$PROXY_UPSTREAM_PORTS" " ")" == "false" ]]; then local PROXY_UPSTREAM_PORTS+="$PROXY_UPSTREAM_PORT " fi done local PROXY_UPSTREAM_PORTS="$(echo "$PROXY_UPSTREAM_PORTS" | xargs)" local NGINX_BUILD_CONFIG_TMP_WORK_DIR=$(mktemp -d /tmp/dokku_nginx_template.XXXXX) local NGINX_CONF=$(mktemp --tmpdir="${NGINX_BUILD_CONFIG_TMP_WORK_DIR}" "nginx.conf.XXXXXX") local CUSTOM_NGINX_TEMPLATE="$NGINX_BUILD_CONFIG_TMP_WORK_DIR/$NGINX_TEMPLATE_NAME" # shellcheck disable=SC2086 trap 'rm -rf $NGINX_CONF $NGINX_BUILD_CONFIG_TMP_WORK_DIR > /dev/null' RETURN INT TERM EXIT get_custom_nginx_template "$APP" "$CUSTOM_NGINX_TEMPLATE" if [[ -f "$CUSTOM_NGINX_TEMPLATE" ]]; then dokku_log_info1 'Overriding default nginx.conf with detected nginx.conf.sigil' local NGINX_TEMPLATE="$CUSTOM_NGINX_TEMPLATE" local NGINX_TEMPLATE_SOURCE="app-supplied" fi local NONSSL_VHOSTS=$(get_app_domains "$APP") local NOSSL_SERVER_NAME=$(echo "$NONSSL_VHOSTS" | xargs) if is_ssl_enabled "$APP"; then local SSL_INUSE=true; local SCHEME=https validate_ssl_domains "$APP" local SSL_HOSTNAME=$(get_ssl_hostnames "$APP") local SSL_HOSTNAME_REGEX=$(echo "$SSL_HOSTNAME" | xargs | sed 's|\.|\\.|g' | sed 's/\*/\[^\.\]\*/g' | sed 's/ /|/g') if [[ "$IS_APP_VHOST_ENABLED" == "true" ]]; then local SSL_VHOSTS=$(egrep "^${SSL_HOSTNAME_REGEX}$" "$VHOST_PATH" || true) else local SSL_VHOSTS=$(< "$DOKKU_ROOT/HOSTNAME") fi local SSL_SERVER_NAME local host for host in $SSL_VHOSTS; do # SSL_SERVER_NAME should only contain items not in NOSSL_SERVER_NAME if [[ ! $NOSSL_SERVER_NAME =~ (^|[[:space:]])$host($|[[:space:]]) ]]; then SSL_SERVER_NAME="${host}${SSL_SERVER_NAME:+ $SSL_SERVER_NAME}" fi done fi local NGINX_LOCATION NGINX_VERSION SPDY_SUPPORTED HTTP2_SUPPORTED NGINX_LOCATION=$(get_nginx_location) if [[ -z "$NGINX_LOCATION" ]]; then exit 1; fi NGINX_VERSION="$("$NGINX_LOCATION" -v 2>&1 | cut -d'/' -f 2)" SPDY_SUPPORTED="$(is_spdy_enabled "$NGINX_VERSION")" HTTP2_SUPPORTED="$(is_http2_enabled "$NGINX_VERSION")" PROXY_PORT_MAP=$(echo "$PROXY_PORT_MAP" | xargs) # trailing spaces mess up default template eval "$(config_export app "$APP")" local SIGIL_PARAMS=(-f $NGINX_TEMPLATE APP="$APP" DOKKU_ROOT="$DOKKU_ROOT" NOSSL_SERVER_NAME="$NOSSL_SERVER_NAME" DOKKU_APP_LISTENERS="$DOKKU_APP_LISTENERS" DOKKU_LIB_ROOT="$DOKKU_LIB_ROOT" PASSED_LISTEN_IP_PORT="$PASSED_LISTEN_IP_PORT" SPDY_SUPPORTED="$SPDY_SUPPORTED" HTTP2_SUPPORTED="$HTTP2_SUPPORTED" DOKKU_APP_LISTEN_PORT="$DOKKU_APP_LISTEN_PORT" DOKKU_APP_LISTEN_IP="$DOKKU_APP_LISTEN_IP" APP_SSL_PATH="$APP_SSL_PATH" SSL_INUSE="$SSL_INUSE" SSL_SERVER_NAME="$SSL_SERVER_NAME" NGINX_PORT="$NGINX_PORT" NGINX_SSL_PORT="$NGINX_SSL_PORT" RAW_TCP_PORTS="$RAW_TCP_PORTS" PROXY_PORT_MAP="$PROXY_PORT_MAP" PROXY_UPSTREAM_PORTS="$PROXY_UPSTREAM_PORTS") # execute sigil template processing xargs -i echo "-----> Configuring {}...(using $NGINX_TEMPLATE_SOURCE template)" <<< "$(echo "${SSL_VHOSTS}" "${NONSSL_VHOSTS}" | tr ' ' '\n' | sort -u)" # echo "sigil ${SIGIL_PARAMS[@]}" sigil "${SIGIL_PARAMS[@]}" | cat -s > "$NGINX_CONF" if (is_deployed "$APP"); then dokku_log_info1 "Creating $SCHEME nginx.conf" mv "$NGINX_CONF" "$DOKKU_ROOT/$APP/nginx.conf" else dokku_log_info1 "App $APP has not been deployed. Skipping nginx config creation" rm -f "$NGINX_CONF" fi if (is_deployed "$APP"); then dokku_log_info1 "Running nginx-pre-reload" plugn trigger nginx-pre-reload "$APP" "$DOKKU_APP_LISTEN_PORT" "$DOKKU_APP_LISTEN_IP" dokku_log_verbose "Reloading nginx" validate_nginx && restart_nginx fi if ([[ -n "$NONSSL_VHOSTS" ]] || [[ -n "$SSL_VHOSTS" ]]) && [[ "$IS_APP_VHOST_ENABLED" == "true" ]]; then echo "# THIS FILE IS GENERATED BY DOKKU - DO NOT EDIT, YOUR CHANGES WILL BE OVERWRITTEN" > "$URLS_PATH" xargs -i echo "$SCHEME://{}" <<< "$(echo "${SSL_VHOSTS}" "${NONSSL_VHOSTS}" | tr ' ' '\n' | sort -u)" >> "$URLS_PATH" fi else # note because this clause is long. if the proxy is disabled: dokku_log_info1 "nginx support is disabled for app ($APP)." if [[ -f "$DOKKU_ROOT/$APP/nginx.conf" ]]; then dokku_log_info1 "deleting nginx.conf" rm "$DOKKU_ROOT/$APP/nginx.conf" if (is_deployed "$APP"); then dokku_log_info1 "reloading nginx after nginx.conf deletion" validate_nginx && restart_nginx fi fi fi }