Files
Loki/internal/ajean/sys_paths.go
T
nathaninline 8a2b5c6687 Migration : reecrire les chemins absolus pointant vers l'ancien dossier
Bug trouve en testant la migration sur une vraie machine (5,4 Go, 51 351
fichiers). Le rename du dossier reussissait, mais config.env contenait

  BIN=C:\ProgramData\jean\backends\llama.cpp\build\bin\Release\llama-server.exe

soit un chemin ABSOLU vers le dossier qu'on venait de renommer. Resultat :
le dossier migre intact, et llama-server ne demarre plus. Ca touche tous
ceux qui ont fait un `llamacpp install`, donc le cas nominal, pas un cas
limite. Les presets de configs/ portent le meme BIN et etaient touches
pareil (deux presets concernes sur la machine de test).

Apres un rename reussi, on reecrit donc les references a l'ancien chemin
dans les fichiers de config qu'on ecrit nous-memes : config.env,
model_dirs.json, mcp.json, webprefs.json et les presets de configs/. Les
deux ecritures de separateur sont couvertes, Windows acceptant \ et / dans
une meme valeur. Restreint a ces fichiers texte : on ne reecrit pas a
l'aveugle les 51 000 fichiers d'un dossier de donnees.

Les fichiers d'etat du service sont repris au passage (jean.pid/jean.log ->
ajean.pid/ajean.log), meme raison que pour le worker de lien : le PID dit
si le service tourne, l'ignorer ferait demarrer un second service.

Non traite volontairement : l'arborescence de build llama.cpp
(CMakeCache.txt et consorts) contient aussi des chemins absolus. Elle est
regenerable par `ajean llamacpp update` et la reecrire en masse serait plus
risque qu'utile.

Verifie de bout en bout sur la machine de test : service demarre,
llama-server chargé depuis le nouveau chemin, /health ok, completion reelle
ok. Deux tests ajoutes reproduisant le cas.
2026-08-04 14:24:53 +02:00

88 lines
3.1 KiB
Go

package ajean
import (
"fmt"
"net/http"
"os"
"path/filepath"
"runtime"
)
// « Où sont mes fichiers ? » — la question que personne ne devrait avoir à poser.
// Un binaire unique qui s'auto-installe au premier lancement rend les
// emplacements invisibles : on ne sait plus si le .exe qu'on a double-cliqué EST
// l'application ou seulement un installateur, ni où atterrissent la config, les
// modèles et les fichiers que l'agent écrit. On centralise donc la réponse ici,
// et on l'expose à la fois en ligne de commande (`ajean where`) et dans l'UI.
type ajeanPaths struct {
Home string `json:"home"` // AJEAN_HOME : racine des données
Config string `json:"config"` // config.env
Exe string `json:"exe"` // binaire en cours d'exécution
Installed string `json:"installed"` // binaire installé (peut différer de Exe)
Workspace string `json:"workspace"` // dossier de travail du mode agent
Memory string `json:"memory"`
Presets string `json:"presets"`
}
// installedExePath est l'emplacement canonique du binaire après installation.
// Il diffère par plateforme : sous Unix les installateurs posent /usr/local/bin/ajean
// (c'est ce que référencent les unités systemd et le plist launchd) ; sous Windows,
// faute d'équivalent, on utilise AJEAN_HOME\bin ajouté au PATH utilisateur.
func installedExePath() string { return exePathNamed("ajean") }
// legacyExePath est l'emplacement d'avant le renommage. L'installation continue
// d'y poser un alias : `jean` reste tapable indéfiniment. Ça ne coûte qu'un lien
// et ça préserve les habitudes, les scripts cron, les alias shell et les tutos
// déjà écrits par les utilisateurs — dont on ne contrôle aucun.
func legacyExePath() string { return exePathNamed("jean") }
func exePathNamed(name string) string {
if runtime.GOOS == "windows" {
return filepath.Join(AjeanHome(), "bin", name+".exe")
}
return "/usr/local/bin/" + name
}
func currentPaths() ajeanPaths {
exe, _ := os.Executable()
if resolved, err := filepath.EvalSymlinks(exe); err == nil {
exe = resolved
}
return ajeanPaths{
Home: AjeanHome(),
Config: confPath(),
Exe: exe,
Installed: installedExePath(),
Workspace: agentWorkspace(),
Memory: memoryDir(),
Presets: presetsDir(),
}
}
func cmdWhere(args []string) error {
p := currentPaths()
fmt.Printf("Emplacements AJEAN\n\n")
for _, row := range [][2]string{
{"données (AJEAN_HOME)", p.Home},
{"configuration", p.Config},
{"binaire en cours", p.Exe},
{"binaire installé", p.Installed},
{"travail de l'agent", p.Workspace},
{"mémoire", p.Memory},
{"presets", p.Presets},
} {
fmt.Printf(" %-20s %s\n", row[0], row[1])
}
if p.Exe != p.Installed {
fmt.Printf("\n%s tu exécutes une copie qui n'est PAS le binaire installé.\n", dim("[info]"))
fmt.Printf(" Les mises à jour depuis l'application ne modifient que la copie lancée.\n")
}
return nil
}
// handlePaths (GET /api/paths) alimente le bloc « Emplacements » de l'UI.
func handlePaths(w http.ResponseWriter, r *http.Request) {
sendJSON(w, 200, currentPaths())
}