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:
@@ -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
|
||||||
|
|||||||
@@ -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}")
|
||||||
|
|||||||
Reference in New Issue
Block a user