Merge branch 'staging' into nightly

This commit is contained in:
FreddleSpl0it 2025-03-24 11:38:59 +01:00
commit cf2d3c1b4e
No known key found for this signature in database
GPG Key ID: 00E14E7634F4BEC5
29 changed files with 237 additions and 114 deletions

View File

@ -9,6 +9,8 @@ on:
jobs: jobs:
docker_image_build: docker_image_build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
packages: write
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -19,11 +21,13 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub - name: Login to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
username: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_USERNAME }} registry: ghcr.io
password: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_TOKEN }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
@ -32,4 +36,4 @@ jobs:
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
file: data/Dockerfiles/backup/Dockerfile file: data/Dockerfiles/backup/Dockerfile
push: true push: true
tags: mailcow/backup:latest tags: ghcr.io/mailcow/backup:latest

View File

@ -1,8 +1,7 @@
FROM alpine:3.20 FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
RUN apk upgrade --no-cache \ RUN apk upgrade --no-cache \
&& apk add --update --no-cache \ && apk add --update --no-cache \
bash \ bash \
@ -15,7 +14,7 @@ RUN apk upgrade --no-cache \
tini \ tini \
tzdata \ tzdata \
python3 \ python3 \
acme-tiny --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community/ acme-tiny
COPY acme.sh /srv/acme.sh COPY acme.sh /srv/acme.sh
COPY functions.sh /srv/functions.sh COPY functions.sh /srv/functions.sh

View File

@ -138,7 +138,7 @@ log_f "Resolver OK"
log_f "Waiting for domain table..." log_f "Waiting for domain table..."
while [[ -z ${DOMAIN_TABLE} ]]; do while [[ -z ${DOMAIN_TABLE} ]]; do
curl --silent http://nginx.${COMPOSE_PROJECT_NAME}_mailcow-network/ >/dev/null 2>&1 curl --silent http://nginx.${COMPOSE_PROJECT_NAME}_mailcow-network/ >/dev/null 2>&1
DOMAIN_TABLE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs) DOMAIN_TABLE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs)
[[ -z ${DOMAIN_TABLE} ]] && sleep 10 [[ -z ${DOMAIN_TABLE} ]] && sleep 10
done done
log_f "OK" no_date log_f "OK" no_date
@ -231,7 +231,7 @@ while true; do
######################################### #########################################
# IP and webroot challenge verification # # IP and webroot challenge verification #
SQL_DOMAINS=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0 and active=1" -Bs) SQL_DOMAINS=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0 and active=1" -Bs)
if [[ ! $? -eq 0 ]]; then if [[ ! $? -eq 0 ]]; then
log_f "Failed to read SQL domains, retrying in 1 minute..." log_f "Failed to read SQL domains, retrying in 1 minute..."
sleep 1m sleep 1m

View File

@ -1,3 +1,3 @@
FROM debian:bookworm-slim FROM debian:bookworm-slim
RUN apt update && apt install pigz RUN apt update && apt install pigz -y --no-install-recommends

View File

@ -1,4 +1,4 @@
FROM alpine:3.20 FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View File

@ -1,4 +1,4 @@
FROM alpine:3.20 FROM alpine:3.21
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
@ -69,7 +69,7 @@ RUN addgroup -g 5000 vmail \
perl-par-packer \ perl-par-packer \
perl-parse-recdescent \ perl-parse-recdescent \
perl-lockfile-simple \ perl-lockfile-simple \
libproc \ libproc2 \
perl-readonly \ perl-readonly \
perl-regexp-common \ perl-regexp-common \
perl-sys-meminfo \ perl-sys-meminfo \

View File

@ -15,6 +15,6 @@ if ! [[ ${MAX_AGE} =~ ${NUM_REGEXP} ]] ; then
exit 1 exit 1
fi fi
TO_DELETE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT COUNT(id) FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY" -BN) TO_DELETE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT COUNT(id) FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY" -BN)
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY" mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY"
echo "Deleted ${TO_DELETE} items from quarantine table (max age is ${MAX_AGE//[!0-9]/} days)" echo "Deleted ${TO_DELETE} items from quarantine table (max age is ${MAX_AGE//[!0-9]/} days)"

View File

