mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Arborescence unifiee et installateurs simplifies
- provisionDataDir unique pour les trois plateformes (six dossiers, config par defaut) - plus de modele config.env duplique dans chaque installateur - plus d'alias jean pose a l'installation
This commit is contained in:
1 parent
2455d9f402
commit
6de356f649
9 files changed
+93
-240
No files matched your search
@@ -82,7 +82,7 @@ func llamacppRepoDir() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func defaultRepoDir() string {
|
func defaultRepoDir() string {
|
||||||
return filepath.Join(AjeanHome(), "backends", "llama.cpp")
|
return filepath.Join(backendsDir(), "llama.cpp")
|
||||||
}
|
}
|
||||||
|
|
||||||
// findRepoRoot walks up from a binary path (…/build/bin/llama-server) looking
|
// findRepoRoot walks up from a binary path (…/build/bin/llama-server) looking
|
||||||
@@ -510,7 +510,7 @@ func sanitizeBackendName(name string) string {
|
|||||||
// dossier backends (../, chemin absolu…).
|
// dossier backends (../, chemin absolu…).
|
||||||
func backendDir(name string) (string, error) {
|
func backendDir(name string) (string, error) {
|
||||||
name = sanitizeBackendName(name)
|
name = sanitizeBackendName(name)
|
||||||
root, err := filepath.Abs(filepath.Join(AjeanHome(), "backends"))
|
root, err := filepath.Abs(backendsDir())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ type ghAsset struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func prebuiltDir() string {
|
func prebuiltDir() string {
|
||||||
return filepath.Join(AjeanHome(), "backends", "llama.cpp-prebuilt")
|
return filepath.Join(backendsDir(), "llama.cpp-prebuilt")
|
||||||
}
|
}
|
||||||
|
|
||||||
// prebuiltVersion lit le marqueur VERSION du dossier prebuilt : "tag cudaVer"
|
// prebuiltVersion lit le marqueur VERSION du dossier prebuilt : "tag cudaVer"
|
||||||
|
|||||||
@@ -60,11 +60,8 @@ func workspaceCandidates() []string {
|
|||||||
if v := strings.TrimSpace(os.Getenv(workspaceEnv)); v != "" {
|
if v := strings.TrimSpace(os.Getenv(workspaceEnv)); v != "" {
|
||||||
c = append(c, v)
|
c = append(c, v)
|
||||||
}
|
}
|
||||||
c = append(c, filepath.Join(AjeanHome(), "workspace"))
|
c = append(c, workspaceDir())
|
||||||
if home, err := os.UserHomeDir(); err == nil && home != "" {
|
if home, err := os.UserHomeDir(); err == nil && home != "" {
|
||||||
// Le nouveau nom d'abord, l'ancien ensuite : un dossier ~/ajean/workspace
|
|
||||||
// déjà rempli par l'agent reste utilisable au lieu de repartir de zéro.
|
|
||||||
c = append(c, filepath.Join(home, "ajean", "workspace"))
|
|
||||||
c = append(c, filepath.Join(home, "ajean", "workspace"))
|
c = append(c, filepath.Join(home, "ajean", "workspace"))
|
||||||
}
|
}
|
||||||
c = append(c, filepath.Join(os.TempDir(), "ajean-workspace"))
|
c = append(c, filepath.Join(os.TempDir(), "ajean-workspace"))
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package ajean
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// sys_datadir.go — création du dossier de données, identique sur les trois
|
||||||
|
// plateformes. Avant, chaque installateur (Linux, macOS, Windows, plus le
|
||||||
|
// premier lancement de l'app) créait ses dossiers et écrivait son propre
|
||||||
|
// modèle de config.env : quatre copies qui divergeaient à la première
|
||||||
|
// modification. Il n'y en a plus qu'une.
|
||||||
|
|
||||||
|
// dataDirs est l'arborescence complète de $AJEAN_HOME. Rien d'autre n'y est
|
||||||
|
// créé : tout le reste vit dans ajean.db.
|
||||||
|
func dataDirs() []string {
|
||||||
|
return []string{AjeanHome(), backendsDir(), binDir(), presetsDir(), memoryDir(), modelsDir(), workspaceDir()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultConfig est la configuration de départ d'une installation neuve. Les
|
||||||
|
// valeurs sont volontairement incomplètes (BIN et MODEL sont vides) : c'est
|
||||||
|
// l'écran d'accueil, ou « ajean llamacpp install », qui les renseigne.
|
||||||
|
func defaultConfig() map[string]string {
|
||||||
|
host := "0.0.0.0"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
host = "127.0.0.1"
|
||||||
|
}
|
||||||
|
return map[string]string{
|
||||||
|
"PORT": "8080",
|
||||||
|
"HOST": host,
|
||||||
|
"CTX": "32768",
|
||||||
|
"BATCH": "2048",
|
||||||
|
"UBATCH": "512",
|
||||||
|
"NGL": "999",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// provisionDataDir crée l'arborescence et, sur une installation neuve, pose la
|
||||||
|
// configuration de départ. Idempotente : une configuration existante n'est
|
||||||
|
// jamais écrasée.
|
||||||
|
func provisionDataDir() error {
|
||||||
|
for _, d := range dataDirs() {
|
||||||
|
if err := os.MkdirAll(d, 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ReadConfig()) > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return WriteConfig(defaultConfig())
|
||||||
|
}
|
||||||
@@ -2,14 +2,12 @@
|
|||||||
|
|
||||||
package ajean
|
package ajean
|
||||||
|
|
||||||
import "os"
|
|
||||||
|
|
||||||
// appFirstRun : hors Windows, `ajean app` est lancé depuis un terminal par
|
// appFirstRun : hors Windows, `ajean app` est lancé depuis un terminal par
|
||||||
// quelqu'un qui a déjà suivi l'installation — pas de double-clic à désambiguïser,
|
// quelqu'un qui a déjà suivi l'installation — pas de double-clic à désambiguïser,
|
||||||
// donc pas de boîte de dialogue. On se contente de provisionner les données au
|
// donc pas de boîte de dialogue. On se contente de provisionner les données au
|
||||||
// premier lancement, comme avant. Ne relance jamais (renvoie toujours false).
|
// premier lancement. Ne relance jamais (renvoie toujours false).
|
||||||
func appFirstRun() bool {
|
func appFirstRun() bool {
|
||||||
if _, err := os.Stat(confPath()); err != nil {
|
if len(ReadConfig()) == 0 {
|
||||||
_ = cmdInstall(nil)
|
_ = cmdInstall(nil)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -372,26 +372,6 @@ type vsFixedFileInfo struct {
|
|||||||
FileDateLS uint32
|
FileDateLS uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// provisionDataDir crée le dossier de données et un config.env de départ.
|
|
||||||
// Extrait de cmdInstall pour être réutilisable au premier lancement, sans
|
|
||||||
// embarquer l'installation du binaire ni les messages de console.
|
|
||||||
func provisionDataDir() error {
|
|
||||||
ajeanHome := AjeanHome()
|
|
||||||
// Plus de dossier SKILLS : les skills ont ete fondus dans la memoire.
|
|
||||||
// On ne le CREE plus, mais on continue de le LIRE une fois au demarrage
|
|
||||||
// pour reprendre ceux d'une ancienne installation (migrateSkillsToMemory).
|
|
||||||
for _, d := range []string{ajeanHome, filepath.Join(ajeanHome, "configs")} {
|
|
||||||
if err := os.MkdirAll(d, 0o755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conf := filepath.Join(ajeanHome, "config.env")
|
|
||||||
if _, err := os.Stat(conf); os.IsNotExist(err) {
|
|
||||||
return os.WriteFile(conf, []byte(configTemplate), 0o644)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensureShortcuts garantit qu'un raccourci « AJEAN » existe dans le menu
|
// ensureShortcuts garantit qu'un raccourci « AJEAN » existe dans le menu
|
||||||
// Démarrer et sur le Bureau, et qu'il pointe sur le bon exécutable. Appelée à
|
// Démarrer et sur le Bureau, et qu'il pointe sur le bon exécutable. Appelée à
|
||||||
// CHAQUE lancement du fichier téléchargé, pas seulement à l'installation :
|
// CHAQUE lancement du fichier téléchargé, pas seulement à l'installation :
|
||||||
|
|||||||
@@ -17,36 +17,11 @@ import (
|
|||||||
// sur « systemctl: command not found » / « /etc/default/ajean », cf. issue #4).
|
// sur « systemctl: command not found » / « /etc/default/ajean », cf. issue #4).
|
||||||
// Implémentation prudente ; à valider sur une machine Apple.
|
// Implémentation prudente ; à valider sur une machine Apple.
|
||||||
|
|
||||||
const configTemplate = `# Configuration AJEAN — édite-moi puis: ajean restart
|
|
||||||
# Le service launchd lit ce fichier et lance ton binaire llama.cpp.
|
|
||||||
|
|
||||||
# Chemins
|
|
||||||
BIN="/usr/local/bin/llama-server"
|
|
||||||
MODEL="/Users/USER/models/your-model.gguf"
|
|
||||||
|
|
||||||
# Serveur
|
|
||||||
PORT="8080"
|
|
||||||
HOST="0.0.0.0"
|
|
||||||
|
|
||||||
# Inference
|
|
||||||
CTX="32768"
|
|
||||||
BATCH="2048"
|
|
||||||
UBATCH="512"
|
|
||||||
NGL="999"
|
|
||||||
|
|
||||||
# Args supplémentaires passés à llama-server
|
|
||||||
EXTRA_ARGS=""
|
|
||||||
`
|
|
||||||
|
|
||||||
// launchdPlistTemplate — champs formatés : Label, chemin du binaire, UserName,
|
// launchdPlistTemplate — champs formatés : Label, chemin du binaire, UserName,
|
||||||
// WorkingDirectory, AJEAN_HOME, AJEAN_HOME, StandardOutPath, StandardErrorPath.
|
// WorkingDirectory, AJEAN_HOME, StandardOutPath, StandardErrorPath.
|
||||||
// KeepAlive/SuccessfulExit=false ≈ Restart=on-failure ; RunAtLoad relance au
|
// KeepAlive/SuccessfulExit=false ≈ Restart=on-failure ; RunAtLoad relance au
|
||||||
// boot une fois chargé avec `-w`.
|
// boot une fois chargé avec `-w`.
|
||||||
//
|
|
||||||
// Le chemin du binaire est injecté et non codé en dur : il a changé avec le
|
|
||||||
// renommage, et un plist figé sur l'ancien chemin donnerait un daemon qui refuse
|
|
||||||
// de démarrer. Les deux variables d'environnement sont posées car le code lit
|
|
||||||
// encore AJEAN_HOME en héritage.
|
|
||||||
const launchdPlistTemplate = `<?xml version="1.0" encoding="UTF-8"?>
|
const launchdPlistTemplate = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
@@ -60,7 +35,7 @@ const launchdPlistTemplate = `<?xml version="1.0" encoding="UTF-8"?>
|
|||||||
<key>UserName</key><string>%s</string>
|
<key>UserName</key><string>%s</string>
|
||||||
<key>WorkingDirectory</key><string>%s</string>
|
<key>WorkingDirectory</key><string>%s</string>
|
||||||
<key>EnvironmentVariables</key>
|
<key>EnvironmentVariables</key>
|
||||||
<dict><key>AJEAN_HOME</key><string>%s</string><key>AJEAN_HOME</key><string>%s</string></dict>
|
<dict><key>AJEAN_HOME</key><string>%s</string></dict>
|
||||||
<key>RunAtLoad</key><true/>
|
<key>RunAtLoad</key><true/>
|
||||||
<key>KeepAlive</key>
|
<key>KeepAlive</key>
|
||||||
<dict><key>SuccessfulExit</key><false/></dict>
|
<dict><key>SuccessfulExit</key><false/></dict>
|
||||||
@@ -87,14 +62,6 @@ func cmdInstall(args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("utilisateur '%s' introuvable: %w", targetUser, err)
|
return fmt.Errorf("utilisateur '%s' introuvable: %w", targetUser, err)
|
||||||
}
|
}
|
||||||
// Migration de l'agencement ajean -> ajean AVANT de résoudre les chemins :
|
|
||||||
// `install` est root et délibéré, c'est le bon moment. Ne fait rien si la
|
|
||||||
// machine est déjà en ajean, ou si l'utilisateur a imposé un chemin.
|
|
||||||
if err := migrateLayout(defaultLayoutPlan()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resetResolvedHome() // le dossier a pu changer à l'instant
|
|
||||||
|
|
||||||
ajeanHome := defaultAjeanHome()
|
ajeanHome := defaultAjeanHome()
|
||||||
if v := os.Getenv("AJEAN_HOME"); v != "" {
|
if v := os.Getenv("AJEAN_HOME"); v != "" {
|
||||||
ajeanHome = v
|
ajeanHome = v
|
||||||
@@ -105,27 +72,12 @@ func cmdInstall(args []string) error {
|
|||||||
fmt.Printf(" AJEAN_HOME = %s\n", ajeanHome)
|
fmt.Printf(" AJEAN_HOME = %s\n", ajeanHome)
|
||||||
fmt.Printf(" service = %s (launchd)\n", svc)
|
fmt.Printf(" service = %s (launchd)\n", svc)
|
||||||
|
|
||||||
// 1. Répertoires
|
// 1. Arborescence + configuration de départ
|
||||||
// Plus de dossier SKILLS : les skills ont ete fondus dans la memoire.
|
if err := provisionDataDir(); err != nil {
|
||||||
// On ne le CREE plus, mais on continue de le LIRE une fois au demarrage
|
return err
|
||||||
// pour reprendre ceux d'une ancienne installation (migrateSkillsToMemory).
|
|
||||||
for _, d := range []string{ajeanHome, filepath.Join(ajeanHome, "configs")} {
|
|
||||||
if err := os.MkdirAll(d, 0o755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. config.env si absent
|
// 2. Symlink idempotent vers /usr/local/bin/ajean — même garde-fou que Linux
|
||||||
conf := filepath.Join(ajeanHome, "config.env")
|
|
||||||
if _, err := os.Stat(conf); os.IsNotExist(err) {
|
|
||||||
body := strings.ReplaceAll(configTemplate, "USER", targetUser)
|
|
||||||
if err := os.WriteFile(conf, []byte(body), 0o644); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf(" %s écrit %s\n", green("✓"), conf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Symlink idempotent vers /usr/local/bin/ajean — même garde-fou que Linux
|
|
||||||
// (issue #5 : ne pas se lier sur soi-même si on tourne déjà depuis la cible).
|
// (issue #5 : ne pas se lier sur soi-même si on tourne déjà depuis la cible).
|
||||||
self, err := os.Executable()
|
self, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -154,38 +106,24 @@ func cmdInstall(args []string) error {
|
|||||||
fmt.Printf(" %s %s -> %s\n", green("✓"), target, self)
|
fmt.Printf(" %s %s -> %s\n", green("✓"), target, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3 bis. Alias hérité : `ajean` doit rester tapable (voir sys_paths.go).
|
// 3. /etc/default/ajean pour que les invocations CLI résolvent AJEAN_HOME.
|
||||||
if alias := legacyExePath(); alias != target {
|
|
||||||
if rp, e := filepath.EvalSymlinks(alias); e != nil || rp != self {
|
|
||||||
_ = os.Remove(alias)
|
|
||||||
if err := os.Symlink(target, alias); err != nil {
|
|
||||||
fmt.Printf(" %s alias %s non créé (%v) — « ajean » reste disponible\n", yellow("[info]"), alias, err)
|
|
||||||
} else {
|
|
||||||
fmt.Printf(" %s %s -> %s (alias hérité)\n", green("✓"), alias, target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. /etc/default/ajean pour que les invocations CLI résolvent AJEAN_HOME.
|
|
||||||
// Les deux clés sont écrites : des scripts d'utilisateurs sourcent ce fichier
|
|
||||||
// et lisent encore $AJEAN_HOME.
|
|
||||||
_ = os.MkdirAll("/etc/default", 0o755)
|
_ = os.MkdirAll("/etc/default", 0o755)
|
||||||
defaults := fmt.Sprintf("# Generated by ajean install — racine des configs/memoire/presets\nAJEAN_HOME=%s\nJEAN_HOME=%s\n", ajeanHome, ajeanHome)
|
defaults := fmt.Sprintf("# Généré par ajean install — racine des données\nAJEAN_HOME=%s\n", ajeanHome)
|
||||||
if err := os.WriteFile("/etc/default/ajean", []byte(defaults), 0o644); err != nil {
|
if err := os.WriteFile("/etc/default/ajean", []byte(defaults), 0o644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf(" %s /etc/default/ajean\n", green("✓"))
|
fmt.Printf(" %s /etc/default/ajean\n", green("✓"))
|
||||||
|
|
||||||
// 5. LaunchDaemon plist (le log va sous AJEAN_HOME, accessible au user cible)
|
// 4. LaunchDaemon plist (le log va sous AJEAN_HOME, accessible au user cible)
|
||||||
logPath := filepath.Join(ajeanHome, svc+".log")
|
logPath := filepath.Join(ajeanHome, svc+".log")
|
||||||
plist := fmt.Sprintf(launchdPlistTemplate, launchdLabel(svc), target, targetUser, ajeanHome, ajeanHome, ajeanHome, logPath, logPath)
|
plist := fmt.Sprintf(launchdPlistTemplate, launchdLabel(svc), target, targetUser, ajeanHome, ajeanHome, logPath, logPath)
|
||||||
plistPath := launchdPlistPath(svc)
|
plistPath := launchdPlistPath(svc)
|
||||||
if err := os.WriteFile(plistPath, []byte(plist), 0o644); err != nil {
|
if err := os.WriteFile(plistPath, []byte(plist), 0o644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf(" %s %s\n", green("✓"), plistPath)
|
fmt.Printf(" %s %s\n", green("✓"), plistPath)
|
||||||
|
|
||||||
// 6. chown AJEAN_HOME au user cible
|
// 5. chown AJEAN_HOME au user cible
|
||||||
chown(ajeanHome, u)
|
chown(ajeanHome, u)
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
@@ -208,13 +146,12 @@ func cmdUninstall(args []string) error {
|
|||||||
launchdPlistPath(svc),
|
launchdPlistPath(svc),
|
||||||
"/etc/default/ajean",
|
"/etc/default/ajean",
|
||||||
installedExePath(),
|
installedExePath(),
|
||||||
legacyExePath(),
|
|
||||||
} {
|
} {
|
||||||
if err := os.Remove(p); err == nil {
|
if err := os.Remove(p); err == nil {
|
||||||
fmt.Printf(" %s %s\n", green("✓"), p)
|
fmt.Printf(" %s %s\n", green("✓"), p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// On ne supprime jamais AJEAN_HOME automatiquement (modèles, configs).
|
// On ne supprime jamais AJEAN_HOME automatiquement (modèles, presets).
|
||||||
fmt.Println(dim("(données utilisateur conservées — supprime $AJEAN_HOME manuellement si tu veux purger)"))
|
fmt.Println(dim("(données utilisateur conservées — supprime $AJEAN_HOME manuellement si tu veux purger)"))
|
||||||
fmt.Println(green("[ok]") + " désinstallé")
|
fmt.Println(green("[ok]") + " désinstallé")
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -11,26 +11,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const configTemplate = `# Configuration AJEAN — édite-moi puis: ajean restart
|
|
||||||
# Le service systemd lit ce fichier et lance ton binaire llama.cpp.
|
|
||||||
|
|
||||||
# Chemins
|
|
||||||
BIN="/usr/local/bin/llama-server"
|
|
||||||
MODEL="/home/USER/models/your-model.gguf"
|
|
||||||
|
|
||||||
# Serveur
|
|
||||||
PORT="8080"
|
|
||||||
HOST="0.0.0.0"
|
|
||||||
|
|
||||||
# Inference
|
|
||||||
CTX="32768"
|
|
||||||
BATCH="2048"
|
|
||||||
UBATCH="512"
|
|
||||||
NGL="999"
|
|
||||||
|
|
||||||
# Args supplémentaires passés à llama-server
|
|
||||||
EXTRA_ARGS=""
|
|
||||||
`
|
|
||||||
|
|
||||||
const sudoersTemplate = `# Allow %s to manage the %s systemd unit without a password (installed by ajean).
|
const sudoersTemplate = `# Allow %s to manage the %s systemd unit without a password (installed by ajean).
|
||||||
%s ALL=(root) NOPASSWD: /bin/systemctl start %s, /bin/systemctl stop %s, /bin/systemctl restart %s, /bin/systemctl enable %s, /bin/systemctl disable %s
|
%s ALL=(root) NOPASSWD: /bin/systemctl start %s, /bin/systemctl stop %s, /bin/systemctl restart %s, /bin/systemctl enable %s, /bin/systemctl disable %s
|
||||||
@@ -76,14 +56,6 @@ func cmdInstall(args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("utilisateur '%s' introuvable: %w", targetUser, err)
|
return fmt.Errorf("utilisateur '%s' introuvable: %w", targetUser, err)
|
||||||
}
|
}
|
||||||
// Migration de l'agencement ajean -> ajean AVANT de résoudre les chemins :
|
|
||||||
// `install` est root et délibéré, c'est le bon moment. Ne fait rien si la
|
|
||||||
// machine est déjà en ajean, ou si l'utilisateur a imposé un chemin.
|
|
||||||
if err := migrateLayout(defaultLayoutPlan()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resetResolvedHome() // le dossier a pu changer à l'instant
|
|
||||||
|
|
||||||
ajeanHome := defaultAjeanHome()
|
ajeanHome := defaultAjeanHome()
|
||||||
if v := os.Getenv("AJEAN_HOME"); v != "" {
|
if v := os.Getenv("AJEAN_HOME"); v != "" {
|
||||||
ajeanHome = v
|
ajeanHome = v
|
||||||
@@ -94,27 +66,13 @@ func cmdInstall(args []string) error {
|
|||||||
fmt.Printf(" AJEAN_HOME = %s\n", ajeanHome)
|
fmt.Printf(" AJEAN_HOME = %s\n", ajeanHome)
|
||||||
fmt.Printf(" service = %s\n", svc)
|
fmt.Printf(" service = %s\n", svc)
|
||||||
|
|
||||||
// 1. Create directories
|
// 1. Arborescence + configuration de départ
|
||||||
// Plus de dossier SKILLS : les skills ont ete fondus dans la memoire.
|
if err := provisionDataDir(); err != nil {
|
||||||
// On ne le CREE plus, mais on continue de le LIRE une fois au demarrage
|
return err
|
||||||
// pour reprendre ceux d'une ancienne installation (migrateSkillsToMemory).
|
|
||||||
for _, d := range []string{ajeanHome, filepath.Join(ajeanHome, "configs")} {
|
|
||||||
if err := os.MkdirAll(d, 0o755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
fmt.Printf(" %s arborescence prête\n", green("✓"))
|
||||||
|
|
||||||
// 2. Drop a config.env if none exists
|
// 2. Symlink current binary to /usr/local/bin/ajean
|
||||||
conf := filepath.Join(ajeanHome, "config.env")
|
|
||||||
if _, err := os.Stat(conf); os.IsNotExist(err) {
|
|
||||||
body := strings.ReplaceAll(configTemplate, "USER", targetUser)
|
|
||||||
if err := os.WriteFile(conf, []byte(body), 0o644); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf(" %s écrit %s\n", green("✓"), conf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Symlink current binary to /usr/local/bin/ajean
|
|
||||||
self, err := os.Executable()
|
self, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -145,27 +103,8 @@ func cmdInstall(args []string) error {
|
|||||||
fmt.Printf(" %s %s -> %s\n", green("✓"), target, self)
|
fmt.Printf(" %s %s -> %s\n", green("✓"), target, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3 bis. Alias hérité : `ajean` doit rester tapable. Des utilisateurs ont des
|
// 3. /etc/default/ajean, pour que les invocations root résolvent AJEAN_HOME.
|
||||||
// alias shell, des cron, des scripts de déploiement et des tutos qui appellent
|
defaults := fmt.Sprintf("# Généré par ajean install — racine des données\nAJEAN_HOME=%s\n", ajeanHome)
|
||||||
// « ajean » — on ne contrôle rien de tout ça, et un binaire renommé qui casse
|
|
||||||
// la commande d'hier est une régression pour eux. Le lien pointe sur le
|
|
||||||
// nouveau nom, donc il suit les mises à jour sans entretien.
|
|
||||||
if alias := legacyExePath(); alias != target {
|
|
||||||
if rp, e := filepath.EvalSymlinks(alias); e != nil || rp != self {
|
|
||||||
_ = os.Remove(alias)
|
|
||||||
if err := os.Symlink(target, alias); err != nil {
|
|
||||||
fmt.Printf(" %s alias %s non créé (%v) — « ajean » reste disponible\n", yellow("[info]"), alias, err)
|
|
||||||
} else {
|
|
||||||
fmt.Printf(" %s %s -> %s (alias hérité)\n", green("✓"), alias, target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Drop /etc/default/ajean so root invocations resolve AJEAN_HOME correctly.
|
|
||||||
// On écrit les DEUX clés : des scripts d'utilisateurs sourcent ce fichier et
|
|
||||||
// lisent $AJEAN_HOME. L'ancien /etc/default/ajean est laissé en place — le
|
|
||||||
// supprimer casserait ces mêmes scripts, et AjeanHome() sait déjà le lire.
|
|
||||||
defaults := fmt.Sprintf("# Generated by ajean install — racine des configs/memoire/presets\nAJEAN_HOME=%s\nJEAN_HOME=%s\n", ajeanHome, ajeanHome)
|
|
||||||
if err := os.WriteFile("/etc/default/ajean", []byte(defaults), 0o644); err != nil {
|
if err := os.WriteFile("/etc/default/ajean", []byte(defaults), 0o644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,26 +16,6 @@ import (
|
|||||||
// service itself is managed by the PID-file supervisor in sys_service_windows.go
|
// service itself is managed by the PID-file supervisor in sys_service_windows.go
|
||||||
// (ajean start / stop / status), which needs no admin rights.
|
// (ajean start / stop / status), which needs no admin rights.
|
||||||
|
|
||||||
const configTemplate = `# Configuration AJEAN — édite-moi puis: ajean restart
|
|
||||||
# ajean serve lit ce fichier et lance ton binaire llama.cpp.
|
|
||||||
|
|
||||||
# Chemins (utilise des chemins Windows ; les antislashs ou slashs marchent)
|
|
||||||
BIN="C:/llama.cpp/build/bin/Release/llama-server.exe"
|
|
||||||
MODEL="C:/models/your-model.gguf"
|
|
||||||
|
|
||||||
# Serveur
|
|
||||||
PORT="8080"
|
|
||||||
HOST="127.0.0.1"
|
|
||||||
|
|
||||||
# Inference
|
|
||||||
CTX="32768"
|
|
||||||
BATCH="2048"
|
|
||||||
UBATCH="512"
|
|
||||||
NGL="999"
|
|
||||||
|
|
||||||
# Args supplémentaires passés à llama-server
|
|
||||||
EXTRA_ARGS=""
|
|
||||||
`
|
|
||||||
|
|
||||||
func cmdInstall(args []string) error {
|
func cmdInstall(args []string) error {
|
||||||
ajeanHome := AjeanHome()
|
ajeanHome := AjeanHome()
|
||||||
@@ -44,41 +24,31 @@ func cmdInstall(args []string) error {
|
|||||||
fmt.Printf(" AJEAN_HOME = %s\n", ajeanHome)
|
fmt.Printf(" AJEAN_HOME = %s\n", ajeanHome)
|
||||||
fmt.Printf(" service = %s\n", serviceName())
|
fmt.Printf(" service = %s\n", serviceName())
|
||||||
|
|
||||||
// 1-2. Dossiers + config.env de départ (partagé avec le premier lancement de
|
// 1. Arborescence + configuration de départ (partagé avec le premier
|
||||||
// l'app, voir provisionDataDir dans sys_firstrun_windows.go).
|
// lancement de l'app, voir sys_datadir.go).
|
||||||
conf := filepath.Join(ajeanHome, "config.env")
|
|
||||||
_, statErr := os.Stat(conf)
|
|
||||||
alreadyThere := statErr == nil
|
|
||||||
if err := provisionDataDir(); err != nil {
|
if err := provisionDataDir(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if alreadyThere {
|
fmt.Printf(" %s arborescence prête\n", green("✓"))
|
||||||
fmt.Printf(" %s config.env déjà présent — inchangé\n", dim("•"))
|
|
||||||
} else {
|
|
||||||
fmt.Printf(" %s écrit %s\n", green("✓"), conf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Install the binary into AJEAN_HOME\bin and put that dir on the user PATH,
|
// 2. Le binaire va dans AJEAN_HOME\bin, ajouté au PATH utilisateur, pour que
|
||||||
// so `ajean` is callable from any shell (this is the Windows analogue of the
|
// `ajean` soit appelable depuis n'importe quel shell (pendant Windows du
|
||||||
// /usr/local/bin symlink the Unix installer creates).
|
// lien /usr/local/bin créé par l'installateur Unix).
|
||||||
binDir := filepath.Join(ajeanHome, "bin")
|
dir := binDir()
|
||||||
if err := os.MkdirAll(binDir, 0o755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
onPath := false
|
onPath := false
|
||||||
if dst, err := installSelf(binDir); err != nil {
|
if dst, err := installSelf(dir); err != nil {
|
||||||
fmt.Printf(" %s copie du binaire impossible (%v) — ajoute-le au PATH à la main\n", dim("•"), err)
|
fmt.Printf(" %s copie du binaire impossible (%v) — ajoute-le au PATH à la main\n", dim("•"), err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" %s binaire installé %s\n", green("✓"), dst)
|
fmt.Printf(" %s binaire installé %s\n", green("✓"), dst)
|
||||||
added, err := addToUserPath(binDir)
|
added, err := addToUserPath(dir)
|
||||||
switch {
|
switch {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
fmt.Printf(" %s mise à jour du PATH impossible (%v)\n", dim("•"), err)
|
fmt.Printf(" %s mise à jour du PATH impossible (%v)\n", dim("•"), err)
|
||||||
case added:
|
case added:
|
||||||
fmt.Printf(" %s %s ajouté au PATH utilisateur\n", green("✓"), binDir)
|
fmt.Printf(" %s %s ajouté au PATH utilisateur\n", green("✓"), dir)
|
||||||
onPath = true
|
onPath = true
|
||||||
default:
|
default:
|
||||||
fmt.Printf(" %s %s déjà dans le PATH\n", dim("•"), binDir)
|
fmt.Printf(" %s %s déjà dans le PATH\n", dim("•"), dir)
|
||||||
onPath = true
|
onPath = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,10 +67,9 @@ func cmdInstall(args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// installSelf copies the currently running executable into binDir as ajean.exe
|
// installSelf copie l'exécutable en cours dans binDir sous le nom ajean.exe et
|
||||||
// and returns the destination path. If the running exe already lives there
|
// renvoie le chemin de destination. Sans effet si l'exe tourne déjà depuis là
|
||||||
// (re-install), it's a no-op. Une copie « ajean.exe » est posée à côté pour que
|
// (ré-installation).
|
||||||
// l'ancienne commande reste tapable (voir installLegacyAlias).
|
|
||||||
//
|
//
|
||||||
// binDir est créé ici, et pas seulement par l'appelant : sur une machine vierge,
|
// binDir est créé ici, et pas seulement par l'appelant : sur une machine vierge,
|
||||||
// le premier lancement passait par appFirstRun, qui ne créait que le dossier de
|
// le premier lancement passait par appFirstRun, qui ne créait que le dossier de
|
||||||
@@ -115,7 +84,6 @@ func installSelf(binDir string) (string, error) {
|
|||||||
src, _ = filepath.EvalSymlinks(src)
|
src, _ = filepath.EvalSymlinks(src)
|
||||||
dst := filepath.Join(binDir, "ajean.exe")
|
dst := filepath.Join(binDir, "ajean.exe")
|
||||||
if strings.EqualFold(src, dst) {
|
if strings.EqualFold(src, dst) {
|
||||||
installLegacyAlias(binDir, src)
|
|
||||||
return dst, nil
|
return dst, nil
|
||||||
}
|
}
|
||||||
if err := os.MkdirAll(binDir, 0o755); err != nil {
|
if err := os.MkdirAll(binDir, 0o755); err != nil {
|
||||||
@@ -126,26 +94,9 @@ func installSelf(binDir string) (string, error) {
|
|||||||
if err := replaceExe(src, dst); err != nil {
|
if err := replaceExe(src, dst); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
installLegacyAlias(binDir, dst)
|
|
||||||
return dst, nil
|
return dst, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// installLegacyAlias pose une copie « ajean.exe » à côté de « ajean.exe ».
|
|
||||||
//
|
|
||||||
// Une copie et pas un lien : créer un lien symbolique sous Windows demande des
|
|
||||||
// droits particuliers qu'on n'a pas toujours, et un binaire de quelques dizaines
|
|
||||||
// de Mo dupliqué est un prix dérisoire pour ne casser ni les raccourcis du menu
|
|
||||||
// Démarrer, ni les tâches planifiées, ni les scripts que les utilisateurs ont
|
|
||||||
// écrits eux-mêmes. Best-effort : un échec ne compromet pas l'installation,
|
|
||||||
// « ajean » reste disponible.
|
|
||||||
func installLegacyAlias(binDir, src string) {
|
|
||||||
alias := filepath.Join(binDir, "ajean.exe")
|
|
||||||
if strings.EqualFold(src, alias) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = replaceExe(src, alias)
|
|
||||||
}
|
|
||||||
|
|
||||||
// replaceExe copie src vers dst, y compris quand dst est un exécutable EN COURS.
|
// replaceExe copie src vers dst, y compris quand dst est un exécutable EN COURS.
|
||||||
//
|
//
|
||||||
// Windows refuse d'écraser un .exe en cours d'exécution, mais accepte de le
|
// Windows refuse d'écraser un .exe en cours d'exécution, mais accepte de le
|
||||||
@@ -253,9 +204,9 @@ func cmdUninstall(args []string) error {
|
|||||||
_ = svcStop(false)
|
_ = svcStop(false)
|
||||||
|
|
||||||
// Pull AJEAN_HOME\bin off the user PATH (best-effort).
|
// Pull AJEAN_HOME\bin off the user PATH (best-effort).
|
||||||
binDir := filepath.Join(AjeanHome(), "bin")
|
dir := binDir()
|
||||||
if removed, err := removeFromUserPath(binDir); err == nil && removed {
|
if removed, err := removeFromUserPath(dir); err == nil && removed {
|
||||||
fmt.Printf(" %s %s retiré du PATH utilisateur\n", green("✓"), binDir)
|
fmt.Printf(" %s %s retiré du PATH utilisateur\n", green("✓"), dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Raccourcis posés à l'installation (menu Démarrer + Bureau).
|
// Raccourcis posés à l'installation (menu Démarrer + Bureau).
|
||||||
|
|||||||
Reference in new issue
Block a user