This commit is contained in:
ilkeral
2025-07-21 13:49:36 +03:00
commit 342f1314c7
57 changed files with 9297 additions and 0 deletions

607
ssh_manager/ssh_client.py Normal file
View File

@ -0,0 +1,607 @@
import paramiko
import logging
import os
import tempfile
from django.utils import timezone
from django.template.loader import render_to_string
logger = logging.getLogger(__name__)
class SSHManager:
def __init__(self, ssh_credential):
self.ssh_credential = ssh_credential
self.client = None
self.connect()
logger.info(f'SSHManager başlatıldı: {ssh_credential.hostname}')
def connect(self):
"""SSH bağlantısı kur"""
try:
self.client = paramiko.SSHClient()
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.client.connect(
hostname=self.ssh_credential.hostname,
username=self.ssh_credential.username,
password=self.ssh_credential.password,
port=self.ssh_credential.port or 22,
look_for_keys=False,
allow_agent=False
)
return True
except Exception as e:
logger.error(f'SSH bağlantı hatası: {str(e)}')
return False
def close(self):
"""SSH bağlantısını kapat"""
if self.client:
self.client.close()
self.client = None
def check_connection(self):
"""SSH bağlantısını kontrol et"""
try:
if not self.client:
return self.connect()
self.client.exec_command('echo "Connection test"')
return True
except:
return self.connect()
def execute_command(self, command):
"""
SSH üzerinden komut çalıştır ve sonuçları döndür
"""
try:
if not self.client:
self.connect()
stdin, stdout, stderr = self.client.exec_command(command)
exit_status = stdout.channel.recv_exit_status()
# Binary veriyi oku
stdout_data = stdout.read()
stderr_data = stderr.read()
# Farklı encoding'leri dene
encodings = ['utf-8', 'latin1', 'cp1252', 'iso-8859-9']
# stdout için encoding dene
stdout_str = None
for enc in encodings:
try:
stdout_str = stdout_data.decode(enc)
break
except UnicodeDecodeError:
continue
# stderr için encoding dene
stderr_str = None
for enc in encodings:
try:
stderr_str = stderr_data.decode(enc)
break
except UnicodeDecodeError:
continue
# Eğer hiçbir encoding çalışmazsa, latin1 kullan (her byte'ı decode edebilir)
if stdout_str is None:
stdout_str = stdout_data.decode('latin1')
if stderr_str is None:
stderr_str = stderr_data.decode('latin1')
return stdout_str, stderr_str, exit_status == 0
except Exception as e:
logger.exception(f"Komut çalıştırma hatası: {command}")
return "", str(e), False
def download_req_file(self, project):
"""req.txt dosyasını oku ve geçici dosya olarak kaydet"""
try:
# Dosya içeriğini oku
cmd = f'cat "{project.get_full_path()}/req.txt"'
stdout, stderr, status = self.execute_command(cmd)
if not status:
logger.error(f"req.txt okunamadı: {stderr}")
return None
# Geçici dosya oluştur
temp = tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix='.txt')
temp.write(stdout)
temp.close()
logger.info(f"req.txt temp file created: {temp.name}")
return temp.name
except Exception as e:
logger.exception(f"Error in download_req_file: {str(e)}")
return None
def delete_project(self, project):
"""Proje klasörünü sil"""
try:
cmd = f'rm -rf "{project.get_full_path()}"'
stdout, stderr, status = self.execute_command(cmd)
if status:
return True, "Proje başarıyla silindi"
else:
return False, f"Silme hatası: {stderr}"
except Exception as e:
return False, str(e)
def upload_zip(self, project, zip_file):
"""ZIP dosyasını yükle ve aç"""
try:
# Geçici dizin oluştur
temp_dir = '/tmp/project_upload'
mkdir_out, mkdir_err, mkdir_status = self.execute_command(f'rm -rf {temp_dir} && mkdir -p {temp_dir}')
if not mkdir_status:
return False, f'Geçici dizin oluşturulamadı: {mkdir_err}'
# SFTP bağlantısı
sftp = self.client.open_sftp()
try:
# ZIP dosyasını yükle
remote_zip = f"{temp_dir}/{zip_file.name}"
sftp.putfo(zip_file, remote_zip)
# ZIP dosyasını
unzip_cmd = f'''
cd {temp_dir} && \
unzip -o "{zip_file.name}" && \
rm "{zip_file.name}" && \
mv * "{project.get_full_path()}/" 2>/dev/null || true && \
cd / && \
rm -rf {temp_dir}
'''
stdout, stderr, status = self.execute_command(unzip_cmd)
if not status:
return False, f'ZIP açma hatası: {stderr}'
return True, "Dosya başarıyla yüklendi"
finally:
sftp.close()
except Exception as e:
return False, str(e)
def upload_txt(self, project, txt_file):
"""TXT dosyasını yükle"""
try:
# SFTP bağlantısı
sftp = self.client.open_sftp()
try:
# Dosya adını belirle
remote_path = f"{project.get_full_path()}/req.txt"
# Dosyayı yükle
sftp.putfo(txt_file, remote_path)
# İzinleri ayarla
self.execute_command(f'chmod 644 "{remote_path}"')
return True, "Dosya başarıyla yüklendi"
finally:
sftp.close()
except Exception as e:
return False, str(e)
def create_config_files(self, project):
"""Tüm konfigürasyon dosyalarını oluşturur"""
try:
logger.info(f'"{project.folder_name}" projesi için konfigürasyon dosyaları oluşturuluyor')
# Değişkenleri hazırla
context = {
'project_name': project.folder_name,
'project_path': project.get_full_path(),
'domain_name': project.url
}
logger.info('Konfigürasyon şablonları için context hazırlandı')
# Konfigürasyon içeriklerini hazırla
configs = {
'nginx.conf': render_to_string('ssh_manager/nginx.conf.template', context),
'supervisor.conf': render_to_string('ssh_manager/supervisor.conf.template', context),
'wsgi_conf': render_to_string('ssh_manager/wsgi.conf.template', context)
}
logger.info('Konfigürasyon şablonları render edildi')
# WSGI dosyasını proje dizininde oluştur
logger.info('WSGI dosyası oluşturuluyor')
wsgi_cmd = f'cat > "{project.get_full_path()}/wsgi_conf" << "EOF"\n{configs["wsgi_conf"]}\nEOF'
stdout, stderr, status = self.execute_command(wsgi_cmd)
if not status:
logger.error(f'WSGI dosyası oluşturma hatası: {stderr}')
raise Exception(f'WSGI dosyası oluşturulamadı: {stderr}')
logger.info('WSGI dosyası başarıyla oluşturuldu')
# WSGI için izinleri ayarla
logger.info('WSGI dosyası için çalıştırma izinleri ayarlanıyor')
chmod_cmd = f'chmod +x "{project.get_full_path()}/wsgi_conf"'
stdout, stderr, status = self.execute_command(chmod_cmd)
if not status:
logger.error(f'WSGI izin ayarlama hatası: {stderr}')
raise Exception(f'WSGI için izinler ayarlanamadı: {stderr}')
logger.info('WSGI dosyası için izinler başarıyla ayarlandı')
# Nginx konfigürasyonunu direkt hedef konumunda oluştur
if project.url:
logger.info('Nginx konfigürasyonu ayarlanıyor')
nginx_target = f'/etc/nginx/sites-available/{project.url}'
nginx_enabled = f'/etc/nginx/sites-enabled/{project.url}'
# Eski konfigürasyonları temizle
logger.info('Eski Nginx konfigürasyonları temizleniyor')
self.execute_command(f'sudo rm -f {nginx_target} {nginx_enabled}')
# Yeni konfigürasyonu oluştur
logger.info(f'Nginx konfigürasyonu "{nginx_target}" konumunda oluşturuluyor')
nginx_cmd = f'sudo bash -c \'cat > "{nginx_target}" << "EOF"\n{configs["nginx.conf"]}\nEOF\''
stdout, stderr, status = self.execute_command(nginx_cmd)
if not status:
logger.error(f'Nginx konfigürasyon oluşturma hatası: {stderr}')
raise Exception(f'Nginx konfigürasyonu oluşturulamadı: {stderr}')
logger.info('Nginx konfigürasyonu başarıyla oluşturuldu')
# İzinleri ayarla
logger.info('Nginx konfigürasyonu için izinler ayarlanıyor')
self.execute_command(f'sudo chown root:root "{nginx_target}"')
self.execute_command(f'sudo chmod 644 "{nginx_target}"')
logger.info('Nginx konfigürasyonu için izinler başarıyla ayarlandı')
# Symbolic link oluştur
logger.info('Nginx symbolic link oluşturuluyor')
link_cmd = f'sudo ln -sf "{nginx_target}" "{nginx_enabled}"'
stdout, stderr, status = self.execute_command(link_cmd)
if not status:
logger.error(f'Nginx symbolic link oluşturma hatası: {stderr}')
raise Exception(f'Nginx symbolic link oluşturulamadı: {stderr}')
logger.info('Nginx symbolic link başarıyla oluşturuldu')
# Supervisor konfigürasyonunu direkt hedef konumunda oluştur
logger.info('Supervisor konfigürasyonu ayarlanıyor')
supervisor_target = f'/etc/supervisor/conf.d/{project.folder_name}.conf'
# Eski konfigürasyonu temizle
logger.info('Eski Supervisor konfigürasyonu temizleniyor')
self.execute_command(f'sudo rm -f {supervisor_target}')
# Yeni konfigürasyonu oluştur
logger.info(f'Supervisor konfigürasyonu "{supervisor_target}" konumunda oluşturuluyor')
supervisor_cmd = f'sudo bash -c \'cat > "{supervisor_target}" << "EOF"\n{configs["supervisor.conf"]}\nEOF\''
stdout, stderr, status = self.execute_command(supervisor_cmd)
if not status:
logger.error(f'Supervisor konfigürasyon oluşturma hatası: {stderr}')
raise Exception(f'Supervisor konfigürasyonu oluşturulamadı: {stderr}')
logger.info('Supervisor konfigürasyonu başarıyla oluşturuldu')
# İzinleri ayarla
logger.info('Supervisor konfigürasyonu için izinler ayarlanıyor')
self.execute_command(f'sudo chown root:root "{supervisor_target}"')
self.execute_command(f'sudo chmod 644 "{supervisor_target}"')
logger.info('Supervisor konfigürasyonu için izinler başarıyla ayarlandı')
# Servisleri yeniden yükle
logger.info('Servisler yeniden başlatılıyor')
self.execute_command('sudo systemctl reload nginx')
self.execute_command('sudo supervisorctl reread')
self.execute_command('sudo supervisorctl update')
logger.info('Servisler başarıyla yeniden başlatıldı')
logger.info('Tüm konfigürasyon işlemleri başarıyla tamamlandı')
return True, 'Konfigürasyon dosyaları başarıyla oluşturuldu ve konumlandırıldı'
except Exception as e:
logger.error('Konfigürasyon dosyaları oluşturma hatası')
logger.exception(e)
return False, str(e)
def get_disk_usage(self):
"""Sunucunun disk kullanım bilgilerini al"""
try:
# Ana disk bölümünün kullanım bilgilerini al (genellikle /)
cmd = "df -h / | tail -n 1 | awk '{print $2,$3,$4,$5}'"
stdout, stderr, status = self.execute_command(cmd)
if status:
# Çıktıyı parçala: toplam, kullanılan, boş, yüzde
parts = stdout.strip().split()
if len(parts) == 4:
# Yüzde işaretini kaldır ve sayıya çevir
usage_percent = int(parts[3].replace('%', ''))
return {
'total': parts[0],
'used': parts[1],
'available': parts[2],
'usage_percent': usage_percent
}
return None
except Exception as e:
logger.exception("Disk kullanım bilgisi alınamadı")
return None
def upload_project_zip(self, project, zip_file):
"""Proje dosyalarını yükle (zip veya txt)"""
try:
logger.info(f'"{project.folder_name}" projesi için dosya yükleme başlatıldı')
# Başlangıç disk kullanımını al
initial_disk = self.get_disk_usage()
if initial_disk:
logger.info(f'Başlangıç disk kullanımı - Toplam: {initial_disk["total"]}, Kullanılan: {initial_disk["used"]}, Boş: {initial_disk["available"]}')
# Dosya uzantısını kontrol et
file_extension = os.path.splitext(zip_file.name)[1].lower()
# Sadece zip ve txt dosyalarına izin ver
if file_extension not in ['.zip', '.txt']:
logger.warning(f'Geçersiz dosya uzantısı: {file_extension}')
return False, "Sadece .zip ve .txt dosyaları yüklenebilir."
# Dosyayı yükle
logger.info('SFTP bağlantısıılıyor')
sftp = self.client.open_sftp()
try:
if file_extension == '.txt':
# TXT dosyası ise direkt req.txt olarak kaydet
remote_path = f"{project.get_full_path()}/req.txt"
logger.info('TXT dosyası req.txt olarak yükleniyor')
# İlerleme için callback fonksiyonu
total_size = zip_file.size
uploaded_size = 0
start_time = timezone.now()
last_update = start_time
def progress_callback(sent_bytes, remaining_bytes):
nonlocal uploaded_size, start_time, last_update
uploaded_size = sent_bytes
current_time = timezone.now()
elapsed_time = (current_time - start_time).total_seconds()
# Her 0.5 saniyede bir güncelle
if (current_time - last_update).total_seconds() >= 0.5:
if elapsed_time > 0:
speed = uploaded_size / elapsed_time # bytes/second
percent = (uploaded_size / total_size) * 100
remaining_size = total_size - uploaded_size
eta = remaining_size / speed if speed > 0 else 0
logger.info(f'Upload Progress: {percent:.1f}% - Speed: {speed/1024:.1f} KB/s - ETA: {eta:.1f}s')
last_update = current_time
sftp.putfo(zip_file, remote_path, callback=progress_callback)
logger.info('TXT dosyası başarıyla yüklendi')
# İzinleri ayarla
self.execute_command(f'chmod 644 "{remote_path}"')
# Venv kontrolü yap
logger.info('Virtual environment kontrol ediliyor')
venv_exists = self.check_venv_exists(project)
if not venv_exists:
# Venv oluştur
logger.info('Virtual environment oluşturuluyor')
venv_cmd = f'cd "{project.get_full_path()}" && python3 -m venv venv'
stdout, stderr, status = self.execute_command(venv_cmd)
if not status:
error_msg = f'Venv oluşturma hatası: {stderr}'
logger.error(error_msg)
return False, stderr
logger.info('Virtual environment başarıyla oluşturuldu')
else:
logger.info('Mevcut virtual environment kullanılacak')
# Requirements'ları kur
logger.info('Requirements kuruluyor')
install_cmd = f'''
cd "{project.get_full_path()}" && \
source venv/bin/activate && \
pip install --upgrade pip 2>&1 && \
pip install -r req.txt 2>&1
'''
stdout, stderr, status = self.execute_command(install_cmd)
if not status:
# Pip çıktısını logla
error_msg = 'Requirements kurulum hatası'
logger.error(error_msg)
logger.error(f'Pip çıktısı:\n{stdout}')
if stderr:
logger.error(f'Pip hata çıktısı:\n{stderr}')
return False, stdout if stdout else stderr
# Başarılı pip çıktısını da logla
logger.info('Requirements başarıyla kuruldu')
logger.info(f'Pip kurulum çıktısı:\n{stdout}')
return True, "Requirements dosyası yüklendi ve kuruldu"
else: # ZIP dosyası
# Geçici dizin oluştur
temp_dir = f'/tmp/project_upload_{project.id}'
mkdir_cmd = f'rm -rf {temp_dir} && mkdir -p {temp_dir}'
stdout, stderr, status = self.execute_command(mkdir_cmd)
if not status:
return False, f'Geçici dizin oluşturulamadı: {stderr}'
# ZIP dosyasını geçici dizine yükle
remote_zip = f"{temp_dir}/upload.zip"
logger.info(f'Zip dosyası "{remote_zip}" konumuna yükleniyor')
# İlerleme için callback fonksiyonu
total_size = zip_file.size
uploaded_size = 0
start_time = timezone.now()
last_update = start_time
def progress_callback(sent_bytes, remaining_bytes):
nonlocal uploaded_size, start_time, last_update
uploaded_size = sent_bytes
current_time = timezone.now()
elapsed_time = (current_time - start_time).total_seconds()
# Her 0.5 saniyede bir güncelle
if (current_time - last_update).total_seconds() >= 0.5:
if elapsed_time > 0:
speed = uploaded_size / elapsed_time # bytes/second
percent = (uploaded_size / total_size) * 100
remaining_size = total_size - uploaded_size
eta = remaining_size / speed if speed > 0 else 0
logger.info(f'Upload Progress: {percent:.1f}% - Speed: {speed/1024:.1f} KB/s - ETA: {eta:.1f}s')
last_update = current_time
sftp.putfo(zip_file, remote_zip, callback=progress_callback)
logger.info('Zip dosyası başarıyla yüklendi')
# Zip dosyasını çıkart
logger.info('Zip dosyası çıkartılıyor')
unzip_cmd = f'''
cd "{temp_dir}" && \
unzip -o upload.zip && \
rm upload.zip && \
cp -rf * "{project.get_full_path()}/" && \
cd / && \
rm -rf "{temp_dir}"
'''
stdout, stderr, status = self.execute_command(unzip_cmd)
if not status:
error_msg = f'Zip çıkartma hatası: {stderr}'
logger.error(error_msg)
return False, stderr
logger.info('Zip dosyası başarıyla çıkartıldı')
# req.txt var mı kontrol et
logger.info('req.txt dosyası kontrol ediliyor')
check_req = f'test -f "{project.get_full_path()}/req.txt" && echo "exists"'
stdout, stderr, status = self.execute_command(check_req)
if status and stdout.strip() == "exists":
logger.info('req.txt bulundu, venv kurulumu başlatılıyor')
# Venv kontrolü yap
logger.info('Virtual environment kontrol ediliyor')
venv_exists = self.check_venv_exists(project)
if not venv_exists:
# Venv oluştur
logger.info('Virtual environment oluşturuluyor')
venv_cmd = f'cd "{project.get_full_path()}" && python3 -m venv venv'
stdout, stderr, status = self.execute_command(venv_cmd)
if not status:
error_msg = f'Venv oluşturma hatası: {stderr}'
logger.error(error_msg)
return False, stderr
logger.info('Virtual environment başarıyla oluşturuldu')
else:
logger.info('Mevcut virtual environment kullanılacak')
# Requirements'ları kur
logger.info('Requirements kuruluyor')
install_cmd = f'''
cd "{project.get_full_path()}" && \
source venv/bin/activate && \
pip install --upgrade pip 2>&1 && \
pip install -r req.txt 2>&1
'''
stdout, stderr, status = self.execute_command(install_cmd)
if not status:
# Pip çıktısını logla
error_msg = 'Requirements kurulum hatası'
logger.error(error_msg)
logger.error(f'Pip çıktısı:\n{stdout}')
if stderr:
logger.error(f'Pip hata çıktısı:\n{stderr}')
return False, stdout if stdout else stderr
# Başarılı pip çıktısını da logla
logger.info('Requirements başarıyla kuruldu')
logger.info(f'Pip kurulum çıktısı:\n{stdout}')
return True, "Proje dosyaları yüklendi ve requirements kuruldu"
return True, "Proje dosyaları başarıyla yüklendi"
finally:
sftp.close()
logger.info('SFTP bağlantısı kapatıldı')
# Son disk kullanımını al ve değişimi logla
final_disk = self.get_disk_usage()
if final_disk:
logger.info(f'Son disk kullanımı - Toplam: {final_disk["total"]}, Kullanılan: {final_disk["used"]}, Boş: {final_disk["available"]}')
except Exception as e:
logger.exception("Dosya yükleme hatası")
return False, str(e)
def check_venv_exists(self, project):
"""Virtual environment'ın var olup olmadığını kontrol et"""
cmd = f'test -d "{project.get_full_path()}/venv" && echo "exists" || echo "not exists"'
stdout, stderr, status = self.execute_command(cmd)
return stdout.strip() == "exists"
def setup_venv_and_install_requirements(self, project):
"""Virtual environment oluştur ve requirements'ları kur"""
try:
logger.info(f'"{project.folder_name}" projesi için venv oluşturuluyor')
# Venv klasörünün varlığını kontrol et
check_cmd = f'test -d "{project.get_full_path()}/venv" && echo "exists" || echo "not exists"'
stdout, stderr, status = self.execute_command(check_cmd)
if stdout.strip() == "exists":
logger.info('Mevcut venv kullanılacak')
else:
# Venv oluştur
logger.info('Yeni venv oluşturuluyor')
venv_cmd = f'cd "{project.get_full_path()}" && python3 -m venv venv'
stdout, stderr, status = self.execute_command(venv_cmd)
if not status:
logger.error(f'Venv oluşturma hatası: {stderr}')
return False, f'Venv oluşturulamadı: {stderr}'
logger.info('Venv başarıyla oluşturuldu')
# pip'i güncelle ve requirements'ları kur
logger.info('Requirements kuruluyor')
install_cmd = f'''
cd "{project.get_full_path()}" && \
source venv/bin/activate && \
pip install --upgrade pip && \
pip install -r req.txt
'''
stdout, stderr, status = self.execute_command(install_cmd)
if not status:
logger.error(f'Requirements kurulum hatası: {stderr}')
return False, f'Requirements kurulamadı: {stderr}'
logger.info('Requirements başarıyla kuruldu')
return True, "Venv oluşturuldu ve requirements kuruldu"
except Exception as e:
logger.exception(f"Venv ve requirements kurulum hatası: {str(e)}")
return False, str(e)
# SSHManager'ın tüm metodları buraya taşınacak...
# utils.py'daki SSHManager sınıfının tüm içeriğini buraya kopyalayın