@ -297,15 +297,15 @@ printenv | sed 's/^\(.*\)$/export \1/g' > /source_env.sh
# Clean stopped imapsync jobs # Clean stopped imapsync jobs
rm -f /tmp/imapsync_busy.lock rm -f /tmp/imapsync_busy.lock
IMAPSYNC_TABLE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'imapsync'" -Bs) IMAPSYNC_TABLE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'imapsync'" -Bs)
[[ ! -z ${IMAPSYNC_TABLE} ]] && mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "UPDATE imapsync SET is_running='0'" [[ ! -z ${IMAPSYNC_TABLE} ]] && mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "UPDATE imapsync SET is_running='0'"
# Envsubst maildir_gc # Envsubst maildir_gc
echo "$(envsubst < /usr/local/bin/maildir_gc.sh)" > /usr/local/bin/maildir_gc.sh echo "$(envsubst < /usr/local/bin/maildir_gc.sh)" > /usr/local/bin/maildir_gc.sh
# GUID generation # GUID generation
while [[ ${VERSIONS_OK} != 'OK' ]]; do while [[ ${VERSIONS_OK} != 'OK' ]]; do
if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = \"${DBNAME}\" AND TABLE_NAME = 'versions'") ]]; then if [[ ! -z $(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = \"${DBNAME}\" AND TABLE_NAME = 'versions'") ]]; then
VERSIONS_OK=OK VERSIONS_OK=OK
else else
echo "Waiting for versions table to be created..." echo "Waiting for versions table to be created..."
@ -316,11 +316,11 @@ PUBKEY_MCRYPT=$(doveconf -P 2> /dev/null | grep -i mail_crypt_global_public_key
if [ -f ${PUBKEY_MCRYPT} ]; then if [ -f ${PUBKEY_MCRYPT} ]; then
GUID=$(cat <(echo ${MAILCOW_HOSTNAME}) /mail_crypt/ecpubkey.pem | sha256sum | cut -d ' ' -f1 | tr -cd "[a-fA-F0-9.:/] ") GUID=$(cat <(echo ${MAILCOW_HOSTNAME}) /mail_crypt/ecpubkey.pem | sha256sum | cut -d ' ' -f1 | tr -cd "[a-fA-F0-9.:/] ")
if [ ${#GUID} -eq 64 ]; then if [ ${#GUID} -eq 64 ]; then
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
REPLACE INTO versions (application, version) VALUES ("GUID", "${GUID}"); REPLACE INTO versions (application, version) VALUES ("GUID", "${GUID}");
EOF EOF
else else
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
REPLACE INTO versions (application, version) VALUES ("GUID", "INVALID"); REPLACE INTO versions (application, version) VALUES ("GUID", "INVALID");
EOF EOF
fi fi

View File

@ -1,4 +1,4 @@
FROM alpine:3.20 FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View File

@ -1,4 +1,4 @@
FROM alpine:3.20 FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View File

@ -1,4 +1,4 @@
FROM php:8.2-fpm-alpine3.20 FROM php:8.2-fpm-alpine3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
@ -13,7 +13,7 @@ ARG MEMCACHED_PECL_VERSION=3.2.0
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$ # renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$
ARG REDIS_PECL_VERSION=6.1.0 ARG REDIS_PECL_VERSION=6.1.0
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$ # renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
ARG COMPOSER_VERSION=2.6.6 ARG COMPOSER_VERSION=2.8.6
RUN apk add -U --no-cache autoconf \ RUN apk add -U --no-cache autoconf \
aspell-dev \ aspell-dev \

View File

@ -81,7 +81,7 @@ if [ ${SQL_CHANGED} -eq 1 ]; then
fi fi
# Check mysql tz import (master and slave) # Check mysql tz import (master and slave)
TZ_CHECK=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT CONVERT_TZ('2019-11-02 23:33:00','Europe/Berlin','UTC') AS time;" -BN 2> /dev/null) TZ_CHECK=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT CONVERT_TZ('2019-11-02 23:33:00','Europe/Berlin','UTC') AS time;" -BN 2> /dev/null)
if [[ -z ${TZ_CHECK} ]] || [[ "${TZ_CHECK}" == "NULL" ]]; then if [[ -z ${TZ_CHECK} ]] || [[ "${TZ_CHECK}" == "NULL" ]]; then
SQL_FULL_TZINFO_IMPORT_RETURN=$(curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_tzinfo_to_sql"}' --silent -H 'Content-type: application/json') SQL_FULL_TZINFO_IMPORT_RETURN=$(curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_tzinfo_to_sql"}' --silent -H 'Content-type: application/json')
echo "MySQL mysql_tzinfo_to_sql - debug output:" echo "MySQL mysql_tzinfo_to_sql - debug output:"
@ -120,11 +120,11 @@ if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
while read line while read line
do do
DOMAIN_ARR+=("$line") DOMAIN_ARR+=("$line")
done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs) done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs)
while read line while read line
do do
DOMAIN_ARR+=("$line") DOMAIN_ARR+=("$line")
done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs) done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs)
if [[ ! -z ${DOMAIN_ARR} ]]; then if [[ ! -z ${DOMAIN_ARR} ]]; then
for domain in "${DOMAIN_ARR[@]}"; do for domain in "${DOMAIN_ARR[@]}"; do
@ -146,13 +146,13 @@ if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
VALIDATED_IPS=$(array_by_comma ${VALIDATED_API_ALLOW_FROM_ARR[*]}) VALIDATED_IPS=$(array_by_comma ${VALIDATED_API_ALLOW_FROM_ARR[*]})
if [[ ! -z ${VALIDATED_IPS} ]]; then if [[ ! -z ${VALIDATED_IPS} ]]; then
if [[ ${API_KEY} != "invalid" ]] && [[ ! -z ${API_KEY} ]]; then if [[ ${API_KEY} != "invalid" ]] && [[ ! -z ${API_KEY} ]]; then
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
DELETE FROM api WHERE access = 'rw'; DELETE FROM api WHERE access = 'rw';
INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY}", "1", "${VALIDATED_IPS}", "rw"); INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY}", "1", "${VALIDATED_IPS}", "rw");
EOF EOF
fi fi
if [[ ${API_KEY_READ_ONLY} != "invalid" ]] && [[ ! -z ${API_KEY_READ_ONLY} ]]; then if [[ ${API_KEY_READ_ONLY} != "invalid" ]] && [[ ! -z ${API_KEY_READ_ONLY} ]]; then
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
DELETE FROM api WHERE access = 'ro'; DELETE FROM api WHERE access = 'ro';
INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY_READ_ONLY}", "1", "${VALIDATED_IPS}", "ro"); INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY_READ_ONLY}", "1", "${VALIDATED_IPS}", "ro");
EOF EOF
@ -161,7 +161,7 @@ EOF
fi fi
# Create events (master only, STATUS for event on slave will be SLAVESIDE_DISABLED) # Create events (master only, STATUS for event on slave will be SLAVESIDE_DISABLED)
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
DROP EVENT IF EXISTS clean_spamalias; DROP EVENT IF EXISTS clean_spamalias;
DELIMITER // DELIMITER //
CREATE EVENT clean_spamalias CREATE EVENT clean_spamalias

View File

@ -2,7 +2,7 @@ FROM debian:bookworm-slim
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ARG RSPAMD_VER=rspamd_3.11.0-2~90a175b45 ARG RSPAMD_VER=rspamd_3.11.1-1~ab0b44951
ARG CODENAME=bookworm ARG CODENAME=bookworm
ENV LC_ALL=C ENV LC_ALL=C

View File

