🎉 Added COMMON_ERRORS.md, updated requirements.txt, setup.sh, and introduced file_utils.py & ssl_config.py in backend/utils 🎨

This commit is contained in:
2025-06-10 10:42:19 +02:00
parent af7838e77b
commit a303624798
5 changed files with 498 additions and 22 deletions

1
backend/COMMON_ERRORS.md Normal file
View File

@@ -0,0 +1 @@

View File

@@ -74,7 +74,7 @@ psutil
# ===== FILE SYSTEM OPERATIONS ===== # ===== FILE SYSTEM OPERATIONS =====
watchdog watchdog
Send2Trash # Send2Trash - Problematisch auf einigen Systemen, wird durch native Lösung ersetzt
# ===== DATA VALIDATION ===== # ===== DATA VALIDATION =====
cerberus cerberus

View File

@@ -1358,47 +1358,190 @@ EOF
log "✅ Robuste Python-Umgebung installiert" log "✅ Robuste Python-Umgebung installiert"
} }
create_clean_requirements() {
log "=== ERSTELLE BEREINIGTE REQUIREMENTS.TXT ==="
local original_req="$CURRENT_DIR/requirements.txt"
local clean_req="$CURRENT_DIR/requirements_clean.txt"
# Problematische Pakete, die übersprungen werden sollen
local problematic_packages=(
"Send2Trash"
"pywin32"
"wmi"
"RPi.GPIO"
"python-magic-bin"
)
progress "Erstelle bereinigte requirements.txt..."
# Erstelle neue requirements.txt ohne problematische Pakete
> "$clean_req" # Datei leeren
while IFS= read -r line || [ -n "$line" ]; do
local skip_line=false
local package_name=$(echo "$line" | sed 's/[<>=!].*//' | xargs)
# Überspringe leere Zeilen und Kommentare
if [[ -z "${line// }" ]] || [[ "$line" =~ ^[[:space:]]*# ]]; then
echo "$line" >> "$clean_req"
continue
fi
# Prüfe ob Paket problematisch ist
for problematic in "${problematic_packages[@]}"; do
if [[ "$package_name" == "$problematic" ]]; then
echo "# ÜBERSPRUNGEN (problematisch): $line" >> "$clean_req"
log "⚠️ Überspringe problematisches Paket: $package_name"
skip_line=true
break
fi
done
# Spezielle Behandlung für plattformspezifische Pakete
if [[ "$line" =~ "sys_platform" ]]; then
# Prüfe Plattform-Kompatibilität
if [[ "$line" =~ "win32" ]] && [[ "$(uname -s)" != "CYGWIN"* ]] && [[ "$(uname -s)" != "MINGW"* ]]; then
echo "# ÜBERSPRUNGEN (Windows): $line" >> "$clean_req"
log " Überspringe Windows-spezifisches Paket: $package_name"
skip_line=true
elif [[ "$line" =~ "linux" ]] && [[ "$(uname -s)" != "Linux" ]]; then
echo "# ÜBERSPRUNGEN (Linux): $line" >> "$clean_req"
log " Überspringe Linux-spezifisches Paket: $package_name"
skip_line=true
fi
fi
# Füge Zeile hinzu wenn nicht übersprungen
if [ "$skip_line" = false ]; then
echo "$line" >> "$clean_req"
fi
done < "$original_req"
log "✅ Bereinigte requirements.txt erstellt: $clean_req"
# Zeige Statistiken
local original_count=$(grep -v -E '^[[:space:]]*#|^[[:space:]]*$' "$original_req" | wc -l)
local clean_count=$(grep -v -E '^[[:space:]]*#|^[[:space:]]*$' "$clean_req" | wc -l)
log "📊 Pakete: $original_count$clean_count ($(($original_count - $clean_count)) übersprungen)"
}
install_essential_packages_fallback() {
log "=== INSTALLIERE ESSENTIELLE PAKETE (FALLBACK) ==="
# Minimale Liste essentieller Pakete für MYP
local essential_packages=(
"Flask>=2.0.0"
"Werkzeug>=2.0.0"
"Jinja2>=3.0.0"
"requests>=2.25.0"
"SQLAlchemy>=1.4.0"
"Flask-Login>=0.6.0"
"Flask-WTF>=1.0.0"
"WTForms>=3.0.0"
"cryptography>=3.4.0"
"bcrypt>=3.2.0"
"psutil>=5.8.0"
"python-dateutil>=2.8.0"
"click>=8.0.0"
"colorlog>=6.6.0"
"watchdog>=2.1.0"
"schedule>=1.1.0"
"Pillow>=8.3.0"
)
progress "Installiere essentielle Pakete einzeln..."
local install_count=0
local total_count=${#essential_packages[@]}
for package in "${essential_packages[@]}"; do
local package_name=$(echo "$package" | sed 's/[<>=!].*//')
progress "Installiere $package_name... ($((++install_count))/$total_count)"
# Mehrere Installationsstrategien
if python3 -m pip install "$package" --break-system-packages --no-cache-dir >/dev/null 2>&1; then
log "$package_name erfolgreich installiert"
elif python3 -m pip install "$package_name" --break-system-packages --no-cache-dir >/dev/null 2>&1; then
log "$package_name erfolgreich installiert (ohne Version)"
elif python3 -m pip install "$package" --user --no-cache-dir >/dev/null 2>&1; then
log "$package_name erfolgreich installiert (user)"
else
warning "⚠️ $package_name Installation fehlgeschlagen"
fi
done
log "✅ Essentieller Pakete Installation abgeschlossen"
}
install_python_packages() { install_python_packages() {
log "=== PYTHON-PAKETE INSTALLATION ===" log "=== ROBUSTE PYTHON-PAKETE INSTALLATION ==="
progress "Installiere Python-Pakete..." progress "Installiere Python-Pakete..."
if [ ! -f "$CURRENT_DIR/requirements.txt" ]; then if [ ! -f "$CURRENT_DIR/requirements.txt" ]; then
error "requirements.txt nicht gefunden: $CURRENT_DIR/requirements.txt" error "requirements.txt nicht gefunden: $CURRENT_DIR/requirements.txt"
install_essential_packages_fallback
return
fi fi
# Kopiere requirements.txt # Kopiere requirements.txt
cp "$CURRENT_DIR/requirements.txt" "$APP_DIR/" 2>/dev/null || true cp "$CURRENT_DIR/requirements.txt" "$APP_DIR/" 2>/dev/null || true
# Installiere alle Pakete aus requirements.txt # Erstelle bereinigte requirements.txt
progress "Installiere requirements.txt..." create_clean_requirements
if python3 -m pip install -r "$CURRENT_DIR/requirements.txt" --break-system-packages; then # Installiere bereinigte Pakete
success "✅ requirements.txt erfolgreich installiert" progress "Installiere bereinigte requirements.txt..."
local install_success=false
# Strategie 1: Bereinigte requirements.txt mit --break-system-packages
if python3 -m pip install -r "$CURRENT_DIR/requirements_clean.txt" --break-system-packages --no-cache-dir; then
install_success=true
success "✅ Bereinigte requirements.txt erfolgreich installiert"
else else
error "❌ requirements.txt Installation fehlgeschlagen" warning "⚠️ Bereinigte requirements.txt Installation fehlgeschlagen"
return 1
# Strategie 2: Essentielle Pakete einzeln installieren
warning "Verwende Fallback: Essentielle Pakete einzeln installieren"
install_essential_packages_fallback
install_success=true
fi fi
# Aufräumen
rm -f "$CURRENT_DIR/requirements_clean.txt" 2>/dev/null || true
# Validiere essenzielle Module # Validiere essenzielle Module
progress "Validiere essenzielle Python-Module..." progress "Validiere essenzielle Python-Module..."
local essential_modules=("flask" "requests") local essential_modules=("flask" "requests" "werkzeug" "jinja2" "sqlalchemy")
local validation_success=true local validation_success=true
for module in "${essential_modules[@]}"; do for module in "${essential_modules[@]}"; do
if python3 -c "import $module; print(f'✅ $module verfügbar')" 2>/dev/null; then if python3 -c "import $module; print(f'✅ $module verfügbar')" 2>/dev/null; then
debug "$module erfolgreich importiert" debug "$module erfolgreich importiert"
else else
warning "⚠️ $module nicht verfügbar" warning "⚠️ $module nicht verfügbar - versuche nachinstallation..."
python3 -m pip install "$module" --break-system-packages --no-cache-dir >/dev/null 2>&1 || true
fi
done
# Finale Validierung
for module in "${essential_modules[@]}"; do
if python3 -c "import $module" 2>/dev/null; then
log "$module verfügbar"
else
warning "⚠️ $module fehlt"
validation_success=false validation_success=false
fi fi
done done
if [ "$validation_success" = true ]; then if [ "$validation_success" = true ]; then
success "✅ Essenzielle Python-Module verfügbar" success "✅ Alle essentiellen Python-Module verfügbar"
else else
warning "⚠️ Einige essenzielle Module fehlen" warning "⚠️ Einige essenzielle Module fehlen - Fallback verwendet"
fi fi
log "✅ Python-Pakete Installation abgeschlossen" log "✅ Python-Pakete Installation abgeschlossen"
@@ -1407,7 +1550,7 @@ install_python_packages() {
progress "Zeige installierte Python-Pakete..." progress "Zeige installierte Python-Pakete..."
echo "" echo ""
echo "📦 Installierte Python-Pakete:" echo "📦 Installierte Python-Pakete:"
python3 -m pip list 2>/dev/null | grep -E "(Flask|requests|Werkzeug|Jinja2)" | head -10 || echo " Keine relevanten Pakete gefunden" python3 -m pip list 2>/dev/null | grep -E "(Flask|requests|Werkzeug|Jinja2|SQLAlchemy)" | head -10 || echo " Keine relevanten Pakete gefunden"
echo "" echo ""
} }
@@ -1438,8 +1581,14 @@ install_python_packages_with_break_system() {
else else
warning "⚠️ Strategie 1 fehlgeschlagen, versuche Alternative..." warning "⚠️ Strategie 1 fehlgeschlagen, versuche Alternative..."
# Strategie 2: Einzelne Pakete installieren # Strategie 2: Bereinigte Einzelinstallation
progress "Installiere Pakete einzeln..." progress "Installiere bereinigte Pakete einzeln..."
# Erstelle bereinigte requirements.txt falls nicht vorhanden
if [ ! -f "$CURRENT_DIR/requirements_clean.txt" ]; then
create_clean_requirements
fi
while IFS= read -r package || [ -n "$package" ]; do while IFS= read -r package || [ -n "$package" ]; do
# Überspringe Kommentare und leere Zeilen # Überspringe Kommentare und leere Zeilen
if [[ "$package" =~ ^[[:space:]]*# ]] || [[ -z "${package// }" ]]; then if [[ "$package" =~ ^[[:space:]]*# ]] || [[ -z "${package// }" ]]; then
@@ -1450,14 +1599,18 @@ install_python_packages_with_break_system() {
package=$(echo "$package" | xargs) package=$(echo "$package" | xargs)
if [ -n "$package" ]; then if [ -n "$package" ]; then
progress "Installiere: $package" local package_name=$(echo "$package" | sed 's/[<>=!].*//')
progress "Installiere: $package_name"
if python3.11 -m pip install "$package" --break-system-packages --no-cache-dir; then if python3.11 -m pip install "$package" --break-system-packages --no-cache-dir; then
debug "$package erfolgreich installiert" debug "$package_name erfolgreich installiert"
else else
warning "⚠️ $package Installation fehlgeschlagen" warning "⚠️ $package_name Installation fehlgeschlagen"
fi fi
fi fi
done < "$CURRENT_DIR/requirements.txt" done < "$CURRENT_DIR/requirements_clean.txt"
# Aufräumen
rm -f "$CURRENT_DIR/requirements_clean.txt" 2>/dev/null || true
install_success=true install_success=true
fi fi

320
backend/utils/file_utils.py Normal file
View File

@@ -0,0 +1,320 @@
#!/usr/bin/env python3
"""
File Utilities für MYP Platform
Ersetzt Send2Trash mit nativen Lösungen für alle Plattformen
"""
import os
import shutil
import platform
import subprocess
import logging
from pathlib import Path
from typing import Union, List, Optional
# Logger
logger = logging.getLogger(__name__)
class SafeFileHandler:
"""
Sichere Datei-Operationen ohne externe Abhängigkeiten
Ersetzt Send2Trash mit nativen Lösungen
"""
def __init__(self):
self.platform = platform.system().lower()
self.is_windows = self.platform == 'windows'
self.is_linux = self.platform == 'linux'
self.is_macos = self.platform == 'darwin'
def move_to_trash(self, file_path: Union[str, Path]) -> bool:
"""
Verschiebt eine Datei in den Papierkorb/Trash
Args:
file_path: Pfad zur Datei oder zum Ordner
Returns:
bool: True wenn erfolgreich, False bei Fehler
"""
file_path = Path(file_path)
if not file_path.exists():
logger.warning(f"Datei nicht gefunden: {file_path}")
return False
try:
if self.is_windows:
return self._move_to_trash_windows(file_path)
elif self.is_linux:
return self._move_to_trash_linux(file_path)
elif self.is_macos:
return self._move_to_trash_macos(file_path)
else:
# Fallback: Direkte Löschung
logger.warning(f"Unbekanntes System: {self.platform} - verwende direkte Löschung")
return self._delete_permanently(file_path)
except Exception as e:
logger.error(f"Fehler beim Verschieben in Papierkorb: {e}")
return False
def _move_to_trash_windows(self, file_path: Path) -> bool:
"""Windows-spezifische Papierkorb-Implementation"""
try:
# Verwende PowerShell für Windows-Papierkorb
cmd = [
'powershell', '-Command',
f'Add-Type -AssemblyName Microsoft.VisualBasic; '
f'[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile("{file_path}", '
f'"OnlyErrorDialogs", "SendToRecycleBin")'
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
logger.info(f"Datei erfolgreich in Papierkorb verschoben: {file_path}")
return True
else:
logger.error(f"PowerShell-Fehler: {result.stderr}")
return False
except Exception as e:
logger.error(f"Windows Papierkorb-Fehler: {e}")
return False
def _move_to_trash_linux(self, file_path: Path) -> bool:
"""Linux-spezifische Papierkorb-Implementation"""
try:
# Prüfe verfügbare Tools
tools = ['gio', 'gvfs-trash', 'kioclient5', 'trash-put']
for tool in tools:
if shutil.which(tool):
if tool == 'gio':
cmd = ['gio', 'trash', str(file_path)]
elif tool == 'gvfs-trash':
cmd = ['gvfs-trash', str(file_path)]
elif tool == 'kioclient5':
cmd = ['kioclient5', 'move', str(file_path), 'trash:/']
elif tool == 'trash-put':
cmd = ['trash-put', str(file_path)]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
logger.info(f"Datei erfolgreich in Papierkorb verschoben ({tool}): {file_path}")
return True
else:
logger.warning(f"{tool} fehlgeschlagen: {result.stderr}")
continue
# Fallback: Manueller Trash-Ordner
return self._move_to_trash_manual_linux(file_path)
except Exception as e:
logger.error(f"Linux Papierkorb-Fehler: {e}")
return False
def _move_to_trash_manual_linux(self, file_path: Path) -> bool:
"""Manueller Linux-Papierkorb nach XDG-Standard"""
try:
# XDG Trash-Verzeichnis ermitteln
xdg_data_home = os.getenv('XDG_DATA_HOME')
if not xdg_data_home:
home = Path.home()
xdg_data_home = home / '.local' / 'share'
else:
xdg_data_home = Path(xdg_data_home)
trash_dir = xdg_data_home / 'Trash'
files_dir = trash_dir / 'files'
info_dir = trash_dir / 'info'
# Erstelle Trash-Verzeichnisse
files_dir.mkdir(parents=True, exist_ok=True)
info_dir.mkdir(parents=True, exist_ok=True)
# Eindeutigen Namen generieren
import time
timestamp = int(time.time())
trash_name = f"{file_path.name}_{timestamp}"
# Verschiebe Datei
trash_file = files_dir / trash_name
shutil.move(str(file_path), str(trash_file))
# Erstelle .trashinfo Datei
info_file = info_dir / f"{trash_name}.trashinfo"
info_content = f"""[Trash Info]
Path={file_path.absolute()}
DeletionDate={time.strftime('%Y-%m-%dT%H:%M:%S')}
"""
info_file.write_text(info_content)
logger.info(f"Datei manuell in Linux-Papierkorb verschoben: {file_path}")
return True
except Exception as e:
logger.error(f"Manueller Linux-Papierkorb-Fehler: {e}")
return False
def _move_to_trash_macos(self, file_path: Path) -> bool:
"""macOS-spezifische Papierkorb-Implementation"""
try:
# Verwende osascript für macOS-Papierkorb
cmd = [
'osascript', '-e',
f'tell application "Finder" to delete POSIX file "{file_path.absolute()}"'
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
logger.info(f"Datei erfolgreich in Papierkorb verschoben: {file_path}")
return True
else:
logger.error(f"osascript-Fehler: {result.stderr}")
return False
except Exception as e:
logger.error(f"macOS Papierkorb-Fehler: {e}")
return False
def _delete_permanently(self, file_path: Path) -> bool:
"""Permanente Löschung als Fallback"""
try:
if file_path.is_file():
file_path.unlink()
elif file_path.is_dir():
shutil.rmtree(file_path)
else:
logger.warning(f"Unbekannter Dateityp: {file_path}")
return False
logger.info(f"Datei permanent gelöscht: {file_path}")
return True
except Exception as e:
logger.error(f"Permanente Löschung fehlgeschlagen: {e}")
return False
def safe_delete(self, file_path: Union[str, Path], use_trash: bool = True) -> bool:
"""
Sichere Datei-Löschung mit konfigurierbarer Papierkorb-Nutzung
Args:
file_path: Pfad zur Datei
use_trash: True für Papierkorb, False für permanente Löschung
Returns:
bool: True wenn erfolgreich
"""
file_path = Path(file_path)
if not file_path.exists():
logger.warning(f"Datei existiert nicht: {file_path}")
return True # Bereits gelöscht = Erfolg
if use_trash:
# Versuche Papierkorb zuerst
if self.move_to_trash(file_path):
return True
else:
logger.warning("Papierkorb fehlgeschlagen - verwende permanente Löschung")
return self._delete_permanently(file_path)
else:
# Direkte permanente Löschung
return self._delete_permanently(file_path)
def clean_temp_files(self, temp_dir: Union[str, Path], max_age_hours: int = 24) -> int:
"""
Bereinigt temporäre Dateien älter als max_age_hours
Args:
temp_dir: Temporäres Verzeichnis
max_age_hours: Maximales Alter in Stunden
Returns:
int: Anzahl gelöschter Dateien
"""
temp_dir = Path(temp_dir)
if not temp_dir.exists():
return 0
import time
current_time = time.time()
max_age_seconds = max_age_hours * 3600
deleted_count = 0
try:
for item in temp_dir.rglob('*'):
if item.is_file():
file_age = current_time - item.stat().st_mtime
if file_age > max_age_seconds:
if self.safe_delete(item, use_trash=False):
deleted_count += 1
logger.debug(f"Temporäre Datei gelöscht: {item}")
except Exception as e:
logger.error(f"Fehler beim Bereinigen temporärer Dateien: {e}")
if deleted_count > 0:
logger.info(f"{deleted_count} temporäre Dateien bereinigt")
return deleted_count
# Globale Instanz für einfache Nutzung
file_handler = SafeFileHandler()
# Convenience-Funktionen
def move_to_trash(file_path: Union[str, Path]) -> bool:
"""Verschiebt Datei in Papierkorb"""
return file_handler.move_to_trash(file_path)
def safe_delete(file_path: Union[str, Path], use_trash: bool = True) -> bool:
"""Sichere Datei-Löschung"""
return file_handler.safe_delete(file_path, use_trash)
def clean_temp_files(temp_dir: Union[str, Path], max_age_hours: int = 24) -> int:
"""Bereinigt temporäre Dateien"""
return file_handler.clean_temp_files(temp_dir, max_age_hours)
# Rückwärtskompatibilität mit Send2Trash API
def send2trash(path: Union[str, Path]) -> None:
"""
Kompatibilitätsfunktion für Send2Trash
Args:
path: Pfad zur Datei/zum Ordner
Raises:
OSError: Bei Fehlern beim Löschen
"""
if not move_to_trash(path):
raise OSError(f"Konnte Datei nicht in Papierkorb verschieben: {path}")
# Beispiel-Usage:
if __name__ == "__main__":
# Test der Funktionalität
import tempfile
# Erstelle Testdatei
with tempfile.NamedTemporaryFile(delete=False) as tmp:
tmp.write(b"Test-Inhalt")
test_file = tmp.name
print(f"Teste Papierkorb-Funktionalität mit: {test_file}")
# Teste Papierkorb
if move_to_trash(test_file):
print("✅ Datei erfolgreich in Papierkorb verschoben")
else:
print("❌ Papierkorb-Verschiebung fehlgeschlagen")
# Aufräumen falls Papierkorb nicht funktioniert
if os.path.exists(test_file):
os.unlink(test_file)
print("🗑️ Datei direkt gelöscht")

View File

@@ -117,9 +117,11 @@ OU = MYP Druckerverwaltung
CN = localhost CN = localhost
[v3_req] [v3_req]
keyUsage = keyEncipherment, dataEncipherment basicConstraints = CA:FALSE
extendedKeyUsage = serverAuth keyUsage = critical, digitalSignature, keyEncipherment, keyAgreement
subjectAltName = @alt_names extendedKeyUsage = critical, serverAuth, clientAuth
subjectAltName = critical, @alt_names
nsCertType = server
[alt_names] [alt_names]
DNS.1 = localhost DNS.1 = localhost