setup_functions.sh 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. # All functions used during setup of a service
  2. docker_build() {
  3. if [[ -z $1 ]];then
  4. for key in ${!BUILD_IMAGES[@]};do
  5. if [[ -f "${BUILD_IMAGES[$key]}/Dockerfile" ]];then
  6. if [[ $(docker images | grep "\<${key}\>") ]];then
  7. echo ""
  8. prompt_confirm "Existing image found for ${key}. Rebuild image?" || return
  9. fi
  10. echo -e "\n\e[1mBuilding image ${1}\e[0m"
  11. docker build -t $key "${BUILD_IMAGES[$key]}"
  12. fi
  13. done
  14. else
  15. if [[ -f "${BUILD_IMAGES[$1]}/Dockerfile" ]];then
  16. if [[ $(docker images | grep "\<${1}\>") ]];then
  17. prompt_confirm "Existing image found for ${1}. Rebuild image?" || return
  18. fi
  19. echo -e "\n\e[1mBuilding image ${1}\e[0m"
  20. docker build -t $1 "${BUILD_IMAGES[$1]}"
  21. fi
  22. fi
  23. }
  24. docker_pull() {
  25. for image in ${IMAGES[@]};do
  26. [[ "$image" != "dockerbunker/${SERVICE_NAME}" ]] \
  27. && echo -e "\n\e[1mPulling $image\e[0m" \
  28. && docker pull $image
  29. done
  30. }
  31. docker_run() {
  32. $1
  33. }
  34. docker_run_all() {
  35. echo -e "\n\e[1mStarting up containers\e[0m"
  36. for container in "${containers[@]}";do
  37. ! [[ $(docker ps -q --filter name="^/${container}$") ]] \
  38. && echo -en "- $container" \
  39. && ${container//-/_} \
  40. && exit_response \
  41. || echo "- $container (already running)"
  42. done
  43. if elementInArray "${PROPER_NAME}" "${STOPPED_SERVICES[@]}";then
  44. remove_from_STOPPED_SERVICES
  45. fi
  46. connect_containers_to_network
  47. activate_nginx_conf
  48. restart_nginx
  49. }
  50. get_current_images_sha256() {
  51. # get current images' sha256
  52. if [[ -z ${CURRENT_IMAGES_SHA256[@]} ]];then
  53. collectImageNamesAndCorrespondingSha256
  54. declare -A CURRENT_IMAGES_SHA256
  55. for key in "${!IMAGES_AND_SHA256[@]}";do
  56. CURRENT_IMAGES_SHA256[$key]+=${IMAGES_AND_SHA256[$key]}
  57. done
  58. fi
  59. declare -p CURRENT_IMAGES_SHA256 >> "${BASE_DIR}"/.image_shas.tmp
  60. unset IMAGES_AND_SHA256
  61. }
  62. pull_and_compare() {
  63. [[ -f "${BASE_DIR}"/.image_shas.tmp ]] \
  64. && rm "${BASE_DIR}"/.image_shas.tmp
  65. get_current_images_sha256
  66. if [[ ${DOCKER_COMPOSE} ]];then
  67. pushd "${SERVICE_HOME}" >/dev/null
  68. echo ""
  69. echo -e "\e[1mPulling new images\e[0m"
  70. echo ""
  71. docker-compose pull
  72. else
  73. docker_build
  74. docker_pull
  75. fi
  76. if [[ -f "${BASE_DIR}"/.image_shas.tmp ]];then
  77. source "${BASE_DIR}"/.image_shas.tmp
  78. else
  79. echo -e "\n\e[31mCould not find digests of current images.\nExiting.\e[0m"
  80. exit 1
  81. fi
  82. # compare sha256 and delete old unused images
  83. collectImageNamesAndCorrespondingSha256
  84. declare -A NEW_IMAGES_SHA256
  85. for key in "${!IMAGES_AND_SHA256[@]}";do
  86. NEW_IMAGES_SHA256[$key]+=${IMAGES_AND_SHA256[$key]}
  87. done
  88. for key in "${!CURRENT_IMAGES_SHA256[@]}";do
  89. if [[ ${CURRENT_IMAGES_SHA256[$key]} != ${NEW_IMAGES_SHA256[$key]} ]];then
  90. old_images_to_delete+=( ${CURRENT_IMAGES_SHA256[$key]} )
  91. else
  92. unchanged_images_to_keep+=( ${CURRENT_IMAGES_SHA256[$key]} )
  93. fi
  94. done
  95. if [[ ${DOCKER_COMPOSE} ]] \
  96. && [[ ${old_images_to_delete[0]} ]];then
  97. pushd "${SERVICE_HOME}" >/dev/null
  98. echo -e "\n\e[1mTaking down ${PROPER_NAME}\e[0m"
  99. docker-compose down
  100. echo -e "\n\e[1mBringing ${PROPER_NAME} back up\e[0m"
  101. docker-compose up -d
  102. connect_containers_to_network
  103. popd >/dev/null
  104. fi
  105. [[ ${old_images_to_delete[0]} ]] \
  106. && declare -p old_images_to_delete >> "${BASE_DIR}"/.image_shas.tmp
  107. [[ ${unchanged_images_to_keep[0]} ]] \
  108. && declare -p unchanged_images_to_keep >> "${BASE_DIR}"/.image_shas.tmp
  109. }
  110. delete_old_images() {
  111. if [[ -f "${BASE_DIR}"/.image_shas.tmp ]];then
  112. source "${BASE_DIR}"/.image_shas.tmp
  113. else
  114. echo -en "\n\e[31mCould not find digests of current images.\nExiting.\e[0m"
  115. return
  116. fi
  117. [[ -z ${old_images_to_delete[0]} ]] \
  118. && echo -e "\n\e[1mImages did not change.\e[0m" \
  119. && rm "${BASE_DIR}"/.image_shas.tmp \
  120. && return
  121. prompt_confirm "Delete all old images?"
  122. if [[ $? == 0 ]];then
  123. echo ""
  124. for image in "${old_images_to_delete[@]}";do
  125. echo -en "\e[1m[DELETING]\e[0m $image"
  126. docker rmi $image >/dev/null
  127. exit_response
  128. done
  129. for image in ${unchanged_images_to_keep[@]};do
  130. echo -en "\e[1m[KEEPING]\e[0m $image (did not change)"
  131. done
  132. echo ""
  133. fi
  134. rm "${BASE_DIR}"/.image_shas.tmp
  135. }
  136. setup_nginx() {
  137. [[ ! $(docker ps -q --filter name=^/${NGINX_CONTAINER}$) ]] && bash "${SERVICES_DIR}"/nginx/nginx.sh setup
  138. }
  139. # Build image if necessary, set up nginx container if necessary, create or use existing volumes, create networks if necessary, pull images if necessary
  140. initial_setup_routine() {
  141. [[ ${STATIC} ]] && return
  142. setup_nginx
  143. for container in "${containers[@]}";do
  144. [[ ( $(docker inspect $container 2> /dev/null) && $? == 0 ) ]] && docker rm $container
  145. done
  146. docker_build
  147. docker_pull
  148. if [[ ${volumes[0]} ]];then
  149. echo -e "\n\e[1mCreating volumes\e[0m"
  150. for volume in "${volumes[@]}";do
  151. [[ ! $(docker volume ls -q --filter name=^${volume}$) ]] \
  152. && echo -en "- $volume" \
  153. && docker volume create $volume >/dev/null \
  154. && exit_response \
  155. || echo "- $volume (already exists)"
  156. done
  157. fi
  158. create_networks
  159. }
  160. create_networks() {
  161. for network in "${networks[@]}";do
  162. [[ $(docker network ls -q --filter name=^${network}$) ]] \
  163. && docker network rm $network >/dev/null
  164. [[ ! $(docker network ls -q --filter name=^${network}$) ]] \
  165. && docker network create $network >/dev/null
  166. done
  167. }
  168. generate_certificate() {
  169. [[ -L "${CONF_DIR}"/nginx/ssl/${SERVICE_DOMAIN[0]}/cert.pem ]] && rm "${CONF_DIR}"/nginx/ssl/${SERVICE_DOMAIN[0]}/cert.pem
  170. [[ -L "${CONF_DIR}"/nginx/ssl/${SERVICE_DOMAIN[0]}/key.pem ]] && rm "${CONF_DIR}"/nginx/ssl/${SERVICE_DOMAIN[0]}/key.pem
  171. echo -en "\n\e[1mGenerating self-signed certificate for ${SERVICE_DOMAIN[0]}\e[0m"
  172. openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout "${CONF_DIR}"/nginx/ssl/${SERVICE_DOMAIN[0]}/key.pem -out "${CONF_DIR}"/nginx/ssl/${SERVICE_DOMAIN[0]}/cert.pem -subj "/C=XY/ST=hidden/L=nowhere/O=${PROPER_NAME}/OU=IT Department/CN=${SERVICE_DOMAIN[0]}" >/dev/null 2>&1
  173. exit_response
  174. }
  175. # this generates the nginx configuration for the service that is being set up and puts it into data/services/ngix/conf.d
  176. basic_nginx() {
  177. if [[ -z $reinstall ]];then
  178. [[ ! -d "${CONF_DIR}"/nginx/ssl/${SERVICE_DOMAIN[0]} ]] && \
  179. mkdir -p "${CONF_DIR}"/nginx/ssl/${SERVICE_DOMAIN[0]}
  180. if [[ ! -f "${CONF_DIR}"/nginx/ssl/${SERVICE_DOMAIN[0]}/cert.pem ]];then
  181. generate_certificate
  182. fi
  183. [[ ! -d "${CONF_DIR}"/nginx/conf.d ]] && \
  184. mkdir -p "${CONF_DIR}"/nginx/conf.d
  185. if [[ ! -f "${CONF_DIR}"/nginx/conf.d/${SERVICE_DOMAIN[0]}.conf ]];then
  186. cp "${SERVICES_DIR}"/${SERVICE_NAME}/nginx/${SERVICE_NAME}.conf "${SERVICES_DIR}"/${SERVICE_NAME}/nginx/${SERVICE_DOMAIN[0]}.conf
  187. for variable in "${SUBSTITUTE[@]}";do
  188. subst="\\${variable}"
  189. variable=`eval echo "$variable"`
  190. sed -i "s@${subst}@${variable}@g;" \
  191. "${SERVICES_DIR}"/${SERVICE_NAME}/nginx/${SERVICE_DOMAIN[0]}.conf
  192. done
  193. fi
  194. echo -en "\n\e[1mMoving nginx configuration in place\e[0m"
  195. if [[ -f "${SERVICES_DIR}"/${SERVICE_NAME}/nginx/${SERVICE_DOMAIN[0]}.conf ]];then
  196. mv "${SERVICES_DIR}"/${SERVICE_NAME}/nginx/${SERVICE_DOMAIN[0]}.conf "${CONF_DIR}"/nginx/conf.d
  197. # add basic_auth
  198. if [[ ${BASIC_AUTH} == "yes" ]];then
  199. [[ ! -d "${CONF_DIR}"/nginx/conf.d/${SERVICE_DOMAIN} ]] && \
  200. mkdir -p "${CONF_DIR}"/nginx/conf.d/${SERVICE_DOMAIN}
  201. SALT="$(openssl rand 3)"
  202. SHA1="$(printf "%s%s" "${HTPASSWD}" "$SALT" | openssl dgst -binary -sha1)"
  203. printf "${HTUSER}:{SSHA}%s\n" "$(printf "%s%s" "$SHA1" "$SALT" | base64)" > "${CONF_DIR}"/nginx/conf.d/${SERVICE_DOMAIN}/.htpasswd
  204. cp "${SERVICES_DIR}"/nginx/basic_auth.conf \
  205. "${CONF_DIR}"/nginx/conf.d/${SERVICE_DOMAIN}/basic_auth.conf
  206. for variable in "${SUBSTITUTE[@]}";do
  207. subst="\\${variable}"
  208. variable=`eval echo "$variable"`
  209. sed -i "s@${subst}@${variable}@g;" \
  210. "${CONF_DIR}"/nginx/conf.d/${SERVICE_DOMAIN}/basic_auth.conf
  211. done
  212. fi
  213. exit_response
  214. else
  215. ! [[ -f "${CONF_DIR}"/nginx/conf.d/${SERVICE_DOMAIN[0]}.conf ]] && echo "Nginx configuration file could not be found. Exiting." && exit 1
  216. fi
  217. fi
  218. }
  219. # called in docker_run_all if container is found in ${add_to_network}
  220. connect_containers_to_network() {
  221. [[ $1 ]] \
  222. && [[ $(docker ps -q --filter name=^/"${1}"$) ]] \
  223. && ! [[ $(docker network inspect dockerbunker-network | grep $1) ]] \
  224. && docker network connect ${NETWORK} ${1} >/dev/null \
  225. && return
  226. for container in ${add_to_network};do
  227. [[ $(docker ps -q --filter name=^/"${container}"$) ]] \
  228. && docker network connect ${NETWORK} ${container} >/dev/null
  229. done
  230. }
  231. wait_for_db() {
  232. if ! docker exec ${FUNCNAME[1]//_/-} mysqladmin ping -h"127.0.0.1" --silent;then
  233. while ! docker exec ${FUNCNAME[1]//_/-} mysqladmin ping -h"127.0.0.1" --silent;do
  234. sleep 1
  235. done
  236. fi
  237. }
  238. post_setup_routine() {
  239. if [[ $SSL_CHOICE == "le" ]] && [[ ! -d "${CONF_DIR}"/nginx/ssl/letsencrypt/${SERVICE_DOMAIN[0]} ]];then
  240. letsencrypt issue
  241. fi
  242. remove_from_CONFIGURED_SERVICES
  243. ! elementInArray "${PROPER_NAME}" "${INSTALLED_SERVICES[@]}" && INSTALLED_SERVICES+=( "${PROPER_NAME}" )
  244. for container in ${add_to_network[@]};do
  245. ! elementInArray "${container}" "${CONTAINERS_IN_DOCKERBUNKER_NETWORK[@]}" && CONTAINERS_IN_DOCKERBUNKER_NETWORK+=( "${container}" )
  246. done
  247. [[ -f "${ENV_DIR}/dockerbunker.env" ]] && ( sed -i '/CONFIGURED_SERVICES/d' "${ENV_DIR}/dockerbunker.env"; sed -i '/WEB_SERVICES/d' "${ENV_DIR}/dockerbunker.env"; sed -i '/CONTAINERS_IN_DOCKERBUNKER_NETWORK/d' "${ENV_DIR}/dockerbunker.env"; sed -i '/INSTALLED_SERVICES/d' "${ENV_DIR}/dockerbunker.env" )
  248. declare -p CONFIGURED_SERVICES >> "${ENV_DIR}/dockerbunker.env"
  249. declare -p INSTALLED_SERVICES >> "${ENV_DIR}/dockerbunker.env"
  250. declare -p WEB_SERVICES >> "${ENV_DIR}/dockerbunker.env"
  251. declare -p CONTAINERS_IN_DOCKERBUNKER_NETWORK >> "${ENV_DIR}/dockerbunker.env" 2>/dev/null
  252. }
  253. # This function issues a Let's Encrypt certificate if the choice has been made earlier and finally updates the arrays in dockerbunker.env so dockerbunker knows that the service now is installed
  254. letsencrypt() {
  255. echo ""
  256. add_domains() {
  257. prompt_confirm "Include other domains in certificate beside ${SERVICE_DOMAIN[*]}?"
  258. if [[ $? == 0 ]];then
  259. unset fqdn_is_valid
  260. unset domains
  261. while [[ -z $fqdn_is_valid ]];do
  262. if [[ $invalid ]];then
  263. echo -e "\nPlease enter a valid domain!\n"
  264. fi
  265. unset invalid
  266. read -p "Enter domains, separated by spaces: ${SERVICE_DOMAIN[*]} " -ei "" domains
  267. if [[ -z $domains ]];then
  268. domains=( ${SERVICE_DOMAIN[*]} )
  269. break
  270. else
  271. domains=( ${SERVICE_DOMAIN[*]} $domains )
  272. for i in "${domains[@]}";do
  273. # don't check if main domain is valid, otherwise it will complain that the domain already exists because an nginx configuration is already in place
  274. [[ $i != ${SERVICE_DOMAIN[0]} ]] && validate_fqdn $i
  275. done
  276. invalid=1
  277. fi
  278. invalid=1
  279. done
  280. if [[ ${STATIC} ]];then
  281. [[ ${domains[1]} ]] && sed -i "s/server_name.*/server_name ${domains[*]}\;/" "data/conf/nginx/conf.d/${SERVICE_DOMAIN[0]}.conf" && sed -i "s/^SERVICE\_DOMAIN.*/SERVICE\_DOMAIN\=\(\ ${domains[*]}\ \)/" "data/env/static/${SERVICE_DOMAIN[0]}.env"
  282. else
  283. [[ ${domains[1]} ]] && sed -i "s/server_name.*/server_name ${domains[*]}\;/" "data/conf/nginx/conf.d/${SERVICE_DOMAIN[0]}.conf" && sed -i "s/^SERVICE\_DOMAIN.*/SERVICE\_DOMAIN\=\(\ ${domains[*]}\ \)/" "data/env/${SERVICE_NAME}.env"
  284. fi
  285. expand="--expand "
  286. else
  287. domains=( ${SERVICE_DOMAIN[@]} )
  288. fi
  289. }
  290. issue() {
  291. [[ -z $1 ]] && ! [[ $(docker ps -q --filter name=^/${SERVICE_NAME}-service-dockerbunker$) ]] && echo "${PROPER_NAME} container not running. Exiting." && exit 1
  292. for value in $*;do
  293. [[ ( $value == "letsencrypt" || $value == "issue" || $value == "static" ) ]] || domains+=( "$value" )
  294. done
  295. for domain in ${domains[@]};do
  296. [[ ${domain} != ${SERVICE_DOMAIN[0]} ]] && validate_fqdn $domain || fqdn_is_valid=1
  297. if [[ $fqdn_is_valid ]];then
  298. [[ ! "${le_domains[@]}" =~ $domain ]] && le_domains+=( "-d $domain" )
  299. else
  300. exit
  301. fi
  302. done
  303. [[ ( "${domains[@]}" =~ ${SERVICE_DOMAIN[0]} && ! "${domains[0]}" =~ "${SERVICE_DOMAIN[0]}" ) ]] && ( echo "Please list ${SERVICE_DOMAIN[0]} first.";exit 1 )
  304. [[ "${domains[@]}" =~ ${SERVICE_DOMAIN[0]} ]] || ( echo -e "Please include your chosen ${PROPER_NAME} domain ${SERVICE_DOMAIN[0]}";exit 1 )
  305. [[ ! -d "${CONF_DIR}"/nginx/ssl/letsencrypt ]] && mkdir "${CONF_DIR}"/nginx/ssl/letsencrypt
  306. [[ ( "${domains[@]}" =~ "${SERVICE_DOMAIN[0]}" && "${domains[0]}" =~ "${SERVICE_DOMAIN[0]}" ) ]] \
  307. && echo "" \
  308. && docker run --rm -it --name=certbot \
  309. --network ${NETWORK} \
  310. -v "${CONF_DIR}"/nginx/ssl/letsencrypt:/etc/letsencrypt \
  311. -v "${BASE_DIR}"/data/web:/var/www/html:rw \
  312. certbot/certbot \
  313. certonly --noninteractive \
  314. --webroot -w /var/www/html \
  315. ${le_domains[@]} \
  316. --email ${LE_EMAIL} ${expand}\
  317. --agree-tos
  318. if [[ $? == 0 ]];then
  319. if ! [[ -L "data/conf/nginx/ssl/${SERVICE_DOMAIN[0]}/cert.pem" ]];then
  320. echo -en "\n\e[1mBacking up self-signed certificate\e[0m"
  321. mv "${CONF_DIR}"/nginx/ssl/${SERVICE_DOMAIN[0]}/cert.{pem,pem.backup} && \
  322. mv "${CONF_DIR}"/nginx/ssl/${SERVICE_DOMAIN[0]}/key.{pem,pem.backup} && exit_response || exit_response
  323. echo -en "\n\e[1mSymlinking letsencrypt certificate\e[0m"
  324. ln -sf "/etc/nginx/ssl/letsencrypt/live/${SERVICE_DOMAIN[0]}/fullchain.pem" "${CONF_DIR}"/nginx/ssl/${SERVICE_DOMAIN[0]}/cert.pem && \
  325. ln -sf "/etc/nginx/ssl/letsencrypt/live/${SERVICE_DOMAIN[0]}/privkey.pem" "${CONF_DIR}"/nginx/ssl/${SERVICE_DOMAIN[0]}/key.pem && exit_response || exit_response
  326. fi
  327. restart_nginx
  328. fi
  329. }
  330. renew() {
  331. docker run --rm -it --name=certbot \
  332. --network ${NETWORK} \
  333. -v "${CONF_DIR}"/nginx/ssl/letsencrypt:/etc/letsencrypt \
  334. -v "${BASE_DIR}"/data/web:/var/www/html:rw \
  335. certbot/certbot \
  336. renew
  337. restart_nginx
  338. }
  339. echo -e "\e[1mObtain certificate from Let's Encrypt\e[0m"
  340. if [[ ( "$1" == "issue" ) ]] || [[ ( "$1" == "letsencrypt" && "$2" == "issue" ) ]];then
  341. add_domains
  342. issue ${domains[@]}
  343. elif [[ ( "$1" == "letsencrypt" && "$2" == "renew" ) ]] || [[ "$1" == "renew" ]];then
  344. renew
  345. else
  346. echo "Usage: issue example.org www.example.org | renew"
  347. exit 0
  348. fi
  349. }