setup.sh 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. #! /bin/bash
  2. # version v1.0.0
  3. # executed manually / via Make
  4. # task wrapper for various setup scripts
  5. CONFIG_PATH=
  6. CONTAINER_NAME=
  7. CRI=
  8. DEFAULT_CONFIG_PATH=
  9. DESIRED_CONFIG_PATH=
  10. DIR="$(pwd)"
  11. DMS_CONFIG='/tmp/docker-mailserver'
  12. IMAGE_NAME=
  13. DEFAULT_IMAGE_NAME='docker.io/mailserver/docker-mailserver:latest'
  14. INFO=
  15. PODMAN_ROOTLESS=false
  16. USE_SELINUX=
  17. USE_TTY=
  18. VOLUME=
  19. RED="\e[31m\e[1m"
  20. WHITE="\e[37m"
  21. ORANGE="\e[38;5;214m"
  22. LBLUE="\e[94m"
  23. RESET="\e[0m"
  24. set -euEo pipefail
  25. shopt -s inherit_errexit
  26. trap '__err "${BASH_SOURCE}" "${FUNCNAME[0]:-?}" "${BASH_COMMAND:-?}" "${LINENO:-?}" "${?:-?}"' ERR
  27. function __err
  28. {
  29. [[ ${5} -gt 1 ]] && exit 1
  30. local ERR_MSG="\n--- ${RED}UNCHECKED ERROR${RESET}"
  31. ERR_MSG+="\n - script = ${1}"
  32. ERR_MSG+="\n - function = ${2}"
  33. ERR_MSG+="\n - command = ${3}"
  34. ERR_MSG+="\n - line = ${4}"
  35. ERR_MSG+="\n - exit code = ${5}"
  36. ERR_MSG+='\n\nThis should not have happened. Please file a bug report.\n'
  37. echo -e "${ERR_MSG}"
  38. }
  39. function _show_local_usage
  40. {
  41. # shellcheck disable=SC2059
  42. printf "${ORANGE}OPTIONS${RESET}
  43. ${LBLUE}Config path, container or image adjustments${RESET}
  44. -i IMAGE_NAME
  45. Provides the name of the 'docker-mailserver' image. The default value is
  46. '${WHITE}${DEFAULT_IMAGE_NAME}${RESET}'
  47. -c CONTAINER_NAME
  48. Provides the name of the running container.
  49. -p PATH
  50. Provides the local path of the config folder to the temporary container instance.
  51. Does not work if an existing a 'docker-mailserver' container is already running.
  52. ${LBLUE}SELinux${RESET}
  53. -z
  54. Allows container access to the bind mount content that is shared among
  55. multiple containers on a SELinux-enabled host.
  56. -Z
  57. Allows container access to the bind mount content that is private and
  58. unshared with other containers on a SELinux-enabled host.
  59. ${LBLUE}Podman${RESET}
  60. -R
  61. Accept running in Podman rootless mode. Ignored when using Docker / Docker Compose.
  62. "
  63. [[ ${1:-} == 'no-exit' ]] && return 0
  64. # shellcheck disable=SC2059
  65. printf "${ORANGE}EXIT STATUS${RESET}
  66. Exit status is 0 if the command was successful. If there was an unexpected error, an error
  67. message is shown describing the error. In case of an error, the script will exit with exit
  68. status 1.
  69. "
  70. }
  71. function _get_absolute_script_directory
  72. {
  73. if [[ "$(uname)" == 'Darwin' ]]
  74. then
  75. readlink() {
  76. # requires coreutils
  77. greadlink "${@:+$@}"
  78. }
  79. fi
  80. if dirname "$(readlink -f "${0}")" &>/dev/null
  81. then
  82. DIR="$(dirname "$(readlink -f "${0}")")"
  83. elif realpath -e -L "${0}" &>/dev/null
  84. then
  85. DIR="$(realpath -e -L "${0}")"
  86. DIR="${DIR%/setup.sh}"
  87. fi
  88. }
  89. function _set_default_config_path
  90. {
  91. if [[ -d "${DIR}/config" ]]
  92. then
  93. # legacy path (pre v10.2.0)
  94. DEFAULT_CONFIG_PATH="${DIR}/config"
  95. else
  96. DEFAULT_CONFIG_PATH="${DIR}/docker-data/dms/config"
  97. fi
  98. }
  99. function _handle_config_path
  100. {
  101. if [[ -z ${DESIRED_CONFIG_PATH} ]]
  102. then
  103. # no desired config path
  104. if [[ -n ${CONTAINER_NAME} ]]
  105. then
  106. VOLUME=$(${CRI} inspect "${CONTAINER_NAME}" \
  107. --format="{{range .Mounts}}{{ println .Source .Destination}}{{end}}" | \
  108. grep "${DMS_CONFIG}$" 2>/dev/null || :)
  109. fi
  110. if [[ -n ${VOLUME} ]]
  111. then
  112. CONFIG_PATH=$(echo "${VOLUME}" | awk '{print $1}')
  113. fi
  114. if [[ -z ${CONFIG_PATH} ]]
  115. then
  116. CONFIG_PATH=${DEFAULT_CONFIG_PATH}
  117. fi
  118. else
  119. CONFIG_PATH=${DESIRED_CONFIG_PATH}
  120. fi
  121. }
  122. function _run_in_new_container
  123. {
  124. # start temporary container with specified image
  125. if ! ${CRI} history -q "${IMAGE_NAME}" &>/dev/null
  126. then
  127. echo "Image '${IMAGE_NAME}' not found. Pulling ..."
  128. ${CRI} pull "${IMAGE_NAME}"
  129. fi
  130. ${CRI} run --rm "${USE_TTY}" \
  131. -v "${CONFIG_PATH}:${DMS_CONFIG}${USE_SELINUX}" \
  132. "${IMAGE_NAME}" "${@:+$@}"
  133. }
  134. function _main
  135. {
  136. _get_absolute_script_directory
  137. _set_default_config_path
  138. local OPTIND
  139. while getopts ":c:i:p:zZR" OPT
  140. do
  141. case ${OPT} in
  142. ( i ) IMAGE_NAME="${OPTARG}" ;;
  143. ( z | Z ) USE_SELINUX=":${OPT}" ;;
  144. ( c ) CONTAINER_NAME="${OPTARG}" ;;
  145. ( R ) PODMAN_ROOTLESS=true ;;
  146. ( p )
  147. case "${OPTARG}" in
  148. ( /* ) DESIRED_CONFIG_PATH="${OPTARG}" ;;
  149. ( * ) DESIRED_CONFIG_PATH="${DIR}/${OPTARG}" ;;
  150. esac
  151. if [[ ! -d ${DESIRED_CONFIG_PATH} ]]
  152. then
  153. echo "Specified directory '${DESIRED_CONFIG_PATH}' doesn't exist" >&2
  154. exit 1
  155. fi
  156. ;;
  157. ( * )
  158. echo "Invalid option: '-${OPTARG}'" >&2
  159. echo -e "Use './setup.sh help' to get a complete overview.\n" >&2
  160. _show_local_usage 'no-exit'
  161. exit 1
  162. ;;
  163. esac
  164. done
  165. shift $(( OPTIND - 1 ))
  166. if command -v docker &>/dev/null
  167. then
  168. CRI=docker
  169. elif command -v podman &>/dev/null
  170. then
  171. CRI=podman
  172. if ! ${PODMAN_ROOTLESS} && [[ ${EUID} -ne 0 ]]
  173. then
  174. read -r -p "You are running Podman in rootless mode. Continue? [Y/n] "
  175. [[ -n ${REPLY} ]] && [[ ${REPLY} =~ (n|N) ]] && exit 0
  176. fi
  177. else
  178. echo 'No supported Container Runtime Interface detected.'
  179. exit 1
  180. fi
  181. INFO=$(${CRI} ps --no-trunc --format "{{.Image}};{{.Names}}" --filter \
  182. label=org.opencontainers.image.title="docker-mailserver" | tail -1)
  183. CONTAINER_NAME=${INFO#*;}
  184. [[ -z ${IMAGE_NAME} ]] && IMAGE_NAME=${INFO%;*}
  185. if [[ -z ${IMAGE_NAME} ]]
  186. then
  187. IMAGE_NAME=${NAME:-${DEFAULT_IMAGE_NAME}}
  188. fi
  189. if test -t 0
  190. then
  191. USE_TTY="-it"
  192. else
  193. # GitHub Actions will fail (or really anything else
  194. # lacking an interactive tty) if we don't set a
  195. # value here; "-t" alone works for these cases.
  196. USE_TTY="-t"
  197. fi
  198. _handle_config_path
  199. if [[ -n ${CONTAINER_NAME} ]]
  200. then
  201. ${CRI} exec "${USE_TTY}" "${CONTAINER_NAME}" setup "${@:+$@}"
  202. else
  203. _run_in_new_container setup "${@:+$@}"
  204. fi
  205. [[ ${1} == 'help' ]] && _show_local_usage
  206. return 0
  207. }
  208. _main "${@:+$@}"