Files
ARC/orchestrator.py
2026-07-07 09:57:21 +02:00

195 lines
6.6 KiB
Python

import os
import sys
import asyncio
import subprocess
import httpx
current_dir = os.path.dirname(os.path.abspath(__file__))
backend_dir = os.path.join(current_dir, "backend")
if not os.path.exists(backend_dir):
print(f"❌ Erreur : Le dossier '{backend_dir}' est introuvable.")
sys.exit(1)
os.chdir(backend_dir)
sys.path.append(".")
try:
from app.core.config import settings
except ImportError as e:
print(f"❌ Erreur d'importation des settings : {e}")
print(f"Vérifie que ton dossier 'app' est bien dans '{backend_dir}'")
sys.exit(1)
COMPOSE_DIR = "."
NETWORK_NAME = "arc-network"
TOKEN_FILE = ".gitea_token"
PROJECTS = {
"infra": "arc-infra",
"ai": "arc-ai",
"app": "arc-app",
"sandbox": "arc-sandbox"
}
def run_command(command, cwd=None):
"""Lance une commande système de manière synchrone."""
print(f"🚀 Exécution : {' '.join(command) if isinstance(command, list) else command}")
try:
subprocess.run(command, cwd=cwd, check=True, capture_output=False)
return True
except subprocess.CalledProcessError as e:
print(f"❌ Erreur : {e}")
return False
async def wait_for_gitea(local_api_url):
"""Attend que l'API HTTP de Gitea soit pleinement initialisée."""
public_ping_url = f"{local_api_url}/version"
max_retries = 20
print("⏳ Attente du démarrage complet de Gitea et des migrations de la base de données...")
for i in range(max_retries):
try:
async with httpx.AsyncClient(verify=False) as client:
resp = await client.get(public_ping_url)
if resp.status_code == 200:
print("✅ Gitea est prêt et opérationnel.")
return True
except Exception:
pass
if i < max_retries - 1:
await asyncio.sleep(3)
raise Exception(f"Gitea n'a pas répondu sur {public_ping_url} après plusieurs tentatives.")
async def bootstrap_gitea_agent(settings_obj, local_api_url):
"""Crée un token d'accès d'application pour l'admin."""
if os.path.exists(TOKEN_FILE):
with open(TOKEN_FILE, "r") as f:
token = f.read().strip()
if token:
return token
token_url = f"{local_api_url}/users/{settings_obj.gitea_admin_user}/tokens"
async with httpx.AsyncClient(verify=False) as client:
resp = await client.post(
token_url,
auth=(settings_obj.gitea_admin_user, settings_obj.gitea_admin_password),
json={
"name": "dev-agent-token",
"scopes": ["all"]
}
)
if resp.status_code in [200, 201]:
token_data = resp.json()
token = token_data.get('token') or token_data.get('sha1')
if not token:
raise Exception(f"Format de réponse inconnu pour le token : {token_data}")
with open(TOKEN_FILE, "w") as f:
f.write(token)
return token
elif resp.status_code in [400, 409]:
print("⚠️ Le token existe déjà dans Gitea. Réinitialisation...")
subprocess.run([
"docker", "exec", "-u", "git", "git-arc",
"gitea", "admin", "token", "delete",
"--username", settings_obj.gitea_admin_user,
"--name", "dev-agent-token"
], capture_output=True)
return await bootstrap_gitea_agent(settings_obj, local_api_url)
else:
raise Exception(f"Échec création token : {resp.status_code} - {resp.text}")
async def main():
# 1. Réseau
print("--- ÉTAPE 1 : RÉSEAU ---")
create_net = subprocess.run(
["docker", "network", "create", NETWORK_NAME],
capture_output=True, text=True, shell=(os.name == 'nt')
)
if create_net.returncode == 0:
print(f"✅ Réseau '{NETWORK_NAME}' créé.")
else:
print(f"✅ Réseau '{NETWORK_NAME}' déjà présent ou géré.")
# 2. Infrastructure
print(f"\n--- ÉTAPE 2 : INFRASTRUCTURE (Projet: {PROJECTS['infra']}) ---")
infra_cmd = [
"docker", "compose",
"-p", PROJECTS["infra"],
"-f", f"{COMPOSE_DIR}/docker-compose-infra.yml",
"up", "-d"
]
if not run_command(infra_cmd):
sys.exit(1)
# 3. Provisioning Gitea
print("\n--- ÉTAPE 3 : PROVISIONING GITEA ---")
local_api_url = settings.gitea_api_url.replace("git-arc", "localhost")
# Étape critique : on attend d'abord que le service web et la DB soient OK
try:
await wait_for_gitea(local_api_url)
except Exception as e:
print(f"❌ Erreur critique de démarrage : {e}")
sys.exit(1)
print("👤 Création / Vérification du compte administrateur Gitea...")
result = subprocess.run([
"docker", "exec", "-u", "git", "git-arc",
"gitea", "admin", "user", "create",
"--username", settings.gitea_admin_user,
"--password", settings.gitea_admin_password,
"--email", "admin@arc.local",
"--admin"
], capture_output=True, text=True)
if result.returncode == 0:
print("👤 Compte administrateur créé avec succès.")
elif "user already exists" in result.stderr:
print("👤 Compte administrateur existant et valide.")
else:
print(f"⚠️ Note Gitea CLI : {result.stderr.strip()}")
try:
agent_token = await bootstrap_gitea_agent(settings, local_api_url)
os.environ["GITEA_TOKEN"] = agent_token
print("✅ Provisioning Gitea réussi, token généré.")
except Exception as e:
print(f"❌ Erreur critique de provisioning : {e}")
sys.exit(1)
# 4. IA
print(f"\n--- ÉTAPE 4 : IA (Projet: {PROJECTS['ai']}) ---")
ai_cmd = [
"docker", "compose",
"-p", PROJECTS["ai"],
"-f", f"{COMPOSE_DIR}/docker-compose-ai.yml",
"up", "-d"
]
if not run_command(ai_cmd):
sys.exit(1)
print(f"\n--- ÉTAPE 5 : Sandbox (Projet: {PROJECTS['sandbox']}) ---")
sandbox_cmd = [
"docker", "compose",
"-p", PROJECTS["sandbox"],
"-f", f"{COMPOSE_DIR}/docker-compose-sandbox.yml",
"up", "-d", "--build"
]
if not run_command(sandbox_cmd):
sys.exit(1)
# 5. Application
print(f"\n--- ÉTAPE 6 : APPLICATION (Projet: {PROJECTS['app']}) ---")
app_cmd = [
"docker", "compose",
"-p", PROJECTS["app"],
"-f", f"{COMPOSE_DIR}/docker-compose.yml",
"up", "-d", "--build"
]
run_command(app_cmd)
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\n🛑 Arrêt demandé par l'utilisateur.")