207 Zeilen
7.6 KiB
Python
207 Zeilen
7.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
SkillMate - Windows-optimierter Haupteinstiegspunkt
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
import time
|
|
import webbrowser
|
|
import signal
|
|
import atexit
|
|
import argparse
|
|
from pathlib import Path
|
|
from typing import Optional, List
|
|
|
|
class SkillMateStarter:
|
|
def __init__(self, mode: str = 'dev'):
|
|
self.processes = []
|
|
self.base_dir = Path(__file__).parent.absolute()
|
|
self.mode = mode # 'dev' oder 'prod'
|
|
# Ports (Backend/Frontend/Admin)
|
|
self.backend_port = int(os.environ.get('PORT', '3004'))
|
|
self.frontend_port = 5173
|
|
self.admin_port = 5174
|
|
|
|
# Registriere Cleanup-Handler
|
|
atexit.register(self.cleanup)
|
|
if sys.platform != 'win32':
|
|
signal.signal(signal.SIGINT, self.signal_handler)
|
|
signal.signal(signal.SIGTERM, self.signal_handler)
|
|
|
|
def signal_handler(self, signum, frame):
|
|
"""Handle Strg+C und andere Signale"""
|
|
print("\n\n🛑 SkillMate wird beendet...")
|
|
self.cleanup()
|
|
sys.exit(0)
|
|
|
|
def cleanup(self):
|
|
"""Beende alle gestarteten Prozesse"""
|
|
for process in self.processes:
|
|
try:
|
|
if process.poll() is None: # Prozess läuft noch
|
|
if sys.platform == 'win32':
|
|
subprocess.call(['taskkill', '/F', '/T', '/PID', str(process.pid)],
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL)
|
|
else:
|
|
process.terminate()
|
|
process.wait(timeout=5)
|
|
except:
|
|
pass
|
|
# Sicherheitshalber Ports freigeben, falls noch Prozesse hängen
|
|
if sys.platform == 'win32':
|
|
for p in [self.backend_port, self.frontend_port, self.admin_port]:
|
|
self._free_port_windows(p)
|
|
|
|
def _popen_new_console(self, workdir: Path, command: str) -> Optional[subprocess.Popen]:
|
|
try:
|
|
creationflag = getattr(subprocess, 'CREATE_NEW_CONSOLE', 0x00000010)
|
|
p = subprocess.Popen(
|
|
["cmd", "/k", command],
|
|
cwd=str(workdir),
|
|
creationflags=creationflag,
|
|
)
|
|
self.processes.append(p)
|
|
return p
|
|
except Exception as e:
|
|
print(f"❌ Konnte Konsole nicht starten: {e}")
|
|
return None
|
|
|
|
def _free_port_windows(self, port: int):
|
|
"""Versucht Prozesse auf Port zu beenden (Windows)."""
|
|
try:
|
|
# Finde Zeilen mit Port
|
|
out = subprocess.check_output(f'netstat -ano | findstr :{port}', shell=True, text=True, stderr=subprocess.DEVNULL)
|
|
pids: List[str] = []
|
|
for line in out.splitlines():
|
|
parts = line.split()
|
|
if parts:
|
|
pid = parts[-1]
|
|
if pid.isdigit():
|
|
pids.append(pid)
|
|
# Einzigartige PIDs killen
|
|
for pid in sorted(set(pids)):
|
|
try:
|
|
subprocess.call(["taskkill", "/PID", pid, "/F"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
except Exception:
|
|
pass
|
|
except subprocess.CalledProcessError:
|
|
# Kein Treffer → Port frei
|
|
pass
|
|
|
|
def check_node_npm(self):
|
|
"""Prüfe ob Node.js und npm installiert sind"""
|
|
try:
|
|
# Windows-spezifische Prüfung
|
|
if sys.platform == 'win32':
|
|
subprocess.run(["node", "--version"],
|
|
capture_output=True,
|
|
check=True,
|
|
shell=True)
|
|
subprocess.run(["npm", "--version"],
|
|
capture_output=True,
|
|
check=True,
|
|
shell=True)
|
|
else:
|
|
subprocess.run(["node", "--version"], capture_output=True, check=True)
|
|
subprocess.run(["npm", "--version"], capture_output=True, check=True)
|
|
return True
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
print("❌ Node.js und npm müssen installiert sein!")
|
|
print(" Bitte installieren Sie Node.js von: https://nodejs.org/")
|
|
return False
|
|
|
|
def start_services_windows(self):
|
|
"""Starte alle Services in Windows mit separaten CMD-Fenstern"""
|
|
print("🚀 Starte SkillMate Services...")
|
|
|
|
# Ports freimachen (wenn vorherige Läufe hingen blieben)
|
|
for p in [self.backend_port, self.frontend_port, self.admin_port]:
|
|
self._free_port_windows(p)
|
|
|
|
# Backend
|
|
backend_dir = self.base_dir / "backend"
|
|
if self.mode == 'dev':
|
|
self._popen_new_console(backend_dir, "npm run dev")
|
|
else:
|
|
# Produktionsstart: Build & Start
|
|
self._popen_new_console(backend_dir, "cmd /c npm run build && npm start")
|
|
|
|
# Warte bis Backend bereit ist
|
|
print(" Warte auf Backend...")
|
|
time.sleep(5)
|
|
|
|
# Frontend
|
|
frontend_dir = self.base_dir / "frontend"
|
|
if self.mode == 'dev':
|
|
self._popen_new_console(frontend_dir, f"npm run dev -- --port {self.frontend_port}")
|
|
else:
|
|
self._popen_new_console(frontend_dir, f"cmd /c npm run build && npm run preview -- --port {self.frontend_port}")
|
|
|
|
# Admin Panel (optional)
|
|
admin_dir = self.base_dir / "admin-panel"
|
|
if admin_dir.exists():
|
|
if self.mode == 'dev':
|
|
self._popen_new_console(admin_dir, f"npm run dev -- --port {self.admin_port}")
|
|
else:
|
|
self._popen_new_console(admin_dir, f"cmd /c npm run build && npm run preview -- --port {self.admin_port}")
|
|
|
|
print("\n✨ SkillMate läuft!")
|
|
print("\n📍 Zugriff:")
|
|
print(f" - Frontend: http://localhost:{self.frontend_port}")
|
|
print(f" - Backend: http://localhost:{self.backend_port}")
|
|
if admin_dir.exists():
|
|
print(f" - Admin: http://localhost:{self.admin_port}")
|
|
|
|
# Öffne Frontend im Browser
|
|
time.sleep(3)
|
|
webbrowser.open(f"http://localhost:{self.frontend_port}")
|
|
|
|
print("\n⚡ Schließen Sie dieses Fenster, um SkillMate zu beenden")
|
|
|
|
|
|
def run(self):
|
|
"""Hauptausführungsmethode"""
|
|
print("🎯 SkillMate wird gestartet...\n")
|
|
|
|
# Prüfe Voraussetzungen
|
|
if not self.check_node_npm():
|
|
return False
|
|
|
|
# Windows-spezifischer Start
|
|
if sys.platform == 'win32':
|
|
self.start_services_windows()
|
|
# Halte das Hauptfenster offen
|
|
try:
|
|
input("\nDrücken Sie Enter zum Beenden...")
|
|
except KeyboardInterrupt:
|
|
pass
|
|
else:
|
|
print("❌ Dieses Skript ist für Windows optimiert.")
|
|
print(" Nutzen Sie 'python main.py' für andere Systeme.")
|
|
return False
|
|
|
|
return True
|
|
|
|
def main():
|
|
"""Haupteinstiegspunkt"""
|
|
parser = argparse.ArgumentParser(description='SkillMate Starter')
|
|
parser.add_argument('--mode', choices=['dev', 'prod'], default='dev', help='Startmodus: dev oder prod')
|
|
args = parser.parse_args()
|
|
|
|
starter = SkillMateStarter(mode=args.mode)
|
|
|
|
try:
|
|
success = starter.run()
|
|
if not success:
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
print(f"\n❌ Unerwarteter Fehler: {e}")
|
|
starter.cleanup()
|
|
sys.exit(1)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|