Add latest changes
Dieser Commit ist enthalten in:
@@ -3,6 +3,8 @@ import time
|
||||
import gzip
|
||||
import logging
|
||||
import subprocess
|
||||
import json
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
@@ -10,7 +12,7 @@ from cryptography.fernet import Fernet
|
||||
from db import get_db_connection, get_db_cursor
|
||||
from config import BACKUP_DIR, DATABASE_CONFIG, EMAIL_ENABLED, BACKUP_ENCRYPTION_KEY
|
||||
from utils.audit import log_audit
|
||||
from utils.github_backup import GitHubBackupManager, create_server_backup as create_server_backup_impl
|
||||
from utils.github_backup import GitHubBackupManager, create_server_backup_impl
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -125,6 +127,10 @@ def create_backup(backup_type="manual", created_by=None):
|
||||
send_backup_notification(True, filename, filesize, duration)
|
||||
|
||||
logger.info(f"Backup successfully created: {filename}")
|
||||
|
||||
# Apply retention policy - keep only last 5 local backups
|
||||
cleanup_old_backups("database", 5)
|
||||
|
||||
return True, filename
|
||||
|
||||
except Exception as e:
|
||||
@@ -224,6 +230,69 @@ def send_backup_notification(success, filename, filesize=None, duration=None, er
|
||||
logger.info(f"Email notification prepared: Backup {'successful' if success else 'failed'}")
|
||||
|
||||
|
||||
def cleanup_old_backups(backup_type="database", keep_count=5):
|
||||
"""Clean up old local backups, keeping only the most recent ones"""
|
||||
try:
|
||||
# Get list of local backups from database
|
||||
with get_db_connection() as conn:
|
||||
with get_db_cursor(conn) as cur:
|
||||
cur.execute("""
|
||||
SELECT id, filename, filepath
|
||||
FROM backup_history
|
||||
WHERE backup_type = %s
|
||||
AND status = 'success'
|
||||
AND local_deleted = FALSE
|
||||
AND filepath IS NOT NULL
|
||||
ORDER BY created_at DESC
|
||||
""", (backup_type,))
|
||||
backups = cur.fetchall()
|
||||
|
||||
if len(backups) <= keep_count:
|
||||
logger.info(f"No cleanup needed. Found {len(backups)} {backup_type} backups, keeping {keep_count}")
|
||||
return
|
||||
|
||||
# Delete old backups
|
||||
backups_to_delete = backups[keep_count:]
|
||||
deleted_count = 0
|
||||
|
||||
for backup_id, filename, filepath in backups_to_delete:
|
||||
try:
|
||||
# Check if file exists
|
||||
if filepath and os.path.exists(filepath):
|
||||
os.unlink(filepath)
|
||||
logger.info(f"Deleted old backup: {filename}")
|
||||
|
||||
# Update database
|
||||
with get_db_connection() as conn:
|
||||
with get_db_cursor(conn) as cur:
|
||||
cur.execute("""
|
||||
UPDATE backup_history
|
||||
SET local_deleted = TRUE
|
||||
WHERE id = %s
|
||||
""", (backup_id,))
|
||||
conn.commit()
|
||||
|
||||
deleted_count += 1
|
||||
else:
|
||||
# File doesn't exist, just update database
|
||||
with get_db_connection() as conn:
|
||||
with get_db_cursor(conn) as cur:
|
||||
cur.execute("""
|
||||
UPDATE backup_history
|
||||
SET local_deleted = TRUE
|
||||
WHERE id = %s
|
||||
""", (backup_id,))
|
||||
conn.commit()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to delete backup {filename}: {e}")
|
||||
|
||||
logger.info(f"Backup cleanup completed. Deleted {deleted_count} old {backup_type} backups")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Backup cleanup failed: {e}")
|
||||
|
||||
|
||||
def create_backup_with_github(backup_type="manual", created_by=None, push_to_github=True, delete_local=True):
|
||||
"""Create backup and optionally push to GitHub"""
|
||||
# Create the backup
|
||||
@@ -242,7 +311,8 @@ def create_backup_with_github(backup_type="manual", created_by=None, push_to_git
|
||||
db_backup_dir.mkdir(exist_ok=True)
|
||||
|
||||
target_path = db_backup_dir / filename
|
||||
filepath.rename(target_path)
|
||||
# Use shutil.move instead of rename to handle cross-device links
|
||||
shutil.move(str(filepath), str(target_path))
|
||||
|
||||
# Push to GitHub
|
||||
github = GitHubBackupManager()
|
||||
@@ -269,8 +339,8 @@ def create_backup_with_github(backup_type="manual", created_by=None, push_to_git
|
||||
conn.commit()
|
||||
else:
|
||||
logger.error(f"Failed to push to GitHub: {git_result}")
|
||||
# Move file back
|
||||
target_path.rename(filepath)
|
||||
# Move file back using shutil
|
||||
shutil.move(str(target_path), str(filepath))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"GitHub upload error: {str(e)}")
|
||||
@@ -279,6 +349,63 @@ def create_backup_with_github(backup_type="manual", created_by=None, push_to_git
|
||||
return True, filename
|
||||
|
||||
|
||||
def create_container_server_backup_info(created_by="system"):
|
||||
"""Create a server info backup in container environment"""
|
||||
try:
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"server_backup_info_{timestamp}.json"
|
||||
filepath = Path("/app/backups") / filename
|
||||
|
||||
# Collect server info available in container
|
||||
server_info = {
|
||||
"backup_type": "server_info",
|
||||
"created_at": datetime.now().isoformat(),
|
||||
"created_by": created_by,
|
||||
"container_environment": True,
|
||||
"message": "Full server backups nur über Host-System möglich. Dies ist eine Info-Datei.",
|
||||
"docker_compose": None,
|
||||
"env_vars": {},
|
||||
"existing_backups": []
|
||||
}
|
||||
|
||||
# Try to read docker-compose if mounted
|
||||
if os.path.exists("/app/docker-compose.yaml"):
|
||||
try:
|
||||
with open("/app/docker-compose.yaml", 'r') as f:
|
||||
server_info["docker_compose"] = f.read()
|
||||
except:
|
||||
pass
|
||||
|
||||
# Try to read env vars (without secrets)
|
||||
if os.path.exists("/app/.env"):
|
||||
try:
|
||||
with open("/app/.env", 'r') as f:
|
||||
for line in f:
|
||||
if '=' in line and not any(secret in line.upper() for secret in ['PASSWORD', 'SECRET', 'KEY']):
|
||||
key, value = line.strip().split('=', 1)
|
||||
server_info["env_vars"][key] = "***" if len(value) > 20 else value
|
||||
except:
|
||||
pass
|
||||
|
||||
# List existing server backups
|
||||
if os.path.exists("/app/server-backups"):
|
||||
try:
|
||||
server_info["existing_backups"] = sorted(os.listdir("/app/server-backups"))[-10:]
|
||||
except:
|
||||
pass
|
||||
|
||||
# Write info file
|
||||
with open(filepath, 'w') as f:
|
||||
json.dump(server_info, f, indent=2)
|
||||
|
||||
logger.info(f"Container server backup info created: {filename}")
|
||||
return True, str(filepath)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Container server backup info failed: {e}")
|
||||
return False, str(e)
|
||||
|
||||
|
||||
def create_server_backup(created_by=None, push_to_github=True, delete_local=True):
|
||||
"""Create full server backup"""
|
||||
start_time = time.time()
|
||||
@@ -296,7 +423,7 @@ def create_server_backup(created_by=None, push_to_github=True, delete_local=True
|
||||
conn.commit()
|
||||
|
||||
try:
|
||||
# Create server backup
|
||||
# Create server backup - always use full backup now
|
||||
success, result = create_server_backup_impl(created_by)
|
||||
|
||||
if not success:
|
||||
@@ -353,6 +480,9 @@ def create_server_backup(created_by=None, push_to_github=True, delete_local=True
|
||||
log_audit('BACKUP', 'server', backup_id,
|
||||
additional_info=f"Server backup created: {filename} ({filesize} bytes)")
|
||||
|
||||
# Apply retention policy - keep only last 5 local server backups
|
||||
cleanup_old_backups("server", 5)
|
||||
|
||||
return True, filename
|
||||
|
||||
except Exception as e:
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren