mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-17 20:26:35 +02:00
Socle du renommage jean -> AJEAN, sans aucun impact visible pour le parc installe. Trois briques : - module github.com/nathaninline/ajean, cmd/ajean, internal/ajean, package ajean. Purement interne. - AjeanHome() remplace JeanHome(). Les defauts deviennent %ProgramData%\ajean et /etc/ajean, avec migration du dossier existant au premier lancement. La migration est un os.Rename dans le dossier parent de l'ancien chemin : intra-volume, donc atomique et instantane meme avec des .gguf de plusieurs dizaines de Go. Si le rename echoue (handle ouvert sous Windows, droits, volume en lecture seule) on CONTINUE sur l'ancien chemin et on retentera : jamais de copie, jamais de suppression, le pire cas est "rien n'a bouge". $AJEAN_HOME et $JEAN_HOME restent tous deux honores, ainsi que /etc/default/ajean et /etc/default/jean : un chemin impose a la main est un ordre, on ne le migre pas. - l'auto-MAJ accepte desormais ajean-<os>-<arch> ET jean-<os>-<arch>, dans cet ordre. Indispensable dans les deux sens : les binaires deja installes ne connaissent que jean-*, et ce binaire-ci doit pouvoir s'installer depuis une release anterieure au renommage. Couvert par des tests, dont le repli quand un handle ouvert bloque le rename sous Windows.
103 lines
3.5 KiB
Go
103 lines
3.5 KiB
Go
package ajean
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
)
|
|
|
|
// mkHome crée un dossier de données factice contenant un fichier témoin, pour
|
|
// vérifier que la migration déplace bien le CONTENU et pas seulement le dossier.
|
|
func mkHome(t *testing.T, dir string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "config.env"), []byte("MODEL=x.gguf\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Cas nominal : seul l'ancien dossier existe → il est migré, contenu compris.
|
|
func TestMigrateHomeMovesLegacy(t *testing.T) {
|
|
root := t.TempDir()
|
|
legacy := filepath.Join(root, "jean")
|
|
target := filepath.Join(root, "ajean")
|
|
mkHome(t, legacy)
|
|
|
|
if got := migrateHome(target, legacy); got != target {
|
|
t.Fatalf("got %q, attendu le nouveau chemin %q", got, target)
|
|
}
|
|
if b, err := os.ReadFile(filepath.Join(target, "config.env")); err != nil || string(b) != "MODEL=x.gguf\n" {
|
|
t.Fatalf("contenu non migré: %q (%v)", b, err)
|
|
}
|
|
if isDir(legacy) {
|
|
t.Fatalf("l'ancien dossier existe encore après un rename réussi")
|
|
}
|
|
}
|
|
|
|
// Installation neuve : rien à migrer, on renvoie le nouveau chemin sans rien créer.
|
|
func TestMigrateHomeFreshInstall(t *testing.T) {
|
|
root := t.TempDir()
|
|
target := filepath.Join(root, "ajean")
|
|
if got := migrateHome(target, filepath.Join(root, "jean")); got != target {
|
|
t.Fatalf("got %q, attendu %q", got, target)
|
|
}
|
|
}
|
|
|
|
// Déjà migré : le nouveau dossier existe → on ne retouche à rien, même si un
|
|
// vieux dossier « jean » traîne encore (utilisateur qui l'a recréé, restauration
|
|
// de sauvegarde…). Écraser serait perdre les données courantes.
|
|
func TestMigrateHomeKeepsExistingTarget(t *testing.T) {
|
|
root := t.TempDir()
|
|
legacy := filepath.Join(root, "jean")
|
|
target := filepath.Join(root, "ajean")
|
|
mkHome(t, legacy)
|
|
mkHome(t, target)
|
|
if err := os.WriteFile(filepath.Join(target, "config.env"), []byte("MODEL=courant.gguf\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if got := migrateHome(target, legacy); got != target {
|
|
t.Fatalf("got %q, attendu %q", got, target)
|
|
}
|
|
b, _ := os.ReadFile(filepath.Join(target, "config.env"))
|
|
if string(b) != "MODEL=courant.gguf\n" {
|
|
t.Fatalf("le dossier courant a été écrasé par l'ancien: %q", b)
|
|
}
|
|
if !isDir(legacy) {
|
|
t.Fatalf("l'ancien dossier a été supprimé — la migration ne doit JAMAIS supprimer")
|
|
}
|
|
}
|
|
|
|
// Rename impossible → on doit retomber sur l'ancien chemin, intact. C'est la
|
|
// garantie « au pire, rien n'a bougé », celle qui protège les .gguf.
|
|
//
|
|
// On reproduit le scénario réel : un service AJEAN tourne encore et tient un
|
|
// handle ouvert dans le dossier de données, ce qui fait échouer le rename du
|
|
// dossier sous Windows. Unix, lui, renomme sans broncher un dossier dont des
|
|
// fichiers sont ouverts — il n'y a donc rien à simuler là-bas.
|
|
func TestMigrateHomeFallsBackWhenRenameFails(t *testing.T) {
|
|
if runtime.GOOS != "windows" {
|
|
t.Skip("un rename de dossier réussit avec des fichiers ouverts hors Windows")
|
|
}
|
|
root := t.TempDir()
|
|
legacy := filepath.Join(root, "jean")
|
|
target := filepath.Join(root, "ajean")
|
|
mkHome(t, legacy)
|
|
|
|
held, err := os.Open(filepath.Join(legacy, "config.env"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer held.Close()
|
|
|
|
if got := migrateHome(target, legacy); got != legacy {
|
|
t.Fatalf("got %q, attendu un repli sur l'ancien chemin %q", got, legacy)
|
|
}
|
|
if b, err := os.ReadFile(filepath.Join(legacy, "config.env")); err != nil || string(b) != "MODEL=x.gguf\n" {
|
|
t.Fatalf("l'ancien dossier a été abîmé par une migration ratée: %q (%v)", b, err)
|
|
}
|
|
}
|