fork 0k-charms
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.

843 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. esac
  379. mkdir -p "$host_path" || return 1
  380. init-config-add "
  381. $SERVICE_NAME:
  382. volumes:
  383. - \"${docker_host_path}:$local_path\"
  384. "
  385. }
  386. apache_data_dirs() {
  387. local cfg="$1" data_dirs dst data fdir to_create
  388. data_dirs=$(e "$cfg" | cfg-get-value data-dirs 2>/dev/null | shyaml get-values 2>/dev/null) || true
  389. if [ -z "$data_dirs" ]; then
  390. return 0
  391. fi
  392. domain=$(get_domain "$cfg") || return 1
  393. local_path="/var/www/${domain}"
  394. dst=$DATASTORE/$BASE_SERVICE_NAME$local_path
  395. data=()
  396. while IFS="," read -ra addr; do
  397. for dir in "${addr[@]}"; do
  398. data+=("$dir")
  399. done
  400. done <<< "$data_dirs"
  401. www_data_gid=$(cached_cmd_on_base_image "$TARGET_SERVICE_NAME" 'id -g www-data') || {
  402. debug "Failed to query for www-data gid in ${DARKYELLOW}$TARGET_SERVICE_NAME${NORMAL} base image."
  403. return 1
  404. }
  405. info "www-data gid from ${DARKYELLOW}$TARGET_SERVICE_NAME${NORMAL} is '$www_data_gid'"
  406. to_create=()
  407. volumes=""
  408. for d in "${data[@]}"; do
  409. fdir="$dst/$d"
  410. volumes+=" - $HOST_DATASTORE${dst##$DATASTORE}/$d:$local_path/$d"$'\n'
  411. if ! [ -d "$fdir" ]; then
  412. to_create+=("$fdir")
  413. fi
  414. done
  415. if [ "${#to_create[@]}" -gt 0 ]; then
  416. mkdir -p "${to_create[@]}" || return 1
  417. chgrp -v "${www_data_gid}" "${to_create[@]}" || return 1
  418. chmod g+rwx "${to_create[@]}" || return 1
  419. fi
  420. init-config-add "
  421. $SERVICE_NAME:
  422. volumes:
  423. $volumes"
  424. }
  425. deploy_files() {
  426. local src="$1" dst="$2"
  427. if ! [ -d "$dst" ]; then
  428. err "Destination '$dst' does not exist or is not a directory"
  429. return 1
  430. fi
  431. (
  432. cd "$dst" && info "In $dst:" &&
  433. get_file "$src" | tar xv
  434. )
  435. }
  436. export -f deploy_files
  437. apache_core_rules_add() {
  438. local conf="$1" dst="/etc/apache2/conf-enabled/$SERVICE_NAME.conf"
  439. debug "Adding core rule."
  440. echo "$conf" | file_put "$CONFIGSTORE/$SERVICE_NAME$dst"
  441. config_hash=$(printf "%s\0" "$config_hash" "$conf" | md5_compat)
  442. init-config-add "
  443. $SERVICE_NAME:
  444. volumes:
  445. - $CONFIGSTORE/$SERVICE_NAME$dst:$dst:ro
  446. "
  447. }
  448. __vhost_ssl_statement() {
  449. ## defaults
  450. __vhost_cfg_SSL_CERT_LOCATION=${__vhost_cfg_SSL_CERT_LOCATION:-/etc/ssl/certs/ssl-cert-snakeoil.pem}
  451. __vhost_cfg_SSL_KEY_LOCATION=${__vhost_cfg_SSL_KEY_LOCATION:-/etc/ssl/private/ssl-cert-snakeoil.key}
  452. cat <<EOF
  453. ##
  454. ## SSL Configuration
  455. ##
  456. SSLEngine On
  457. SSLCertificateFile $__vhost_cfg_SSL_CERT_LOCATION
  458. SSLCertificateKeyFile $__vhost_cfg_SSL_KEY_LOCATION
  459. $([ -z "$__vhost_cfg_SSL_CA_CERT_LOCATION" ] || echo "SSLCACertificateFile $__vhost_cfg_SSL_CA_CERT_LOCATION")
  460. $([ -z "$__vhost_cfg_SSL_CHAIN" ] || echo "SSLCertificateChainFile $__vhost_cfg_SSL_CHAIN")
  461. SSLVerifyClient None
  462. EOF
  463. }
  464. password-path-get() {
  465. local dest="$1"
  466. echo "/etc/apache2/sites-enabled/${dest}.passwd"
  467. }
  468. __vhost_creds_statement() {
  469. local cfg="$1" dest="$2" password_path
  470. password_path=$(password-path-get "$dest") || return 1
  471. if ! e "$cfg" | cfg-get-value creds >/dev/null 2>&1; then
  472. echo "Allow from all"
  473. return 0
  474. fi
  475. cat <<EOF
  476. AuthType basic
  477. AuthName "private"
  478. AuthUserFile ${password_path}
  479. Require valid-user
  480. EOF
  481. }
  482. __vhost_head_statement() {
  483. local cfg="$1" protocol="$2" domain="$3" server_aliases admin_mail prefix
  484. admin_mail=$(e "$1" | cfg-get-value "admin-mail" 2>/dev/null) || true
  485. if [ -z "$admin_mail" ]; then
  486. if [ -z "$domain" ]; then
  487. admin_mail=webmaster@localhost
  488. else
  489. admin_mail=${admin_mail:-contact@$domain}
  490. fi
  491. fi
  492. server_aliases=$(e "$cfg" | cfg-get-value server-aliases 2>/dev/null) || true
  493. [ "$server_aliases" == None ] && server_aliases=""
  494. if [ "$server_aliases" ]; then
  495. server_aliases=($(e "$server_aliases" | shyaml get-values)) || return 1
  496. if [ -z "$domain" ]; then
  497. err "You can't specify server aliases if you don't have a domain."
  498. return 1
  499. fi
  500. else
  501. server_aliases=()
  502. fi
  503. if [ "$protocol" == "https" ]; then
  504. prefix="s-"
  505. else
  506. prefix=
  507. fi
  508. if [ "$domain" ]; then
  509. log_prefix="${prefix}${domain}_"
  510. else
  511. log_prefix=""
  512. fi
  513. cat <<EOF
  514. $(
  515. echo "ServerAdmin ${admin_mail}"
  516. [ "$domain" ] && echo "ServerName ${domain}"
  517. for alias in "${server_aliases[@]}"; do
  518. [ "$alias" ] || continue
  519. echo "ServerAlias $alias"
  520. done
  521. )
  522. ServerSignature Off
  523. CustomLog /var/log/apache2/${log_prefix}access.log combined
  524. ErrorLog /var/log/apache2/${log_prefix}error.log
  525. ErrorLog syslog:local2
  526. EOF
  527. }
  528. _get_custom_rules() {
  529. local cfg="$1" custom_rules
  530. custom_rules=$(e "$cfg" | shyaml -y -q get-value apache-custom-rules 2>/dev/null) || true
  531. e "$custom_rules" | yaml_get_values
  532. }
  533. __vhost_custom_rules() {
  534. local cfg="$1" custom_rules
  535. custom_rules=$(_get_custom_rules "$cfg") || return 1
  536. if [ "$custom_rules" ]; then
  537. cat <<EOF
  538. ##
  539. ## Custom rules
  540. ##
  541. $custom_rules
  542. EOF
  543. fi
  544. }
  545. __vhost_content_statement() {
  546. local type="$1"
  547. shift
  548. case "$type" in
  549. "web_proxy")
  550. __vhost_proxy_statement "$@" || return 1
  551. ;;
  552. "publish_dir")
  553. __vhost_publish_dir_statement "$@" || return 1
  554. ;;
  555. "ssh_tunnel")
  556. __vhost_tunnel_ssh_statement "$@" || return 1
  557. ;;
  558. esac
  559. }
  560. target-get() {
  561. local cfg="$1" target first_exposed_port base_image
  562. target=$(e "$cfg" | cfg-get-value target 2>/dev/null) || true
  563. if [ -z "$target" ]; then
  564. ## First exposed port:
  565. base_image=$(service_base_docker_image "$BASE_SERVICE_NAME") || return 1
  566. if ! docker_has_image "$base_image"; then
  567. docker pull "$base_image" >&2
  568. fi
  569. first_exposed_port=$(image_exposed_ports_0 "$base_image" | tr '\0' '\n' | head -n 1 | cut -f 1 -d /) || return 1
  570. if [ -z "$first_exposed_port" ]; then
  571. err "Failed to get first exposed port of image '$base_image'."
  572. return 1
  573. fi
  574. target=$MASTER_BASE_SERVICE_NAME:$first_exposed_port
  575. info "No target was specified, introspection found: $target"
  576. fi
  577. echo "$target"
  578. }
  579. __vhost_proxy_statement() {
  580. local protocol="$1" cfg="$2" dest="$3" proxy_pass_options target
  581. target=$(target-get "$cfg") || return 1
  582. proxy_pass_options=($(e "$cfg" | shyaml -y -q get-value apache-proxy-pass-options | yaml_get_values))
  583. if [ "${#proxy_pass_options[@]}" == 0 ]; then
  584. proxy_pass_options=(${proxy_pass_options:-"retry=0"})
  585. fi
  586. cat <<EOF
  587. ##
  588. ## Proxy declaration towards $target
  589. ##
  590. <IfModule mod_proxy.c>
  591. ProxyRequests Off
  592. <Proxy *>
  593. Order deny,allow
  594. Allow from all
  595. </Proxy>
  596. ProxyVia On
  597. ProxyPass / http://$target/ ${proxy_pass_options[*]}
  598. <Location / >
  599. $(__vhost_creds_statement "$cfg" "$dest" | prefix " ")
  600. ProxyPassReverse http://$target/
  601. </Location>
  602. $([ "$protocol" == "https" ] && echo " SSLProxyEngine On")
  603. </IfModule>
  604. SetEnvIf X-Forwarded-Proto "^$" forwarded_proto_not_set=true
  605. RequestHeader set "X-Forwarded-Proto" "$protocol" env=forwarded_proto_not_set
  606. ## Fix IE problem (httpapache proxy dav error 408/409)
  607. SetEnv proxy-nokeepalive 1
  608. EOF
  609. }
  610. __vhost_full_vhost_statement() {
  611. local type="$1" protocol="$2" cfg="$3" domain="$4" head_statement custom_rules content_statement
  612. head_statement=$(__vhost_head_statement "$cfg" "$protocol" "$domain") || return 1
  613. custom_rules=$(__vhost_custom_rules "$cfg") || return 1
  614. content_statement=$(__vhost_content_statement "$type" "$protocol" "$cfg" "${domain:-html}") || return 1
  615. case "$protocol" in
  616. https)
  617. PORT=443
  618. ;;
  619. http)
  620. PORT=80
  621. ;;
  622. esac
  623. cat <<EOF
  624. <VirtualHost *:$PORT>
  625. $(
  626. {
  627. echo "$head_statement"
  628. [ "$custom_rules" ] && echo "$custom_rules"
  629. echo "$content_statement"
  630. } | prefix " ")
  631. ## Forbid any cache, this is only usefull on dev server.
  632. #Header set Cache-Control "no-cache"
  633. #Header set Access-Control-Allow-Origin "*"
  634. #Header set Access-Control-Allow-Methods "POST, GET, OPTIONS"
  635. #Header set Access-Control-Allow-Headers "origin, content-type, accept"
  636. $([ "$protocol" == "https" ] && __vhost_ssl_statement | prefix " " && echo )
  637. </VirtualHost>
  638. EOF
  639. }
  640. __vhost_publish_dir_statement() {
  641. local protocol="$1" cfg="$2" dest="$3" location
  642. local_path="/var/www/${dest}"
  643. cat <<EOF
  644. ##
  645. ## Publish directory $local_path
  646. ##
  647. DocumentRoot $local_path
  648. <Directory />
  649. Options FollowSymLinks
  650. AllowOverride None
  651. </Directory>
  652. <Directory $local_path>
  653. Options Indexes FollowSymLinks MultiViews
  654. AllowOverride all
  655. $(__vhost_creds_statement "$cfg" "$dest" | prefix " ")
  656. </Directory>
  657. EOF
  658. }
  659. __vhost_tunnel_ssh_statement() {
  660. local protocol="$1" cfg="$2" dest="$3" custom_rules content_statement
  661. cat <<EOF
  662. ##
  663. ## SSH Tunnel
  664. ##
  665. #HostnameLookups On
  666. ProxyRequests On
  667. AllowConnect 22
  668. #ProxyVia on
  669. ### Deny everything by default
  670. <Proxy *>
  671. Order deny,allow
  672. Deny from all
  673. </proxy>
  674. ### Accept redirect only to same domain
  675. <Proxy $domain>
  676. Order deny,allow
  677. $(__vhost_creds_statement "$cfg" "$dest" | prefix " ")
  678. </Proxy>
  679. EOF
  680. }
  681. apache_config_hash() {
  682. debug "Adding config hash to enable recreating upon config change."
  683. config_hash=$({
  684. printf "%s\0" "$config_hash"
  685. find "$SERVICE_CONFIGSTORE/etc/apache2/sites-enabled" \
  686. -name \*.conf -exec md5sum {} \;
  687. } | md5_compat) || exit 1
  688. init-config-add "
  689. $SERVICE_NAME:
  690. labels:
  691. - compose.config_hash=$config_hash
  692. "
  693. }