mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Perte de données en conteneur : alerte si /data n'est pas monté
Modèles, discussions et fichiers disparaissaient à chaque recréation du conteneur quand /data n'était pas un volume : tout vivait dans la couche éphémère. Loki le détecte maintenant (mountinfo) et le dit — bandeau rouge dans l'UI (/api/status warn), avertissement en tête du journal, et l'entrypoint exporte LOKI_HOME pour ses sous-commandes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
11a4aebf37
commit
fb908b180b
3 files changed
+87
-1
No files matched your search
@@ -4,8 +4,17 @@
|
|||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
: "${LOKI_HOME:=/data}"
|
: "${LOKI_HOME:=/data}"
|
||||||
|
export LOKI_HOME # les sous-commandes loki doivent voir le MEME dossier
|
||||||
mkdir -p "$LOKI_HOME"
|
mkdir -p "$LOKI_HOME"
|
||||||
|
|
||||||
|
# Garde-fou persistance : si /data n'est pas un point de montage, tout ce que
|
||||||
|
# l'utilisateur fera (modèles, discussions, fichiers) mourra avec le conteneur.
|
||||||
|
# On le dit au premier écran du journal — l'UI l'affiche aussi en bandeau.
|
||||||
|
if ! grep -qs " $LOKI_HOME " /proc/self/mountinfo; then
|
||||||
|
echo "[entrypoint] ⚠️ $LOKI_HOME n'est pas un volume monté : les données seront PERDUES à la recréation du conteneur."
|
||||||
|
echo "[entrypoint] Ajoute « -v /chemin/hote/data:/data » (docker run) ou le mapping volumes du docker-compose."
|
||||||
|
fi
|
||||||
|
|
||||||
# Le chemin du moteur est imposé par l'image (llama-server précompilé de
|
# Le chemin du moteur est imposé par l'image (llama-server précompilé de
|
||||||
# l'image officielle llama.cpp, dans /app) : on le (re)pose à chaque boot,
|
# l'image officielle llama.cpp, dans /app) : on le (re)pose à chaque boot,
|
||||||
# une mise à jour de l'image ne doit pas laisser un BIN obsolète en base.
|
# une mise à jour de l'image ne doit pas laisser un BIN obsolète en base.
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package loki
|
||||||
|
|
||||||
|
// sys_datavolume.go — garde-fou contre la PERTE DE DONNÉES en conteneur.
|
||||||
|
//
|
||||||
|
// Le symptôme vécu : modèles, discussions et fichiers disparus après un
|
||||||
|
// redémarrage du serveur. La cause, dans tous les cas observés : /data n'est
|
||||||
|
// PAS un volume monté (template Docker sans mapping, `docker run` sans -v,
|
||||||
|
// chemin hôte qui change d'un lancement à l'autre). Tout vit alors dans la
|
||||||
|
// couche d'écriture du conteneur, qui meurt avec lui à la première recréation
|
||||||
|
// (mise à jour d'image, compose down/up, redémarrage de l'array Unraid).
|
||||||
|
//
|
||||||
|
// Loki ne peut pas monter le volume à la place de l'utilisateur, mais il peut
|
||||||
|
// le VOIR et le DIRE : au démarrage en conteneur, si LOKI_HOME n'est pas un
|
||||||
|
// point de montage, on le journalise en rouge et l'UI l'affiche en bandeau.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dataDirIsMounted dit si LokiHome() est un point de montage (bind mount ou
|
||||||
|
// volume Docker). Lecture de /proc/self/mountinfo — Linux seulement, or c'est
|
||||||
|
// le seul endroit où Loki tourne en conteneur. Hors conteneur : sans objet.
|
||||||
|
//
|
||||||
|
// Renvoie (monté, connu) : connu=false quand on n'a pas pu lire mountinfo —
|
||||||
|
// on ne crie pas au loup sur une simple erreur de lecture.
|
||||||
|
func dataDirIsMounted() (mounted, known bool) {
|
||||||
|
f, err := os.Open("/proc/self/mountinfo")
|
||||||
|
if err != nil {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
target := filepath.Clean(LokiHome())
|
||||||
|
sc := bufio.NewScanner(f)
|
||||||
|
for sc.Scan() {
|
||||||
|
// Format mountinfo : champs séparés par des espaces, le 5ᵉ (index 4)
|
||||||
|
// est le point de montage. Les espaces dans les chemins sont encodés
|
||||||
|
// \040 — décodés avant comparaison.
|
||||||
|
fields := strings.Fields(sc.Text())
|
||||||
|
if len(fields) < 5 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mp := strings.ReplaceAll(fields[4], `\040`, " ")
|
||||||
|
if filepath.Clean(mp) == target {
|
||||||
|
return true, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, sc.Err() == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dataVolumeWarning renvoie le message d'alerte si les données sont en périple
|
||||||
|
// ("" si tout va bien). Évalué au démarrage du serveur web et servi par
|
||||||
|
// /api/status pour le bandeau de l'UI.
|
||||||
|
func dataVolumeWarning() string {
|
||||||
|
if os.Getenv("LOKI_CONTAINER") == "" {
|
||||||
|
return "" // hors conteneur : l'utilisateur gère ses chemins
|
||||||
|
}
|
||||||
|
mounted, known := dataDirIsMounted()
|
||||||
|
if !known || mounted {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return "⚠️ " + LokiHome() + " n'est PAS un volume monté : modèles, discussions et fichiers seront PERDUS à la recréation du conteneur (mise à jour, redémarrage du serveur). " +
|
||||||
|
"Ajoute un mapping de volume — compose : « - /mnt/user/appdata/loki/data:/data » (et « …/models:/models ») ; " +
|
||||||
|
"docker run : « -v /chemin/hote/data:/data ». Voir docker-compose.unraid.yml du dépôt."
|
||||||
|
}
|
||||||
@@ -44,6 +44,16 @@ func handleStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
if !health {
|
if !health {
|
||||||
loadErr = modelLoadError()
|
loadErr = modelLoadError()
|
||||||
}
|
}
|
||||||
|
// Alerte de persistance : /data non monté en conteneur = données perdues à
|
||||||
|
// la prochaine recréation. Même bandeau que les autres avertissements.
|
||||||
|
warn := appWarning()
|
||||||
|
if dv := dataVolumeWarning(); dv != "" {
|
||||||
|
if warn != "" {
|
||||||
|
warn += " — " + dv
|
||||||
|
} else {
|
||||||
|
warn = dv
|
||||||
|
}
|
||||||
|
}
|
||||||
sendJSON(w, 200, map[string]any{
|
sendJSON(w, 200, map[string]any{
|
||||||
"state": state,
|
"state": state,
|
||||||
"active": active,
|
"active": active,
|
||||||
@@ -51,7 +61,7 @@ func handleStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
"port": LLMPort(),
|
"port": LLMPort(),
|
||||||
"ctx": ctx,
|
"ctx": ctx,
|
||||||
"version": Version,
|
"version": Version,
|
||||||
"warn": appWarning(), // ex. App Translocation macOS — vide si tout va bien
|
"warn": warn, // App Translocation macOS, /data non monté… — vide si tout va bien
|
||||||
"load_error": loadErr, // modèle qui ne charge pas (incompat moteur…) — vide sinon
|
"load_error": loadErr, // modèle qui ne charge pas (incompat moteur…) — vide sinon
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user