core.sh 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. #!/usr/bin/env bash
  2. # _modules/scripts/core.sh
  3. # THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
  4. # DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!!
  5. # ANSI color for red errors
  6. RED='\e[31m'
  7. GREEN='\e[32m'
  8. YELLOW='\e[33m'
  9. BLUE='\e[34m'
  10. MAGENTA='\e[35m'
  11. LIGHT_RED='\e[91m'
  12. LIGHT_GREEN='\e[92m'
  13. NC='\e[0m'
  14. caller="${BASH_SOURCE[1]##*/}"
  15. get_installed_tools(){
  16. for bin in openssl curl docker git awk sha1sum grep cut jq; do
  17. if [[ -z $(command -v ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi
  18. done
  19. if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox grep detected, please install gnu grep, \"apk add --no-cache --upgrade grep\"${NC}"; exit 1; fi
  20. # This will also cover sort
  21. if cp --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox cp detected, please install coreutils, \"apk add --no-cache --upgrade coreutils\"${NC}"; exit 1; fi
  22. if sed --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox sed detected, please install gnu sed, \"apk add --no-cache --upgrade sed\"${NC}"; exit 1; fi
  23. }
  24. get_docker_version(){
  25. # Check Docker Version (need at least 24.X)
  26. docker_version=$(docker version --format '{{.Server.Version}}' | cut -d '.' -f 1)
  27. }
  28. get_compose_type(){
  29. if docker compose > /dev/null 2>&1; then
  30. if docker compose version --short | grep -e "^2." -e "^v2." > /dev/null 2>&1; then
  31. COMPOSE_VERSION=native
  32. COMPOSE_COMMAND="docker compose"
  33. if [[ "$caller" == "update.sh" ]]; then
  34. sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=native/' "$SCRIPT_DIR/mailcow.conf"
  35. fi
  36. echo -e "\e[33mFound Docker Compose Plugin (native).\e[0m"
  37. echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to native\e[0m"
  38. sleep 2
  39. echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m"
  40. else
  41. echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
  42. echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
  43. exit 1
  44. fi
  45. elif docker-compose > /dev/null 2>&1; then
  46. if ! [[ $(alias docker-compose 2> /dev/null) ]] ; then
  47. if docker-compose version --short | grep "^2." > /dev/null 2>&1; then
  48. COMPOSE_VERSION=standalone
  49. COMPOSE_COMMAND="docker-compose"
  50. if [[ "$caller" == "update.sh" ]]; then
  51. sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=standalone/' "$SCRIPT_DIR/mailcow.conf"
  52. fi
  53. echo -e "\e[33mFound Docker Compose Standalone.\e[0m"
  54. echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m"
  55. sleep 2
  56. echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m"
  57. else
  58. echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
  59. echo -e "\e[31mPlease update/install manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
  60. exit 1
  61. fi
  62. fi
  63. else
  64. echo -e "\e[31mCannot find Docker Compose.\e[0m"
  65. echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
  66. exit 1
  67. fi
  68. }
  69. detect_bad_asn() {
  70. echo -e "\e[33mDetecting if your IP is listed on Spamhaus Bad ASN List...\e[0m"
  71. response=$(curl --connect-timeout 15 --max-time 30 -s -o /dev/null -w "%{http_code}" "https://asn-check.mailcow.email")
  72. if [ "$response" -eq 503 ]; then
  73. if [ -z "$SPAMHAUS_DQS_KEY" ]; then
  74. echo -e "\e[33mYour server's public IP uses an AS that is blocked by Spamhaus to use their DNS public blocklists for Postfix.\e[0m"
  75. echo -e "\e[33mmailcow did not detected a value for the variable SPAMHAUS_DQS_KEY inside mailcow.conf!\e[0m"
  76. sleep 2
  77. echo ""
  78. echo -e "\e[33mTo use the Spamhaus DNS Blocklists again, you will need to create a FREE account for their Data Query Service (DQS) at: https://www.spamhaus.com/free-trial/sign-up-for-a-free-data-query-service-account\e[0m"
  79. echo -e "\e[33mOnce done, enter your DQS API key in mailcow.conf and mailcow will do the rest for you!\e[0m"
  80. echo ""
  81. sleep 2
  82. else
  83. echo -e "\e[33mYour server's public IP uses an AS that is blocked by Spamhaus to use their DNS public blocklists for Postfix.\e[0m"
  84. echo -e "\e[32mmailcow detected a Value for the variable SPAMHAUS_DQS_KEY inside mailcow.conf. Postfix will use DQS with the given API key...\e[0m"
  85. fi
  86. elif [ "$response" -eq 200 ]; then
  87. echo -e "\e[33mCheck completed! Your IP is \e[32mclean\e[0m"
  88. elif [ "$response" -eq 429 ]; then
  89. echo -e "\e[33mCheck completed! \e[31mYour IP seems to be rate limited on the ASN Check service... please try again later!\e[0m"
  90. else
  91. echo -e "\e[31mCheck failed! \e[0mMaybe a DNS or Network problem?\e[0m"
  92. fi
  93. }
  94. check_online_status() {
  95. CHECK_ONLINE_DOMAINS=('https://github.com' 'https://hub.docker.com')
  96. for domain in "${CHECK_ONLINE_DOMAINS[@]}"; do
  97. if timeout 6 curl --head --silent --output /dev/null ${domain}; then
  98. return 0
  99. fi
  100. done
  101. return 1
  102. }
  103. prefetch_images() {
  104. [[ -z ${BRANCH} ]] && { echo -e "\e[33m\nUnknown branch...\e[0m"; exit 1; }
  105. git fetch origin #${BRANCH}
  106. while read image; do
  107. RET_C=0
  108. until docker pull "${image}"; do
  109. RET_C=$((RET_C + 1))
  110. echo -e "\e[33m\nError pulling $image, retrying...\e[0m"
  111. [ ${RET_C} -gt 3 ] && { echo -e "\e[31m\nToo many failed retries, exiting\e[0m"; exit 1; }
  112. sleep 1
  113. done
  114. done < <(git show "origin/${BRANCH}:docker-compose.yml" | grep "image:" | awk '{ gsub("image:","", $3); print $2 }')
  115. }
  116. docker_garbage() {
  117. SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../.." && pwd )"
  118. IMGS_TO_DELETE=()
  119. declare -A IMAGES_INFO
  120. COMPOSE_IMAGES=($(grep -oP "image: \K(ghcr\.io/)?mailcow.+" "${SCRIPT_DIR}/docker-compose.yml"))
  121. for existing_image in $(docker images --format "{{.ID}}:{{.Repository}}:{{.Tag}}" | grep -E '(mailcow/|ghcr\.io/mailcow/)'); do
  122. ID=$(echo "$existing_image" | cut -d ':' -f 1)
  123. REPOSITORY=$(echo "$existing_image" | cut -d ':' -f 2)
  124. TAG=$(echo "$existing_image" | cut -d ':' -f 3)
  125. if [[ "$REPOSITORY" == "mailcow/backup" || "$REPOSITORY" == "ghcr.io/mailcow/backup" ]]; then
  126. if [[ "$TAG" != "<none>" ]]; then
  127. continue
  128. fi
  129. fi
  130. if [[ " ${COMPOSE_IMAGES[@]} " =~ " ${REPOSITORY}:${TAG} " ]]; then
  131. continue
  132. else
  133. IMGS_TO_DELETE+=("$ID")
  134. IMAGES_INFO["$ID"]="$REPOSITORY:$TAG"
  135. fi
  136. done
  137. if [[ ! -z ${IMGS_TO_DELETE[*]} ]]; then
  138. echo "The following unused mailcow images were found:"
  139. for id in "${IMGS_TO_DELETE[@]}"; do
  140. echo " ${IMAGES_INFO[$id]} ($id)"
  141. done
  142. if [ -z "$FORCE" ]; then
  143. read -r -p "Do you want to delete them to free up some space? [y/N] " response
  144. if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
  145. docker rmi ${IMGS_TO_DELETE[*]}
  146. else
  147. echo "OK, skipped."
  148. fi
  149. else
  150. echo "Running in forced mode! Force removing old mailcow images..."
  151. docker rmi ${IMGS_TO_DELETE[*]}
  152. fi
  153. echo -e "\e[32mFurther cleanup...\e[0m"
  154. echo "If you want to cleanup further garbage collected by Docker, please make sure all containers are up and running before cleaning your system by executing \"docker system prune\""
  155. fi
  156. }
  157. in_array() {
  158. local e match="$1"
  159. shift
  160. for e; do [[ "$e" == "$match" ]] && return 0; done
  161. return 1
  162. }
  163. detect_major_update() {
  164. if [ ${BRANCH} == "master" ]; then
  165. # Array with major versions
  166. # Add major versions here
  167. MAJOR_VERSIONS=(
  168. "2025-02"
  169. "2025-03"
  170. "2025-08"
  171. )
  172. current_version=""
  173. if [[ -f "${SCRIPT_DIR}/data/web/inc/app_info.inc.php" ]]; then
  174. current_version=$(grep 'MAILCOW_GIT_VERSION' ${SCRIPT_DIR}/data/web/inc/app_info.inc.php | sed -E 's/.*MAILCOW_GIT_VERSION="([^"]+)".*/\1/')
  175. fi
  176. if [[ -z "$current_version" ]]; then
  177. return 1
  178. fi
  179. release_url="https://github.com/mailcow/mailcow-dockerized/releases/tag"
  180. updates_to_apply=()
  181. for version in "${MAJOR_VERSIONS[@]}"; do
  182. if [[ "$current_version" < "$version" ]]; then
  183. updates_to_apply+=("$version")
  184. fi
  185. done
  186. if [[ ${#updates_to_apply[@]} -gt 0 ]]; then
  187. echo -e "\e[33m\nMAJOR UPDATES to be applied:\e[0m"
  188. for update in "${updates_to_apply[@]}"; do
  189. echo "$update - $release_url/$update"
  190. done
  191. echo -e "\nPlease read the release notes before proceeding."
  192. read -p "Do you want to proceed with the update? [y/n] " response
  193. if [[ "${response}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
  194. echo "Proceeding with the update..."
  195. else
  196. echo "Update canceled. Exiting."
  197. exit 1
  198. fi
  199. fi
  200. fi
  201. }