Files
hostpanel/ssh_manager/backup.py
ilkeral 342f1314c7 yeni
2025-07-21 13:49:36 +03:00

627 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import zipfile
import boto3
from boto3.s3.transfer import TransferConfig
from django.utils.text import slugify
from datetime import datetime
import requests
import stat
haric_dosya_uzantilari = ['.zip', ]
excluded_folders = ['venv', 'yedek', '.idea', '.sock']
hostname = "ams1.vultrobjects.com"
secret_key = "Ec1pq3OQAObFLOQrfAVqJKhDAk4BkT7OqgYszlef"
access_key = "KQAOMJ8CQ8HP4CY23YPK"
x = 1
def upload_file_via_presigned_url(url, file_path):
if not os.path.exists(file_path):
print(f"Dosya bulunamadi: {file_path}")
return False
with open(file_path, 'rb') as file_data:
try:
response = requests.put(url, data=file_data)
if response.status_code == 200:
print("Dosya yuklendi!")
return True
else:
print(f"Yukleme olmadi. Status code: {response.status_code}")
print(f"Response: {response.content}")
return False
except Exception as e:
print(f"Yukleme hatasi: {e}")
return False
def get_filtered_folder_names(directory, excluded_folders):
folder_names = []
for item in os.listdir(directory):
item_path = os.path.join(directory, item)
if os.path.isdir(item_path) and item not in excluded_folders:
folder_names.append(item)
return folder_names
def zip_klasor(ziplenecek_klasor, hedef_zip_adi, haric_klasorler=[], haric_dosya_uzantilari=[]):
# Parametrelerin geçerliliğini kontrol et
if not ziplenecek_klasor or not hedef_zip_adi:
raise ValueError("Ziplenecek klasör ve hedef zip adı boş olamaz")
if not os.path.exists(ziplenecek_klasor):
raise FileNotFoundError(f"Ziplenecek klasör bulunamadı: {ziplenecek_klasor}")
# Hedef zip dosyasının bulunacağı dizini oluştur ve izinleri ayarla
hedef_dizin = os.path.dirname(hedef_zip_adi)
# Eğer hedef dizin boşsa, mevcut dizini kullan
if not hedef_dizin:
hedef_dizin = "."
hedef_zip_adi = os.path.join(hedef_dizin, hedef_zip_adi)
if not os.path.exists(hedef_dizin):
os.makedirs(hedef_dizin, mode=0o755, exist_ok=True)
# Zip dosyası oluşturmadan önce izinleri kontrol et
if os.path.exists(hedef_zip_adi):
try:
os.chmod(hedef_zip_adi, 0o666)
except Exception as e:
print(f"Mevcut zip dosyasinin izinleri guncellenemedi: {e}")
with zipfile.ZipFile(hedef_zip_adi, 'w', zipfile.ZIP_DEFLATED) as zipf:
for klasor_yolu, _, dosya_listesi in os.walk(ziplenecek_klasor):
if not any(k in klasor_yolu for k in haric_klasorler):
for dosya in dosya_listesi:
dosya_adi, dosya_uzantisi = os.path.splitext(dosya)
dosya_yolu = os.path.join(klasor_yolu, dosya)
# Dosyanın var olup olmadığını kontrol et
if not os.path.exists(dosya_yolu):
print(f"Dosya bulunamadi: {dosya_yolu}")
continue
# Socket dosyalarını atla
try:
file_stat = os.stat(dosya_yolu)
if stat.S_ISSOCK(file_stat.st_mode):
print(f"Socket dosyasi atlandi: {dosya_yolu}")
continue
except (OSError, PermissionError) as e:
print(f"Dosya stat alinamadi: {dosya_yolu} -> Hata: {e}")
continue
if dosya_uzantisi.lower() not in haric_dosya_uzantilari:
try:
# Dosya okuma izinlerini kontrol et
if os.access(dosya_yolu, os.R_OK):
zipf.write(dosya_yolu, os.path.relpath(dosya_yolu, ziplenecek_klasor))
print(f"Dosya eklendi: {dosya_yolu}")
else:
print(f"Dosya okuma izni yok: {dosya_yolu}")
except (PermissionError, OSError) as e:
print(f"Dosya eklenemedi: {dosya_yolu} -> Hata: {e}")
except Exception as e:
print(f"Beklenmeyen hata: {dosya_yolu} -> Hata: {e}")
# Oluşturulan zip dosyasının izinlerini ayarla
try:
os.chmod(hedef_zip_adi, 0o644)
print(f"Zip dosyasi olusturuldu: {hedef_zip_adi}")
except Exception as e:
print(f"Zip dosyasi izinleri ayarlanamadi: {e}")
def create_ssh_zip(ssh_manager, source_dir, zip_name, excluded_folders=[], excluded_extensions=[]):
"""SSH üzerinden uzak sunucuda zip dosyası oluşturur"""
# Uzak sunucuda geçici zip dosyası yolu
remote_zip_path = f"/tmp/{zip_name}"
# Önce kaynak dizinin varlığını kontrol et
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)}")
# Zip komutunun varlığını kontrol et ve gerekirse kur
zip_check_command = "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)}")
# Hariç tutulacak klasörler için exclude parametresi
exclude_args = ""
for folder in excluded_folders:
exclude_args += f" --exclude='{folder}/*' --exclude='{folder}'"
for ext in excluded_extensions:
exclude_args += f" --exclude='*{ext}'"
# Eski zip dosyasını temizle
cleanup_command = f"rm -f '{remote_zip_path}'"
ssh_manager.execute_command(cleanup_command)
# Zip komutunu oluştur (daha basit ve güvenilir)
zip_command = f"cd '{source_dir}' && zip -r '{remote_zip_path}' . {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}")
# Zip komutu bazen uyarılarla birlikte başarılı olabilir
# Bu yüzden sadece status kontrolü yerine dosya varlığını da kontrol edelim
# Zip dosyasının varlığını kontrol et
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}")
# Dosya boyutunu al
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:
# Boyut alınamazsa alternatif yöntem
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:
# Hata durumunda oluşmuş olabilecek zip dosyasını temizle
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 dizinin varlığını kontrol et ve oluştur
local_dir = os.path.dirname(local_path)
if not os.path.exists(local_dir):
os.makedirs(local_dir, mode=0o755, exist_ok=True)
# SFTP kullanarak dosyayı indir
with ssh_manager.client.open_sftp() as sftp:
# Uzak dosyanın varlığını kontrol et
try:
file_stat = sftp.stat(remote_path)
print(f"Uzak dosya boyutu: {file_stat.st_size} byte")
except FileNotFoundError:
raise Exception(f"Uzak dosya bulunamadı: {remote_path}")
sftp.get(remote_path, local_path)
# İndirilen dosyanın varlığını ve boyutunu kontrol et
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}")
# Başarısız indirme durumunda local dosyayı temizle
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
def job(folder, calisma_dizini, project_id=None):
import ssl
logs = []
# Parametrelerin geçerliliğini kontrol et
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}
# NOT: calisma_dizini SSH sunucusundaki bir yol olduğu için burada local kontrol yapılmaz
# Dizin kontrolü views.py'da SSH üzerinden yapılmalı
try:
project = Project.objects.get(id=project_id)
ssh_manager = project.ssh_credential.get_manager()
except Exception as e:
return {'success': False, 'message': f'SSH bağlantısı kurulamadı: {str(e)}', 'logs': logs}
# --- Vultr/S3 config ---
config = {
'access_key': "KQAOMJ8CQ8HP4CY23YPK",
'secret_key': "Ec1pq3OQAObFLOQrfAVqJKhDAk4BkT7OqgYszlef",
'host_base': "ams1.vultrobjects.com",
'bucket_location': "US",
'use_https': True,
'check_ssl_certificate': False, # SSL doğrulamasını kapat
'multipart_chunk_size_mb': 50, # Chunk boyutunu artır
}
endpoint_url = f"https://{config['host_base']}"
region_name = config['bucket_location']
# ---
session = boto3.session.Session()
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, # SSL doğrulamasını tamamen kapat
config=boto3.session.Config(
signature_version='s3v4',
retries={'max_attempts': 3},
s3={
'addressing_style': 'path',
'payload_signing_enabled': False,
'chunked_encoding': False
}
)
)
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 oturumu başlatıldı.</span>")
local_dt = datetime.now()
current_date = slugify(str(local_dt))
# Zip dosyası için tam yol oluştur
zip_dosya_adi = folder + "_" + current_date + ".zip"
output_zip = os.path.join("/tmp", zip_dosya_adi) # /tmp dizininde oluştur
log_and_db(f"<span style='color:#bdbdbd'>SSH üzerinden zip dosyası oluşturuluyor...</span>")
try:
# SSH üzerinden uzak sunucuda zip oluştur
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>")
try:
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>")
# Zip dosyasını local'e indir
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>")
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>")
# Uzak sunucudaki geçici zip dosyasını temizle
cleanup_ssh_file(ssh_manager, remote_zip_path)
output_zip = local_zip_path
except Exception as zip_error:
log_and_db(f"<span style='color:#ff9800'>Zip oluşturma başarısız: {str(zip_error)}</span>")
log_and_db(f"<span style='color:#bdbdbd'>Tar ile yedekleme deneniyor...</span>")
# Zip başarısız olursa tar kullan
tar_dosya_adi = folder + "_" + current_date + ".tar.gz"
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>")
# Tar dosyasını local'e indir
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>")
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>")
# Uzak sunucudaki geçici tar dosyasını temizle
cleanup_ssh_file(ssh_manager, remote_tar_path)
output_zip = local_tar_path
except Exception as e:
error_msg = f"SSH zip oluşturma hatası: {str(e)}"
log_and_db(f"<span style='color:#ff5252'>{error_msg}</span>", status=False)
# SSH bağlantısını kapat
try:
ssh_manager.close()
except:
pass
return {'success': False, 'message': error_msg, 'logs': logs}
log_and_db(f"<span style='color:#bdbdbd'>Zip işlemi tamamlandı: <b>{output_zip}</b></span>")
# --- Zip dosyası oluştu mu ve boş mu kontrolü ---
if not os.path.exists(output_zip):
log_and_db(f"<span style='color:#ff5252'>Zip dosyası oluşmadı: <b>{output_zip}</b></span>", status=False)
return {'success': False, 'message': 'Zip dosyası oluşmadı', 'logs': logs}
else:
size = os.path.getsize(output_zip)
log_and_db(f"<span style='color:#bdbdbd'>Zip dosyası boyutu: <b>{size} byte</b></span>")
if size == 0:
log_and_db(f"<span style='color:#ff5252'>Zip dosyası BOŞ!</span>", status=False)
return {'success': False, 'message': 'Zip dosyası boş', 'logs': logs}
bucket_name = folder
s3_key = output_zip # Bucket içinde alt klasör olmadan doğrudan zip dosyası
try:
# Bucket kontrol/oluşturma
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>")
# S3'e yükle (Vultr Object Storage için özel yöntem)
log_and_db(f"<span style='color:#bdbdbd'>Dosya S3'e yükleniyor: <b>{s3_key}</b></span>")
# Dosya boyutunu kontrol et
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>")
try:
# Küçük dosyalar için basit put_object kullan
if file_size < 50 * 1024 * 1024: # 50MB'dan küçükse
with open(output_zip, 'rb') as file_data:
client.put_object(
Bucket=bucket_name,
Key=s3_key,
Body=file_data.read(),
ACL='private',
ContentType='application/zip',
Metadata={
'uploaded_by': 'ssh_manager',
'upload_date': current_date
}
)
else:
# Büyük dosyalar için multipart upload
transfer_config = TransferConfig(
multipart_threshold=1024 * 1024 * 50, # 50MB
max_concurrency=1, # Tek thread kullan
multipart_chunksize=1024 * 1024 * 50, # 50MB chunk
use_threads=False
)
client.upload_file(
output_zip,
bucket_name,
s3_key,
ExtraArgs={
'ACL': 'private',
'ContentType': 'application/zip',
'Metadata': {
'uploaded_by': 'ssh_manager',
'upload_date': current_date
}
},
Config=transfer_config
)
except Exception as upload_error:
# Son çare: presigned URL ile yükleme
log_and_db(f"<span style='color:#ff9800'>Standart yükleme başarısız, presigned URL deneniyor: {upload_error}</span>")
try:
presigned_url = client.generate_presigned_url(
'put_object',
Params={'Bucket': bucket_name, 'Key': s3_key},
ExpiresIn=3600
)
import requests
with open(output_zip, 'rb') as file_data:
headers = {'Content-Type': 'application/zip'}
response = requests.put(presigned_url, data=file_data, headers=headers)
if response.status_code not in [200, 201]:
raise Exception(f"Presigned URL yükleme hatası: {response.status_code} - {response.text}")
except Exception as presigned_error:
raise Exception(f"Tüm yükleme yöntemleri başarısız: {presigned_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:
if os.path.exists(output_zip):
os.remove(output_zip)
log_and_db(f"<span style='color:#bdbdbd'>Geçici zip dosyası silindi: <b>{output_zip}</b></span>")
return {'success': True, 'message': 'Yedekleme tamamlandı', 'logs': logs}
def install_zip_on_remote(ssh_manager):
"""Uzak sunucuya zip kurulumu yapar"""
# Önce zip komutunun varlığını kontrol et
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...")
# İşletim sistemi kontrolü
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:
# Diğer sistemler için genel deneme
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"
]
# Kurulum komutlarını dene
for cmd in install_commands:
print(f"Denenen komut: {cmd}")
stdout, stderr, status = ssh_manager.execute_command(cmd)
if status:
# Kurulum sonrası zip kontrolü
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)"""
# Uzak sunucuda geçici tar dosyası yolu
remote_tar_path = f"/tmp/{tar_name}"
# Kaynak dizinin varlığını kontrol et
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}")
# Hariç tutulacak klasörler için exclude parametresi
exclude_args = ""
for folder in excluded_folders:
exclude_args += f" --exclude='{folder}'"
for ext in excluded_extensions:
exclude_args += f" --exclude='*{ext}'"
# Eski tar dosyasını temizle
cleanup_command = f"rm -f '{remote_tar_path}'"
ssh_manager.execute_command(cleanup_command)
# Tar komutunu oluştur (gzip ile sıkıştır)
tar_command = f"cd '{source_dir}' && tar -czf '{remote_tar_path}' {exclude_args} . 2>/dev/null"
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}")
# Tar dosyasının varlığını kontrol et
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}")
# Dosya boyutunu al
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:
# Hata durumunda oluşmuş olabilecek tar dosyasını temizle
cleanup_command = f"rm -f '{remote_tar_path}'"
ssh_manager.execute_command(cleanup_command)
raise e