yeni
This commit is contained in:
67
ssh_manager/2backup.py
Normal file
67
ssh_manager/2backup.py
Normal file
@ -0,0 +1,67 @@
|
||||
import os
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from config.settings import BASE_DIR
|
||||
import boto3
|
||||
import botocore
|
||||
|
||||
# Configuration for Vultr S3 storage
|
||||
script_klasoru = BASE_DIR
|
||||
haric_dosya_uzantilari = ['.zip']
|
||||
excluded_folders = ['venv', 'yedek', '.idea']
|
||||
hostname = "ams1.vultrobjects.com"
|
||||
secret_key = "Ec1pq3OQAObFLOQrfAVqJKhDAk4BkT7OqgYszlef"
|
||||
access_key = "KQAOMJ8CQ8HP4CY23YPK"
|
||||
|
||||
def zip_klasor(ziplenecek_klasor, hedef_zip_adi, haric_klasorler=[], haric_dosya_uzantilari=[]):
|
||||
"""Zip the target folder excluding specified folders and file extensions."""
|
||||
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_yolu = os.path.join(klasor_yolu, dosya)
|
||||
if not any(dosya_yolu.endswith(ext) for ext in haric_dosya_uzantilari):
|
||||
zipf.write(dosya_yolu, os.path.relpath(dosya_yolu, ziplenecek_klasor))
|
||||
|
||||
def job(folder_name):
|
||||
"""Create a zip backup and upload it to Vultr S3."""
|
||||
session = boto3.session.Session()
|
||||
client = session.client('s3', region_name='ams1',
|
||||
endpoint_url=f'https://{hostname}',
|
||||
aws_access_key_id=access_key,
|
||||
aws_secret_access_key=secret_key,
|
||||
config=botocore.client.Config(signature_version='s3v4'))
|
||||
|
||||
output_zip = os.path.join(os.getcwd(), f"{folder_name}.zip")
|
||||
zip_klasor(script_klasoru, output_zip, excluded_folders, haric_dosya_uzantilari)
|
||||
|
||||
try:
|
||||
# Ensure the bucket exists
|
||||
try:
|
||||
client.head_bucket(Bucket=folder_name)
|
||||
print(f"Bucket already exists: {folder_name}")
|
||||
except botocore.exceptions.ClientError as e:
|
||||
if e.response['Error']['Code'] == '404':
|
||||
print(f"Bucket not found, creating: {folder_name}")
|
||||
client.create_bucket(Bucket=folder_name)
|
||||
|
||||
# Upload the file to S3 using boto3 (avoids XAmzContentSHA256Mismatch)
|
||||
try:
|
||||
client.upload_file(
|
||||
output_zip,
|
||||
folder_name,
|
||||
os.path.basename(output_zip),
|
||||
ExtraArgs={
|
||||
'ACL': 'public-read',
|
||||
'ContentType': 'application/zip'
|
||||
}
|
||||
)
|
||||
print(f"File successfully uploaded: {output_zip}")
|
||||
except Exception as e:
|
||||
print(f"Upload error: {e}")
|
||||
|
||||
finally:
|
||||
# Clean up local zip file
|
||||
if os.path.exists(output_zip):
|
||||
os.remove(output_zip)
|
||||
print(f"Local zip file deleted: {output_zip}")
|
||||
1
ssh_manager/__init__.py
Normal file
1
ssh_manager/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
default_app_config = 'ssh_manager.apps.SshManagerConfig'
|
||||
71
ssh_manager/admin.py
Normal file
71
ssh_manager/admin.py
Normal file
@ -0,0 +1,71 @@
|
||||
from django.contrib import admin
|
||||
from .models import SSHCredential, Project, SSHLog
|
||||
|
||||
@admin.register(SSHCredential)
|
||||
class SSHCredentialAdmin(admin.ModelAdmin):
|
||||
list_display = ('hostname', 'username', 'port', 'is_online', 'last_check')
|
||||
list_filter = ('is_online', 'created_at')
|
||||
search_fields = ('hostname', 'username')
|
||||
readonly_fields = ('is_online', 'last_check', 'created_at')
|
||||
fieldsets = (
|
||||
('Bağlantı Bilgileri', {
|
||||
'fields': ('hostname', 'username', 'password', 'port')
|
||||
}),
|
||||
('Durum Bilgisi', {
|
||||
'fields': ('is_online', 'last_check', 'created_at'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
|
||||
@admin.register(Project)
|
||||
class ProjectAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'folder_name', 'url', 'ssh_credential', 'created_at')
|
||||
list_filter = ('ssh_credential', 'created_at')
|
||||
search_fields = ('name', 'folder_name', 'url')
|
||||
readonly_fields = ('created_at', 'updated_at')
|
||||
fieldsets = (
|
||||
('Temel Bilgiler', {
|
||||
'fields': ('name', 'folder_name', 'ssh_credential')
|
||||
}),
|
||||
('Domain Bilgisi', {
|
||||
'fields': ('url',),
|
||||
'description': 'Nginx konfigürasyonu için domain adı (Örnek: example.com)'
|
||||
}),
|
||||
('Zaman Bilgileri', {
|
||||
'fields': ('created_at', 'updated_at'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
|
||||
def get_full_path(self, obj):
|
||||
return obj.get_full_path()
|
||||
get_full_path.short_description = 'Tam Yol'
|
||||
|
||||
@admin.register(SSHLog)
|
||||
class SSHLogAdmin(admin.ModelAdmin):
|
||||
list_display = ('ssh_credential', 'log_type', 'command', 'status', 'created_at')
|
||||
list_filter = ('log_type', 'status', 'created_at')
|
||||
search_fields = ('command', 'output')
|
||||
readonly_fields = ('created_at',)
|
||||
fieldsets = (
|
||||
('Log Detayları', {
|
||||
'fields': ('ssh_credential', 'log_type', 'command', 'status')
|
||||
}),
|
||||
('Çıktı', {
|
||||
'fields': ('output',),
|
||||
'classes': ('wide',)
|
||||
}),
|
||||
('Zaman Bilgisi', {
|
||||
'fields': ('created_at',),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super().get_queryset(request).select_related('ssh_credential')
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False # Log kayıtları manuel olarak eklenemez
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return False # Log kayıtları değiştirilemez
|
||||
24
ssh_manager/apps.py
Normal file
24
ssh_manager/apps.py
Normal file
@ -0,0 +1,24 @@
|
||||
from django.apps import AppConfig
|
||||
from django.conf import settings
|
||||
|
||||
def check_server_connection():
|
||||
from .ssh_client import SSHManager # utils yerine ssh_client'dan import et
|
||||
from .models import SSHCredential
|
||||
|
||||
# Tüm SSH bağlantılarını kontrol et
|
||||
for credential in SSHCredential.objects.all():
|
||||
ssh_manager = SSHManager(credential)
|
||||
is_online = ssh_manager.check_connection()
|
||||
|
||||
# Bağlantı durumunu güncelle
|
||||
credential.is_online = is_online
|
||||
credential.save(update_fields=['is_online', 'last_check'])
|
||||
|
||||
ssh_manager.close()
|
||||
|
||||
class SshManagerConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'ssh_manager'
|
||||
|
||||
def ready(self):
|
||||
import ssh_manager.signals # signals'ı import et
|
||||
626
ssh_manager/backup.py
Normal file
626
ssh_manager/backup.py
Normal file
@ -0,0 +1,626 @@
|
||||
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
|
||||
|
||||
|
||||
11
ssh_manager/middleware.py
Normal file
11
ssh_manager/middleware.py
Normal file
@ -0,0 +1,11 @@
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
class SSHConnectionMiddleware(MiddlewareMixin):
|
||||
_connection_checked = False # Sınıf değişkeni olarak tanımla
|
||||
|
||||
def process_request(self, request):
|
||||
# Sadece bir kez çalıştır
|
||||
if not SSHConnectionMiddleware._connection_checked:
|
||||
from .apps import check_server_connection
|
||||
check_server_connection()
|
||||
SSHConnectionMiddleware._connection_checked = True
|
||||
54
ssh_manager/migrations/0001_initial.py
Normal file
54
ssh_manager/migrations/0001_initial.py
Normal file
@ -0,0 +1,54 @@
|
||||
# Generated by Django 5.1.5 on 2025-01-23 05:38
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SSHCredential',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('hostname', models.CharField(max_length=255)),
|
||||
('username', models.CharField(max_length=100)),
|
||||
('password', models.CharField(max_length=100)),
|
||||
('port', models.IntegerField(default=22)),
|
||||
('is_online', models.BooleanField(default=False)),
|
||||
('last_check', models.DateTimeField(auto_now=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Project',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('folder_name', models.CharField(max_length=255)),
|
||||
('path', models.CharField(max_length=500)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('ssh_credential', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ssh_manager.sshcredential')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SSHLog',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('log_type', models.CharField(choices=[('connection', 'Bağlantı Kontrolü'), ('command', 'Komut Çalıştırma'), ('folder', 'Klasör İşlemi')], max_length=20)),
|
||||
('command', models.TextField()),
|
||||
('output', models.TextField()),
|
||||
('status', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('ssh_credential', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ssh_manager.sshcredential')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
]
|
||||
20
ssh_manager/migrations/0002_default_ssh_credential.py
Normal file
20
ssh_manager/migrations/0002_default_ssh_credential.py
Normal file
@ -0,0 +1,20 @@
|
||||
from django.db import migrations
|
||||
|
||||
def create_default_ssh_credential(apps, schema_editor):
|
||||
SSHCredential = apps.get_model('ssh_manager', 'SSHCredential')
|
||||
if not SSHCredential.objects.exists():
|
||||
SSHCredential.objects.create(
|
||||
hostname='localhost', # Varsayılan sunucu bilgileri
|
||||
username='root',
|
||||
password='password',
|
||||
port=22
|
||||
)
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('ssh_manager', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_default_ssh_credential),
|
||||
]
|
||||
@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.1.5 on 2025-01-23 06:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ssh_manager', '0002_default_ssh_credential'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='project',
|
||||
name='path',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sshcredential',
|
||||
name='base_path',
|
||||
field=models.CharField(default=1, help_text='Projelerin oluşturulacağı ana dizin', max_length=500),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,33 @@
|
||||
# Generated by Django 5.1.5 on 2025-01-23 15:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ssh_manager', '0003_remove_project_path_sshcredential_base_path'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='project',
|
||||
name='updated_at',
|
||||
field=models.DateTimeField(auto_now=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='folder_name',
|
||||
field=models.CharField(max_length=100),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='name',
|
||||
field=models.CharField(max_length=100),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sshlog',
|
||||
name='log_type',
|
||||
field=models.CharField(choices=[('connection', 'Bağlantı Kontrolü'), ('command', 'Komut Çalıştırma'), ('folder', 'Klasör İşlemi'), ('file', 'Dosya İşlemi')], max_length=20),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,39 @@
|
||||
# Generated by Django 5.1.5 on 2025-01-25 05:25
|
||||
|
||||
import django.db.models.deletion
|
||||
import ssh_manager.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ssh_manager', '0004_project_updated_at_alter_project_folder_name_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='project',
|
||||
options={'ordering': ['-created_at'], 'verbose_name': 'Proje', 'verbose_name_plural': 'Projeler'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='project',
|
||||
name='url',
|
||||
field=models.CharField(blank=True, help_text='Örnek: example.com veya subdomain.example.com', max_length=255, null=True, unique=True, validators=[ssh_manager.models.validate_domain], verbose_name='Domain Adı'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='folder_name',
|
||||
field=models.CharField(max_length=100, verbose_name='Klasör Adı'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='name',
|
||||
field=models.CharField(max_length=100, verbose_name='Proje Adı'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='ssh_credential',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ssh_manager.sshcredential', verbose_name='SSH Bağlantısı'),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,39 @@
|
||||
# Generated by Django 5.1.5 on 2025-01-29 04:07
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ssh_manager', '0005_alter_project_options_project_url_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='project',
|
||||
name='disk_usage',
|
||||
field=models.CharField(blank=True, help_text='Projenin disk kullanımı', max_length=20, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='folder_name',
|
||||
field=models.CharField(max_length=100),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='name',
|
||||
field=models.CharField(max_length=100),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='ssh_credential',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ssh_manager.sshcredential'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='url',
|
||||
field=models.CharField(blank=True, max_length=200, null=True),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,38 @@
|
||||
# Generated by Django 5.1.5 on 2025-01-30 10:52
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ssh_manager', '0006_project_disk_usage_alter_project_folder_name_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='project',
|
||||
name='last_backup',
|
||||
field=models.DateTimeField(blank=True, null=True, verbose_name='Son Yedekleme'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='disk_usage',
|
||||
field=models.CharField(blank=True, max_length=20, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='folder_name',
|
||||
field=models.CharField(max_length=100, verbose_name='Klasör Adı'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='name',
|
||||
field=models.CharField(max_length=100, verbose_name='Proje Adı'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='url',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,33 @@
|
||||
# Generated by Django 5.1.5 on 2025-03-01 18:39
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ssh_manager', '0007_project_last_backup_alter_project_disk_usage_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='project',
|
||||
name='email',
|
||||
field=models.EmailField(blank=True, max_length=254, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='project',
|
||||
name='image',
|
||||
field=models.ImageField(blank=True, null=True, upload_to='project_images'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='project',
|
||||
name='phone',
|
||||
field=models.CharField(blank=True, max_length=20, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sshlog',
|
||||
name='log_type',
|
||||
field=models.CharField(choices=[('connection', 'Bağlantı Kontrolü'), ('command', 'Komut Çalıştırma'), ('folder', 'Klasör İşlemi'), ('file', 'Dosya İşlemi'), ('backup', 'Yedekleme')], max_length=20),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,40 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-20 00:26
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ssh_manager', '0008_project_email_project_image_project_phone_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='project',
|
||||
name='email',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='project',
|
||||
name='image',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='project',
|
||||
name='phone',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='project',
|
||||
name='is_site_active',
|
||||
field=models.BooleanField(default=False, verbose_name='Site Aktif'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='project',
|
||||
name='last_site_check',
|
||||
field=models.DateTimeField(blank=True, null=True, verbose_name='Son Site Kontrolü'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='project',
|
||||
name='meta_key',
|
||||
field=models.CharField(blank=True, help_text='Site aktiflik kontrolü için benzersiz anahtar', max_length=32, null=True, verbose_name='Meta Key'),
|
||||
),
|
||||
]
|
||||
18
ssh_manager/migrations/0010_sshcredential_disk_usage.py
Normal file
18
ssh_manager/migrations/0010_sshcredential_disk_usage.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-20 02:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ssh_manager', '0009_remove_project_email_remove_project_image_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='sshcredential',
|
||||
name='disk_usage',
|
||||
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Disk Kullanımı'),
|
||||
),
|
||||
]
|
||||
46
ssh_manager/migrations/0011_customer_project_customer.py
Normal file
46
ssh_manager/migrations/0011_customer_project_customer.py
Normal file
@ -0,0 +1,46 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-20 11:19
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ssh_manager', '0010_sshcredential_disk_usage'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Customer',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('customer_type', models.CharField(choices=[('individual', 'Bireysel'), ('corporate', 'Kurumsal')], max_length=20, verbose_name='Müşteri Tipi')),
|
||||
('name', models.CharField(max_length=200, verbose_name='Ad/Firma Adı')),
|
||||
('email', models.EmailField(max_length=254, verbose_name='E-posta')),
|
||||
('phone', models.CharField(blank=True, max_length=20, null=True, verbose_name='Telefon')),
|
||||
('address', models.TextField(blank=True, null=True, verbose_name='Adres')),
|
||||
('surname', models.CharField(blank=True, max_length=100, null=True, verbose_name='Soyad')),
|
||||
('birth_date', models.DateField(blank=True, null=True, verbose_name='Doğum Tarihi')),
|
||||
('tc_number', models.CharField(blank=True, max_length=11, null=True, verbose_name='TC Kimlik No')),
|
||||
('company_name', models.CharField(blank=True, max_length=200, null=True, verbose_name='Şirket Adı')),
|
||||
('tax_number', models.CharField(blank=True, max_length=20, null=True, verbose_name='Vergi No')),
|
||||
('tax_office', models.CharField(blank=True, max_length=100, null=True, verbose_name='Vergi Dairesi')),
|
||||
('authorized_person', models.CharField(blank=True, max_length=200, null=True, verbose_name='Yetkili Kişi')),
|
||||
('notes', models.TextField(blank=True, null=True, verbose_name='Notlar')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Oluşturma Tarihi')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Güncelleme Tarihi')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Aktif')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Müşteri',
|
||||
'verbose_name_plural': 'Müşteriler',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='project',
|
||||
name='customer',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='ssh_manager.customer', verbose_name='Müşteri'),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,38 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-20 13:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ssh_manager', '0011_customer_project_customer'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='sshcredential',
|
||||
name='connection_status',
|
||||
field=models.CharField(choices=[('connected', 'Bağlı'), ('failed', 'Başarısız'), ('unknown', 'Bilinmiyor')], default='unknown', max_length=20),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sshcredential',
|
||||
name='is_default',
|
||||
field=models.BooleanField(default=False, verbose_name='Varsayılan Host'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sshcredential',
|
||||
name='last_checked',
|
||||
field=models.DateTimeField(blank=True, null=True, verbose_name='Son Kontrol'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sshcredential',
|
||||
name='name',
|
||||
field=models.CharField(default='Varsayılan Host', max_length=100, verbose_name='Host Adı'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sshcredential',
|
||||
name='disk_usage',
|
||||
field=models.FloatField(blank=True, null=True, verbose_name='Disk Kullanımı (%)'),
|
||||
),
|
||||
]
|
||||
1
ssh_manager/migrations/__init__.py
Normal file
1
ssh_manager/migrations/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# Bu dosya boş kalacak
|
||||
198
ssh_manager/models.py
Normal file
198
ssh_manager/models.py
Normal file
@ -0,0 +1,198 @@
|
||||
from django.db import models
|
||||
import logging
|
||||
from .ssh_client import SSHManager
|
||||
from django.core.validators import URLValidator
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def validate_domain(value):
|
||||
"""Domain formatını kontrol et"""
|
||||
if ' ' in value: # Boşluk kontrolü
|
||||
raise ValidationError('Domain adında boşluk olamaz')
|
||||
if not any(char.isalpha() for char in value): # En az bir harf kontrolü
|
||||
raise ValidationError('Domain adında en az bir harf olmalıdır')
|
||||
if not all(c.isalnum() or c in '-.' for c in value): # Geçerli karakter kontrolü
|
||||
raise ValidationError('Domain adında sadece harf, rakam, tire (-) ve nokta (.) olabilir')
|
||||
|
||||
class Customer(models.Model):
|
||||
CUSTOMER_TYPES = (
|
||||
('individual', 'Bireysel'),
|
||||
('corporate', 'Kurumsal'),
|
||||
)
|
||||
|
||||
customer_type = models.CharField(max_length=20, choices=CUSTOMER_TYPES, verbose_name='Müşteri Tipi')
|
||||
|
||||
# Ortak alanlar
|
||||
name = models.CharField(max_length=200, verbose_name='Ad/Firma Adı')
|
||||
email = models.EmailField(verbose_name='E-posta')
|
||||
phone = models.CharField(max_length=20, blank=True, null=True, verbose_name='Telefon')
|
||||
address = models.TextField(blank=True, null=True, verbose_name='Adres')
|
||||
|
||||
# Bireysel müşteri alanları
|
||||
surname = models.CharField(max_length=100, blank=True, null=True, verbose_name='Soyad')
|
||||
birth_date = models.DateField(blank=True, null=True, verbose_name='Doğum Tarihi')
|
||||
tc_number = models.CharField(max_length=11, blank=True, null=True, verbose_name='TC Kimlik No')
|
||||
|
||||
# Kurumsal müşteri alanları
|
||||
company_name = models.CharField(max_length=200, blank=True, null=True, verbose_name='Şirket Adı')
|
||||
tax_number = models.CharField(max_length=20, blank=True, null=True, verbose_name='Vergi No')
|
||||
tax_office = models.CharField(max_length=100, blank=True, null=True, verbose_name='Vergi Dairesi')
|
||||
authorized_person = models.CharField(max_length=200, blank=True, null=True, verbose_name='Yetkili Kişi')
|
||||
|
||||
# Genel bilgiler
|
||||
notes = models.TextField(blank=True, null=True, verbose_name='Notlar')
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='Oluşturma Tarihi')
|
||||
updated_at = models.DateTimeField(auto_now=True, verbose_name='Güncelleme Tarihi')
|
||||
is_active = models.BooleanField(default=True, verbose_name='Aktif')
|
||||
|
||||
def __str__(self):
|
||||
if self.customer_type == 'corporate':
|
||||
return f"{self.company_name or self.name} (Kurumsal)"
|
||||
else:
|
||||
return f"{self.name} {self.surname or ''} (Bireysel)".strip()
|
||||
|
||||
def get_display_name(self):
|
||||
"""Görüntüleme için uygun isim döndür"""
|
||||
if self.customer_type == 'corporate':
|
||||
return self.company_name or self.name
|
||||
else:
|
||||
return f"{self.name} {self.surname or ''}".strip()
|
||||
|
||||
def clean(self):
|
||||
"""Model validasyonu"""
|
||||
if self.customer_type == 'individual':
|
||||
if not self.surname:
|
||||
raise ValidationError('Bireysel müşteriler için soyad zorunludur.')
|
||||
elif self.customer_type == 'corporate':
|
||||
if not self.company_name:
|
||||
raise ValidationError('Kurumsal müşteriler için şirket adı zorunludur.')
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Müşteri"
|
||||
verbose_name_plural = "Müşteriler"
|
||||
ordering = ['-created_at']
|
||||
|
||||
class SSHCredential(models.Model):
|
||||
name = models.CharField(max_length=100, verbose_name='Host Adı', default='Varsayılan Host')
|
||||
hostname = models.CharField(max_length=255)
|
||||
username = models.CharField(max_length=100)
|
||||
password = models.CharField(max_length=100)
|
||||
port = models.IntegerField(default=22)
|
||||
base_path = models.CharField(max_length=500, help_text="Projelerin oluşturulacağı ana dizin") # Yeni alan
|
||||
is_default = models.BooleanField(default=False, verbose_name='Varsayılan Host')
|
||||
connection_status = models.CharField(max_length=20, default='unknown', choices=[
|
||||
('connected', 'Bağlı'),
|
||||
('failed', 'Başarısız'),
|
||||
('unknown', 'Bilinmiyor')
|
||||
])
|
||||
is_online = models.BooleanField(default=False) # Bağlantı durumu
|
||||
last_check = models.DateTimeField(auto_now=True) # Son kontrol zamanı
|
||||
last_checked = models.DateTimeField(null=True, blank=True, verbose_name='Son Kontrol')
|
||||
disk_usage = models.FloatField(null=True, blank=True, verbose_name='Disk Kullanımı (%)')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def get_manager(self):
|
||||
"""SSHManager instance'ı döndür"""
|
||||
return SSHManager(self)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.username}@{self.hostname}"
|
||||
|
||||
class Project(models.Model):
|
||||
name = models.CharField(max_length=100, verbose_name='Proje Adı')
|
||||
folder_name = models.CharField(max_length=100, verbose_name='Klasör Adı')
|
||||
ssh_credential = models.ForeignKey(SSHCredential, on_delete=models.CASCADE)
|
||||
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, verbose_name='Müşteri', null=True, blank=True)
|
||||
url = models.CharField(max_length=255, null=True, blank=True)
|
||||
disk_usage = models.CharField(max_length=20, null=True, blank=True)
|
||||
last_backup = models.DateTimeField(null=True, blank=True, verbose_name='Son Yedekleme')
|
||||
meta_key = models.CharField(max_length=32, null=True, blank=True, verbose_name='Meta Key', help_text='Site aktiflik kontrolü için benzersiz anahtar')
|
||||
is_site_active = models.BooleanField(default=False, verbose_name='Site Aktif')
|
||||
last_site_check = models.DateTimeField(null=True, blank=True, verbose_name='Son Site Kontrolü')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def generate_meta_key(self):
|
||||
"""Benzersiz meta key oluştur"""
|
||||
import uuid
|
||||
self.meta_key = uuid.uuid4().hex[:32]
|
||||
return self.meta_key
|
||||
|
||||
def get_meta_tag(self):
|
||||
"""HTML meta tag'ı döndür"""
|
||||
if not self.meta_key:
|
||||
self.generate_meta_key()
|
||||
self.save()
|
||||
return f'<meta name="site-verification" content="{self.meta_key}">'
|
||||
|
||||
def clean(self):
|
||||
# URL formatını kontrol et
|
||||
if self.url:
|
||||
# URL'den http/https ve www. kısımlarını temizle
|
||||
cleaned_url = self.url.lower()
|
||||
for prefix in ['http://', 'https://', 'www.']:
|
||||
if cleaned_url.startswith(prefix):
|
||||
cleaned_url = cleaned_url[len(prefix):]
|
||||
# Sondaki / işaretini kaldır
|
||||
cleaned_url = cleaned_url.rstrip('/')
|
||||
self.url = cleaned_url
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.clean()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def get_full_path(self):
|
||||
"""Projenin tam yolunu döndür"""
|
||||
return f"{self.ssh_credential.base_path}/{self.folder_name}"
|
||||
|
||||
@property
|
||||
def has_requirements(self):
|
||||
"""Projenin req.txt dosyası var mı kontrol et"""
|
||||
try:
|
||||
ssh_manager = self.ssh_credential.get_manager()
|
||||
check_cmd = f'test -f "{self.get_full_path()}/req.txt" && echo "exists"'
|
||||
stdout, stderr, status = ssh_manager.execute_command(check_cmd)
|
||||
|
||||
# Debug için log ekle
|
||||
logger.info(f"has_requirements check for project {self.id}")
|
||||
logger.info(f"Command: {check_cmd}")
|
||||
logger.info(f"Status: {status}, Stdout: {stdout}, Stderr: {stderr}")
|
||||
|
||||
return status and stdout.strip() == "exists"
|
||||
except Exception as e:
|
||||
logger.exception(f"Error checking requirements for project {self.id}")
|
||||
return False
|
||||
finally:
|
||||
if 'ssh_manager' in locals():
|
||||
ssh_manager.close()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Proje"
|
||||
verbose_name_plural = "Projeler"
|
||||
ordering = ['-created_at']
|
||||
|
||||
class SSHLog(models.Model):
|
||||
LOG_TYPES = (
|
||||
('connection', 'Bağlantı Kontrolü'),
|
||||
('command', 'Komut Çalıştırma'),
|
||||
('folder', 'Klasör İşlemi'),
|
||||
('file', 'Dosya İşlemi'),
|
||||
('backup', 'Yedekleme'), # Yeni tip ekleyelim
|
||||
)
|
||||
|
||||
ssh_credential = models.ForeignKey(SSHCredential, on_delete=models.CASCADE)
|
||||
log_type = models.CharField(max_length=20, choices=LOG_TYPES)
|
||||
command = models.TextField()
|
||||
output = models.TextField()
|
||||
status = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.ssh_credential.hostname} - {self.log_type} - {self.created_at}"
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created_at']
|
||||
13
ssh_manager/settings.py
Normal file
13
ssh_manager/settings.py
Normal file
@ -0,0 +1,13 @@
|
||||
# Google Drive API Settings
|
||||
GOOGLE_DRIVE_CREDENTIALS = {
|
||||
"type": "service_account",
|
||||
"project_id": "your-project-id",
|
||||
"private_key_id": "your-private-key-id",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nYour Private Key\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "your-service-account@your-project.iam.gserviceaccount.com",
|
||||
"client_id": "your-client-id",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/your-service-account"
|
||||
}
|
||||
13
ssh_manager/signals.py
Normal file
13
ssh_manager/signals.py
Normal file
@ -0,0 +1,13 @@
|
||||
from django.db.models.signals import post_migrate
|
||||
from django.dispatch import receiver
|
||||
from django.apps import apps
|
||||
from .ssh_client import SSHManager # utils yerine ssh_client'dan import et
|
||||
|
||||
@receiver(post_migrate)
|
||||
def check_connection_on_startup(sender, **kwargs):
|
||||
"""
|
||||
Uygulama başlatıldığında sunucu bağlantısını kontrol et
|
||||
"""
|
||||
if sender.name == 'ssh_manager':
|
||||
from .apps import check_server_connection
|
||||
check_server_connection()
|
||||
607
ssh_manager/ssh_client.py
Normal file
607
ssh_manager/ssh_client.py
Normal 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ı 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
|
||||
19
ssh_manager/ssh_manager.py
Normal file
19
ssh_manager/ssh_manager.py
Normal file
@ -0,0 +1,19 @@
|
||||
def read_requirements_file(self, binary=False):
|
||||
"""
|
||||
Requirements dosyasını oku
|
||||
binary=True ise binary modda okur
|
||||
"""
|
||||
try:
|
||||
command = f'cat "{self.project_path}/req.txt"'
|
||||
stdin, stdout, stderr = self.client.exec_command(command)
|
||||
|
||||
if binary:
|
||||
# Binary modda oku
|
||||
return stdout.read()
|
||||
else:
|
||||
# Text modda oku
|
||||
return stdout.read().decode('utf-8', errors='replace')
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("req.txt okunamadı")
|
||||
return None
|
||||
0
ssh_manager/templatetags/__init__.py
Normal file
0
ssh_manager/templatetags/__init__.py
Normal file
9
ssh_manager/templatetags/ssh_manager_tags.py
Normal file
9
ssh_manager/templatetags/ssh_manager_tags.py
Normal file
@ -0,0 +1,9 @@
|
||||
from django import template
|
||||
from ssh_manager.models import Project
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.simple_tag
|
||||
def get_projects():
|
||||
"""Tüm projeleri döndür"""
|
||||
return Project.objects.all().select_related('ssh_credential', 'customer')
|
||||
79
ssh_manager/urls.py
Normal file
79
ssh_manager/urls.py
Normal file
@ -0,0 +1,79 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
from django.apps import apps
|
||||
from django.core.management import call_command
|
||||
|
||||
# İlk yüklemede SSH kontrolü yap
|
||||
if apps.apps_ready:
|
||||
try:
|
||||
call_command('check_ssh')
|
||||
except:
|
||||
pass
|
||||
|
||||
# app_name = 'ssh_manager' # namespace'i kaldır
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.dashboard, name='project_list'), # Ana sayfa dashboard olsun
|
||||
path('dashboard/', views.dashboard, name='dashboard'),
|
||||
path('projeler/', views.project_list, name='projeler'),
|
||||
path('host-yonetimi/', views.host_yonetimi, name='host_yonetimi'),
|
||||
path('yedeklemeler/', views.yedeklemeler, name='yedeklemeler'),
|
||||
path('islem-gecmisi/', views.islem_gecmisi, name='islem_gecmisi'),
|
||||
path('ayarlar/', views.ayarlar, name='ayarlar'),
|
||||
path('musteriler/', views.musteriler, name='musteriler'),
|
||||
path('musteri/create/', views.create_customer, name='create_customer'),
|
||||
path('musteri/<int:customer_id>/edit/', views.edit_customer, name='edit_customer'),
|
||||
path('musteri/<int:customer_id>/delete/', views.delete_customer, name='delete_customer'),
|
||||
path('get-customer-details/<int:customer_id>/', views.get_customer_details, name='get_customer_details'),
|
||||
path('update-customer/<int:customer_id>/', views.update_customer, name='update_customer'),
|
||||
path('get_host/<int:host_id>/', views.get_host, name='get_host'),
|
||||
path('update_host/<int:host_id>/', views.update_host, name='update_host'),
|
||||
path('delete_host/<int:host_id>/', views.delete_host, name='delete_host'),
|
||||
path('update-hosts-status/', views.update_hosts_status, name='update_hosts_status'),
|
||||
path('project/create/', views.create_project, name='create_project'),
|
||||
path('project/<int:project_id>/upload/', views.upload_project_zip, name='upload_project_zip'),
|
||||
path('delete_project/<int:project_id>/', views.delete_project, name='delete_project'),
|
||||
path('project/<int:project_id>/setup-venv/', views.setup_venv, name='setup_venv'),
|
||||
path('project/<int:project_id>/check-requirements/', views.check_requirements, name='check_requirements'),
|
||||
path('project/<int:project_id>/update-requirements/', views.update_requirements, name='update_requirements'),
|
||||
path('project/<int:project_id>/delete-requirement-line/', views.delete_requirement_line, name='delete_requirement_line'),
|
||||
path('project/<int:project_id>/download-req/', views.download_req_file, name='download_req_file'),
|
||||
path('logs/<int:ssh_credential_id>/', views.view_logs, name='credential_logs'),
|
||||
path('logs/clear/', views.clear_logs, name='clear_logs'),
|
||||
path('logs/all/', views.get_all_logs, name='get_all_logs'),
|
||||
path('logs/test/', views.get_all_logs, name='get_all_logs_test'), # Test için
|
||||
path('logs/', views.view_logs, name='system_logs'),
|
||||
path('project/<int:project_id>/check-venv/', views.check_venv, name='check_venv'),
|
||||
path('project/<int:project_id>/install-requirements/', views.install_requirements, name='install_requirements'),
|
||||
path('project/<int:project_id>/check-folder/', views.check_folder_empty, name='check_folder_empty'),
|
||||
path('project/<int:project_id>/list-files/', views.list_project_files, name='list_project_files'),
|
||||
path('project/upload/', views.upload_project_files, name='upload_project_files'),
|
||||
path('get-latest-logs/', views.get_latest_logs, name='get_latest_logs'),
|
||||
path('project/<int:project_id>/restart-supervisor/', views.restart_supervisor, name='restart_supervisor'),
|
||||
path('project/<int:project_id>/refresh/', views.refresh_project, name='refresh_project'),
|
||||
# path('backup/', views.backup_projects, name='backup_projects'),
|
||||
path('backup-project/<int:project_id>/', views.backup_project, name='backup_project'),
|
||||
path('project/<int:project_id>/backup-logs/', views.project_backup_logs, name='project_backup_logs'),
|
||||
path('project/<int:project_id>/clear-logs/', views.clear_project_logs, name='clear_project_logs'),
|
||||
path('project/<int:project_id>/check-site/', views.check_site_status_view, name='check_site_status'),
|
||||
path('project/<int:project_id>/meta-key/', views.get_project_meta_key, name='get_project_meta_key'),
|
||||
path('check-all-sites/', views.check_all_sites_view, name='check_all_sites'),
|
||||
path('get-project-details/<int:project_id>/', views.get_project_details, name='get_project_details'),
|
||||
path('update-project/<int:project_id>/', views.update_project, name='update_project'),
|
||||
|
||||
# Host yönetimi URL'leri
|
||||
path('test-host-connection/<int:host_id>/', views.test_host_connection, name='test_host_connection'),
|
||||
path('refresh-all-hosts/', views.refresh_all_hosts, name='refresh_all_hosts'),
|
||||
path('create-host/', views.create_host, name='create_host'),
|
||||
path('update-host/<int:host_id>/', views.update_host, name='update_host'),
|
||||
path('get-host-details/<int:host_id>/', views.get_host_details, name='get_host_details'),
|
||||
path('delete-host/<int:host_id>/', views.delete_host, name='delete_host'),
|
||||
path('test-host-connection-form/', views.test_host_connection_form, name='test_host_connection_form'),
|
||||
|
||||
# Yedekleme endpoints
|
||||
path('start-backup/', views.start_backup, name='start_backup'),
|
||||
path('backup-all-projects/', views.backup_all_projects, name='backup_all_projects'),
|
||||
path('retry-backup/', views.retry_backup, name='retry_backup'),
|
||||
|
||||
# path('upload-to-drive/<int:project_id>/', views.upload_to_drive, name='upload_to_drive').
|
||||
]
|
||||
146
ssh_manager/utils.py
Normal file
146
ssh_manager/utils.py
Normal file
@ -0,0 +1,146 @@
|
||||
from .models import SSHLog, Project
|
||||
from django.utils import timezone
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Yardımcı fonksiyonlar buraya gelebilir
|
||||
|
||||
def check_site_status(project):
|
||||
"""
|
||||
Projenin web sitesinin aktif olup olmadığını kontrol eder
|
||||
Aynı zamanda proje klasörünün disk kullanımını günceller
|
||||
"""
|
||||
from .ssh_client import SSHManager
|
||||
|
||||
result_messages = []
|
||||
|
||||
# 1. Disk kullanımını güncelle
|
||||
try:
|
||||
if project.ssh_credential:
|
||||
ssh_manager = SSHManager(project.ssh_credential)
|
||||
|
||||
if ssh_manager.check_connection():
|
||||
# Proje klasörünün tam yolu
|
||||
base_path = project.ssh_credential.base_path.rstrip('/')
|
||||
folder_name = project.folder_name.strip('/')
|
||||
full_path = f"{base_path}/{folder_name}"
|
||||
|
||||
# Debug bilgisi ekle
|
||||
result_messages.append(f"Kontrol edilen path: {full_path}")
|
||||
|
||||
# Önce base path'in var olup olmadığını kontrol et
|
||||
base_check_command = f"test -d '{base_path}' && echo 'BASE_EXISTS' || echo 'BASE_NOT_EXISTS'"
|
||||
stdout_base, stderr_base, success_base = ssh_manager.execute_command(base_check_command)
|
||||
|
||||
if success_base and stdout_base.strip() == 'BASE_EXISTS':
|
||||
# Base path var, şimdi proje klasörünü kontrol et
|
||||
|
||||
# Önce base path içindeki klasörleri listele
|
||||
list_command = f"ls -la '{base_path}' | grep '^d'"
|
||||
stdout_list, stderr_list, success_list = ssh_manager.execute_command(list_command)
|
||||
|
||||
if success_list:
|
||||
result_messages.append(f"Base path içindeki klasörler: {stdout_list.strip()[:200]}")
|
||||
|
||||
# Proje klasörünü kontrol et
|
||||
check_command = f"test -d '{full_path}' && echo 'EXISTS' || echo 'NOT_EXISTS'"
|
||||
stdout_check, stderr_check, success_check = ssh_manager.execute_command(check_command)
|
||||
|
||||
if success_check and stdout_check.strip() == 'EXISTS':
|
||||
# Disk kullanımını al
|
||||
command = f"du -sh '{full_path}' 2>/dev/null | cut -f1"
|
||||
stdout, stderr, success = ssh_manager.execute_command(command)
|
||||
|
||||
if success and stdout.strip():
|
||||
old_usage = project.disk_usage or "Bilinmiyor"
|
||||
project.disk_usage = stdout.strip()
|
||||
result_messages.append(f"Disk kullanımı güncellendi: {old_usage} → {project.disk_usage}")
|
||||
else:
|
||||
result_messages.append("Disk kullanımı komutu başarısız")
|
||||
else:
|
||||
result_messages.append(f"Proje klasörü bulunamadı: {full_path}")
|
||||
else:
|
||||
result_messages.append(f"Base path bulunamadı: {base_path}")
|
||||
|
||||
ssh_manager.close()
|
||||
else:
|
||||
result_messages.append("SSH bağlantısı kurulamadı")
|
||||
else:
|
||||
result_messages.append("SSH bilgisi eksik")
|
||||
except Exception as e:
|
||||
result_messages.append(f"Disk kontrolü hatası: {str(e)}")
|
||||
|
||||
# 2. Site durumunu kontrol et
|
||||
if not project.url:
|
||||
project.last_site_check = timezone.now()
|
||||
project.save()
|
||||
return False, "; ".join(result_messages + ["URL eksik"])
|
||||
|
||||
try:
|
||||
# URL'yi düzenle
|
||||
url = project.url
|
||||
if not url.startswith(('http://', 'https://')):
|
||||
url = f'http://{url}'
|
||||
|
||||
# Site kontrolü
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||||
}
|
||||
|
||||
response = requests.get(url, headers=headers, timeout=10)
|
||||
|
||||
if response.status_code == 200:
|
||||
# Site erişilebilir
|
||||
project.is_site_active = True
|
||||
project.last_site_check = timezone.now()
|
||||
project.save()
|
||||
result_messages.append(f"Site aktif (HTTP {response.status_code})")
|
||||
return True, "; ".join(result_messages)
|
||||
else:
|
||||
# Site erişilemez
|
||||
project.is_site_active = False
|
||||
project.last_site_check = timezone.now()
|
||||
project.save()
|
||||
result_messages.append(f"Site erişilemez (HTTP {response.status_code})")
|
||||
return False, "; ".join(result_messages)
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
project.is_site_active = False
|
||||
project.last_site_check = timezone.now()
|
||||
project.save()
|
||||
result_messages.append("Site zaman aşımı")
|
||||
return False, "; ".join(result_messages)
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
project.is_site_active = False
|
||||
project.last_site_check = timezone.now()
|
||||
project.save()
|
||||
result_messages.append("Site bağlantı hatası")
|
||||
return False, "; ".join(result_messages)
|
||||
|
||||
except Exception as e:
|
||||
project.is_site_active = False
|
||||
project.last_site_check = timezone.now()
|
||||
project.save()
|
||||
result_messages.append(f"Site hatası: {str(e)}")
|
||||
return False, "; ".join(result_messages)
|
||||
|
||||
def check_all_sites():
|
||||
"""Tüm projelerin site durumunu kontrol et"""
|
||||
projects = Project.objects.filter(url__isnull=False).exclude(url='')
|
||||
results = []
|
||||
|
||||
for project in projects:
|
||||
status, message = check_site_status(project)
|
||||
results.append({
|
||||
'project': project,
|
||||
'status': status,
|
||||
'message': message
|
||||
})
|
||||
|
||||
return results
|
||||
2494
ssh_manager/views.py
Normal file
2494
ssh_manager/views.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user