@ -14,11 +14,11 @@ do
done done
# Wait for updated schema # Wait for updated schema
DBV_NOW=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN) DBV_NOW=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN)
DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2) DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2)
while [[ "${DBV_NOW}" != "${DBV_NEW}" ]]; do while [[ "${DBV_NOW}" != "${DBV_NEW}" ]]; do
echo "Waiting for schema update..." echo "Waiting for schema update..."
DBV_NOW=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN) DBV_NOW=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN)
DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2) DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2)
sleep 5 sleep 5
done done
@ -112,7 +112,7 @@ while read -r line gal
/etc/sogo/plist_ldap.sh ${line} ${gal} >> /var/lib/sogo/GNUstep/Defaults/sogod.plist /etc/sogo/plist_ldap.sh ${line} ${gal} >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
echo " </array> echo " </array>
</dict>" >> /var/lib/sogo/GNUstep/Defaults/sogod.plist </dict>" >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain, CASE gal WHEN '1' THEN 'YES' ELSE 'NO' END AS gal FROM domain;" -B -N) done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain, CASE gal WHEN '1' THEN 'YES' ELSE 'NO' END AS gal FROM domain;" -B -N)
# Generate footer # Generate footer
echo ' </dict> echo ' </dict>

View File

@ -1,4 +1,4 @@
FROM alpine:3.20 FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View File

@ -1,4 +1,4 @@
FROM alpine:3.20 FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View File

@ -132,9 +132,9 @@ fi
# Connect to the DB server and store output in vars # Connect to the DB server and store output in vars
if [[ -n $socket ]]; then if [[ -n $socket ]]; then
ConnectionResult=$(mysql ${optfile} ${socket} ${user} -e "show slave ${connection} status\G" 2>&1) ConnectionResult=$(mariadb --skip-ssl ${optfile} ${socket} ${user} -e "show slave ${connection} status\G" 2>&1)
else else
ConnectionResult=$(mysql ${optfile} ${host} ${port} ${user} -e "show slave ${connection} status\G" 2>&1) ConnectionResult=$(mariadb --skip-ssl ${optfile} ${host} ${port} ${user} -e "show slave ${connection} status\G" 2>&1)
fi fi
if [ -z "`echo "${ConnectionResult}" |grep Slave_IO_State`" ]; then if [ -z "`echo "${ConnectionResult}" |grep Slave_IO_State`" ]; then

View File

