Browse Source

Update

master
Harish Karumuthil 2 months ago
parent
commit
00bc959a9b
  1. 52
      home/.local/Apps/daily-utils/bin/mysql-backup.py

52
home/.local/Apps/daily-utils/bin/mysql-backup.py

@ -8,6 +8,7 @@ import shutil
import argparse import argparse
import sys import sys
import fnmatch import fnmatch
import logging
def get_args(): def get_args():
parser = argparse.ArgumentParser(description="High-performance MySQL multi-database backup tool.") parser = argparse.ArgumentParser(description="High-performance MySQL multi-database backup tool.")
@ -16,17 +17,16 @@ def get_args():
parser.add_argument("-u", "--user", required=True, help="MySQL Username") parser.add_argument("-u", "--user", required=True, help="MySQL Username")
parser.add_argument("-p", "--password", required=True, help="MySQL Password") parser.add_argument("-p", "--password", required=True, help="MySQL Password")
parser.add_argument("-o", "--output", default="backups", help="Output directory") parser.add_argument("-o", "--output", default="backups", help="Output directory")
# New argument for exclusion patterns
parser.add_argument("-x", "--exclude", action="append", default=[], parser.add_argument("-x", "--exclude", action="append", default=[],
help="Pattern to exclude (e.g., 'test_*' or 'tmp_'). Can be used multiple times.")
help="Pattern to exclude (e.g., 'test_*'). Can be used multiple times.")
# Added verbose flag
parser.add_argument("-v", "--verbose", action="store_true", help="Print executed commands and detailed logs")
return parser.parse_args() return parser.parse_args()
def should_exclude(db_name, patterns): def should_exclude(db_name, patterns):
"""Checks if a database name matches any provided shell-style patterns."""
system_dbs = ('information_schema', 'performance_schema', 'sys', 'mysql', 'Database') system_dbs = ('information_schema', 'performance_schema', 'sys', 'mysql', 'Database')
if db_name in system_dbs: if db_name in system_dbs:
return True return True
for pattern in patterns: for pattern in patterns:
if fnmatch.fnmatch(db_name, pattern): if fnmatch.fnmatch(db_name, pattern):
return True return True
@ -35,38 +35,43 @@ def should_exclude(db_name, patterns):
def run_backup(): def run_backup():
args = get_args() args = get_args()
# Configure logging based on verbose flag
log_level = logging.DEBUG if args.verbose else logging.INFO
logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level)
if not os.path.exists(args.output): if not os.path.exists(args.output):
logging.debug(f"Creating output directory: {args.output}")
os.makedirs(args.output) os.makedirs(args.output)
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
conn_flags = ['-h', args.host, '-P', args.port, '-u', args.user, f'-p{args.password}']
# Hide password in logs for security, even in verbose mode
safe_conn_flags = ['-h', args.host, '-P', args.port, '-u', args.user, '-p****']
real_conn_flags = ['-h', args.host, '-P', args.port, '-u', args.user, f'-p{args.password}']
try: try:
# 1. Fetch Database List # 1. Fetch Database List
print(f"Connecting to {args.host}...")
proc = subprocess.run(
['mysql'] + conn_flags + ['-e', 'SHOW DATABASES;', '-N', '-s'],
capture_output=True, text=True, check=True
)
fetch_cmd = ['mysql'] + real_conn_flags + ['-e', 'SHOW DATABASES;', '-N', '-s']
logging.debug(f"Executing: {' '.join(['mysql'] + safe_conn_flags + fetch_cmd[-3:])}")
proc = subprocess.run(fetch_cmd, capture_output=True, text=True, check=True)
raw_databases = proc.stdout.split() raw_databases = proc.stdout.split()
# 2. Filter databases based on patterns
databases = [db for db in raw_databases if not should_exclude(db, args.exclude)] databases = [db for db in raw_databases if not should_exclude(db, args.exclude)]
if not databases: if not databases:
print("No databases found matching the criteria.")
logging.info("No databases found matching the criteria.")
return return
print(f"Starting backup for {len(databases)} databases...")
logging.info(f"Starting backup for {len(databases)} databases...")
for db in databases: for db in databases:
print(f" -> {db}...", end=" ", flush=True)
filename = f"{db}_{timestamp}.sql.gz" filename = f"{db}_{timestamp}.sql.gz"
filepath = os.path.join(args.output, filename) filepath = os.path.join(args.output, filename)
dump_cmd = [ dump_cmd = [
'mysqldump' 'mysqldump'
] + conn_flags + [
] + real_conn_flags + [
'--single-transaction', '--single-transaction',
'--extended-insert', '--extended-insert',
'--quick', '--quick',
@ -75,6 +80,12 @@ def run_backup():
db db
] ]
# Detailed log of the dump command
safe_dump_cmd = ['mysqldump'] + safe_conn_flags + dump_cmd[7:]
logging.debug(f"Executing: {' '.join(safe_dump_cmd)} | gzip > {filepath}")
logging.info(f" -> Backing up: {db}")
with subprocess.Popen(dump_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as dump_proc: with subprocess.Popen(dump_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as dump_proc:
with gzip.open(filepath, 'wb', compresslevel=6) as f_out: with gzip.open(filepath, 'wb', compresslevel=6) as f_out:
shutil.copyfileobj(dump_proc.stdout, f_out) shutil.copyfileobj(dump_proc.stdout, f_out)
@ -83,15 +94,18 @@ def run_backup():
if dump_proc.returncode != 0: if dump_proc.returncode != 0:
error_msg = dump_proc.stderr.read().decode() error_msg = dump_proc.stderr.read().decode()
print(f"\nFAILED: {error_msg}")
logging.error(f"Failed to backup {db}: {error_msg}")
else: else:
print(f"OK")
logging.debug(f"Successfully completed {db}")
logging.info("All tasks completed.")
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print(f"\nConnection Error: {e.stderr}")
# Even errors should hide the password
logging.error("Connection failed. Check your credentials and host settings.")
sys.exit(1) sys.exit(1)
except Exception as e: except Exception as e:
print(f"\nUnexpected error: {e}")
logging.error(f"Unexpected error: {e}")
sys.exit(1) sys.exit(1)
if __name__ == "__main__": if __name__ == "__main__":

Loading…
Cancel
Save