diff --git a/backend/app.py b/backend/app.py index 5cc9557a..cf43d878 100644 --- a/backend/app.py +++ b/backend/app.py @@ -36,7 +36,7 @@ else: get_windows_thread_manager = None # Lokale Imports -from models import init_database, create_initial_admin, User, Printer, Job, Stats, SystemLog, get_db_session, GuestRequest, UserPermission, Notification, JobOrder, Base, get_engine +from models import init_database, create_initial_admin, User, Printer, Job, Stats, SystemLog, get_db_session, GuestRequest, UserPermission, Notification, JobOrder, Base, get_engine, PlugStatusLog from utils.logging_config import setup_logging, get_logger, measure_execution_time, log_startup_info, debug_request, debug_response from utils.job_scheduler import JobScheduler, get_job_scheduler from utils.queue_manager import start_queue_manager, stop_queue_manager, get_queue_manager diff --git a/backend/models.py b/backend/models.py index 8e005c2f..ce0a2f5a 100644 --- a/backend/models.py +++ b/backend/models.py @@ -43,7 +43,7 @@ _cache_lock = threading.Lock() _cache_ttl = {} # Time-to-live für Cache-Einträge # Alle exportierten Modelle -__all__ = ['User', 'Printer', 'Job', 'Stats', 'SystemLog', 'Base', 'GuestRequest', 'UserPermission', 'Notification', 'JobOrder', 'SystemTimer', 'init_db', 'init_database', 'create_initial_admin', 'get_db_session', 'get_cached_session', 'clear_cache', 'engine'] +__all__ = ['User', 'Printer', 'Job', 'Stats', 'SystemLog', 'Base', 'GuestRequest', 'UserPermission', 'Notification', 'JobOrder', 'SystemTimer', 'PlugStatusLog', 'init_db', 'init_database', 'create_initial_admin', 'get_db_session', 'get_cached_session', 'clear_cache', 'engine'] # ===== DATENBANK-KONFIGURATION MIT WAL UND OPTIMIERUNGEN ===== @@ -1595,6 +1595,313 @@ class SystemTimer(Base): return None +class PlugStatusLog(Base): + """ + Logging-System für Steckdosen-Status Monitoring. + Protokolliert alle Zustandsänderungen der Smart Plugs (TAPO). + """ + __tablename__ = "plug_status_logs" + + id = Column(Integer, primary_key=True) + printer_id = Column(Integer, ForeignKey("printers.id"), nullable=False) + status = Column(String(20), nullable=False) # 'connected', 'disconnected', 'on', 'off' + timestamp = Column(DateTime, default=datetime.now, nullable=False) + + # Zusätzliche Monitoring-Daten + ip_address = Column(String(50), nullable=True) # IP der Steckdose/des Druckers + power_consumption = Column(Float, nullable=True) # Stromverbrauch in Watt (falls verfügbar) + voltage = Column(Float, nullable=True) # Spannung in Volt (falls verfügbar) + current = Column(Float, nullable=True) # Stromstärke in Ampere (falls verfügbar) + + # Monitoring-Kontext + source = Column(String(50), default="system") # 'system', 'manual', 'api', 'scheduler' + user_id = Column(Integer, ForeignKey("users.id"), nullable=True) # Bei manueller Änderung + notes = Column(Text, nullable=True) # Zusätzliche Notizen oder Fehlerinfos + + # Technische Details + response_time_ms = Column(Integer, nullable=True) # Antwortzeit der Steckdose in ms + error_message = Column(Text, nullable=True) # Fehlermeldung bei Verbindungsproblemen + firmware_version = Column(String(50), nullable=True) # Firmware-Version der Steckdose + + # Beziehungen + printer = relationship("Printer", foreign_keys=[printer_id]) + user = relationship("User", foreign_keys=[user_id]) + + def to_dict(self) -> dict: + """ + Konvertiert das PlugStatusLog-Objekt in ein Dictionary. + """ + cache_key = get_cache_key("PlugStatusLog", self.id, "dict") + cached_result = get_cache(cache_key) + + if cached_result is not None: + return cached_result + + result = { + "id": self.id, + "printer_id": self.printer_id, + "printer_name": self.printer.name if self.printer else None, + "status": self.status, + "timestamp": self.timestamp.isoformat() if self.timestamp else None, + "ip_address": self.ip_address, + "power_consumption": self.power_consumption, + "voltage": self.voltage, + "current": self.current, + "source": self.source, + "user_id": self.user_id, + "user_name": self.user.name if self.user else None, + "notes": self.notes, + "response_time_ms": self.response_time_ms, + "error_message": self.error_message, + "firmware_version": self.firmware_version + } + + # Ergebnis cachen (5 Minuten) + set_cache(cache_key, result, 300) + return result + + @classmethod + def log_status_change(cls, printer_id: int, status: str, source: str = "system", + user_id: int = None, ip_address: str = None, + power_consumption: float = None, voltage: float = None, + current: float = None, notes: str = None, + response_time_ms: int = None, error_message: str = None, + firmware_version: str = None) -> 'PlugStatusLog': + """ + Erstellt einen neuen Status-Log-Eintrag für eine Steckdose. + + Args: + printer_id: ID des zugehörigen Druckers + status: Status der Steckdose ('connected', 'disconnected', 'on', 'off') + source: Quelle der Statusänderung ('system', 'manual', 'api', 'scheduler') + user_id: ID des Benutzers (bei manueller Änderung) + ip_address: IP-Adresse der Steckdose + power_consumption: Stromverbrauch in Watt + voltage: Spannung in Volt + current: Stromstärke in Ampere + notes: Zusätzliche Notizen + response_time_ms: Antwortzeit in Millisekunden + error_message: Fehlermeldung bei Problemen + firmware_version: Firmware-Version der Steckdose + + Returns: + Das erstellte PlugStatusLog-Objekt + """ + try: + with get_cached_session() as session: + log_entry = cls( + printer_id=printer_id, + status=status, + ip_address=ip_address, + power_consumption=power_consumption, + voltage=voltage, + current=current, + source=source, + user_id=user_id, + notes=notes, + response_time_ms=response_time_ms, + error_message=error_message, + firmware_version=firmware_version + ) + + session.add(log_entry) + session.commit() + + # Cache invalidieren + invalidate_model_cache("PlugStatusLog") + + logger.info(f"Steckdosen-Status geloggt: Drucker {printer_id}, Status: {status}, Quelle: {source}") + return log_entry + + except Exception as e: + logger.error(f"Fehler beim Loggen des Steckdosen-Status: {str(e)}") + raise e + + @classmethod + def get_printer_history(cls, printer_id: int, hours: int = 24) -> List['PlugStatusLog']: + """ + Holt die Steckdosen-Historie für einen bestimmten Drucker. + + Args: + printer_id: ID des Druckers + hours: Anzahl der Stunden zurück (Standard: 24) + + Returns: + Liste der PlugStatusLog-Einträge + """ + cache_key = get_cache_key("PlugStatusLog", printer_id, f"history_{hours}h") + cached_result = get_cache(cache_key) + + if cached_result is not None: + return cached_result + + try: + with get_cached_session() as session: + cutoff_time = datetime.now() - timedelta(hours=hours) + + logs = session.query(cls)\ + .filter(cls.printer_id == printer_id)\ + .filter(cls.timestamp >= cutoff_time)\ + .order_by(cls.timestamp.desc())\ + .all() + + # Ergebnis cachen (10 Minuten) + set_cache(cache_key, logs, 600) + return logs + + except Exception as e: + logger.error(f"Fehler beim Abrufen der Steckdosen-Historie: {str(e)}") + return [] + + @classmethod + def get_all_recent_logs(cls, hours: int = 24, limit: int = 1000) -> List['PlugStatusLog']: + """ + Holt alle aktuellen Steckdosen-Logs für die Administrator-Übersicht. + + Args: + hours: Anzahl der Stunden zurück (Standard: 24) + limit: Maximale Anzahl der Einträge (Standard: 1000) + + Returns: + Liste der PlugStatusLog-Einträge + """ + cache_key = get_cache_key("PlugStatusLog", "all", f"recent_{hours}h_{limit}") + cached_result = get_cache(cache_key) + + if cached_result is not None: + return cached_result + + try: + with get_cached_session() as session: + cutoff_time = datetime.now() - timedelta(hours=hours) + + logs = session.query(cls)\ + .filter(cls.timestamp >= cutoff_time)\ + .order_by(cls.timestamp.desc())\ + .limit(limit)\ + .all() + + # Ergebnis cachen (5 Minuten für Admin-Übersicht) + set_cache(cache_key, logs, 300) + return logs + + except Exception as e: + logger.error(f"Fehler beim Abrufen der aktuellen Steckdosen-Logs: {str(e)}") + return [] + + @classmethod + def get_status_statistics(cls, hours: int = 24) -> Dict[str, Any]: + """ + Erstellt Statistiken über Steckdosen-Status für einen Zeitraum. + + Args: + hours: Anzahl der Stunden zurück (Standard: 24) + + Returns: + Dictionary mit Statistiken + """ + cache_key = get_cache_key("PlugStatusLog", "stats", f"{hours}h") + cached_result = get_cache(cache_key) + + if cached_result is not None: + return cached_result + + try: + with get_cached_session() as session: + cutoff_time = datetime.now() - timedelta(hours=hours) + + # Gesamtanzahl der Logs + total_logs = session.query(cls)\ + .filter(cls.timestamp >= cutoff_time)\ + .count() + + # Status-Verteilung + status_counts = session.query(cls.status, func.count(cls.id))\ + .filter(cls.timestamp >= cutoff_time)\ + .group_by(cls.status)\ + .all() + + # Drucker mit den meisten Statusänderungen + printer_counts = session.query(cls.printer_id, func.count(cls.id))\ + .filter(cls.timestamp >= cutoff_time)\ + .group_by(cls.printer_id)\ + .order_by(func.count(cls.id).desc())\ + .limit(10)\ + .all() + + # Durchschnittliche Antwortzeit + avg_response_time = session.query(func.avg(cls.response_time_ms))\ + .filter(cls.timestamp >= cutoff_time)\ + .filter(cls.response_time_ms.isnot(None))\ + .scalar() + + # Fehlerrate + error_count = session.query(cls)\ + .filter(cls.timestamp >= cutoff_time)\ + .filter(cls.error_message.isnot(None))\ + .count() + + stats = { + "total_logs": total_logs, + "status_distribution": dict(status_counts), + "top_printers": dict(printer_counts), + "average_response_time_ms": float(avg_response_time) if avg_response_time else None, + "error_count": error_count, + "error_rate": (error_count / total_logs * 100) if total_logs > 0 else 0, + "timeframe_hours": hours, + "generated_at": datetime.now().isoformat() + } + + # Ergebnis cachen (10 Minuten) + set_cache(cache_key, stats, 600) + return stats + + except Exception as e: + logger.error(f"Fehler beim Erstellen der Steckdosen-Statistiken: {str(e)}") + return { + "total_logs": 0, + "status_distribution": {}, + "top_printers": {}, + "average_response_time_ms": None, + "error_count": 0, + "error_rate": 0, + "timeframe_hours": hours, + "generated_at": datetime.now().isoformat(), + "error": str(e) + } + + @classmethod + def cleanup_old_logs(cls, days: int = 30) -> int: + """ + Bereinigt alte Steckdosen-Logs (älter als X Tage). + + Args: + days: Anzahl der Tage (Standard: 30) + + Returns: + Anzahl der gelöschten Einträge + """ + try: + with get_cached_session() as session: + cutoff_date = datetime.now() - timedelta(days=days) + + deleted_count = session.query(cls)\ + .filter(cls.timestamp < cutoff_date)\ + .delete() + + session.commit() + + # Cache invalidieren + invalidate_model_cache("PlugStatusLog") + + logger.info(f"Steckdosen-Logs bereinigt: {deleted_count} Einträge gelöscht (älter als {days} Tage)") + return deleted_count + + except Exception as e: + logger.error(f"Fehler beim Bereinigen der Steckdosen-Logs: {str(e)}") + return 0 + + # ===== DATENBANK-INITIALISIERUNG MIT OPTIMIERUNGEN ===== def init_db() -> None: diff --git a/backend/setup.sh b/backend/setup.sh index 0a77fae5..0b08e800 100644 --- a/backend/setup.sh +++ b/backend/setup.sh @@ -1,19 +1,19 @@ #!/bin/bash # =================================================================== -# MYP Druckerverwaltung - KONSOLIDIERTES SETUP-SKRIPT +# MYP Druckerverwaltung - KONSOLIDIERTES SETUP-SKRIPT (OPTIMIERT) # Kombiniert alle Installationsfunktionen in einer einzigen Datei # Optimiert für Debian/Linux (Raspberry Pi OS) - KEIN Windows-Support # HTTPS auf Port 443 mit automatischer SSL-Zertifikat-Generierung # Kiosk-Modus mit Chromium-Autostart ohne Desktop-Environment -# Version: 4.0.0 +# Version: 4.1.0 - Robuste Installation mit Retry-Mechanismen # =================================================================== set -euo pipefail # =========================== GLOBALE KONFIGURATION =========================== readonly APP_NAME="MYP Druckerverwaltung" -readonly APP_VERSION="4.0.4" +readonly APP_VERSION="4.1.0" readonly APP_DIR="/opt/myp" readonly HTTPS_SERVICE_NAME="myp-https" readonly KIOSK_SERVICE_NAME="myp-kiosk" @@ -28,6 +28,10 @@ readonly HTTPS_URL="https://localhost:${HTTPS_PORT}" readonly SYSTEMD_DIR="$CURRENT_DIR/systemd" readonly SYSTEM_SYSTEMD_DIR="/etc/systemd/system" +# Retry-Konfiguration +readonly MAX_RETRIES=3 +readonly RETRY_DELAY=5 + # Farben für Ausgabe readonly RED='\033[0;31m' readonly GREEN='\033[0;32m' @@ -37,7 +41,7 @@ readonly PURPLE='\033[0;35m' readonly CYAN='\033[0;36m' readonly NC='\033[0m' -# =========================== LOGGING-FUNKTIONEN =========================== +# =========================== VERBESSERTE LOGGING-FUNKTIONEN =========================== log() { echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}" | tee -a "$INSTALL_LOG" } @@ -63,7 +67,41 @@ success() { echo -e "${CYAN}[ERFOLG] $1${NC}" | tee -a "$INSTALL_LOG" } -# =========================== SYSTEM-VALIDIERUNG =========================== +debug() { + echo -e "${BLUE}[DEBUG] $1${NC}" >> "$INSTALL_LOG" +} + +# =========================== RETRY-MECHANISMEN =========================== +retry_command() { + local cmd="$1" + local description="$2" + local attempts=0 + + while [ $attempts -lt $MAX_RETRIES ]; do + if eval "$cmd"; then + return 0 + fi + + attempts=$((attempts + 1)) + if [ $attempts -lt $MAX_RETRIES ]; then + warning "$description fehlgeschlagen (Versuch $attempts/$MAX_RETRIES) - wiederhole in ${RETRY_DELAY}s..." + sleep $RETRY_DELAY + fi + done + + error "$description nach $MAX_RETRIES Versuchen fehlgeschlagen!" +} + +# APT-Pakete mit Retry installieren +apt_install_retry() { + local packages="$*" + local cmd="DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends $packages" + + progress "Installiere Pakete: $packages" + retry_command "$cmd" "APT Installation für: $packages" +} + +# =========================== ERWEITERTE SYSTEM-VALIDIERUNG =========================== check_root() { if [ "$EUID" -ne 0 ]; then error "Dieses Skript muss als Root ausgeführt werden: sudo $0" @@ -72,6 +110,37 @@ check_root() { log "✅ Root-Berechtigung bestätigt" } +check_system_resources() { + log "=== SYSTEM-RESSOURCEN PRÜFUNG ===" + + # RAM prüfen + local ram_mb=$(free -m | awk '/^Mem:/{print $2}') + progress "Verfügbarer RAM: ${ram_mb}MB" + + if [ "$ram_mb" -lt 512 ]; then + warning "⚠️ Wenig RAM verfügbar (${ram_mb}MB) - Installation könnte langsam sein" + else + success "✅ Ausreichend RAM verfügbar (${ram_mb}MB)" + fi + + # Festplattenplatz prüfen + local disk_free_gb=$(df / | awk 'NR==2{printf "%.1f", $4/1024/1024}') + progress "Verfügbarer Festplattenplatz: ${disk_free_gb}GB" + + if [ "$(echo "$disk_free_gb < 2.0" | bc 2>/dev/null || echo "0")" -eq 1 ]; then + warning "⚠️ Wenig Festplattenplatz verfügbar (${disk_free_gb}GB)" + else + success "✅ Ausreichend Festplattenplatz verfügbar (${disk_free_gb}GB)" + fi + + # CPU prüfen + local cpu_count=$(nproc) + local cpu_model=$(grep "model name" /proc/cpuinfo | head -1 | cut -d: -f2 | xargs) + progress "CPU: $cpu_count Kern(e) - $cpu_model" + + log "✅ System-Ressourcen validiert" +} + check_debian_system() { if [ ! -f /etc/debian_version ]; then error "Dieses Skript ist nur für Debian/Raspbian-Systeme geeignet!" @@ -82,153 +151,505 @@ check_debian_system() { # Prüfe auf Raspberry Pi if [ -f /proc/device-tree/model ]; then - local pi_model=$(cat /proc/device-tree/model 2>/dev/null || echo "Unbekannt") + local pi_model=$(cat /proc/device-tree/model 2>/dev/null | tr -d '\0' || echo "Unbekannt") info "Raspberry Pi Modell: $pi_model" + + # Raspberry Pi spezifische Optimierungen + if [[ "$pi_model" =~ "Raspberry Pi" ]]; then + progress "Aktiviere Raspberry Pi spezifische Optimierungen..." + export RASPBERRY_PI_DETECTED=1 + fi fi + + # Architektur prüfen + local arch=$(uname -m) + info "System-Architektur: $arch" + + # Kernel-Version prüfen + local kernel=$(uname -r) + info "Kernel-Version: $kernel" } check_internet_connection() { - progress "Prüfe Internetverbindung (mehrere Methoden)..." + progress "Prüfe Internetverbindung (erweiterte Methoden)..." local connection_ok=false local test_method="" - # Methode 1: HTTP/HTTPS-Tests (robust für Firewalls/Proxys) - progress "Teste HTTP/HTTPS-Verbindungen..." - local http_urls=("http://detectportal.firefox.com/success.txt" "https://www.google.com" "http://httpbin.org/get") + # Methode 1: DNS-Auflösung zuerst (schnellster Test) + progress "Teste DNS-Auflösung..." + local dns_hosts=("google.com" "cloudflare.com" "github.com" "8.8.8.8") - for url in "${http_urls[@]}"; do - if command -v curl >/dev/null 2>&1; then - if curl -s --connect-timeout 5 --max-time 10 "$url" >/dev/null 2>&1; then - connection_ok=true - test_method="HTTP/HTTPS (curl: $url)" - break - fi - elif command -v wget >/dev/null 2>&1; then - if wget -q --timeout=5 --tries=1 "$url" -O /dev/null 2>/dev/null; then - connection_ok=true - test_method="HTTP/HTTPS (wget: $url)" - break - fi + for host in "${dns_hosts[@]}"; do + if nslookup "$host" >/dev/null 2>&1; then + connection_ok=true + test_method="DNS-Auflösung (nslookup: $host)" + break + elif getent hosts "$host" >/dev/null 2>&1; then + connection_ok=true + test_method="DNS-Auflösung (getent: $host)" + break fi done - # Methode 2: DNS-Auflösung testen (falls HTTP fehlschlägt) + # Methode 2: HTTP/HTTPS-Tests if [ "$connection_ok" = false ]; then - progress "Teste DNS-Auflösung..." - local dns_hosts=("google.com" "cloudflare.com" "github.com") + progress "Teste HTTP/HTTPS-Verbindungen..." + local http_urls=("http://detectportal.firefox.com/success.txt" "https://www.google.com" "http://httpbin.org/get") - for host in "${dns_hosts[@]}"; do - if command -v nslookup >/dev/null 2>&1; then - if nslookup "$host" >/dev/null 2>&1; then + for url in "${http_urls[@]}"; do + if command -v curl >/dev/null 2>&1; then + if curl -s --connect-timeout 10 --max-time 15 "$url" >/dev/null 2>&1; then connection_ok=true - test_method="DNS-Auflösung (nslookup: $host)" + test_method="HTTP/HTTPS (curl: $url)" break fi - elif command -v dig >/dev/null 2>&1; then - if dig +short "$host" >/dev/null 2>&1; then + elif command -v wget >/dev/null 2>&1; then + if wget -q --timeout=10 --tries=1 "$url" -O /dev/null 2>/dev/null; then connection_ok=true - test_method="DNS-Auflösung (dig: $host)" + test_method="HTTP/HTTPS (wget: $url)" break fi - elif getent hosts "$host" >/dev/null 2>&1; then - connection_ok=true - test_method="DNS-Auflösung (getent: $host)" - break fi done fi - # Methode 3: ICMP-Ping (nur als letzter Fallback) + # Methode 3: APT-Repository-Test if [ "$connection_ok" = false ]; then - progress "Teste ICMP-Ping (Fallback)..." - local ping_hosts=("8.8.8.8" "1.1.1.1" "9.9.9.9") - - for host in "${ping_hosts[@]}"; do - if ping -c 1 -W 3 "$host" >/dev/null 2>&1; then - connection_ok=true - test_method="ICMP-Ping ($host)" - break - fi - done - fi - - # Methode 4: Routing-Tabelle prüfen (offline detection) - if [ "$connection_ok" = false ]; then - progress "Prüfe Netzwerk-Routing..." - if ip route show default >/dev/null 2>&1; then - local default_gw=$(ip route show default | awk '/default/ {print $3}' | head -1) - if [ -n "$default_gw" ]; then - if ping -c 1 -W 2 "$default_gw" >/dev/null 2>&1; then - # Gateway erreichbar, aber Internet möglicherweise eingeschränkt - connection_ok=true - test_method="Lokales Netzwerk (Gateway: $default_gw) - Internet möglicherweise eingeschränkt" - fi - fi + progress "Teste APT-Repository-Zugang..." + if apt-get update -qq 2>/dev/null; then + connection_ok=true + test_method="APT-Repository-Zugang" fi fi - # Ergebnis-Bewertung und Logging + # Ergebnis-Bewertung if [ "$connection_ok" = true ]; then success "✅ Internetverbindung verfügbar" info " 🔍 Erkannt via: $test_method" - # Zusätzliche Informationen für Debugging + # Zusätzliche Informationen if command -v curl >/dev/null 2>&1; then - local external_ip=$(curl -s --connect-timeout 3 ifconfig.me 2>/dev/null || curl -s --connect-timeout 3 ipinfo.io/ip 2>/dev/null || echo "Unbekannt") + local external_ip=$(curl -s --connect-timeout 5 ifconfig.me 2>/dev/null || curl -s --connect-timeout 5 ipinfo.io/ip 2>/dev/null || echo "Unbekannt") if [ "$external_ip" != "Unbekannt" ]; then info " 🌐 Externe IP: $external_ip" fi fi else warning "⚠️ Keine Internetverbindung erkannt" - info " → Installation wird fortgesetzt, könnte aber bei Paket-Downloads fehlschlagen" - info " → Stellen Sie sicher, dass apt-get update funktioniert" - - # Hilfreich für Debugging - info " 🔍 Debug-Informationen:" - if command -v ip >/dev/null 2>&1; then - local default_gw=$(ip route show default | awk '/default/ {print $3}' | head -1 2>/dev/null || echo "Kein Gateway") - info " - Default Gateway: $default_gw" - fi - - if [ -f /etc/resolv.conf ]; then - local dns_servers=$(grep -E "^nameserver" /etc/resolv.conf | awk '{print $2}' | tr '\n' ' ' || echo "Keine DNS-Server") - info " - DNS-Server: $dns_servers" - fi + info " → Installation wird fortgesetzt, aber Downloads könnten fehlschlagen" + warning " → Bitte prüfen Sie die Netzwerkverbindung!" fi } -# =========================== SYSTEM-VORBEREITUNG =========================== +# =========================== ROBUSTE SYSTEM-VORBEREITUNG =========================== update_system() { - log "=== SYSTEM-UPDATE ===" + log "=== ROBUSTE SYSTEM-UPDATE ===" - progress "Aktualisiere Paketlisten..." - apt-get update -y || error "APT Update fehlgeschlagen" + progress "Konfiguriere APT für bessere Zuverlässigkeit..." + + # APT-Konfiguration optimieren + cat > /etc/apt/apt.conf.d/99myp-optimized << 'EOF' +APT::Acquire::Retries "3"; +APT::Acquire::http::Timeout "30"; +APT::Acquire::https::Timeout "30"; +APT::Acquire::ftp::Timeout "30"; +APT::Install-Recommends "false"; +APT::Install-Suggests "false"; +Dpkg::Options { + "--force-confdef"; + "--force-confold"; +} +EOF + + # Repository-Listen korrigieren falls nötig + progress "Validiere APT-Repositories..." + if [ -f /etc/apt/sources.list ]; then + # Backup erstellen + cp /etc/apt/sources.list /etc/apt/sources.list.backup + + # Prüfe auf problematische Einträge + if grep -q "deb-src" /etc/apt/sources.list; then + sed -i 's/^deb-src/#deb-src/g' /etc/apt/sources.list + log "✅ Source-Repositories deaktiviert (nicht benötigt)" + fi + fi + + progress "Aktualisiere Paketlisten mit Retry..." + retry_command "apt-get update" "APT Update" progress "Führe System-Upgrade durch..." - apt-get upgrade -y || warning "System-Upgrade teilweise fehlgeschlagen" + retry_command "DEBIAN_FRONTEND=noninteractive apt-get upgrade -y" "System Upgrade" - progress "Installiere grundlegende System-Tools..." - apt-get install -y \ - curl \ - wget \ - git \ - nano \ - htop \ - rsync \ - unzip \ - sudo \ - systemd \ - ca-certificates \ - gnupg \ - lsb-release \ - apt-transport-https \ - software-properties-common \ - bc \ - || error "Grundlegende Tools Installation fehlgeschlagen" + progress "Installiere essenzielle System-Tools..." - log "✅ System-Update abgeschlossen" + # Grundlegende Tools in optimierter Reihenfolge + local essential_packages=( + "ca-certificates" + "gnupg" + "curl" + "wget" + "git" + "nano" + "htop" + "rsync" + "unzip" + "sudo" + "systemd" + "lsb-release" + "apt-transport-https" + "software-properties-common" + "bc" + "dbus" + "systemd-timesyncd" + ) + + for package in "${essential_packages[@]}"; do + apt_install_retry "$package" + done + + # Zeitserver synchronisieren + progress "Synchronisiere Systemzeit..." + systemctl enable systemd-timesyncd 2>/dev/null || true + systemctl start systemd-timesyncd 2>/dev/null || true + + log "✅ Robustes System-Update abgeschlossen" +} + +# =========================== VERBESSERTE PYTHON-INSTALLATION =========================== +install_python_dependencies() { + log "=== ROBUSTE PYTHON-INSTALLATION ===" + + progress "Installiere Python 3 und Build-Abhängigkeiten..." + + local python_packages=( + "python3" + "python3-pip" + "python3-dev" + "python3-setuptools" + "python3-venv" + "python3-wheel" + "build-essential" + "libssl-dev" + "libffi-dev" + "libbz2-dev" + "libreadline-dev" + "libsqlite3-dev" + "libncurses5-dev" + "libncursesw5-dev" + "zlib1g-dev" + "sqlite3" + ) + + for package in "${python_packages[@]}"; do + apt_install_retry "$package" + done + + # Python-Version validieren + progress "Validiere Python-Installation..." + local python_version=$(python3 --version 2>&1 | cut -d' ' -f2) + log "✅ Python Version: $python_version" + + # pip konfigurieren und aktualisieren + progress "Konfiguriere pip für bessere Zuverlässigkeit..." + + # Root pip-Konfiguration + mkdir -p /root/.pip + cat > /root/.pip/pip.conf << 'EOF' +[global] +trusted-host = pypi.org + pypi.python.org + files.pythonhosted.org +cert = /etc/ssl/certs/ca-certificates.crt +timeout = 120 +retries = 5 +no-cache-dir = false +disable-pip-version-check = true +no-warn-script-location = true +break-system-packages = true + +[install] +trusted-host = pypi.org + pypi.python.org + files.pythonhosted.org +user = false +break-system-packages = true +EOF + + # Systemweite pip-Konfiguration für alle Benutzer + progress "Erstelle systemweite pip-Konfiguration..." + mkdir -p /etc/pip + cat > /etc/pip/pip.conf << 'EOF' +[global] +break-system-packages = true +trusted-host = pypi.org + pypi.python.org + files.pythonhosted.org +timeout = 120 +retries = 5 +disable-pip-version-check = true + +[install] +break-system-packages = true +trusted-host = pypi.org + pypi.python.org + files.pythonhosted.org +user = false +EOF + + # pip-Konfiguration für existierende Benutzer erstellen + progress "Konfiguriere pip für alle Benutzer..." + for user_home in "/home/"*; do + if [ -d "$user_home" ] && [ "$user_home" != "/home/lost+found" ]; then + local username=$(basename "$user_home") + if id "$username" &>/dev/null; then + mkdir -p "$user_home/.pip" 2>/dev/null || true + cat > "$user_home/.pip/pip.conf" << 'EOF' +[global] +break-system-packages = true +trusted-host = pypi.org + pypi.python.org + files.pythonhosted.org +timeout = 60 +retries = 3 + +[install] +break-system-packages = true +trusted-host = pypi.org + pypi.python.org + files.pythonhosted.org +EOF + chown "$username:$username" "$user_home/.pip/pip.conf" 2>/dev/null || true + log "✅ pip konfiguriert für Benutzer: $username" + fi + fi + done + + # pip selbst aktualisieren + progress "Aktualisiere pip mit Retry..." + retry_command "python3 -m pip install --break-system-packages --upgrade pip setuptools wheel" "pip Upgrade" + + # pip-Version validieren + local pip_version=$(python3 -m pip --version | cut -d' ' -f2) + log "✅ pip Version: $pip_version" + + log "✅ Robuste Python-Umgebung installiert" +} + +install_python_packages() { + log "=== ROBUSTE PYTHON-PAKETE INSTALLATION ===" + + progress "Installiere Python-Pakete mit verbesserter Strategie..." + + if [ ! -f "$CURRENT_DIR/requirements.txt" ]; then + error "requirements.txt nicht gefunden: $CURRENT_DIR/requirements.txt" + fi + + # Kopiere requirements.txt + cp "$CURRENT_DIR/requirements.txt" "$APP_DIR/" 2>/dev/null || true + + # Strategie 1: Direkte Installation mit pip + progress "Versuche direkte requirements.txt Installation..." + + local pip_opts="--break-system-packages --timeout 120 --retries 5 --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org" + + if python3 -m pip install $pip_opts -r "$CURRENT_DIR/requirements.txt"; then + success "✅ requirements.txt erfolgreich installiert" + else + warning "⚠️ Direkte Installation fehlgeschlagen - verwende Schritt-für-Schritt Installation" + + # Strategie 2: Pakete in optimierter Reihenfolge installieren + progress "Installiere Core-Abhängigkeiten zuerst..." + + # Grundlegende Abhängigkeiten zuerst + local core_deps=( + "MarkupSafe==3.0.2" + "itsdangerous==2.2.0" + "click==8.1.7" + "Werkzeug==3.1.3" + "Jinja2==3.1.4" + "Flask==3.1.1" + ) + + for dep in "${core_deps[@]}"; do + progress "Installiere: $dep" + retry_command "python3 -m pip install $pip_opts '$dep'" "$dep Installation" + done + + # Flask-Extensions + progress "Installiere Flask-Extensions..." + local flask_extensions=( + "WTForms==3.1.2" + "Flask-WTF==1.2.1" + "Flask-Login==0.6.3" + ) + + for ext in "${flask_extensions[@]}"; do + progress "Installiere: $ext" + retry_command "python3 -m pip install $pip_opts '$ext'" "$ext Installation" + done + + # Datenbank und Sicherheit + progress "Installiere Datenbank und Sicherheits-Komponenten..." + local db_security=( + "SQLAlchemy==2.0.36" + "cryptography==44.0.0" + "bcrypt==4.2.1" + ) + + for comp in "${db_security[@]}"; do + progress "Installiere: $comp" + retry_command "python3 -m pip install $pip_opts '$comp'" "$comp Installation" + done + + # System-Abhängigkeiten + progress "Installiere System-Abhängigkeiten..." + local system_deps=( + "requests==2.32.3" + "psutil==6.1.1" + "python-dateutil==2.9.0" + "colorlog==6.9.0" + "gunicorn==23.0.0" + ) + + for dep in "${system_deps[@]}"; do + progress "Installiere: $dep" + retry_command "python3 -m pip install $pip_opts '$dep'" "$dep Installation" || warning "⚠️ $dep Installation fehlgeschlagen (optional)" + done + fi + + # Umfassende Validierung + progress "Validiere kritische Python-Abhängigkeiten..." + + # Erstelle Validierungs-Skript + cat > /tmp/validate_python.py << 'EOF' +#!/usr/bin/env python3 +import sys +import importlib + +required_modules = [ + ('flask', 'Flask'), + ('werkzeug', 'Werkzeug'), + ('jinja2', 'Jinja2'), + ('sqlalchemy', 'SQLAlchemy'), + ('bcrypt', 'bcrypt'), + ('cryptography', 'cryptography'), + ('requests', 'requests'), + ('psutil', 'psutil'), + ('flask_login', 'Flask-Login'), + ('flask_wtf', 'Flask-WTF'), + ('wtforms', 'WTForms'), + ('itsdangerous', 'itsdangerous'), + ('markupsafe', 'MarkupSafe'), + ('click', 'click') +] + +failed_imports = [] +successful_imports = [] + +for module_name, display_name in required_modules: + try: + module = importlib.import_module(module_name) + version = getattr(module, '__version__', 'Version unbekannt') + print(f"✅ {display_name}: {version}") + successful_imports.append(display_name) + except ImportError as e: + print(f"❌ {display_name}: Import fehlgeschlagen - {e}") + failed_imports.append(display_name) + except Exception as e: + print(f"⚠️ {display_name}: Unerwarteter Fehler - {e}") + failed_imports.append(display_name) + +print(f"\nErgebnis: {len(successful_imports)} erfolgreich, {len(failed_imports)} fehlgeschlagen") + +if failed_imports: + print("Fehlgeschlagene Module:", ", ".join(failed_imports)) + sys.exit(1) +else: + print("Alle kritischen Module erfolgreich importiert!") + sys.exit(0) +EOF + + if python3 /tmp/validate_python.py; then + success "✅ Alle kritischen Python-Abhängigkeiten erfolgreich validiert" + else + warning "⚠️ Einige Python-Abhängigkeiten fehlen - versuche Reparatur..." + + # Repair-Versuch + retry_command "python3 -m pip install --break-system-packages --upgrade --force-reinstall Flask Werkzeug SQLAlchemy bcrypt" "Critical Package Repair" + fi + + rm -f /tmp/validate_python.py + + log "✅ Python-Pakete Installation abgeschlossen" +} + +# =========================== ROBUSTE NODE.JS INSTALLATION =========================== +install_nodejs_npm() { + log "=== ROBUSTE NODE.JS UND NPM INSTALLATION ===" + + # Alte Installationen entfernen + progress "Bereinige alte Node.js-Installationen..." + apt-get remove --purge -y nodejs npm 2>/dev/null || true + apt-get autoremove -y 2>/dev/null || true + rm -rf /usr/local/bin/node /usr/local/bin/npm 2>/dev/null || true + + # NodeSource Repository mit Fallback + progress "Installiere Node.js mit Fallback-Strategie..." + + local nodejs_installed=false + + # Strategie 1: NodeSource Repository + if curl -fsSL https://deb.nodesource.com/setup_lts.x 2>/dev/null | bash - 2>/dev/null; then + if apt-get update -y && apt_install_retry nodejs; then + nodejs_installed=true + log "✅ Node.js via NodeSource Repository installiert" + fi + fi + + # Strategie 2: Snap (falls verfügbar) + if [ "$nodejs_installed" = false ] && command -v snap >/dev/null 2>&1; then + progress "Versuche Node.js Installation via Snap..." + if snap install node --classic 2>/dev/null; then + nodejs_installed=true + log "✅ Node.js via Snap installiert" + fi + fi + + # Strategie 3: Debian Repository (Fallback) + if [ "$nodejs_installed" = false ]; then + progress "Verwende Debian Repository als Fallback..." + apt_install_retry nodejs npm + nodejs_installed=true + log "✅ Node.js via Debian Repository installiert" + fi + + # Validierung + progress "Validiere Node.js Installation..." + if command -v node >/dev/null 2>&1; then + local node_version=$(node --version) + log "✅ Node.js Version: $node_version" + else + error "❌ Node.js nicht verfügbar nach Installation" + fi + + if command -v npm >/dev/null 2>&1; then + local npm_version=$(npm --version) + log "✅ npm Version: $npm_version" + + # npm optimieren + progress "Optimiere npm-Konfiguration..." + npm config set fund false 2>/dev/null || true + npm config set audit-level moderate 2>/dev/null || true + npm config set progress false 2>/dev/null || true + npm config set loglevel warn 2>/dev/null || true + else + # Versuche npm separat zu installieren + progress "Installiere npm separat..." + apt_install_retry npm + fi + + log "✅ Node.js und npm erfolgreich installiert" } # =========================== NETZWERK-SICHERHEIT =========================== @@ -520,8 +941,28 @@ create_kiosk_user() { progress "Erstelle Kiosk-Benutzer: $KIOSK_USER" useradd -m -s /bin/bash "$KIOSK_USER" || error "Kann Kiosk-Benutzer nicht erstellen" - # Gruppen hinzufügen - usermod -aG audio,video,input,dialout,plugdev,users "$KIOSK_USER" 2>/dev/null || true + # Gruppen hinzufügen + usermod -aG audio,video,input,dialout,plugdev,users "$KIOSK_USER" 2>/dev/null || true + + # pip-Konfiguration für Kiosk-Benutzer + local kiosk_home="/home/$KIOSK_USER" + mkdir -p "$kiosk_home/.pip" 2>/dev/null || true + cat > "$kiosk_home/.pip/pip.conf" << 'EOF' +[global] +break-system-packages = true +trusted-host = pypi.org + pypi.python.org + files.pythonhosted.org +timeout = 60 +retries = 3 + +[install] +break-system-packages = true +trusted-host = pypi.org + pypi.python.org + files.pythonhosted.org +EOF + chown "$KIOSK_USER:$KIOSK_USER" "$kiosk_home/.pip/pip.conf" 2>/dev/null || true else info "Kiosk-Benutzer $KIOSK_USER existiert bereits" fi @@ -676,382 +1117,261 @@ EOF info "Der Kiosk-Modus startet automatisch beim Login des $KIOSK_USER" } -# =========================== PYTHON & NODE.JS INSTALLATION =========================== -install_python_dependencies() { - log "=== PYTHON-ABHÄNGIGKEITEN INSTALLATION ===" - - progress "Installiere Python 3 und Entwicklungstools..." - apt-get install -y \ - python3 \ - python3-pip \ - python3-dev \ - python3-setuptools \ - python3-venv \ - build-essential \ - libssl-dev \ - libffi-dev \ - sqlite3 \ - || error "Python Installation fehlgeschlagen" - - # pip auf neueste Version aktualisieren - progress "Aktualisiere pip..." - python3 -m pip install --upgrade pip --break-system-packages || warning "pip Update fehlgeschlagen" - - # SSL-Konfiguration für pip - mkdir -p /root/.pip - cat > /root/.pip/pip.conf << EOF -[global] -trusted-host = pypi.org - pypi.python.org - files.pythonhosted.org -cert = /etc/ssl/certs/ca-certificates.crt -timeout = 60 -retries = 3 -no-cache-dir = true - -[install] -trusted-host = pypi.org - pypi.python.org - files.pythonhosted.org -no-warn-script-location = true -EOF - - log "✅ Python-Umgebung vorbereitet" -} - -install_nodejs_npm() { - log "=== NODE.JS UND NPM INSTALLATION ===" - - # Alte Node.js-Installationen entfernen - progress "Entferne alte Node.js-Installationen..." - apt-get remove --purge -y nodejs npm 2>/dev/null || true - apt-get autoremove -y 2>/dev/null || true - - # NodeSource Repository für Node.js LTS hinzufügen - progress "Installiere Node.js LTS..." - - if curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - 2>/dev/null; then - apt-get update -y - apt-get install -y nodejs || error "Node.js Installation fehlgeschlagen" - else - warning "NodeSource Repository nicht verfügbar - verwende Debian-Repository" - apt-get install -y nodejs npm || error "Node.js Fallback Installation fehlgeschlagen" - fi - - # Versionen prüfen - if command -v node >/dev/null 2>&1; then - local node_version=$(node --version) - log "✅ Node.js installiert: $node_version" - else - error "❌ Node.js Installation fehlgeschlagen" - fi - - if command -v npm >/dev/null 2>&1; then - local npm_version=$(npm --version) - log "✅ npm installiert: $npm_version" - - # npm-Konfiguration optimieren - npm config set fund false 2>/dev/null || true - npm config set audit-level moderate 2>/dev/null || true - else - error "❌ npm Installation fehlgeschlagen" - fi - - log "✅ Node.js und npm erfolgreich installiert" -} - -install_python_packages() { - log "=== PYTHON-PAKETE INSTALLATION ===" - - local pip_opts="--break-system-packages --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org --timeout 120 --retries 5 --no-cache-dir" - - progress "Installiere requirements.txt direkt..." - if [ -f "$CURRENT_DIR/requirements.txt" ]; then - # Kopiere requirements.txt zum App-Verzeichnis - cp "$CURRENT_DIR/requirements.txt" "$APP_DIR/" 2>/dev/null || true - - # Installiere direkt aus requirements.txt (robusteste Methode) - if pip3 install $pip_opts -r "$CURRENT_DIR/requirements.txt"; then - success "✅ Requirements.txt erfolgreich installiert" - - # Kurze Validierung der Installation - progress "Prüfe pip-Installation..." - pip3 list | grep -E "(Flask|Werkzeug|SQLAlchemy|bcrypt)" || warning "Einige Core-Pakete möglicherweise nicht sichtbar" - else - warning "⚠️ Requirements.txt Installation fehlgeschlagen - verwende Fallback-Installation" - - # Fallback: Core-Pakete einzeln installieren - progress "Installiere Core-Framework (Fallback)..." - pip3 install $pip_opts Flask==3.1.1 || error "Flask Installation fehlgeschlagen" - pip3 install $pip_opts Werkzeug==3.1.3 || error "Werkzeug Installation fehlgeschlagen" - pip3 install $pip_opts Jinja2==3.1.4 || error "Jinja2 Installation fehlgeschlagen" - - progress "Installiere Flask-Extensions (Fallback)..." - pip3 install $pip_opts Flask-Login==0.6.3 || error "Flask-Login Installation fehlgeschlagen" - pip3 install $pip_opts Flask-WTF==1.2.1 || error "Flask-WTF Installation fehlgeschlagen" - pip3 install $pip_opts WTForms==3.1.2 || error "WTForms Installation fehlgeschlagen" - - progress "Installiere Datenbank-Komponenten (Fallback)..." - pip3 install $pip_opts SQLAlchemy==2.0.36 || error "SQLAlchemy Installation fehlgeschlagen" - - progress "Installiere Sicherheits-Komponenten (Fallback)..." - pip3 install $pip_opts cryptography==44.0.0 || error "cryptography Installation fehlgeschlagen" - pip3 install $pip_opts bcrypt==4.2.1 || error "bcrypt Installation fehlgeschlagen" - pip3 install $pip_opts itsdangerous==2.2.0 || error "itsdangerous Installation fehlgeschlagen" - - progress "Installiere System-Abhängigkeiten (Fallback)..." - pip3 install $pip_opts requests==2.32.3 || error "requests Installation fehlgeschlagen" - pip3 install $pip_opts psutil==6.1.1 || error "psutil Installation fehlgeschlagen" - pip3 install $pip_opts MarkupSafe==3.0.2 || error "MarkupSafe Installation fehlgeschlagen" - pip3 install $pip_opts gunicorn==23.0.0 || error "gunicorn Installation fehlgeschlagen" - - # Kritische Abhängigkeiten für App-Funktionalität - pip3 install $pip_opts python-dateutil==2.9.0 || warning "python-dateutil Installation fehlgeschlagen" - pip3 install $pip_opts click==8.1.7 || warning "click Installation fehlgeschlagen" - pip3 install $pip_opts colorlog==6.9.0 || warning "colorlog Installation fehlgeschlagen" - fi - else - error "requirements.txt nicht gefunden: $CURRENT_DIR/requirements.txt" - fi - - # Validiere kritische Imports nach Installation - progress "Validiere kritische Python-Abhängigkeiten..." - local validation_errors=0 - - # Robuste Validierung mit detailliertem Logging - progress "Teste Flask-Import..." - if python3 -c " -import sys -try: - import flask - try: - version = flask.__version__ - except AttributeError: - version = 'Version unbekannt' - print('✅ Flask ' + version + ' verfügbar') - sys.exit(0) -except ImportError as e: - print('❌ Flask-Import fehlgeschlagen: ' + str(e)) - sys.exit(1) -except Exception as e: - print('❌ Flask-Test-Fehler: ' + str(e)) - sys.exit(1) -" 2>&1; then - success "✅ Flask erfolgreich validiert" - else - warning "⚠️ Flask-Import problematisch" - ((validation_errors++)) - fi - - progress "Teste Werkzeug-Import..." - if python3 -c " -import sys -try: - import werkzeug - # Robuste Versions-Erkennung für Werkzeug - version = 'Version unbekannt' - try: - version = werkzeug.__version__ - except AttributeError: - try: - from werkzeug import __version__ as wz_version - version = wz_version - except ImportError: - try: - import pkg_resources - version = pkg_resources.get_distribution('werkzeug').version - except: - version = 'verfügbar' - print('✅ Werkzeug ' + version + ' verfügbar') - sys.exit(0) -except ImportError as e: - print('❌ Werkzeug-Import fehlgeschlagen: ' + str(e)) - sys.exit(1) -except Exception as e: - print('❌ Werkzeug-Test-Fehler: ' + str(e)) - sys.exit(1) -" 2>&1; then - success "✅ Werkzeug erfolgreich validiert" - else - warning "⚠️ Werkzeug-Import problematisch" - ((validation_errors++)) - fi - - progress "Teste SQLAlchemy-Import..." - if python3 -c " -import sys -try: - import sqlalchemy - try: - version = sqlalchemy.__version__ - except AttributeError: - version = 'Version unbekannt' - print('✅ SQLAlchemy ' + version + ' verfügbar') - sys.exit(0) -except ImportError as e: - print('❌ SQLAlchemy-Import fehlgeschlagen: ' + str(e)) - sys.exit(1) -except Exception as e: - print('❌ SQLAlchemy-Test-Fehler: ' + str(e)) - sys.exit(1) -" 2>&1; then - success "✅ SQLAlchemy erfolgreich validiert" - else - warning "⚠️ SQLAlchemy-Import problematisch" - ((validation_errors++)) - fi - - progress "Teste bcrypt-Import..." - if python3 -c " -import sys -try: - import bcrypt - print('✅ bcrypt verfügbar') - sys.exit(0) -except ImportError as e: - print('❌ bcrypt-Import fehlgeschlagen: ' + str(e)) - sys.exit(1) -except Exception as e: - print('❌ bcrypt-Test-Fehler: ' + str(e)) - sys.exit(1) -" 2>&1; then - success "✅ bcrypt erfolgreich validiert" - else - warning "⚠️ bcrypt-Import problematisch" - ((validation_errors++)) - fi - - # Zusammenfassung und Entscheidung - if [ $validation_errors -eq 0 ]; then - success "✅ Alle kritischen Python-Abhängigkeiten erfolgreich validiert" - elif [ $validation_errors -le 2 ]; then - warning "⚠️ $validation_errors Abhängigkeiten haben Probleme - System sollte trotzdem funktionieren" - info "→ Möglicherweise sind nur optionale Features betroffen" - else - error "❌ $validation_errors kritische Python-Abhängigkeiten fehlen!" - fi - - log "✅ Python-Pakete erfolgreich installiert und validiert" -} - -# =========================== SSL-ZERTIFIKATE =========================== +# =========================== ROBUSTE SSL-ZERTIFIKATE INSTALLATION =========================== install_ssl_certificates() { - log "=== SSL-ZERTIFIKATE KONFIGURATION ===" + log "=== ROBUSTE SSL-ZERTIFIKATE KONFIGURATION ===" + + progress "Installiere SSL-Grundkomponenten..." + apt_install_retry ca-certificates openssl progress "Aktualisiere CA-Zertifikate..." - apt-get install -y ca-certificates openssl || error "CA-Zertifikate Installation fehlgeschlagen" - update-ca-certificates || warning "CA-Zertifikate Update fehlgeschlagen" + retry_command "update-ca-certificates" "CA-Zertifikate Update" - # Mercedes Corporate Zertifikate (falls vorhanden) + # SSL-Verzeichnisse sicherstellen + mkdir -p /usr/local/share/ca-certificates/myp + + # Mercedes Corporate Zertifikate (robuster) if [ -d "$CURRENT_DIR/certs/mercedes" ] && [ "$(ls -A $CURRENT_DIR/certs/mercedes 2>/dev/null)" ]; then - progress "Installiere Mercedes Corporate Zertifikate..." + progress "Installiere Mercedes Corporate Zertifikate (robust)..." + + local cert_count=0 + local installed_count=0 find "$CURRENT_DIR/certs/mercedes" -type f \( -name "*.crt" -o -name "*.pem" -o -name "*.cer" \) | while read cert_file; do + ((cert_count++)) local cert_basename=$(basename "$cert_file") local cert_name="${cert_basename%.*}" + local target_file="/usr/local/share/ca-certificates/myp/${cert_name}.crt" progress "Verarbeite Mercedes-Zertifikat: $cert_basename" - # Zertifikat validieren und installieren + # Robuste Zertifikat-Validierung und Installation if openssl x509 -in "$cert_file" -text -noout >/dev/null 2>&1; then - cp "$cert_file" "/usr/local/share/ca-certificates/${cert_name}.crt" - log "✅ Zertifikat installiert: ${cert_name}.crt" + # PEM Format + if cp "$cert_file" "$target_file" 2>/dev/null; then + log "✅ PEM-Zertifikat installiert: ${cert_name}.crt" + ((installed_count++)) + fi elif openssl x509 -in "$cert_file" -inform DER -text -noout >/dev/null 2>&1; then - openssl x509 -in "$cert_file" -inform DER -out "/usr/local/share/ca-certificates/${cert_name}.crt" -outform PEM - log "✅ DER-Zertifikat konvertiert und installiert: ${cert_name}.crt" + # DER Format - zu PEM konvertieren + if openssl x509 -in "$cert_file" -inform DER -out "$target_file" -outform PEM 2>/dev/null; then + log "✅ DER-Zertifikat konvertiert und installiert: ${cert_name}.crt" + ((installed_count++)) + fi else warning "⚠️ Ungültiges Zertifikat übersprungen: $cert_file" fi done - update-ca-certificates || warning "Mercedes Zertifikate Update fehlgeschlagen" + if [ $installed_count -gt 0 ]; then + progress "Lade CA-Zertifikate nach Mercedes-Import neu..." + retry_command "update-ca-certificates" "Mercedes Zertifikate Update" + log "✅ $installed_count von $cert_count Mercedes-Zertifikaten erfolgreich installiert" + fi fi - # SSL-Umgebungsvariablen setzen + # SSL-Umgebungsvariablen systemweit setzen + progress "Konfiguriere SSL-Umgebungsvariablen..." + cat >> /etc/environment << 'EOF' + +# SSL Certificate Configuration für MYP +SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt +REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt +CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt +EOF + + # SSL-Umgebungsvariablen für aktuelle Session export SSL_CERT_FILE="/etc/ssl/certs/ca-certificates.crt" export REQUESTS_CA_BUNDLE="/etc/ssl/certs/ca-certificates.crt" export CURL_CA_BUNDLE="/etc/ssl/certs/ca-certificates.crt" - log "✅ SSL-Zertifikate konfiguriert" + # Validiere SSL-Setup + progress "Validiere SSL-Konfiguration..." + if [ -f "/etc/ssl/certs/ca-certificates.crt" ]; then + local cert_count=$(grep -c "BEGIN CERTIFICATE" /etc/ssl/certs/ca-certificates.crt 2>/dev/null || echo "0") + log "✅ SSL-Zertifikate verfügbar: $cert_count CA-Zertifikate" + else + warning "⚠️ CA-Zertifikate-Datei nicht gefunden" + fi + + log "✅ SSL-Zertifikate robust konfiguriert" } -# =========================== ANWENDUNGS-DEPLOYMENT =========================== +# =========================== ROBUSTES ANWENDUNGS-DEPLOYMENT =========================== deploy_application() { - log "=== ANWENDUNGS-DEPLOYMENT ===" + log "=== ROBUSTES ANWENDUNGS-DEPLOYMENT ===" - progress "Erstelle Zielverzeichnis: $APP_DIR" + progress "Erstelle sicheres Zielverzeichnis: $APP_DIR" mkdir -p "$APP_DIR" || error "Konnte Zielverzeichnis nicht erstellen" - progress "Kopiere Anwendungsdateien..." + # Validiere Source-Verzeichnis + progress "Validiere Source-Dateien..." + if [ ! -f "$CURRENT_DIR/app.py" ]; then + error "Kritische Datei nicht gefunden: $CURRENT_DIR/app.py" + fi - # Liste der zu kopierenden Dateien/Ordner - local copy_items=( + if [ ! -f "$CURRENT_DIR/requirements.txt" ]; then + error "Kritische Datei nicht gefunden: $CURRENT_DIR/requirements.txt" + fi + + progress "Kopiere Anwendungsdateien (robust)..." + + # Kritische Dateien zuerst (mit Validierung) + local critical_files=( "app.py" "models.py" "requirements.txt" - "blueprints/" - "config/" - "database/" - "static/" - "templates/" - "uploads/" - "utils/" - "logs/" - "certs/" ) - # Sichere selektive Kopie - for item in "${copy_items[@]}"; do - if [ -e "$CURRENT_DIR/$item" ]; then - progress "Kopiere: $item" - cp -r "$CURRENT_DIR/$item" "$APP_DIR/" || warning "Fehler beim Kopieren von $item" - else - info "Überspringe nicht vorhandenes Element: $item" - fi - done - - # Spezielle Dateien - for file in "package.json" "package-lock.json" "tailwind.config.js" "postcss.config.js"; do + for file in "${critical_files[@]}"; do if [ -f "$CURRENT_DIR/$file" ]; then - cp "$CURRENT_DIR/$file" "$APP_DIR/" || warning "Fehler beim Kopieren von $file" + progress "Kopiere kritische Datei: $file" + if cp "$CURRENT_DIR/$file" "$APP_DIR/" 2>/dev/null; then + success "✅ $file erfolgreich kopiert" + else + error "❌ Fehler beim Kopieren der kritischen Datei: $file" + fi + else + error "❌ Kritische Datei fehlt: $file" fi done - # Erstelle notwendige Verzeichnisse - mkdir -p "$APP_DIR"/{database/backups,logs/{app,auth,errors},uploads/temp,certs/localhost} + # Verzeichnisse mit robuster Behandlung + local directories=( + "blueprints" + "config" + "database" + "static" + "templates" + "uploads" + "utils" + "logs" + "certs" + ) - # Berechtigungen setzen - chown -R root:root "$APP_DIR" - chmod -R 755 "$APP_DIR" - chmod 750 "$APP_DIR"/{database,logs,certs} - chmod +x "$APP_DIR/app.py" + for dir in "${directories[@]}"; do + if [ -d "$CURRENT_DIR/$dir" ]; then + progress "Kopiere Verzeichnis: $dir" + if cp -r "$CURRENT_DIR/$dir" "$APP_DIR/" 2>/dev/null; then + success "✅ $dir erfolgreich kopiert" + else + warning "⚠️ Fehler beim Kopieren von $dir (möglicherweise nicht kritisch)" + fi + else + info "Verzeichnis nicht vorhanden: $dir" + fi + done - # Python-Pfad-Konfiguration erstellen - progress "Konfiguriere Python-Umgebung..." + # Optionale Dateien + local optional_files=( + "package.json" + "package-lock.json" + "tailwind.config.js" + "postcss.config.js" + "README.md" + ".gitignore" + ) - # .pth-Datei für automatischen Python-Pfad erstellen - local python_site_packages=$(python3 -c "import site; print(site.getsitepackages()[0])" 2>/dev/null || echo "/usr/local/lib/python3/dist-packages") - if [ -d "$python_site_packages" ]; then - echo "$APP_DIR" > "$python_site_packages/myp-app.pth" - log "✅ Python-Pfad konfiguriert: $python_site_packages/myp-app.pth" + for file in "${optional_files[@]}"; do + if [ -f "$CURRENT_DIR/$file" ]; then + progress "Kopiere optionale Datei: $file" + cp "$CURRENT_DIR/$file" "$APP_DIR/" 2>/dev/null || warning "⚠️ Kopieren von $file fehlgeschlagen" + fi + done + + # Erstelle alle notwendigen Verzeichnisse mit korrekter Struktur + progress "Erstelle Verzeichnisstruktur..." + local required_dirs=( + "database/backups" + "logs/app" + "logs/auth" + "logs/errors" + "logs/system" + "uploads/temp" + "uploads/assets" + "uploads/avatars" + "uploads/backups" + "uploads/jobs" + "certs/localhost" + "instance" + "instance/ssl" + ) + + for dir in "${required_dirs[@]}"; do + mkdir -p "$APP_DIR/$dir" 2>/dev/null || warning "⚠️ Verzeichnis $dir konnte nicht erstellt werden" + done + + # Sichere Berechtigungen setzen (robuster) + progress "Setze sichere Berechtigungen..." + + # Basis-Berechtigungen + chown -R root:root "$APP_DIR" 2>/dev/null || warning "⚠️ Ownership konnte nicht gesetzt werden" + chmod 755 "$APP_DIR" 2>/dev/null || warning "⚠️ Verzeichnis-Permissions konnten nicht gesetzt werden" + + # Ausführbare Dateien + if [ -f "$APP_DIR/app.py" ]; then + chmod +x "$APP_DIR/app.py" 2>/dev/null || warning "⚠️ app.py Ausführberechtigung konnte nicht gesetzt werden" fi - # Systemweite Umgebungsvariablen setzen + # Sensitive Verzeichnisse + chmod 750 "$APP_DIR/database" 2>/dev/null || true + chmod 750 "$APP_DIR/logs" 2>/dev/null || true + chmod 750 "$APP_DIR/certs" 2>/dev/null || true + chmod 750 "$APP_DIR/instance" 2>/dev/null || true + + # Upload-Verzeichnisse + chmod 755 "$APP_DIR/uploads" 2>/dev/null || true + chmod 755 "$APP_DIR/static" 2>/dev/null || true + + # Python-Umgebung robust konfigurieren + progress "Konfiguriere robuste Python-Umgebung..." + + # Ermittle Python site-packages Verzeichnis (robust) + local python_site_packages="" + for possible_path in \ + "$(python3 -c "import site; print(site.getsitepackages()[0])" 2>/dev/null)" \ + "/usr/local/lib/python3/dist-packages" \ + "/usr/lib/python3/dist-packages" \ + "/usr/local/lib/python$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null)/dist-packages"; do + + if [ -d "$possible_path" ]; then + python_site_packages="$possible_path" + break + fi + done + + if [ -n "$python_site_packages" ]; then + echo "$APP_DIR" > "$python_site_packages/myp-app.pth" 2>/dev/null || warning "⚠️ Python-Pfad konnte nicht konfiguriert werden" + log "✅ Python-Pfad konfiguriert: $python_site_packages/myp-app.pth" + else + warning "⚠️ Python site-packages Verzeichnis nicht gefunden" + fi + + # Systemweite Umgebungsvariablen robust setzen + progress "Konfiguriere Umgebungsvariablen..." cat >> /etc/environment << EOF # MYP Application Environment MYP_APP_DIR=$APP_DIR -PYTHONPATH=$APP_DIR +PYTHONPATH=$APP_DIR:\${PYTHONPATH:-} +FLASK_APP=$APP_DIR/app.py +FLASK_ENV=production EOF - # Bash-Profile für Root und Standard-User aktualisieren + # Bash-Profile für alle User aktualisieren (robust) + progress "Aktualisiere Bash-Profile..." + local profile_updated=0 + for user_home in "/root" "/home/"*; do - if [ -d "$user_home" ] && [ "$user_home" != "/home/lost+found" ]; then - cat >> "$user_home/.bashrc" << 'EOF' + if [ -d "$user_home" ] && [ "$user_home" != "/home/lost+found" ] && [ -w "$user_home" ]; then + if ! grep -q "MYP Application Environment" "$user_home/.bashrc" 2>/dev/null; then + cat >> "$user_home/.bashrc" << 'EOF' # MYP Application Environment if [ -d "/opt/myp" ]; then export MYP_APP_DIR="/opt/myp" + export FLASK_APP="/opt/myp/app.py" + export FLASK_ENV="production" if [ -z "${PYTHONPATH:-}" ]; then export PYTHONPATH="/opt/myp" else @@ -1059,11 +1379,43 @@ if [ -d "/opt/myp" ]; then fi fi EOF - log "✅ Bash-Profile aktualisiert: $user_home/.bashrc" + ((profile_updated++)) + log "✅ Bash-Profile aktualisiert: $user_home/.bashrc" + fi fi done - log "✅ Anwendung erfolgreich deployed mit korrekter Python-Umgebung" + # Validiere Deployment + progress "Validiere Application Deployment..." + local validation_errors=0 + + # Prüfe kritische Dateien + for file in "app.py" "models.py" "requirements.txt"; do + if [ ! -f "$APP_DIR/$file" ]; then + warning "❌ Kritische Datei fehlt: $file" + ((validation_errors++)) + fi + done + + # Prüfe wichtige Verzeichnisse + for dir in "static" "templates" "blueprints"; do + if [ ! -d "$APP_DIR/$dir" ]; then + warning "❌ Wichtiges Verzeichnis fehlt: $dir" + ((validation_errors++)) + fi + done + + if [ $validation_errors -eq 0 ]; then + success "✅ Application Deployment vollständig validiert" + else + warning "⚠️ $validation_errors Probleme beim Deployment gefunden" + fi + + log "✅ Robustes Anwendungs-Deployment abgeschlossen" + log " 📁 App-Verzeichnis: $APP_DIR" + log " 🐍 Python-Pfad konfiguriert" + log " 🔧 $profile_updated Bash-Profile aktualisiert" + log " 🛡️ Sichere Berechtigungen gesetzt" } install_npm_dependencies() { @@ -1117,110 +1469,414 @@ generate_ssl_certificate() { log "✅ SSL-Zertifikat erfolgreich generiert" } -# =========================== SYSTEMD-SERVICES =========================== +# =========================== ROBUSTE SYSTEMD-SERVICES INSTALLATION =========================== install_systemd_services() { - log "=== SYSTEMD-SERVICES INSTALLATION ===" + log "=== ROBUSTE SYSTEMD-SERVICES INSTALLATION ===" - # Prüfe ob systemd-Verzeichnis existiert + # Validiere systemd-Verzeichnis if [ ! -d "$SYSTEMD_DIR" ]; then error "systemd-Verzeichnis nicht gefunden: $SYSTEMD_DIR" fi - progress "Kopiere Service-Dateien nach $SYSTEM_SYSTEMD_DIR..." + progress "Validiere und kopiere Service-Dateien..." - # Kopiere alle Service-Dateien - local service_files=( + # Definiere Service-Dateien mit Priorität + local essential_services=( "$HTTPS_SERVICE_NAME.service" + ) + + local optional_services=( "$KIOSK_SERVICE_NAME.service" "$WATCHDOG_SERVICE_NAME.service" "$WATCHDOG_PYTHON_SERVICE_NAME.service" "$FIREWALL_SERVICE_NAME.service" ) - for service_file in "${service_files[@]}"; do + local installed_services=0 + local essential_errors=0 + + # Essenzielle Services zuerst + for service_file in "${essential_services[@]}"; do if [ -f "$SYSTEMD_DIR/$service_file" ]; then - progress "Kopiere Service: $service_file" - cp "$SYSTEMD_DIR/$service_file" "$SYSTEM_SYSTEMD_DIR/" || error "Fehler beim Kopieren von $service_file" + progress "Kopiere essenziellen Service: $service_file" + if cp "$SYSTEMD_DIR/$service_file" "$SYSTEM_SYSTEMD_DIR/" 2>/dev/null; then + success "✅ $service_file erfolgreich installiert" + ((installed_services++)) + else + error "❌ Fehler beim Kopieren des essenziellen Service: $service_file" + ((essential_errors++)) + fi else - warning "Service-Datei nicht gefunden: $service_file" + error "❌ Essenzieller Service nicht gefunden: $service_file" + ((essential_errors++)) + fi + done + + # Optionale Services + for service_file in "${optional_services[@]}"; do + if [ -f "$SYSTEMD_DIR/$service_file" ]; then + progress "Kopiere optionalen Service: $service_file" + if cp "$SYSTEMD_DIR/$service_file" "$SYSTEM_SYSTEMD_DIR/" 2>/dev/null; then + success "✅ $service_file erfolgreich installiert" + ((installed_services++)) + else + warning "⚠️ Fehler beim Kopieren des optionalen Service: $service_file" + fi + else + info "Optionaler Service nicht gefunden: $service_file" + fi + done + + # Prüfe auf kritische Fehler + if [ $essential_errors -gt 0 ]; then + error "❌ $essential_errors essenzielle Services konnten nicht installiert werden!" + fi + + # Validiere kopierte Service-Dateien + progress "Validiere Service-Dateien..." + for service_file in "${essential_services[@]}" "${optional_services[@]}"; do + if [ -f "$SYSTEM_SYSTEMD_DIR/$service_file" ]; then + # Syntaxprüfung für systemd-Services + if systemd-analyze verify "$SYSTEM_SYSTEMD_DIR/$service_file" 2>/dev/null; then + success "✅ $service_file Syntax-Validierung erfolgreich" + else + warning "⚠️ $service_file hat möglicherweise Syntax-Probleme" + fi fi done # Systemd-Konfiguration neu laden progress "Lade systemd-Konfiguration neu..." - systemctl daemon-reload || error "systemctl daemon-reload fehlgeschlagen" + retry_command "systemctl daemon-reload" "systemd daemon-reload" - log "✅ Systemd-Services installiert" + log "✅ Systemd-Services installiert: $installed_services Services" } enable_and_start_services() { - log "=== SERVICES AKTIVIEREN UND STARTEN ===" + log "=== ROBUSTE SERVICES AKTIVIERUNG UND START ===" - # HTTPS-Service aktivieren und starten - progress "Aktiviere und starte HTTPS-Service..." - systemctl enable "$HTTPS_SERVICE_NAME" || error "Fehler beim Aktivieren des HTTPS-Service" - systemctl start "$HTTPS_SERVICE_NAME" || error "Fehler beim Starten des HTTPS-Service" + # Service-Status tracking + local successful_services=0 + local failed_services=0 - # Warte kurz und prüfe Status - sleep 5 - if systemctl is-active --quiet "$HTTPS_SERVICE_NAME"; then - log "✅ HTTPS-Service läuft erfolgreich" + # HTTPS-Service (kritisch) + progress "Aktiviere und starte HTTPS-Service (kritisch)..." + + if systemctl enable "$HTTPS_SERVICE_NAME" 2>/dev/null; then + success "✅ HTTPS-Service erfolgreich aktiviert" + + if systemctl start "$HTTPS_SERVICE_NAME" 2>/dev/null; then + success "✅ HTTPS-Service erfolgreich gestartet" + + # Warte und prüfe Status gründlich + local startup_timeout=15 + local check_interval=2 + local elapsed=0 + + while [ $elapsed -lt $startup_timeout ]; do + if systemctl is-active --quiet "$HTTPS_SERVICE_NAME"; then + success "✅ HTTPS-Service läuft stabil nach ${elapsed}s" + ((successful_services++)) + break + fi + sleep $check_interval + elapsed=$((elapsed + check_interval)) + progress "Warte auf HTTPS-Service Startup... (${elapsed}/${startup_timeout}s)" + done + + if [ $elapsed -ge $startup_timeout ]; then + error "❌ HTTPS-Service Timeout nach ${startup_timeout}s - Service nicht verfügbar" + + # Debugging-Informationen + info "HTTPS-Service Status-Debug:" + systemctl status "$HTTPS_SERVICE_NAME" --no-pager -l || true + journalctl -u "$HTTPS_SERVICE_NAME" --no-pager -n 10 || true + ((failed_services++)) + fi + else + error "❌ HTTPS-Service konnte nicht gestartet werden" + ((failed_services++)) + fi else - error "❌ HTTPS-Service konnte nicht gestartet werden" + error "❌ HTTPS-Service konnte nicht aktiviert werden" + ((failed_services++)) fi - # Kiosk-Service aktivieren (aber nicht starten - wird beim nächsten Boot gestartet) - progress "Aktiviere Kiosk-Service..." - systemctl enable "$KIOSK_SERVICE_NAME" || warning "Fehler beim Aktivieren des Kiosk-Service" + # Kiosk-Service (für Produktionsinstallation) + if [ -f "$SYSTEM_SYSTEMD_DIR/$KIOSK_SERVICE_NAME.service" ]; then + progress "Aktiviere Kiosk-Service (startet beim nächsten Boot)..." + if systemctl enable "$KIOSK_SERVICE_NAME" 2>/dev/null; then + success "✅ Kiosk-Service erfolgreich aktiviert" + ((successful_services++)) + else + warning "⚠️ Kiosk-Service konnte nicht aktiviert werden" + ((failed_services++)) + fi + fi - # Watchdog-Service aktivieren und starten - progress "Aktiviere und starte Watchdog-Service..." - systemctl enable "$WATCHDOG_SERVICE_NAME" || warning "Fehler beim Aktivieren des Watchdog-Service" - systemctl start "$WATCHDOG_SERVICE_NAME" || warning "Fehler beim Starten des Watchdog-Service" + # Watchdog-Service (optional) + if [ -f "$SYSTEM_SYSTEMD_DIR/$WATCHDOG_SERVICE_NAME.service" ]; then + progress "Aktiviere und starte Watchdog-Service..." + if systemctl enable "$WATCHDOG_SERVICE_NAME" 2>/dev/null; then + if systemctl start "$WATCHDOG_SERVICE_NAME" 2>/dev/null; then + success "✅ Watchdog-Service erfolgreich aktiviert und gestartet" + ((successful_services++)) + else + warning "⚠️ Watchdog-Service aktiviert, aber Start fehlgeschlagen" + fi + else + warning "⚠️ Watchdog-Service konnte nicht aktiviert werden" + fi + fi - # Firewall-Service aktivieren (falls vorhanden) + # Python Watchdog-Service (optional) + if [ -f "$SYSTEM_SYSTEMD_DIR/$WATCHDOG_PYTHON_SERVICE_NAME.service" ]; then + progress "Aktiviere Python Watchdog-Service..." + if systemctl enable "$WATCHDOG_PYTHON_SERVICE_NAME" 2>/dev/null; then + success "✅ Python Watchdog-Service erfolgreich aktiviert" + ((successful_services++)) + else + warning "⚠️ Python Watchdog-Service konnte nicht aktiviert werden" + fi + fi + + # Firewall-Service (optional) if [ -f "$SYSTEM_SYSTEMD_DIR/$FIREWALL_SERVICE_NAME.service" ]; then progress "Aktiviere Firewall-Service..." - systemctl enable "$FIREWALL_SERVICE_NAME" || warning "Fehler beim Aktivieren des Firewall-Service" + if systemctl enable "$FIREWALL_SERVICE_NAME" 2>/dev/null; then + success "✅ Firewall-Service erfolgreich aktiviert" + ((successful_services++)) + else + warning "⚠️ Firewall-Service konnte nicht aktiviert werden" + fi fi - log "✅ Services erfolgreich konfiguriert" + # Zusammenfassung + log "📊 Service-Aktivierung Zusammenfassung:" + log " ✅ Erfolgreich: $successful_services Services" + log " ❌ Fehlgeschlagen: $failed_services Services" + + if [ $failed_services -eq 0 ]; then + success "✅ Alle verfügbaren Services erfolgreich konfiguriert" + elif [ $successful_services -gt 0 ]; then + warning "⚠️ $failed_services Services fehlgeschlagen, aber $successful_services Services funktionieren" + info "→ System ist grundsätzlich funktionsfähig" + else + error "❌ Alle Services fehlgeschlagen - System möglicherweise nicht funktionsfähig" + fi } -# =========================== SYSTEM-TEST =========================== +# =========================== ROBUSTE SYSTEM-TESTS =========================== test_application() { - log "=== SYSTEM-TEST ===" + log "=== UMFASSENDE SYSTEM-TESTS ===" - progress "Teste HTTPS-Verbindung..." + local test_errors=0 + local test_warnings=0 - # Warte auf Service-Start - local max_attempts=30 + # Test 1: Service-Status prüfen + progress "Teste Service-Status..." + if systemctl is-active --quiet "$HTTPS_SERVICE_NAME"; then + success "✅ HTTPS-Service ist aktiv" + else + warning "⚠️ HTTPS-Service ist nicht aktiv" + ((test_warnings++)) + + # Debug-Informationen + info "Service-Status Debug:" + systemctl status "$HTTPS_SERVICE_NAME" --no-pager -l || true + fi + + # Test 2: Port-Verfügbarkeit + progress "Teste Port-Verfügbarkeit..." + if ss -tlnp | grep -q ":443 "; then + success "✅ Port 443 ist geöffnet" + else + warning "⚠️ Port 443 ist nicht geöffnet" + ((test_warnings++)) + fi + + # Test 3: HTTPS-Verbindung (robust mit mehreren Methoden) + progress "Teste HTTPS-Verbindung (robust)..." + + local max_attempts=20 local attempt=1 + local connection_successful=false while [ $attempt -le $max_attempts ]; do - if curl -k -s --connect-timeout 5 "$HTTPS_URL" >/dev/null 2>&1; then - success "✅ HTTPS-Backend erreichbar unter $HTTPS_URL" + # Methode 1: curl mit verschiedenen Optionen + if curl -k -s --connect-timeout 3 --max-time 8 "$HTTPS_URL" >/dev/null 2>&1; then + connection_successful=true + break + fi + + # Methode 2: wget als Fallback + if command -v wget >/dev/null 2>&1; then + if wget -q --no-check-certificate --timeout=3 --tries=1 "$HTTPS_URL" -O /dev/null 2>/dev/null; then + connection_successful=true + break + fi + fi + + # Methode 3: openssl s_client als direkter Test + if echo "GET / HTTP/1.0" | openssl s_client -connect localhost:443 -quiet 2>/dev/null | grep -q "HTTP"; then + connection_successful=true break fi progress "Warte auf HTTPS-Backend... ($attempt/$max_attempts)" - sleep 2 + sleep 3 ((attempt++)) done - if [ $attempt -gt $max_attempts ]; then + if [ "$connection_successful" = true ]; then + success "✅ HTTPS-Backend erreichbar unter $HTTPS_URL" + + # Erweiterte Verbindungstests + progress "Führe erweiterte HTTPS-Tests durch..." + + # Test Antwortzeit + local response_time=$(curl -k -s -w "%{time_total}" -o /dev/null "$HTTPS_URL" 2>/dev/null || echo "timeout") + if [ "$response_time" != "timeout" ]; then + info "🕐 HTTPS Antwortzeit: ${response_time}s" + + # Bewerte Antwortzeit + if [ "$(echo "$response_time < 2.0" | bc 2>/dev/null || echo "0")" -eq 1 ]; then + success "✅ Gute Antwortzeit" + elif [ "$(echo "$response_time < 5.0" | bc 2>/dev/null || echo "0")" -eq 1 ]; then + info "ℹ️ Akzeptable Antwortzeit" + else + warning "⚠️ Langsame Antwortzeit" + ((test_warnings++)) + fi + fi + + # Test HTTP-Status + local http_status=$(curl -k -s -o /dev/null -w "%{http_code}" "$HTTPS_URL" 2>/dev/null || echo "000") + if [ "$http_status" = "200" ]; then + success "✅ HTTP Status 200 OK" + else + info "ℹ️ HTTP Status: $http_status (möglicherweise Redirect oder Login-Seite)" + fi + + else error "❌ HTTPS-Backend nicht erreichbar nach $max_attempts Versuchen" + ((test_errors++)) + + # Debugging-Informationen + info "HTTPS-Debug Informationen:" + netstat -tlnp | grep ":443" || info "Port 443 nicht gefunden" + ss -tlnp | grep ":443" || info "Port 443 nicht in ss gefunden" fi - # Teste SSL-Zertifikat - progress "Teste SSL-Zertifikat..." - if openssl s_client -connect localhost:443 -servername localhost /dev/null | openssl x509 -noout -text >/dev/null 2>&1; then - success "✅ SSL-Zertifikat gültig" + # Test 4: SSL-Zertifikat (erweitert) + progress "Teste SSL-Zertifikat (erweitert)..." + + # Methode 1: openssl s_client + if echo | openssl s_client -connect localhost:443 -servername localhost 2>/dev/null | openssl x509 -noout -text >/dev/null 2>&1; then + success "✅ SSL-Zertifikat ist gültig" + + # Zertifikat-Details extrahieren + local cert_info=$(echo | openssl s_client -connect localhost:443 -servername localhost 2>/dev/null | openssl x509 -noout -subject -dates 2>/dev/null || echo "Nicht verfügbar") + info "📜 Zertifikat-Info: $cert_info" + else warning "⚠️ SSL-Zertifikat-Test fehlgeschlagen" + ((test_warnings++)) + + # Alternative: Teste ob Zertifikat-Dateien existieren + if [ -f "$APP_DIR/certs/localhost/localhost.crt" ] && [ -f "$APP_DIR/certs/localhost/localhost.key" ]; then + info "📁 SSL-Zertifikat-Dateien sind vorhanden" + + # Teste Zertifikat-Datei direkt + if openssl x509 -in "$APP_DIR/certs/localhost/localhost.crt" -noout -text >/dev/null 2>&1; then + success "✅ SSL-Zertifikat-Datei ist gültig" + else + warning "⚠️ SSL-Zertifikat-Datei ist ungültig" + ((test_warnings++)) + fi + fi fi - log "✅ System-Test abgeschlossen" + # Test 5: Python-Anwendung Import-Test + progress "Teste Python-Anwendung Import..." + + cd "$APP_DIR" 2>/dev/null || true + + # Setze Test-Umgebung + export FLASK_ENV=testing + export MYP_TESTING=1 + export PYTHONPATH="$APP_DIR:${PYTHONPATH:-}" + + if timeout 15 python3 -c " +import sys +import os +sys.path.insert(0, '$APP_DIR') +os.chdir('$APP_DIR') + +try: + import app + print('✅ Flask-App Import erfolgreich') + + # Test ob Flask-App-Objekt verfügbar + if hasattr(app, 'app'): + print('✅ Flask-App-Objekt verfügbar') + else: + print('⚠️ Flask-App-Objekt nicht gefunden') + +except Exception as e: + print(f'⚠️ App-Import-Problem: {e}') + exit(1) +" 2>&1; then + success "✅ Python-Anwendung kann erfolgreich importiert werden" + else + warning "⚠️ Python-Anwendung Import-Probleme (möglicherweise nicht kritisch)" + ((test_warnings++)) + fi + + cd "$CURRENT_DIR" 2>/dev/null || true + + # Test 6: Verzeichnisstruktur-Validierung + progress "Validiere Verzeichnisstruktur..." + + local structure_ok=true + local required_files=("$APP_DIR/app.py" "$APP_DIR/models.py" "$APP_DIR/requirements.txt") + local required_dirs=("$APP_DIR/static" "$APP_DIR/templates" "$APP_DIR/blueprints") + + for file in "${required_files[@]}"; do + if [ ! -f "$file" ]; then + warning "❌ Wichtige Datei fehlt: $file" + structure_ok=false + ((test_warnings++)) + fi + done + + for dir in "${required_dirs[@]}"; do + if [ ! -d "$dir" ]; then + warning "❌ Wichtiges Verzeichnis fehlt: $dir" + structure_ok=false + ((test_warnings++)) + fi + done + + if [ "$structure_ok" = true ]; then + success "✅ Verzeichnisstruktur vollständig" + fi + + # Zusammenfassung der Tests + log "📊 System-Test Zusammenfassung:" + log " ❌ Fehler: $test_errors" + log " ⚠️ Warnungen: $test_warnings" + + if [ $test_errors -eq 0 ] && [ $test_warnings -eq 0 ]; then + success "✅ Alle System-Tests erfolgreich - System vollständig funktionsfähig" + return 0 + elif [ $test_errors -eq 0 ]; then + warning "⚠️ System-Tests mit $test_warnings Warnungen abgeschlossen - System grundsätzlich funktionsfähig" + return 0 + else + error "❌ System-Tests mit $test_errors Fehlern fehlgeschlagen - System möglicherweise nicht funktionsfähig" + return 1 + fi } # =========================== AUFRÄUMEN =========================== @@ -1268,246 +1924,81 @@ show_menu() { # =========================== INSTALLATIONS-MODI =========================== install_dependencies_only() { - log "=== MODUS: ABHÄNGIGKEITEN INSTALLIEREN FÜR MANUELLES TESTEN ===" + log "=== MODUS: ROBUSTE ABHÄNGIGKEITEN-INSTALLATION FÜR MANUELLES TESTEN ===" + # Grundlegende System-Validierung check_root + check_system_resources check_debian_system check_internet_connection + # System-Konfiguration configure_hostname update_system configure_network_security + + # Core-Abhängigkeiten installieren install_python_dependencies install_nodejs_npm install_ssl_certificates install_python_packages + + # Anwendung deployen deploy_application install_npm_dependencies generate_ssl_certificate + # Services für manuelles Testen vorbereiten + install_systemd_services + enable_and_start_services + # Performance-Optimierungen auch für manuelles Testen optimize_webapp_performance optimize_static_assets - # Erweiterter Systemtest mit robuster Fehlerbehandlung - progress "Starte erweiterten Systemtest..." - - # 1. Teste kritische Python-Imports (außerhalb der App) - progress "Teste kritische Python-Module..." - local import_test_errors=0 - - # Core-Framework-Tests (robustere Version) - if python3 -c " -try: - import flask, werkzeug, jinja2 - print('✅ Core-Framework verfügbar') -except ImportError as e: - print('⚠️ Core-Framework Import-Problem: ' + str(e)) - exit(1) -" 2>&1; then - success "✅ Core-Framework erfolgreich getestet" + # Umfassende System-Tests + progress "Starte umfassende System-Tests..." + if test_application; then + success "✅ Alle System-Tests erfolgreich!" else - warning "⚠️ Core-Framework Import-Problem" - ((import_test_errors++)) + warning "⚠️ System-Tests mit Problemen - System möglicherweise eingeschränkt funktionsfähig" fi - # Datenbank-Tests (robustere Version) - if python3 -c " -try: - import sqlalchemy - print('✅ Datenbank-Module verfügbar') -except ImportError as e: - print('⚠️ Datenbank-Module Import-Problem: ' + str(e)) - exit(1) -" 2>&1; then - success "✅ Datenbank-Module erfolgreich getestet" - else - warning "⚠️ Datenbank-Module Import-Problem" - ((import_test_errors++)) - fi + # Cleanup + cleanup_old_files - # Security-Tests (robustere Version) - if python3 -c " -try: - import bcrypt, cryptography - print('✅ Security-Module verfügbar') -except ImportError as e: - print('⚠️ Security-Module Import-Problem: ' + str(e)) - exit(1) -" 2>&1; then - success "✅ Security-Module erfolgreich getestet" - else - warning "⚠️ Security-Module Import-Problem" - ((import_test_errors++)) - fi - - # 2. Teste App-Struktur - progress "Validiere App-Struktur..." - local structure_errors=0 - - required_files=( - "$APP_DIR/app.py" - "$APP_DIR/models.py" - "$APP_DIR/requirements.txt" - "$APP_DIR/config" - "$APP_DIR/blueprints" - "$APP_DIR/utils" - "$APP_DIR/static" - "$APP_DIR/templates" - ) - - for required_file in "${required_files[@]}"; do - if [ ! -e "$required_file" ]; then - warning "⚠️ Fehlende App-Komponente: $required_file" - ((structure_errors++)) - fi - done - - # 3. Teste Datenbank-Initialisierung (sicher) - progress "Teste Datenbank-Grundfunktionen..." - - cd "$APP_DIR" - if python3 -c " -import sys -import os -sys.path.insert(0, os.getcwd()) - -try: - # Sichere Datenbank-Initialisierung testen - from models import init_database - print('✅ Datenbank-Initialisierung verfügbar') - - # Test SQLite-Verbindung - import sqlite3 - import tempfile - with tempfile.NamedTemporaryFile(suffix='.db') as tmp: - conn = sqlite3.connect(tmp.name) - conn.execute('CREATE TABLE test (id INTEGER)') - conn.close() - print('✅ SQLite-Funktionalität verfügbar') - -except Exception as e: - print(f'⚠️ Datenbank-Test-Problem: {str(e)}') - exit(1) -" 2>/dev/null; then - log "✅ Datenbank-Grundfunktionen funktionieren" - else - warning "⚠️ Datenbank-Grundfunktionen haben Probleme" - ((import_test_errors++)) - fi - - # 4. Teste Flask-App-Import (mit Timeout und sicherer Umgebung) - progress "Teste Flask-App-Import (sicher)..." - - # Erstelle sichere Test-Umgebung - export FLASK_ENV=testing - export MYP_TESTING=1 - - # Robuste PYTHONPATH-Konfiguration - if [ -z "${PYTHONPATH:-}" ]; then - export PYTHONPATH="$APP_DIR" - else - export PYTHONPATH="$APP_DIR:$PYTHONPATH" - fi - - if timeout 30 python3 -c " -import sys -import os - -# Sichere Python-Pfad-Konfiguration -current_dir = os.getcwd() -app_dir = '$APP_DIR' - -# Pfade hinzufügen -if current_dir not in sys.path: - sys.path.insert(0, current_dir) -if app_dir not in sys.path: - sys.path.insert(0, app_dir) - -# Setze Test-Umgebung -os.environ['FLASK_ENV'] = 'testing' -os.environ['MYP_TESTING'] = '1' - -try: - # Minimaler Import-Test - import app - print('✅ App-Modul erfolgreich importiert') - - # Test ob Flask-App-Objekt verfügbar ist - if hasattr(app, 'app'): - print('✅ Flask-App-Objekt verfügbar') - else: - print('⚠️ Flask-App-Objekt nicht gefunden') - sys.exit(1) - -except ImportError as ie: - print('❌ Import-Fehler: ' + str(ie)) - sys.exit(1) -except Exception as e: - print('⚠️ App-Import-Problem: ' + str(e)) - sys.exit(1) -" 2>&1; then - success "✅ Flask-App kann erfolgreich importiert werden" - else - warning "⚠️ Flask-App-Import hat Probleme - möglicherweise fehlende optionale Abhängigkeiten" - warning " → Das ist normal für minimale Installation - App sollte trotzdem funktionieren" - - # Zusätzliche Diagnose bei Import-Problemen - progress "Führe erweiterte Diagnose durch..." - - # Prüfe ob Python-Module grundsätzlich findbar sind - if python3 -c "import sys; print('Python-Pfad:', sys.path)" 2>/dev/null; then - info "✅ Python-System funktioniert grundsätzlich" - fi - - # Prüfe ob App-Verzeichnis korrekt ist - if [ -f "$APP_DIR/app.py" ]; then - info "✅ App-Datei gefunden: $APP_DIR/app.py" - else - warning "❌ App-Datei nicht gefunden: $APP_DIR/app.py" - fi - - # Zeige installierte Python-Pakete - if command -v pip3 >/dev/null 2>&1; then - info "📋 Installierte Kernpakete:" - pip3 list | grep -E "(Flask|Werkzeug|SQLAlchemy|bcrypt)" | head -5 || info " Keine Kernpakete sichtbar" - fi - fi - - cd "$CURRENT_DIR" - - # 5. Zusammenfassung - local total_errors=$((import_test_errors + structure_errors)) - - if [ $total_errors -eq 0 ]; then - success "✅ Alle Systemtests erfolgreich - System vollständig funktionsfähig" - elif [ $total_errors -le 2 ]; then - warning "⚠️ $total_errors kleinere Probleme gefunden - System grundsätzlich funktionsfähig" - info "→ Fehlende Komponenten sind meist optionale Abhängigkeiten" - else - error "❌ $total_errors Probleme gefunden - System möglicherweise nicht vollständig funktionsfähig" - fi - - success "✅ Abhängigkeiten-Installation abgeschlossen!" - info "Das System ist bereit für manuelle Tests und Entwicklung" - info "Hostname wurde auf 'raspberrypi' gesetzt" - info "HTTPS-Backend kann manuell gestartet werden mit:" - info " cd /opt/myp && python3 app.py" + success "✅ Robuste Abhängigkeiten-Installation abgeschlossen!" + log "📋 Installation Zusammenfassung:" + log " 🖥️ Hostname: raspberrypi" + log " 🐍 Python-Umgebung: Vollständig konfiguriert" + log " 🌐 Node.js: Installiert und optimiert" + log " 🔒 SSL-Zertifikate: Generiert und konfiguriert" + log " 📁 Anwendung: In $APP_DIR deployed" + log " ⚡ Performance: Optimiert für Raspberry Pi" + log " 🔧 Services: Installiert und gestartet" + info "" + info "🚀 System bereit für manuelle Tests und Entwicklung!" + info "🌐 HTTPS-Backend sollte verfügbar sein: $HTTPS_URL" + info "⚙️ Manuelle App-Start Alternative: cd /opt/myp && python3 app.py" } install_full_production_system() { - log "=== MODUS: VOLLSTÄNDIGE KIOSK-INSTALLATION MIT REMOTE-ZUGANG ===" + log "=== MODUS: VOLLSTÄNDIGE ROBUSTE KIOSK-INSTALLATION MIT REMOTE-ZUGANG ===" + # Umfassende System-Validierung check_root + check_system_resources check_debian_system check_internet_connection - # Hostname zuerst setzen + # System-Grundkonfiguration configure_hostname - # Führe zuerst Abhängigkeiten-Installation durch (falls noch nicht geschehen) + # Intelligente Abhängigkeiten-Installation if [ ! -d "$APP_DIR" ] || [ ! -f "$APP_DIR/app.py" ]; then - warning "Anwendung noch nicht deployed - führe Abhängigkeiten-Installation durch..." + warning "Anwendung noch nicht deployed - führe robuste Abhängigkeiten-Installation durch..." + + # Vollständige Basis-Installation update_system configure_network_security install_python_dependencies @@ -1518,7 +2009,8 @@ install_full_production_system() { install_npm_dependencies generate_ssl_certificate else - # Netzwerk-Sicherheit auch bei bestehender Installation konfigurieren + info "Anwendung bereits deployed - überspringe Basis-Installation" + # Trotzdem Netzwerk-Sicherheit aktualisieren configure_network_security fi @@ -1530,34 +2022,60 @@ install_full_production_system() { optimize_webapp_performance optimize_static_assets - # Remote-Zugang konfigurieren + # Remote-Zugang konfigurieren (robust) install_remote_access configure_firewall - # Kiosk-Benutzer und Autologin konfigurieren + # Kiosk-System konfigurieren create_kiosk_user configure_autologin configure_kiosk_autostart - # Services installieren und aktivieren + # Services installieren und aktivieren (robust) install_systemd_services enable_and_start_services - # System-Test - test_application - test_remote_access + # Umfassende System-Tests + progress "Führe umfassende Produktions-System-Tests durch..." + local system_tests_passed=true + + if test_application; then + success "✅ Anwendungs-Tests erfolgreich" + else + warning "⚠️ Anwendungs-Tests mit Problemen" + system_tests_passed=false + fi + + if test_remote_access; then + success "✅ Remote-Zugang-Tests erfolgreich" + else + warning "⚠️ Remote-Zugang-Tests mit Problemen" + system_tests_passed=false + fi # Aufräumen cleanup_old_files - success "✅ Vollständige Kiosk-Installation abgeschlossen!" - info "Das System ist vollständig konfiguriert:" - info " 🖥️ Hostname: raspberrypi" - info " 🖥️ Automatischer Kiosk-Modus beim Boot" - info " 📡 SSH: ssh user@ (Passwort: raspberry)" - info " 🖥️ RDP: :3389 (Benutzer: root, Passwort: 744563017196A)" - info " 🔒 Firewall: 192.168.0.0/16 + localhost + raspberrypi + m040tbaraspi001" - warning "⚠️ Neustart erforderlich für automatischen Kiosk-Start: sudo reboot" + # Finale Status-Bewertung + if [ "$system_tests_passed" = true ]; then + success "✅ Vollständige robuste Kiosk-Installation erfolgreich abgeschlossen!" + else + warning "⚠️ Vollständige Kiosk-Installation mit Einschränkungen abgeschlossen" + info "→ Grundfunktionalität sollte verfügbar sein, manuelle Überprüfung empfohlen" + fi + + log "📋 Vollständige Installation Zusammenfassung:" + log " 🖥️ Hostname: raspberrypi" + log " 🔐 Kiosk-Modus: Konfiguriert (startet beim nächsten Boot)" + log " 📡 SSH-Zugang: user:raspberry (Port 22)" + log " 🖥️ RDP-Zugang: root:744563017196A (Port 3389)" + log " 🔒 Firewall: Konfiguriert und aktiv" + log " ⚡ Performance: Optimiert für Raspberry Pi" + log " 🌐 HTTPS-Backend: $HTTPS_URL" + log " 🛡️ Sicherheit: IPv6 deaktiviert, erweiterte Netzwerk-Sicherheit" + info "" + success "🚀 Produktionssystem vollständig einsatzbereit!" + warning "⚠️ NEUSTART ERFORDERLICH für automatischen Kiosk-Start: sudo reboot" } # =========================== RDP & SSH ZUGANG =========================== @@ -1578,6 +2096,26 @@ install_remote_access() { useradd -m -s /bin/bash user || error "Kann SSH-Benutzer nicht erstellen" echo "user:raspberry" | chpasswd || error "Kann Passwort für SSH-Benutzer nicht setzen" usermod -aG sudo user 2>/dev/null || true + + # pip-Konfiguration für SSH-Benutzer + mkdir -p "/home/user/.pip" 2>/dev/null || true + cat > "/home/user/.pip/pip.conf" << 'EOF' +[global] +break-system-packages = true +trusted-host = pypi.org + pypi.python.org + files.pythonhosted.org +timeout = 60 +retries = 3 + +[install] +break-system-packages = true +trusted-host = pypi.org + pypi.python.org + files.pythonhosted.org +EOF + chown "user:user" "/home/user/.pip/pip.conf" 2>/dev/null || true + log "✅ SSH-Benutzer 'user' erstellt mit Passwort 'raspberry'" else info "SSH-Benutzer 'user' existiert bereits"