@ -234,7 +234,7 @@ external_checks() {
diff_c=0 diff_c=0
THRESHOLD=${EXTERNAL_CHECKS_THRESHOLD} THRESHOLD=${EXTERNAL_CHECKS_THRESHOLD}
# Reduce error count by 2 after restarting an unhealthy container # Reduce error count by 2 after restarting an unhealthy container
GUID=$(mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'GUID'" -BN) GUID=$(mariadb --skip-ssl -u${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'GUID'" -BN)
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
while [ ${err_count} -lt ${THRESHOLD} ]; do while [ ${err_count} -lt ${THRESHOLD} ]; do
err_c_cur=${err_count} err_c_cur=${err_count}

View File

@ -1,6 +1,6 @@
# Whitelist generated by Postwhite v3.4 on Sat Feb 1 00:18:03 UTC 2025 # Whitelist generated by Postwhite v3.4 on Sat Mar 1 00:19:29 UTC 2025
# https://github.com/stevejenkins/postwhite/ # https://github.com/stevejenkins/postwhite/
# 1984 total rules # 2000 total rules
2a00:1450:4000::/36 permit 2a00:1450:4000::/36 permit
2a01:111:f400::/48 permit 2a01:111:f400::/48 permit
2a01:111:f403:8000::/50 permit 2a01:111:f403:8000::/50 permit
@ -8,6 +8,13 @@
2a01:111:f403::/49 permit 2a01:111:f403::/49 permit
2a01:111:f403:c000::/51 permit 2a01:111:f403:c000::/51 permit
2a01:111:f403:f000::/52 permit 2a01:111:f403:f000::/52 permit
2a01:b747:3000:200::/56 permit
2a01:b747:3001:200::/56 permit
2a01:b747:3002:200::/56 permit
2a01:b747:3003:200::/56 permit
2a01:b747:3004:200::/56 permit
2a01:b747:3005:200::/56 permit
2a01:b747:3006:200::/56 permit
2a02:a60:0:5::/64 permit 2a02:a60:0:5::/64 permit
2c0f:fb50:4000::/36 permit 2c0f:fb50:4000::/36 permit
2.207.151.53 permit 2.207.151.53 permit
@ -19,7 +26,6 @@
8.20.114.31 permit 8.20.114.31 permit
8.25.194.0/23 permit 8.25.194.0/23 permit
8.25.196.0/23 permit 8.25.196.0/23 permit
10.162.0.0/16 permit
12.130.86.238 permit 12.130.86.238 permit
13.110.208.0/21 permit 13.110.208.0/21 permit
13.110.209.0/24 permit 13.110.209.0/24 permit
@ -35,7 +41,9 @@
17.57.156.0/24 permit 17.57.156.0/24 permit
17.58.0.0/16 permit 17.58.0.0/16 permit
17.142.0.0/15 permit 17.142.0.0/15 permit
17.143.234.140/30 permit 18.97.0.8/30 permit
18.97.1.184/29 permit
18.97.2.64/26 permit
18.156.89.250 permit 18.156.89.250 permit
18.157.243.190 permit 18.157.243.190 permit
18.194.95.56 permit 18.194.95.56 permit
@ -283,6 +291,9 @@
64.207.219.13 permit 64.207.219.13 permit
64.207.219.14 permit 64.207.219.14 permit
64.207.219.15 permit 64.207.219.15 permit
64.207.219.24 permit
64.207.219.25 permit
64.207.219.26 permit
64.207.219.71 permit 64.207.219.71 permit
64.207.219.72 permit 64.207.219.72 permit
64.207.219.73 permit 64.207.219.73 permit
@ -292,6 +303,9 @@
64.207.219.77 permit 64.207.219.77 permit
64.207.219.78 permit 64.207.219.78 permit
64.207.219.79 permit 64.207.219.79 permit
64.207.219.88 permit
64.207.219.89 permit
64.207.219.90 permit
64.207.219.135 permit 64.207.219.135 permit
64.207.219.136 permit 64.207.219.136 permit
64.207.219.137 permit 64.207.219.137 permit
@ -1464,6 +1478,8 @@
159.135.224.0/20 permit 159.135.224.0/20 permit
159.135.228.10 permit 159.135.228.10 permit
159.183.0.0/16 permit 159.183.0.0/16 permit
159.183.68.71 permit
159.183.79.38 permit
160.1.62.192 permit 160.1.62.192 permit
161.38.192.0/20 permit 161.38.192.0/20 permit
161.38.204.0/22 permit 161.38.204.0/22 permit

View File

@ -409,7 +409,7 @@ paths:
description: a list of domains for which a dkim key should be generated description: a list of domains for which a dkim key should be generated
type: string type: string
key_size: key_size:
description: the key size (1024 or 2048) description: the key size (1024, 2048, 3072 or 4096)
type: number type: number
type: object type: object
summary: Generate DKIM Key summary: Generate DKIM Key

View File

@ -240,9 +240,12 @@ function dkim($_action, $_data = null, $privkey = false) {
if (strlen($dkimdata['pubkey']) < 391) { if (strlen($dkimdata['pubkey']) < 391) {
$dkimdata['length'] = "1024"; $dkimdata['length'] = "1024";
} }
elseif (strlen($dkimdata['pubkey']) < 736) { elseif (strlen($dkimdata['pubkey']) < 564) {
$dkimdata['length'] = "2048"; $dkimdata['length'] = "2048";
} }
elseif (strlen($dkimdata['pubkey']) < 736) {
$dkimdata['length'] = "3072";
}
elseif (strlen($dkimdata['pubkey']) < 1416) { elseif (strlen($dkimdata['pubkey']) < 1416) {
$dkimdata['length'] = "4096"; $dkimdata['length'] = "4096";
} }

View File

@ -25,7 +25,11 @@
"syncjobs": "동기화 작업", "syncjobs": "동기화 작업",
"tls_policy": "TLS 정책", "tls_policy": "TLS 정책",
"unlimited_quota": "메일에 무제한 할당", "unlimited_quota": "메일에 무제한 할당",
"domain_desc": "도메인 설명 변경" "domain_desc": "도메인 설명 변경",
"pw_reset": "mailcow 사용자 비밀번호 재설정 허용",
"domain_relayhost": "도메인의 릴레이 호스트 변경",
"mailbox_relayhost": "메일함의 릴레이 호스트 변경",
"quarantine_category": "검역소 알림 카테고리 변경"
}, },
"add": { "add": {
"activate_filter_warn": "활성화가 체크되어 있으면 모든 다른 필터들은 비활성화됩니다.", "activate_filter_warn": "활성화가 체크되어 있으면 모든 다른 필터들은 비활성화됩니다.",
@ -101,7 +105,9 @@
"timeout2": "로컬 호스트 연결 시간 초과", "timeout2": "로컬 호스트 연결 시간 초과",
"username": "사용자명", "username": "사용자명",
"validate": "확인하기", "validate": "확인하기",
"validation_success": "성공적으로 확인됨" "validation_success": "성공적으로 확인됨",
"tags": "태그",
"app_passwd_protocols": "앱 비밀번호에 대해 허용되는 프로토콜"
}, },
"admin": { "admin": {
"access": "접근", "access": "접근",
@ -195,7 +201,7 @@
"link": "Link", "link": "Link",
"loading": "잠시만 기다려주세요...", "loading": "잠시만 기다려주세요...",
"logo_info": "이미지 크기는 상단 탐색 막대의 경우 40px, 시작 페이지의 경우 최대 너비 250px로 조정됩니다. 확장 가능한 그래픽을 권장합니다.", "logo_info": "이미지 크기는 상단 탐색 막대의 경우 40px, 시작 페이지의 경우 최대 너비 250px로 조정됩니다. 확장 가능한 그래픽을 권장합니다.",
"lookup_mx": "MX와 목적지 일치 (.outlook.com이 홉을 통해서 MX *.outlook.com을 대상으로 하는 모든 메일을 라우트한다.)", "lookup_mx": "목적지가 MX 이름과 일치하는 정규 표현식입니다. (<code>.*\\.google\\.com</code>를 사용하여 이 홉을 통해 google.com으로 끝나는 모든 메일을 대상으로 하는 MX로 라우팅합니다.)",
"main_name": "\"mailcow UI\" 이름", "main_name": "\"mailcow UI\" 이름",
"merged_vars_hint": "회색으로 표시된 행은 <code>vars.(local.)php</code> 에서 병합되었고 이는 수정할 수 없습니다.", "merged_vars_hint": "회색으로 표시된 행은 <code>vars.(local.)php</code> 에서 병합되었고 이는 수정할 수 없습니다.",
"message": "메세지", "message": "메세지",
@ -301,7 +307,53 @@
"username": "사용자 이름", "username": "사용자 이름",
"validate_license_now": "라이선스 서버와 GUID 확인", "validate_license_now": "라이선스 서버와 GUID 확인",
"verify": "확인", "verify": "확인",
"yes": "&#10003;" "yes": "&#10003;",
"domain_admin": "도메인 관리자",
"f2b_filter": "정규식 필터",
"f2b_manage_external": "외부에서 Fail2Ban 관리",
"f2b_max_ban_time": "최대 차단 시간(초)",
"f2b_regex_info": "고려되는 로그: SOGo, Postfix, Dovecot, PHP-FPM.",
"html": "HTML",
"oauth2_apps": "OAuth2 앱",
"oauth2_add_client": "OAuth2 클라이언트 추가",
"optional": "선택 사항",
"options": "옵션",
"password_length": "비밀번호 길이",
"password_policy_chars": "하나 이상의 알파벳 문자를 포함해야 합니다.",
"password_policy_length": "최소 암호 길이가 %d입니다.",
"password_policy_numbers": "숫자 하나 이상을 포함해야 합니다.",
"password_policy_special_chars": "특수 문자를 포함해야 합니다.",
"password_reset_info": "복구 이메일이 제공되지 않으면 이 기능을 사용할 수 없습니다.",
"password_reset_settings": "비밀번호 복구 설정",
"password_reset_tmpl_html": "HTML 템플릿",
"password_reset_tmpl_text": "Text 템플릿",
"password_settings": "비밀번호 설정",
"queue_unban": "차단 해제",
"restore_template": "기본 템플릿을 복원하려면 비워둡니다.",
"service": "서비스",
"success": "성공",
"dkim_overwrite_key": "기존 DKIM 키 덮어쓰기",
"f2b_ban_time_increment": "차단 시간은 차단될 때마다 증가합니다.",
"password_policy": "비밀번호 정책",
"quarantine_max_score": "메일의 스팸 점수가 이 값보다 높으면 알림을 삭제합니다:<br><small>기본값: 9999.0</small>",
"f2b_manage_external_info": "Fail2ban은 차단 목록을 유지하지만 트래픽을 차단하는 규칙을 능동적으로 설정하지는 않습니다. 트래픽을 외부에서 차단하려면 아래 생성된 차단 목록을 사용하세요.",
"password_policy_lowerupper": "소문자 및 대문자를 포함해야 합니다.",
"transport_test_rcpt_info": "&#8226; null@hosted.mailcow.de 을 사용하여 해외 목적지로 릴레이를 테스트하세요.",
"ip_check_disabled": "IP 확인이 비활성화됩니다. 아래에서 활성화할 수 있습니다<br> <strong>시스템 > 구성 > 옵션 > 사용자 정의</strong>",
"logo_normal_label": "일반",
"logo_dark_label": "다크 모드의 경우 반전",
"convert_html_to_text": "HTML을 일반 텍스트로 변환",
"copy_to_clipboard": "클립보드에 텍스트가 복사되었습니다!",
"cors_settings": "CORS 설정",
"rsettings_preset_4": "도메인에 대해 Rspamd 비활성화",
"ip_check": "IP 확인",
"admins": "관리자",
"admins_ldap": "LDAP 관리자",
"api_read_only": "읽기 전용 액세스",
"api_read_write": "읽기-쓰기 액세스",
"is_mx_based": "MX 기반",
"login_time": "로그인 시간",
"ip_check_opt_in": "외부 IP 주소 확인을 위해 타사 서비스 <strong>ipv4.mailcow.email</strong> 및 <strong>ipv6.mailcow.email</strong>을 사용하도록 설정합니다."
}, },
"danger": { "danger": {
"access_denied": "접근이 거부되거나 잘못된 데이터 양식", "access_denied": "접근이 거부되거나 잘못된 데이터 양식",
@ -415,7 +467,30 @@
"username_invalid": "%s는 사용지 이름으로 사용할 수 없습니다.", "username_invalid": "%s는 사용지 이름으로 사용할 수 없습니다.",
"validity_missing": "유효 기간을 지정해주세요.", "validity_missing": "유효 기간을 지정해주세요.",
"value_missing": "모든 값을 입력해주세요.", "value_missing": "모든 값을 입력해주세요.",
"yotp_verification_failed": "Yubico OTP 검증 실패: %s" "yotp_verification_failed": "Yubico OTP 검증 실패: %s",
"dkim_domain_or_sel_exists": "“%s\"에 대한 DKIM 키가 존재하며 덮어쓰지 않습니다.",
"img_size_exceeded": "이미지가 최대 파일 크기를 초과합니다.",
"invalid_reset_token": "잘못된 리셋 토큰",
"nginx_reload_failed": "Nginx 리로드 실패: %s",
"password_reset_na": "현재 비밀번호 복구를 사용할 수 없습니다. 관리자에게 문의하세요.",
"reset_f2b_regex": "정규식 필터를 제때 재설정하지 못했습니다. 다시 시도하거나 몇 초 더 기다렸다가 웹사이트를 다시 로드하세요.",
"template_exists": "템플릿 %s이(가) 이미 존재합니다.",
"template_id_invalid": "템플릿 ID %s가 잘못되었습니다.",
"template_name_invalid": "템플릿 이름이 잘못되었습니다.",
"tfa_token_invalid": "TFA 토큰이 유효하지 않습니다.",
"to_invalid": "수신자가 비어 있지 않아야 합니다.",
"webauthn_authenticator_failed": "선택한 인증기를 찾을 수 없습니다.",
"webauthn_username_failed": "선택한 인증기가 다른 계정에 속해 있습니다.",
"demo_mode_enabled": "데모 모드가 활성화됨",
"recovery_email_failed": "복구 이메일을 보낼 수 없습니다. 관리자에게 문의하세요.",
"password_reset_invalid_user": "사서함을 찾을 수 없거나 복구 이메일이 설정되어 있지 않습니다.",
"webauthn_publickey_failed": "선택한 인증기에 대한 공개 키가 저장되지 않았습니다.",
"fido2_verification_failed": "FIDO2 인증 실패: %s",
"extended_sender_acl_denied": "외부 발신자 주소를 설정하는 ACL 누락",
"img_dimensions_exceeded": "이미지가 최대 이미지 크기를 초과합니다.",
"reset_token_limit_exceeded": "토큰 재설정 한도를 초과했습니다. 나중에 다시 시도해 주세요.",
"cors_invalid_method": "잘못된 허용 메서드를 지정했습니다.",
"cors_invalid_origin": "잘못된 허용 원본을 지정했습니다."
}, },
"debug": { "debug": {
"chart_this_server": "Chart (this server)", "chart_this_server": "Chart (this server)",
@ -434,7 +509,24 @@
"uptime": "Uptime", "uptime": "Uptime",
"started_on": "Started on", "started_on": "Started on",
"static_logs": "Static logs", "static_logs": "Static logs",
"system_containers": "System & Containers" "system_containers": "System & Containers",
"current_time": "시스템 시간",
"no_update_available": "시스템이 최신 버전입니다.",
"architecture": "아키텍처",
"container_running": "실행 중",
"container_disabled": "컨테이너 중지 또는 비활성화",
"container_stopped": "중지됨",
"online_users": "온라인 사용자",
"service": "서비스",
"success": "성공",
"show_ip": "공인 IP 표시",
"timezone": "시간대",
"update_available": "사용 가능한 업데이트가 있습니다.",
"update_failed": "업데이트를 확인할 수 없습니다",
"username": "사용자 이름",
"memory": "메모리",
"error_show_ip": "공인 IP 주소를 확인할 수 없습니다",
"login_time": "시간"
}, },
"diagnostics": { "diagnostics": {
"cname_from_a": "Value derived from A/AAAA record. This is supported as long as the record points to the correct resource.", "cname_from_a": "Value derived from A/AAAA record. This is supported as long as the record points to the correct resource.",
@ -542,7 +634,13 @@
"title": "Edit object", "title": "Edit object",
"unchanged_if_empty": "If unchanged leave blank", "unchanged_if_empty": "If unchanged leave blank",
"username": "Username", "username": "Username",
"validate_save": "Validate and save" "validate_save": "Validate and save",
"allow_from_smtp": "다음 IP만 <b>SMTP</b>를 사용하도록 허용합니다.",
"allow_from_smtp_info": "모든 발신자를 허용하려면 비워둡니다.<br>IPv4/IPv6 주소 및 네트워크.",
"allowed_protocols": "허용된 프로토콜",
"app_passwd_protocols": "앱 비밀번호에 대해 허용되는 프로토콜",
"acl": "ACL (권한)",
"admin": "관리자 수정"
}, },
"footer": { "footer": {
"cancel": "Cancel", "cancel": "Cancel",
@ -560,13 +658,14 @@
"header": { "header": {
"administration": "Configuration & Details", "administration": "Configuration & Details",
"apps": "Apps", "apps": "Apps",
"debug": "System Information", "debug": "정보",
"email": "E-Mail", "email": "E-Mail",
"mailcow_config": "Configuration", "mailcow_config": "Configuration",
"quarantine": "Quarantine", "quarantine": "Quarantine",
"restart_netfilter": "Restart netfilter", "restart_netfilter": "Restart netfilter",
"restart_sogo": "Restart SOGo", "restart_sogo": "Restart SOGo",
"user_settings": "User Settings" "user_settings": "User Settings",
"mailcow_system": "시스템"
}, },
"info": { "info": {
"awaiting_tfa_confirmation": "Awaiting TFA confirmation", "awaiting_tfa_confirmation": "Awaiting TFA confirmation",
@ -1015,5 +1114,25 @@
"quota_exceeded_scope": "Domain quota exceeded: Only unlimited mailboxes can be created in this domain scope.", "quota_exceeded_scope": "Domain quota exceeded: Only unlimited mailboxes can be created in this domain scope.",
"session_token": "Form token invalid: Token mismatch", "session_token": "Form token invalid: Token mismatch",
"session_ua": "Form token invalid: User-Agent validation error" "session_ua": "Form token invalid: User-Agent validation error"
},
"datatables": {
"collapse_all": "모두 접기",
"decimal": ".",
"emptyTable": "테이블에 사용 가능한 데이터가 없습니다.",
"expand_all": "모두 펼치기",
"infoEmpty": "0개 항목 중 0개부터 0개까지 표시",
"infoFiltered": "(_MAX_ 총 항목에서 필터링됨)",
"thousands": ",",
"lengthMenu": "_MENU_ 항목 표시",
"loadingRecords": "로딩 중...",
"processing": "잠시만 기다려 주세요...",
"search": "검색:",
"zeroRecords": "일치하는 레코드가 없습니다.",
"paginate": {
"first": "처음",
"last": "마지막",
"next": "다음",
"previous": "이전"
}
} }
} }

