diff --git a/.gitignore b/.gitignore index fbed6dc..18b5320 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,17 @@ Projet IA.pdf Multi IA Personnel & familliale.pdf WF ATTACHEMENT STIC.pdf gitea cmd.md +consignes.txt +history.json +# Environnement et clés secrètes +.env + +# Dossiers dynamiques d'exécution +/logs/ +/sessions/ + +# Fichiers de données locales et clés de session +history.json +*.sesskey +__pycache__/ +*.pyc \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5780f00 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM python:3.13-slim + +WORKDIR /app + +# Installation des dépendances système de base +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Copie et installation des bibliothèques Python +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copie du code source +COPY . . + +EXPOSE 5001 + +CMD ["python", "main.py"] \ No newline at end of file diff --git a/auth.py b/auth.py new file mode 100644 index 0000000..0c75246 --- /dev/null +++ b/auth.py @@ -0,0 +1,35 @@ +import re +import pyotp +import hashlib +import hmac + +def validate_password_strength(password): + """Vérifie que le mot de passe respecte les critères de sécurité stricts.""" + if len(password) < 12: + return "Le mot de passe doit contenir au moins 12 caractères." + if not re.search(r"[A-Z]", password): + return "Le mot de passe doit contenir au moins une majuscule." + if not re.search(r"[a-z]", password): + return "Le mot de passe doit contenir au moins une minuscule." + if not re.search(r"[0-9]", password): + return "Le mot de passe doit contenir au moins un chiffre." + if not re.search(r"[!@#$%^&*(),.?\":{}|<>]", password): + return "Le mot de passe doit contenir au moins un caractère spécial." + return None + +def hash_password(password): + """Hachage sécurisé du mot de passe.""" + return hashlib.sha256(password.encode()).hexdigest() + +def verify_password(stored_hash, provided_password): + """Vérifie un mot de passe par rapport à son haché.""" + return hmac.compare_digest(stored_hash, hash_password(provided_password)) + +def generate_totp_secret(): + """Génère une clé secrète pour le 2FA.""" + return pyotp.random_base32() + +def verify_totp(secret, code): + """Vérifie le code TOTP fourni par l'application d'authentification.""" + totp = pyotp.TOTP(secret) + return totp.verify(code) \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..302a196 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +services: + aethas38-core: + build: . + container_name: aethas38-multi-ia + restart: unless-stopped + ports: + - "5001:5001" + environment: + - TZ=Europe/Paris + env_file: + - .env + volumes: + - ./sessions:/app/sessions + - ./logs:/app/logs + - ./history.json:/app/history.json + extra_hosts: + - "host.docker.internal:host-gateway" + networks: + - dockge-network + +networks: + dockge-network: + external: true \ No newline at end of file diff --git a/main.py b/main.py index af5558e..c4ef515 100644 --- a/main.py +++ b/main.py @@ -9,6 +9,26 @@ from dotenv import load_dotenv load_dotenv() +from auth import validate_password_strength, hash_password, verify_password, generate_totp_secret, verify_totp + +# Exemple de structure pour les routes de connexion (Login / Setup initial) +@rt('/login') +def get(): + return Title("Connexion - AETHAS38"), Body( + Div( + H2("AETHAS 38 - Sécurité", style="color:#00e5ff; margin-bottom:20px;"), + Form( + Input(type="email", name="email", placeholder="E-mail", cls="input-box mb-3", required=True), + Input(type="password", name="password", placeholder="Mot de passe", cls="input-box mb-3", required=True), + Input(type="text", name="totp_code", placeholder="Code 2FA (6 chiffres)", cls="input-box mb-3", required=True), + Button("Se connecter", type="submit", cls="send-btn w-full"), + hx_post="/auth/login", hx_target="#auth-response" + ), + Div(id="auth-response", style="color:red; margin-top:10px;"), + cls="sidebar", style="margin: auto; width: 400px; height: auto; border-radius: 10px; margin-top: 15vh;" + ), style="background-color: #0f172a; height: 100vh; display: flex;" + ) + # --- 0. INITIALISATION DES DOSSIERS (BDD & LOGS) --- os.makedirs("sessions", exist_ok=True) os.makedirs("logs", exist_ok=True) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f17a7c0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +python-fasthtml +requests +python-dotenv +pyotp +qrcode +psutil \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..766ac50 --- /dev/null +++ b/setup.py @@ -0,0 +1,148 @@ +import os +import platform +import re +import pyotp +import qrcode +import psutil +from dotenv import load_dotenv + +def clear_screen(): + os.system('cls' if platform.system() == 'Windows' else 'clear') + +def check_hardware(): + print("=" * 60) + print(" 🖥️ ANALYSE DU MATÉRIEL ET LOGICIEL DU SERVEUR") + print("=" * 60) + + cpu_count = psutil.cpu_count(logical=True) + ram_gb = round(psutil.virtual_memory().total / (1024**3), 2) + os_info = f"{platform.system()} {platform.release()}" + + print(f"• Système d'exploitation : {os_info}") + print(f"• Cœurs CPU logiques : {cpu_count}") + print(f"• Mémoire RAM totale : {ram_gb} Go") + + # Évaluation pour phuzzy/minecraft + print("\n[Vérification du modèle lourd : phuzzy/minecraft]") + if ram_gb < 16: + print("⚠️ AVERTISSEMENT CRITIQUE : Votre serveur dispose de moins de 16 Go de RAM/VRAM.") + print(" Le modèle 'phuzzy/minecraft' requiert des ressources importantes.") + print(" Risque de ralentissements sévères ou de plantage par manque de mémoire (OOM).") + else: + print("✅ La configuration matérielle semble suffisante pour exécuter les modèles locaux (Qwen & Phuzzy).") + print("-" * 60 + "\n") + +def validate_password(password): + if len(password) < 12: + return "Le mot de passe doit contenir au moins 12 caractères." + if not re.search(r"[A-Z]", password): + return "Le mot de passe doit contenir au moins une majuscule." + if not re.search(r"[a-z]", password): + return "Le mot de passe doit contenir au moins une minuscule." + if not re.search(r"[0-9]", password): + return "Le mot de passe doit contenir au moins un chiffre." + if not re.search(r"[!@#$%^&*(),.?\":{}|<>]", password): + return "Le mot de passe doit contenir au moins un caractère spécial." + return None + +def configure_admin(): + print("=" * 60) + print(" 🔑 CRÉATION DU COMPTE ADMINISTRATEUR & SÉCURITÉ 2FA") + print("=" * 60) + + email = input("Entrez l'e-mail administrateur : ").strip() + + while True: + password = input("Définissez le mot de passe (min. 12 caractères, majuscules, minuscules, chiffres, symboles) : ").strip() + err = validate_password(password) + if err: + print(f"❌ Erreur : {err}") + else: + print("✅ Mot de passe valide.") + break + + # Génération 2FA (TOTP) + totp_secret = pyotp.random_base32() + totp_uri = pyotp.totp.TOTP(totp_secret).provisioning_uri(name=email, issuer_name="AETHAS38-Hub") + + print("\n--- DOUBLE AUTHENTIFICATION (2FA) ---") + print("Scannez le QR code ci-dessous avec votre application (Google Authenticator, KeePassXC, etc.) :") + + # Génération d'un QR code dans le terminal en texte ASCII + qr = qrcode.QRCode() + qr.add_data(totp_uri) + qr.make(fit=True) + qr.print_ascii(invert=True) + + print(nis := f"Clé secrète manuelle (si besoin) : {totp_secret}") + print("-" * 60 + "\n") + return email, password, totp_secret + +def configure_api_keys(): + print("=" * 60) + print(" 🌐 CONFIGURATION DES CLÉS API (PAR SPÉCIALITÉ)") + print("=" * 60) + print("Laissez vide si vous ne possédez pas la clé pour l'instant.\n") + + keys = {} + + print("1. [CODAGE & RAISONNEMENT] - OpenRouter & Management Key") + print(" -> Où la trouver : https://openrouter.ai/settings/keys") + keys["OPENROUTER_API_KEY"] = input(" Entrez OPENROUTER_API_KEY : ").strip() + keys["OPENROUTER_MANAGEMENT_KEY"] = input(" Entrez OPENROUTER_MANAGEMENT_KEY (pour le suivi du budget) : ").strip() + + print("\n2. [CODAGE ULTRA-RAPIDE] - Groq") + print(" -> Où la trouver : https://console.groq.com/keys") + keys["GROQ_API_KEY"] = input(" Entrez GROQ_API_KEY : ").strip() + + print("\n3. [MULTIMODAL & CODE] - Google Gemini") + print(" -> Où la trouver : https://aistudio.google.com/app/apikey") + keys["GEMINI_API_KEY"] = input(" Entrez GEMINI_API_KEY : ").strip() + + print("\n4. [CODAGE AVANCÉ] - DeepSeek") + print(" -> Où la trouver : https://platform.deepseek.com/api_keys") + keys["DEEPSEEK_API_KEY"] = input(" Entrez DEEPSEEK_API_KEY : ").strip() + + print("\n5. [TEXTE & GÉNÉRAL] - Mistral AI") + print(" -> Où la trouver : https://console.mistral.ai/api-keys/") + keys["MISTRAL_API_KEY"] = input(" Entrez MISTRAL_API_KEY : ").strip() + + print("\n6. [INFRASTRUCTURE & IA EDGE] - Cloudflare") + print(" -> Où la trouver : Tableau de bord Cloudflare (Workers & Pages > AI)") + keys["CLOUDFLARE_ACCOUNT_ID"] = input(" Entrez CLOUDFLARE_ACCOUNT_ID : ").strip() + keys["CLOUDFLARE_API_TOKEN"] = input(" Entrez CLOUDFLARE_API_TOKEN : ").strip() + + print("\n7. [MODÈLES LIBRES] - HuggingFace") + print(" -> Où la trouver : https://huggingface.co/settings/tokens") + keys["HUGGINGFACE_API_KEY"] = input(" Entrez HUGGINGFACE_API_KEY : ").strip() + + print("-" * 60 + "\n") + return keys + +def save_env(keys, admin_data): + env_content = f"""# ========================================== +# AETHAS38 - CONFIGURATION AUTOMATISÉE +# ========================================== + +# Administrateur principal +ADMIN_EMAIL={admin_data[0]} +ADMIN_PASSWORD_HASH={admin_data[1]} +ADMIN_2FA_SECRET={admin_data[2]} + +# Clés API Distantes +""" + for k, v in keys.items(): + env_content += f"{k}={v}\n" + + with open(".env", "w", encoding="utf-8") as f: + f.write(env_content) + print("✅ Fichier .env généré avec succès !") + +if __name__ == "__main__": + clear_screen() + check_hardware() + admin_data = configure_admin() + keys = configure_api_keys() + save_env(keys, admin_data) + print("\n🎉 Installation et configuration initiale terminées avec succès !") + print("Vous pouvez maintenant lancer le service Dockge / Docker.") \ No newline at end of file