Files
Loki/internal/loki/facade_system.go
T
Claude a4f8bd3ec4 Lot 17 : interrupteurs système tenus par la façade (LOKI_SYSTEM=rust)
La façade sert /api/internet (moteur web, serveur Crawl4AI et sa clé
jamais rendue, joignabilité avec son cache de 30 s), /api/computer
(interrupteur du navigateur, binaire trouvé, vision), /api/apikey (clé de
complétion, repli sur config.env, adresse /v1 à coller dans un client
tiers) et /api/network (écoute du moteur) quand LOKI_SYSTEM=rust
(expérimental ; le Go reste le défaut).

Restent au Go derrière son API interne (facade_system.go) : le cache de
joignabilité de ses outils web (/internal/v1/web/reach/reset), le
Chromium des outils browser_* (/internal/v1/cdp/shutdown) et le
redémarrage qui applique une clé neuve quand la façade ne pilote pas le
moteur (/internal/v1/service/action). Le relais du tunnel garde l'hôte
et X-Forwarded-Proto du client.

Correction en passant : maskAPIKey paniquait sur une clé d'un octet, et
/api/apikey tombait à chaque lecture.

Parité : parity_system_test.go tire du Go 61 appels de route avec la
base et les sondes reçues par trois faux Crawl4AI après chacun, et huit
dispositions du dossier de Playwright, rejoués par tests/system.rs ; ce
qui reste au Go vérifié de bout en bout ; TestFacadeSysteme,
TestFacadeRelaisGardeHote et TestMaskAPIKeyCourte côté Go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
2026-10-15 18:51:58 +00:00

99 lines
3.3 KiB
Go

package loki
import (
"encoding/json"
"net/http"
"os"
"strings"
)
// facade_system.go — interrupteurs système tenus par la façade Rust
// (LOKI_SYSTEM=rust, lot 17 de la migration ; expérimental, le Go reste le
// défaut) : accès internet (/api/internet), pilotage du navigateur
// (/api/computer), clé de complétion (/api/apikey) et écoute réseau du moteur
// (/api/network).
//
// Les réglages restent des clés de la base. Ce qui vit dans ce processus reste
// ici, derrière l'API interne :
//
// - le cache de joignabilité de Crawl4AI des outils web, vidé quand l'URL, la
// clé ou le moteur changent (/internal/v1/web/reach/reset) ;
// - le Chromium piloté par les outils browser_*, fermé quand l'interrupteur
// se coupe (/internal/v1/cdp/shutdown) ;
// - le redémarrage du moteur qui applique une nouvelle clé, quand la façade
// ne pilote pas le moteur (/internal/v1/service/action).
const facadeSystemEnv = "LOKI_SYSTEM"
// facadeSystem : la façade tient-elle les interrupteurs système ?
func facadeSystem() bool {
return strings.EqualFold(strings.TrimSpace(os.Getenv(facadeSystemEnv)), "rust") &&
facadeAddr() != "" && internalToken() != ""
}
// facadeSystemRoutes : les routes que la façade sert alors (system.rs).
var facadeSystemRoutes = map[string]bool{
"/api/internet": true,
"/api/computer": true,
"/api/apikey": true,
"/api/network": true,
}
// handleInternalReachReset : POST → 204 — le prochain tour reteste Crawl4AI.
func handleInternalReachReset(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
sendJSON(w, http.StatusMethodNotAllowed, map[string]any{"error": "POST attendu"})
return
}
reachMu.Lock()
reachURL = ""
reachMu.Unlock()
w.WriteHeader(http.StatusNoContent)
}
// handleInternalCDPShutdown : POST → 204 — ferme le navigateur piloté.
func handleInternalCDPShutdown(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
sendJSON(w, http.StatusMethodNotAllowed, map[string]any{"error": "POST attendu"})
return
}
cdpShutdown()
w.WriteHeader(http.StatusNoContent)
}
// internalServiceAction : serviceAction, variable pour les tests.
var internalServiceAction = serviceAction
// handleInternalServiceAction : POST {action, if_active} → 200 {ran, error}.
// if_active : seulement si le moteur tourne (une clé neuve ne s'applique qu'au
// redémarrage, inutile de démarrer un moteur arrêté pour elle).
func handleInternalServiceAction(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
sendJSON(w, http.StatusMethodNotAllowed, map[string]any{"error": "POST attendu"})
return
}
var in struct {
Action string `json:"action"`
IfActive bool `json:"if_active"`
}
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
sendJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
return
}
switch in.Action {
case "start", "stop", "restart":
default:
sendJSON(w, http.StatusBadRequest, map[string]any{"error": "action inconnue : " + in.Action})
return
}
if in.IfActive && !serviceIsActive() {
sendJSON(w, http.StatusOK, map[string]any{"ran": false, "error": ""})
return
}
msg := ""
if err := internalServiceAction(in.Action); err != nil {
msg = err.Error()
}
sendJSON(w, http.StatusOK, map[string]any{"ran": true, "error": msg})
}