View File

@ -117,6 +117,8 @@
<select data-style="btn btn-light btn-sm" class="form-control" id="key_size" name="key_size" title="{{ lang.admin.dkim_key_length }}" required> <select data-style="btn btn-light btn-sm" class="form-control" id="key_size" name="key_size" title="{{ lang.admin.dkim_key_length }}" required>
<option data-subtext="bits">1024</option> <option data-subtext="bits">1024</option>
<option data-subtext="bits">2048</option> <option data-subtext="bits">2048</option>
<option data-subtext="bits">3072</option>
<option data-subtext="bits">4096</option>
</select> </select>
</div> </div>
</div> </div>

View File

@ -103,6 +103,8 @@
<select data-style="btn btn-light" class="form-control" id="key_size" name="key_size"> <select data-style="btn btn-light" class="form-control" id="key_size" name="key_size">
<option value="1024" data-subtext="bits" {% if template.attributes.key_size == 1024 %} selected{% endif %}>1024</option> <option value="1024" data-subtext="bits" {% if template.attributes.key_size == 1024 %} selected{% endif %}>1024</option>
<option value="2048" data-subtext="bits" {% if template.attributes.key_size == 2048 %} selected{% endif %}>2048</option> <option value="2048" data-subtext="bits" {% if template.attributes.key_size == 2048 %} selected{% endif %}>2048</option>
<option value="3072" data-subtext="bits" {% if template.attributes.key_size == 3072 %} selected{% endif %}>3072</option>
<option value="4096" data-subtext="bits" {% if template.attributes.key_size == 4096 %} selected{% endif %}>4096</option>
</select> </select>
</div> </div>
</div> </div>

