🎉 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 =====
watchdog
Send2Trash
# Send2Trash - Problematisch auf einigen Systemen, wird durch native Lösung ersetzt
# ===== DATA VALIDATION =====
cerberus

View File

@ -1358,47 +1358,190 @@ EOF
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() {
log "=== PYTHON-PAKETE INSTALLATION ==="
log "=== ROBUSTE PYTHON-PAKETE INSTALLATION ==="
progress "Installiere Python-Pakete..."
if [ ! -f "$CURRENT_DIR/requirements.txt" ]; then
error "requirements.txt nicht gefunden: $CURRENT_DIR/requirements.txt"
install_essential_packages_fallback
return
fi
# Kopiere requirements.txt
cp "$CURRENT_DIR/requirements.txt" "$APP_DIR/" 2>/dev/null || true
# Installiere alle Pakete aus requirements.txt
progress "Installiere requirements.txt..."
# Erstelle bereinigte requirements.txt
create_clean_requirements
if python3 -m pip install -r "$CURRENT_DIR/requirements.txt" --break-system-packages; then
success "✅ requirements.txt erfolgreich installiert"
# Installiere bereinigte Pakete
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
error "❌ requirements.txt Installation fehlgeschlagen"
return 1
warning "⚠️ Bereinigte requirements.txt Installation fehlgeschlagen"
# Strategie 2: Essentielle Pakete einzeln installieren
warning "Verwende Fallback: Essentielle Pakete einzeln installieren"
install_essential_packages_fallback
install_success=true
fi
# Aufräumen
rm -f "$CURRENT_DIR/requirements_clean.txt" 2>/dev/null || true
# Validiere essenzielle Module
progress "Validiere essenzielle Python-Module..."
local essential_modules=("flask" "requests")
local essential_modules=("flask" "requests" "werkzeug" "jinja2" "sqlalchemy")
local validation_success=true
for module in "${essential_modules[@]}"; do
if python3 -c "import $module; print(f'✅ $module verfügbar')" 2>/dev/null; then
debug "$module erfolgreich importiert"
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
fi
done
if [ "$validation_success" = true ]; then
success "✅ Essenzielle Python-Module verfügbar"
success "✅ Alle essentiellen Python-Module verfügbar"
else
warning "⚠️ Einige essenzielle Module fehlen"
warning "⚠️ Einige essenzielle Module fehlen - Fallback verwendet"
fi
log "✅ Python-Pakete Installation abgeschlossen"
@ -1407,7 +1550,7 @@ install_python_packages() {
progress "Zeige installierte Python-Pakete..."
echo ""
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 ""
}
@ -1438,8 +1581,14 @@ install_python_packages_with_break_system() {
else
warning "⚠️ Strategie 1 fehlgeschlagen, versuche Alternative..."
# Strategie 2: Einzelne Pakete installieren
progress "Installiere Pakete einzeln..."
# Strategie 2: Bereinigte Einzelinstallation
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
# Überspringe Kommentare und leere Zeilen
if [[ "$package" =~ ^[[:space:]]*# ]] || [[ -z "${package// }" ]]; then
@ -1450,14 +1599,18 @@ install_python_packages_with_break_system() {
package=$(echo "$package" | xargs)
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
debug "$package erfolgreich installiert"
debug "$package_name erfolgreich installiert"
else
warning "⚠️ $package Installation fehlgeschlagen"
warning "⚠️ $package_name Installation fehlgeschlagen"
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
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
[v3_req]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment, keyAgreement
extendedKeyUsage = critical, serverAuth, clientAuth
subjectAltName = critical, @alt_names
nsCertType = server
[alt_names]
DNS.1 = localhost