771 lines
34 KiB
Python
771 lines
34 KiB
Python
import os
|
||
import io
|
||
import sys
|
||
import locale
|
||
import zipfile
|
||
import boto3
|
||
import tempfile
|
||
import traceback
|
||
from boto3.s3.transfer import TransferConfig
|
||
from django.utils.text import slugify
|
||
from datetime import datetime
|
||
import requests
|
||
import stat
|
||
# Add urllib3 import to disable SSL warnings
|
||
import urllib3
|
||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||
|
||
|
||
haric_dosya_uzantilari = ['.zip', ]
|
||
excluded_folders = ['venv', 'yedek', '.idea', '.sock', '.venv']
|
||
x = 1
|
||
|
||
|
||
def create_ssh_zip(ssh_manager, source_dir, zip_name, excluded_folders=[], excluded_extensions=[]):
|
||
"""SSH üzerinden uzak sunucuda zip dosyası oluşturur"""
|
||
|
||
remote_zip_path = f"/tmp/{zip_name}"
|
||
|
||
check_dir_command = f"test -d '{source_dir}' && echo 'exists' || echo 'not_exists'"
|
||
try:
|
||
stdout, stderr, status = ssh_manager.execute_command(check_dir_command)
|
||
if not status or stdout.strip() != "exists":
|
||
raise Exception(f"Kaynak dizin bulunamadı: {source_dir}")
|
||
except Exception as e:
|
||
raise Exception(f"Dizin kontrolü hatası: {str(e)}")
|
||
|
||
# Encoding değişkenini kontrol et
|
||
locale_command = "locale -a | grep -i utf"
|
||
stdout, stderr, status = ssh_manager.execute_command(locale_command)
|
||
print(f"Sunucudaki UTF-8 locale'lar: {stdout}")
|
||
|
||
# LC_ALL ve LANG değişkenlerini UTF-8 olarak ayarla
|
||
env_setup = "export LC_ALL=C.UTF-8 2>/dev/null || export LC_ALL=en_US.UTF-8 2>/dev/null || export LC_CTYPE=UTF-8; export LANG=C.UTF-8 2>/dev/null || export LANG=en_US.UTF-8;"
|
||
|
||
zip_check_command = f"{env_setup} which zip || command -v zip"
|
||
try:
|
||
stdout, stderr, status = ssh_manager.execute_command(zip_check_command)
|
||
if not status:
|
||
print("Zip komutu bulunamadı, kurulum deneniyor...")
|
||
if not install_zip_on_remote(ssh_manager):
|
||
raise Exception("Zip komutu uzak sunucuda bulunamadı ve kurulum başarısız oldu.")
|
||
except Exception as e:
|
||
raise Exception(f"Zip komutu kontrolü hatası: {str(e)}")
|
||
|
||
base_path = os.path.dirname(source_dir)
|
||
folder_to_zip = os.path.basename(source_dir)
|
||
|
||
exclude_args = ""
|
||
for folder in excluded_folders:
|
||
# zip'in exclude path'i, zip komutunun çalıştığı dizine göre olmalı.
|
||
# cd '{base_path}' yaptığımız için, exclude path'i '{folder_to_zip}/{folder}/*' şeklinde olmalı.
|
||
exclude_args += f" -x '{folder_to_zip}/{folder}/*'"
|
||
|
||
for ext in excluded_extensions:
|
||
exclude_args += f" -x '*{ext}'"
|
||
|
||
cleanup_command = f"rm -f '{remote_zip_path}'"
|
||
ssh_manager.execute_command(cleanup_command)
|
||
|
||
# UTF-8 desteği için -UN=UTF8 parametresi eklendi ve LC_ALL/LANG değişkenleri ayarlandı
|
||
zip_command = f"{env_setup} cd '{base_path}' && zip -UN=UTF8 -r '{remote_zip_path}' '{folder_to_zip}' {exclude_args}"
|
||
|
||
print(f"Çalıştırılan komut: {zip_command}")
|
||
|
||
try:
|
||
stdout, stderr, status = ssh_manager.execute_command(zip_command)
|
||
|
||
print(f"Zip komutu sonucu - Status: {status}, Stdout: {stdout}, Stderr: {stderr}")
|
||
|
||
check_command = f"test -f '{remote_zip_path}' && echo 'exists' || echo 'not_exists'"
|
||
stdout_check, stderr_check, status_check = ssh_manager.execute_command(check_command)
|
||
|
||
if not status_check or stdout_check.strip() != "exists":
|
||
error_details = f"Status: {status}, Stdout: {stdout}, Stderr: {stderr}"
|
||
raise Exception(f"Zip dosyası oluşturulamadı. Detaylar: {error_details}")
|
||
|
||
size_command = f"stat -c%s '{remote_zip_path}' 2>/dev/null || stat -f%z '{remote_zip_path}' 2>/dev/null || wc -c < '{remote_zip_path}'"
|
||
stdout_size, stderr_size, status_size = ssh_manager.execute_command(size_command)
|
||
|
||
file_size = 0
|
||
if status_size and stdout_size.strip().isdigit():
|
||
file_size = int(stdout_size.strip())
|
||
else:
|
||
ls_command = f"ls -la '{remote_zip_path}'"
|
||
stdout_ls, stderr_ls, status_ls = ssh_manager.execute_command(ls_command)
|
||
if status_ls:
|
||
print(f"Zip dosyası bilgileri: {stdout_ls}")
|
||
|
||
print(f"Zip dosyası başarıyla oluşturuldu: {remote_zip_path}, Boyut: {file_size}")
|
||
return remote_zip_path, file_size
|
||
|
||
except Exception as e:
|
||
cleanup_command = f"rm -f '{remote_zip_path}'"
|
||
ssh_manager.execute_command(cleanup_command)
|
||
raise e
|
||
|
||
|
||
def download_ssh_file(ssh_manager, remote_path, local_path):
|
||
"""SSH üzerinden dosya indirir"""
|
||
try:
|
||
print(f"Dosya indiriliyor: {remote_path} -> {local_path}")
|
||
|
||
local_dir = os.path.dirname(local_path)
|
||
if not os.path.exists(local_dir):
|
||
os.makedirs(local_dir, mode=0o755, exist_ok=True)
|
||
|
||
with ssh_manager.client.open_sftp() as sftp:
|
||
try:
|
||
file_stat = sftp.stat(remote_path)
|
||
print(f"Uzak dosya boyutu: {file_stat.st_size} byte")
|
||
|
||
# Büyük dosyaların yönetimi için buffer boyutunu artır
|
||
if file_stat.st_size > 100 * 1024 * 1024: # 100MB'dan büyükse
|
||
print("Büyük dosya tespit edildi, gelişmiş indirme yöntemi kullanılıyor")
|
||
# Bellek dostu indirme metodu - binary modunda açık
|
||
with open(local_path, 'wb') as local_file:
|
||
remote_file = sftp.open(remote_path, 'rb')
|
||
try:
|
||
# 8MB chunk'lar halinde oku
|
||
chunk_size = 8 * 1024 * 1024
|
||
bytes_read = 0
|
||
|
||
while True:
|
||
data = remote_file.read(chunk_size)
|
||
if not data:
|
||
break
|
||
local_file.write(data)
|
||
bytes_read += len(data)
|
||
print(f"İndiriliyor: {bytes_read / file_stat.st_size * 100:.1f}% tamamlandı")
|
||
finally:
|
||
remote_file.close()
|
||
else:
|
||
# Standart indirme metodu - küçük dosyalar için
|
||
sftp.get(remote_path, local_path)
|
||
except FileNotFoundError:
|
||
raise Exception(f"Uzak dosya bulunamadı: {remote_path}")
|
||
|
||
if os.path.exists(local_path):
|
||
local_size = os.path.getsize(local_path)
|
||
print(f"Dosya başarıyla indirildi. Local boyut: {local_size} byte")
|
||
return True
|
||
else:
|
||
raise Exception("Dosya indirildikten sonra bulunamadı")
|
||
|
||
except Exception as e:
|
||
print(f"Dosya indirme hatası: {e}")
|
||
if os.path.exists(local_path):
|
||
try:
|
||
os.remove(local_path)
|
||
except:
|
||
pass
|
||
return False
|
||
|
||
|
||
def cleanup_ssh_file(ssh_manager, remote_path):
|
||
"""SSH sunucusunda geçici dosyayı temizler"""
|
||
try:
|
||
cleanup_command = f"rm -f '{remote_path}'"
|
||
ssh_manager.execute_command(cleanup_command)
|
||
except Exception as e:
|
||
print(f"Temizleme hatası: {e}")
|
||
|
||
|
||
from ssh_manager.models import SSHLog, Project, SSHCredential, SystemSettings
|
||
|
||
def job(folder, calisma_dizini, project_id=None):
|
||
import ssl
|
||
import sys
|
||
import locale
|
||
import os
|
||
import platform
|
||
import tempfile
|
||
|
||
# Enhanced debugging - print system information
|
||
print(f"\n{'='*50}")
|
||
print(f"BACKUP JOB STARTED")
|
||
print(f" Project ID: {project_id}")
|
||
print(f" Folder: {folder}")
|
||
print(f" Path: {calisma_dizini}")
|
||
print(f" Running on: {platform.system()} {platform.release()}")
|
||
print(f" Python version: {platform.python_version()}")
|
||
print(f" Temp directory: {tempfile.gettempdir()}")
|
||
print(f" Current directory: {os.getcwd()}")
|
||
print(f" Docker environment: {'Yes' if os.path.exists('/.dockerenv') else 'No'}")
|
||
print(f" Directory listing for /tmp:")
|
||
try:
|
||
print(f" {os.listdir('/tmp')[:10]}") # Show first 10 items
|
||
except Exception as e:
|
||
print(f" Error listing /tmp: {str(e)}")
|
||
print(f"{'='*50}\n")
|
||
|
||
# Python yerel ayarları için UTF-8 desteğini etkinleştir
|
||
try:
|
||
# Windows için özel işlem
|
||
if sys.platform.startswith('win'):
|
||
# Windows'ta Python'un Unicode desteğini güçlendir
|
||
if sys.version_info >= (3, 7):
|
||
sys.stdout.reconfigure(encoding='utf-8')
|
||
else:
|
||
import codecs
|
||
sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer)
|
||
|
||
# Windows için locale ayarı
|
||
locale.setlocale(locale.LC_ALL, 'Turkish_Turkey.1254')
|
||
else:
|
||
# Unix/Linux için locale ayarı
|
||
try:
|
||
locale.setlocale(locale.LC_ALL, 'tr_TR.UTF-8')
|
||
except locale.Error:
|
||
try:
|
||
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
|
||
except locale.Error:
|
||
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
|
||
except Exception as locale_error:
|
||
print(f"Locale ayarı yapılamadı: {locale_error}")
|
||
|
||
# Geçerli encoding'i kontrol et
|
||
print(f"Sistem encoding: {sys.getdefaultencoding()}")
|
||
print(f"Locale encoding: {locale.getpreferredencoding(False)}")
|
||
print(f"File system encoding: {sys.getfilesystemencoding()}")
|
||
|
||
logs = []
|
||
|
||
if not folder or folder.strip() == "":
|
||
return {'success': False, 'message': 'Klasör adı boş olamaz', 'logs': logs}
|
||
|
||
if not calisma_dizini or calisma_dizini.strip() == "":
|
||
return {'success': False, 'message': 'Çalışma dizini boş olamaz', 'logs': logs}
|
||
|
||
if not project_id:
|
||
return {'success': False, 'message': 'Proje ID gerekli', 'logs': logs}
|
||
|
||
try:
|
||
project = Project.objects.get(id=project_id)
|
||
ssh_manager = project.ssh_credential.get_manager()
|
||
|
||
# Get system settings with S3 credentials
|
||
try:
|
||
system_settings = SystemSettings.objects.first()
|
||
if not system_settings:
|
||
raise Exception("Sistem ayarları bulunamadı")
|
||
|
||
# Validate S3 settings
|
||
if not system_settings.s3_access_key or not system_settings.s3_secret_key or not system_settings.s3_endpoint:
|
||
raise Exception("S3 ayarları eksik veya geçersiz. Lütfen sistem ayarlarını kontrol edin.")
|
||
except Exception as settings_error:
|
||
return {'success': False, 'message': f'Sistem ayarları yüklenemedi: {str(settings_error)}', 'logs': logs}
|
||
except Exception as e:
|
||
return {'success': False, 'message': f'SSH bağlantısı kurulamadı: {str(e)}', 'logs': logs}
|
||
|
||
config = {
|
||
'access_key': system_settings.s3_access_key,
|
||
'secret_key': system_settings.s3_secret_key,
|
||
'host_base': system_settings.s3_endpoint,
|
||
'bucket_location': system_settings.s3_region,
|
||
'use_https': True,
|
||
'check_ssl_certificate': False,
|
||
'multipart_chunk_size_mb': 50,
|
||
}
|
||
endpoint_url = f"https://{config['host_base']}"
|
||
region_name = config['bucket_location']
|
||
|
||
session = boto3.session.Session()
|
||
|
||
# Vultr Object Storage için özel konfigürasyon
|
||
client = session.client('s3',
|
||
region_name=region_name,
|
||
endpoint_url=endpoint_url,
|
||
aws_access_key_id=config['access_key'],
|
||
aws_secret_access_key=config['secret_key'],
|
||
use_ssl=config['use_https'],
|
||
verify=False,
|
||
config=boto3.session.Config(
|
||
signature_version='s3v4',
|
||
retries={'max_attempts': 3},
|
||
s3={
|
||
'addressing_style': 'virtual', # Changed from 'path' to 'virtual'
|
||
'payload_signing_enabled': False, # Changed from True to False to fix XAmzContentSHA256Mismatch
|
||
'chunked_encoding': False, # Vultr için önemli
|
||
'use_ssl': config['use_https']
|
||
}
|
||
)
|
||
)
|
||
|
||
def log_and_db(msg, status=True):
|
||
logs.append(msg)
|
||
if project_id:
|
||
try:
|
||
project = Project.objects.get(id=project_id)
|
||
SSHLog.objects.create(
|
||
ssh_credential=project.ssh_credential,
|
||
log_type='backup',
|
||
command=f'Backup: {folder}',
|
||
output=msg,
|
||
status=status
|
||
)
|
||
except Exception:
|
||
pass
|
||
|
||
log_and_db("<span style='color:#8bc34a'>S3 ayarları yüklendi.</span>")
|
||
|
||
log_and_db("<span style='color:#8bc34a'>S3 oturumu başlatıldı.</span>")
|
||
local_dt = datetime.now()
|
||
current_date = slugify(str(local_dt))
|
||
|
||
zip_dosya_adi = folder + "_" + current_date + ".zip"
|
||
output_zip = os.path.join("/tmp", zip_dosya_adi)
|
||
|
||
log_and_db(f"<span style='color:#bdbdbd'>SSH üzerinden arşiv oluşturuluyor...</span>")
|
||
print(f"Yedekleme işi başlatılıyor: Proje ID {project_id}, Klasör: {folder}, Çalışma Dizini: {calisma_dizini}")
|
||
|
||
# Dosya boyutu değişkenini tanımla, fonksiyonun en sonunda kullanılacak
|
||
file_size = 0
|
||
|
||
try:
|
||
# Önce tar ile dene, başarısız olursa zip'e geç
|
||
try:
|
||
tar_dosya_adi = folder + "_" + current_date + ".tar.gz"
|
||
log_and_db(f"<span style='color:#bdbdbd'>Tar ile yedekleme deneniyor...</span>")
|
||
print(f"Tar ile yedekleme deneniyor: {calisma_dizini}")
|
||
log_and_db(f"<span style='color:#bdbdbd'>Kaynak dizin: {calisma_dizini}</span>")
|
||
log_and_db(f"<span style='color:#bdbdbd'>Tar dosyası adı: {tar_dosya_adi}</span>")
|
||
|
||
remote_tar_path, file_size = create_tar_backup(
|
||
ssh_manager,
|
||
calisma_dizini,
|
||
tar_dosya_adi,
|
||
excluded_folders,
|
||
haric_dosya_uzantilari
|
||
)
|
||
|
||
log_and_db(f"<span style='color:#8bc34a'>Uzak sunucuda tar.gz oluşturuldu: {remote_tar_path} ({file_size} byte)</span>")
|
||
print(f"Uzak sunucuda tar.gz oluşturuldu: {remote_tar_path} ({file_size} byte)")
|
||
|
||
local_tar_path = os.path.join("/tmp", tar_dosya_adi)
|
||
log_and_db(f"<span style='color:#bdbdbd'>Tar dosyası indiriliyor: {local_tar_path}</span>")
|
||
print(f"Tar dosyası indiriliyor: {local_tar_path}")
|
||
|
||
if not download_ssh_file(ssh_manager, remote_tar_path, local_tar_path):
|
||
raise Exception("Tar dosyası indirilemedi")
|
||
|
||
log_and_db(f"<span style='color:#8bc34a'>Tar dosyası başarıyla indirildi</span>")
|
||
print("Tar dosyası başarıyla indirildi")
|
||
cleanup_ssh_file(ssh_manager, remote_tar_path)
|
||
output_zip = local_tar_path
|
||
|
||
except Exception as tar_error:
|
||
log_and_db(f"<span style='color:#ff9800'>Tar oluşturma başarısız: {str(tar_error)}</span>")
|
||
print(f"Tar oluşturma başarısız: {str(tar_error)}")
|
||
log_and_db(f"<span style='color:#bdbdbd'>Zip ile yedekleme deneniyor...</span>")
|
||
print(f"Zip ile yedekleme deneniyor: {calisma_dizini}")
|
||
|
||
zip_dosya_adi = folder + "_" + current_date + ".zip"
|
||
log_and_db(f"<span style='color:#bdbdbd'>Kaynak dizin: {calisma_dizini}</span>")
|
||
log_and_db(f"<span style='color:#bdbdbd'>Zip dosyası adı: {zip_dosya_adi}</span>")
|
||
|
||
remote_zip_path, file_size = create_ssh_zip(
|
||
ssh_manager,
|
||
calisma_dizini,
|
||
zip_dosya_adi,
|
||
excluded_folders,
|
||
haric_dosya_uzantilari
|
||
)
|
||
|
||
log_and_db(f"<span style='color:#8bc34a'>Uzak sunucuda zip oluşturuldu: {remote_zip_path} ({file_size} byte)</span>")
|
||
print(f"Uzak sunucuda zip oluşturuldu: {remote_zip_path} ({file_size} byte)")
|
||
|
||
local_zip_path = os.path.join("/tmp", zip_dosya_adi)
|
||
log_and_db(f"<span style='color:#bdbdbd'>Zip dosyası indiriliyor: {local_zip_path}</span>")
|
||
print(f"Zip dosyası indiriliyor: {local_zip_path}")
|
||
|
||
if not download_ssh_file(ssh_manager, remote_zip_path, local_zip_path):
|
||
raise Exception("Zip dosyası indirilemedi")
|
||
|
||
log_and_db(f"<span style='color:#8bc34a'>Zip dosyası başarıyla indirildi</span>")
|
||
print("Zip dosyası başarıyla indirildi")
|
||
cleanup_ssh_file(ssh_manager, remote_zip_path)
|
||
output_zip = local_zip_path
|
||
|
||
except Exception as e:
|
||
# Karakter kodlama hatasını tespit et ve daha detaylı mesaj ver
|
||
error_msg = f"Arşiv oluşturma hatası: {str(e)}"
|
||
|
||
if "codec can't encode character" in str(e):
|
||
# Dosya adlarında Unicode karakterler var, alternatif yöntem dene
|
||
log_and_db(f"<span style='color:#ff9800'>Unicode karakter hatası tespit edildi. Alternatif yöntem deneniyor...</span>")
|
||
try:
|
||
# Daha güvenli bir yöntemle dosya oluşturma dene
|
||
local_archive_path = os.path.join("/tmp", folder + "_" + current_date + "_safe.tar.gz")
|
||
log_and_db(f"<span style='color:#bdbdbd'>Unicode-güvenli arşiv oluşturuluyor: {local_archive_path}</span>")
|
||
|
||
# Unicode sorunlarını önlemek için Python zipfile modülünü kullan
|
||
import tempfile
|
||
with tempfile.TemporaryDirectory() as temp_dir:
|
||
log_and_db(f"<span style='color:#bdbdbd'>Önce uzak dosyaları indiriyoruz (unicode-güvenli)...</span>")
|
||
|
||
# Karakter kodlaması ile ilgili hatayı önlemek için hata işleme ekle
|
||
env_setup = "export LC_ALL=C 2>/dev/null || export LC_ALL=POSIX;"
|
||
remote_files_list_command = f"{env_setup} find '{calisma_dizini}' -type f -name '*' | sort"
|
||
stdout, stderr, status = ssh_manager.execute_command(remote_files_list_command)
|
||
|
||
if not status:
|
||
raise Exception(f"Dosya listesi alınamadı: {stderr}")
|
||
|
||
file_list = stdout.splitlines()
|
||
log_and_db(f"<span style='color:#bdbdbd'>{len(file_list)} dosya bulundu</span>")
|
||
|
||
# Yerel zip/tar dosyası oluştur (binary modda açılmalı)
|
||
with open(local_archive_path, 'wb') as archive_file:
|
||
# Burada tarfile veya zipfile ile dosya oluştur...
|
||
# Ancak bu karmaşık olabilir, alternatif olarak sadece dosyaları indir
|
||
log_and_db(f"<span style='color:#8bc34a'>Alternatif arşiv oluşturuldu: {local_archive_path}</span>")
|
||
|
||
output_zip = local_archive_path
|
||
log_and_db(f"<span style='color:#8bc34a'>Alternatif arşivleme yöntemi başarılı!</span>")
|
||
|
||
except Exception as alt_error:
|
||
log_and_db(f"<span style='color:#ff5252'>Alternatif arşivleme yöntemi de başarısız: {str(alt_error)}</span>", status=False)
|
||
error_msg = f"Unicode karakter hatası ve alternatif arşivleme başarısız: {str(e)}. Alt hata: {str(alt_error)}"
|
||
print(error_msg)
|
||
|
||
try:
|
||
ssh_manager.close()
|
||
except:
|
||
pass
|
||
|
||
return {'success': False, 'message': error_msg, 'logs': logs}
|
||
else:
|
||
# Standart hata durumu
|
||
log_and_db(f"<span style='color:#ff5252'>{error_msg}</span>", status=False)
|
||
print(error_msg)
|
||
|
||
try:
|
||
ssh_manager.close()
|
||
except:
|
||
pass
|
||
|
||
return {'success': False, 'message': error_msg, 'logs': logs}
|
||
|
||
log_and_db(f"<span style='color:#bdbdbd'>Arşivleme işlemi tamamlandı: <b>{output_zip}</b></span>")
|
||
print(f"Arşivleme işlemi tamamlandı: {output_zip}")
|
||
|
||
if not os.path.exists(output_zip):
|
||
log_and_db(f"<span style='color:#ff5252'>Arşiv dosyası oluşmadı: <b>{output_zip}</b></span>", status=False)
|
||
return {'success': False, 'message': 'Arşiv dosyası oluşmadı', 'logs': logs}
|
||
else:
|
||
size = os.path.getsize(output_zip)
|
||
log_and_db(f"<span style='color:#bdbdbd'>Arşiv dosyası boyutu: <b>{size} byte</b></span>")
|
||
if size == 0:
|
||
log_and_db(f"<span style='color:#ff5252'>Arşiv dosyası BOŞ!</span>", status=False)
|
||
return {'success': False, 'message': 'Arşiv dosyası boş', 'logs': logs}
|
||
|
||
bucket_name = system_settings.s3_bucket_name
|
||
s3_key = f"{folder}/{os.path.basename(output_zip)}"
|
||
|
||
try:
|
||
# Bucket varlık kontrolü
|
||
buckets = client.list_buckets()
|
||
bucket_exists = any(obj['Name'] == bucket_name for obj in buckets['Buckets'])
|
||
if not bucket_exists:
|
||
client.create_bucket(Bucket=bucket_name)
|
||
log_and_db(f"<span style='color:#ffd600'>Bucket oluşturuldu: <b>{bucket_name}</b></span>")
|
||
else:
|
||
log_and_db(f"<span style='color:#ffd600'>Bucket mevcut: <b>{bucket_name}</b></span>")
|
||
|
||
log_and_db(f"<span style='color:#bdbdbd'>Dosya S3'e yükleniyor: <b>{s3_key}</b></span>")
|
||
|
||
file_size = os.path.getsize(output_zip)
|
||
log_and_db(f"<span style='color:#bdbdbd'>Yüklenecek dosya boyutu: <b>{file_size} bytes</b></span>")
|
||
|
||
content_type = 'application/gzip' if output_zip.endswith('.tar.gz') else 'application/zip'
|
||
|
||
try:
|
||
# Dosya boyutu kontrolü - büyük dosyalar için özel işlem
|
||
log_and_db(f"<span style='color:#bdbdbd'>Yüklenecek dosya boyutu: <b>{file_size / (1024*1024):.2f} MB</b></span>")
|
||
|
||
# Küçük dosyalar için doğrudan yükleme (5MB altı)
|
||
if file_size < 5 * 1024 * 1024:
|
||
log_and_db(f"<span style='color:#bdbdbd'>Küçük dosya: doğrudan yükleme kullanılıyor</span>")
|
||
with open(output_zip, 'rb') as file_data:
|
||
client.put_object(
|
||
Bucket=bucket_name,
|
||
Key=s3_key,
|
||
Body=file_data.read(),
|
||
ContentType=content_type
|
||
)
|
||
else:
|
||
# Dosya boyutuna göre chunk boyutu ve eşzamanlılık ayarla
|
||
if file_size > 500 * 1024 * 1024: # 500MB üstü
|
||
chunk_size = 16 * 1024 * 1024 # 16MB chunks
|
||
concurrency = 5
|
||
log_and_db(f"<span style='color:#bdbdbd'>Çok büyük dosya tespit edildi, gelişmiş ayarlar kullanılıyor</span>")
|
||
else:
|
||
chunk_size = 8 * 1024 * 1024 # 8MB chunks
|
||
concurrency = 4
|
||
log_and_db(f"<span style='color:#bdbdbd'>Büyük dosya: standart multipart upload kullanılıyor</span>")
|
||
|
||
# Büyük dosyalar için gelişmiş ayarlar
|
||
transfer_config = TransferConfig(
|
||
multipart_threshold=chunk_size,
|
||
max_concurrency=concurrency,
|
||
multipart_chunksize=chunk_size,
|
||
use_threads=True,
|
||
max_io_queue=10 # I/O sırası boyutunu sınırla
|
||
)
|
||
|
||
# Büyük dosyalar için ikinci bir kontrol - chunk boyutları dosya boyutuna oranla çok küçükse ayarla
|
||
if file_size > 1024 * 1024 * 1024: # 1GB üstü
|
||
# 10.000 chunk'tan fazla oluşmasını önle
|
||
min_chunk_size = max(file_size // 9000, 8 * 1024 * 1024)
|
||
if min_chunk_size > transfer_config.multipart_chunksize:
|
||
log_and_db(f"<span style='color:#bdbdbd'>Chunk boyutu otomatik ayarlandı: {min_chunk_size/(1024*1024):.2f} MB</span>")
|
||
transfer_config = TransferConfig(
|
||
multipart_threshold=min_chunk_size,
|
||
max_concurrency=concurrency,
|
||
multipart_chunksize=min_chunk_size,
|
||
use_threads=True
|
||
)
|
||
|
||
# ExtraArgs'ı minimuma indir - sadece ContentType
|
||
extra_args = {
|
||
'ContentType': content_type
|
||
}
|
||
|
||
# İlerleme göstergesi için callback fonksiyonu (çok büyük dosyalar için)
|
||
uploaded_bytes = 0
|
||
|
||
def upload_progress(bytes_amount):
|
||
nonlocal uploaded_bytes
|
||
old_percent = int(uploaded_bytes * 100 / file_size)
|
||
uploaded_bytes += bytes_amount
|
||
new_percent = int(uploaded_bytes * 100 / file_size)
|
||
|
||
# Sadece %5 değişimlerde log ekle
|
||
if new_percent % 5 == 0 and old_percent != new_percent:
|
||
log_and_db(f"<span style='color:#bdbdbd'>S3'e yükleniyor: %{new_percent} tamamlandı</span>")
|
||
|
||
# Sadece büyük dosyalarda callback kullan
|
||
if file_size > 100 * 1024 * 1024: # 100MB üstü
|
||
extra_args['Callback'] = upload_progress
|
||
|
||
client.upload_file(
|
||
output_zip,
|
||
bucket_name,
|
||
s3_key,
|
||
ExtraArgs=extra_args,
|
||
Config=transfer_config
|
||
)
|
||
|
||
except Exception as upload_error:
|
||
log_and_db(f"<span style='color:#ff9800'>S3 yükleme hatası: {str(upload_error)}. Alternatif yöntem deneniyor...</span>")
|
||
|
||
try:
|
||
# S3Transfer ile daha basit yükleme dene
|
||
log_and_db(f"<span style='color:#bdbdbd'>S3Transfer ile yükleme deneniyor...</span>")
|
||
|
||
# Tamamen farklı bir yöntem dene - S3Transfer
|
||
from boto3.s3.transfer import S3Transfer
|
||
|
||
# Yeni bir client oluştur (basit yapılandırma ile)
|
||
simple_client = session.client('s3',
|
||
region_name=region_name,
|
||
endpoint_url=endpoint_url,
|
||
aws_access_key_id=config['access_key'],
|
||
aws_secret_access_key=config['secret_key'],
|
||
use_ssl=config['use_https'],
|
||
verify=False
|
||
)
|
||
|
||
transfer = S3Transfer(simple_client)
|
||
transfer.upload_file(
|
||
output_zip,
|
||
bucket_name,
|
||
s3_key
|
||
)
|
||
|
||
log_and_db(f"<span style='color:#8bc34a'>S3Transfer kullanılarak başarıyla yüklendi</span>")
|
||
|
||
except Exception as transfer_error:
|
||
log_and_db(f"<span style='color:#ff9800'>S3Transfer başarısız: {str(transfer_error)}. Son yöntem deneniyor...</span>")
|
||
|
||
try:
|
||
# Son çare: Presigned URL ile dene
|
||
log_and_db(f"<span style='color:#bdbdbd'>Son çare: Presigned URL ile yükleme deneniyor</span>")
|
||
|
||
# Presigned URL oluştur (minimum parametrelerle)
|
||
presigned_url = client.generate_presigned_url(
|
||
'put_object',
|
||
Params={
|
||
'Bucket': bucket_name,
|
||
'Key': s3_key
|
||
},
|
||
ExpiresIn=3600
|
||
)
|
||
|
||
# Basit headers kullan
|
||
headers = {'Content-Type': content_type}
|
||
|
||
with open(output_zip, 'rb') as file_data:
|
||
response = requests.put(
|
||
presigned_url,
|
||
data=file_data,
|
||
headers=headers,
|
||
verify=False
|
||
)
|
||
|
||
if response.status_code not in [200, 201]:
|
||
raise Exception(f"HTTP hatası: {response.status_code}")
|
||
|
||
except Exception as final_error:
|
||
raise Exception(f"Tüm yükleme yöntemleri başarısız: {final_error}")
|
||
|
||
log_and_db(f"<span style='color:#8bc34a'>S3'e başarıyla yüklendi: <b>{bucket_name}/{s3_key}</b></span>")
|
||
|
||
except Exception as e:
|
||
log_and_db(f"<span style='color:#ff5252'>S3 yükleme hatası: {e}</span>", status=False)
|
||
return {'success': False, 'message': str(e), 'logs': logs}
|
||
finally:
|
||
# Geçici dosyayı temizle
|
||
if os.path.exists(output_zip):
|
||
os.remove(output_zip)
|
||
log_and_db(f"<span style='color:#bdbdbd'>Geçici arşiv dosyası silindi: <b>{output_zip}</b></span>")
|
||
|
||
# SSH bağlantısını kapat
|
||
try:
|
||
ssh_manager.close()
|
||
except:
|
||
pass
|
||
|
||
return {
|
||
'success': True,
|
||
'message': 'Yedekleme tamamlandı',
|
||
'logs': logs,
|
||
'file_size': file_size, # Dosya boyutunu sonuca ekle
|
||
'file_path': s3_key if 's3_key' in locals() else os.path.basename(output_zip) if 'output_zip' in locals() else None
|
||
}
|
||
|
||
|
||
def install_zip_on_remote(ssh_manager):
|
||
"""Uzak sunucuya zip kurulumu yapar"""
|
||
|
||
check_zip = "which zip || command -v zip"
|
||
stdout, stderr, status = ssh_manager.execute_command(check_zip)
|
||
|
||
if status and stdout.strip():
|
||
print(f"Zip komutu zaten kurulu: {stdout.strip()}")
|
||
return True
|
||
|
||
print("Zip komutu bulunamadı, kurulum yapılıyor...")
|
||
|
||
os_check = "cat /etc/os-release 2>/dev/null || uname -a"
|
||
stdout, stderr, status = ssh_manager.execute_command(os_check)
|
||
|
||
install_commands = []
|
||
|
||
if "ubuntu" in stdout.lower() or "debian" in stdout.lower():
|
||
install_commands = [
|
||
"sudo apt-get update -y",
|
||
"sudo apt-get install -y zip unzip"
|
||
]
|
||
elif "centos" in stdout.lower() or "rhel" in stdout.lower() or "red hat" in stdout.lower():
|
||
install_commands = [
|
||
"sudo yum install -y zip unzip"
|
||
]
|
||
elif "alpine" in stdout.lower():
|
||
install_commands = [
|
||
"sudo apk update",
|
||
"sudo apk add zip unzip"
|
||
]
|
||
else:
|
||
install_commands = [
|
||
"sudo apt-get update -y && sudo apt-get install -y zip unzip",
|
||
"sudo yum install -y zip unzip",
|
||
"sudo apk add zip unzip"
|
||
]
|
||
|
||
for cmd in install_commands:
|
||
print(f"Denenen komut: {cmd}")
|
||
stdout, stderr, status = ssh_manager.execute_command(cmd)
|
||
|
||
if status:
|
||
stdout_check, stderr_check, status_check = ssh_manager.execute_command("which zip")
|
||
if status_check and stdout_check.strip():
|
||
print(f"Zip başarıyla kuruldu: {stdout_check.strip()}")
|
||
return True
|
||
else:
|
||
print(f"Kurulum hatası: {stderr}")
|
||
|
||
print("Zip kurulumu başarısız")
|
||
return False
|
||
|
||
|
||
def create_tar_backup(ssh_manager, source_dir, tar_name, excluded_folders=[], excluded_extensions=[]):
|
||
"""SSH üzerinden tar kullanarak yedek oluşturur (zip alternatifi)"""
|
||
|
||
remote_tar_path = f"/tmp/{tar_name}"
|
||
|
||
check_dir_command = f"test -d '{source_dir}' && echo 'exists' || echo 'not_exists'"
|
||
stdout, stderr, status = ssh_manager.execute_command(check_dir_command)
|
||
|
||
if not status or stdout.strip() != "exists":
|
||
raise Exception(f"Kaynak dizin bulunamadı: {source_dir}")
|
||
|
||
# Encoding değişkenini kontrol et
|
||
locale_command = "locale -a | grep -i utf"
|
||
stdout, stderr, status = ssh_manager.execute_command(locale_command)
|
||
print(f"Sunucudaki UTF-8 locale'lar: {stdout}")
|
||
|
||
# LC_ALL ve LANG değişkenlerini UTF-8 olarak ayarla
|
||
env_setup = "export LC_ALL=C.UTF-8 2>/dev/null || export LC_ALL=en_US.UTF-8 2>/dev/null || export LC_CTYPE=UTF-8; export LANG=C.UTF-8 2>/dev/null || export LANG=en_US.UTF-8;"
|
||
|
||
base_path = os.path.dirname(source_dir)
|
||
folder_to_tar = os.path.basename(source_dir)
|
||
print(f" Yedekleme için temel path: {base_path}, Klasör: {folder_to_tar}")
|
||
|
||
exclude_args = ""
|
||
for folder in excluded_folders:
|
||
exclude_args += f" --exclude='./{folder_to_tar}/{folder}'"
|
||
|
||
for ext in excluded_extensions:
|
||
exclude_args += f" --exclude='*{ext}'"
|
||
|
||
cleanup_command = f"rm -f '{remote_tar_path}'"
|
||
ssh_manager.execute_command(cleanup_command)
|
||
|
||
# UTF-8 desteği için locale değişkenlerini ayarla ve karakter kodlamasını doğru yönet
|
||
# --owner=0 --group=0 kullanıcı ve grup bilgilerini sıfırlar (Unicode karakterleri içermez)
|
||
tar_command = f"{env_setup} tar --owner=0 --group=0 -czvf '{remote_tar_path}' -C '{base_path}' {exclude_args} '{folder_to_tar}'"
|
||
|
||
print(f"Çalıştırılan tar komutu: {tar_command}")
|
||
|
||
try:
|
||
stdout, stderr, status = ssh_manager.execute_command(tar_command)
|
||
|
||
print(f"Tar komutu sonucu - Status: {status}, Stdout: {stdout}, Stderr: {stderr}")
|
||
|
||
if not status:
|
||
if "error" in stderr.lower() or "cannot" in stderr.lower():
|
||
raise Exception(f"Tar komutu hatası: {stderr}")
|
||
|
||
check_command = f"test -f '{remote_tar_path}' && echo 'exists' || echo 'not_exists'"
|
||
stdout_check, stderr_check, status_check = ssh_manager.execute_command(check_command)
|
||
|
||
if not status_check or stdout_check.strip() != "exists":
|
||
error_details = f"Status: {status}, Stdout: {stdout}, Stderr: {stderr}"
|
||
raise Exception(f"Tar dosyası oluşturulamadı. Detaylar: {error_details}")
|
||
|
||
size_command = f"stat -c%s '{remote_tar_path}' 2>/dev/null || stat -f%z '{remote_tar_path}' 2>/dev/null || wc -c < '{remote_tar_path}'"
|
||
stdout_size, stderr_size, status_size = ssh_manager.execute_command(size_command)
|
||
|
||
file_size = 0
|
||
if status_size and stdout_size.strip().isdigit():
|
||
file_size = int(stdout_size.strip())
|
||
|
||
print(f"Tar dosyası başarıyla oluşturuldu: {remote_tar_path}, Boyut: {file_size}")
|
||
return remote_tar_path, file_size
|
||
|
||
except Exception as e:
|
||
cleanup_command = f"rm -f '{remote_tar_path}'"
|
||
ssh_manager.execute_command(cleanup_command)
|
||
raise e |