View File

@ -493,6 +493,8 @@
<select data-style="btn btn-light" class="form-control" id="key_size" name="key_size"> <select data-style="btn btn-light" class="form-control" id="key_size" name="key_size">
<option data-subtext="bits" value="1024">1024</option> <option data-subtext="bits" value="1024">1024</option>
<option data-subtext="bits" value="2048" selected>2048</option> <option data-subtext="bits" value="2048" selected>2048</option>
<option data-subtext="bits" value="3072">3072</option>
<option data-subtext="bits" value="4096">4096</option>
</select> </select>
</div> </div>
</div> </div>
@ -631,6 +633,8 @@
<select data-style="btn btn-light" class="form-control" id="key_size" name="key_size"> <select data-style="btn btn-light" class="form-control" id="key_size" name="key_size">
<option data-subtext="bits">1024</option> <option data-subtext="bits">1024</option>
<option data-subtext="bits" selected>2048</option> <option data-subtext="bits" selected>2048</option>
<option data-subtext="bits">3072</option>
<option data-subtext="bits">4096</option>
</select> </select>
</div> </div>
</div> </div>
@ -846,6 +850,8 @@
<select data-style="btn btn-light" class="form-control" id="key_size2" name="key_size"> <select data-style="btn btn-light" class="form-control" id="key_size2" name="key_size">
<option data-subtext="bits">1024</option> <option data-subtext="bits">1024</option>
<option data-subtext="bits" selected>2048</option> <option data-subtext="bits" selected>2048</option>
<option data-subtext="bits">3072</option>
<option data-subtext="bits">4096</option>
</select> </select>
</div> </div>
</div> </div>

