diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1e76fe5..8c3f3e6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,8 @@ # Release : sur tag vX.Y.Z, compile les 6 cibles et publie la release GitHub -# avec les binaires (mêmes noms d'assets que les releases manuelles historiques) -# ET le fichier SHA256SUMS que `ajean update` (v0.4.6+) vérifie avant d'installer. +# avec les binaires (ajean-linux, ajean-macos, ajean-windows.exe et leurs +# variantes -arm) ET le fichier SHA256SUMS que `ajean update` vérifie avant +# d'installer. Ces noms doivent rester en phase avec assetNameFor (sys_update.go), +# sinon plus aucune machine ne se met à jour. # ⚠️ NE PAS uploader de binaires à la main en plus : ce workflow écrase les # assets du même nom (softprops remplace), et des sommes qui ne correspondent # plus aux binaires font échouer `ajean update` (vécu sur v0.4.7). diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f10f655..fa8c618 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,28 +1,40 @@ Le renommage est terminé. Plus rien ne s'appelle jean : ni le binaire, ni les services, ni les variables, ni les dossiers. Et le dossier de données, qui s'était couvert d'une douzaine de petits fichiers d'état, tient désormais dans six dossiers et une base. -**Une installation 0.7 doit être RÉINSTALLÉE : ne passez pas par le bouton de mise à jour.** Téléchargez le binaire 0.8, puis `sudo ajean install` — il fait la reprise complète. Il déplace les dossiers, reprend les réglages en base, désactive les anciens services et installe les nouveaux. Rien n'est supprimé : presets, mémoire et modèles sont déplacés, les anciens fichiers d'état rangés dans `avant-0.8/`. C'est le seul code de compatibilité de la version, isolé dans un fichier prévu pour être supprimé. +## Mise à jour : il faut réinstaller + +**Ne passez pas par le bouton « mettre à jour » de la 0.7.** Il ne trouvera d'ailleurs rien : les binaires de cette version portent de nouveaux noms, que la 0.7 ne sait pas chercher. C'est délibéré. La laisser installer ce binaire aurait remplacé l'exécutable sans migrer ni les données ni les unités : le service de lien serait reparti en boucle d'échec sur une sous-commande disparue, et le moteur n'aurait plus trouvé sa configuration. Une machine à réparer en SSH après un clic dans un navigateur. + +La marche à suivre, sur une machine déjà installée : + +```bash +curl -L -o ajean https://github.com/nathaninline/ajean/releases/latest/download/ajean-linux +chmod +x ajean && sudo mv ajean /usr/local/bin/ajean +sudo ajean install +``` + +`install` fait la reprise complète : il arrête et désactive les anciens services, déplace `configs/` vers `presets/`, `MEMORY/` vers `memory/` et les `.gguf` vers `models/`, reprend en base la configuration, les préférences, la conversation, les clés, le jeton de liaison, les interrupteurs, les serveurs MCP et les benchmarks, puis installe les deux nouvelles unités. + +Rien n'est supprimé. Les presets, la mémoire et les modèles sont déplacés, jamais copiés ni effacés. Les anciens fichiers d'état sont rangés dans `avant-0.8/`, que vous pourrez supprimer quand tout ira bien. La clé du chiffrement de bout en bout n'est pas touchée, donc l'empreinte confirmée dans le portail reste valable. ## Une base à la place des fichiers d'état -`config.env`, `webprefs.json`, `conversation.json`, `model_dirs.json`, `mcp.json`, `.api_key`, `.web_key`, `.link_token`, `.agent_enabled`, `.internet_enabled` et le reste ont disparu. Tout cela vit maintenant dans **`ajean.db`**, un fichier unique en [bbolt](https://github.com/etcd-io/bbolt) — pur Go, transactionnel, sans dépendance système. +`config.env`, `webprefs.json`, `conversation.json`, `model_dirs.json`, `mcp.json`, `.api_key`, `.web_key`, `.link_token`, `.agent_enabled`, `.internet_enabled` et le reste ont disparu. Tout cela vit maintenant dans **`ajean.db`**, un fichier unique en [bbolt](https://github.com/etcd-io/bbolt), pur Go, transactionnel, sans dépendance système. Ce n'est pas qu'un rangement. Chaque fichier avait sa façon d'être écrit, et donc sa façon de rater une écriture concurrente : l'interface et un tour de chat qui touchaient au même réglage au même instant pouvaient en perdre un. Une transaction remplace tout ça. Une bascule de preset, en particulier, remplace la configuration d'un bloc : il n'existe plus d'instant où elle serait à moitié l'ancienne et à moitié la nouvelle. -Restent des fichiers ceux qui sont faits pour être lus, édités et sauvegardés à la main : les presets, les pages de mémoire, les modèles. `ajean edit` déroule donc la configuration au format `clé=valeur` dans votre éditeur, puis la relit — le fichier n'existe que le temps de l'édition. +Restent des fichiers ceux qui sont faits pour être lus, édités et sauvegardés à la main : les presets, les pages de mémoire, les modèles. `ajean edit` déroule donc la configuration au format `clé=valeur` dans votre éditeur, puis la relit ; le fichier n'existe que le temps de l'édition. -## Six dossiers, et rien d'autre +## Six dossiers -`$AJEAN_HOME` contient `backends/`, `bin/`, `presets/`, `memory/`, `models/`, `workspace/`, plus la base. `configs/` devient `presets/`, `MEMORY/` devient `memory/`, et les `.gguf` ont enfin leur `models/` au lieu d'être posés à la racine. - -Ne restent à la racine que ce qui ne peut pas aller ailleurs : la clé privée du chiffrement de bout en bout, le dossier des certificats TLS, et les journaux et fichiers PID des services. +`$AJEAN_HOME` contient `backends/`, `bin/`, `presets/`, `memory/`, `models/`, `workspace/`, plus la base. `configs/` devient `presets/`, `MEMORY/` devient `memory/`, et les `.gguf` ont enfin leur `models/` au lieu d'être posés à la racine. Ne restent à côté que ce qui ne peut pas aller ailleurs : la clé privée du chiffrement de bout en bout, le dossier des certificats TLS, les journaux et les fichiers PID des services. ## Deux services qui portent enfin leur nom `ajean.service` et `ajean-link.service` deviennent **`ajean-engine`** et **`ajean-ui`**. Le premier exécute le modèle, le second sert l'interface web, le tunnel d'accès distant et l'endpoint OpenAI. Le nom « link » cachait l'essentiel : ce service est d'abord le serveur web. -Surtout, il n'y a plus **qu'une seule façon** de servir l'interface. Avant, `ajean web` et `ajean link serve` savaient tous deux le faire, ce qui posait un piège permanent : lancer les deux, c'était un conflit sur le port 8090 et surtout deux fils de conversation qui divergeaient — la conversation vit en mémoire, deux process qui la servent finissent par s'écraser l'un l'autre. La consigne « ne jamais lancer `ajean web` » circulait comme une règle à retenir ; elle n'existait que parce que le code offrait deux portes pour la même pièce. +Surtout, il n'y a plus **qu'une seule façon** de servir l'interface. Avant, `ajean web` et `ajean link serve` savaient tous deux le faire, ce qui posait un piège permanent : lancer les deux, c'était un conflit sur le port 8090 et surtout deux fils de conversation qui divergeaient (la conversation vit en mémoire, deux process qui la servent finissent par s'écraser l'un l'autre). La consigne « ne jamais lancer `ajean web` » circulait comme une règle à retenir ; elle n'existait que parce que le code offrait deux portes pour la même pièce. -Désormais `ajean web` est cette porte unique : il sert l'interface et, si un jeton de liaison est enregistré, ouvre le tunnel dans le même process. `ajean link` ne s'occupe plus que du compte — jeton, code d'appairage, état — et `ajean ui start|stop|restart|status` pilote le service. `ajean link serve`, `link start`, `link stop` et `link restart` disparaissent. +Désormais `ajean web` est cette porte unique : il sert l'interface et, si un jeton de liaison est enregistré, ouvre le tunnel dans le même process. `ajean link` ne s'occupe plus que du compte (jeton, code d'appairage, état), et `ajean ui start|stop|restart|status` pilote le service. `ajean link serve`, `link start`, `link stop` et `link restart` disparaissent. Séparer les deux services garde son intérêt : redémarrer l'interface est instantané, alors que redémarrer le moteur recharge des dizaines de gigaoctets. @@ -30,8 +42,10 @@ Séparer les deux services garde son intérêt : redémarrer l'interface est ins Tout le code écrit pour ménager les installations « jean » : la migration du dossier de données et ses reprises après échec, la migration de l'agencement système (unités, `/etc/default`, réécriture des chemins), l'élévation Windows qu'elle demandait, la résolution du nom d'unité réellement installée, la reprise des fichiers PID et des skills, les alias `jean` posés à l'installation, les variables `JEAN_*` lues en second. -La CLI perd ses alias hérités — `skills`, `machine`, `tools`, `web-access`, `mem`, `upgrade`, `self-update`, `paths`, `llama` — et son aide est réorganisée autour des deux services. Chaque commande a désormais un seul nom. `app` quitte l'aide : c'est le comportement du double-clic, qu'on n'atteint pas en tapant son nom. +La CLI perd ses alias hérités (`skills`, `machine`, `tools`, `web-access`, `mem`, `upgrade`, `self-update`, `paths`, `llama`) et son aide est réorganisée autour des deux services. Chaque commande a désormais un seul nom. `app` quitte l'aide : c'est le comportement du double-clic, qu'on n'atteint pas en tapant son nom. -Les releases ne publient plus qu'un jeu de binaires, et sous des noms lisibles : `ajean-linux`, `ajean-linux-arm`, `ajean-macos`, `ajean-macos-arm`, `ajean-windows.exe`, `ajean-windows-arm.exe`. La double publication qui accompagnait la transition n'a plus d'objet. +Les binaires publiés prennent des noms lisibles : `ajean-linux`, `ajean-linux-arm`, `ajean-macos`, `ajean-macos-arm`, `ajean-windows.exe`, `ajean-windows-arm.exe`. Le suffixe `-arm` désigne l'arm64, son absence l'x86-64. -Ce changement de noms n'est pas cosmétique. Les versions 0.7 cherchent leur mise à jour sous la forme `ajean--` : ne trouvant aucun asset qui corresponde, leur bouton « mettre à jour » échoue proprement, sans rien remplacer. C'est délibéré. Laisser la 0.7 installer ce binaire aurait remplacé l'exécutable sans migrer les données ni les unités : le service de lien serait reparti en boucle d'échec sur une sous-commande disparue, et le moteur n'aurait plus trouvé sa configuration — une machine à réparer en SSH après un clic dans un navigateur. +## Ce qui n'a pas été testé + +La reprise 0.7 vers 0.8 a été vérifiée sur Linux (un serveur réel, avec 9 presets, 24 pages de mémoire et 8 modèles) et sur Windows (dossier de test complet), plus par trois tests automatisés. **Elle n'a pas été essayée sur macOS**, faute de machine : le support macOS reste globalement non validé sur du matériel Apple. Sauvegardez `$AJEAN_HOME` avant de vous lancer. diff --git a/internal/ajean/migrate_07.go b/internal/ajean/migrate_07.go index 0cfd203..777fa9e 100644 --- a/internal/ajean/migrate_07.go +++ b/internal/ajean/migrate_07.go @@ -131,6 +131,12 @@ func migrateFrom07(home string) error { if err := importLegacyState(home); err != nil { return err } + // SKILLS/ : les skills ont été fondus dans la mémoire avant la 0.8, le + // dossier ne sert plus à rien mais peut contenir du travail de l'utilisateur. + // On l'archive au lieu de le laisser traîner ou de l'effacer. + if hasEntry(home, "SKILLS") { + _ = os.Rename(filepath.Join(home, "SKILLS"), filepath.Join(home, "avant-0.8", "SKILLS")) + } fmt.Printf("%s reprise terminée — les anciens fichiers sont dans avant-0.8/\n\n", green("[ok]")) return nil } diff --git a/internal/ajean/migrate_07_test.go b/internal/ajean/migrate_07_test.go index 1c663fa..f36a115 100644 --- a/internal/ajean/migrate_07_test.go +++ b/internal/ajean/migrate_07_test.go @@ -39,6 +39,7 @@ func fake07Home(t *testing.T) string { write("mcp.json", `{"mcpServers":{"fs":{"command":"npx","enabled":true}}}`) write("sysprompt.txt", "Tu es AJEAN.\n") write("conversation.json", `{"seq":42,"messages":[{"role":"user","content":"salut"}]}`) + write("SKILLS/vieux-skill/SKILL.md", "# un skill d'avant\n") return home } @@ -102,8 +103,13 @@ func TestMigration07(t *testing.T) { } // Les fichiers consommés sont rangés, pas détruits. - if _, err := os.Stat(filepath.Join(home, "avant-0.8", "config.env")); err != nil { - t.Error("config.env n'a pas été conservée dans avant-0.8/") + for _, archive := range []string{"config.env", "SKILLS"} { + if _, err := os.Stat(filepath.Join(home, "avant-0.8", archive)); err != nil { + t.Errorf("%s n'a pas été conservé dans avant-0.8/", archive) + } + } + if hasEntry(home, "SKILLS") { + t.Error("SKILLS/ traîne encore à la racine") } // Idempotence : relancer ne doit plus rien voir à migrer.