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.

978 lines
29 KiB

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