blob: d7fa1555626d6e6b5f3443cf6051a035895a54c6 [file] [log] [blame]
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +01001#!/bin/bash
2
3function array_by_comma { local IFS=","; echo "$*"; }
4
5# Wait for containers
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +02006while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +01007 echo "Waiting for SQL..."
8 sleep 2
9done
10
11# Do not attempt to write to slave
12if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020013 REDIS_HOST=$REDIS_SLAVEOF_IP
14 REDIS_PORT=$REDIS_SLAVEOF_PORT
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010015else
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020016 REDIS_HOST="redis"
17 REDIS_PORT="6379"
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010018fi
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020019REDIS_CMDLINE="redis-cli -h ${REDIS_HOST} -p ${REDIS_PORT} -a ${REDISPASS} --no-auth-warning"
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010020
21until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do
22 echo "Waiting for Redis..."
23 sleep 2
24done
25
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020026# Set redis session store
27echo -n '
28session.save_handler = redis
29session.save_path = "tcp://'${REDIS_HOST}':'${REDIS_PORT}'?auth='${REDISPASS}'"
30' > /usr/local/etc/php/conf.d/session_store.ini
31
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010032# Check mysql_upgrade (master and slave)
33CONTAINER_ID=
34until [[ ! -z "${CONTAINER_ID}" ]] && [[ "${CONTAINER_ID}" =~ ^[[:alnum:]]*$ ]]; do
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020035 CONTAINER_ID=$(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" 2> /dev/null | jq -rc "select( .name | tostring | contains(\"mysql-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" 2> /dev/null)
36 echo "Could not get mysql-mailcow container id... trying again"
Matthias Andreas Benkard8f915942021-08-16 10:57:25 +020037 sleep 2
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010038done
39echo "MySQL @ ${CONTAINER_ID}"
40SQL_LOOP_C=0
41SQL_CHANGED=0
42until [[ ${SQL_UPGRADE_STATUS} == 'success' ]]; do
43 if [ ${SQL_LOOP_C} -gt 4 ]; then
44 echo "Tried to upgrade MySQL and failed, giving up after ${SQL_LOOP_C} retries and starting container (oops, not good)"
45 break
46 fi
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020047 SQL_FULL_UPGRADE_RETURN=$(curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_upgrade"}' --silent -H 'Content-type: application/json')
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010048 SQL_UPGRADE_STATUS=$(echo ${SQL_FULL_UPGRADE_RETURN} | jq -r .type)
49 SQL_LOOP_C=$((SQL_LOOP_C+1))
50 echo "SQL upgrade iteration #${SQL_LOOP_C}"
51 if [[ ${SQL_UPGRADE_STATUS} == 'warning' ]]; then
52 SQL_CHANGED=1
53 echo "MySQL applied an upgrade, debug output:"
54 echo ${SQL_FULL_UPGRADE_RETURN}
55 sleep 3
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020056 while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010057 echo "Waiting for SQL to return, please wait"
58 sleep 2
59 done
60 continue
61 elif [[ ${SQL_UPGRADE_STATUS} == 'success' ]]; then
62 echo "MySQL is up-to-date - debug output:"
63 echo ${SQL_FULL_UPGRADE_RETURN}
64 else
65 echo "No valid reponse for mysql_upgrade was received, debug output:"
66 echo ${SQL_FULL_UPGRADE_RETURN}
67 fi
68done
69
70# doing post-installation stuff, if SQL was upgraded (master and slave)
71if [ ${SQL_CHANGED} -eq 1 ]; then
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020072 POSTFIX=$(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" 2> /dev/null | jq -rc "select( .name | tostring | contains(\"postfix-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" 2> /dev/null)
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010073 if [[ -z "${POSTFIX}" ]] || ! [[ "${POSTFIX}" =~ ^[[:alnum:]]*$ ]]; then
74 echo "Could not determine Postfix container ID, skipping Postfix restart."
75 else
76 echo "Restarting Postfix"
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020077 curl -X POST --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${POSTFIX}/restart | jq -r '.msg'
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010078 echo "Sleeping 5 seconds..."
79 sleep 5
80 fi
81fi
82
83# Check mysql tz import (master and slave)
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020084TZ_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)
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010085if [[ -z ${TZ_CHECK} ]] || [[ "${TZ_CHECK}" == "NULL" ]]; then
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +020086 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')
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +010087 echo "MySQL mysql_tzinfo_to_sql - debug output:"
88 echo ${SQL_FULL_TZINFO_IMPORT_RETURN}
89fi
90
91if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
92 echo "We are master, preparing..."
93 # Set a default release format
94 if [[ -z $(${REDIS_CMDLINE} --raw GET Q_RELEASE_FORMAT) ]]; then
95 ${REDIS_CMDLINE} --raw SET Q_RELEASE_FORMAT raw
96 fi
97
98 # Set max age of q items - if unset
99 if [[ -z $(${REDIS_CMDLINE} --raw GET Q_MAX_AGE) ]]; then
100 ${REDIS_CMDLINE} --raw SET Q_MAX_AGE 365
101 fi
102
Matthias Andreas Benkard8f915942021-08-16 10:57:25 +0200103 # Set default password policy - if unset
104 if [[ -z $(${REDIS_CMDLINE} --raw HGET PASSWD_POLICY length) ]]; then
105 ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY length 6
106 ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY chars 0
107 ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY special_chars 0
108 ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY lowerupper 0
109 ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY numbers 0
110 fi
111
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +0100112 # Trigger db init
113 echo "Running DB init..."
114 php -c /usr/local/etc/php -f /web/inc/init_db.inc.php
115
116 # Recreating domain map
117 echo "Rebuilding domain map in Redis..."
118 declare -a DOMAIN_ARR
119 ${REDIS_CMDLINE} DEL DOMAIN_MAP > /dev/null
120 while read line
121 do
122 DOMAIN_ARR+=("$line")
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +0200123 done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs)
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +0100124 while read line
125 do
126 DOMAIN_ARR+=("$line")
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +0200127 done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs)
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +0100128
129 if [[ ! -z ${DOMAIN_ARR} ]]; then
130 for domain in "${DOMAIN_ARR[@]}"; do
131 ${REDIS_CMDLINE} HSET DOMAIN_MAP ${domain} 1 > /dev/null
132 done
133 fi
134
135 # Set API options if env vars are not empty
136 if [[ ${API_ALLOW_FROM} != "invalid" ]] && [[ ! -z ${API_ALLOW_FROM} ]]; then
137 IFS=',' read -r -a API_ALLOW_FROM_ARR <<< "${API_ALLOW_FROM}"
138 declare -a VALIDATED_API_ALLOW_FROM_ARR
139 REGEX_IP6='^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$'
140 REGEX_IP4='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/([0-9]|[1-2][0-9]|3[0-2]))?$'
141 for IP in "${API_ALLOW_FROM_ARR[@]}"; do
142 if [[ ${IP} =~ ${REGEX_IP6} ]] || [[ ${IP} =~ ${REGEX_IP4} ]]; then
143 VALIDATED_API_ALLOW_FROM_ARR+=("${IP}")
144 fi
145 done
146 VALIDATED_IPS=$(array_by_comma ${VALIDATED_API_ALLOW_FROM_ARR[*]})
147 if [[ ! -z ${VALIDATED_IPS} ]]; then
148 if [[ ${API_KEY} != "invalid" ]] && [[ ! -z ${API_KEY} ]]; then
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +0200149 mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +0100150DELETE FROM api WHERE access = 'rw';
151INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY}", "1", "${VALIDATED_IPS}", "rw");
152EOF
153 fi
154 if [[ ${API_KEY_READ_ONLY} != "invalid" ]] && [[ ! -z ${API_KEY_READ_ONLY} ]]; then
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +0200155 mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +0100156DELETE FROM api WHERE access = 'ro';
157INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY_READ_ONLY}", "1", "${VALIDATED_IPS}", "ro");
158EOF
159 fi
160 fi
161 fi
162
163 # Create events (master only, STATUS for event on slave will be SLAVESIDE_DISABLED)
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +0200164 mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +0100165DROP EVENT IF EXISTS clean_spamalias;
166DELIMITER //
167CREATE EVENT clean_spamalias
168ON SCHEDULE EVERY 1 DAY DO
169BEGIN
Matthias Andreas Benkard97f12022026-10-23 08:46:40 +0200170 DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP() AND permanent = 0;
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +0100171END;
172//
173DELIMITER ;
174DROP EVENT IF EXISTS clean_oauth2;
175DELIMITER //
176CREATE EVENT clean_oauth2
177ON SCHEDULE EVERY 1 DAY DO
178BEGIN
179 DELETE FROM oauth_refresh_tokens WHERE expires < NOW();
180 DELETE FROM oauth_access_tokens WHERE expires < NOW();
181 DELETE FROM oauth_authorization_codes WHERE expires < NOW();
182END;
183//
184DELIMITER ;
Matthias Andreas Benkardd63fe562023-11-18 13:18:30 +0100185DROP EVENT IF EXISTS clean_sasl_log;
186DELIMITER //
187CREATE EVENT clean_sasl_log
188ON SCHEDULE EVERY 1 DAY DO
189BEGIN
190 DELETE sasl_log.* FROM sasl_log
191 LEFT JOIN (
192 SELECT username, service, MAX(datetime) AS lastdate
193 FROM sasl_log
194 GROUP BY username, service
195 ) AS last ON sasl_log.username = last.username AND sasl_log.service = last.service
196 WHERE datetime < DATE_SUB(NOW(), INTERVAL 31 DAY) AND datetime < lastdate;
197 DELETE FROM sasl_log
198 WHERE username NOT IN (SELECT username FROM mailbox) AND
199 datetime < DATE_SUB(NOW(), INTERVAL 31 DAY);
200END;
201//
202DELIMITER ;
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +0100203EOF
204fi
205
206# Create dummy for custom overrides of mailcow style
207[[ ! -f /web/css/build/0081-custom-mailcow.css ]] && echo '/* Autogenerated by mailcow */' > /web/css/build/0081-custom-mailcow.css
208
209# Fix permissions for global filters
210chown -R 82:82 /global_sieve/*
211
Matthias Andreas Benkardb4b5cc12021-12-28 18:02:04 +0100212# Fix permissions on twig cache folder
213chown -R 82:82 /web/templates/cache
214# Clear cache
215find /web/templates/cache/* -not -name '.gitkeep' -delete
Matthias Andreas Benkard8f915942021-08-16 10:57:25 +0200216
Matthias Andreas Benkard4edeb9f2021-01-02 15:32:21 +0100217# Run hooks
218for file in /hooks/*; do
219 if [ -x "${file}" ]; then
220 echo "Running hook ${file}"
221 "${file}"
222 fi
223done
224
225exec "$@"