1
0
mirror of https://github.com/mailcow/mailcow-dockerized.git synced 2025-12-23 23:01:34 +00:00

[MySQL] Check if MySQL supports timezone conversion on startup

This commit is contained in:
FreddleSpl0it
2025-05-22 12:59:37 +02:00
parent 9174a05af3
commit 767d746419
2 changed files with 57 additions and 9 deletions

View File

@@ -273,33 +273,39 @@ class BootstrapBase:
shutil.copy2(src_path, dst_path) shutil.copy2(src_path, dst_path)
def remove(self, path, recursive=False, wipe_contents=False): def remove(self, path, recursive=False, wipe_contents=False, exclude=None):
""" """
Removes a file or directory. Removes a file or directory with optional exclusion logic.
Args: Args:
path (str or Path): The file or directory path to remove. path (str or Path): The file or directory path to remove.
recursive (bool): If True, directories will be removed recursively. recursive (bool): If True, directories will be removed recursively.
wipe_contents (bool): If True and path is a directory, only its contents are removed, not the dir itself. wipe_contents (bool): If True and path is a directory, only its contents are removed, not the dir itself.
exclude (list[str], optional): List of filenames to exclude from deletion.
Raises: Raises:
FileNotFoundError: If the path does not exist. FileNotFoundError: If the path does not exist.
ValueError: If a directory is passed without recursive or wipe_contents. ValueError: If a directory is passed without recursive or wipe_contents.
""" """
path = Path(path) path = Path(path)
exclude = set(exclude or [])
if not path.exists(): if not path.exists():
raise FileNotFoundError(f"Cannot remove: {path} does not exist") raise FileNotFoundError(f"Cannot remove: {path} does not exist")
if wipe_contents and path.is_dir(): if wipe_contents and path.is_dir():
for child in path.iterdir(): for child in path.iterdir():
if child.name in exclude:
continue
if child.is_dir(): if child.is_dir():
shutil.rmtree(child) shutil.rmtree(child)
else: else:
child.unlink() child.unlink()
elif path.is_file(): elif path.is_file():
path.unlink() if path.name not in exclude:
path.unlink()
elif path.is_dir(): elif path.is_dir():
if recursive: if recursive:
shutil.rmtree(path) shutil.rmtree(path)
@@ -664,21 +670,22 @@ class BootstrapBase:
allowed_chars = string.ascii_letters + string.digits + "_-" allowed_chars = string.ascii_letters + string.digits + "_-"
return ''.join(secrets.choice(allowed_chars) for _ in range(length)) return ''.join(secrets.choice(allowed_chars) for _ in range(length))
def run_command(self, command, check=True, shell=False): def run_command(self, command, check=True, shell=False, input_stream=None):
""" """
Executes an OS command and optionally checks for errors. Executes an OS command and optionally checks for errors.
Supports piping via input_stream.
Args: Args:
command (str or list): The command to execute. Can be a string (if shell=True) command (str or list): The command to execute.
or a list of command arguments. check (bool): Raise CalledProcessError on failure if True.
check (bool): If True, raises CalledProcessError on failure. shell (bool): Run in a shell if True.
shell (bool): If True, runs the command in a shell. input_stream: A pipe source to use as stdin (e.g. another process's stdout).
Returns: Returns:
subprocess.CompletedProcess: The result of the command execution. subprocess.CompletedProcess: The result of the command execution.
Logs: Logs:
Prints the command being run and any error output. Prints command output and errors.
""" """
try: try:
@@ -686,6 +693,7 @@ class BootstrapBase:
command, command,
shell=shell, shell=shell,
check=check, check=check,
stdin=input_stream,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
text=True text=True

View File

@@ -20,6 +20,8 @@ class Bootstrap(BootstrapBase):
print("Running mysql_upgrade...") print("Running mysql_upgrade...")
self.upgrade_mysql(dbuser, dbpass, socket) self.upgrade_mysql(dbuser, dbpass, socket)
print("Checking timezone support with CONVERT_TZ...")
self.check_and_import_timezone_support(dbuser, dbpass, socket)
print("Shutting down temporary mysqld...") print("Shutting down temporary mysqld...")
self.close_mysql() self.close_mysql()
@@ -119,3 +121,41 @@ class Bootstrap(BootstrapBase):
else: else:
print("mysql_upgrade failed after all retries.") print("mysql_upgrade failed after all retries.")
return False return False
def check_and_import_timezone_support(self, dbuser, dbpass, socket):
"""
Checks if MySQL supports timezone conversion (CONVERT_TZ).
If not, it imports timezone info using mysql_tzinfo_to_sql piped into mariadb.
"""
try:
cursor = self.mysql_conn.cursor()
cursor.execute("SELECT CONVERT_TZ('2019-11-02 23:33:00','Europe/Berlin','UTC')")
result = cursor.fetchone()
cursor.close()
if not result or result[0] is None:
print("Timezone conversion failed or returned NULL. Importing timezone info...")
# Use mysql_tzinfo_to_sql piped into mariadb
tz_dump = subprocess.Popen(
["mysql_tzinfo_to_sql", "/usr/share/zoneinfo"],
stdout=subprocess.PIPE
)
self.run_command([
"mariadb",
"--socket", socket,
"-u", dbuser,
f"-p{dbpass}",
"mysql"
], input_stream=tz_dump.stdout)
tz_dump.stdout.close()
tz_dump.wait()
print("Timezone info successfully imported.")
else:
print(f"Timezone support is working. Sample result: {result[0]}")
except Exception as e:
print(f"Failed to verify or import timezone info: {e}")