\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/windows_sql_socket_agent.py |
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Windows SQL Server Yedekleme Socket Agent'ı
-------------------------------------------
Bu script Windows SQL Server üzerinde servis olarak çalışır ve socket üzerinden komut alır.
Socket'ten aldığı bilgilere göre SQL Server veritabanı yedeği alır,
sıkıştırır ve FTP sunucusuna yükler.
Kullanım:
- Windows'da servis olarak çalıştırılır
- TCP/IP socket üzerinden komut dinler
- Bu komutlar: veritabanı adı, MSSQL sunucu bilgisi, FTP bağlantı bilgileri
- Yerel temp dizinine yedeği alır
- Yedeği sıkıştırır
- FTP sunucusuna aktarır
- Sonuç bilgisini socket üzerinden döndürür
"""
import os
import sys
import time
import shutil
import logging
import socket
import subprocess
import tempfile
import json
import zipfile
import ftplib
import psutil
import signal
import threading
import win32serviceutil
import win32service
import win32event
import servicemanager
import socket
from pathlib import Path
from datetime import datetime
# Loglama ayarları
LOG_FILE = "C:\\yedekledb24\\sql_socket_agent.log"
LOG_DIR = os.path.dirname(LOG_FILE)
if not os.path.exists(LOG_DIR):
try:
os.makedirs(LOG_DIR, exist_ok=True)
except:
LOG_FILE = os.path.join(tempfile.gettempdir(), "sql_socket_agent.log")
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(LOG_FILE),
logging.StreamHandler()
]
)
logger = logging.getLogger("win_sql_socket_agent")
# Agent yapılandırması - varsayılan değerler
# Bu değerler agent_config.ini dosyasından okunabilir (varsa)
CONFIG = {
"socket_host": "0.0.0.0", # Tüm arayüzlerden dinle
"socket_port": 9090, # TCP 9090 portunu kullan
"temp_dir": "C:\\temp\\sqlbackups",
"min_free_space_gb": 10, # GB cinsinden minimum boş alan
"sqlcmd_path": r"C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\SQLCMD.EXE",
"max_connections": 5, # Aynı anda maksimum istemci sayısı
"socket_timeout": 3600 # 1 saat (büyük veritabanları için)
}
# agent_config.ini dosyasından yapılandırma yükle (varsa)
try:
import configparser
import os.path
# Uygulama dizini veya çalışma dizinindeki config dosyasını kontrol et
config_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'agent_config.ini')
if not os.path.exists(config_file):
config_file = 'agent_config.ini'
if os.path.exists(config_file):
logger.info(f"Yapılandırma dosyası yükleniyor: {config_file}")
config = configparser.ConfigParser()
config.read(config_file)
if 'Agent' in config:
agent_config = config['Agent']
# String ayarları
if 'SocketHost' in agent_config:
CONFIG["socket_host"] = agent_config['SocketHost']
if 'TempDir' in agent_config:
CONFIG["temp_dir"] = agent_config['TempDir']
if 'SqlcmdPath' in agent_config:
CONFIG["sqlcmd_path"] = agent_config['SqlcmdPath']
# Integer ayarları
if 'SocketPort' in agent_config:
CONFIG["socket_port"] = int(agent_config['SocketPort'])
if 'MinFreeSpaceGB' in agent_config:
CONFIG["min_free_space_gb"] = int(agent_config['MinFreeSpaceGB'])
if 'MaxConnections' in agent_config:
CONFIG["max_connections"] = int(agent_config['MaxConnections'])
if 'SocketTimeout' in agent_config:
CONFIG["socket_timeout"] = int(agent_config['SocketTimeout'])
# Log ayarlarını güncelle
if 'Logging' in config:
log_config = config['Logging']
if 'LogFile' in log_config:
# Var olan log handler'ları temizle
for handler in logger.handlers[:]:
logger.removeHandler(handler)
# Log dizinini oluştur (gerekirse)
log_file = log_config['LogFile']
log_dir = os.path.dirname(log_file)
if log_dir and not os.path.exists(log_dir):
os.makedirs(log_dir, exist_ok=True)
# Yeni handler'ları yapılandır
logging.basicConfig(
level=getattr(logging, log_config.get('LogLevel', 'INFO')),
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(log_file),
logging.StreamHandler()
]
)
logger = logging.getLogger("win_sql_socket_agent")
logger.info("Yapılandırma başarıyla yüklendi")
except Exception as e:
logger.warning(f"Yapılandırma dosyası yüklenemedi: {str(e)}")
logger.warning("Varsayılan yapılandırma kullanılıyor...")
def check_disk_space(path, min_gb=10):
"""
Belirtilen dizinde yeterli boş alan olup olmadığını kontrol eder.
Args:
path (str): Kontrol edilecek dizin
min_gb (int): Minimum GB cinsinden boş alan
Returns:
bool: Yeterli alan varsa True, yoksa False
"""
# Dizin yoksa oluştur
os.makedirs(path, exist_ok=True)
# Disk alanını kontrol et
free_bytes = psutil.disk_usage(path).free
free_gb = free_bytes / (1024**3) # Byte -> GB
logger.info(f"Disk kontrolü: {path} dizininde {free_gb:.2f} GB boş alan var.")
return free_gb >= min_gb
def perform_sql_backup(db_name, backup_file, server="localhost"):
"""
SQL Server veritabanının yedeğini alır.
Args:
db_name (str): Veritabanı adı
backup_file (str): Yedek dosyasının tam yolu
server (str): SQL Server adı veya IP adresi
Returns:
bool: Başarılı ise True, değilse False
"""
# Yedekleme SQL komutu
backup_command = f"""
BACKUP DATABASE [{db_name}]
TO DISK = N'{backup_file}'
WITH NOFORMAT, NOINIT,
NAME = N'{db_name}_backup',
SKIP, NOREWIND, NOUNLOAD, STATS = 10
"""
# SQL dosyası oluştur
sql_file = os.path.join(tempfile.gettempdir(), f"backup_{db_name}_{int(time.time())}.sql")
with open(sql_file, 'w') as f:
f.write(backup_command)
# SQLCMD komutunu belirle
sqlcmd_path = CONFIG["sqlcmd_path"]
if not os.path.exists(sqlcmd_path):
# Alternatif konumları dene
alt_paths = [
r"C:\Program Files\Microsoft SQL Server\110\Tools\Binn\SQLCMD.EXE", # SQL 2012
r"C:\Program Files\Microsoft SQL Server\120\Tools\Binn\SQLCMD.EXE", # SQL 2014
r"C:\Program Files\Microsoft SQL Server\130\Tools\Binn\SQLCMD.EXE", # SQL 2016
r"C:\Program Files\Microsoft SQL Server\140\Tools\Binn\SQLCMD.EXE", # SQL 2017
r"C:\Program Files\Microsoft SQL Server\150\Tools\Binn\SQLCMD.EXE", # SQL 2019
"sqlcmd.exe" # PATH'te varsa
]
for path in alt_paths:
if os.path.exists(path):
sqlcmd_path = path
break
if not os.path.exists(sqlcmd_path) and sqlcmd_path != "sqlcmd.exe":
sqlcmd_path = "sqlcmd.exe" # Son çare
# SQLCMD ile yedeği al
try:
logger.info(f"SQL Backup başlatılıyor: {db_name} ({server})")
cmd = [
sqlcmd_path,
'-S', server,
'-E', # Windows Authentication
'-i', sql_file,
'-b' # Hata durumunda çıkış
]
process = subprocess.run(cmd, capture_output=True, text=True, timeout=3600)
# SQL dosyasını temizle
if os.path.exists(sql_file):
os.remove(sql_file)
# Başarı kontrolü
if process.returncode != 0:
logger.error(f"SQL Backup hatası: {process.stderr}")
return False, process.stderr
# Dosya varlığını ve boyutunu kontrol et
if not os.path.exists(backup_file):
error_msg = "Yedek dosyası oluşturulamadı."
logger.error(error_msg)
return False, error_msg
if os.path.getsize(backup_file) == 0:
error_msg = "Yedek dosyası boş (0 byte)."
logger.error(error_msg)
return False, error_msg
file_size = os.path.getsize(backup_file)
logger.info(f"SQL Backup başarılı: {db_name} -> {backup_file} ({file_size/1024/1024:.2f} MB)")
return True, f"Yedekleme başarılı: {file_size/1024/1024:.2f} MB"
except subprocess.TimeoutExpired:
logger.error(f"SQL Backup zaman aşımı: {db_name}")
return False, "Yedekleme zaman aşımı - 1 saat"
except Exception as e:
logger.error(f"SQL Backup işleminde hata: {str(e)}")
# SQL dosyasını temizlemeyi dene
if os.path.exists(sql_file):
os.remove(sql_file)
return False, str(e)
def compress_file(input_file, output_file=None):
"""
Dosyayı ZIP formatında sıkıştırır
Args:
input_file (str): Sıkıştırılacak dosya
output_file (str, optional): Çıktı dosyası (None ise input + .zip)
Returns:
tuple: (bool, str) - (Başarılı ise True ve dosya yolu, değilse False ve hata mesajı)
"""
if not output_file:
output_file = input_file + '.zip'
try:
logger.info(f"Dosya sıkıştırılıyor: {input_file}")
original_size = os.path.getsize(input_file)
with zipfile.ZipFile(output_file, 'w', zipfile.ZIP_DEFLATED) as zipf:
zipf.write(input_file, arcname=os.path.basename(input_file))
compressed_size = os.path.getsize(output_file)
compression_ratio = ((original_size - compressed_size) / original_size) * 100
logger.info(f"Sıkıştırma tamamlandı. Oran: {compression_ratio:.2f}% kazanç")
logger.info(f"Orijinal: {original_size/1024/1024:.2f} MB, "
f"Sıkıştırılmış: {compressed_size/1024/1024:.2f} MB")
return True, output_file
except Exception as e:
logger.error(f"Dosya sıkıştırma hatası: {str(e)}")
return False, str(e)
def upload_to_ftp(local_file, ftp_config):
"""
Dosyayı FTP sunucusuna yükler
Args:
local_file (str): Yerel dosya yolu
ftp_config (dict): FTP yapılandırması (host, port, username, password, path)
Returns:
tuple: (bool, str) - (Başarılı ise True ve uzak dosya yolu, değilse False ve hata mesajı)
"""
try:
host = ftp_config.get('host', '')
port = ftp_config.get('port', 21)
username = ftp_config.get('username', '')
password = ftp_config.get('password', '')
remote_dir = ftp_config.get('path', '')
if not host:
return False, "FTP sunucu adresi belirtilmedi"
logger.info(f"FTP yükleme başlatılıyor: {local_file} -> {host}:{port}/{remote_dir}")
# Dosya adını al
filename = os.path.basename(local_file)
# Uzak yolu belirle
if remote_dir and not remote_dir.endswith('/'):
remote_dir += '/'
remote_path = remote_dir + filename
# FTP bağlantısı
ftp = ftplib.FTP()
ftp.connect(host, port)
ftp.login(username, password)
# Uzak dizin kontrolü
if remote_dir:
# Dizin yapısını oluştur (gerekirse)
dirs = remote_dir.strip('/').split('/')
for d in dirs:
if d:
try:
ftp.mkd(d)
except:
# Dizin zaten var veya oluşturulamadı
pass
ftp.cwd(d)
# Kök dizine dön
ftp.cwd('/')
# Dosyayı yükle
with open(local_file, 'rb') as file:
ftp.storbinary(f'STOR {remote_path}', file, blocksize=8192)
ftp.quit()
logger.info(f"FTP yükleme başarılı: {remote_path}")
return True, remote_path
except Exception as e:
logger.error(f"FTP yükleme hatası: {str(e)}")
return False, str(e)
def handle_client(client_socket):
"""
İstemci bağlantısını işler
Args:
client_socket: Bağlantı soketi
"""
agent_key = None
# Sonuç şablonu
result = {
'success': False,
'message': '',
'steps': {
'backup': {'success': False, 'message': ''},
'compress': {'success': False, 'message': ''},
'upload': {'success': False, 'message': ''}
},
'backup_file': '',
'backup_size': 0,
'compressed_size': 0,
'timestamp': datetime.now().isoformat()
}
try:
# Socket'i yapılandır
client_socket.settimeout(CONFIG["socket_timeout"])
# İstemciden gelen veriyi oku
data = b""
while True:
chunk = client_socket.recv(4096)
if not chunk:
break
data += chunk
# JSON veri sonunu kontrol et
if data.endswith(b'}\n'):
break
if not data:
result['message'] = "Veri alınamadı"
client_socket.sendall(json.dumps(result).encode() + b'\n')
return
# JSON verisini çözümle
try:
payload = json.loads(data.decode())
except json.JSONDecodeError as e:
result['message'] = f"Geçersiz JSON verisi: {str(e)}"
client_socket.sendall(json.dumps(result).encode() + b'\n')
return
# Parametreleri al
db_name = payload.get('database', '')
db_server = payload.get('server', 'localhost')
ftp_config = payload.get('ftp', {})
agent_key = payload.get('agent_key', '')
# Agent key doğrulama - güvenli bir özellik olarak kullanılabilir
if 'required_agent_key' in CONFIG and CONFIG['required_agent_key']:
if not agent_key or agent_key != CONFIG['required_agent_key']:
error_msg = "Geçersiz agent anahtarı veya yetkilendirme hatası"
logger.warning(f"{error_msg}: {agent_key}")
result['message'] = error_msg
client_socket.sendall(json.dumps(result).encode() + b'\n')
return
if not db_name:
result['message'] = "Veritabanı adı belirtilmedi"
client_socket.sendall(json.dumps(result).encode() + b'\n')
return
logger.info(f"Yedekleme görevi alındı: {db_name} ({db_server})")
# Temp dizini kontrolü
temp_dir = CONFIG['temp_dir']
os.makedirs(temp_dir, exist_ok=True)
# Disk alanı kontrolü
if not check_disk_space(temp_dir, CONFIG['min_free_space_gb']):
error_msg = f"Yetersiz disk alanı: {temp_dir} dizininde en az {CONFIG['min_free_space_gb']} GB boş alan gerekli"
logger.error(error_msg)
result['message'] = error_msg
client_socket.sendall(json.dumps(result).encode() + b'\n')
return
# Yedek dosya yolunu oluştur
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_file = os.path.join(temp_dir, f"{db_name}_{timestamp}.bak")
# ADIM 1: SQL Yedekleme
backup_success, backup_message = perform_sql_backup(db_name, backup_file, db_server)
result['steps']['backup'] = {
'success': backup_success,
'message': backup_message
}
if not backup_success:
result['message'] = f"Yedekleme hatası: {backup_message}"
client_socket.sendall(json.dumps(result).encode() + b'\n')
return
# Yedek dosya boyutunu kaydet
backup_size = os.path.getsize(backup_file)
result['backup_size'] = backup_size
result['backup_file'] = os.path.basename(backup_file)
# ADIM 2: Dosya Sıkıştırma
compress_success, compress_result = compress_file(backup_file)
result['steps']['compress'] = {
'success': compress_success,
'message': compress_result if not compress_success else "Sıkıştırma başarılı"
}
if not compress_success:
result['message'] = f"Sıkıştırma hatası: {compress_result}"
client_socket.sendall(json.dumps(result).encode() + b'\n')
# Orjinal yedeği temizle
try:
os.remove(backup_file)
except:
pass
return
# Orijinal dosyayı sil, sıkıştırılmış dosyayı kullan
compressed_file = compress_result
try:
os.remove(backup_file)
except:
pass
# Sıkıştırılmış dosya boyutunu kaydet
compressed_size = os.path.getsize(compressed_file)
result['compressed_size'] = compressed_size
result['backup_file'] = os.path.basename(compressed_file)
# ADIM 3: FTP Yükleme
if ftp_config:
upload_success, upload_result = upload_to_ftp(compressed_file, ftp_config)
result['steps']['upload'] = {
'success': upload_success,
'message': upload_result if not upload_success else "FTP yükleme başarılı"
}
if not upload_success:
result['message'] = f"FTP yükleme hatası: {upload_result}"
# Yedek başarılı, FTP hatası
result['success'] = True
client_socket.sendall(json.dumps(result).encode() + b'\n')
return
else:
# FTP yapılandırması yoksa bu adımı atla
result['steps']['upload'] = {
'success': True,
'message': "FTP yapılandırması belirtilmediği için atlandı"
}
# Tüm adımlar başarılı
result['success'] = True
result['message'] = "Yedekleme, sıkıştırma ve yükleme başarılı"
# Sıkıştırılmış dosyayı temizle
try:
os.remove(compressed_file)
except:
pass
# Sonucu gönder
client_socket.sendall(json.dumps(result).encode() + b'\n')
except Exception as e:
logger.error(f"İstemci işleme hatası: {str(e)}")
# Hata sonucunu gönder
result['message'] = f"Beklenmeyen hata: {str(e)}"
try:
client_socket.sendall(json.dumps(result).encode() + b'\n')
except:
pass
finally:
# Soketi kapat
try:
client_socket.close()
except:
pass
# Socket sunucusu için global değişkenler
server = None
is_server_running = False
def stop_socket_server():
"""
Socket sunucusunu durdurur
"""
global server, is_server_running
logger.info("Socket sunucusu durduruluyor...")
# Sunucu çalışma bayrağını kapat
is_server_running = False
# Sunucu soketini kapat
if server:
try:
server.close()
logger.info("Socket sunucusu kapatıldı.")
except Exception as e:
logger.error(f"Socket kapatılırken hata: {str(e)}")
def socket_server():
"""
Socket sunucusunu başlatır ve istemci bağlantılarını kabul eder
"""
global server, is_server_running
# Sunucu çalışma bayrağını aç
is_server_running = True
try:
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Timeout ayarla - uygulamayı kilitlemeyi önle
server.settimeout(1.0) # 1 saniye timeout
try:
server.bind((CONFIG["socket_host"], CONFIG["socket_port"]))
server.listen(CONFIG["max_connections"])
logger.info(f"Socket sunucu başlatıldı. {CONFIG['socket_host']}:{CONFIG['socket_port']} adresinde dinleniyor.")
while is_server_running:
try:
client_sock, address = server.accept()
logger.info(f"Yeni bağlantı: {address[0]}:{address[1]}")
# Her istemci için yeni bir thread başlat
client_thread = threading.Thread(target=handle_client, args=(client_sock,))
client_thread.daemon = True
client_thread.start()
except socket.timeout:
# Timeout - normal, devam et ve is_server_running bayrağını kontrol et
continue
except Exception as e:
if is_server_running: # Sadece sunucu çalışıyorsa hata mesajı göster
logger.error(f"Bağlantı kabul hatası: {str(e)}")
logger.info("Socket sunucusu döngüsü sonlandı.")
except Exception as e:
logger.error(f"Socket bind/listen hatası: {str(e)}")
except Exception as e:
logger.error(f"Socket sunucusu oluşturma hatası: {str(e)}")
finally:
# Sunucuyu kapat
if server:
try:
server.close()
logger.info("Socket sunucusu kapatıldı.")
except Exception as e:
logger.error(f"Socket kapatılırken hata: {str(e)}")
# Sunucu çalışma bayrağını kapat
is_server_running = False
class SqlBackupService(win32serviceutil.ServiceFramework):
"""
Windows servisi olarak SQL Server Backup Socket Agent
"""
_svc_name_ = "SQLBackupSocketAgent"
_svc_display_name_ = "YedekleDB24 SQL Server Backup Agent"
_svc_description_ = "YedekleDB24 için SQL Server Yedekleme Socket Agent Servisi"
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
socket.setdefaulttimeout(60)
self.is_running = False
self.server_thread = None
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
# Servisi durduruyor olduğumuzu log'a yaz
logger.info("SQL Backup Socket Agent servisi durduruluyor...")
# Çalışma bayrağını kapat
self.is_running = False
# Durma olayını ayarla
win32event.SetEvent(self.hWaitStop)
# Server thread'i durdurmaya çalış
try:
stop_socket_server()
except Exception as e:
logger.error(f"Socket server durdurulurken hata: {str(e)}")
def SvcDoRun(self):
# Önce SERVICE_RUNNING olarak bildir (önemli - Windows servisi timeout'a düşmemesi için)
self.ReportServiceStatus(win32service.SERVICE_RUNNING)
# Başladığımızı log'a yaz
servicemanager.LogMsg(
servicemanager.EVENTLOG_INFORMATION_TYPE,
servicemanager.PYS_SERVICE_STARTED,
(self._svc_name_, '')
)
# Çalışma bayrağını aç
self.is_running = True
# Ana servis fonksiyonunu çağır
self.main()
def main(self):
"""
Servis ana döngüsü
"""
logger.info("SQL Backup Socket Agent servisi başlatıldı.")
# Socket sunucusunu ayrı thread'de başlat
self.server_thread = threading.Thread(target=socket_server)
self.server_thread.daemon = True
self.server_thread.start()
# Servis durdurana kadar çalış
while self.is_running:
# Durma sinyali kontrol et
if win32event.WaitForSingleObject(self.hWaitStop, 5000) == win32event.WAIT_OBJECT_0:
break
logger.info("SQL Backup Socket Agent servisi durduruldu.")
def main():
"""
Konsol uygulaması olarak çalıştırma
"""
if len(sys.argv) > 1:
win32serviceutil.HandleCommandLine(SqlBackupService)
return
# Servis olarak değil, konsol uygulaması olarak çalıştır
logger.info("SQL Backup Socket Agent konsol uygulaması olarak başlatıldı.")
logger.info("Bu uygulamayı bir Windows servisi olarak yüklemek için:")
logger.info("python windows_sql_socket_agent.py install")
logger.info("python windows_sql_socket_agent.py start")
# CTRL+C işleyici
def signal_handler(sig, frame):
logger.info("Uygulama durduruluyor...")
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
# Socket sunucusunu başlat
socket_server()
if __name__ == "__main__":
main()