mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-23 15:08:16 +02:00
Correctif du worker detache, outil de migration 0.7 dans le depot, CI staticcheck
- uiUserSvcCtl lancait encore « ajean link serve », sous-commande supprimee : l'acces distant etait casse sur macOS et Windows (hors systemd) - tools/migrate-0.7 : reprise d'une installation 0.7.x, hors du binaire pour ne pas reintroduire de compat dans le produit ; procedure complete au README - CI : staticcheck ajoute (le projet est a zero avertissement, il doit le rester) - .gitignore purge de ses entrees mortes (jean, config.env, .api_key...)
This commit is contained in:
1 parent
5daf6fb0eb
commit
35edc631a9
5 files changed
+228
-12
No files matched your search
@@ -18,6 +18,10 @@ jobs:
|
|||||||
- run: go vet ./...
|
- run: go vet ./...
|
||||||
- run: go build ./...
|
- run: go build ./...
|
||||||
- run: go test ./...
|
- run: go test ./...
|
||||||
|
# staticcheck : le projet est à zéro avertissement, et doit le rester.
|
||||||
|
# Les règles écartées et leur raison vivent dans staticcheck.conf.
|
||||||
|
- name: staticcheck
|
||||||
|
run: go run honnef.co/go/tools/cmd/staticcheck@latest ./...
|
||||||
- name: cross-compile (cibles release Linux + Windows)
|
- name: cross-compile (cibles release Linux + Windows)
|
||||||
run: |
|
run: |
|
||||||
# darwin n'est pas ici : depuis v0.5.4 l'icône de barre de menus passe
|
# darwin n'est pas ici : depuis v0.5.4 l'icône de barre de menus passe
|
||||||
|
|||||||
+2
-9
@@ -1,6 +1,6 @@
|
|||||||
# Binaires compilés
|
# Binaires compilés
|
||||||
/jean
|
/ajean
|
||||||
/jean.exe
|
/ajean.exe
|
||||||
/dist/
|
/dist/
|
||||||
|
|
||||||
# Artefacts de build Go
|
# Artefacts de build Go
|
||||||
@@ -8,13 +8,6 @@
|
|||||||
*.test
|
*.test
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
# Config locale / secrets runtime (générés à l'install, jamais versionnés)
|
|
||||||
config.env
|
|
||||||
.api_key
|
|
||||||
.web_key
|
|
||||||
.link_token
|
|
||||||
.link_machine
|
|
||||||
|
|
||||||
# OS / éditeurs
|
# OS / éditeurs
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ sudo mv ajean /usr/local/bin/ajean
|
|||||||
### 2. Installation et compilation du moteur
|
### 2. Installation et compilation du moteur
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo ajean install # unité systemd, sudoers, dossiers
|
sudo ajean install # deux unités systemd, sudoers, dossiers
|
||||||
ajean llamacpp install # compile llama.cpp pour le GPU présent
|
ajean llamacpp install # compile llama.cpp pour le GPU présent
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -305,6 +305,42 @@ CGO_ENABLED=0 go build -o ajean ./cmd/ajean
|
|||||||
- `cmd/ajean/` : point d'entrée + ressources Windows (icône, versioninfo).
|
- `cmd/ajean/` : point d'entrée + ressources Windows (icône, versioninfo).
|
||||||
- `internal/ajean/` : tout le code, fichiers préfixés par domaine (`web_*`, `chat_*`, `llm_*`, `backend_*`, `relay_*`, `sys_*`, `mcp_*`) ; carte dans `doc.go`.
|
- `internal/ajean/` : tout le code, fichiers préfixés par domaine (`web_*`, `chat_*`, `llm_*`, `backend_*`, `relay_*`, `sys_*`, `mcp_*`) ; carte dans `doc.go`.
|
||||||
- `internal/ajean/ui/` : interface web embarquée. **`index.html` est généré** : les sources vivent dans `ui/src/`. Pour modifier l'interface, éditer `ui/src/` puis lancer `go generate ./internal/ajean`.
|
- `internal/ajean/ui/` : interface web embarquée. **`index.html` est généré** : les sources vivent dans `ui/src/`. Pour modifier l'interface, éditer `ui/src/` puis lancer `go generate ./internal/ajean`.
|
||||||
|
- `tools/` : outils hors binaire — `assemble-ui` (génère `index.html`), `gen-icon` (icônes Windows), `migrate-0.7` (reprise d'une installation 0.7.x, voir ci-dessous).
|
||||||
|
|
||||||
|
## Migrer depuis la 0.7.x
|
||||||
|
|
||||||
|
La 0.8 ne contient **aucun code de compatibilité** : elle ignore `config.env`, `configs/`, `MEMORY/` et les fichiers d'état de la 0.7. Sur une machine déjà installée, la reprise se fait une fois, à la main. Rien n'est supprimé : les anciens fichiers restent en place, et un retour arrière consiste à réactiver les anciennes unités.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 0. Sauvegarde (petits fichiers ; les .gguf ne risquent rien, ils ne bougent pas de volume)
|
||||||
|
sudo tar czf /root/ajean-avant-0.8.tar.gz -C /etc/ajean --exclude=backends --exclude='*.gguf' --exclude='*.log' .
|
||||||
|
|
||||||
|
sudo systemctl stop jean-link jean # anciens noms de services
|
||||||
|
|
||||||
|
# 1. Nouvelle arborescence — on COPIE presets et mémoire, on DÉPLACE les modèles
|
||||||
|
sudo mkdir -p /etc/ajean/{presets,memory,models,bin}
|
||||||
|
sudo cp -a /etc/ajean/configs/. /etc/ajean/presets/
|
||||||
|
sudo cp -a /etc/ajean/MEMORY/. /etc/ajean/memory/
|
||||||
|
sudo mv /etc/ajean/*.gguf /etc/ajean/models/
|
||||||
|
|
||||||
|
# 2. Le binaire, puis les réglages dans la base
|
||||||
|
sudo install -m755 ajean /usr/local/bin/ajean
|
||||||
|
go run ./tools/migrate-0.7 /etc/ajean # ou le binaire pré-compilé de l'outil
|
||||||
|
sudo chown -R <utilisateur>:<utilisateur> /etc/ajean
|
||||||
|
|
||||||
|
# 3. Les deux services (remplacent jean.service et jean-link.service)
|
||||||
|
sudo systemctl disable --now jean jean-link
|
||||||
|
sudo ajean install # écrit ajean-engine + ajean-ui, sudoers, /etc/default
|
||||||
|
sudo systemctl enable --now ajean-engine ajean-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
`migrate-0.7` reprend la configuration, les préférences de l'interface, la conversation, les clés (API, pilotage, Crawl4AI), le jeton de liaison et l'identifiant machine, les interrupteurs agent/internet/OpenAI, les serveurs MCP et les benchmarks. Il ne touche ni aux presets, ni à la mémoire, ni aux modèles — ce sont des fichiers, ils ont juste changé de dossier.
|
||||||
|
|
||||||
|
**`.e2e_key` ne doit pas être supprimé** : l'empreinte de chiffrement de bout en bout confirmée dans le portail en dépend. Il reste à sa place, la migration ne le touche pas.
|
||||||
|
|
||||||
|
Vérification : `ajean where`, `ajean switch` (le preset actif doit être détecté), `ajean test`, puis `journalctl -u ajean-ui -n 20` doit afficher « lien établi ✓ » et l'empreinte E2E inchangée.
|
||||||
|
|
||||||
|
Si `ajean install` écrase une unité que vous aviez réglée à la main (priorité CPU, `CUDA_VISIBLE_DEVICES`, utilisateur dédié), rétablissez vos directives dans `/etc/systemd/system/ajean-engine.service` puis `systemctl daemon-reload`.
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
|
|||||||
@@ -416,10 +416,10 @@ func uiUserSvcCtl(action string) error {
|
|||||||
}
|
}
|
||||||
defer logf.Close()
|
defer logf.Close()
|
||||||
|
|
||||||
cmd := spawnDetached(self, "link", "serve")
|
cmd := spawnDetached(self, "web")
|
||||||
cmd.Stdout, cmd.Stderr = logf, logf
|
cmd.Stdout, cmd.Stderr = logf, logf
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
return fmt.Errorf("démarrage de « ajean link serve »: %w", err)
|
return fmt.Errorf("démarrage de « ajean web »: %w", err)
|
||||||
}
|
}
|
||||||
pid := cmd.Process.Pid
|
pid := cmd.Process.Pid
|
||||||
// PID + binaire d'origine : la 2e ligne dit QUELLE copie de l'app a lancé ce
|
// PID + binaire d'origine : la 2e ligne dit QUELLE copie de l'app a lancé ce
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
// migrate-0.7 — reprend une installation AJEAN 0.7.x dans la base de la 0.8.
|
||||||
|
//
|
||||||
|
// USAGE UNIQUE, et volontairement HORS du binaire ajean. La 0.8 ne contient
|
||||||
|
// aucun code de compatibilité : c'est tout l'intérêt de la version. Mais une
|
||||||
|
// machine déjà installée ne doit pas pour autant perdre ses réglages, sa
|
||||||
|
// conversation et ses clés. D'où cet outil séparé, qu'on lance une fois puis
|
||||||
|
// qu'on oublie — le produit reste propre, la migration reste possible.
|
||||||
|
//
|
||||||
|
// go run ./tools/migrate-0.7 [$AJEAN_HOME] (défaut : /etc/ajean)
|
||||||
|
//
|
||||||
|
// Il lit l'ancienne disposition (config.env, webprefs.json, conversation.json,
|
||||||
|
// .api_key, .link_token…) et la recopie dans ajean.db, aux mêmes buckets et aux
|
||||||
|
// mêmes clés que internal/ajean/store.go.
|
||||||
|
//
|
||||||
|
// Il ne SUPPRIME rien : les anciens fichiers restent en place, intacts. Si la
|
||||||
|
// migration s'avère fausse, il suffit d'effacer ajean.db et de la relancer.
|
||||||
|
//
|
||||||
|
// Ce qu'il ne fait PAS, parce que ça se fait mieux au shell (voir le README) :
|
||||||
|
// déplacer configs/ vers presets/, MEMORY/ vers memory/, les .gguf vers
|
||||||
|
// models/, et remplacer les unités systemd.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
bolt "go.etcd.io/bbolt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
bkConfig = "config"
|
||||||
|
bkPrefs = "prefs"
|
||||||
|
bkState = "state"
|
||||||
|
bkChat = "chat"
|
||||||
|
)
|
||||||
|
|
||||||
|
var home = "/etc/ajean"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
home = os.Args[1]
|
||||||
|
}
|
||||||
|
db, err := bolt.Open(filepath.Join(home, "ajean.db"), 0o600, &bolt.Options{Timeout: 5 * time.Second})
|
||||||
|
must(err)
|
||||||
|
defer db.Close()
|
||||||
|
must(db.Update(func(tx *bolt.Tx) error {
|
||||||
|
for _, b := range []string{bkConfig, bkPrefs, bkState, bkChat} {
|
||||||
|
if _, err := tx.CreateBucketIfNotExists([]byte(b)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Configuration du moteur.
|
||||||
|
if cfg := parseEnv(read("config.env")); len(cfg) > 0 {
|
||||||
|
must(putAll(db, bkConfig, cfg))
|
||||||
|
fmt.Printf("config : %d clés\n", len(cfg))
|
||||||
|
} else {
|
||||||
|
fmt.Println("config : ABSENTE — le service ne démarrera pas")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Préférences de l'interface web.
|
||||||
|
var prefs map[string]string
|
||||||
|
if b := read("webprefs.json"); b != "" && json.Unmarshal([]byte(b), &prefs) == nil {
|
||||||
|
must(putAll(db, bkPrefs, prefs))
|
||||||
|
fmt.Printf("prefs : %d clés\n", len(prefs))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clés, jetons et interrupteurs.
|
||||||
|
for key, file := range map[string]string{
|
||||||
|
"api_key": ".api_key",
|
||||||
|
"web_key": ".web_key",
|
||||||
|
"crawl_key": ".crawl4ai_key",
|
||||||
|
"link_token": ".link_token",
|
||||||
|
"link_machine": ".link_machine",
|
||||||
|
} {
|
||||||
|
if v := strings.TrimSpace(read(file)); v != "" {
|
||||||
|
must(put(db, bkState, key, []byte(v)))
|
||||||
|
fmt.Printf("%-12s: repris\n", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Les drapeaux étaient des fichiers dont seule l'EXISTENCE comptait.
|
||||||
|
for key, file := range map[string]string{
|
||||||
|
"agent": ".agent_enabled",
|
||||||
|
"internet": ".internet_enabled",
|
||||||
|
"oai_public": ".oai_public",
|
||||||
|
} {
|
||||||
|
if _, err := os.Stat(filepath.Join(home, file)); err == nil {
|
||||||
|
must(put(db, bkState, key, []byte("1")))
|
||||||
|
fmt.Printf("%-12s: actif\n", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serveurs MCP : on ne garde que la map interne, la clé « mcpServers » du
|
||||||
|
// fichier n'a plus de raison d'être.
|
||||||
|
if b := read("mcp.json"); b != "" {
|
||||||
|
var f struct {
|
||||||
|
MCPServers json.RawMessage `json:"mcpServers"`
|
||||||
|
}
|
||||||
|
if json.Unmarshal([]byte(b), &f) == nil && len(f.MCPServers) > 0 {
|
||||||
|
must(put(db, bkState, "mcp", f.MCPServers))
|
||||||
|
fmt.Println("mcp : repris")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmarks et dossiers de modèles : recopiés tels quels (même forme JSON).
|
||||||
|
for key, file := range map[string]string{
|
||||||
|
"last_bench": ".last_bench.json",
|
||||||
|
"bench_presets": ".bench_presets.json",
|
||||||
|
"model_dirs": "model_dirs.json",
|
||||||
|
} {
|
||||||
|
if b := read(file); b != "" {
|
||||||
|
must(put(db, bkState, key, []byte(b)))
|
||||||
|
fmt.Printf("%-12s: repris\n", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversation partagée.
|
||||||
|
if b := read("conversation.json"); b != "" {
|
||||||
|
must(put(db, bkChat, "conversation", []byte(b)))
|
||||||
|
fmt.Printf("conversation: %d octets\n", len(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("\nTerminé — aucun ancien fichier n'a été supprimé.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func read(name string) string {
|
||||||
|
b, err := os.ReadFile(filepath.Join(home, name))
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func put(db *bolt.DB, bucket, key string, val []byte) error {
|
||||||
|
return db.Update(func(tx *bolt.Tx) error {
|
||||||
|
return tx.Bucket([]byte(bucket)).Put([]byte(key), val)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func putAll(db *bolt.DB, bucket string, m map[string]string) error {
|
||||||
|
return db.Update(func(tx *bolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte(bucket))
|
||||||
|
for k, v := range m {
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte(k), []byte(v)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEnv reprend à l'identique la lecture de backend_config.go.
|
||||||
|
func parseEnv(text string) map[string]string {
|
||||||
|
m := map[string]string{}
|
||||||
|
for _, line := range strings.Split(text, "\n") {
|
||||||
|
s := strings.TrimSpace(line)
|
||||||
|
if s == "" || strings.HasPrefix(s, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
k, v, ok := strings.Cut(strings.TrimPrefix(s, "export "), "=")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m[strings.TrimSpace(k)] = strings.Trim(strings.TrimSpace(v), "\"")
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func must(err error) {
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "migration:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user