Initial commit
Dieser Commit ist enthalten in:
206
main.py
Normale Datei
206
main.py
Normale Datei
@ -0,0 +1,206 @@
|
||||
#!/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()
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren