mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-23 15:08:16 +02:00
v0.3.4 : gestion des mises à jour depuis l'interface web (section « Mise à jour » : vérifier + installer, endpoints /api/update et /api/update/apply)
This commit is contained in:
1 parent
cd58382d73
commit
c50ebdb6d1
7 files changed
+136
-40
No files matched your search
@@ -16,7 +16,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Version = "0.3.3"
|
const Version = "0.3.4"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Migration one-shot des anciens skills (SKILLS/<nom>/SKILL.md) vers la
|
// Migration one-shot des anciens skills (SKILLS/<nom>/SKILL.md) vers la
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
+33
-1
@@ -395,6 +395,11 @@ button:disabled{opacity:.5;cursor:not-allowed}
|
|||||||
<button id="btn-bench" onclick="runBenchUI()">⚡ bench</button>
|
<button id="btn-bench" onclick="runBenchUI()">⚡ bench</button>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
<details id="upd-details"><summary>Mise à jour</summary>
|
||||||
|
<div class="muted" style="font-size:12px;margin-bottom:8px">Version installée : <b id="upd-cur">…</b></div>
|
||||||
|
<div class="row"><button id="upd-check" onclick="checkUpdate()">↻ Vérifier les mises à jour</button></div>
|
||||||
|
<div id="upd-msg" class="muted" style="font-size:12px;margin-top:8px;line-height:1.5"></div>
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
<div class="main" style="position:relative">
|
<div class="main" style="position:relative">
|
||||||
<div id="chat"></div>
|
<div id="chat"></div>
|
||||||
@@ -640,7 +645,34 @@ async function loadStatus(){
|
|||||||
el.innerHTML='<span class="dot"></span>'+txt+' <span class="port">:'+s.port+'</span>';
|
el.innerHTML='<span class="dot"></span>'+txt+' <span class="port">:'+s.port+'</span>';
|
||||||
MODEL_READY = !!(s.active && s.health);
|
MODEL_READY = !!(s.active && s.health);
|
||||||
if(s.ctx){ CTX_MAX=s.ctx; updateCtxMeter(); }
|
if(s.ctx){ CTX_MAX=s.ctx; updateCtxMeter(); }
|
||||||
if(s.version){ document.getElementById('ver').textContent='v'+s.version; }
|
if(s.version){
|
||||||
|
document.getElementById('ver').textContent='v'+s.version;
|
||||||
|
const uc=document.getElementById('upd-cur'); if(uc) uc.textContent='v'+s.version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function checkUpdate(){
|
||||||
|
const b=document.getElementById('upd-check'), msg=document.getElementById('upd-msg');
|
||||||
|
b.disabled=true; msg.textContent='Vérification…';
|
||||||
|
try{
|
||||||
|
const r=await jget('/api/update');
|
||||||
|
if(r.current){ document.getElementById('upd-cur').textContent='v'+r.current; }
|
||||||
|
if(r.error){ msg.textContent='Erreur : '+r.error; }
|
||||||
|
else if(r.available){
|
||||||
|
msg.innerHTML='Nouvelle version <b>v'+r.latest+'</b> disponible. ';
|
||||||
|
const btn=document.createElement('button'); btn.textContent='Mettre à jour'; btn.onclick=applyUpdate;
|
||||||
|
msg.appendChild(btn);
|
||||||
|
} else { msg.textContent='Jean est à jour ✓'; }
|
||||||
|
}catch(e){ msg.textContent='Erreur réseau'; }
|
||||||
|
b.disabled=false;
|
||||||
|
}
|
||||||
|
async function applyUpdate(){
|
||||||
|
const msg=document.getElementById('upd-msg');
|
||||||
|
msg.textContent='Téléchargement et installation…';
|
||||||
|
try{
|
||||||
|
const r=await jpost('/api/update/apply',{});
|
||||||
|
if(r.ok){ msg.innerHTML='✓ Installé en <b>v'+r.version+'</b>.<br>'+(r.restart||''); }
|
||||||
|
else { msg.textContent='Échec : '+(r.error||'inconnu'); }
|
||||||
|
}catch(e){ msg.textContent='Erreur pendant la mise à jour (réessaie).'; }
|
||||||
}
|
}
|
||||||
// Compteur de contexte : CTX_USED estimé via les stats serveur (prefill+decode
|
// Compteur de contexte : CTX_USED estimé via les stats serveur (prefill+decode
|
||||||
// du dernier tour ≈ taille du prochain prompt). À 90% on propose de compacter.
|
// du dernier tour ≈ taille du prochain prompt). À 90% on propose de compacter.
|
||||||
|
|||||||
@@ -69,32 +69,46 @@ func fetchLatestRelease() (*ghRelease, error) {
|
|||||||
return &rel, nil
|
return &rel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdUpdate(args []string) error {
|
// updateInfo décrit l'état de mise à jour (partagé CLI + UI web).
|
||||||
checkOnly := false
|
type updateInfo struct {
|
||||||
for _, a := range args {
|
Current string `json:"current"`
|
||||||
if a == "--check" || a == "-check" || a == "check" {
|
Latest string `json:"latest"`
|
||||||
checkOnly = true
|
Available bool `json:"available"`
|
||||||
}
|
URL string `json:"url"`
|
||||||
}
|
}
|
||||||
fmt.Println("recherche de la dernière version…")
|
|
||||||
|
// checkForUpdate interroge GitHub et compare à la version courante. Réutilisé
|
||||||
|
// par `jean update` (CLI) et par l'endpoint web /api/update.
|
||||||
|
func checkForUpdate() (updateInfo, error) {
|
||||||
|
info := updateInfo{Current: Version}
|
||||||
rel, err := fetchLatestRelease()
|
rel, err := fetchLatestRelease()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("impossible de contacter GitHub : %w", err)
|
return info, err
|
||||||
}
|
}
|
||||||
latest := ensureV(rel.TagName)
|
latest := ensureV(rel.TagName)
|
||||||
cur := ensureV(Version)
|
|
||||||
if !semver.IsValid(latest) {
|
if !semver.IsValid(latest) {
|
||||||
return fmt.Errorf("tag de release inattendu : %q", rel.TagName)
|
return info, fmt.Errorf("tag de release inattendu : %q", rel.TagName)
|
||||||
}
|
}
|
||||||
if semver.Compare(latest, cur) <= 0 {
|
info.Latest = strings.TrimPrefix(latest, "v")
|
||||||
fmt.Printf("jean est déjà à jour (%s).\n", Version)
|
info.URL = rel.HTMLURL
|
||||||
return nil
|
info.Available = semver.Compare(latest, ensureV(Version)) > 0
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyUpdate télécharge et installe le binaire de la dernière release pour
|
||||||
|
// l'OS/arch courant. Renvoie la nouvelle version. Ne redémarre AUCUN service
|
||||||
|
// (voir printRestartHint / le message renvoyé à l'UI).
|
||||||
|
func applyUpdate() (string, error) {
|
||||||
|
rel, err := fetchLatestRelease()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("impossible de contacter GitHub : %w", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("nouvelle version disponible : %s (actuelle : %s)\n", strings.TrimPrefix(latest, "v"), Version)
|
latest := ensureV(rel.TagName)
|
||||||
fmt.Printf(" %s\n", rel.HTMLURL)
|
if !semver.IsValid(latest) {
|
||||||
if checkOnly {
|
return "", fmt.Errorf("tag de release inattendu : %q", rel.TagName)
|
||||||
fmt.Println("lance 'jean update' pour l'installer.")
|
}
|
||||||
return nil
|
if semver.Compare(latest, ensureV(Version)) <= 0 {
|
||||||
|
return Version, fmt.Errorf("déjà à jour (%s)", Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
want := updateAssetName()
|
want := updateAssetName()
|
||||||
@@ -107,45 +121,71 @@ func cmdUpdate(args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if url == "" {
|
if url == "" {
|
||||||
return fmt.Errorf("aucun binaire %q dans la release %s (os/arch %s/%s)", want, latest, runtime.GOOS, runtime.GOARCH)
|
return "", fmt.Errorf("aucun binaire %q dans la release %s (os/arch %s/%s)", want, latest, runtime.GOOS, runtime.GOARCH)
|
||||||
}
|
}
|
||||||
|
|
||||||
exe, err := os.Executable()
|
exe, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
if resolved, err := filepath.EvalSymlinks(exe); err == nil {
|
if resolved, err := filepath.EvalSymlinks(exe); err == nil {
|
||||||
exe = resolved
|
exe = resolved
|
||||||
}
|
}
|
||||||
dir := filepath.Dir(exe)
|
tmp := filepath.Join(filepath.Dir(exe), ".jean-update.tmp")
|
||||||
tmp := filepath.Join(dir, ".jean-update.tmp")
|
|
||||||
|
|
||||||
fmt.Printf("téléchargement de %s (%.1f Mo)…\n", want, float64(size)/1e6)
|
|
||||||
if err := downloadTo(url, tmp); err != nil {
|
if err := downloadTo(url, tmp); err != nil {
|
||||||
return fmt.Errorf("téléchargement : %w", err)
|
return "", fmt.Errorf("téléchargement : %w", err)
|
||||||
}
|
}
|
||||||
// Conserver les permissions du binaire existant (sinon 0755 par défaut).
|
|
||||||
mode := os.FileMode(0o755)
|
mode := os.FileMode(0o755)
|
||||||
if fi, err := os.Stat(exe); err == nil {
|
if fi, err := os.Stat(exe); err == nil {
|
||||||
mode = fi.Mode()
|
mode = fi.Mode()
|
||||||
}
|
}
|
||||||
if err := os.Chmod(tmp, mode); err != nil {
|
if err := os.Chmod(tmp, mode); err != nil {
|
||||||
os.Remove(tmp)
|
os.Remove(tmp)
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
if got := fileSize(tmp); size > 0 && got != size {
|
if got := fileSize(tmp); size > 0 && got != size {
|
||||||
os.Remove(tmp)
|
os.Remove(tmp)
|
||||||
return fmt.Errorf("taille inattendue (%d o reçus, %d attendus) — mise à jour annulée", got, size)
|
return "", fmt.Errorf("taille inattendue (%d o reçus, %d attendus) — mise à jour annulée", got, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := replaceBinary(exe, tmp); err != nil {
|
if err := replaceBinary(exe, tmp); err != nil {
|
||||||
os.Remove(tmp)
|
os.Remove(tmp)
|
||||||
if os.IsPermission(err) {
|
if os.IsPermission(err) {
|
||||||
return fmt.Errorf("droits insuffisants pour écrire %s — relance avec privilèges (ex : sudo jean update)", exe)
|
return "", fmt.Errorf("droits insuffisants pour écrire %s — relance avec privilèges (ex : sudo jean update)", exe)
|
||||||
}
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return strings.TrimPrefix(latest, "v"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdUpdate(args []string) error {
|
||||||
|
checkOnly := false
|
||||||
|
for _, a := range args {
|
||||||
|
if a == "--check" || a == "-check" || a == "check" {
|
||||||
|
checkOnly = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("recherche de la dernière version…")
|
||||||
|
info, err := checkForUpdate()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("impossible de contacter GitHub : %w", err)
|
||||||
|
}
|
||||||
|
if !info.Available {
|
||||||
|
fmt.Printf("jean est déjà à jour (%s).\n", Version)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fmt.Printf("nouvelle version disponible : %s (actuelle : %s)\n", info.Latest, Version)
|
||||||
|
fmt.Printf(" %s\n", info.URL)
|
||||||
|
if checkOnly {
|
||||||
|
fmt.Println("lance 'jean update' pour l'installer.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fmt.Printf("téléchargement de %s…\n", updateAssetName())
|
||||||
|
newVer, err := applyUpdate()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf("✓ jean mis à jour en %s\n", strings.TrimPrefix(latest, "v"))
|
fmt.Printf("✓ jean mis à jour en %s\n", newVer)
|
||||||
printRestartHint()
|
printRestartHint()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -212,10 +252,32 @@ func cleanupOldBinary() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func printRestartHint() {
|
// handleUpdateCheck (GET /api/update) : renvoie l'état de mise à jour pour l'UI.
|
||||||
if runtime.GOOS == "windows" {
|
func handleUpdateCheck(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Println("redémarre les process jean en cours (jean web, etc.) pour appliquer la mise à jour.")
|
info, err := checkForUpdate()
|
||||||
|
if err != nil {
|
||||||
|
sendJSON(w, 200, map[string]any{"current": Version, "available": false, "error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("pense à redémarrer les services : sudo systemctl restart jean jean-link (+ relancer 'jean web' si utilisé).")
|
sendJSON(w, 200, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleUpdateApply (POST /api/update/apply) : télécharge et installe la dernière
|
||||||
|
// version, puis renvoie la version installée + l'indice de redémarrage.
|
||||||
|
func handleUpdateApply(w http.ResponseWriter, r *http.Request) {
|
||||||
|
newVer, err := applyUpdate()
|
||||||
|
if err != nil {
|
||||||
|
sendJSON(w, 200, map[string]any{"ok": false, "error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sendJSON(w, 200, map[string]any{"ok": true, "version": newVer, "restart": restartHintText()})
|
||||||
|
}
|
||||||
|
|
||||||
|
func restartHintText() string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return "Redémarre Jean (quitte puis relance) pour appliquer la mise à jour."
|
||||||
|
}
|
||||||
|
return "Redémarre les services pour appliquer : sudo systemctl restart jean jean-link"
|
||||||
|
}
|
||||||
|
|
||||||
|
func printRestartHint() { fmt.Println(restartHintText()) }
|
||||||
+3
-3
@@ -3,13 +3,13 @@
|
|||||||
"FileVersion": {
|
"FileVersion": {
|
||||||
"Major": 0,
|
"Major": 0,
|
||||||
"Minor": 3,
|
"Minor": 3,
|
||||||
"Patch": 3,
|
"Patch": 4,
|
||||||
"Build": 0
|
"Build": 0
|
||||||
},
|
},
|
||||||
"ProductVersion": {
|
"ProductVersion": {
|
||||||
"Major": 0,
|
"Major": 0,
|
||||||
"Minor": 3,
|
"Minor": 3,
|
||||||
"Patch": 3,
|
"Patch": 4,
|
||||||
"Build": 0
|
"Build": 0
|
||||||
},
|
},
|
||||||
"FileFlagsMask": "3f",
|
"FileFlagsMask": "3f",
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"LegalCopyright": "Copyright (c) 2026 Jean contributors. MIT License.",
|
"LegalCopyright": "Copyright (c) 2026 Jean contributors. MIT License.",
|
||||||
"OriginalFilename": "jean.exe",
|
"OriginalFilename": "jean.exe",
|
||||||
"ProductName": "Jean",
|
"ProductName": "Jean",
|
||||||
"ProductVersion": "0.3.3",
|
"ProductVersion": "0.3.4",
|
||||||
"Comments": "https://github.com/nathaninline/jean — projet open source (MIT)"
|
"Comments": "https://github.com/nathaninline/jean — projet open source (MIT)"
|
||||||
},
|
},
|
||||||
"VarFileInfo": {
|
"VarFileInfo": {
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ func newWebMux() *http.ServeMux {
|
|||||||
api("/api/vram", handleVram)
|
api("/api/vram", handleVram)
|
||||||
api("/api/config", handleConfigEnv)
|
api("/api/config", handleConfigEnv)
|
||||||
api("/api/catalog", handleCatalog)
|
api("/api/catalog", handleCatalog)
|
||||||
|
api("/api/update", handleUpdateCheck)
|
||||||
|
api("/api/update/apply", handleUpdateApply)
|
||||||
api("/api/models", handleModels)
|
api("/api/models", handleModels)
|
||||||
api("/api/models/delete", handleModelDelete)
|
api("/api/models/delete", handleModelDelete)
|
||||||
api("/api/models/download", handleModelDownload)
|
api("/api/models/download", handleModelDownload)
|
||||||
|
|||||||
Reference in new issue
Block a user