View File

@ -1,7 +1,7 @@
services: services:
unbound-mailcow: unbound-mailcow:
image: ghcr.io/mailcow/unbound:1.23 image: ghcr.io/mailcow/unbound:1.24
environment: environment:
- TZ=${TZ} - TZ=${TZ}
- SKIP_UNBOUND_HEALTHCHECK=${SKIP_UNBOUND_HEALTHCHECK:-n} - SKIP_UNBOUND_HEALTHCHECK=${SKIP_UNBOUND_HEALTHCHECK:-n}
@ -84,7 +84,7 @@ services:
- clamd - clamd
rspamd-mailcow: rspamd-mailcow:
image: ghcr.io/mailcow/rspamd:2.0 image: ghcr.io/mailcow/rspamd:2.1
stop_grace_period: 30s stop_grace_period: 30s
depends_on: depends_on:
- dovecot-mailcow - dovecot-mailcow
@ -117,7 +117,7 @@ services:
- rspamd - rspamd
php-fpm-mailcow: php-fpm-mailcow:
image: ghcr.io/mailcow/phpfpm:1.92 image: ghcr.io/mailcow/phpfpm:1.93
command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
depends_on: depends_on:
- redis-mailcow - redis-mailcow
@ -199,7 +199,7 @@ services:
- phpfpm - phpfpm
sogo-mailcow: sogo-mailcow:
image: mailcow/sogo:nightly-20250224 image: ghcr.io/mailcow/sogo:1.131
environment: environment:
- DBNAME=${DBNAME} - DBNAME=${DBNAME}
- DBUSER=${DBUSER} - DBUSER=${DBUSER}
@ -250,7 +250,7 @@ services:
- sogo - sogo
dovecot-mailcow: dovecot-mailcow:
image: mailcow/dovecot:nightly-20250224 image: ghcr.io/mailcow/dovecot:2.33
depends_on: depends_on:
- mysql-mailcow - mysql-mailcow
- netfilter-mailcow - netfilter-mailcow
@ -439,7 +439,7 @@ services:
condition: service_started condition: service_started
unbound-mailcow: unbound-mailcow:
condition: service_healthy condition: service_healthy
image: ghcr.io/mailcow/acme:1.91 image: ghcr.io/mailcow/acme:1.92
dns: dns:
- ${IPV4_NETWORK:-172.22.1}.254 - ${IPV4_NETWORK:-172.22.1}.254
environment: environment:
@ -477,7 +477,7 @@ services:
- acme - acme
netfilter-mailcow: netfilter-mailcow:
image: ghcr.io/mailcow/netfilter:1.61 image: ghcr.io/mailcow/netfilter:1.62
stop_grace_period: 30s stop_grace_period: 30s
restart: always restart: always
privileged: true privileged: true
@ -497,7 +497,7 @@ services:
- /lib/modules:/lib/modules:ro - /lib/modules:/lib/modules:ro
watchdog-mailcow: watchdog-mailcow:
image: ghcr.io/mailcow/watchdog:2.06 image: ghcr.io/mailcow/watchdog:2.07
dns: dns:
- ${IPV4_NETWORK:-172.22.1}.254 - ${IPV4_NETWORK:-172.22.1}.254
tmpfs: tmpfs:
@ -569,7 +569,7 @@ services:
- watchdog - watchdog
dockerapi-mailcow: dockerapi-mailcow:
image: ghcr.io/mailcow/dockerapi:2.10 image: ghcr.io/mailcow/dockerapi:2.11
security_opt: security_opt:
- label=disable - label=disable
restart: always restart: always
@ -589,7 +589,7 @@ services:
- dockerapi - dockerapi
olefy-mailcow: olefy-mailcow:
image: ghcr.io/mailcow/olefy:1.13 image: ghcr.io/mailcow/olefy:1.14
restart: always restart: always
environment: environment:
- TZ=${TZ} - TZ=${TZ}

View File

