mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Conteneurisation : image CUDA autonome (moteur + UI, un seul conteneur)
- Dockerfile 3 étapes : llama.cpp CUDA (flags de backend_build.go, sauf GGML_NATIVE=OFF — l'image est bâtie sur un runner, pas sur la machine cible), binaire Go statique, runtime CUDA léger (tini, git, nodejs pour les serveurs MCP npx). - docker-entrypoint.sh : sème BIN/HOST/PORT (+ MODEL/CTX/NGL depuis l'env, au premier boot seulement), lance le moteur en supervision PID puis 'loki web' au premier plan. UI seule exposée (8090) ; le moteur (8080, non authentifié) reste interne. - Nouvelle commande 'loki config [get|set]' : configuration non interactive (la config vit dans bbolt, pas dans un fichier plat). - docker-compose.yml (build local, réservation GPU) + variante Unraid (image GHCR, runtime nvidia) ; volumes /data (LOKI_HOME) et /models. Un seul conteneur car llm_client.go joint le moteur sur localhost (hérité de l'amont).
This commit is contained in:
8 files changed
+292
No files matched your search
@@ -0,0 +1,10 @@
|
||||
.git
|
||||
.github
|
||||
data/
|
||||
models/
|
||||
docs/
|
||||
dist/
|
||||
*.md
|
||||
!README.md
|
||||
.env
|
||||
.env.*
|
||||
@@ -0,0 +1,19 @@
|
||||
# ── Loki — configuration du build/run Docker ────────────────────────────
|
||||
# (fork d'AJEAN : https://github.com/nathaninline/ajean)
|
||||
|
||||
# Port de l'interface web (identique dedans/dehors).
|
||||
LOKI_WEB_PORT=8090
|
||||
|
||||
# Architectures CUDA compilées (build local uniquement).
|
||||
# 75=Turing (GTX 16xx / RTX 20xx), 86=Ampere (RTX 30xx), 89=Ada (RTX 40xx).
|
||||
# Restreindre à sa carte divise le temps de compilation.
|
||||
CUDA_ARCHS=75;86;89
|
||||
|
||||
# Optionnel — semé au PREMIER démarrage seulement (modifiable ensuite dans
|
||||
# l'UI, qui garde la main) :
|
||||
# Modèle initial : nom d'un .gguf présent dans ./models ou chemin complet.
|
||||
LOKI_MODEL=
|
||||
# Taille de contexte (défaut moteur : 32768)
|
||||
LOKI_CTX=
|
||||
# Couches déportées sur le GPU (défaut : 999 = tout)
|
||||
LOKI_NGL=
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
# ── Loki — image GPU autonome (fork d'AJEAN, https://github.com/nathaninline/ajean)
|
||||
#
|
||||
# Trois étapes :
|
||||
# 1. compilation de llama.cpp avec CUDA (mêmes flags que backend_build.go,
|
||||
# sauf GGML_NATIVE=OFF : l'image est bâtie sur un runner GitHub, pas sur
|
||||
# la machine qui l'exécutera — des instructions CPU « natives » du runner
|
||||
# provoqueraient un Illegal instruction ailleurs) ;
|
||||
# 2. compilation du binaire Go `loki` (UI embarquée via go:embed) ;
|
||||
# 3. runtime CUDA léger : llama-server + loki + entrypoint.
|
||||
#
|
||||
# Pré-requis d'exécution : NVIDIA Container Toolkit sur l'hôte.
|
||||
# docker build -t loki --build-arg CUDA_ARCHS=86 .
|
||||
# CUDA_ARCHS : 75=Turing, 86=Ampere, 89=Ada — restreindre à sa carte divise
|
||||
# le temps de build et la taille de l'image.
|
||||
|
||||
# ── Étape 1 : llama.cpp CUDA ────────────────────────────────────────────
|
||||
FROM nvidia/cuda:12.6.3-devel-ubuntu24.04 AS llamacpp
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
git cmake build-essential ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Épingler LLAMACPP_REF sur un tag (ex. b4991) pour des builds reproductibles.
|
||||
ARG LLAMACPP_REF=master
|
||||
ARG CUDA_ARCHS=75;86;89
|
||||
|
||||
RUN git clone --depth 1 --branch "${LLAMACPP_REF}" \
|
||||
https://github.com/ggml-org/llama.cpp /src/llama.cpp
|
||||
|
||||
RUN cmake -S /src/llama.cpp -B /src/llama.cpp/build \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DGGML_CUDA=ON \
|
||||
-DGGML_CUDA_F16=ON \
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DCMAKE_CUDA_ARCHITECTURES="${CUDA_ARCHS}" \
|
||||
-DLLAMA_CURL=OFF \
|
||||
-DLLAMA_BUILD_TESTS=OFF \
|
||||
-DLLAMA_BUILD_EXAMPLES=OFF \
|
||||
-DLLAMA_BUILD_UI=OFF \
|
||||
-DLLAMA_USE_PREBUILT_UI=OFF \
|
||||
&& cmake --build /src/llama.cpp/build --target llama-server -j"$(nproc)" \
|
||||
&& mkdir -p /opt/llama.cpp \
|
||||
&& find /src/llama.cpp/build -name 'llama-server' -type f -exec cp {} /opt/llama.cpp/ \; \
|
||||
&& find /src/llama.cpp/build -name '*.so*' -exec cp -P {} /opt/llama.cpp/ \;
|
||||
|
||||
# ── Étape 2 : binaire Go loki ───────────────────────────────────────────
|
||||
FROM golang:1.25 AS gobuild
|
||||
WORKDIR /src
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY cmd/ cmd/
|
||||
COPY internal/ internal/
|
||||
COPY tools/ tools/
|
||||
RUN CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o /out/loki ./cmd/loki
|
||||
|
||||
# ── Étape 3 : runtime ───────────────────────────────────────────────────
|
||||
FROM nvidia/cuda:12.6.3-runtime-ubuntu24.04 AS runtime
|
||||
|
||||
# Marqueur de build (sha court), injecté par le workflow GitHub.
|
||||
ARG LOKI_VERSION=dev
|
||||
LABEL org.opencontainers.image.revision="${LOKI_VERSION}" \
|
||||
org.opencontainers.image.source="https://github.com/R0m1k3/Loki" \
|
||||
org.opencontainers.image.description="Loki — fork conteneurisé d'AJEAN (github.com/nathaninline/ajean, MIT)"
|
||||
|
||||
# curl pour le HEALTHCHECK ; git pour les outils de l'agent ; nodejs/npm pour
|
||||
# les serveurs MCP lancés via npx ; libgomp1 pour llama-server ; tini en PID 1.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates curl git libgomp1 tini nodejs npm \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=llamacpp /opt/llama.cpp /opt/llama.cpp
|
||||
COPY --from=gobuild /out/loki /usr/local/bin/loki
|
||||
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh && mkdir -p /data /models
|
||||
|
||||
ENV LOKI_CONTAINER=1 \
|
||||
LOKI_HOME=/data \
|
||||
LOKI_MODEL_DIRS=/models \
|
||||
LD_LIBRARY_PATH=/opt/llama.cpp \
|
||||
NVIDIA_VISIBLE_DEVICES=all \
|
||||
NVIDIA_DRIVER_CAPABILITIES=compute,utility
|
||||
|
||||
# 8090 : interface web (seule à exposer). Le moteur (8080) reste interne au
|
||||
# conteneur — il n'est pas authentifié par défaut.
|
||||
EXPOSE 8090
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
||||
CMD curl -fsS "http://localhost:${LOKI_WEB_PORT:-8090}/" >/dev/null || exit 1
|
||||
|
||||
WORKDIR /data
|
||||
ENTRYPOINT ["tini", "--", "docker-entrypoint.sh"]
|
||||
@@ -0,0 +1,42 @@
|
||||
# ───────────────────────────────────────────────────────────────────────────
|
||||
# Loki — docker-compose pour Unraid (image préconstruite par GitHub Actions)
|
||||
#
|
||||
# Loki est un fork conteneurisé d'AJEAN (github.com/nathaninline/ajean, MIT) :
|
||||
# moteur llama.cpp CUDA + interface web dans UN conteneur autonome.
|
||||
# Aucun build ni git nécessaire sur Unraid — l'image est publiée sur GHCR à
|
||||
# chaque push sur main.
|
||||
#
|
||||
# MISE EN PLACE :
|
||||
# 1) Plugin « Nvidia Driver » (Apps) installé, GPU visible (nvidia-smi).
|
||||
# 2) Crée les dossiers de données (terminal Unraid) :
|
||||
# mkdir -p /mnt/user/appdata/loki/data /mnt/user/appdata/loki/models
|
||||
# 3) Plugin « Compose Manager » -> nouvelle stack -> colle ce fichier.
|
||||
# 4) Compose Up. Interface : http://<ip-unraid>:8090
|
||||
#
|
||||
# Les modèles se téléchargent directement depuis l'UI (catalogue intégré),
|
||||
# ou dépose tes .gguf dans /mnt/user/appdata/loki/models.
|
||||
#
|
||||
# Mise à jour : Compose Down puis Up avec « pull image », ou
|
||||
# « docker compose pull » avant Up.
|
||||
# ───────────────────────────────────────────────────────────────────────────
|
||||
services:
|
||||
loki:
|
||||
image: ghcr.io/r0m1k3/loki:latest
|
||||
container_name: loki
|
||||
runtime: nvidia
|
||||
ports:
|
||||
# Pour changer de port : modifie 8090 à gauche ET LOKI_WEB_PORT… ou
|
||||
# seulement à gauche (ex. "8717:8090") si tu veux garder l'ancien port.
|
||||
- "8090:8090"
|
||||
environment:
|
||||
- NVIDIA_VISIBLE_DEVICES=all
|
||||
# Optionnel : modèle initial (sinon, choisis-le dans l'UI au premier
|
||||
# lancement). Ex. LOKI_MODEL=Qwen3-14B-Q4_K_M.gguf après dépôt du
|
||||
# fichier dans /mnt/user/appdata/loki/models.
|
||||
- LOKI_MODEL=
|
||||
volumes:
|
||||
# /data : config, base, mémoire, workspace, modèles téléchargés par l'UI
|
||||
- /mnt/user/appdata/loki/data:/data
|
||||
# /models : dossier de .gguf déposés à la main (gros fichiers)
|
||||
- /mnt/user/appdata/loki/models:/models
|
||||
restart: unless-stopped
|
||||
@@ -0,0 +1,38 @@
|
||||
# Loki — fork conteneurisé d'AJEAN (https://github.com/nathaninline/ajean, MIT)
|
||||
# Un seul service : le conteneur embarque le moteur llama.cpp (CUDA) ET l'UI.
|
||||
#
|
||||
# cp .env.example .env # ajuste si besoin
|
||||
# docker compose up --build # build local (30-45 min : compilation CUDA)
|
||||
#
|
||||
# Interface : http://localhost:8090
|
||||
# Pré-requis GPU : NVIDIA Container Toolkit sur l'hôte.
|
||||
services:
|
||||
loki:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
# Restreins à ta carte pour un build plus court : 75=Turing (GTX 16xx/RTX 20xx),
|
||||
# 86=Ampere (RTX 30xx), 89=Ada (RTX 40xx).
|
||||
CUDA_ARCHS: ${CUDA_ARCHS:-75;86;89}
|
||||
container_name: loki
|
||||
ports:
|
||||
- "${LOKI_WEB_PORT:-8090}:${LOKI_WEB_PORT:-8090}"
|
||||
environment:
|
||||
- LOKI_WEB_PORT=${LOKI_WEB_PORT:-8090}
|
||||
# Semé UNE fois au premier démarrage (modifiable ensuite dans l'UI) :
|
||||
- LOKI_MODEL=${LOKI_MODEL:-}
|
||||
- LOKI_CTX=${LOKI_CTX:-}
|
||||
- LOKI_NGL=${LOKI_NGL:-}
|
||||
volumes:
|
||||
# /data = LOKI_HOME : config, base bbolt, mémoire, workspace, modèles téléchargés
|
||||
- ./data:/data
|
||||
# /models : dossier(s) de modèles GGUF supplémentaires (LOKI_MODEL_DIRS)
|
||||
- ./models:/models
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: ["gpu"]
|
||||
restart: unless-stopped
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
# Entrypoint du conteneur Loki : sème la configuration au premier démarrage,
|
||||
# lance le moteur (supervision PID, sans systemd), puis l'UI au premier plan.
|
||||
set -eu
|
||||
|
||||
: "${LOKI_HOME:=/data}"
|
||||
mkdir -p "$LOKI_HOME"
|
||||
|
||||
# Le chemin du moteur est imposé par l'image : on le (re)pose à chaque boot,
|
||||
# une mise à jour de l'image ne doit pas laisser un BIN obsolète en base.
|
||||
loki config set "BIN=/opt/llama.cpp/llama-server"
|
||||
|
||||
# Les autres clés ne sont semées QUE si absentes : ce que l'utilisateur règle
|
||||
# ensuite dans l'UI (modèle, contexte…) est conservé d'un redémarrage à l'autre.
|
||||
seed() { # seed CLÉ VALEUR
|
||||
[ -n "$2" ] || return 0
|
||||
cur=$(loki config get "$1")
|
||||
[ -n "$cur" ] || loki config set "$1=$2"
|
||||
}
|
||||
seed HOST "${LOKI_ENGINE_HOST:-127.0.0.1}"
|
||||
seed PORT "${LOKI_ENGINE_PORT:-8080}"
|
||||
seed MODEL "${LOKI_MODEL:-}"
|
||||
seed CTX "${LOKI_CTX:-}"
|
||||
seed NGL "${LOKI_NGL:-}"
|
||||
|
||||
# Moteur : démarrage best-effort. Sans MODEL configuré, on laisse l'UI monter
|
||||
# quand même — le moteur partira au premier modèle choisi/téléchargé dans l'UI.
|
||||
if [ -n "$(loki config get MODEL)" ]; then
|
||||
loki start || echo "[entrypoint] moteur non démarré (voir loki logs) — l'UI reste accessible"
|
||||
else
|
||||
echo "[entrypoint] aucun MODEL configuré — télécharge un modèle depuis l'UI pour démarrer le moteur"
|
||||
fi
|
||||
|
||||
exec loki web "${LOKI_WEB_PORT:-8090}"
|
||||
@@ -0,0 +1,56 @@
|
||||
package loki
|
||||
|
||||
// cli_config.go — lecture/écriture non interactive de la configuration du
|
||||
// moteur (l'équivalent scriptable de `loki edit`). Ajout du fork Loki : le
|
||||
// docker-entrypoint s'en sert pour semer BIN/MODEL/CTX/NGL au premier
|
||||
// démarrage du conteneur, où aucun $EDITOR n'est disponible.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// cmdConfigKV :
|
||||
//
|
||||
// loki config affiche toute la configuration (KEY=VALUE)
|
||||
// loki config get KEY affiche la valeur (vide si absente)
|
||||
// loki config set K=V [K=V…] définit des clés ; une valeur vide supprime
|
||||
func cmdConfigKV(args []string) error {
|
||||
if len(args) == 0 {
|
||||
cfg := ReadConfig()
|
||||
keys := make([]string, 0, len(cfg))
|
||||
for k := range cfg {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
fmt.Printf("%s=%s\n", k, cfg[k])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
switch args[0] {
|
||||
case "get":
|
||||
if len(args) != 2 {
|
||||
return fmt.Errorf("usage : loki config get KEY")
|
||||
}
|
||||
fmt.Println(ReadConfig()[args[1]])
|
||||
return nil
|
||||
case "set":
|
||||
if len(args) < 2 {
|
||||
return fmt.Errorf("usage : loki config set KEY=VALUE [KEY=VALUE…]")
|
||||
}
|
||||
for _, kv := range args[1:] {
|
||||
k, v, ok := strings.Cut(kv, "=")
|
||||
k = strings.TrimSpace(k)
|
||||
if !ok || k == "" {
|
||||
return fmt.Errorf("argument invalide %q — attendu KEY=VALUE", kv)
|
||||
}
|
||||
if err := SetConfigKey(k, strings.TrimSpace(v)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("usage : loki config [get KEY | set KEY=VALUE…]")
|
||||
}
|
||||
@@ -54,6 +54,8 @@ func Main() {
|
||||
mustExit(serviceLogs())
|
||||
case "edit":
|
||||
mustExit(editConfig())
|
||||
case "config":
|
||||
mustExit(cmdConfigKV(args))
|
||||
case "set-api-key":
|
||||
mustExit(cmdSetAPIKey(args))
|
||||
case "set-web-key":
|
||||
|
||||
Reference in new issue
Block a user