mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +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.
260 lines
9.1 KiB
Go
260 lines
9.1 KiB
Go
// loki — single-binary LLM server manager + web UI for llama.cpp deployments.
|
|
// Point d'entrée réel : Main(), appelé par cmd/loki (les métadonnées Windows
|
|
// .syso/go:generate vivent là-bas, dans le dossier du package main).
|
|
package loki
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
const Version = "0.9.4"
|
|
|
|
// Main est le vrai main() du binaire (cmd/loki ne fait que l'appeler).
|
|
func Main() {
|
|
// Rattache la console du terminal parent si on est lancé depuis un shell
|
|
// (Windows : binaire GUI). Retourne false au double-clic (aucune console) →
|
|
// on bascule alors sur l'expérience « application ». Hors Windows : toujours
|
|
// true. À faire AVANT toute écriture.
|
|
haveConsole := setupConsole()
|
|
|
|
// Nettoie un éventuel binaire .old laissé par une mise à jour Windows.
|
|
cleanupOldBinary()
|
|
|
|
args := os.Args[1:]
|
|
noArgs := len(args) == 0
|
|
cmd := "help"
|
|
if len(args) > 0 {
|
|
cmd = args[0]
|
|
args = args[1:]
|
|
}
|
|
// Double-clic sur le binaire (aucun argument, aucune console rattachée) → on
|
|
// lance l'expérience « application » (UI web + navigateur + icône tray) plutôt
|
|
// que d'afficher l'aide. Le binaire étant compilé en sous-système GUI, il n'y a
|
|
// AUCUNE console à ce stade (donc plus de fenêtre noire). Lancé depuis un shell,
|
|
// `loki` sans argument garde son comportement d'aide.
|
|
if noArgs && !haveConsole {
|
|
mustExit(cmdApp(args))
|
|
return
|
|
}
|
|
switch cmd {
|
|
case "app":
|
|
mustExit(cmdApp(args))
|
|
case "start", "restart":
|
|
// Un moteur sans BIN ni MODEL démarre, meurt, et systemd le relance en
|
|
// boucle : l'utilisateur ne voyait qu'un « activating » rassurant et un
|
|
// /health muet. On le dit AVANT de lancer le service.
|
|
mustExit(preflightEngine())
|
|
mustExit(serviceAction(cmd))
|
|
case "stop", "status", "enable", "disable":
|
|
mustExit(serviceAction(cmd))
|
|
case "logs":
|
|
mustExit(serviceLogs())
|
|
case "edit":
|
|
mustExit(editConfig())
|
|
case "set-api-key":
|
|
mustExit(cmdSetAPIKey(args))
|
|
case "set-web-key":
|
|
mustExit(cmdSetWebKey(args))
|
|
case "vram":
|
|
mustExit(showVram())
|
|
case "gpu":
|
|
mustExit(cmdGPU(args))
|
|
case "network":
|
|
mustExit(cmdNetwork(args))
|
|
case "switch":
|
|
mustExit(cmdSwitch(args))
|
|
case "chat":
|
|
mustExit(cmdChat(args))
|
|
case "export":
|
|
mustExit(cmdExport(args))
|
|
case "web":
|
|
mustExit(cmdWeb(args))
|
|
case "link":
|
|
mustExit(cmdLink(args))
|
|
case "remote":
|
|
mustExit(cmdRemote(args))
|
|
case "ui":
|
|
mustExit(cmdUI(args))
|
|
case "oai":
|
|
mustExit(cmdOAI(args))
|
|
case "agent":
|
|
mustExit(cmdAgent(args))
|
|
case "internet":
|
|
mustExit(cmdInternet(args))
|
|
case "memory":
|
|
mustExit(cmdMemory(args))
|
|
case "serve":
|
|
mustExit(cmdServe(args))
|
|
case "test":
|
|
mustExit(cmdTest(args))
|
|
case "bench":
|
|
mustExit(cmdBench(args))
|
|
case "llamacpp":
|
|
mustExit(cmdLlamacpp(args))
|
|
case "update":
|
|
mustExit(cmdUpdate(args))
|
|
case "where":
|
|
mustExit(cmdWhere(args))
|
|
case restartArg:
|
|
// Sous-commande interne, absente de l'aide : l'accompagnateur détaché qui
|
|
// attend la fermeture de l'app puis la relance.
|
|
mustExit(cmdRestartAfterUpdate(args))
|
|
case "install":
|
|
mustExit(cmdInstall(args))
|
|
case "uninstall":
|
|
mustExit(cmdUninstall(args))
|
|
case "version", "-v", "--version":
|
|
fmt.Println("loki", Version)
|
|
case "help", "-h", "--help", "":
|
|
printHelp()
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "commande inconnue: %s\n\n", cmd)
|
|
printHelp()
|
|
os.Exit(2)
|
|
}
|
|
}
|
|
|
|
func printHelp() {
|
|
fmt.Printf(`loki %s — manager llama.cpp + UI web (single binary)
|
|
|
|
Usage: loki <commande> [args]
|
|
|
|
Loki tourne en deux services : loki-engine (le modèle) et loki-ui
|
|
(l'interface web, le tunnel d'accès distant et l'endpoint OpenAI).
|
|
|
|
Moteur (loki-engine) :
|
|
start | stop | restart gérer le service
|
|
status | logs état / logs en direct
|
|
enable | disable auto-démarrage au boot
|
|
edit éditer la configuration dans $EDITOR
|
|
switch [N] activer un preset de presets/ (interactif ou par numéro)
|
|
test | bench [N] vérifier que l'IA répond / mesurer prefill + decode tok/s
|
|
vram utilisation GPU/VRAM (nvidia-smi)
|
|
gpu [index…] liste les GPU / choisit le(s)quel(s) utiliser (gpu all = tous)
|
|
set-api-key [clé] protéger llama-server (clé Bearer); vide = générer, "" = retirer
|
|
network [on|off|status] rendre l'endpoint OpenAI joignable depuis le réseau local
|
|
(HOST + règle de pare-feu sous Windows)
|
|
|
|
Interface (loki-ui) :
|
|
ui [start|stop|restart|status] pilote le service d'interface
|
|
web [PORT] sert l'interface au premier plan (défaut :8090) —
|
|
ouvre aussi le tunnel si un jeton est enregistré
|
|
set-web-key [clé] protéger l'API de pilotage; vide = générer, "" = retirer
|
|
|
|
Interaction:
|
|
chat [system-prompt] chat terminal streamé
|
|
export [options] [fichier] exporte la conversation de l'interface web
|
|
(Markdown par défaut, « - » = sortie standard)
|
|
--json --last N --no-reasoning
|
|
--no-tools --no-results
|
|
agent [on|off|status] donne à l'IA ses outils (shell, fichiers, mémoire)
|
|
memory [off|ondemand|always|status] mode mémoire de l'IA
|
|
internet [on|off|status|engine <go|crawl4ai>|url <url>|key <clé>]
|
|
accès web de l'IA (moteur intégré ou serveur Crawl4AI)
|
|
|
|
Accès distant (ajean.link) :
|
|
link <token> enregistre le jeton et ouvre le tunnel
|
|
link code code d'appairage (10 min, usage unique) pour le portail
|
|
link status | logout état du jeton / l'oublier
|
|
|
|
Poste distant (piloter CE PC depuis l'IA d'un serveur Loki) :
|
|
remote install <url> --machine ID --key CLÉ --code CODE [--allow shell,read,write,list]
|
|
appaire ce PC + installe le service (démarrage auto)
|
|
remote connect <url> … idem en avant-plan (pour tester)
|
|
remote status | uninstall | logout
|
|
(commande d'appairage prête à copier : UI web → Postes distants)
|
|
|
|
Backend llama.cpp :
|
|
llamacpp install clone + compile llama.cpp (CUDA/ROCm/Metal/CPU), pointe BIN dessus
|
|
llamacpp update git pull + recompile le backend existant
|
|
llamacpp status commit courant, backend détecté, retard sur origin
|
|
|
|
Installation:
|
|
where affiche l'emplacement du binaire, de la base et des dossiers
|
|
install | uninstall installer / désinstaller
|
|
update [--check] mettre à jour depuis les releases GitHub
|
|
serve entrypoint du service : exec llama-server (usage interne)
|
|
|
|
Env:
|
|
LOKI_HOME racine des données (défaut : /etc/loki, %%ProgramData%%\loki sous Windows)
|
|
EDITOR éditeur pour 'loki edit' (défaut : nano, notepad sous Windows)
|
|
`, Version)
|
|
}
|
|
|
|
func mustExit(err error) {
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "[err]", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// LokiHome resolves the Loki data directory.
|
|
// Précédence : $LOKI_HOME → /etc/default/loki (unix) → défaut plateforme.
|
|
func LokiHome() string {
|
|
if h := os.Getenv("LOKI_HOME"); h != "" {
|
|
return h
|
|
}
|
|
if h := readEtcDefault(); h != "" {
|
|
return h
|
|
}
|
|
return defaultLokiHome()
|
|
}
|
|
|
|
// readEtcDefault parses /etc/default/loki for LOKI_HOME=<path>. Quiet on errors.
|
|
func readEtcDefault() string {
|
|
b, err := os.ReadFile("/etc/default/loki")
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
for _, line := range strings.Split(string(b), "\n") {
|
|
s := strings.TrimSpace(line)
|
|
if s == "" || strings.HasPrefix(s, "#") {
|
|
continue
|
|
}
|
|
s = strings.TrimPrefix(s, "export ")
|
|
if k, v, ok := strings.Cut(s, "="); ok && strings.TrimSpace(k) == "LOKI_HOME" {
|
|
return strings.Trim(strings.TrimSpace(v), "\"'")
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Arborescence de $LOKI_HOME. Elle tient en six dossiers, et rien d'autre :
|
|
// tout le reste (config, préférences, conversation, clés, drapeaux) vit dans la
|
|
// base loki.db — voir store.go.
|
|
func backendsDir() string { return filepath.Join(LokiHome(), "backends") }
|
|
func binDir() string { return filepath.Join(LokiHome(), "bin") }
|
|
func presetsDir() string { return filepath.Join(LokiHome(), "presets") }
|
|
func memoryDir() string { return filepath.Join(LokiHome(), "memory") }
|
|
func modelsDir() string { return filepath.Join(LokiHome(), "models") }
|
|
func workspaceDir() string { return filepath.Join(LokiHome(), "workspace") }
|
|
|
|
// serviceName est le nom de l'unité qui exécute llama-server. Son pendant est
|
|
// uiUnitName (« loki-ui »), qui sert l'interface et le tunnel.
|
|
func serviceName() string {
|
|
if n := os.Getenv("LOKI_SERVICE"); n != "" {
|
|
return n
|
|
}
|
|
return "loki-engine"
|
|
}
|
|
|
|
// Color helpers (ANSI). Disabled when stdout is not a TTY.
|
|
var colorOn = isTerminal()
|
|
|
|
func col(code, s string) string {
|
|
if !colorOn {
|
|
return s
|
|
}
|
|
return "\033[" + code + "m" + s + "\033[0m"
|
|
}
|
|
func bold(s string) string { return col("1", s) }
|
|
func cyan(s string) string { return col("1;36", s) }
|
|
func green(s string) string { return col("32", s) }
|
|
func red(s string) string { return col("31", s) }
|
|
func dim(s string) string { return col("2", s) }
|
|
func yellow(s string) string { return col("33", s) }
|
|
func magenta(s string) string { return col("35", s) }
|