diff --git a/.gitignore b/.gitignore index c2be15533..5dcaa9496 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,7 @@ data/conf/sogo/cron.creds data/conf/sogo/custom-fulllogo.svg data/conf/sogo/custom-shortlogo.svg data/conf/sogo/custom-fulllogo.png +data/conf/mysql/my.cnf data/gitea/ data/gogs/ data/hooks/dovecot/* diff --git a/data/Dockerfiles/bootstrap/main.py b/data/Dockerfiles/bootstrap/main.py index e4836a116..7ce2cce36 100644 --- a/data/Dockerfiles/bootstrap/main.py +++ b/data/Dockerfiles/bootstrap/main.py @@ -23,6 +23,8 @@ def main(): from modules.BootstrapRspamd import Bootstrap elif container_name == "clamd-mailcow": from modules.BootstrapClamd import Bootstrap + elif container_name == "mysql-mailcow": + from modules.BootstrapMysql import Bootstrap else: print(f"No bootstrap handler for container: {container_name}", file=sys.stderr) sys.exit(1) @@ -31,9 +33,9 @@ def main(): container=container_name, db_config={ "host": "localhost", - "user": os.getenv("DBUSER"), - "password": os.getenv("DBPASS"), - "database": os.getenv("DBNAME"), + "user": os.getenv("DBUSER") or os.getenv("MYSQL_USER"), + "password": os.getenv("DBPASS") or os.getenv("MYSQL_PASSWORD"), + "database": os.getenv("DBNAME") or os.getenv("MYSQL_DATABASE"), "unix_socket": "/var/run/mysqld/mysqld.sock", 'connection_timeout': 2 }, diff --git a/data/Dockerfiles/bootstrap/modules/BootstrapMysql.py b/data/Dockerfiles/bootstrap/modules/BootstrapMysql.py new file mode 100644 index 000000000..4f5eddbd0 --- /dev/null +++ b/data/Dockerfiles/bootstrap/modules/BootstrapMysql.py @@ -0,0 +1,80 @@ +from jinja2 import Environment, FileSystemLoader +from modules.BootstrapBase import BootstrapBase +from pathlib import Path +import os +import sys +import time +import platform +import subprocess + +class Bootstrap(BootstrapBase): + def bootstrap(self): + self.upgrade_mysql() + + # Setup Jinja2 Environment and load vars + self.env = Environment( + loader=FileSystemLoader('./etc/mysql/conf.d/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() + + print("Render config") + self.render_config("my.cnf.j2", "/etc/mysql/conf.d/my.cnf") + + def upgrade_mysql(self, max_retries=5, wait_interval=3): + """ + Runs mysql_upgrade in a controlled way using run_command. + Starts mysqld in background, upgrades, shuts down, then restarts in foreground. + """ + + dbuser = "root" + dbpass = os.getenv("MYSQL_ROOT_PASSWORD", "") + socket = "/var/run/mysqld/mysqld.sock" + + print("Starting temporary mysqld for upgrade...") + temp_proc = subprocess.Popen([ + "mysqld", + "--user=mysql", + "--skip-networking", + f"--socket={socket}" + ]) + + self.connect_mysql() + + print("Running mysql_upgrade...") + retries = 0 + while retries < max_retries: + result = self.run_command([ + "mysql_upgrade", + "-u", dbuser, + f"-p{dbpass}", + f"--socket={socket}" + ], check=False) + + if result.returncode == 0: + print("mysql_upgrade completed successfully.") + break + else: + print(f"mysql_upgrade failed (try {retries+1}/{max_retries})") + retries += 1 + time.sleep(wait_interval) + else: + print("mysql_upgrade failed after all retries.") + temp_proc.terminate() + return False + + print("Shutting down temporary mysqld...") + self.run_command([ + "mariadb-admin", + "shutdown", + f"--socket={socket}", + "-u", dbuser, + f"-p{dbpass}" + ]) diff --git a/data/Dockerfiles/mariadb/Dockerfile b/data/Dockerfiles/mariadb/Dockerfile new file mode 100644 index 000000000..6662f1035 --- /dev/null +++ b/data/Dockerfiles/mariadb/Dockerfile @@ -0,0 +1,28 @@ +FROM mariadb:10.11 + +LABEL maintainer = "The Infrastructure Company GmbH " + + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + python3 \ + python3-pip \ + gosu \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +RUN pip install \ + mysql-connector-python \ + jinja2 \ + redis \ + dnspython + + +COPY data/Dockerfiles/bootstrap /bootstrap +COPY data/Dockerfiles/mariadb/docker-entrypoint.sh /docker-entrypoint.sh + +RUN chmod +x /docker-entrypoint.sh + + +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["mysqld"] diff --git a/data/Dockerfiles/mariadb/docker-entrypoint.sh b/data/Dockerfiles/mariadb/docker-entrypoint.sh new file mode 100644 index 000000000..c1459b2cf --- /dev/null +++ b/data/Dockerfiles/mariadb/docker-entrypoint.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Run hooks +for file in /hooks/*; do + if [ -x "${file}" ]; then + echo "Running hook ${file}" + "${file}" + 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 MariaDB." + exit $BOOTSTRAP_EXIT_CODE +fi + +echo "Bootstrap succeeded. Starting MariaDB..." +exec gosu mysql "$@" diff --git a/data/conf/mysql/my.cnf b/data/conf/mysql/config_templates/my.cnf.j2 similarity index 100% rename from data/conf/mysql/my.cnf rename to data/conf/mysql/config_templates/my.cnf.j2 diff --git a/data/hooks/mariadb/.gitkeep b/data/hooks/mariadb/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/docker-compose.yml b/docker-compose.yml index 6500849c5..6cf0ecd5b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,16 +17,18 @@ services: - unbound mysql-mailcow: - image: mariadb:10.11 + image: ghcr.io/mailcow/mariadb:nightly-19052025 depends_on: - unbound-mailcow - netfilter-mailcow stop_grace_period: 45s volumes: + - ./data/hooks/mariadb:/hooks:z + - ./data/conf/mysql/:/etc/mysql/conf.d/:z - mysql-vol-1:/var/lib/mysql/ - mysql-socket-vol-1:/var/run/mysqld/ - - ./data/conf/mysql/:/etc/mysql/conf.d/:ro,Z environment: + - CONTAINER_NAME=mysql-mailcow - TZ=${TZ} - MYSQL_ROOT_PASSWORD=${DBROOT} - MYSQL_DATABASE=${DBNAME}