mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2025-12-20 13:21:30 +00:00
[PHP-FPM] use python bootstrapper to start PHP-FPM container
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -31,6 +31,11 @@ data/conf/nginx/*.bak
|
||||
data/conf/nginx/*.conf
|
||||
data/conf/nginx/*.custom
|
||||
data/conf/phpfpm/sogo-sso/sogo-sso.pass
|
||||
data/conf/phpfpm/opcache-recommended.ini
|
||||
data/conf/phpfpm/other.ini
|
||||
data/conf/phpfpm/pools.conf
|
||||
data/conf/phpfpm/session_store.ini
|
||||
data/conf/phpfpm/upload.ini
|
||||
data/conf/portainer/
|
||||
data/conf/postfix/allow_mailcow_local.regexp
|
||||
data/conf/postfix/custom_postscreen_whitelist.cidr
|
||||
|
||||
@@ -25,6 +25,8 @@ def main():
|
||||
from modules.BootstrapClamd import Bootstrap
|
||||
elif container_name == "mysql-mailcow":
|
||||
from modules.BootstrapMysql import Bootstrap
|
||||
elif container_name == "php-fpm-mailcow":
|
||||
from modules.BootstrapPhpfpm import Bootstrap
|
||||
else:
|
||||
print(f"No bootstrap handler for container: {container_name}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
207
data/Dockerfiles/bootstrap/modules/BootstrapPhpfpm.py
Normal file
207
data/Dockerfiles/bootstrap/modules/BootstrapPhpfpm.py
Normal file
@@ -0,0 +1,207 @@
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from modules.BootstrapBase import BootstrapBase
|
||||
from pathlib import Path
|
||||
import os
|
||||
import ipaddress
|
||||
import sys
|
||||
import time
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
class Bootstrap(BootstrapBase):
|
||||
def bootstrap(self):
|
||||
self.connect_mysql()
|
||||
self.connect_redis()
|
||||
|
||||
# Setup Jinja2 Environment and load vars
|
||||
self.env = Environment(
|
||||
loader=FileSystemLoader('/php-conf/config_templates'),
|
||||
keep_trailing_newline=True,
|
||||
lstrip_blocks=True,
|
||||
trim_blocks=True
|
||||
)
|
||||
extra_vars = {
|
||||
}
|
||||
self.env_vars = self.prepare_template_vars('/overwrites.json', extra_vars)
|
||||
|
||||
print("Set Timezone")
|
||||
self.set_timezone()
|
||||
|
||||
# Prepare Redis and MySQL Database
|
||||
# TODO: move to dockerapi
|
||||
if self.isYes(os.getenv("MASTER", "")):
|
||||
print("We are master, preparing...")
|
||||
self.prepare_redis()
|
||||
self.setup_apikeys(
|
||||
os.getenv("API_ALLOW_FROM", "").strip(),
|
||||
os.getenv("API_KEY", "").strip(),
|
||||
os.getenv("API_KEY_READ_ONLY", "").strip()
|
||||
)
|
||||
self.setup_mysql_events()
|
||||
|
||||
|
||||
print("Render config")
|
||||
self.render_config("opcache-recommended.ini.j2", "/usr/local/etc/php/conf.d/opcache-recommended.ini")
|
||||
self.render_config("pools.conf.j2", "/usr/local/etc/php-fpm.d/z-pools.conf")
|
||||
self.render_config("other.ini.j2", "/usr/local/etc/php/conf.d/zzz-other.ini")
|
||||
self.render_config("upload.ini.j2", "/usr/local/etc/php/conf.d/upload.ini")
|
||||
self.render_config("session_store.ini.j2", "/usr/local/etc/php/conf.d/session_store.ini")
|
||||
self.render_config("0081-custom-mailcow.css.j2", "/web/css/build/0081-custom-mailcow.css")
|
||||
|
||||
self.copy_file("/usr/local/etc/php/conf.d/opcache-recommended.ini", "/php-conf/opcache-recommended.ini")
|
||||
self.copy_file("/usr/local/etc/php-fpm.d/z-pools.conf", "/php-conf/pools.conf")
|
||||
self.copy_file("/usr/local/etc/php/conf.d/zzz-other.ini", "/php-conf/other.ini")
|
||||
self.copy_file("/usr/local/etc/php/conf.d/upload.ini", "/php-conf/upload.ini")
|
||||
self.copy_file("/usr/local/etc/php/conf.d/session_store.ini", "/php-conf/session_store.ini")
|
||||
|
||||
self.set_owner("/global_sieve", 82, 82, recursive=True)
|
||||
self.set_owner("/web/templates/cache", 82, 82, recursive=True)
|
||||
self.remove("/web/templates/cache", wipe_contents=True, exclude=[".gitkeep"])
|
||||
|
||||
print("Running DB init...")
|
||||
self.run_command(["php", "-c", "/usr/local/etc/php", "-f", "/web/inc/init_db.inc.php"], check=False)
|
||||
|
||||
def prepare_redis(self):
|
||||
print("Setting default Redis keys if missing...")
|
||||
|
||||
# Q_RELEASE_FORMAT
|
||||
if self.redis_conn.get("Q_RELEASE_FORMAT") is None:
|
||||
self.redis_conn.set("Q_RELEASE_FORMAT", "raw")
|
||||
|
||||
# Q_MAX_AGE
|
||||
if self.redis_conn.get("Q_MAX_AGE") is None:
|
||||
self.redis_conn.set("Q_MAX_AGE", 365)
|
||||
|
||||
# PASSWD_POLICY hash defaults
|
||||
if self.redis_conn.hget("PASSWD_POLICY", "length") is None:
|
||||
self.redis_conn.hset("PASSWD_POLICY", mapping={
|
||||
"length": 6,
|
||||
"chars": 0,
|
||||
"special_chars": 0,
|
||||
"lowerupper": 0,
|
||||
"numbers": 0
|
||||
})
|
||||
|
||||
# DOMAIN_MAP
|
||||
print("Rebuilding DOMAIN_MAP from MySQL...")
|
||||
self.redis_conn.delete("DOMAIN_MAP")
|
||||
domains = set()
|
||||
try:
|
||||
cursor = self.mysql_conn.cursor()
|
||||
|
||||
cursor.execute("SELECT domain FROM domain")
|
||||
domains.update(row[0] for row in cursor.fetchall())
|
||||
cursor.execute("SELECT alias_domain FROM alias_domain")
|
||||
domains.update(row[0] for row in cursor.fetchall())
|
||||
|
||||
cursor.close()
|
||||
|
||||
if domains:
|
||||
for domain in domains:
|
||||
self.redis_conn.hset("DOMAIN_MAP", domain, 1)
|
||||
print(f"{len(domains)} domains added to DOMAIN_MAP.")
|
||||
else:
|
||||
print("No domains found to insert into DOMAIN_MAP.")
|
||||
except Exception as e:
|
||||
print(f"Failed to rebuild DOMAIN_MAP: {e}")
|
||||
|
||||
def setup_apikeys(self, api_allow_from, api_key_rw, api_key_ro):
|
||||
if not api_allow_from or api_allow_from == "invalid":
|
||||
return
|
||||
|
||||
print("Validating API_ALLOW_FROM IPs...")
|
||||
ip_list = [ip.strip() for ip in api_allow_from.split(",")]
|
||||
validated_ips = []
|
||||
|
||||
for ip in ip_list:
|
||||
try:
|
||||
ipaddress.ip_network(ip, strict=False)
|
||||
validated_ips.append(ip)
|
||||
except ValueError:
|
||||
continue
|
||||
if not validated_ips:
|
||||
print("No valid IPs found in API_ALLOW_FROM")
|
||||
return
|
||||
|
||||
allow_from_str = ",".join(validated_ips)
|
||||
cursor = self.mysql_conn.cursor()
|
||||
try:
|
||||
if api_key_rw and api_key_rw != "invalid":
|
||||
print("Setting RW API key...")
|
||||
cursor.execute("DELETE FROM api WHERE access = 'rw'")
|
||||
cursor.execute(
|
||||
"INSERT INTO api (api_key, active, allow_from, access) VALUES (%s, %s, %s, %s)",
|
||||
(api_key_rw, 1, allow_from_str, "rw")
|
||||
)
|
||||
|
||||
if api_key_ro and api_key_ro != "invalid":
|
||||
print("Setting RO API key...")
|
||||
cursor.execute("DELETE FROM api WHERE access = 'ro'")
|
||||
cursor.execute(
|
||||
"INSERT INTO api (api_key, active, allow_from, access) VALUES (%s, %s, %s, %s)",
|
||||
(api_key_ro, 1, allow_from_str, "ro")
|
||||
)
|
||||
|
||||
self.mysql_conn.commit()
|
||||
print("API key(s) set successfully.")
|
||||
except Exception as e:
|
||||
print(f"Failed to configure API keys: {e}")
|
||||
self.mysql_conn.rollback()
|
||||
finally:
|
||||
cursor.close()
|
||||
|
||||
def setup_mysql_events(self):
|
||||
print("Creating scheduled MySQL EVENTS...")
|
||||
|
||||
queries = [
|
||||
"DROP EVENT IF EXISTS clean_spamalias;",
|
||||
"""
|
||||
CREATE EVENT clean_spamalias
|
||||
ON SCHEDULE EVERY 1 DAY
|
||||
DO
|
||||
DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP();
|
||||
""",
|
||||
"DROP EVENT IF EXISTS clean_oauth2;",
|
||||
"""
|
||||
CREATE EVENT clean_oauth2
|
||||
ON SCHEDULE EVERY 1 DAY
|
||||
DO
|
||||
BEGIN
|
||||
DELETE FROM oauth_refresh_tokens WHERE expires < NOW();
|
||||
DELETE FROM oauth_access_tokens WHERE expires < NOW();
|
||||
DELETE FROM oauth_authorization_codes WHERE expires < NOW();
|
||||
END;
|
||||
""",
|
||||
"DROP EVENT IF EXISTS clean_sasl_log;",
|
||||
"""
|
||||
CREATE EVENT clean_sasl_log
|
||||
ON SCHEDULE EVERY 1 DAY
|
||||
DO
|
||||
BEGIN
|
||||
DELETE sasl_log.* FROM sasl_log
|
||||
LEFT JOIN (
|
||||
SELECT username, service, MAX(datetime) AS lastdate
|
||||
FROM sasl_log
|
||||
GROUP BY username, service
|
||||
) AS last
|
||||
ON sasl_log.username = last.username AND sasl_log.service = last.service
|
||||
WHERE datetime < DATE_SUB(NOW(), INTERVAL 31 DAY)
|
||||
AND datetime < lastdate;
|
||||
|
||||
DELETE FROM sasl_log
|
||||
WHERE username NOT IN (SELECT username FROM mailbox)
|
||||
AND datetime < DATE_SUB(NOW(), INTERVAL 31 DAY);
|
||||
END;
|
||||
"""
|
||||
]
|
||||
|
||||
try:
|
||||
cursor = self.mysql_conn.cursor()
|
||||
for query in queries:
|
||||
cursor.execute(query)
|
||||
self.mysql_conn.commit()
|
||||
cursor.close()
|
||||
print("MySQL EVENTS created successfully.")
|
||||
except Exception as e:
|
||||
print(f"Failed to create MySQL EVENTS: {e}")
|
||||
self.mysql_conn.rollback()
|
||||
@@ -63,6 +63,7 @@ RUN apk add -U --no-cache autoconf \
|
||||
samba-client \
|
||||
zlib-dev \
|
||||
tzdata \
|
||||
python3 py3-pip \
|
||||
&& pecl install APCu-${APCU_PECL_VERSION} \
|
||||
&& pecl install imagick-${IMAGICK_PECL_VERSION} \
|
||||
&& pecl install mailparse-${MAILPARSE_PECL_VERSION} \
|
||||
@@ -72,7 +73,7 @@ RUN apk add -U --no-cache autoconf \
|
||||
&& pecl clear-cache \
|
||||
&& docker-php-ext-configure intl \
|
||||
&& docker-php-ext-configure exif \
|
||||
&& docker-php-ext-configure gd --with-freetype=/usr/include/ \
|
||||
&& docker-php-ext-configure gd --with-freetype=/usr/include/ \
|
||||
--with-jpeg=/usr/include/ \
|
||||
--with-webp \
|
||||
--with-xpm \
|
||||
@@ -107,8 +108,18 @@ RUN apk add -U --no-cache autoconf \
|
||||
pcre-dev \
|
||||
zlib-dev
|
||||
|
||||
COPY ./docker-entrypoint.sh /
|
||||
RUN pip install --break-system-packages \
|
||||
mysql-connector-python \
|
||||
jinja2 \
|
||||
redis \
|
||||
dnspython
|
||||
|
||||
|
||||
COPY data/Dockerfiles/bootstrap /bootstrap
|
||||
COPY data/Dockerfiles/phpfpm/docker-entrypoint.sh /
|
||||
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
CMD ["php-fpm"]
|
||||
|
||||
@@ -1,219 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
function array_by_comma { local IFS=","; echo "$*"; }
|
||||
|
||||
# Wait for containers
|
||||
while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
|
||||
echo "Waiting for SQL..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Do not attempt to write to slave
|
||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
||||
REDIS_HOST=$REDIS_SLAVEOF_IP
|
||||
REDIS_PORT=$REDIS_SLAVEOF_PORT
|
||||
else
|
||||
REDIS_HOST="redis"
|
||||
REDIS_PORT="6379"
|
||||
fi
|
||||
REDIS_CMDLINE="redis-cli -h ${REDIS_HOST} -p ${REDIS_PORT} -a ${REDISPASS} --no-auth-warning"
|
||||
|
||||
until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do
|
||||
echo "Waiting for Redis..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Set redis session store
|
||||
echo -n '
|
||||
session.save_handler = redis
|
||||
session.save_path = "tcp://'${REDIS_HOST}':'${REDIS_PORT}'?auth='${REDISPASS}'"
|
||||
' > /usr/local/etc/php/conf.d/session_store.ini
|
||||
|
||||
# Check mysql_upgrade (master and slave)
|
||||
CONTAINER_ID=
|
||||
until [[ ! -z "${CONTAINER_ID}" ]] && [[ "${CONTAINER_ID}" =~ ^[[:alnum:]]*$ ]]; do
|
||||
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)
|
||||
echo "Could not get mysql-mailcow container id... trying again"
|
||||
sleep 2
|
||||
done
|
||||
echo "MySQL @ ${CONTAINER_ID}"
|
||||
SQL_LOOP_C=0
|
||||
SQL_CHANGED=0
|
||||
until [[ ${SQL_UPGRADE_STATUS} == 'success' ]]; do
|
||||
if [ ${SQL_LOOP_C} -gt 4 ]; then
|
||||
echo "Tried to upgrade MySQL and failed, giving up after ${SQL_LOOP_C} retries and starting container (oops, not good)"
|
||||
break
|
||||
fi
|
||||
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')
|
||||
SQL_UPGRADE_STATUS=$(echo ${SQL_FULL_UPGRADE_RETURN} | jq -r .type)
|
||||
SQL_LOOP_C=$((SQL_LOOP_C+1))
|
||||
echo "SQL upgrade iteration #${SQL_LOOP_C}"
|
||||
if [[ ${SQL_UPGRADE_STATUS} == 'warning' ]]; then
|
||||
SQL_CHANGED=1
|
||||
echo "MySQL applied an upgrade, debug output:"
|
||||
echo ${SQL_FULL_UPGRADE_RETURN}
|
||||
sleep 3
|
||||
while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
|
||||
echo "Waiting for SQL to return, please wait"
|
||||
sleep 2
|
||||
done
|
||||
continue
|
||||
elif [[ ${SQL_UPGRADE_STATUS} == 'success' ]]; then
|
||||
echo "MySQL is up-to-date - debug output:"
|
||||
echo ${SQL_FULL_UPGRADE_RETURN}
|
||||
else
|
||||
echo "No valid reponse for mysql_upgrade was received, debug output:"
|
||||
echo ${SQL_FULL_UPGRADE_RETURN}
|
||||
fi
|
||||
done
|
||||
|
||||
# doing post-installation stuff, if SQL was upgraded (master and slave)
|
||||
if [ ${SQL_CHANGED} -eq 1 ]; then
|
||||
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)
|
||||
if [[ -z "${POSTFIX}" ]] || ! [[ "${POSTFIX}" =~ ^[[:alnum:]]*$ ]]; then
|
||||
echo "Could not determine Postfix container ID, skipping Postfix restart."
|
||||
else
|
||||
echo "Restarting Postfix"
|
||||
curl -X POST --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${POSTFIX}/restart | jq -r '.msg'
|
||||
echo "Sleeping 5 seconds..."
|
||||
sleep 5
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check mysql tz import (master and slave)
|
||||
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
|
||||
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 ${SQL_FULL_TZINFO_IMPORT_RETURN}
|
||||
fi
|
||||
|
||||
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
echo "We are master, preparing..."
|
||||
# Set a default release format
|
||||
if [[ -z $(${REDIS_CMDLINE} --raw GET Q_RELEASE_FORMAT) ]]; then
|
||||
${REDIS_CMDLINE} --raw SET Q_RELEASE_FORMAT raw
|
||||
fi
|
||||
|
||||
# Set max age of q items - if unset
|
||||
if [[ -z $(${REDIS_CMDLINE} --raw GET Q_MAX_AGE) ]]; then
|
||||
${REDIS_CMDLINE} --raw SET Q_MAX_AGE 365
|
||||
fi
|
||||
|
||||
# Set default password policy - if unset
|
||||
if [[ -z $(${REDIS_CMDLINE} --raw HGET PASSWD_POLICY length) ]]; then
|
||||
${REDIS_CMDLINE} --raw HSET PASSWD_POLICY length 6
|
||||
${REDIS_CMDLINE} --raw HSET PASSWD_POLICY chars 0
|
||||
${REDIS_CMDLINE} --raw HSET PASSWD_POLICY special_chars 0
|
||||
${REDIS_CMDLINE} --raw HSET PASSWD_POLICY lowerupper 0
|
||||
${REDIS_CMDLINE} --raw HSET PASSWD_POLICY numbers 0
|
||||
fi
|
||||
|
||||
# Trigger db init
|
||||
echo "Running DB init..."
|
||||
php -c /usr/local/etc/php -f /web/inc/init_db.inc.php
|
||||
|
||||
# Recreating domain map
|
||||
echo "Rebuilding domain map in Redis..."
|
||||
declare -a DOMAIN_ARR
|
||||
${REDIS_CMDLINE} DEL DOMAIN_MAP > /dev/null
|
||||
while read line
|
||||
do
|
||||
DOMAIN_ARR+=("$line")
|
||||
done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs)
|
||||
while read line
|
||||
do
|
||||
DOMAIN_ARR+=("$line")
|
||||
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
|
||||
for domain in "${DOMAIN_ARR[@]}"; do
|
||||
${REDIS_CMDLINE} HSET DOMAIN_MAP ${domain} 1 > /dev/null
|
||||
done
|
||||
fi
|
||||
|
||||
# Set API options if env vars are not empty
|
||||
if [[ ${API_ALLOW_FROM} != "invalid" ]] && [[ ! -z ${API_ALLOW_FROM} ]]; then
|
||||
IFS=',' read -r -a API_ALLOW_FROM_ARR <<< "${API_ALLOW_FROM}"
|
||||
declare -a VALIDATED_API_ALLOW_FROM_ARR
|
||||
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]))?$'
|
||||
REGEX_IP4='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/([0-9]|[1-2][0-9]|3[0-2]))?$'
|
||||
for IP in "${API_ALLOW_FROM_ARR[@]}"; do
|
||||
if [[ ${IP} =~ ${REGEX_IP6} ]] || [[ ${IP} =~ ${REGEX_IP4} ]]; then
|
||||
VALIDATED_API_ALLOW_FROM_ARR+=("${IP}")
|
||||
fi
|
||||
done
|
||||
VALIDATED_IPS=$(array_by_comma ${VALIDATED_API_ALLOW_FROM_ARR[*]})
|
||||
if [[ ! -z ${VALIDATED_IPS} ]]; then
|
||||
if [[ ${API_KEY} != "invalid" ]] && [[ ! -z ${API_KEY} ]]; then
|
||||
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
|
||||
DELETE FROM api WHERE access = 'rw';
|
||||
INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY}", "1", "${VALIDATED_IPS}", "rw");
|
||||
EOF
|
||||
fi
|
||||
if [[ ${API_KEY_READ_ONLY} != "invalid" ]] && [[ ! -z ${API_KEY_READ_ONLY} ]]; then
|
||||
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
|
||||
DELETE FROM api WHERE access = 'ro';
|
||||
INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY_READ_ONLY}", "1", "${VALIDATED_IPS}", "ro");
|
||||
EOF
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create events (master only, STATUS for event on slave will be SLAVESIDE_DISABLED)
|
||||
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
|
||||
DROP EVENT IF EXISTS clean_spamalias;
|
||||
DELIMITER //
|
||||
CREATE EVENT clean_spamalias
|
||||
ON SCHEDULE EVERY 1 DAY DO
|
||||
BEGIN
|
||||
DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP();
|
||||
END;
|
||||
//
|
||||
DELIMITER ;
|
||||
DROP EVENT IF EXISTS clean_oauth2;
|
||||
DELIMITER //
|
||||
CREATE EVENT clean_oauth2
|
||||
ON SCHEDULE EVERY 1 DAY DO
|
||||
BEGIN
|
||||
DELETE FROM oauth_refresh_tokens WHERE expires < NOW();
|
||||
DELETE FROM oauth_access_tokens WHERE expires < NOW();
|
||||
DELETE FROM oauth_authorization_codes WHERE expires < NOW();
|
||||
END;
|
||||
//
|
||||
DELIMITER ;
|
||||
DROP EVENT IF EXISTS clean_sasl_log;
|
||||
DELIMITER //
|
||||
CREATE EVENT clean_sasl_log
|
||||
ON SCHEDULE EVERY 1 DAY DO
|
||||
BEGIN
|
||||
DELETE sasl_log.* FROM sasl_log
|
||||
LEFT JOIN (
|
||||
SELECT username, service, MAX(datetime) AS lastdate
|
||||
FROM sasl_log
|
||||
GROUP BY username, service
|
||||
) AS last ON sasl_log.username = last.username AND sasl_log.service = last.service
|
||||
WHERE datetime < DATE_SUB(NOW(), INTERVAL 31 DAY) AND datetime < lastdate;
|
||||
DELETE FROM sasl_log
|
||||
WHERE username NOT IN (SELECT username FROM mailbox) AND
|
||||
datetime < DATE_SUB(NOW(), INTERVAL 31 DAY);
|
||||
END;
|
||||
//
|
||||
DELIMITER ;
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Create dummy for custom overrides of mailcow style
|
||||
[[ ! -f /web/css/build/0081-custom-mailcow.css ]] && echo '/* Autogenerated by mailcow */' > /web/css/build/0081-custom-mailcow.css
|
||||
|
||||
# Fix permissions for global filters
|
||||
chown -R 82:82 /global_sieve/*
|
||||
|
||||
# Fix permissions on twig cache folder
|
||||
chown -R 82:82 /web/templates/cache
|
||||
# Clear cache
|
||||
find /web/templates/cache/* -not -name '.gitkeep' -delete
|
||||
|
||||
# Run hooks
|
||||
for file in /hooks/*; do
|
||||
if [ -x "${file}" ]; then
|
||||
@@ -222,4 +8,13 @@ for file in /hooks/*; do
|
||||
fi
|
||||
done
|
||||
|
||||
python3 -u /bootstrap/main.py
|
||||
BOOTSTRAP_EXIT_CODE=$?
|
||||
|
||||
if [ $BOOTSTRAP_EXIT_CODE -ne 0 ]; then
|
||||
echo "Bootstrap failed with exit code $BOOTSTRAP_EXIT_CODE. Not starting PHP-FPM."
|
||||
exit $BOOTSTRAP_EXIT_CODE
|
||||
fi
|
||||
|
||||
echo "Bootstrap succeeded. Starting PHP-FPM..."
|
||||
exec "$@"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
/*
|
||||
Custom styling for mailcow UI
|
||||
*/
|
||||
2
data/conf/phpfpm/config_templates/session_store.ini.j2
Normal file
2
data/conf/phpfpm/config_templates/session_store.ini.j2
Normal file
@@ -0,0 +1,2 @@
|
||||
session.save_handler = redis
|
||||
session.save_path = "tcp://{{ REDIS_SLAVEOF_IP or 'redis-mailcow' }}:{{ REDIS_SLAVEOF_PORT or '6379' }}?auth={{ REDISPASS }}"
|
||||
@@ -131,7 +131,7 @@ services:
|
||||
- rspamd
|
||||
|
||||
php-fpm-mailcow:
|
||||
image: ghcr.io/mailcow/phpfpm:1.93
|
||||
image: ghcr.io/mailcow/phpfpm:nightly-19052025
|
||||
command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
|
||||
depends_on:
|
||||
- redis-mailcow
|
||||
@@ -151,12 +151,8 @@ services:
|
||||
- mysql-socket-vol-1:/var/run/mysqld/
|
||||
- ./data/conf/sogo/:/etc/sogo/:z
|
||||
- ./data/conf/rspamd/meta_exporter:/meta_exporter:ro,z
|
||||
- ./data/conf/phpfpm/crons:/crons:z
|
||||
- ./data/conf/phpfpm:/php-conf:z
|
||||
- ./data/conf/phpfpm/sogo-sso/:/etc/sogo-sso/:z
|
||||
- ./data/conf/phpfpm/php-fpm.d/pools.conf:/usr/local/etc/php-fpm.d/z-pools.conf:Z
|
||||
- ./data/conf/phpfpm/php-conf.d/opcache-recommended.ini:/usr/local/etc/php/conf.d/opcache-recommended.ini:Z
|
||||
- ./data/conf/phpfpm/php-conf.d/upload.ini:/usr/local/etc/php/conf.d/upload.ini:Z
|
||||
- ./data/conf/phpfpm/php-conf.d/other.ini:/usr/local/etc/php/conf.d/zzz-other.ini:Z
|
||||
- ./data/conf/dovecot/global_sieve_before:/global_sieve/before:z
|
||||
- ./data/conf/dovecot/global_sieve_after:/global_sieve/after:z
|
||||
- ./data/assets/templates:/tpls:z
|
||||
@@ -164,6 +160,7 @@ services:
|
||||
dns:
|
||||
- ${IPV4_NETWORK:-172.22.1}.254
|
||||
environment:
|
||||
- CONTAINER_NAME=php-fpm-mailcow
|
||||
- REDIS_SLAVEOF_IP=${REDIS_SLAVEOF_IP:-}
|
||||
- REDIS_SLAVEOF_PORT=${REDIS_SLAVEOF_PORT:-}
|
||||
- REDISPASS=${REDISPASS}
|
||||
@@ -204,10 +201,10 @@ services:
|
||||
ofelia.enabled: "true"
|
||||
ofelia.job-exec.phpfpm_keycloak_sync.schedule: "@every 1m"
|
||||
ofelia.job-exec.phpfpm_keycloak_sync.no-overlap: "true"
|
||||
ofelia.job-exec.phpfpm_keycloak_sync.command: "/bin/bash -c \"php /crons/keycloak-sync.php || exit 0\""
|
||||
ofelia.job-exec.phpfpm_keycloak_sync.command: "/bin/bash -c \"php /php-conf/crons/keycloak-sync.php || exit 0\""
|
||||
ofelia.job-exec.phpfpm_ldap_sync.schedule: "@every 1m"
|
||||
ofelia.job-exec.phpfpm_ldap_sync.no-overlap: "true"
|
||||
ofelia.job-exec.phpfpm_ldap_sync.command: "/bin/bash -c \"php /crons/ldap-sync.php || exit 0\""
|
||||
ofelia.job-exec.phpfpm_ldap_sync.command: "/bin/bash -c \"php /php-conf/crons/ldap-sync.php || exit 0\""
|
||||
networks:
|
||||
mailcow-network:
|
||||
aliases:
|
||||
|
||||
Reference in New Issue
Block a user