diff --git a/.gitignore b/.gitignore index 5dcaa9496..9a86b330c 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/data/Dockerfiles/bootstrap/main.py b/data/Dockerfiles/bootstrap/main.py index 7ce2cce36..af18f7036 100644 --- a/data/Dockerfiles/bootstrap/main.py +++ b/data/Dockerfiles/bootstrap/main.py @@ -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) diff --git a/data/Dockerfiles/bootstrap/modules/BootstrapPhpfpm.py b/data/Dockerfiles/bootstrap/modules/BootstrapPhpfpm.py new file mode 100644 index 000000000..41857d941 --- /dev/null +++ b/data/Dockerfiles/bootstrap/modules/BootstrapPhpfpm.py @@ -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() diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index ff333f5b3..3432b092f 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -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"] diff --git a/data/Dockerfiles/phpfpm/docker-entrypoint.sh b/data/Dockerfiles/phpfpm/docker-entrypoint.sh index 0d09ac5fc..51ae0aeaf 100755 --- a/data/Dockerfiles/phpfpm/docker-entrypoint.sh +++ b/data/Dockerfiles/phpfpm/docker-entrypoint.sh @@ -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 "$@" diff --git a/data/conf/phpfpm/config_templates/0081-custom-mailcow.css.j2 b/data/conf/phpfpm/config_templates/0081-custom-mailcow.css.j2 new file mode 100644 index 000000000..4406ebebf --- /dev/null +++ b/data/conf/phpfpm/config_templates/0081-custom-mailcow.css.j2 @@ -0,0 +1,3 @@ +/* + Custom styling for mailcow UI +*/ diff --git a/data/conf/phpfpm/php-conf.d/opcache-recommended.ini b/data/conf/phpfpm/config_templates/opcache-recommended.ini.j2 similarity index 100% rename from data/conf/phpfpm/php-conf.d/opcache-recommended.ini rename to data/conf/phpfpm/config_templates/opcache-recommended.ini.j2 diff --git a/data/conf/phpfpm/php-conf.d/other.ini b/data/conf/phpfpm/config_templates/other.ini.j2 similarity index 100% rename from data/conf/phpfpm/php-conf.d/other.ini rename to data/conf/phpfpm/config_templates/other.ini.j2 diff --git a/data/conf/phpfpm/php-fpm.d/pools.conf b/data/conf/phpfpm/config_templates/pools.conf.j2 similarity index 100% rename from data/conf/phpfpm/php-fpm.d/pools.conf rename to data/conf/phpfpm/config_templates/pools.conf.j2 diff --git a/data/conf/phpfpm/config_templates/session_store.ini.j2 b/data/conf/phpfpm/config_templates/session_store.ini.j2 new file mode 100644 index 000000000..00c052b7a --- /dev/null +++ b/data/conf/phpfpm/config_templates/session_store.ini.j2 @@ -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 }}" diff --git a/data/conf/phpfpm/php-conf.d/upload.ini b/data/conf/phpfpm/config_templates/upload.ini.j2 similarity index 100% rename from data/conf/phpfpm/php-conf.d/upload.ini rename to data/conf/phpfpm/config_templates/upload.ini.j2 diff --git a/docker-compose.yml b/docker-compose.yml index 6cf0ecd5b..5414d31ea 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: