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.

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