You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

846 lines
24 KiB

  1. # -*- mode: shell-script -*-
  2. get_domain() {
  3. local cfg="$1" cache_file="$state_tmpdir/$FUNCNAME.cache.$(H "$SERVICE_NAME" "$MASTER_BASE_SERVICE_NAME" "$@")" \
  4. domain
  5. if [ -e "$cache_file" ]; then
  6. cat "$cache_file"
  7. return 0
  8. fi
  9. domain=$(e "$cfg" | cfg-get-value domain 2>/dev/null) || true
  10. if [ "$domain" ]; then
  11. echo "$domain" | tee "$cache_file"
  12. elif [[ "$BASE_SERVICE_NAME" =~ ^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$ ]]; then
  13. echo "$BASE_SERVICE_NAME" | tee "$cache_file"
  14. else
  15. err "You must specify a ${WHITE}domain$NORMAL option. (${FUNCNAME[*]})"
  16. return 1
  17. fi
  18. }
  19. ##
  20. ## Master entrypoints
  21. ##
  22. apache_proxy_dir() {
  23. local cfg="$1" domain
  24. apache_vhost_create web_proxy "$cfg" || return 1
  25. }
  26. export -f apache_proxy_dir
  27. apache_publish_dir() {
  28. local cfg="$1" domain
  29. apache_vhost_create publish_dir "$cfg" || return 1
  30. apache_code_dir "$cfg" || return 1
  31. apache_data_dirs "$cfg"
  32. }
  33. export -f apache_publish_dir
  34. apache_ssh_tunnel() {
  35. local cfg="$1" domain ssh_tunnel
  36. domain=$(e "$cfg" | cfg-get-value domain) || {
  37. err "${WHITE}domain${NORMAL} must be valued in ${WHITE}ssh-tunnel${NORMAL} config."
  38. return 1
  39. }
  40. protocols=$(__vhost_cfg_normalize_protocol "$cfg") || return 1
  41. if ! is_protocol_enabled https "$protocols"; then
  42. err "${WHITE}ssl${NORMAL} must be valued in ${WHITE}ssh-tunnel${NORMAL} config."
  43. return 1
  44. fi
  45. apache_vhost_create ssh_tunnel "$cfg" ",https," "000-$domain" || return 1
  46. }
  47. export -f apache_publish_dir
  48. ##
  49. ## Simple functions
  50. ##
  51. apache_vhost_create() {
  52. local type="$1" cfg="$2" protocols="$3" dest="$4" custom_rules vhost_statement creds \
  53. redirect domain ssl_plugin_fun ssl_cfg_value ssl_cfg_options
  54. export APACHE_CONFIG_LOCATION="$SERVICE_CONFIGSTORE/etc/apache2/sites-enabled"
  55. if [ "$cfg" == "None" ]; then
  56. cfg=
  57. fi
  58. if [ -z "$protocols" ]; then
  59. protocols=$(__vhost_cfg_normalize_protocol "$cfg") || return 1
  60. fi
  61. domain=$(get_domain "$cfg") && {
  62. [ "$RELATION_DATA_FILE" ] && relation-set domain "$domain"
  63. }
  64. if is_protocol_enabled https "$protocols"; then
  65. [ "$RELATION_DATA_FILE" ] && {
  66. relation-set url "https://$domain"
  67. }
  68. if [ -z "$domain" ]; then
  69. err "You must specify a domain for ssl to work."
  70. return 1
  71. fi
  72. read-0 ssl_plugin_fun ssl_cfg_value ssl_cfg_options < <(ssl_get_plugin_fun "$cfg") || return 1
  73. "$ssl_plugin_fun"_vars "$cfg" "$ssl_cfg_options" "$ssl_cfg_value" "$domain" || return 1
  74. redirect=$(e "$cfg" | cfg-get-value 'redirect-to-ssl' 2>/dev/null) || true
  75. if is_protocol_enabled http "$protocols"; then
  76. redirect=${redirect:-true}
  77. else
  78. redirect=false
  79. fi
  80. if [ "$redirect" == "true" ]; then
  81. custom_rules=$(_get_custom_rules "$cfg") || return 1
  82. if [[ "$custom_rules" != *"## Auto-redirection from http to https"* ]]; then
  83. redirect_rule="- |
  84. ## Auto-redirection from http to https
  85. RewriteEngine On
  86. RewriteCond %{HTTPS} off
  87. RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=302,L,QSA]
  88. "
  89. [ "$RELATION_DATA_FILE" ] && \
  90. relation-set apache-custom-rules "$redirect_rule
  91. $(if [ "$custom_rules" ]; then
  92. echo "- |"$'\n'"$(echo "$custom_rules" | prefix " ")"
  93. fi)"
  94. cfg=$(merge_yaml_str "$cfg" "$(yaml_key_val_str "apache-custom-rules" "$redirect_rule
  95. $(if [ "$custom_rules" ]; then
  96. echo "- |"$'\n'"$(echo "$custom_rules" | prefix " ")"
  97. fi)")") || return 1
  98. fi
  99. fi
  100. else
  101. [ "$RELATION_DATA_FILE" ] && {
  102. relation-set url "http://$domain"
  103. }
  104. fi
  105. vhost_statement=$(apache_vhost_statement "$type" "$protocols" "$cfg" "$domain") || {
  106. err "Failed to get vhost statement for type $type on ${protocols:1:-1}"
  107. return 1
  108. }
  109. dest=${dest:-$domain}
  110. if [ -z "$dest" ]; then
  111. err "Please set either a domain or set a destination file."
  112. return 1
  113. fi
  114. echo "$vhost_statement" | file_put "$APACHE_CONFIG_LOCATION/$dest.conf" || return 1
  115. creds=$(e "$cfg" | cfg-get-value creds 2>/dev/null) || true
  116. if [ "$creds" ]; then
  117. apache_passwd_file "$cfg" "$dest"|| return 1
  118. fi
  119. if is_protocol_enabled https "$protocols"; then
  120. "$ssl_plugin_fun"_prepare "$cfg" "$ssl_cfg_options" "$ssl_cfg_value" || return 1
  121. fi
  122. }
  123. is_protocol_enabled() {
  124. local protocol="$1" protocols="$2"
  125. [[ "$protocols" == *",$protocol,"* ]]
  126. }
  127. export -f is_protocol_enabled
  128. _get_ssl_option_value() {
  129. local cfg="$1" cache_file="$state_tmpdir/$FUNCNAME.cache.$(H "$SERVICE_NAME" "$MASTER_BASE_SERVICE_NAME" "$@")" \
  130. target_relation rn ts rc td
  131. if [ -e "$cache_file" ]; then
  132. cat "$cache_file"
  133. return 0
  134. fi
  135. if ssl_cfg=$(e "$cfg" | cfg-get-value ssl 2>/dev/null); then
  136. if [[ "$ssl_cfg" =~ ^False|None$ ]]; then
  137. ssl_cfg=""
  138. fi
  139. echo "$ssl_cfg" | tee "$cache_file"
  140. return 0
  141. fi
  142. target_relation="cert-provider"
  143. while read-0 rn ts rc td; do
  144. [ "$rn" == "${target_relation}" ] || continue
  145. info "A cert-provider '$ts' declared as 'ssl' value"
  146. echo "$ts" | tee "$cache_file"
  147. return 0
  148. done < <(get_service_relations "$SERVICE_NAME")
  149. return 1
  150. }
  151. __vhost_cfg_normalize_protocol() {
  152. local cfg="$1" protocol ssl
  153. ## XXXvlab: can't cache if libcharm is not able to give me some checksums
  154. ## indeed, ``_get_ssl_option_value`` depends on relations calculations...
  155. # local cfg="$1" cache_file="$CACHEDIR/$FUNCNAME.cache.$(p0 "$@" | md5_compat)" \
  156. # protocol
  157. # if [ -e "$cache_file" ]; then
  158. # #debug "$FUNCNAME: STATIC cache hit $1"
  159. # cat "$cache_file" &&
  160. # touch "$cache_file" || return 1
  161. # return 0
  162. # fi
  163. if protocol=$(e "$cfg" | cfg-get-value protocol 2>/dev/null); then
  164. protocol=${protocol:-auto}
  165. else
  166. protocol=auto
  167. fi
  168. case "$protocol" in
  169. auto)
  170. ssl=$(_get_ssl_option_value "$cfg" 2>/dev/null)
  171. if [ "$ssl" ]; then
  172. protocol="http,https"
  173. else
  174. protocol="http"
  175. fi
  176. ;;
  177. both)
  178. protocol="http,https"
  179. ;;
  180. ssl|https)
  181. protocol="https"
  182. ;;
  183. http)
  184. protocol="http"
  185. ;;
  186. *)
  187. err "Invalid value '$protocol' for ${WHITE}protocol$NORMAL option (use one of: http, https, both, auto)."
  188. return 1
  189. esac
  190. echo -n ",$protocol,"
  191. #| tee "$cache_file"
  192. }
  193. ## ssl_plugin_* and ssl_fallback should :
  194. ## - do anything to ensure that
  195. ## - issue config-add to add volumes if necessary
  196. ## - output 3 vars of where to find the 3 files from within the docker apache
  197. ssl_get_plugin_fun() {
  198. # from ssl conf, return the function that should manage SSL code creation
  199. local master_cfg="$1" cfg type keys
  200. cfg=$(_get_ssl_option_value "$master_cfg") || {
  201. err "No ssl options available."
  202. return 1
  203. }
  204. local cache_file="$state_tmpdir/$FUNCNAME.cache.$(H "$SERVICE_NAME" "$cfg")"
  205. if [ -e "$cache_file" ]; then
  206. cat "$cache_file"
  207. return 0
  208. fi
  209. [ "$cfg" ] || {
  210. touch "$cache_file"
  211. return 0
  212. }
  213. type="$(echo "$cfg" | shyaml -y get-type 2>/dev/null)" || return 1
  214. if [[ "$type" == "bool" ]]; then
  215. printf "%s\0" "ssl_fallback" "" "$cfg" | tee "$cache_file"
  216. return 0
  217. fi
  218. if ! [[ "$type" == "str" || "$type" == "struct" ]]; then
  219. err "Invalid ${WHITE}ssl${NORMAL} value type '$type': please provide a string or a struct."
  220. return 1
  221. fi
  222. if [ -z "$NO_CERT_PROVIDER" ]; then
  223. if [[ "$type" == "str" ]]; then
  224. keys=("$cfg")
  225. else
  226. keys=($(echo "$cfg" | shyaml keys 2>/dev/null))
  227. fi
  228. for key in "${keys[@]}"; do
  229. target_relation="cert-provider"
  230. fun="ssl_plugin_${target_relation}"
  231. while read-0 relation_name target_service relation_config tech_dep; do
  232. [ "$relation_name" == "${target_relation}" ] || continue
  233. [ "$target_service" == "$key" ] || continue
  234. verb "Corresponding plugin ${DARKGREEN}found${NORMAL}" \
  235. "in ${DARKBLUE}$relation_name${NORMAL}/${DARKYELLOW}$key${NORMAL}"
  236. ssl_cfg=$(printf "%s" "$cfg" | shyaml get-value "$key" 2>/dev/null) || true
  237. merged_config=$(merge_yaml_str "$relation_config" "$ssl_cfg") || return 1
  238. printf "%s\0" "$fun" "$key" "$merged_config" | tee "$cache_file"
  239. return 0
  240. done < <(get_service_relations "$SERVICE_NAME") || return 1
  241. case "$key" in
  242. cert|ca-cert|key)
  243. :
  244. ;;
  245. *)
  246. err "Invalid key '$key' in ${WHITE}ssl${NORMAL}:" \
  247. "no corresponding services declared in ${DARKBLUE}${target_relation}$NORMAL"
  248. return 1
  249. ;;
  250. esac
  251. done
  252. fi
  253. ## No key of the struct seem to be declared cert-provider, so fallback
  254. printf "%s\0" "ssl_fallback" "" "$cfg" | tee "$cache_file"
  255. }
  256. ssl_fallback_vars() {
  257. local cfg="$1" ssl_cfg="$2" value="$3" domain="$4" cert key ca_cert domain
  258. if __vhost_cfg_ssl_cert=$(echo "$ssl_cfg" | shyaml get-value cert 2>/dev/null); then
  259. __vhost_cfg_SSL_CERT_LOCATION=/etc/ssl/certs/${domain}.pem
  260. fi
  261. if __vhost_cfg_ssl_key=$(echo "$ssl_cfg" | shyaml get-value key 2>/dev/null); then
  262. __vhost_cfg_SSL_KEY_LOCATION=/etc/ssl/private/${domain}.key
  263. fi
  264. if __vhost_cfg_ssl_ca_cert=$(echo "$ssl_cfg" | shyaml get-value ca-cert 2>/dev/null); then
  265. __vhost_cfg_SSL_CA_CERT_LOCATION=/etc/ssl/certs/${domain}-ca.pem
  266. fi
  267. }
  268. ssl_fallback_prepare() {
  269. local cfg="$1" cert key ca_cert
  270. dst="$CONFIGSTORE/$BASE_SERVICE_NAME"
  271. volumes=""
  272. for label in cert key ca_cert; do
  273. content="$(eval echo "\"\$__vhost_cfg_ssl_$label\"")"
  274. if [ "$content" ]; then
  275. location="$(eval echo "\$__vhost_cfg_SSL_${label^^}_LOCATION")"
  276. echo "$content" | file_put "$dst$location"
  277. config_hash=$(printf "%s\0" "$config_hash" "$label" "$content" | md5_compat)
  278. volumes="$volumes
  279. - $dst$location:$location:ro"
  280. fi
  281. done
  282. if [ "$volumes" ]; then
  283. init-config-add "\
  284. $MASTER_TARGET_SERVICE_NAME:
  285. volumes:
  286. $volumes
  287. "
  288. fi
  289. }
  290. ssl_plugin_cert-provider_vars() {
  291. local cfg="$1" ssl_cfg="$2" value="$3" domain="$4"
  292. __vhost_cfg_SSL_CERT_LOCATION=/etc/letsencrypt/live/${domain}/cert.pem
  293. __vhost_cfg_SSL_KEY_LOCATION=/etc/letsencrypt/live/${domain}/privkey.pem
  294. __vhost_cfg_SSL_CHAIN=/etc/letsencrypt/live/${domain}/chain.pem
  295. }
  296. ssl_plugin_cert-provider_prepare() {
  297. local cfg="$1" ssl_cfg="$2" service="$3" options domain server_aliases
  298. domain=$(get_domain "$cfg") || return 1
  299. options=$(yaml_key_val_str "options" "$ssl_cfg") || return 1
  300. service_config=$(yaml_key_val_str "$service" "$options")
  301. server_aliases=$(e "$cfg" | cfg-get-value server-aliases 2>/dev/null) || true
  302. [ "$server_aliases" == None ] && server_aliases=""
  303. if [ "$server_aliases" ]; then
  304. server_aliases=($(echo "$server_aliases" | shyaml get-values)) || return 1
  305. else
  306. server_aliases=()
  307. fi
  308. compose --debug --add-compose-content "$service_config" crt "$service" \
  309. create "$domain" "${server_aliases[@]}" || {
  310. err "Failed to launch letsencrypt for certificate creation."
  311. return 1
  312. }
  313. init-config-add "\
  314. $SERVICE_NAME:
  315. volumes:
  316. - $DATASTORE/$service/etc/letsencrypt:/etc/letsencrypt:ro
  317. " || return 1
  318. }
  319. apache_passwd_file() {
  320. local cfg="$1" dest="$2" creds
  321. include parse || true
  322. ## XXXvlab: called twice... no better way to do this ?
  323. creds=$(e "$cfg" | cfg-get-value creds 2>/dev/null) || true
  324. password_path=$(password-path-get "$dest")
  325. first=
  326. if ! [ -e "$CONFIGSTORE/$MASTER_TARGET_SERVICE_NAME$password_path" ]; then
  327. debug "No file $CONFIGSTORE/$MASTER_TARGET_SERVICE_NAME$password_path, creating password file." || true
  328. first=c
  329. fi
  330. while read-0 login password; do
  331. debug "htpasswd -b$first '${password_path}' '$login' '$password'"
  332. echo "htpasswd -b$first '${password_path}' '$login' '$password'"
  333. if [ "$first" ]; then
  334. first=
  335. fi
  336. done < <(e "$creds" | shyaml key-values-0 2>/dev/null) |
  337. docker run -i --entrypoint "/bin/bash" \
  338. -v "$APACHE_CONFIG_LOCATION:/etc/apache2/sites-enabled" \
  339. "$DOCKER_BASE_IMAGE" || return 1
  340. }
  341. ## Produce the full statements depending on relation-get informations
  342. apache_vhost_statement() {
  343. local type="$1" protocols="$2" cfg="$3" domain="$4" \
  344. vhost_statement
  345. if is_protocol_enabled http "$protocols"; then
  346. __vhost_full_vhost_statement "$type" http "$cfg" "$domain" || return 1
  347. fi
  348. if is_protocol_enabled https "$protocols"; then
  349. read-0 ssl_plugin_fun ssl_cfg_value ssl_cfg_options < <(ssl_get_plugin_fun "$cfg") || return 1
  350. "$ssl_plugin_fun"_vars "$cfg" "$ssl_cfg_options" "$ssl_cfg_value" "$domain" || return 1
  351. vhost_statement=$(__vhost_full_vhost_statement "$type" https "$cfg" "$domain") || return 1
  352. cat <<EOF
  353. <IfModule mod_ssl.c>
  354. $(echo "$vhost_statement" | prefix " ")
  355. </IfModule>
  356. EOF
  357. fi
  358. }
  359. export -f apache_vhost_statement
  360. apache_code_dir() {
  361. local cfg="$1" www_data_gid local_path
  362. www_data_gid=$(cached_cmd_on_base_image "$TARGET_SERVICE_NAME" 'id -g www-data') || {
  363. debug "Failed to query for www-data gid in ${DARKYELLOW}$TARGET_SERVICE_NAME${NORMAL} base image."
  364. return 1
  365. }
  366. domain=$(get_domain "$cfg") || return 1
  367. local_path="/var/www/${domain}"
  368. host_path=$(e "$cfg" | cfg-get-value location 2>/dev/null) ||
  369. host_path="$DATASTORE/$BASE_SERVICE_NAME${local_path}"
  370. ## convert to docker host path
  371. case "$host_path" in
  372. "$DATASTORE"*)
  373. docker_host_path="$HOST_DATASTORE${host_path##$DATASTORE}"
  374. ;;
  375. "$CONFIGSTORE"*)
  376. docker_host_path="$HOST_CONFIGSTORE${host_path##$CONFIGSTORE}"
  377. ;;
  378. *)
  379. docker_host_path="$host_path"
  380. ;;
  381. esac
  382. mkdir -p "$host_path" || return 1
  383. init-config-add "
  384. $SERVICE_NAME:
  385. volumes:
  386. - \"${docker_host_path}:$local_path\"
  387. "
  388. }
  389. apache_data_dirs() {
  390. local cfg="$1" data_dirs dst data fdir to_create
  391. data_dirs=$(e "$cfg" | cfg-get-value data-dirs 2>/dev/null | shyaml get-values 2>/dev/null) || true
  392. if [ -z "$data_dirs" ]; then
  393. return 0
  394. fi
  395. domain=$(get_domain "$cfg") || return 1
  396. local_path="/var/www/${domain}"
  397. dst=$DATASTORE/$BASE_SERVICE_NAME$local_path
  398. data=()
  399. while IFS="," read -ra addr; do
  400. for dir in "${addr[@]}"; do
  401. data+=("$dir")
  402. done
  403. done <<< "$data_dirs"
  404. www_data_gid=$(cached_cmd_on_base_image "$TARGET_SERVICE_NAME" 'id -g www-data') || {
  405. debug "Failed to query for www-data gid in ${DARKYELLOW}$TARGET_SERVICE_NAME${NORMAL} base image."
  406. return 1
  407. }
  408. info "www-data gid from ${DARKYELLOW}$TARGET_SERVICE_NAME${NORMAL} is '$www_data_gid'"
  409. to_create=()
  410. volumes=""
  411. for d in "${data[@]}"; do
  412. fdir="$dst/$d"
  413. volumes+=" - $HOST_DATASTORE${dst##$DATASTORE}/$d:$local_path/$d"$'\n'
  414. if ! [ -d "$fdir" ]; then
  415. to_create+=("$fdir")
  416. fi
  417. done
  418. if [ "${#to_create[@]}" -gt 0 ]; then
  419. mkdir -p "${to_create[@]}" || return 1
  420. chgrp -v "${www_data_gid}" "${to_create[@]}" || return 1
  421. chmod g+rwx "${to_create[@]}" || return 1
  422. fi
  423. init-config-add "
  424. $SERVICE_NAME:
  425. volumes:
  426. $volumes"
  427. }
  428. deploy_files() {
  429. local src="$1" dst="$2"
  430. if ! [ -d "$dst" ]; then
  431. err "Destination '$dst' does not exist or is not a directory"
  432. return 1
  433. fi
  434. (
  435. cd "$dst" && info "In $dst:" &&
  436. get_file "$src" | tar xv
  437. )
  438. }
  439. export -f deploy_files
  440. apache_core_rules_add() {
  441. local conf="$1" dst="/etc/apache2/conf-enabled/$SERVICE_NAME.conf"
  442. debug "Adding core rule."
  443. echo "$conf" | file_put "$CONFIGSTORE/$SERVICE_NAME$dst"
  444. config_hash=$(printf "%s\0" "$config_hash" "$conf" | md5_compat)
  445. init-config-add "
  446. $SERVICE_NAME:
  447. volumes:
  448. - $CONFIGSTORE/$SERVICE_NAME$dst:$dst:ro
  449. "
  450. }
  451. __vhost_ssl_statement() {
  452. ## defaults
  453. __vhost_cfg_SSL_CERT_LOCATION=${__vhost_cfg_SSL_CERT_LOCATION:-/etc/ssl/certs/ssl-cert-snakeoil.pem}
  454. __vhost_cfg_SSL_KEY_LOCATION=${__vhost_cfg_SSL_KEY_LOCATION:-/etc/ssl/private/ssl-cert-snakeoil.key}
  455. cat <<EOF
  456. ##
  457. ## SSL Configuration
  458. ##
  459. SSLEngine On
  460. SSLCertificateFile $__vhost_cfg_SSL_CERT_LOCATION
  461. SSLCertificateKeyFile $__vhost_cfg_SSL_KEY_LOCATION
  462. $([ -z "$__vhost_cfg_SSL_CA_CERT_LOCATION" ] || echo "SSLCACertificateFile $__vhost_cfg_SSL_CA_CERT_LOCATION")
  463. $([ -z "$__vhost_cfg_SSL_CHAIN" ] || echo "SSLCertificateChainFile $__vhost_cfg_SSL_CHAIN")
  464. SSLVerifyClient None
  465. EOF
  466. }
  467. password-path-get() {
  468. local dest="$1"
  469. echo "/etc/apache2/sites-enabled/${dest}.passwd"
  470. }
  471. __vhost_creds_statement() {
  472. local cfg="$1" dest="$2" password_path
  473. password_path=$(password-path-get "$dest") || return 1
  474. if ! e "$cfg" | cfg-get-value creds >/dev/null 2>&1; then
  475. echo "Allow from all"
  476. return 0
  477. fi
  478. cat <<EOF
  479. AuthType basic
  480. AuthName "private"
  481. AuthUserFile ${password_path}
  482. Require valid-user
  483. EOF
  484. }
  485. __vhost_head_statement() {
  486. local cfg="$1" protocol="$2" domain="$3" server_aliases admin_mail prefix
  487. admin_mail=$(e "$1" | cfg-get-value "admin-mail" 2>/dev/null) || true
  488. if [ -z "$admin_mail" ]; then
  489. if [ -z "$domain" ]; then
  490. admin_mail=webmaster@localhost
  491. else
  492. admin_mail=${admin_mail:-contact@$domain}
  493. fi
  494. fi
  495. server_aliases=$(e "$cfg" | cfg-get-value server-aliases 2>/dev/null) || true
  496. [ "$server_aliases" == None ] && server_aliases=""
  497. if [ "$server_aliases" ]; then
  498. server_aliases=($(e "$server_aliases" | shyaml get-values)) || return 1
  499. if [ -z "$domain" ]; then
  500. err "You can't specify server aliases if you don't have a domain."
  501. return 1
  502. fi
  503. else
  504. server_aliases=()
  505. fi
  506. if [ "$protocol" == "https" ]; then
  507. prefix="s-"
  508. else
  509. prefix=
  510. fi
  511. if [ "$domain" ]; then
  512. log_prefix="${prefix}${domain}_"
  513. else
  514. log_prefix=""
  515. fi
  516. cat <<EOF
  517. $(
  518. echo "ServerAdmin ${admin_mail}"
  519. [ "$domain" ] && echo "ServerName ${domain}"
  520. for alias in "${server_aliases[@]}"; do
  521. [ "$alias" ] || continue
  522. echo "ServerAlias $alias"
  523. done
  524. )
  525. ServerSignature Off
  526. CustomLog /var/log/apache2/${log_prefix}access.log combined
  527. ErrorLog /var/log/apache2/${log_prefix}error.log
  528. ErrorLog syslog:local2
  529. EOF
  530. }
  531. _get_custom_rules() {
  532. local cfg="$1" custom_rules
  533. custom_rules=$(e "$cfg" | shyaml -y -q get-value apache-custom-rules 2>/dev/null) || true
  534. e "$custom_rules" | yaml_get_values
  535. }
  536. __vhost_custom_rules() {
  537. local cfg="$1" custom_rules
  538. custom_rules=$(_get_custom_rules "$cfg") || return 1
  539. if [ "$custom_rules" ]; then
  540. cat <<EOF
  541. ##
  542. ## Custom rules
  543. ##
  544. $custom_rules
  545. EOF
  546. fi
  547. }
  548. __vhost_content_statement() {
  549. local type="$1"
  550. shift
  551. case "$type" in
  552. "web_proxy")
  553. __vhost_proxy_statement "$@" || return 1
  554. ;;
  555. "publish_dir")
  556. __vhost_publish_dir_statement "$@" || return 1
  557. ;;
  558. "ssh_tunnel")
  559. __vhost_tunnel_ssh_statement "$@" || return 1
  560. ;;
  561. esac
  562. }
  563. target-get() {
  564. local cfg="$1" target first_exposed_port base_image
  565. target=$(e "$cfg" | cfg-get-value target 2>/dev/null) || true
  566. if [ -z "$target" ]; then
  567. ## First exposed port:
  568. base_image=$(service_base_docker_image "$BASE_SERVICE_NAME") || return 1
  569. if ! docker_has_image "$base_image"; then
  570. docker pull "$base_image" >&2
  571. fi
  572. first_exposed_port=$(image_exposed_ports_0 "$base_image" | tr '\0' '\n' | head -n 1 | cut -f 1 -d /) || return 1
  573. if [ -z "$first_exposed_port" ]; then
  574. err "Failed to get first exposed port of image '$base_image'."
  575. return 1
  576. fi
  577. target=$MASTER_BASE_SERVICE_NAME:$first_exposed_port
  578. info "No target was specified, introspection found: $target"
  579. fi
  580. echo "$target"
  581. }
  582. __vhost_proxy_statement() {
  583. local protocol="$1" cfg="$2" dest="$3" proxy_pass_options target
  584. target=$(target-get "$cfg") || return 1
  585. proxy_pass_options=($(e "$cfg" | shyaml -y -q get-value apache-proxy-pass-options | yaml_get_values))
  586. if [ "${#proxy_pass_options[@]}" == 0 ]; then
  587. proxy_pass_options=(${proxy_pass_options:-"retry=0"})
  588. fi
  589. cat <<EOF
  590. ##
  591. ## Proxy declaration towards $target
  592. ##
  593. <IfModule mod_proxy.c>
  594. ProxyRequests Off
  595. <Proxy *>
  596. Order deny,allow
  597. Allow from all
  598. </Proxy>
  599. ProxyVia On
  600. ProxyPass / http://$target/ ${proxy_pass_options[*]}
  601. <Location / >
  602. $(__vhost_creds_statement "$cfg" "$dest" | prefix " ")
  603. ProxyPassReverse http://$target/
  604. </Location>
  605. $([ "$protocol" == "https" ] && echo " SSLProxyEngine On")
  606. </IfModule>
  607. SetEnvIf X-Forwarded-Proto "^$" forwarded_proto_not_set=true
  608. RequestHeader set "X-Forwarded-Proto" "$protocol" env=forwarded_proto_not_set
  609. ## Fix IE problem (httpapache proxy dav error 408/409)
  610. SetEnv proxy-nokeepalive 1
  611. EOF
  612. }
  613. __vhost_full_vhost_statement() {
  614. local type="$1" protocol="$2" cfg="$3" domain="$4" head_statement custom_rules content_statement
  615. head_statement=$(__vhost_head_statement "$cfg" "$protocol" "$domain") || return 1
  616. custom_rules=$(__vhost_custom_rules "$cfg") || return 1
  617. content_statement=$(__vhost_content_statement "$type" "$protocol" "$cfg" "${domain:-html}") || return 1
  618. case "$protocol" in
  619. https)
  620. PORT=443
  621. ;;
  622. http)
  623. PORT=80
  624. ;;
  625. esac
  626. cat <<EOF
  627. <VirtualHost *:$PORT>
  628. $(
  629. {
  630. echo "$head_statement"
  631. [ "$custom_rules" ] && echo "$custom_rules"
  632. echo "$content_statement"
  633. } | prefix " ")
  634. ## Forbid any cache, this is only usefull on dev server.
  635. #Header set Cache-Control "no-cache"
  636. #Header set Access-Control-Allow-Origin "*"
  637. #Header set Access-Control-Allow-Methods "POST, GET, OPTIONS"
  638. #Header set Access-Control-Allow-Headers "origin, content-type, accept"
  639. $([ "$protocol" == "https" ] && __vhost_ssl_statement | prefix " " && echo )
  640. </VirtualHost>
  641. EOF
  642. }
  643. __vhost_publish_dir_statement() {
  644. local protocol="$1" cfg="$2" dest="$3" location
  645. local_path="/var/www/${dest}"
  646. cat <<EOF
  647. ##
  648. ## Publish directory $local_path
  649. ##
  650. DocumentRoot $local_path
  651. <Directory />
  652. Options FollowSymLinks
  653. AllowOverride None
  654. </Directory>
  655. <Directory $local_path>
  656. Options Indexes FollowSymLinks MultiViews
  657. AllowOverride all
  658. $(__vhost_creds_statement "$cfg" "$dest" | prefix " ")
  659. </Directory>
  660. EOF
  661. }
  662. __vhost_tunnel_ssh_statement() {
  663. local protocol="$1" cfg="$2" dest="$3" custom_rules content_statement
  664. cat <<EOF
  665. ##
  666. ## SSH Tunnel
  667. ##
  668. #HostnameLookups On
  669. ProxyRequests On
  670. AllowConnect 22
  671. #ProxyVia on
  672. ### Deny everything by default
  673. <Proxy *>
  674. Order deny,allow
  675. Deny from all
  676. </proxy>
  677. ### Accept redirect only to same domain
  678. <Proxy $domain>
  679. Order deny,allow
  680. $(__vhost_creds_statement "$cfg" "$dest" | prefix " ")
  681. </Proxy>
  682. EOF
  683. }
  684. apache_config_hash() {
  685. debug "Adding config hash to enable recreating upon config change."
  686. config_hash=$({
  687. printf "%s\0" "$config_hash"
  688. find "$SERVICE_CONFIGSTORE/etc/apache2/sites-enabled" \
  689. -name \*.conf -exec md5sum {} \;
  690. } | md5_compat) || exit 1
  691. init-config-add "
  692. $SERVICE_NAME:
  693. labels:
  694. - compose.config_hash=$config_hash
  695. "
  696. }