|
|
# -*- mode: shell-script -*-
replace_by_rematch_pattern() { local input="$1" output="" char next_char i # Loop through each character in the string for (( i=0; i<${#input}; i++ )); do char="${input:$i:1}"
# If a dollar sign is found if [[ "$char" == '$' ]]; then next_char="${input:$((i+1)):1}" # Check if next character is a digit if [[ "$next_char" =~ [0-9] ]]; then # Replace $N with ${rematch[N]} output+='${rematch['"$next_char"']}' ((i++)) # Skip next character as it's already processed continue fi fi
output+="$char" done
echo "$output" } export -f replace_by_rematch_pattern
get_domains() { local cfg="$1" service_cfg="$2" cache_file="$state_tmpdir/$FUNCNAME.cache.$(H "$SERVICE_NAME" "$MASTER_BASE_SERVICE_NAME" "$@")" \ domains new_domains maps m regex rematch type value key domain if [ -e "$cache_file" ]; then cat "$cache_file" return 0 fi domains=() for key in domain server-aliases; do type=$(e "$cfg" | shyaml -q get-type "$key" 2>/dev/null) || true value=$(e "$cfg" | shyaml -q get-value -y "$key" 2>/dev/null) || true while true; do case "$type" in sequence) while read-0 domain; do if [[ "$domain" != "None" ]]; then if [ "${#domains[@]}" == 0 ] && [ "$key" == "server-aliases" ]; then err "You can't specify server aliases if you don't have a domain." return 1 fi
domains+=("$domain") fi done < <(e "$value" | shyaml get-values-0) ;; str) if ! domain=$(e "$value" | shyaml get-value 2>/dev/null); then err "Failed to get domain value from config." return 1 fi [[ "$domain" == "" ]] && break if [ "${#domains[@]}" == 0 ] && [ "$key" == "server-aliases" ]; then err "You can't specify server aliases if you don't have a domain." return 1 fi
domains+=("$domain") ;; NoneType|"") : ;; \!*) value=$(e "$value" | cfg-get-value "$key" 2>/dev/null) || true type=$(e "$value" | shyaml -q get-type) || true continue ;; esac break done ## check and expand domain new_domains=() for domain in "${domains[@]}"; do if ! [[ "$domain" =~ ^[a-z0-9\{\}*\ \,.-]+$ ]]; then err "Invalid domain value '$domain' expression in ${WHITE}$key$NORMAL option." return 1 fi new_domains+=($(eval echo "${domain//\*/\\*}")) done domains=("${new_domains[@]}") done ## Fill with service-domain-map if [ "${#domains[@]}" == 0 ] && service_domain_map=$(e "$service_cfg" | cfg-get-value 'options.service-domain-map' 2>/dev/null) && [ -n "$service_domain_map" ]; then while read-0 regex map; do if [[ "$BASE_SERVICE_NAME" =~ ^$regex$ ]]; then rematch=("${BASH_REMATCH[@]}") maps=() type=$(e "$map" | shyaml -q get-type 2>/dev/null) || true value=$(e "$map" | shyaml -q get-value -y 2>/dev/null) || true case "$type" in sequence) while read-0 m; do if [[ "$m" != "None" ]] && [ -n "$m" ]; then maps+=("$m") fi done < <(e "$value" | shyaml get-values-0) ;; str) if ! m=$(e "$value" | shyaml get-value 2>/dev/null); then err "Failed to get mapping value from config." return 1 fi [ -z "$m" ] && continue maps+=("$m") ;; NoneType|"") : ;; esac for map in "${maps[@]}"; do if ! [[ "$map" =~ ^([a-z0-9*\{\}\ \,.-]|\$[0-9])+$ ]]; then err "Invalid mapping value '$map' in ${WHITE}service-domain-map$NORMAL option." return 1 fi
map="${map//\*/\\*}" ## protect star from eval map=$(replace_by_rematch_pattern "$map")
domains+=($(set -- "${BASH_REMATCH[@]}"; eval echo "${map//\*/\\*}")) done break fi done < <(e "$service_domain_map" | yq e -0 'to_entries | .[] | [.key, .value] |.[]') fi if [ "${#domains[@]}" == 0 ] && [[ "$BASE_SERVICE_NAME" =~ ^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$ ]]; then domains+=("$BASE_SERVICE_NAME") fi new_domains=() ## remove duplicates for domain in "${domains[@]}"; do if ! [[ " ${new_domains[*]} " == *" $domain "* ]]; then new_domains+=("$domain") fi done domains=("${new_domains[@]}") if [ "${#domains[@]}" == 0 ]; then err "No domain name set for your service ${YELLOW}$BASE_SERVICE_NAME${NORMAL}." echo " You can specify a ${WHITE}domain$NORMAL option in the" \ "relation with ${YELLOW}$TARGET_SERVICE_NAME${NORMAL}." >&2 echo " Or you can specify a service to domain mapping in" \ "${WHITE}service-domain-map${NORMAL} option of ${YELLOW}$TARGET_SERVICE_NAME${NORMAL}." >&2 return 1 fi ## check that first domain should not have a wildcard if [[ "${domains[0]}" == *"*"* ]]; then err "First domain name '${domains[0]}' can't contain a wildcard." return 1 fi echo "${domains[@]}" | tee "$cache_file" }
## ## Master entrypoints ##
apache_proxy_dir() { local cfg="$1" service_cfg="$2" apache_vhost_create web_proxy "$cfg" "$service_cfg" || return 1 } export -f apache_proxy_dir
apache_publish_dir() { local cfg="$1" service_cfg="$2" apache_vhost_create publish_dir "$cfg" "$service_cfg" || return 1 apache_code_dir "$cfg" "$service_cfg" || return 1 apache_data_dirs "$cfg" "$service_cfg" } export -f apache_publish_dir
apache_ssh_tunnel() { local cfg="$1" domain ssh_tunnel domain=$(e "$cfg" | cfg-get-value domain) || { err "${WHITE}domain${NORMAL} must be valued in ${WHITE}ssh-tunnel${NORMAL} config." return 1 } protocols=$(__vhost_cfg_normalize_protocol "$cfg") || return 1 if ! is_protocol_enabled https "$protocols"; then err "${WHITE}ssl${NORMAL} must be valued in ${WHITE}ssh-tunnel${NORMAL} config." return 1 fi apache_vhost_create ssh_tunnel "$cfg" "" ",https," "000-$domain" || return 1 } export -f apache_publish_dir
## ## Simple functions ##
apache_vhost_create() { local type="$1" cfg="$2" service_cfg="$3" protocols="$4" dest="$5" custom_rules vhost_statement creds \ redirect domain ssl_plugin_fun ssl_cfg_value ssl_cfg_options
export APACHE_CONFIG_LOCATION="$SERVICE_CONFIGSTORE/etc/apache2/sites-enabled" if [ "$cfg" == "None" ]; then cfg= fi if [ -z "$protocols" ]; then protocols=$(__vhost_cfg_normalize_protocol "$cfg") || return 1 fi
domains=($(get_domains "$cfg" "$service_cfg")) && { [ "$RELATION_DATA_FILE" ] && relation-set domain "${domains[0]}" } echo "Domains: ${domains[*]}" >&2 if is_protocol_enabled https "$protocols"; then [ "$RELATION_DATA_FILE" ] && { relation-set url "https://${domains[0]}" }
if [ "${#domains[@]}" == 0 ]; then err "You must specify a domain for ssl to work." return 1 fi read-0 ssl_plugin_fun ssl_cfg_value ssl_cfg_options < <(ssl_get_plugin_fun "$cfg") || return 1 "$ssl_plugin_fun"_vars "$cfg" "$ssl_cfg_options" "$ssl_cfg_value" "${domains[*]}" || return 1 redirect=$(e "$cfg" | cfg-get-value 'redirect-to-ssl' 2>/dev/null) || true if is_protocol_enabled http "$protocols"; then redirect=${redirect:-true} else redirect=false fi if [ "$redirect" == "true" ]; then custom_rules=$(_get_custom_rules "$cfg") || return 1 if [[ "$custom_rules" != *"## Auto-redirection from http to https"* ]]; then redirect_rule="- | ## Auto-redirection from http to https RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=302,L,QSA] " [ "$RELATION_DATA_FILE" ] && \ relation-set apache-custom-rules "$redirect_rule $(if [ "$custom_rules" ]; then echo "- |"$'\n'"$(echo "$custom_rules" | prefix " ")" fi)" cfg=$(merge_yaml_str "$cfg" "$(yaml_key_val_str "apache-custom-rules" "$redirect_rule $(if [ "$custom_rules" ]; then echo "- |"$'\n'"$(echo "$custom_rules" | prefix " ")" fi)")") || return 1 fi fi else [ "$RELATION_DATA_FILE" ] && { relation-set url "http://${domains[0]}" } fi vhost_statement=$(apache_vhost_statement "$type" "$protocols" "$cfg" "${domains[*]}") || { err "Failed to get vhost statement for type $type on ${protocols:1:-1}" return 1 }
dest=${dest:-${domains[0]}} if [ -z "$dest" ]; then err "Please set either a domain or set a destination file." return 1 fi echo "$vhost_statement" | file_put "$APACHE_CONFIG_LOCATION/$dest.conf" || return 1
creds=$(e "$cfg" | cfg-get-value creds 2>/dev/null) || true if [ "$creds" ]; then apache_passwd_file "$cfg" "$dest"|| return 1 fi
if is_protocol_enabled https "$protocols"; then "$ssl_plugin_fun"_prepare "$cfg" "$ssl_cfg_options" "$ssl_cfg_value" "${domains[*]}" || return 1 fi }
is_protocol_enabled() { local protocol="$1" protocols="$2" [[ "$protocols" == *",$protocol,"* ]] } export -f is_protocol_enabled
_get_ssl_option_value() { local cfg="$1" cache_file="$state_tmpdir/$FUNCNAME.cache.$(H "$SERVICE_NAME" "$MASTER_BASE_SERVICE_NAME" "$@")" \ target_relation rn ts rc td
if [ -e "$cache_file" ]; then cat "$cache_file" return 0 fi
if ssl_cfg=$(e "$cfg" | cfg-get-value ssl 2>/dev/null); then if [[ "$ssl_cfg" =~ ^False|None|false|null$ ]]; then ssl_cfg="" fi echo "$ssl_cfg" | tee "$cache_file" return 0 fi
target_relation="cert-provider" while read-0 rn ts rc td; do [ "$rn" == "${target_relation}" ] || continue info "A cert-provider '$ts' declared as 'ssl' value" echo "$ts" | tee "$cache_file" return 0 done < <(get_service_relations "$SERVICE_NAME")
return 1 }
__vhost_cfg_normalize_protocol() { local cfg="$1" protocol ssl ## XXXvlab: can't cache if libcharm is not able to give me some checksums ## indeed, ``_get_ssl_option_value`` depends on relations calculations...
# local cfg="$1" cache_file="$CACHEDIR/$FUNCNAME.cache.$(p0 "$@" | md5_compat)" \ # protocol # if [ -e "$cache_file" ]; then # #debug "$FUNCNAME: STATIC cache hit $1" # cat "$cache_file" && # touch "$cache_file" || return 1 # return 0 # fi
if protocol=$(e "$cfg" | cfg-get-value protocol 2>/dev/null); then protocol=${protocol:-auto} else protocol=auto fi
case "$protocol" in auto) ssl=$(_get_ssl_option_value "$cfg" 2>/dev/null) if [ "$ssl" ]; then protocol="http,https" else protocol="http" fi ;; both) protocol="http,https" ;; ssl|https) protocol="https" ;; http) protocol="http" ;; *) err "Invalid value '$protocol' for ${WHITE}protocol$NORMAL option (use one of: http, https, both, auto)." return 1 esac echo -n ",$protocol," #| tee "$cache_file" }
## ssl_plugin_* and ssl_fallback should : ## - do anything to ensure that ## - issue config-add to add volumes if necessary ## - output 3 vars of where to find the 3 files from within the docker apache
ssl_get_plugin_fun() { # from ssl conf, return the function that should manage SSL code creation local master_cfg="$1" cfg type keys cfg=$(_get_ssl_option_value "$master_cfg") || { err "No ssl options available." return 1 }
local cache_file="$state_tmpdir/$FUNCNAME.cache.$(H "$SERVICE_NAME" "$cfg")" if [ -e "$cache_file" ]; then cat "$cache_file" return 0 fi [ "$cfg" ] || { touch "$cache_file" return 0 }
type="$(echo "$cfg" | shyaml -y get-type 2>/dev/null)" || return 1 if [[ "$type" == "bool" ]] || [[ "$type" == "str" && "$cfg" =~ ^false|true$ ]]; then printf "%s\0" "ssl_fallback" "" "$cfg" | tee "$cache_file" return 0 fi
if ! [[ "$type" == "str" || "$type" == "struct" ]]; then err "Invalid ${WHITE}ssl${NORMAL} value type '$type': please provide a string or a struct." return 1 fi
if [ -z "$NO_CERT_PROVIDER" ]; then if [[ "$type" == "str" ]]; then keys=("$cfg") else keys=($(echo "$cfg" | shyaml keys 2>/dev/null)) fi for key in "${keys[@]}"; do target_relation="cert-provider" fun="ssl_plugin_${target_relation}" while read-0 relation_name target_service relation_config tech_dep; do [ "$relation_name" == "${target_relation}" ] || continue [ "$target_service" == "$key" ] || continue verb "Corresponding plugin ${DARKGREEN}found${NORMAL}" \ "in ${DARKBLUE}$relation_name${NORMAL}/${DARKYELLOW}$key${NORMAL}" ssl_cfg=$(printf "%s" "$cfg" | shyaml get-value "$key" 2>/dev/null) || true merged_config=$(merge_yaml_str "$relation_config" "$ssl_cfg") || return 1 printf "%s\0" "$fun" "$key" "$merged_config" | tee "$cache_file" return 0 done < <(get_service_relations "$SERVICE_NAME") || return 1 case "$key" in cert|ca-cert|key) : ;; *) err "Invalid key '$key' in ${WHITE}ssl${NORMAL}:" \ "no corresponding services declared in ${DARKBLUE}${target_relation}$NORMAL" return 1 ;; esac done fi ## No key of the struct seem to be declared cert-provider, so fallback printf "%s\0" "ssl_fallback" "" "$cfg" | tee "$cache_file" }
ssl_fallback_vars() { local cfg="$1" ssl_cfg="$2" value="$3" domains="$4" cert key ca_cert domains=($domains) if __vhost_cfg_ssl_cert=$(echo "$ssl_cfg" | shyaml get-value cert 2>/dev/null); then __vhost_cfg_SSL_CERT_LOCATION=/etc/ssl/certs/${domains[0]}.pem fi
if __vhost_cfg_ssl_key=$(echo "$ssl_cfg" | shyaml get-value key 2>/dev/null); then __vhost_cfg_SSL_KEY_LOCATION=/etc/ssl/private/${domains[0]}.key fi
if __vhost_cfg_ssl_ca_cert=$(echo "$ssl_cfg" | shyaml get-value ca-cert 2>/dev/null); then __vhost_cfg_SSL_CA_CERT_LOCATION=/etc/ssl/certs/${domains[0]}-ca.pem fi }
ssl_fallback_prepare() { local cfg="$1" cert key ca_cert
dst="$CONFIGSTORE/$BASE_SERVICE_NAME" volumes="" for label in cert key ca_cert; do content="$(eval echo "\"\$__vhost_cfg_ssl_$label\"")" if [ "$content" ]; then location="$(eval echo "\$__vhost_cfg_SSL_${label^^}_LOCATION")" echo "$content" | file_put "$dst$location" config_hash=$(printf "%s\0" "$config_hash" "$label" "$content" | md5_compat) volumes="$volumes - $dst$location:$location:ro" fi done
if [ "$volumes" ]; then init-config-add "\ $MASTER_TARGET_SERVICE_NAME: volumes: $volumes " fi
}
ssl_plugin_cert-provider_vars() { local cfg="$1" ssl_cfg="$2" value="$3" domains="$4" domains=($domains) __vhost_cfg_SSL_CERT_LOCATION=/etc/letsencrypt/live/${domains[0]}/cert.pem __vhost_cfg_SSL_KEY_LOCATION=/etc/letsencrypt/live/${domains[0]}/privkey.pem __vhost_cfg_SSL_CHAIN=/etc/letsencrypt/live/${domains[0]}/chain.pem }
ssl_plugin_cert-provider_prepare() { local cfg="$1" ssl_cfg="$2" service="$3" domains="$4" options server_aliases domains=($domains) options=$(yaml_key_val_str "options" "$ssl_cfg") || return 1 service_config=$(yaml_key_val_str "$service" "$options") compose --debug --add-compose-content "$service_config" crt "$service" \ create "${domains[@]}" || { err "Failed to launch letsencrypt for certificate creation." return 1 } init-config-add "\ $SERVICE_NAME: volumes: - $DATASTORE/$service/etc/letsencrypt:/etc/letsencrypt:ro " || return 1
}
apache_passwd_file() { local cfg="$1" dest="$2" creds include parse || true
## XXXvlab: called twice... no better way to do this ? creds=$(e "$cfg" | cfg-get-value creds 2>/dev/null) || true password_path=$(password-path-get "$dest") first= if ! [ -e "$CONFIGSTORE/$MASTER_TARGET_SERVICE_NAME$password_path" ]; then debug "No file $CONFIGSTORE/$MASTER_TARGET_SERVICE_NAME$password_path, creating password file." || true first=c fi while read-0 login password; do debug "htpasswd -b$first '${password_path}' '$login' '$password'" echo "htpasswd -b$first '${password_path}' '$login' '$password'" if [ "$first" ]; then first= fi done < <(e "$creds" | shyaml key-values-0 2>/dev/null) | docker run -i --entrypoint "/bin/bash" \ -v "$APACHE_CONFIG_LOCATION:/etc/apache2/sites-enabled" \ "$DOCKER_BASE_IMAGE" || return 1 }
## Produce the full statements depending on relation-get informations apache_vhost_statement() { local type="$1" protocols="$2" cfg="$3" domains="$4" \ vhost_statement
if is_protocol_enabled http "$protocols"; then __vhost_full_vhost_statement "$type" http "$cfg" "$domains" || return 1 fi if is_protocol_enabled https "$protocols"; then read-0 ssl_plugin_fun ssl_cfg_value ssl_cfg_options < <(ssl_get_plugin_fun "$cfg") || return 1 "$ssl_plugin_fun"_vars "$cfg" "$ssl_cfg_options" "$ssl_cfg_value" "$domains" || return 1 vhost_statement=$(__vhost_full_vhost_statement "$type" https "$cfg" "$domains") || return 1 cat <<EOF
<IfModule mod_ssl.c> $(echo "$vhost_statement" | prefix " ") </IfModule> EOF fi } export -f apache_vhost_statement
apache_code_dir() { local cfg="$1" service_cfg="$2" www_data_gid local_path www_data_gid=$(cached_cmd_on_base_image "$TARGET_SERVICE_NAME" 'id -g www-data') || { debug "Failed to query for www-data gid in ${DARKYELLOW}$TARGET_SERVICE_NAME${NORMAL} base image." return 1 } domains=($(get_domains "$cfg" "$service_cfg")) || return 1 local_path="/var/www/${domains[0]}" host_path=$(e "$cfg" | cfg-get-value location 2>/dev/null) || host_path="$DATASTORE/$BASE_SERVICE_NAME${local_path}"
## convert to docker host path case "$host_path" in "$DATASTORE"*) docker_host_path="$HOST_DATASTORE${host_path##$DATASTORE}" ;; "$CONFIGSTORE"*) docker_host_path="$HOST_CONFIGSTORE${host_path##$CONFIGSTORE}" ;; *) docker_host_path="$host_path" ;; esac
mkdir -p "$host_path" || return 1
init-config-add " $SERVICE_NAME: volumes: - \"${docker_host_path}:$local_path\" " }
apache_data_dirs() { local cfg="$1" service_cfg="$2" data_dirs dst data fdir to_create data_dirs=$(e "$cfg" | cfg-get-value data-dirs 2>/dev/null | shyaml get-values 2>/dev/null) || true if [ -z "$data_dirs" ]; then return 0 fi domains=($(get_domains "$cfg" "$service_cfg")) || return 1 local_path="/var/www/${domains[0]}" dst=$DATASTORE/$BASE_SERVICE_NAME$local_path data=() while IFS="," read -ra addr; do for dir in "${addr[@]}"; do data+=("$dir") done done <<< "$data_dirs"
www_data_gid=$(cached_cmd_on_base_image "$TARGET_SERVICE_NAME" 'id -g www-data') || { debug "Failed to query for www-data gid in ${DARKYELLOW}$TARGET_SERVICE_NAME${NORMAL} base image." return 1 } info "www-data gid from ${DARKYELLOW}$TARGET_SERVICE_NAME${NORMAL} is '$www_data_gid'"
to_create=() volumes="" for d in "${data[@]}"; do fdir="$dst/$d" volumes+=" - $HOST_DATASTORE${dst##$DATASTORE}/$d:$local_path/$d"$'\n' if ! [ -d "$fdir" ]; then to_create+=("$fdir") fi done
if [ "${#to_create[@]}" -gt 0 ]; then mkdir -p "${to_create[@]}" || return 1 chgrp -v "${www_data_gid}" "${to_create[@]}" || return 1 chmod g+rwx "${to_create[@]}" || return 1 fi
init-config-add " $SERVICE_NAME: volumes: $volumes"
}
deploy_files() { local src="$1" dst="$2"
if ! [ -d "$dst" ]; then err "Destination '$dst' does not exist or is not a directory" return 1 fi ( cd "$dst" && info "In $dst:" && get_file "$src" | tar xv ) } export -f deploy_files
apache_core_rules_add() { local conf="$1" dst="/etc/apache2/conf-enabled/$SERVICE_NAME.conf" debug "Adding core rule." echo "$conf" | file_put "$CONFIGSTORE/$SERVICE_NAME$dst" config_hash=$(printf "%s\0" "$config_hash" "$conf" | md5_compat) init-config-add " $SERVICE_NAME: volumes: - $CONFIGSTORE/$SERVICE_NAME$dst:$dst:ro " }
__vhost_ssl_statement() {
## defaults
__vhost_cfg_SSL_CERT_LOCATION=${__vhost_cfg_SSL_CERT_LOCATION:-/etc/ssl/certs/ssl-cert-snakeoil.pem} __vhost_cfg_SSL_KEY_LOCATION=${__vhost_cfg_SSL_KEY_LOCATION:-/etc/ssl/private/ssl-cert-snakeoil.key}
cat <<EOF
## ## SSL Configuration ##
SSLEngine On
SSLCertificateFile $__vhost_cfg_SSL_CERT_LOCATION SSLCertificateKeyFile $__vhost_cfg_SSL_KEY_LOCATION $([ -z "$__vhost_cfg_SSL_CA_CERT_LOCATION" ] || echo "SSLCACertificateFile $__vhost_cfg_SSL_CA_CERT_LOCATION") $([ -z "$__vhost_cfg_SSL_CHAIN" ] || echo "SSLCertificateChainFile $__vhost_cfg_SSL_CHAIN") SSLVerifyClient None
EOF
}
password-path-get() { local dest="$1" echo "/etc/apache2/sites-enabled/${dest}.passwd" }
__vhost_creds_statement() { local cfg="$1" dest="$2" password_path password_path=$(password-path-get "$dest") || return 1 if ! e "$cfg" | cfg-get-value creds >/dev/null 2>&1; then echo "Allow from all" return 0 fi
cat <<EOF AuthType basic AuthName "private" AuthUserFile ${password_path} Require valid-user EOF
}
__vhost_head_statement() { local cfg="$1" protocol="$2" domains="$3" server_aliases admin_mail prefix domains=($domains) admin_mail=$(e "$1" | cfg-get-value "admin-mail" 2>/dev/null) || true if [ -z "$admin_mail" ]; then if [ "${#domains[@]}" == 0 ]; then admin_mail=webmaster@localhost else admin_mail=${admin_mail:-contact@${domains[0]}} fi fi
if [ "$protocol" == "https" ]; then prefix="s-" else prefix= fi
if [ "${#domains[@]}" != 0 ]; then log_prefix="${prefix}${domains[0]}_" else log_prefix="" fi
cat <<EOF $( echo "ServerAdmin ${admin_mail}" [ "${#domains[@]}" != 0 ] && echo "ServerName ${domains[0]}" for alias in "${domains[@]:1}"; do [ "$alias" ] || continue echo "ServerAlias $alias" done )
ServerSignature Off CustomLog /var/log/apache2/${log_prefix}access.log combined ErrorLog /var/log/apache2/${log_prefix}error.log ErrorLog syslog:local2 EOF
}
_get_custom_rules() { local cfg="$1" custom_rules custom_rules=$(e "$cfg" | shyaml -y -q get-value apache-custom-rules 2>/dev/null) || true e "$custom_rules" | yaml_get_values }
__vhost_custom_rules() { local cfg="$1" custom_rules custom_rules=$(_get_custom_rules "$cfg") || return 1 if [ "$custom_rules" ]; then cat <<EOF
## ## Custom rules ##
$custom_rules
EOF fi }
__vhost_content_statement() { local type="$1" shift case "$type" in "web_proxy") __vhost_proxy_statement "$@" || return 1 ;; "publish_dir") __vhost_publish_dir_statement "$@" || return 1 ;; "ssh_tunnel") __vhost_tunnel_ssh_statement "$@" || return 1 ;; esac }
target-get() { local cfg="$1" target first_exposed_port base_image target=$(e "$cfg" | cfg-get-value target 2>/dev/null) || true if [ -z "$target" ]; then ## First exposed port: base_image=$(service_ensure_image_ready "$BASE_SERVICE_NAME") || return 1 if ! docker_has_image "$base_image"; then docker pull "$base_image" >&2 fi first_exposed_port=$(image_exposed_ports_0 "$base_image" | tr '\0' '\n' | head -n 1 | cut -f 1 -d /) || return 1 if [ -z "$first_exposed_port" ]; then err "Failed to get first exposed port of image '$base_image'." return 1 fi target=$MASTER_BASE_SERVICE_NAME:$first_exposed_port info "No target was specified, introspection found: $target" fi echo "$target" }
__vhost_proxy_statement() { local protocol="$1" cfg="$2" domains="$3" proxy_pass_options target domains=($domains) target=$(target-get "$cfg") || return 1 proxy_pass_options=($(e "$cfg" | shyaml -y -q get-value apache-proxy-pass-options | yaml_get_values)) if [ "${#proxy_pass_options[@]}" == 0 ]; then proxy_pass_options=(${proxy_pass_options:-"retry=0"}) fi dest=${domains[0]} dest=${dest:-html} cat <<EOF
## ## Proxy declaration towards $target ##
<IfModule mod_proxy.c> ProxyRequests Off <Proxy *> Order deny,allow Allow from all </Proxy> ProxyVia On ProxyPass / http://$target/ ${proxy_pass_options[*]} <Location / > $(__vhost_creds_statement "$cfg" "$dest" | prefix " ") ProxyPassReverse http://$target/ </Location> $([ "$protocol" == "https" ] && echo " SSLProxyEngine On") </IfModule>
SetEnvIf X-Forwarded-Proto "^$" forwarded_proto_not_set=true RequestHeader set "X-Forwarded-Proto" "$protocol" env=forwarded_proto_not_set
## Fix IE problem (httpapache proxy dav error 408/409) SetEnv proxy-nokeepalive 1 EOF
}
__vhost_full_vhost_statement() { local type="$1" protocol="$2" cfg="$3" domains="$4" head_statement custom_rules content_statement
domains=($domains) head_statement=$(__vhost_head_statement "$cfg" "$protocol" "${domains[*]}") || return 1 custom_rules=$(__vhost_custom_rules "$cfg") || return 1 content_statement=$(__vhost_content_statement "$type" "$protocol" "$cfg" "${domains[*]}") || return 1
case "$protocol" in https) PORT=443 ;; http) PORT=80 ;; esac
cat <<EOF <VirtualHost *:$PORT>
$( { echo "$head_statement" [ "$custom_rules" ] && echo "$custom_rules" echo "$content_statement" } | prefix " ")
## Forbid any cache, this is only usefull on dev server. #Header set Cache-Control "no-cache" #Header set Access-Control-Allow-Origin "*" #Header set Access-Control-Allow-Methods "POST, GET, OPTIONS" #Header set Access-Control-Allow-Headers "origin, content-type, accept" $([ "$protocol" == "https" ] && __vhost_ssl_statement | prefix " " && echo ) </VirtualHost> EOF
}
__vhost_publish_dir_statement() { local protocol="$1" cfg="$2" domains="$3" location domains=($domains) dest=${domains[0]} dest=${dest:-html} local_path="/var/www/${dest}"
cat <<EOF
## ## Publish directory $local_path ##
DocumentRoot $local_path
<Directory /> Options FollowSymLinks AllowOverride None </Directory>
<Directory $local_path> Options Indexes FollowSymLinks MultiViews AllowOverride all $(__vhost_creds_statement "$cfg" "$dest" | prefix " ") </Directory>
EOF }
__vhost_tunnel_ssh_statement() { local protocol="$1" cfg="$2" domains="$3" custom_rules content_statement domains=($domains) dest=${domains[0]} if [ "${#domains[@]}" == 0 ]; then err "You must specify a domain for ssh tunnel to work." return 1 fi cat <<EOF
## ## SSH Tunnel ##
#HostnameLookups On ProxyRequests On AllowConnect 22 #ProxyVia on
### Deny everything by default
<Proxy *> Order deny,allow Deny from all </proxy>
### Accept redirect only to same domain
<Proxy ${domains[0]}> Order deny,allow $(__vhost_creds_statement "$cfg" "$dest" | prefix " ") </Proxy>
EOF }
apache_config_hash() { debug "Adding config hash to enable recreating upon config change." config_hash=$({ printf "%s\0" "$config_hash" find "$SERVICE_CONFIGSTORE/etc/apache2/sites-enabled" \ -name \*.conf -exec md5sum {} \; } | md5_compat) || exit 1 init-config-add " $SERVICE_NAME: labels: - compose.config_hash=$config_hash " }
|