\x89PNG\r\n\x1a\n\x00\x00\x00\x0DIHDR\x00\x00\x00\x01\x00 \x00\x00\x01\x08\x06\x00\x00\x00\x1F\x15\xC4\x89\x00\x00\x00 \x0AIDATx\x9Ccb\x00\x00\x00\x06\x00\x03\x1A\x05\x9D\x00\x00 \x00\x00IEND\xAE\x42\x60\x82 csarite.com
KUJUNTI.ID MINISH3LL
Path : /var/www/html/yedekledb24/DatabaseBackupMasterv8/
(S)h3ll Cr3at0r :
F!le Upl0ad :

B-Con CMD Config cPanel C-Rdp D-Log Info Jump Mass Ransom Symlink vHost Zone-H

Current File : /var/www/html/yedekledb24/DatabaseBackupMasterv8/perform_incremental_mysql_backup.py


import os
import subprocess
import tempfile
import logging
import shutil
import tarfile
import traceback
from models import BackupLog
from pathlib import Path
import storage_utils

logger = logging.getLogger(__name__)

def perform_incremental_mysql_backup(db_conn, temp_file, job, parent_log_id=None):
    """
    Perform incremental MySQL/MariaDB backup using XtraBackup or MariaBackup.
    
    Args:
        db_conn (DatabaseConnection): Database connection object
        temp_file (str): Path to temporary file for backup
        job (BackupJob): Backup job configuration
        parent_log_id (int, optional): ID of parent backup log entry
        
    Returns:
        bool: True if backup successful, False otherwise
    """
    # Geçici dizinleri takip etmek için liste
    temp_dirs = []
    
    try:
        # Detect database type and select the appropriate backup tool
        db_type = db_conn.db_type.lower()
        
        # Select the appropriate backup tool based on database type
        if db_type == 'mariadb':
            backup_tool = 'mariabackup'
            logger.info("Using MariaBackup for MariaDB")
        elif db_type == 'mysql':
            backup_tool = 'xtrabackup'
            logger.info("Using Percona XtraBackup for MySQL")
        else:
            logger.error(f"Unsupported database type for incremental backup: {db_type}")
            return False
            
        # Check if the selected backup tool is installed
        try:
            subprocess.run([backup_tool, '--version'], capture_output=True, check=True)
        except (subprocess.SubprocessError, FileNotFoundError):
            logger.error(f"{backup_tool} is not installed. Cannot perform incremental backup.")
            return False
            
        # Set MySQL/MariaDB connection parameters
        mysql_args = {
            'host': db_conn.host,
            'port': db_conn.port,
            'user': db_conn.username,
            'password': db_conn.password,
            'database': db_conn.database
        }
        
        # Create a temporary directory for the backup
        backup_dir = tempfile.mkdtemp(prefix=f"backup_{mysql_args['database']}_")
        temp_dirs.append(backup_dir)
        
        # Determine backup type
        backup_type = job.backup_method  # 'full', 'incremental', 'differential'
        
        # Optimum parametreler ile komut oluştur
        cmd = [
            backup_tool,
            '--backup',
            f'--target-dir={backup_dir}',
            f'--host={mysql_args["host"]}',
            f'--port={mysql_args["port"]}',
            f'--user={mysql_args["user"]}',
            f'--password={mysql_args["password"]}',
            '--compress',             # Yedekleri sıkıştır
            '--compress-threads=4',   # Sıkıştırma için 4 thread kullan  
            '--parallel=2'            # Paralel işlem için 2 thread kullan
        ]
        
        # For incremental/differential backup, find the base backup
        parent_backup_dir = None
        if backup_type in ['incremental', 'differential']:
            if parent_log_id:
                parent_backup = BackupLog.query.get(parent_log_id)
            else:
                # Find the last full backup for this job
                parent_backup = BackupLog.query.filter_by(
                    job_id=job.id,
                    backup_method='full',
                    status='success'
                ).order_by(BackupLog.timestamp.desc()).first()
                
            if not parent_backup:
                logger.error(f"No parent backup found for {backup_type} backup. Performing full backup instead.")
                backup_type = 'full'
            else:
                logger.info(f"Using parent backup ID {parent_backup.id} from {parent_backup.timestamp}")
                
                # Create a temporary directory for the parent backup
                parent_temp_dir = tempfile.mkdtemp(prefix=f"backup_parent_")
                temp_dirs.append(parent_temp_dir)
                parent_backup_file = os.path.join(parent_temp_dir, "parent_backup.tar.gz")
                
                # Download the parent backup file
                try:
                    logger.info(f"Downloading parent backup from storage: {parent_backup.backup_file}")
                    download_success = storage_utils.download_file_from_storage(
                        job.storage, 
                        parent_backup.backup_file, 
                        parent_backup_file
                    )
                    
                    if not download_success:
                        logger.error("Failed to download parent backup from storage")
                        backup_type = 'full'  # Fall back to full backup
                    elif not os.path.exists(parent_backup_file):
                        logger.error(f"Downloaded parent backup file does not exist: {parent_backup_file}")
                        backup_type = 'full'  # Fall back to full backup
                    else:
                        # Extract the parent backup
                        parent_backup_dir = os.path.join(parent_temp_dir, "extracted")
                        os.makedirs(parent_backup_dir, exist_ok=True)
                        
                        logger.info(f"Checking parent backup file format: {parent_backup_file}")
                        
                        # Eğer dosya ismi .sql, .zip veya .gpg içeriyorsa, bu mysqldump ile oluşturulmuş bir dosyadır
                        if '.sql' in parent_backup.backup_file or '.zip' in parent_backup.backup_file or '.gpg' in parent_backup.backup_file:
                            logger.warning("Parent backup appears to be in SQL format, not compatible with incremental backups.")
                            logger.warning("To use incremental backups, you need at least one successful full backup using mariabackup.")
                            backup_type = 'full'  # Tam yedeklemeye geri dön
                        else:
                            logger.info(f"Extracting parent backup file: {parent_backup_file}")
                            try:
                                # Dosya formatını kontrol et
                                if parent_backup_file.endswith('.gpg'):
                                    logger.warning("Parent backup is encrypted and cannot be used for incremental backup.")
                                    backup_type = 'full'
                                    
                                # Dosya formatını kontrol et - tar.gz mi değil mi?
                                try:
                                    with tarfile.open(parent_backup_file, 'r:gz') as tar:
                                        tar.extractall(path=parent_backup_dir)
                                        # Find the extracted directory in parent_backup_dir
                                        subdirs = [d for d in os.listdir(parent_backup_dir) if os.path.isdir(os.path.join(parent_backup_dir, d))]
                                        if subdirs:
                                            parent_backup_dir = os.path.join(parent_backup_dir, subdirs[0])
                                            logger.info(f"Using extracted parent backup directory: {parent_backup_dir}")
                                        else:
                                            logger.error("No valid directory found in extracted parent backup")
                                            backup_type = 'full'  # Fall back to full backup
                                except tarfile.ReadError:
                                    logger.warning("Parent backup is not in tar.gz format. Falling back to full backup.")
                                    backup_type = 'full'  # Tam yedeklemeye geri dön
                            except Exception as extract_err:
                                logger.error(f"Failed to extract parent backup: {str(extract_err)}")
                                backup_type = 'full'  # Fall back to full backup
                except Exception as download_err:
                    logger.error(f"Error accessing parent backup: {str(download_err)}")
                    backup_type = 'full'  # Fall back to full backup
                
                # Add incremental backup parameters if we have a valid parent backup dir
                if backup_type != 'full' and parent_backup_dir and os.path.isdir(parent_backup_dir):
                    if backup_type == 'incremental':
                        logger.info(f"Setting up incremental backup with basedir: {parent_backup_dir}")
                        cmd.append('--incremental')
                        cmd.append(f'--incremental-basedir={parent_backup_dir}')
                    elif backup_type == 'differential':
                        # For differential backup, find the original full backup (base)
                        if parent_backup.backup_method == 'full':
                            logger.info(f"Setting up differential backup using full backup as base")
                            cmd.append('--incremental')
                            cmd.append(f'--incremental-basedir={parent_backup_dir}')
                        else:
                            logger.info(f"Setting up differential backup, searching for base full backup")
                            # Traverse up the backup chain until we find a full backup
                            base_backup = parent_backup
                            while base_backup and base_backup.backup_method != 'full':
                                base_backup = BackupLog.query.get(base_backup.parent_log_id)
                                
                            if base_backup and base_backup.id != parent_backup.id:
                                # Download and extract the base backup
                                base_temp_dir = tempfile.mkdtemp(prefix=f"backup_base_")
                                temp_dirs.append(base_temp_dir)
                                base_backup_file = os.path.join(base_temp_dir, "base_backup.tar.gz")
                                
                                logger.info(f"Downloading base full backup ID {base_backup.id} from storage")
                                download_success = storage_utils.download_file_from_storage(
                                    job.storage, 
                                    base_backup.backup_file, 
                                    base_backup_file
                                )
                                
                                if download_success and os.path.exists(base_backup_file):
                                    base_backup_dir = os.path.join(base_temp_dir, "extracted")
                                    os.makedirs(base_backup_dir, exist_ok=True)
                                    
                                    try:
                                        with tarfile.open(base_backup_file, 'r:gz') as tar:
                                            tar.extractall(path=base_backup_dir)
                                            subdirs = [d for d in os.listdir(base_backup_dir) if os.path.isdir(os.path.join(base_backup_dir, d))]
                                            if subdirs:
                                                base_backup_dir = os.path.join(base_backup_dir, subdirs[0])
                                                logger.info(f"Using base backup directory: {base_backup_dir}")
                                                cmd.append('--incremental')
                                                cmd.append(f'--incremental-basedir={base_backup_dir}')
                                            else:
                                                logger.error("No valid directory found in extracted base backup")
                                                backup_type = 'full'  # Fall back to full backup
                                    except Exception as extract_err:
                                        logger.error(f"Failed to extract base backup: {str(extract_err)}")
                                        backup_type = 'full'  # Fall back to full backup
                                else:
                                    logger.error("Failed to download base backup from storage")
                                    backup_type = 'full'  # Fall back to full backup
                            else:
                                logger.error("Could not find valid base full backup for differential backup")
                                backup_type = 'full'  # Fall back to full backup
                else:
                    logger.warning(f"Invalid parent backup directory or backup type is now 'full'")
        
        # Add any extra options specified for XtraBackup/MariaBackup
        if hasattr(job, 'xtrabackup_options') and job.xtrabackup_options:
            extra_options = job.xtrabackup_options.split()
            cmd.extend(extra_options)
            logger.info(f"Added user-specified options: {job.xtrabackup_options}")
        
        # Log final backup type and command
        if backup_type == 'full':
            logger.info("Executing FULL backup")
        else:
            logger.info(f"Executing {backup_type.upper()} backup")
            
        logger.debug(f"Backup command: {' '.join(cmd)}")
        
        # Execute backup command with timeout
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=3600)  # 1 hour timeout
        
        if result.returncode != 0:
            logger.error(f"Backup error ({result.returncode}): {result.stderr}")
            
            # InnoDB hatası varsa, otomatik olarak mysqldump'a geçiş yap
            if "InnoDB" in result.stderr or "mariadbd got signal 6" in result.stderr:
                logger.warning("Detected InnoDB or corruption error in MariaBackup/XtraBackup")
                logger.warning("Falling back to standard mysqldump method")
                
                # Standart mysqldump komutunu hazırla
                mysqldump_cmd = [
                    'mysqldump',
                    f'--host={mysql_args["host"]}',
                    f'--port={mysql_args["port"]}',
                    f'--user={mysql_args["user"]}',
                    f'--password={mysql_args["password"]}',
                    '--single-transaction',
                    '--quick',
                    '--routines',
                    '--triggers',
                    '--add-drop-table',
                    '--compress',
                    mysql_args["database"]
                ]
                
                # temp_file dosya uzantısını düzeltmemiz gerekebilir
                # sql dosyası değilse, sql uzantılı hale getir
                if not temp_file.endswith('.sql'):
                    temp_file = os.path.splitext(temp_file)[0] + '.sql'
                
                logger.info(f"Executing fallback mysqldump to {temp_file}")
                with open(temp_file, 'w') as f:
                    try:
                        dump_result = subprocess.run(mysqldump_cmd, stdout=f, stderr=subprocess.PIPE, text=True)
                        
                        if dump_result.returncode != 0:
                            logger.error(f"Fallback mysqldump error: {dump_result.stderr}")
                            return False
                        else:
                            logger.info("Fallback mysqldump completed successfully")
                            return True
                    except Exception as dump_err:
                        logger.error(f"Fallback mysqldump exception: {str(dump_err)}")
                        return False
            
            # Diğer hatalar için başarısız olarak işaretle
            return False
            
        # For full backups, prepare the backup to make it consistent
        if backup_type == 'full':
            # backup-my.cnf dosyasını kontrol et ve gerekirse oluştur
            backup_my_cnf_path = os.path.join(backup_dir, 'backup-my.cnf')
            if not os.path.exists(backup_my_cnf_path):
                logger.info("Creating backup-my.cnf file for preparation step")
                with open(backup_my_cnf_path, 'w') as f:
                    f.write("[mysqld]\n")
                    f.write("innodb_checksum_algorithm=crc32\n")
                    f.write("innodb_log_checksums=ON\n")
                    f.write("innodb_data_file_path=ibdata1:12M:autoextend\n")
                    f.write("innodb_log_files_in_group=2\n")
                    f.write("innodb_log_file_size=50331648\n")
                    f.write("innodb_page_size=16384\n")
                    f.write("innodb_undo_directory=./\n")
                    f.write("innodb_undo_tablespaces=0\n")
                
            prepare_cmd = [
                backup_tool,
                '--prepare',
                f'--target-dir={backup_dir}'
            ]
            
            # MySQL için XtraBackup kullanıyorsak, ek parametre ekle
            if 'xtrabackup' in backup_tool:
                prepare_cmd.append('--skip-innodb-redo-log-restore')  # Bu parametre sadece MySQL XtraBackup için geçerli
            logger.info("Preparing full backup to ensure consistency")
            prepare_result = subprocess.run(prepare_cmd, capture_output=True, text=True)
            if prepare_result.returncode != 0:
                logger.error(f"Backup prepare error: {prepare_result.stderr}")
                
                # InnoDB hatası varsa, otomatik olarak mysqldump'a geçiş yap
                if "InnoDB" in prepare_result.stderr or "mariadbd got signal" in prepare_result.stderr:
                    logger.warning("Detected InnoDB or corruption error in prepare step")
                    logger.warning("Falling back to standard mysqldump method")
                    
                    # Standart mysqldump komutunu hazırla
                    mysqldump_cmd = [
                        'mysqldump',
                        f'--host={mysql_args["host"]}',
                        f'--port={mysql_args["port"]}',
                        f'--user={mysql_args["user"]}',
                        f'--password={mysql_args["password"]}',
                        '--single-transaction',
                        '--quick',
                        '--routines',
                        '--triggers',
                        '--add-drop-table',
                        '--compress',
                        mysql_args["database"]
                    ]
                    
                    # temp_file dosya uzantısını düzelt
                    if not temp_file.endswith('.sql'):
                        temp_file = os.path.splitext(temp_file)[0] + '.sql'
                    
                    logger.info(f"Executing fallback mysqldump to {temp_file}")
                    with open(temp_file, 'w') as f:
                        try:
                            dump_result = subprocess.run(mysqldump_cmd, stdout=f, stderr=subprocess.PIPE, text=True)
                            
                            if dump_result.returncode != 0:
                                logger.error(f"Fallback mysqldump error: {dump_result.stderr}")
                                logger.warning("Skipping backup preparation, proceeding with raw backup")
                            else:
                                logger.info("Fallback mysqldump completed successfully")
                                return True
                        except Exception as dump_err:
                            logger.error(f"Fallback mysqldump exception: {str(dump_err)}")
                            logger.warning("Skipping backup preparation, proceeding with raw backup")
                else:
                    # Hata olsa bile devam et ve hazırlama adımını atla
                    logger.warning("Skipping backup preparation, proceeding with raw backup")
            else:
                logger.info("Backup preparation completed successfully")
        
        # Create a tarball of the backup directory
        target_dir = os.path.dirname(temp_file)
        os.makedirs(target_dir, exist_ok=True)
        
        logger.info(f"Creating compressed archive of backup: {temp_file}")
        with tarfile.open(temp_file, "w:gz") as tar:
            tar.add(backup_dir, arcname=os.path.basename(backup_dir))
            
        # If encryption is enabled, encrypt the tarball
        # Artırımlı yedekleme türü için şifreleme devre dışı bırakıldı
        if backup_type == 'full' and hasattr(job, 'encrypt_backup') and job.encrypt_backup and hasattr(job, 'encryption_type') and job.encryption_type == 'symmetric' and hasattr(job, 'encryption_passphrase') and job.encryption_passphrase:
            try:
                logger.info("Encrypting backup file with GPG")
                # Önce basitleştirilmiş versiyonu dene
                try:
                    from encryption_utils_simplified import encrypt_file
                except ImportError:
                    # Sonra normal versiyonu dene
                    from encryption_utils import encrypt_file
                    
                encrypted_file = encrypt_file(
                    temp_file,
                    recipients=None,
                    output_file=f"{temp_file}.gpg",
                    passphrase=job.encryption_passphrase
                )
                if encrypted_file:
                    # Remove the unencrypted file and update the temp_file path
                    os.unlink(temp_file)
                    temp_file = encrypted_file
                    logger.info(f"Backup file encrypted successfully: {encrypted_file}")
                else:
                    logger.error("Failed to encrypt backup file")
            except Exception as e:
                logger.error(f"Encryption error: {str(e)}")
        elif backup_type != 'full' and hasattr(job, 'encrypt_backup') and job.encrypt_backup:
            logger.info("Encryption skipped for incremental/differential backup to maintain proper backup chain")
            
        # Record final backup type in log message
        logger.info(f"Successfully completed {backup_type.upper()} backup using {backup_tool}")
        
        # Başarılı yedekleme durumunu bildirin
        return True
        
    except Exception as e:
        logger.error(f"Backup error: {str(e)}")
        logger.error(traceback.format_exc())
        return False
    finally:
        # Cleanup temporary directories even if there's an error
        for temp_dir in temp_dirs:
            try:
                if os.path.exists(temp_dir):
                    logger.debug(f"Cleaning up temporary directory: {temp_dir}")
                    shutil.rmtree(temp_dir)
            except Exception as e:
                logger.warning(f"Failed to clean up temporary directory {temp_dir}: {str(e)}")
                # Continue with cleanup even if one directory fails

© KUJUNTI.ID