@ -10,46 +10,6 @@ echo "If this script is run automatically by cron or a timer AND you are using b
echo "The snapshots of your backup destination should run AFTER the cold standby script finished to ensure consistent snapshots." echo "The snapshots of your backup destination should run AFTER the cold standby script finished to ensure consistent snapshots."
echo echo
function docker_garbage() {
IMGS_TO_DELETE=()
for container in $(grep -oP "image: \Kmailcow.+" docker-compose.yml); do
REPOSITORY=${container/:*}
TAG=${container/*:}
V_MAIN=${container/*.}
V_SUB=${container/*.}
EXISTING_TAGS=$(docker images | grep ${REPOSITORY} | awk '{ print $2 }')
for existing_tag in ${EXISTING_TAGS[@]}; do
V_MAIN_EXISTING=${existing_tag/*.}
V_SUB_EXISTING=${existing_tag/*.}
# Not an integer
[[ ! ${V_MAIN_EXISTING} =~ ^[0-9]+$ ]] && continue
[[ ! ${V_SUB_EXISTING} =~ ^[0-9]+$ ]] && continue
if [[ ${V_MAIN_EXISTING} == "latest" ]]; then
echo "Found deprecated label \"latest\" for repository ${REPOSITORY}, it should be deleted."
IMGS_TO_DELETE+=(${REPOSITORY}:${existing_tag})
elif [[ ${V_MAIN_EXISTING} -lt ${V_MAIN} ]]; then
echo "Found tag ${existing_tag} for ${REPOSITORY}, which is older than the current tag ${TAG} and should be deleted."
IMGS_TO_DELETE+=(${REPOSITORY}:${existing_tag})
elif [[ ${V_SUB_EXISTING} -lt ${V_SUB} ]]; then
echo "Found tag ${existing_tag} for ${REPOSITORY}, which is older than the current tag ${TAG} and should be deleted."
IMGS_TO_DELETE+=(${REPOSITORY}:${existing_tag})
fi
done
done
if [[ ! -z ${IMGS_TO_DELETE[*]} ]]; then
docker rmi ${IMGS_TO_DELETE[*]}
fi
}
function preflight_local_checks() { function preflight_local_checks() {
if [[ -z "${REMOTE_SSH_KEY}" ]]; then if [[ -z "${REMOTE_SSH_KEY}" ]]; then
>&2 echo -e "\e[31mREMOTE_SSH_KEY is not set\e[0m" >&2 echo -e "\e[31mREMOTE_SSH_KEY is not set\e[0m"
@ -139,11 +99,11 @@ EOF
if [ $? = 0 ]; then if [ $? = 0 ]; then
COMPOSE_COMMAND="docker compose" COMPOSE_COMMAND="docker compose"
echo "DEBUG: Using native docker compose on remote" echo "INFO: Using native docker compose on remote"
elif [ $? = 1 ]; then elif [ $? = 1 ]; then
COMPOSE_COMMAND="docker-compose" COMPOSE_COMMAND="docker-compose"
echo "DEBUG: Using standalone docker compose on remote" echo "INFO: Using standalone docker compose on remote"
else else
echo -e "\e[31mCannot find any Docker Compose on remote, exiting...\e[0m" echo -e "\e[31mCannot find any Docker Compose on remote, exiting...\e[0m"
@ -324,7 +284,7 @@ echo "OK"
-i "${REMOTE_SSH_KEY}" \ -i "${REMOTE_SSH_KEY}" \
${REMOTE_SSH_HOST} \ ${REMOTE_SSH_HOST} \
-p ${REMOTE_SSH_PORT} \ -p ${REMOTE_SSH_PORT} \
${COMPOSE_COMMAND} -f "${SCRIPT_DIR}/../docker-compose.yml" pull --no-parallel --quiet 2>&1 ; then ${COMPOSE_COMMAND} -f "${SCRIPT_DIR}/../docker-compose.yml" pull --quiet 2>&1 ; then
>&2 echo -e "\e[31m[ERR]\e[0m - Could not pull images on remote" >&2 echo -e "\e[31m[ERR]\e[0m - Could not pull images on remote"
fi fi

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
DEBIAN_DOCKER_IMAGE="mailcow/backup:latest" DEBIAN_DOCKER_IMAGE="ghcr.io/mailcow/backup:latest"
if [[ ! -z ${MAILCOW_BACKUP_LOCATION} ]]; then if [[ ! -z ${MAILCOW_BACKUP_LOCATION} ]]; then
BACKUP_LOCATION="${MAILCOW_BACKUP_LOCATION}" BACKUP_LOCATION="${MAILCOW_BACKUP_LOCATION}"

View File

@ -36,13 +36,19 @@ docker_garbage() {
IMGS_TO_DELETE=() IMGS_TO_DELETE=()
declare -A IMAGES_INFO declare -A IMAGES_INFO
COMPOSE_IMAGES=($(grep -oP "image: \Kmailcow.+" "${SCRIPT_DIR}/docker-compose.yml")) COMPOSE_IMAGES=($(grep -oP "image: \K(ghcr\.io/)?mailcow.+" "${SCRIPT_DIR}/docker-compose.yml"))
for existing_image in $(docker images --format "{{.ID}}:{{.Repository}}:{{.Tag}}" | grep 'mailcow/'); do for existing_image in $(docker images --format "{{.ID}}:{{.Repository}}:{{.Tag}}" | grep -E '(mailcow/|ghcr\.io/mailcow/)'); do
ID=$(echo "$existing_image" | cut -d ':' -f 1) ID=$(echo "$existing_image" | cut -d ':' -f 1)
REPOSITORY=$(echo "$existing_image" | cut -d ':' -f 2) REPOSITORY=$(echo "$existing_image" | cut -d ':' -f 2)
TAG=$(echo "$existing_image" | cut -d ':' -f 3) TAG=$(echo "$existing_image" | cut -d ':' -f 3)
if [[ "$REPOSITORY" == "mailcow/backup" || "$REPOSITORY" == "ghcr.io/mailcow/backup" ]]; then
if [[ "$TAG" != "<none>" ]]; then
continue
fi
fi
if [[ " ${COMPOSE_IMAGES[@]} " =~ " ${REPOSITORY}:${TAG} " ]]; then if [[ " ${COMPOSE_IMAGES[@]} " =~ " ${REPOSITORY}:${TAG} " ]]; then
continue continue
else else
@ -57,7 +63,7 @@ docker_garbage() {
echo " ${IMAGES_INFO[$id]} ($id)" echo " ${IMAGES_INFO[$id]} ($id)"
done done
if [ ! $FORCE ]; then if [ -z "$FORCE" ]; then
read -r -p "Do you want to delete them to free up some space? [y/N] " response read -r -p "Do you want to delete them to free up some space? [y/N] " response
if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
docker rmi ${IMGS_TO_DELETE[*]} docker rmi ${IMGS_TO_DELETE[*]}
@ -716,9 +722,16 @@ detect_major_update() {
# Add major versions here # Add major versions here
MAJOR_VERSIONS=( MAJOR_VERSIONS=(
"2025-02" "2025-02"
"2025-03"
) )
current_version=$(git describe --tags $(git rev-list --tags --max-count=1)) current_version=""
if [[ -f "${SCRIPT_DIR}/data/web/inc/app_info.inc.php" ]]; then
current_version=$(grep 'MAILCOW_GIT_VERSION' ${SCRIPT_DIR}/data/web/inc/app_info.inc.php | sed -E 's/.*MAILCOW_GIT_VERSION="([^"]+)".*/\1/')
fi
if [[ -z "$current_version" ]]; then
return 1
fi
release_url="https://github.com/mailcow/mailcow-dockerized/releases/tag" release_url="https://github.com/mailcow/mailcow-dockerized/releases/tag"
updates_to_apply=() updates_to_apply=()
@ -735,8 +748,7 @@ detect_major_update() {
echo "$update - $release_url/$update" echo "$update - $release_url/$update"
done done
echo -e "\n⚠ Please read the release notes before proceeding.\n" echo -e "\nPlease read the release notes before proceeding."
read -p "Do you want to proceed with the update? [y/n] " response read -p "Do you want to proceed with the update? [y/n] " response
if [[ "${response}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then if [[ "${response}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo "Proceeding with the update..." echo "Proceeding with the update..."