core.sh 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  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
  18. echo "Error: Cannot find command '${bin}'. Cannot proceed."
  19. echo "Solution: Please review system requirements and install requirements. Then, re-run the script."
  20. echo "See System Requirements: https://docs.mailcow.email/getstarted/install/"
  21. echo "Exiting..."
  22. exit 1
  23. fi
  24. done
  25. 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
  26. # This will also cover sort
  27. 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
  28. 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
  29. }
  30. get_docker_version(){
  31. # Check Docker Version (need at least 24.X)
  32. docker_version=$(docker version --format '{{.Server.Version}}' | cut -d '.' -f 1)
  33. }
  34. get_compose_type(){
  35. if docker compose > /dev/null 2>&1; then
  36. if docker compose version --short | grep -e "^2." -e "^v2." > /dev/null 2>&1; then
  37. COMPOSE_VERSION=native
  38. COMPOSE_COMMAND="docker compose"
  39. if [[ "$caller" == "update.sh" ]]; then
  40. sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=native/' "$SCRIPT_DIR/mailcow.conf"
  41. fi
  42. echo -e "\e[33mFound Docker Compose Plugin (native).\e[0m"
  43. echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to native\e[0m"
  44. sleep 2
  45. echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m"
  46. else
  47. echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
  48. echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
  49. exit 1
  50. fi
  51. elif docker-compose > /dev/null 2>&1; then
  52. if ! [[ $(alias docker-compose 2> /dev/null) ]] ; then
  53. if docker-compose version --short | grep "^2." > /dev/null 2>&1; then
  54. COMPOSE_VERSION=standalone
  55. COMPOSE_COMMAND="docker-compose"
  56. if [[ "$caller" == "update.sh" ]]; then
  57. sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=standalone/' "$SCRIPT_DIR/mailcow.conf"
  58. fi
  59. echo -e "\e[33mFound Docker Compose Standalone.\e[0m"
  60. echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m"
  61. sleep 2
  62. 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"
  63. else
  64. echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
  65. echo -e "\e[31mPlease update/install manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
  66. exit 1
  67. fi
  68. fi
  69. else
  70. echo -e "\e[31mCannot find Docker Compose.\e[0m"
  71. echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
  72. exit 1
  73. fi
  74. }
  75. detect_bad_asn() {
  76. echo -e "\e[33mDetecting if your IP is listed on Spamhaus Bad ASN List...\e[0m"
  77. response=$(curl --connect-timeout 15 --max-time 30 -s -o /dev/null -w "%{http_code}" "https://asn-check.mailcow.email")
  78. if [ "$response" -eq 503 ]; then
  79. if [ -z "$SPAMHAUS_DQS_KEY" ]; then
  80. 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"
  81. echo -e "\e[33mmailcow did not detected a value for the variable SPAMHAUS_DQS_KEY inside mailcow.conf!\e[0m"
  82. sleep 2
  83. echo ""
  84. 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"
  85. echo -e "\e[33mOnce done, enter your DQS API key in mailcow.conf and mailcow will do the rest for you!\e[0m"
  86. echo ""
  87. sleep 2
  88. else
  89. 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"
  90. 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"
  91. fi
  92. elif [ "$response" -eq 200 ]; then
  93. echo -e "\e[33mCheck completed! Your IP is \e[32mclean\e[0m"
  94. elif [ "$response" -eq 429 ]; then
  95. echo -e "\e[33mCheck completed! \e[31mYour IP seems to be rate limited on the ASN Check service... please try again later!\e[0m"
  96. else
  97. echo -e "\e[31mCheck failed! \e[0mMaybe a DNS or Network problem?\e[0m"
  98. fi
  99. }
  100. check_online_status() {
  101. CHECK_ONLINE_DOMAINS=('https://github.com' 'https://hub.docker.com')
  102. for domain in "${CHECK_ONLINE_DOMAINS[@]}"; do
  103. if timeout 6 curl --head --silent --output /dev/null ${domain}; then
  104. return 0
  105. fi
  106. done
  107. return 1
  108. }
  109. prefetch_images() {
  110. [[ -z ${BRANCH} ]] && { echo -e "\e[33m\nUnknown branch...\e[0m"; exit 1; }
  111. git fetch origin #${BRANCH}
  112. while read image; do
  113. RET_C=0
  114. until docker pull "${image}"; do
  115. RET_C=$((RET_C + 1))
  116. echo -e "\e[33m\nError pulling $image, retrying...\e[0m"
  117. [ ${RET_C} -gt 3 ] && { echo -e "\e[31m\nToo many failed retries, exiting\e[0m"; exit 1; }
  118. sleep 1
  119. done
  120. done < <(git show "origin/${BRANCH}:docker-compose.yml" | grep "image:" | awk '{ gsub("image:","", $3); print $2 }')
  121. }
  122. docker_garbage() {
  123. SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../.." && pwd )"
  124. IMGS_TO_DELETE=()
  125. declare -A IMAGES_INFO
  126. COMPOSE_IMAGES=($(grep -oP "image: \K(ghcr\.io/)?mailcow.+" "${SCRIPT_DIR}/docker-compose.yml"))
  127. for existing_image in $(docker images --format "{{.ID}}:{{.Repository}}:{{.Tag}}" | grep -E '(mailcow/|ghcr\.io/mailcow/)'); do
  128. ID=$(echo "$existing_image" | cut -d ':' -f 1)
  129. REPOSITORY=$(echo "$existing_image" | cut -d ':' -f 2)
  130. TAG=$(echo "$existing_image" | cut -d ':' -f 3)
  131. if [[ "$REPOSITORY" == "mailcow/backup" || "$REPOSITORY" == "ghcr.io/mailcow/backup" ]]; then
  132. if [[ "$TAG" != "<none>" ]]; then
  133. continue
  134. fi
  135. fi
  136. if [[ " ${COMPOSE_IMAGES[@]} " =~ " ${REPOSITORY}:${TAG} " ]]; then
  137. continue
  138. else
  139. IMGS_TO_DELETE+=("$ID")
  140. IMAGES_INFO["$ID"]="$REPOSITORY:$TAG"
  141. fi
  142. done
  143. if [[ ! -z ${IMGS_TO_DELETE[*]} ]]; then
  144. echo "The following unused mailcow images were found:"
  145. for id in "${IMGS_TO_DELETE[@]}"; do
  146. echo " ${IMAGES_INFO[$id]} ($id)"
  147. done
  148. if [ -z "$FORCE" ]; then
  149. read -r -p "Do you want to delete them to free up some space? [y/N] " response
  150. if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
  151. docker rmi ${IMGS_TO_DELETE[*]}
  152. else
  153. echo "OK, skipped."
  154. fi
  155. else
  156. echo "Running in forced mode! Force removing old mailcow images..."
  157. docker rmi ${IMGS_TO_DELETE[*]}
  158. fi
  159. echo -e "\e[32mFurther cleanup...\e[0m"
  160. 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\""
  161. fi
  162. }
  163. in_array() {
  164. local e match="$1"
  165. shift
  166. for e; do [[ "$e" == "$match" ]] && return 0; done
  167. return 1
  168. }
  169. detect_major_update() {
  170. if [ ${BRANCH} == "master" ]; then
  171. # Array with major versions
  172. # Add major versions here
  173. MAJOR_VERSIONS=(
  174. "2025-02"
  175. "2025-03"
  176. "2025-09"
  177. )
  178. current_version=""
  179. if [[ -f "${SCRIPT_DIR}/data/web/inc/app_info.inc.php" ]]; then
  180. current_version=$(grep 'MAILCOW_GIT_VERSION' ${SCRIPT_DIR}/data/web/inc/app_info.inc.php | sed -E 's/.*MAILCOW_GIT_VERSION="([^"]+)".*/\1/')
  181. fi
  182. if [[ -z "$current_version" ]]; then
  183. return 1
  184. fi
  185. release_url="https://github.com/mailcow/mailcow-dockerized/releases/tag"
  186. updates_to_apply=()
  187. for version in "${MAJOR_VERSIONS[@]}"; do
  188. if [[ "$current_version" < "$version" ]]; then
  189. updates_to_apply+=("$version")
  190. fi
  191. done
  192. if [[ ${#updates_to_apply[@]} -gt 0 ]]; then
  193. echo -e "\e[33m\nMAJOR UPDATES to be applied:\e[0m"
  194. for update in "${updates_to_apply[@]}"; do
  195. echo "$update - $release_url/$update"
  196. done
  197. echo -e "\nPlease read the release notes before proceeding."
  198. read -p "Do you want to proceed with the update? [y/n] " response
  199. if [[ "${response}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
  200. echo "Proceeding with the update..."
  201. else
  202. echo "Update canceled. Exiting."
  203. exit 1
  204. fi
  205. fi
  206. fi
  207. }