mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-22 22:48:11 +02:00
Rebaptise AJEAN en Loki (fork, lignée conservée)
- 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.
This commit is contained in:
157 files changed
+928
-928
No files matched your search
@@ -0,0 +1,611 @@
|
||||
// web_llamacpp.go — pilotage du backend llama.cpp depuis l'UI web :
|
||||
// statut (commit, retard, binaire, plan de build), vérification des mises à
|
||||
// jour (git fetch), et jobs asynchrones install / update / rebuild dont la
|
||||
// progression et les logs sont pollés par le client.
|
||||
//
|
||||
// GET /api/llamacpp → statut complet (dépôt, commit, binaire, plan)
|
||||
// POST /api/llamacpp/check → git fetch + retard sur origin (réseau)
|
||||
// POST /api/llamacpp/install → job d'installation {force?}
|
||||
// POST /api/llamacpp/update → job de mise à jour {clean?}
|
||||
// GET /api/llamacpp/job?from= → progression du job (phase, lignes, erreur)
|
||||
package loki
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// lcJob est LE job llama.cpp en cours (un seul à la fois). Les lignes de log
|
||||
// sont indexées de façon absolue (base = index de Lines[0]) pour que le client
|
||||
// puisse poller « à partir de N » même quand on tronque le début.
|
||||
type lcJob struct {
|
||||
Action string // "install" | "update"
|
||||
Phase string
|
||||
Compiled int // fichiers compilés (progression du build)
|
||||
Running bool
|
||||
Err string
|
||||
StartedAt int64
|
||||
EndedAt int64
|
||||
OldCommit string
|
||||
NewCommit string
|
||||
lines []string
|
||||
base int
|
||||
}
|
||||
|
||||
var (
|
||||
lcMu sync.Mutex
|
||||
lcCur *lcJob
|
||||
)
|
||||
|
||||
const lcMaxLines = 4000
|
||||
|
||||
// lcAppend ajoute une ligne au log du job courant et met à jour la phase / le
|
||||
// compteur de fichiers compilés à partir du contenu (mêmes heuristiques que le
|
||||
// spinner du CLI). Appelée par le sink de build (emitBuildLine).
|
||||
func lcAppend(line string) {
|
||||
lcMu.Lock()
|
||||
defer lcMu.Unlock()
|
||||
if lcCur == nil {
|
||||
return
|
||||
}
|
||||
lcCur.lines = append(lcCur.lines, line)
|
||||
if len(lcCur.lines) > lcMaxLines {
|
||||
drop := len(lcCur.lines) - lcMaxLines
|
||||
lcCur.lines = append([]string(nil), lcCur.lines[drop:]...)
|
||||
lcCur.base += drop
|
||||
}
|
||||
if f := compiledFile(line); f != "" {
|
||||
lcCur.Compiled++
|
||||
lcCur.Phase = fmt.Sprintf("compilation… %d fichiers", lcCur.Compiled)
|
||||
} else if p := phaseLabel(line); p != "" {
|
||||
lcCur.Phase = p
|
||||
}
|
||||
lcSaveLine(line)
|
||||
lcSave(false)
|
||||
}
|
||||
|
||||
// lcPhase pose une phase explicite (étapes hors build : clone, fetch, service…)
|
||||
// et la trace aussi dans le log.
|
||||
func lcPhase(phase string) {
|
||||
lcMu.Lock()
|
||||
if lcCur != nil {
|
||||
lcCur.Phase = phase
|
||||
lcCur.lines = append(lcCur.lines, "▶ "+phase)
|
||||
lcSaveLine("▶ " + phase)
|
||||
lcSave(true)
|
||||
}
|
||||
lcMu.Unlock()
|
||||
}
|
||||
|
||||
// startLcJob démarre un job (install ou update) si aucun n'est en cours.
|
||||
func startLcJob(action string, run func()) error {
|
||||
lcMu.Lock()
|
||||
defer lcMu.Unlock()
|
||||
if lcCur != nil && lcCur.Running {
|
||||
return fmt.Errorf("un job %s est déjà en cours", lcCur.Action)
|
||||
}
|
||||
lcCur = &lcJob{Action: action, Running: true, Phase: "démarrage…", StartedAt: time.Now().Unix()}
|
||||
lcResetLog()
|
||||
lcSave(true)
|
||||
setBuildSink(lcAppend)
|
||||
go func() {
|
||||
defer func() {
|
||||
setBuildSink(nil)
|
||||
lcMu.Lock()
|
||||
lcCur.Running = false
|
||||
lcCur.EndedAt = time.Now().Unix()
|
||||
lcSave(true)
|
||||
lcMu.Unlock()
|
||||
}()
|
||||
run()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func lcFail(err error) {
|
||||
lcMu.Lock()
|
||||
if lcCur != nil {
|
||||
lcCur.Err = err.Error()
|
||||
lcCur.Phase = "échec"
|
||||
lcCur.lines = append(lcCur.lines, "✗ "+err.Error())
|
||||
lcSaveLine("✗ " + err.Error())
|
||||
lcSave(true)
|
||||
}
|
||||
lcMu.Unlock()
|
||||
}
|
||||
|
||||
func lcDone(msg string) {
|
||||
lcMu.Lock()
|
||||
if lcCur != nil {
|
||||
lcCur.Phase = msg
|
||||
lcCur.lines = append(lcCur.lines, "✓ "+msg)
|
||||
lcSaveLine("✓ " + msg)
|
||||
lcSave(true)
|
||||
}
|
||||
lcMu.Unlock()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handlers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// handleLlamacpp renvoie le statut complet du backend llama.cpp : dépôt,
|
||||
// commit courant, retard connu (sans fetch réseau), binaire compilé et son
|
||||
// usage dans config.env, plan de build détecté, et le job éventuel.
|
||||
func handleLlamacpp(w http.ResponseWriter, r *http.Request) {
|
||||
repo := llamacppRepoDir()
|
||||
out := map[string]any{
|
||||
"repo": repo,
|
||||
"installed": isDir(filepath.Join(repo, ".git")),
|
||||
}
|
||||
if out["installed"].(bool) {
|
||||
out["branch"] = gitOutput(repo, "rev-parse", "--abbrev-ref", "HEAD")
|
||||
out["commit"] = gitOutput(repo, "rev-parse", "--short", "HEAD")
|
||||
out["commit_date"] = gitOutput(repo, "log", "-1", "--format=%ci")
|
||||
out["commit_msg"] = gitOutput(repo, "log", "-1", "--format=%s")
|
||||
if br, _ := out["branch"].(string); br != "" && br != "HEAD" {
|
||||
if behind := gitOutput(repo, "rev-list", "--count", "HEAD..origin/"+br); behind != "" {
|
||||
n, _ := strconv.Atoi(behind)
|
||||
out["behind"] = n
|
||||
}
|
||||
}
|
||||
}
|
||||
bin := llamaServerBin(repo)
|
||||
out["bin"] = bin
|
||||
cfgBin := ReadConfig()["BIN"]
|
||||
out["config_bin"] = cfgBin
|
||||
out["in_use"] = bin != "" && samePath(bin, cfgBin)
|
||||
|
||||
plan := detectBuildPlan()
|
||||
out["plan"] = map[string]any{
|
||||
"backend": plan.backend,
|
||||
"arch": plan.cudaArch,
|
||||
"jobs": plan.jobs,
|
||||
}
|
||||
// Mode « binaires précompilés » : installé ? utilisé par la config ?
|
||||
pbTag, _ := prebuiltVersion()
|
||||
pbBin := prebuiltServerBin()
|
||||
out["prebuilt"] = map[string]any{
|
||||
"tag": pbTag,
|
||||
"bin": pbBin,
|
||||
"dir": prebuiltDir(),
|
||||
// Tout BIN vivant sous le dossier prebuilt EST le moteur précompilé, même
|
||||
// s'il porte le numéro d'une release remplacée depuis (chemin versionné).
|
||||
"in_use": pbBin != "" && prebuiltOwns(cfgBin),
|
||||
}
|
||||
out["backends_dir"] = filepath.Join(LokiHome(), "backends")
|
||||
out["reco"] = recommendedMode(plan.backend)
|
||||
out["job"] = lcJobSnapshot(0, false)
|
||||
sendJSON(w, 200, out)
|
||||
}
|
||||
|
||||
// samePath compare deux chemins en neutralisant séparateurs, symlinks et casse
|
||||
// (Windows).
|
||||
func samePath(a, b string) bool {
|
||||
if a == "" || b == "" {
|
||||
return false
|
||||
}
|
||||
norm := func(p string) string {
|
||||
if real, err := filepath.EvalSymlinks(p); err == nil {
|
||||
p = real
|
||||
}
|
||||
p = filepath.Clean(p)
|
||||
return strings.ToLower(filepath.ToSlash(p))
|
||||
}
|
||||
return norm(a) == norm(b)
|
||||
}
|
||||
|
||||
// handleLlamacppCheck fait un vrai git fetch puis renvoie le retard sur origin
|
||||
// et le dernier commit distant. Synchrone (quelques secondes réseau).
|
||||
func handleLlamacppCheck(w http.ResponseWriter, r *http.Request) {
|
||||
repo := llamacppRepoDir()
|
||||
if !isDir(filepath.Join(repo, ".git")) {
|
||||
sendJSON(w, 200, map[string]any{"ok": false, "error": "llama.cpp n'est pas installé"})
|
||||
return
|
||||
}
|
||||
branch := gitOutput(repo, "rev-parse", "--abbrev-ref", "HEAD")
|
||||
if branch == "" || branch == "HEAD" {
|
||||
branch = "master"
|
||||
}
|
||||
if err := runStep("git fetch", repo, "git", "fetch", "origin", "--quiet"); err != nil {
|
||||
sendJSON(w, 200, map[string]any{"ok": false, "error": "git fetch a échoué : " + err.Error()})
|
||||
return
|
||||
}
|
||||
behind := 0
|
||||
if b := gitOutput(repo, "rev-list", "--count", "HEAD..origin/"+branch); b != "" {
|
||||
behind, _ = strconv.Atoi(b)
|
||||
}
|
||||
sendJSON(w, 200, map[string]any{
|
||||
"ok": true,
|
||||
"behind": behind,
|
||||
"local": gitOutput(repo, "rev-parse", "--short", "HEAD"),
|
||||
"remote": gitOutput(repo, "rev-parse", "--short", "origin/"+branch),
|
||||
"remote_date": gitOutput(repo, "log", "-1", "--format=%ci", "origin/"+branch),
|
||||
"remote_msg": gitOutput(repo, "log", "-1", "--format=%s", "origin/"+branch),
|
||||
"branch": branch,
|
||||
"has_binary": llamaServerBin(repo) != "",
|
||||
"needs_rebuild": behind > 0 || llamaServerBin(repo) == "",
|
||||
})
|
||||
}
|
||||
|
||||
// handleLlamacppInstall lance le job d'installation (clone + build + BIN).
|
||||
func handleLlamacppInstall(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Force bool `json:"force"`
|
||||
}
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err := startLcJob("install", func() { lcRunInstall(req.Force) }); err != nil {
|
||||
sendJSON(w, 409, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
sendJSON(w, 200, map[string]any{"ok": true})
|
||||
}
|
||||
|
||||
// handleLlamacppInstallCustom lance un job d'installation d'un backend CUSTOM
|
||||
// (fork llama.cpp) depuis une URL de dépôt Git : cloné dans backends/<name> et
|
||||
// compilé pour la machine, SANS toucher au BIN global. Il apparaît ensuite dans
|
||||
// /api/backends et se choisit par modèle (éditeur de preset → Moteur).
|
||||
func handleLlamacppInstallCustom(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Repo string `json:"repo"`
|
||||
Name string `json:"name"`
|
||||
Ref string `json:"ref"`
|
||||
}
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
if strings.TrimSpace(req.Repo) == "" {
|
||||
sendJSON(w, 400, map[string]any{"ok": false, "error": "URL du dépôt requise"})
|
||||
return
|
||||
}
|
||||
if err := startLcJob("custom", func() { lcRunCustomInstall(req.Repo, req.Name, req.Ref) }); err != nil {
|
||||
sendJSON(w, 409, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
sendJSON(w, 200, map[string]any{"ok": true})
|
||||
}
|
||||
|
||||
// lcRunCustomInstall : corps du job d'install d'un backend custom (sans bascule
|
||||
// de BIN — c'est volontaire, il se rattache par preset).
|
||||
func lcRunCustomInstall(url, name, ref string) {
|
||||
bin, err := installCustomBackend(url, name, ref, lcPhase)
|
||||
if err != nil {
|
||||
lcFail(err)
|
||||
return
|
||||
}
|
||||
lcAppend("binaire compilé : " + bin)
|
||||
lcAppend("→ pour l'utiliser : édite un modèle → section Moteur → « backend détecté » et choisis-le.")
|
||||
lcDone("backend custom installé")
|
||||
}
|
||||
|
||||
// handleLlamacppUpdate lance le job de mise à jour (pull + rebuild + restart).
|
||||
// {clean:true} force une recompilation from scratch même sans nouveau commit.
|
||||
func handleLlamacppUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Clean bool `json:"clean"`
|
||||
}
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err := startLcJob("update", func() { lcRunUpdate(req.Clean) }); err != nil {
|
||||
sendJSON(w, 409, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
sendJSON(w, 200, map[string]any{"ok": true})
|
||||
}
|
||||
|
||||
// handleLlamacppPrebuiltCheck interroge la dernière release officielle de
|
||||
// llama.cpp et la compare à la version précompilée installée. Synchrone.
|
||||
func handleLlamacppPrebuiltCheck(w http.ResponseWriter, r *http.Request) {
|
||||
tag, assets, err := fetchLlamaLatest()
|
||||
if err != nil {
|
||||
sendJSON(w, 200, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
main, _, label, _, err := pickPrebuilt(assets)
|
||||
if err != nil {
|
||||
sendJSON(w, 200, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
cur, _ := prebuiltVersion()
|
||||
sendJSON(w, 200, map[string]any{
|
||||
"ok": true,
|
||||
"latest": tag,
|
||||
"current": cur,
|
||||
"variant": label,
|
||||
"size_mb": main.Size / 1_000_000,
|
||||
"update": cur != tag || prebuiltServerBin() == "",
|
||||
})
|
||||
}
|
||||
|
||||
// handleLlamacppPrebuilt lance le job de téléchargement / mise à jour des
|
||||
// binaires précompilés officiels (pas de compilation).
|
||||
func handleLlamacppPrebuilt(w http.ResponseWriter, r *http.Request) {
|
||||
if err := startLcJob("prebuilt", lcRunPrebuilt); err != nil {
|
||||
sendJSON(w, 409, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
sendJSON(w, 200, map[string]any{"ok": true})
|
||||
}
|
||||
|
||||
// lcRunPrebuilt : télécharge les binaires officiels, pointe BIN dessus, en
|
||||
// stoppant le service pendant le remplacement (binaire verrouillé en cours
|
||||
// d'exécution) puis en le relançant.
|
||||
func lcRunPrebuilt() {
|
||||
svcWasUp := serviceIsActive()
|
||||
if svcWasUp {
|
||||
lcPhase("arrêt du service le temps de l'installation…")
|
||||
if err := serviceAction("stop"); err != nil {
|
||||
lcAppend("[warn] impossible d'arrêter le service : " + err.Error())
|
||||
}
|
||||
}
|
||||
bin, err := prebuiltInstall(lcAppend, lcPhase)
|
||||
if err == nil {
|
||||
if serr := SetConfigKey("BIN", bin); serr != nil {
|
||||
err = fmt.Errorf("binaires installés mais échec d'écriture de BIN : %w", serr)
|
||||
} else {
|
||||
lcAppend("BIN mis à jour")
|
||||
}
|
||||
}
|
||||
if svcWasUp {
|
||||
lcPhase("redémarrage du service…")
|
||||
if serr := serviceAction("start"); serr != nil {
|
||||
lcAppend("[warn] redémarrage du service échoué : " + serr.Error())
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
lcFail(err)
|
||||
return
|
||||
}
|
||||
tag, _ := prebuiltVersion()
|
||||
lcDone("binaires précompilés installés (" + tag + ")")
|
||||
}
|
||||
|
||||
// handleLlamacppUse bascule BIN entre deux versions DÉJÀ installées, sans
|
||||
// rien recompiler : "fast" = binaires précompilés, "opt" = build local. Le
|
||||
// service redémarre pour prendre le nouveau binaire. L'UI n'appelle ceci que
|
||||
// quand la version cible existe déjà (sinon elle lance un job d'installation).
|
||||
func handleLlamacppUse(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Mode string `json:"mode"`
|
||||
}
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
var bin string
|
||||
switch req.Mode {
|
||||
case "fast":
|
||||
bin = prebuiltServerBin()
|
||||
case "opt":
|
||||
bin = llamaServerBin(llamacppRepoDir())
|
||||
default:
|
||||
sendJSON(w, 400, map[string]any{"ok": false, "error": "mode inconnu"})
|
||||
return
|
||||
}
|
||||
if bin == "" {
|
||||
sendJSON(w, 400, map[string]any{"ok": false, "error": "cette version n'est pas installée"})
|
||||
return
|
||||
}
|
||||
if err := SetConfigKey("BIN", bin); err != nil {
|
||||
sendJSON(w, 500, map[string]any{"ok": false, "error": err.Error()})
|
||||
return
|
||||
}
|
||||
if serviceIsActive() {
|
||||
_ = serviceAction("restart")
|
||||
}
|
||||
sendJSON(w, 200, map[string]any{"ok": true, "bin": bin})
|
||||
}
|
||||
|
||||
// handleLlamacppJob renvoie l'état du job courant + les lignes de log depuis
|
||||
// l'offset absolu ?from=N (le client mémorise `next` et enchaîne).
|
||||
func handleLlamacppJob(w http.ResponseWriter, r *http.Request) {
|
||||
from, _ := strconv.Atoi(r.URL.Query().Get("from"))
|
||||
sendJSON(w, 200, lcJobSnapshot(from, true))
|
||||
}
|
||||
|
||||
// lcJobSnapshot construit la vue JSON du job courant. withLines=false renvoie
|
||||
// juste l'entête (imbriquée dans /api/llamacpp).
|
||||
func lcJobSnapshot(from int, withLines bool) map[string]any {
|
||||
lcMu.Lock()
|
||||
defer lcMu.Unlock()
|
||||
if lcCur == nil {
|
||||
return map[string]any{"exists": false}
|
||||
}
|
||||
j := lcCur
|
||||
out := map[string]any{
|
||||
"exists": true,
|
||||
"action": j.Action,
|
||||
"running": j.Running,
|
||||
"phase": j.Phase,
|
||||
"compiled": j.Compiled,
|
||||
"error": j.Err,
|
||||
"started_at": j.StartedAt,
|
||||
"ended_at": j.EndedAt,
|
||||
"old": j.OldCommit,
|
||||
"new": j.NewCommit,
|
||||
}
|
||||
if withLines {
|
||||
start := from - j.base
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
if start > len(j.lines) {
|
||||
start = len(j.lines)
|
||||
}
|
||||
out["lines"] = append([]string(nil), j.lines[start:]...)
|
||||
out["next"] = j.base + len(j.lines)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Corps des jobs (miroir web de llamacppInstall / llamacppUpdate, sans stdout
|
||||
// interactif : tout passe par lcPhase / le sink de build)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func lcRunInstall(force bool) {
|
||||
repo := defaultRepoDir()
|
||||
|
||||
lcPhase("vérification des outils (git, cmake, compilateur)…")
|
||||
if err := requireTools("git", "cmake"); err != nil {
|
||||
lcFail(err)
|
||||
return
|
||||
}
|
||||
if err := ensureCompiler(); err != nil {
|
||||
lcFail(err)
|
||||
return
|
||||
}
|
||||
ensureAccelerator()
|
||||
|
||||
if isDir(filepath.Join(repo, ".git")) {
|
||||
if !force {
|
||||
// Dépôt déjà là : on bascule sur une mise à jour (même intention).
|
||||
lcPhase("dépôt déjà présent — bascule en mise à jour")
|
||||
lcRunUpdate(false)
|
||||
return
|
||||
}
|
||||
lcPhase("suppression du dépôt existant (--force)…")
|
||||
if err := os.RemoveAll(repo); err != nil {
|
||||
lcFail(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(repo), 0o755); err != nil {
|
||||
lcFail(err)
|
||||
return
|
||||
}
|
||||
|
||||
lcPhase("clone de llama.cpp…")
|
||||
if err := runStep("git clone", "", "git", "clone", "--depth=1", llamacppRepoURL, repo); err != nil {
|
||||
lcFail(fmt.Errorf("git clone a échoué : %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if !lcBuildAndSwitch(repo, true) {
|
||||
return
|
||||
}
|
||||
lcDone("installation terminée")
|
||||
}
|
||||
|
||||
func lcRunUpdate(clean bool) {
|
||||
lcPhase("vérification des outils (git, cmake, compilateur)…")
|
||||
if err := requireTools("git", "cmake"); err != nil {
|
||||
lcFail(err)
|
||||
return
|
||||
}
|
||||
if err := ensureCompiler(); err != nil {
|
||||
lcFail(err)
|
||||
return
|
||||
}
|
||||
ensureAccelerator()
|
||||
|
||||
repo := llamacppRepoDir()
|
||||
if !isDir(filepath.Join(repo, ".git")) {
|
||||
lcFail(fmt.Errorf("aucun dépôt llama.cpp (%s) — lance d'abord l'installation", repo))
|
||||
return
|
||||
}
|
||||
|
||||
oldCommit := gitOutput(repo, "rev-parse", "--short", "HEAD")
|
||||
lcMu.Lock()
|
||||
lcCur.OldCommit = oldCommit
|
||||
lcMu.Unlock()
|
||||
|
||||
branch := gitOutput(repo, "rev-parse", "--abbrev-ref", "HEAD")
|
||||
if branch == "" || branch == "HEAD" {
|
||||
branch = "master"
|
||||
}
|
||||
|
||||
lcPhase("git fetch origin…")
|
||||
if err := runStep("git fetch", repo, "git", "fetch", "origin", "--quiet"); err != nil {
|
||||
lcFail(fmt.Errorf("git fetch a échoué : %w", err))
|
||||
return
|
||||
}
|
||||
localRev := gitOutput(repo, "rev-parse", "HEAD")
|
||||
remoteRev := gitOutput(repo, "rev-parse", "origin/"+branch)
|
||||
if localRev != "" && localRev == remoteRev && !clean && llamaServerBin(repo) != "" {
|
||||
lcDone("déjà à jour (" + oldCommit + ") — rien à faire")
|
||||
return
|
||||
}
|
||||
if localRev != remoteRev {
|
||||
lcPhase("git pull origin/" + branch + "…")
|
||||
if err := runStep("git pull --ff-only", repo, "git", "pull", "--ff-only", "origin", branch); err != nil {
|
||||
lcFail(fmt.Errorf("git pull a échoué (modifs locales ?) : %w", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
newCommit := gitOutput(repo, "rev-parse", "--short", "HEAD")
|
||||
lcMu.Lock()
|
||||
lcCur.NewCommit = newCommit
|
||||
lcMu.Unlock()
|
||||
|
||||
// Le binaire en cours d'exécution ne peut pas être réécrit → stop du service
|
||||
// pendant le build, redémarrage après (même en échec).
|
||||
svcWasUp := serviceIsActive()
|
||||
if svcWasUp {
|
||||
lcPhase("arrêt du service le temps du build…")
|
||||
if err := serviceAction("stop"); err != nil {
|
||||
lcAppend("[warn] impossible d'arrêter le service : " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
ok := lcBuildAndSwitch(repo, clean)
|
||||
if svcWasUp {
|
||||
lcPhase("redémarrage du service…")
|
||||
if err := serviceAction("start"); err != nil {
|
||||
lcAppend("[warn] redémarrage du service échoué : " + err.Error())
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if oldCommit == newCommit {
|
||||
lcDone("recompilé (" + newCommit + ")")
|
||||
} else {
|
||||
lcDone("mis à jour : " + oldCommit + " → " + newCommit)
|
||||
}
|
||||
}
|
||||
|
||||
// lcBuildAndSwitch détecte le plan, compile llama-server et pointe BIN dessus.
|
||||
// Renvoie false (job en échec) si une étape casse.
|
||||
func lcBuildAndSwitch(repo string, clean bool) bool {
|
||||
plan := detectBuildPlan()
|
||||
lcAppend(fmt.Sprintf("plan de build : backend=%s arch=%s jobs=%d", plan.backend, plan.cudaArch, plan.jobs))
|
||||
lcPhase("configuration CMake…")
|
||||
if err := buildLlamacpp(repo, plan, clean); err != nil {
|
||||
lcAppendLogTail(filepath.Join(repo, "configure.log"), filepath.Join(repo, "build.log"))
|
||||
lcFail(err)
|
||||
return false
|
||||
}
|
||||
bin := llamaServerBin(repo)
|
||||
if bin == "" {
|
||||
lcFail(fmt.Errorf("build terminé mais binaire introuvable sous %s", filepath.Join(repo, "build")))
|
||||
return false
|
||||
}
|
||||
lcAppend("binaire compilé : " + bin)
|
||||
if err := SetConfigKey("BIN", bin); err != nil {
|
||||
lcFail(fmt.Errorf("build ok mais échec d'écriture de BIN : %w", err))
|
||||
return false
|
||||
}
|
||||
lcAppend("BIN mis à jour")
|
||||
return true
|
||||
}
|
||||
|
||||
// lcAppendLogTail remonte la fin des logs de build dans le job pour que
|
||||
// l'erreur réelle soit visible dans l'UI sans aller chercher les fichiers.
|
||||
func lcAppendLogTail(paths ...string) {
|
||||
for _, p := range paths {
|
||||
b, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
lines := strings.Split(strings.TrimRight(string(b), "\n"), "\n")
|
||||
if len(lines) > 30 {
|
||||
lines = lines[len(lines)-30:]
|
||||
}
|
||||
lcAppend("--- fin de " + filepath.Base(p) + " ---")
|
||||
for _, l := range lines {
|
||||
lcAppend(l)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user