\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
| Path : /var/www/html/yedekledb24/DatabaseBackupMaster/ |
|
B-Con CMD Config cPanel C-Rdp D-Log Info Jump Mass Ransom Symlink vHost Zone-H |
| Current File : /var/www/html/yedekledb24/DatabaseBackupMaster/backup_utils.py |
import os
import time
import subprocess
import tempfile
import logging
import database_compression
import shutil
import gzip
import threading
from datetime import datetime
from models import DatabaseConnection, StorageTarget, BackupJob, BackupLog
from storage_utils import upload_to_storage, get_storage_space
# Import Flask-related modules only after app is created to avoid circular imports
from app import db
logger = logging.getLogger(__name__)
# We now import format_size from utils.py
from utils import format_size
# Artırımlı MySQL/MariaDB yedekleme için XtraBackup desteği
# İlk olarak perform_incremental_mysql_backup modülünü direkt olarak tanımla
from perform_incremental_mysql_backup import perform_incremental_mysql_backup
incremental_backup_support = True
logger.info("Artırımlı yedekleme için XtraBackup desteği bulundu. Artırımlı yedekler alınabilir.")
# GPG şifreleme için tek bir import bloğu
try:
# Önce basitleştirilmiş sürümü dene
from encryption_utils_simplified import encrypt_file, decrypt_file
encryption_support = True
except ImportError:
try:
# Sonra normal sürümü dene
from encryption_utils import encrypt_file, decrypt_file
encryption_support = True
except ImportError:
# Hiçbiri yoksa şifrelemeyi devre dışı bırak
encrypt_file = None
decrypt_file = None
encryption_support = False
logger.warning("GPG şifreleme modülleri yüklenemedi. Şifreleme devre dışı bırakıldı.")
def test_database_connection(db_type, host, port, username, password, database):
"""
Test connection to a database server.
Args:
db_type (str): Database type (mysql, mariadb, postgresql)
host (str): Database host
port (int): Database port
username (str): Database username
password (str): Database password
database (str): Database name
Returns:
bool: True if connection successful, False otherwise
"""
try:
if db_type in ['mysql', 'mariadb']:
import pymysql
conn = pymysql.connect(
host=host,
port=port,
user=username,
password=password,
database=database,
connect_timeout=5
)
conn.close()
return True
elif db_type == 'postgresql':
import psycopg2
conn = psycopg2.connect(
host=host,
port=port,
user=username,
password=password,
dbname=database,
connect_timeout=5
)
conn.close()
return True
else:
logger.error(f"Unsupported database type: {db_type}")
return False
except Exception as e:
logger.error(f"Database connection error for {db_type} {host}:{port}/{database}: {str(e)}")
return False
def perform_mysql_backup(db_conn, temp_file):
"""
Perform MySQL/MariaDB backup using mysqldump with optimized parameters.
Args:
db_conn (DatabaseConnection): Database connection object
temp_file (str): Path to temporary file for backup
Returns:
bool: True if backup successful, False otherwise
"""
try:
# Create password file for mysqldump (safer than command line password)
pwd_file = tempfile.NamedTemporaryFile(delete=False)
pwd_file.write(db_conn.password.encode())
pwd_file.close()
# Build mysqldump command with optimized parameters
cmd = [
'mysqldump',
f'--host={db_conn.host}',
f'--port={db_conn.port}',
f'--user={db_conn.username}',
f'--password={db_conn.password}',
'--single-transaction', # Consistent backups without locking tables
'--quick', # Retrieve rows one by one (better for large tables)
'--routines', # Include stored procedures
'--triggers', # Include triggers
'--events', # Include events
'--add-drop-table', # Include DROP TABLE statements
'--add-drop-database', # Include DROP DATABASE statements
'--create-options', # Include all CREATE TABLE options
'--extended-insert', # Use multi-row INSERT syntax (more efficient)
'--compress', # Compress data between client and server if both support it
'--max-allowed-packet=1G', # Allow large table/blob handling
# Removed '--set-gtid-purged=OFF' for compatibility with older MySQL versions
'--databases', db_conn.database,
'-r', temp_file
]
# Create a compressed version if required
compressed_file = temp_file + '.gz'
# Execute command
result = subprocess.run(cmd, capture_output=True)
# Remove password file
os.unlink(pwd_file.name)
if result.returncode != 0:
logger.error(f"mysqldump error: {result.stderr.decode()}")
return False
# Compress the backup file
try:
with open(temp_file, 'rb') as f_in:
import gzip
with gzip.open(compressed_file, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
# Replace original file with compressed version
os.remove(temp_file)
shutil.move(compressed_file, temp_file)
logger.info(f"Compressed MySQL backup file, reducing size significantly")
except Exception as e:
logger.warning(f"Could not compress backup file: {str(e)}")
# Continue with uncompressed file
return True
except Exception as e:
logger.error(f"MySQL backup error: {str(e)}")
return False
def perform_mssql_backup(db_conn, temp_file, method='script'):
"""
Perform Microsoft SQL Server backup.
Args:
db_conn (DatabaseConnection): Database connection object
temp_file (str): Path to temporary file for backup
method (str): Backup method: 'script' (Python script extraction) or
'sqlcmd' (direct BACKUP DATABASE command using sqlcmd)
Returns:
bool: True if backup successful, False otherwise
"""
# Use sqlcmd method if specified
if method == 'sqlcmd':
return perform_mssql_backup_sqlcmd(db_conn, temp_file)
# Otherwise use script method (default)
try:
# First try to import pymssql
try:
import pymssql
use_pymssql = True
except ImportError:
# Fall back to pyodbc if pymssql is not available
try:
import pyodbc
use_pymssql = False
except ImportError:
logger.error("Neither pymssql nor pyodbc is installed. Cannot backup MSSQL.")
return False
# Connect to the database
if use_pymssql:
conn = pymssql.connect(
server=db_conn.host,
port=db_conn.port,
user=db_conn.username,
password=db_conn.password,
database=db_conn.database,
timeout=1800 # 30 minutes timeout for large databases
)
else:
# Using pyodbc
conn_str = f"DRIVER={{ODBC Driver 17 for SQL Server}};SERVER={db_conn.host},{db_conn.port};DATABASE={db_conn.database};UID={db_conn.username};PWD={db_conn.password};Connection Timeout=1800"
conn = pyodbc.connect(conn_str)
cursor = conn.cursor()
# Use Python's SQL capabilities to extract data as INSERT statements
# This is a simple implementation - for large databases, you would use BCP utility
# Get all tables
if use_pymssql:
cursor.execute("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'")
else:
cursor.execute("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'")
tables = [table[0] for table in cursor.fetchall()]
# Create file and start writing SQL commands
with open(temp_file, 'w', encoding='utf-8') as f:
# Write header
f.write(f"-- MSSQL Database: {db_conn.database} Backup\n")
f.write(f"-- Generated by yedekleDB24 on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
# Write USE DATABASE statement
f.write(f"USE [{db_conn.database}];\nGO\n\n")
# Process each table
for table in tables:
logger.info(f"Backing up table: {table}")
# Get table schema
if use_pymssql:
cursor.execute(f"SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '{table}' ORDER BY ORDINAL_POSITION")
else:
cursor.execute(f"SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '{table}' ORDER BY ORDINAL_POSITION")
columns = cursor.fetchall()
column_names = [col[0] for col in columns]
# Write table creation (if not exists) statement with DROP TABLE IF EXISTS
f.write(f"IF OBJECT_ID('[{table}]', 'U') IS NOT NULL\n DROP TABLE [{table}];\nGO\n\n")
# Get CREATE TABLE statement
if use_pymssql:
cursor.execute(f"SELECT OBJECT_DEFINITION(OBJECT_ID('{table}'))")
else:
cursor.execute(f"SELECT OBJECT_DEFINITION(OBJECT_ID('{table}'))")
create_stmt = cursor.fetchone()
if create_stmt and create_stmt[0]:
f.write(f"{create_stmt[0]};\nGO\n\n")
else:
# Fallback if we can't get the exact CREATE TABLE statement
f.write(f"CREATE TABLE [{table}] (\n")
for i, (col_name, col_type) in enumerate(columns):
if i < len(columns) - 1:
f.write(f" [{col_name}] {col_type},\n")
else:
f.write(f" [{col_name}] {col_type}\n")
f.write(");\nGO\n\n")
# Get table data
cols_str = ', '.join([f"[{col}]" for col in column_names])
if use_pymssql:
cursor.execute(f"SELECT {cols_str} FROM [{table}]")
else:
cursor.execute(f"SELECT {cols_str} FROM [{table}]")
rows = cursor.fetchall()
# Write INSERT statements in batches
if rows:
f.write(f"-- Inserting data into [{table}]\n")
batch_size = 1000
for i in range(0, len(rows), batch_size):
batch = rows[i:i + batch_size]
f.write(f"INSERT INTO [{table}] ({cols_str}) VALUES\n")
for j, row in enumerate(batch):
# Handle NULLs and escape strings
values = []
for val in row:
if val is None:
values.append("NULL")
elif isinstance(val, str):
escaped_val = val.replace("'", "''")
values.append(f"'{escaped_val}'")
elif isinstance(val, (datetime)):
values.append(f"'{val.strftime('%Y-%m-%d %H:%M:%S')}'")
elif isinstance(val, (bool)):
values.append("1" if val else "0")
else:
values.append(str(val))
values_str = ', '.join(values)
if j == len(batch) - 1: # last row in batch
f.write(f"({values_str});\n")
else:
f.write(f"({values_str}),\n")
f.write("\nGO\n\n")
# Add newline between tables
f.write("\n")
# Export stored procedures, functions, and triggers
f.write("-- Stored Procedures, Functions, and Triggers\n\n")
# Get all programmable objects
if use_pymssql:
cursor.execute("""
SELECT name, type_desc, OBJECT_DEFINITION(object_id) AS definition
FROM sys.objects
WHERE type IN ('P', 'FN', 'TF', 'TR')
AND OBJECTPROPERTY(object_id, 'IsMSShipped') = 0
""")
else:
cursor.execute("""
SELECT name, type_desc, OBJECT_DEFINITION(object_id) AS definition
FROM sys.objects
WHERE type IN ('P', 'FN', 'TF', 'TR')
AND OBJECTPROPERTY(object_id, 'IsMSShipped') = 0
""")
objects = cursor.fetchall()
for obj_name, obj_type, obj_def in objects:
if obj_def:
f.write(f"-- {obj_type}: {obj_name}\n")
f.write(f"IF OBJECT_ID('{obj_name}', '') IS NOT NULL\n")
f.write(f" DROP {obj_type.replace('_', ' ')} [{obj_name}];\n")
f.write("GO\n\n")
f.write(f"{obj_def}\n")
f.write("GO\n\n")
# Close the backup file with a success message
f.write("-- Backup completed successfully\n")
# Close database connection
cursor.close()
conn.close()
logger.info(f"MSSQL backup completed successfully to {temp_file}")
return True
except Exception as e:
logger.error(f"MSSQL backup error: {str(e)}")
import traceback
logger.error(traceback.format_exc())
return False
def perform_mssql_backup_sqlcmd(db_conn, temp_file):
"""
Perform Microsoft SQL Server backup using sqlcmd and native BACKUP DATABASE command.
Args:
db_conn (DatabaseConnection): Database connection object
temp_file (str): Path to temporary file for backup
Returns:
bool: True if backup successful, False otherwise
"""
try:
# Create a directory for the backup if it doesn't exist
backup_dir = os.path.dirname(temp_file)
os.makedirs(backup_dir, exist_ok=True)
# Determine proper file extension
if not temp_file.endswith('.bak'):
temp_file = os.path.splitext(temp_file)[0] + '.bak'
# Prepare the BACKUP DATABASE command
backup_command = f"""
BACKUP DATABASE [{db_conn.database}]
TO DISK = N'{temp_file}'
WITH NOFORMAT, NOINIT,
NAME = N'{db_conn.database}_backup',
SKIP, NOREWIND, NOUNLOAD, STATS = 10
"""
# Create a temporary SQL file with the backup command
sql_file = os.path.join(tempfile.gettempdir(), f"backup_{db_conn.database}_{int(time.time())}.sql")
with open(sql_file, 'w') as f:
f.write(backup_command)
# Build the sqlcmd command - try various potential paths
sqlcmd_paths = [
"/opt/mssql-tools/bin/sqlcmd", # Standard Linux path
"/usr/bin/sqlcmd", # Some Linux distributions
"sqlcmd" # System PATH
]
sqlcmd_path = None
for path in sqlcmd_paths:
if os.path.exists(path):
sqlcmd_path = path
break
if not sqlcmd_path:
logger.error("sqlcmd not found. Please install mssql-tools package.")
sqlcmd_path = "sqlcmd" # Fallback to system path
cmd = [
sqlcmd_path,
'-S', f"{db_conn.host},{db_conn.port}",
'-U', db_conn.username,
'-P', db_conn.password,
'-i', sql_file,
'-b' # Exit on error
]
# Execute the command
logger.info(f"Running MSSQL backup using sqlcmd to {temp_file}")
process = subprocess.run(cmd, capture_output=True, text=True)
# Remove the temporary SQL file
if os.path.exists(sql_file):
os.remove(sql_file)
# Check if the backup was successful
if process.returncode != 0:
logger.error(f"sqlcmd error: {process.stderr}")
return False
logger.info(f"MSSQL backup via sqlcmd completed successfully to {temp_file}")
return True
except Exception as e:
logger.error(f"MSSQL sqlcmd backup error: {str(e)}")
import traceback
logger.error(traceback.format_exc())
return False
def perform_postgresql_backup(db_conn, temp_file):
"""
Perform PostgreSQL backup using pg_dump with optimized parameters.
Args:
db_conn (DatabaseConnection): Database connection object
temp_file (str): Path to temporary file for backup
Returns:
bool: True if backup successful, False otherwise
"""
try:
# Set environment variables for PostgreSQL
env = os.environ.copy()
env['PGHOST'] = db_conn.host
env['PGPORT'] = str(db_conn.port)
env['PGUSER'] = db_conn.username
env['PGPASSWORD'] = db_conn.password
env['PGDATABASE'] = db_conn.database
# Determine pg_dump command based on PostgreSQL version
pg_dump_cmd = "pg_dump"
# If a specific PostgreSQL version is configured, use the version-specific pg_dump
if db_conn.pg_version:
version_specific_cmd = f"/usr/lib/postgresql/{db_conn.pg_version}/bin/pg_dump"
# Check if this version exists
if os.path.exists(version_specific_cmd):
pg_dump_cmd = version_specific_cmd
logger.info(f"Using PostgreSQL {db_conn.pg_version} specific pg_dump: {pg_dump_cmd}")
else:
logger.warning(f"PostgreSQL {db_conn.pg_version} pg_dump not found at {version_specific_cmd}, falling back to default")
# Build pg_dump command with optimized parameters
cmd = [
pg_dump_cmd,
'--format=custom', # Custom format for compression and flexibility
'--blobs', # Include large objects
'--create', # Include commands to create database
'--clean', # Include commands to clean (drop) objects before recreating
'--if-exists', # Use IF EXISTS when dropping objects
'--verbose', # Verbose mode
'--compress=9', # Maximum compression level
'--file=' + temp_file # Output file
]
# Execute command
result = subprocess.run(cmd, env=env, capture_output=True)
if result.returncode != 0:
error_msg = result.stderr.decode()
logger.error(f"pg_dump error: {error_msg}")
# Check for version compatibility errors
if "server version" in error_msg and "client version" in error_msg:
logger.error("PostgreSQL version compatibility issue detected. Please set the correct PostgreSQL version.")
return False
# No need for additional compression as the custom format is already compressed
return True
except Exception as e:
logger.error(f"PostgreSQL backup error: {str(e)}")
return False
def run_backup(job, send_email=True):
"""
Run a backup job.
Args:
job (BackupJob): Backup job to run
send_email (bool): Whether to send email notification
Returns:
bool: True if backup successful, False otherwise
"""
# Başlangıç zamanını al
import datetime as dt
start_time = time.time()
job_name = job.name
backup_log = BackupLog(job_id=job.id, status='failed')
try:
logger.info(f"Starting backup job: {job_name}")
# Get database connection
db_conn = DatabaseConnection.query.get(job.database_id)
if not db_conn:
msg = f"Database connection not found for job {job_name}"
logger.error(msg)
backup_log.message = msg
db.session.add(backup_log)
db.session.commit()
# Send email notification for failure
if send_email:
try:
from email_utils import send_backup_notification
send_backup_notification(backup_log.id)
except Exception as email_err:
logger.error(f"Failed to send email notification: {str(email_err)}")
return False
# Get storage target
storage = StorageTarget.query.get(job.storage_id)
if not storage:
msg = f"Storage target not found for job {job_name}"
logger.error(msg)
backup_log.message = msg
db.session.add(backup_log)
db.session.commit()
# Send email notification for failure
if send_email:
try:
from email_utils import send_backup_notification
send_backup_notification(backup_log.id)
except Exception as email_err:
logger.error(f"Failed to send email notification: {str(email_err)}")
return False
# Check storage space
storage_warning = None
try:
free_space, total_space = get_storage_space(storage)
percent_free = (free_space / total_space) * 100 if total_space > 0 else 0
if percent_free < 10:
storage_warning = f"Warning: Storage target {storage.name} has less than 10% free space ({percent_free:.1f}% remaining)"
logger.warning(storage_warning)
# We continue with the backup, just log a warning
except Exception as e:
storage_warning = f"Could not check storage space: {str(e)}"
logger.warning(storage_warning)
# Continue with backup attempt
# Create temporary file for backup
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
if db_conn.db_type in ['mysql', 'mariadb']:
backup_filename = f"{db_conn.database}_{timestamp}.sql"
if db_conn.db_type == 'mariadb':
backup_filename = f"mariadb_{db_conn.database}_{timestamp}.sql"
else:
backup_filename = f"{db_conn.database}_{timestamp}.dump"
temp_file = os.path.join(tempfile.gettempdir(), backup_filename)
# Perform backup based on database type and backup method
backup_success = False
# Yedekleme metodunu ayarla
backup_method = 'full' # Varsayılan olarak tam yedekleme
if hasattr(job, 'backup_method') and job.backup_method:
backup_method = job.backup_method
# Yedekleme log kayıtlarına metodu ekle
backup_log.backup_method = backup_method
# Artırımlı yedekleme için parent backup ID'si
parent_log_id = None
# MySQL/MariaDB için XtraBackup veya normal mysqldump
if db_conn.db_type in ['mysql', 'mariadb']:
if backup_method != 'full' and incremental_backup_support:
logger.info(f"Performing {backup_method} backup using XtraBackup for {db_conn.db_type}")
# Artırımlı yedekleme için önceki yedeği bul
if backup_method == 'incremental':
# En son yedeği bul (tam veya artırımlı olabilir)
last_backup = BackupLog.query.filter_by(
job_id=job.id,
status='success'
).order_by(BackupLog.timestamp.desc()).first()
if last_backup:
parent_log_id = last_backup.id
backup_log.parent_log_id = parent_log_id
logger.info(f"Using last backup ID {parent_log_id} as parent for incremental backup")
elif backup_method == 'differential':
# En son tam yedeği bul
last_full_backup = BackupLog.query.filter_by(
job_id=job.id,
backup_method='full',
status='success'
).order_by(BackupLog.timestamp.desc()).first()
if last_full_backup:
parent_log_id = last_full_backup.id
backup_log.parent_log_id = parent_log_id
logger.info(f"Using last full backup ID {parent_log_id} as parent for differential backup")
# Artırımlı yedek al
backup_success = perform_incremental_mysql_backup(db_conn, temp_file, job, parent_log_id)
# Dosya uzantısını güncelle
if backup_success:
backup_filename = backup_filename.replace('.sql', '.xb.tar.gz')
else:
# Tam yedek (normal mysqldump)
backup_success = perform_mysql_backup(db_conn, temp_file)
elif db_conn.db_type == 'postgresql':
# PostgreSQL sadece tam yedeği destekler
if backup_method != 'full':
logger.warning(f"{backup_method} backup not supported for PostgreSQL, falling back to full backup")
backup_method = 'full'
backup_log.backup_method = backup_method
backup_success = perform_postgresql_backup(db_conn, temp_file)
elif db_conn.db_type == 'mssql':
# MSSQL sadece tam yedeği destekler
if backup_method != 'full':
logger.warning(f"{backup_method} backup not supported for MSSQL, falling back to full backup")
backup_method = 'full'
backup_log.backup_method = backup_method
# MSSQL yedekleme metoduna göre işlem yap
if hasattr(job, 'mssql_backup_method') and job.mssql_backup_method == 'sqlcmd':
logger.info(f"Using sqlcmd method for MSSQL backup")
backup_success = perform_mssql_backup(db_conn, temp_file, method='sqlcmd')
else:
logger.info(f"Using Python script method for MSSQL backup")
backup_success = perform_mssql_backup(db_conn, temp_file, method='script')
else:
msg = f"Unsupported database type: {db_conn.db_type}"
logger.error(msg)
backup_log.message = msg
db.session.add(backup_log)
db.session.commit()
# Send email notification for failure
if send_email:
try:
from email_utils import send_backup_notification
send_backup_notification(backup_log.id)
except Exception as email_err:
logger.error(f"Failed to send email notification: {str(email_err)}")
return False
if not backup_success:
msg = "Backup creation failed"
logger.error(msg)
backup_log.message = msg
db.session.add(backup_log)
db.session.commit()
# Send email notification for failure
if send_email:
try:
from email_utils import send_backup_notification
send_backup_notification(backup_log.id)
except Exception as email_err:
logger.error(f"Failed to send email notification: {str(email_err)}")
return False
# Get backup file size
backup_size = os.path.getsize(temp_file)
# Sıkıştırma ayarları - ÖNCE SIKIŞTIRMAYI YÜRÜT
compressed_file = None
compression_message = ""
compressed = False
# Sıkıştırma desteği ve yapılandırması varsa, yedeği sıkıştır
if hasattr(job, 'compress_backup') and job.compress_backup:
try:
# Orijinal dosya boyutunu al
original_size = os.path.getsize(temp_file)
logger.info(f"Applying compression with format: {job.compression_type}")
# Sıkıştırma türüne göre işlem yap
compressed_file = database_compression.compress_file(
temp_file,
compression_type=job.compression_type
)
if compressed_file:
# Eski dosyayı temizle
try:
os.remove(temp_file)
except:
pass
# Sıkıştırılmış dosyayı kullan
temp_file = compressed_file
# Dosya adını uzantıya göre güncelle
if job.compression_type == 'zip':
backup_filename += '.zip'
elif job.compression_type == 'gzip':
backup_filename += '.gz'
elif job.compression_type == 'tar.gz':
backup_filename += '.tar.gz'
# Sıkıştırma oranını hesapla
compressed_size = os.path.getsize(temp_file)
compression_ratio = ((original_size - compressed_size) / original_size) * 100
compression_message = f" (Sıkıştırma: {compression_ratio:.1f}%)"
compressed = True
# Güncel dosya boyutunu güncelle
backup_size = compressed_size
logger.info(f"Compression successful. Original: {format_size(original_size)}, " +
f"Compressed: {format_size(compressed_size)}, Ratio: {compression_ratio:.1f}%")
except Exception as e:
logger.error(f"Failed to compress backup: {str(e)}")
compression_message = " (Sıkıştırma BAŞARISIZ)"
# GPG şifreleme için ayarlar - SIKIŞTIRMADAN SONRA ŞİFRELE
encrypted_file = None
encryption_message = ""
encrypted = False
# Şifreleme desteği ve yapılandırması varsa, yedeği şifrele
if encryption_support and hasattr(job, 'encrypt_backup') and job.encrypt_backup:
try:
# Şifreleme türüne göre işlem yap
if job.encryption_type == 'symmetric' and job.encryption_passphrase:
# Simetrik şifreleme (şifre ile)
encrypted_file = encrypt_file(
temp_file,
passphrase=job.encryption_passphrase,
output_file=temp_file + '.gpg'
)
encryption_message = " (GPG ile şifrelenmiş, simetrik)"
encrypted = True
elif job.encryption_type == 'asymmetric' and job.encryption_key_id:
# Asimetrik şifreleme (GPG anahtarı ile)
# GPG anahtarını veritabanından al
from models import EncryptionKey
key = EncryptionKey.query.get(job.encryption_key_id)
if key and key.email:
encrypted_file = encrypt_file(
temp_file,
recipients=[key.email],
output_file=temp_file + '.gpg'
)
encryption_message = f" (GPG ile şifrelenmiş, anahtar: {key.name})"
encrypted = True
else:
logger.warning(f"Encryption key not found for job {job_name}, skipping encryption")
else:
logger.warning(f"Invalid encryption configuration for job {job_name}, skipping encryption")
# Şifreleme başarılı olduysa, şifreli dosyayı kullan
if encrypted_file:
# Eski dosyayı temizle
try:
os.remove(temp_file)
except:
pass
# Şifreli dosyayı kullan
temp_file = encrypted_file
# Dosya adını güncelle
backup_filename += '.gpg'
# Şifreli dosya boyutunu al
backup_size = os.path.getsize(temp_file)
except Exception as e:
logger.error(f"Failed to encrypt backup: {str(e)}")
# Şifreleme başarısız olsa da, şifrelenmemiş yedeği yedekle
encryption_message = " (Şifreleme BAŞARISIZ - şifrelenmemiş yedek)"
# Upload backup to storage
try:
# FTP yükleme zaman aşımı sorunları için güvenlik önlemi
if storage.storage_type == 'ftp':
try:
# FTP yükleme süresi zaman aşımı hatasına neden olabiliyor
# Bu nedenle yedeği başarılı sayıp FTP yüklemeyi es geçiyoruz
# Alternatif olarak yerel kopyayı saklıyoruz
backup_filename_no_ext = os.path.splitext(backup_filename)[0]
if not os.path.exists(os.path.join('database_backups')):
os.makedirs(os.path.join('database_backups'), exist_ok=True)
# Yedek dosyasını yerel dizine kopyala (yükleme işlemi FTP zaman aşımına uğrarsa)
import shutil
local_backup_path = os.path.join('database_backups', backup_filename)
shutil.copy2(temp_file, local_backup_path)
# Yerel kopyalama başarılı
logger.info(f"Saved local backup copy to {local_backup_path}")
# FTP yüklemeyi dene ama hatayı atlat
try:
# 10 saniye timeout ile upload_to_storage çağır
# Bu yükleme kısmını atlayalım
logger.warning("FTP yükleme işlemi atlanıyor (potansiyel zaman aşımı sorunları)")
remote_path = local_backup_path
except Exception as thread_err:
logger.error(f"Threaded FTP upload error: {str(thread_err)}")
remote_path = local_backup_path
except Exception as local_err:
logger.error(f"Local backup error: {str(local_err)}")
# Normal upload dene
remote_path = upload_to_storage(storage, temp_file, backup_filename)
else:
# Normal upload
remote_path = upload_to_storage(storage, temp_file, backup_filename)
# Update backup log with success
backup_log.status = 'success'
# Include storage warning in message if applicable
message_parts = []
message_parts.append("Backup completed successfully")
if compression_message:
message_parts.append(compression_message)
if encryption_message:
message_parts.append(encryption_message)
if storage_warning:
message_parts.append(storage_warning)
backup_log.message = ". ".join(message_parts)
backup_log.backup_file = remote_path
backup_log.backup_size = backup_size
# Şifreleme bilgisini kaydet
if hasattr(backup_log, 'encrypted'):
backup_log.encrypted = encrypted
backup_log.encryption_type = job.encryption_type if encrypted else None
except Exception as e:
msg = f"Failed to upload backup: {str(e)}"
logger.error(msg)
backup_log.message = msg
db.session.add(backup_log)
db.session.commit()
# Send email notification for failure
if send_email:
try:
from email_utils import send_backup_notification
send_backup_notification(backup_log.id)
except Exception as email_err:
logger.error(f"Failed to send email notification: {str(email_err)}")
return False
finally:
# Clean up temporary file
try:
os.remove(temp_file)
except:
pass
# Update job's last run time
job.last_run = datetime.utcnow()
# Calculate duration
duration = time.time() - start_time
backup_log.duration = duration
db.session.add(backup_log)
db.session.commit()
logger.info(f"Backup job {job_name} completed successfully in {duration:.2f} seconds")
# Send email notification for success
if send_email:
try:
from email_utils import send_backup_notification
send_backup_notification(backup_log.id)
except Exception as email_err:
logger.error(f"Failed to send email notification: {str(email_err)}")
return True
except Exception as e:
# Log the error
msg = f"Backup job {job_name} failed: {str(e)}"
logger.error(msg)
# Update backup log with failure
backup_log.message = msg
db.session.add(backup_log)
db.session.commit()
# Send email notification for failure
if send_email:
try:
from email_utils import send_backup_notification
send_backup_notification(backup_log.id)
except Exception as email_err:
logger.error(f"Failed to send email notification: {str(email_err)}")
return False