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ı aç 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ı açı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