mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-12 01:37:06 +02:00
- module github.com/R0m1k3/Loki, cmd/loki, internal/loki (package loki) - LOKI_HOME, LOKI_MODEL_DIRS, LOKI_SERVICE, LOKI_DL_CONNS ; /etc/loki ; units loki-engine / loki-ui ; binaire et aide CLI - updateRepo pointe sur R0m1k3/Loki (l'auto-update ne tirera plus les binaires AJEAN amont) Conservé à l'identique : le domaine ajean.link (service de tunnel amont), les littéraux de migration 0.7.x (migrate_07.go), RELEASE_NOTES.md et LICENSE (historique et licence de l'amont). go build/vet/test : verts.
178 lines
6.0 KiB
Go
178 lines
6.0 KiB
Go
package loki
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// sys_network.go — « est-ce que ma machine est joignable depuis le réseau ? »
|
|
//
|
|
// Le moteur écoute à l'adresse HOST. Sous Windows, l'installation pose
|
|
// HOST=127.0.0.1 : le chat du navigateur marche (il passe par le serveur web
|
|
// d'Loki, sur la même machine), mais l'endpoint OpenAI :8080/v1 est INVISIBLE
|
|
// depuis le reste du réseau. Or c'est très exactement ce que les gens viennent
|
|
// chercher : brancher un logiciel tiers sur le modèle qui tourne dans le salon.
|
|
//
|
|
// Deux choses manquaient, et pas une seule :
|
|
// 1. HOST n'était réglable NULLE PART dans l'interface — il fallait connaître
|
|
// « loki edit » et savoir quoi y écrire.
|
|
// 2. Même à 0.0.0.0, le pare-feu Windows bloque les connexions entrantes tant
|
|
// qu'aucune règle n'autorise le port. Loki n'en posait aucune.
|
|
//
|
|
// D'où un seul interrupteur qui fait les deux, et qui DIT ce qu'il n'a pas pu
|
|
// faire (poser une règle de pare-feu exige les droits administrateur, que
|
|
// l'installation d'Loki ne réclame pas).
|
|
|
|
// firewallInert coupe tout pilotage du pare-feu. Positionné par les tests : une
|
|
// suite qui laisse une règle entrante derrière elle est un effet de bord
|
|
// inacceptable, et sur un runner élevé elle ouvrirait le port pour de bon.
|
|
// Déclaré ici, et non dans sys_firewall_windows.go, pour être LU sur les trois
|
|
// plateformes (sinon il n'est qu'écrit par les tests hors Windows, ce que
|
|
// staticcheck signale à juste titre comme une variable morte).
|
|
var firewallInert bool
|
|
|
|
// hostLocalOnly est l'adresse d'écoute « cette machine seulement ».
|
|
const hostLocalOnly = "127.0.0.1"
|
|
|
|
// hostAllInterfaces est l'adresse d'écoute « tout le réseau ».
|
|
const hostAllInterfaces = "0.0.0.0"
|
|
|
|
// engineHost renvoie l'adresse d'écoute configurée pour le moteur. Vide = le
|
|
// défaut de backend_serve.go, c'est-à-dire toutes les interfaces.
|
|
func engineHost() string {
|
|
h := strings.TrimSpace(ReadConfig()["HOST"])
|
|
if h == "" {
|
|
return hostAllInterfaces
|
|
}
|
|
return h
|
|
}
|
|
|
|
// lanExposed : le moteur écoute-t-il ailleurs que sur la boucle locale ?
|
|
func lanExposed() bool {
|
|
h := engineHost()
|
|
return h != hostLocalOnly && h != "::1" && !strings.EqualFold(h, "localhost")
|
|
}
|
|
|
|
// netStatus est ce que l'interface affiche, et ce que renvoie `loki network`.
|
|
type netStatus struct {
|
|
Exposed bool `json:"exposed"` // le moteur écoute-t-il sur le réseau
|
|
Host string `json:"host"` // adresse d'écoute effective
|
|
Port int `json:"port"` // port du moteur
|
|
URL string `json:"url"` // endpoint OpenAI à coller dans un logiciel tiers
|
|
Firewall string `json:"firewall"` // "ouvert", "ferme", "inconnu" (aucun pare-feu piloté)
|
|
Hint string `json:"hint"` // ce qu'il reste à faire à la main, ou ""
|
|
}
|
|
|
|
// networkStatus assemble l'état courant.
|
|
func networkStatus() netStatus {
|
|
st := netStatus{Exposed: lanExposed(), Host: engineHost(), Port: LLMPort()}
|
|
st.URL = fmt.Sprintf("http://%s:%d/v1", localIP(), st.Port)
|
|
st.Firewall = firewallState(st.Port)
|
|
if st.Exposed && st.Firewall == "ferme" {
|
|
st.Hint = firewallManualHint(st.Port)
|
|
}
|
|
return st
|
|
}
|
|
|
|
// setLANExposure écrit HOST et, à l'ouverture, tente de poser la règle de
|
|
// pare-feu. Elle renvoie l'état obtenu ; une règle non posée n'est PAS une
|
|
// erreur bloquante (l'utilisateur peut la poser lui-même, Hint le lui dit).
|
|
//
|
|
// Le service n'est pas redémarré ici : llama-server ne lit --host qu'au
|
|
// lancement, et c'est à l'appelant de choisir le moment (l'interface propose le
|
|
// redémarrage, la CLI le rappelle).
|
|
func setLANExposure(on bool) (netStatus, error) {
|
|
host := hostLocalOnly
|
|
if on {
|
|
host = hostAllInterfaces
|
|
}
|
|
if err := SetConfigKey("HOST", host); err != nil {
|
|
return netStatus{}, err
|
|
}
|
|
port := LLMPort()
|
|
switch {
|
|
case firewallInert:
|
|
// rien : voir la note sur firewallInert
|
|
case on:
|
|
_ = firewallOpen(port)
|
|
default:
|
|
_ = firewallClose(port)
|
|
}
|
|
return networkStatus(), nil
|
|
}
|
|
|
|
// cmdNetwork : `loki network [on|off|status]`.
|
|
func cmdNetwork(args []string) error {
|
|
sub := "status"
|
|
if len(args) > 0 {
|
|
sub = args[0]
|
|
}
|
|
switch sub {
|
|
case "on", "off":
|
|
st, err := setLANExposure(sub == "on")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if st.Exposed {
|
|
fmt.Printf("%s le moteur écoutera sur tout le réseau (%s)\n", green("[ok]"), st.URL)
|
|
} else {
|
|
fmt.Printf("%s le moteur n'écoutera plus que sur cette machine\n", green("[ok]"))
|
|
}
|
|
printNetHint(st)
|
|
fmt.Printf("%s redémarre le moteur pour appliquer : %s\n", dim("[info]"), bold("loki restart"))
|
|
return nil
|
|
case "status", "":
|
|
st := networkStatus()
|
|
state := dim("cette machine seulement")
|
|
if st.Exposed {
|
|
state = green("tout le réseau")
|
|
}
|
|
fmt.Printf("%s écoute: %s (HOST=%s, PORT=%d)\n", cyan("Réseau"), state, st.Host, st.Port)
|
|
switch st.Firewall {
|
|
case "ouvert":
|
|
fmt.Printf(" pare-feu : %s\n", green("port autorisé"))
|
|
case "ferme":
|
|
fmt.Printf(" pare-feu : %s\n", yellow("aucune règle pour ce port"))
|
|
default:
|
|
fmt.Printf(" pare-feu : %s\n", dim("non géré par Loki sur cette plateforme"))
|
|
}
|
|
if st.Exposed {
|
|
fmt.Printf(" endpoint OpenAI : %s\n", bold(st.URL))
|
|
}
|
|
printNetHint(st)
|
|
return nil
|
|
}
|
|
return fmt.Errorf("usage: loki network [on|off|status]")
|
|
}
|
|
|
|
func printNetHint(st netStatus) {
|
|
if st.Hint != "" {
|
|
fmt.Printf("%s %s\n", yellow("[attention]"), st.Hint)
|
|
}
|
|
}
|
|
|
|
// handleNetwork : GET l'état, POST {exposed:bool} pour basculer.
|
|
func handleNetwork(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == http.MethodPost {
|
|
var req struct {
|
|
Exposed *bool `json:"exposed"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
sendJSON(w, 400, map[string]any{"ok": false, "error": err.Error()})
|
|
return
|
|
}
|
|
if req.Exposed != nil {
|
|
st, err := setLANExposure(*req.Exposed)
|
|
if err != nil {
|
|
sendJSON(w, 500, map[string]any{"ok": false, "error": err.Error()})
|
|
return
|
|
}
|
|
sendJSON(w, 200, map[string]any{"ok": true, "status": st})
|
|
return
|
|
}
|
|
}
|
|
sendJSON(w, 200, map[string]any{"ok": true, "status": networkStatus()})
|
|
}
|