first try
This commit is contained in:
1 parent
b10435f5f7
commit
99517f483a
14 files changed
+822
No files matched your search
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# Einmalig ausführen: sudo ./scripts/firewall-ufw.sh
|
||||
# Öffnet den Caddy-Port nur für LAN und NetBird (wt0). Caddy filtert zusätzlich selbst.
|
||||
set -euo pipefail
|
||||
|
||||
PORT="${LISTEN_PORT:-8800}"
|
||||
LAN="${LAN_NET:-192.168.178.0/24}"
|
||||
VPN_IF="${VPN_IF:-wt0}"
|
||||
|
||||
ufw allow from "$LAN" to any port "$PORT" proto tcp comment 'localai LAN'
|
||||
ufw allow in on "$VPN_IF" to any port "$PORT" proto tcp comment 'localai NetBird'
|
||||
ufw status numbered | grep -E "$PORT" || true
|
||||
Executable
+150
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env bash
|
||||
# Startet den LocalAI-Stack, wartet bis er bereit ist und zeigt die Verbindungsdaten.
|
||||
#
|
||||
# ./scripts/start.sh starten + Chat-Modell vorladen
|
||||
# ./scripts/start.sh --no-preload nur starten
|
||||
# ./scripts/start.sh --model glm-4.7-flash anderes Chat-Modell vorladen
|
||||
# ./scripts/start.sh --show-key API-Key im Klartext ausgeben
|
||||
# ./scripts/start.sh --pull Images vorher aktualisieren
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$(readlink -f "$0")")/.."
|
||||
|
||||
PRELOAD=1
|
||||
MODEL="qwen3.5-35b-a3b"
|
||||
SHOW_KEY=0
|
||||
PULL=0
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--no-preload) PRELOAD=0 ;;
|
||||
--model) MODEL="$2"; shift ;;
|
||||
--show-key) SHOW_KEY=1 ;;
|
||||
--pull) PULL=1 ;;
|
||||
-h|--help) sed -n '2,9p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
*) echo "Unbekannte Option: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ -t 1 ]]; then B=$'\e[1m'; G=$'\e[32m'; Y=$'\e[33m'; R=$'\e[31m'; N=$'\e[0m'; else B= G= Y= R= N=; fi
|
||||
ok() { echo "${G}✔${N} $*"; }
|
||||
warn() { echo "${Y}!${N} $*"; }
|
||||
die() { echo "${R}✘${N} $*" >&2; exit 1; }
|
||||
step() { echo; echo "${B}== $*${N}"; }
|
||||
|
||||
# ---------------------------------------------------------------- Prüfungen
|
||||
step "Prüfungen"
|
||||
|
||||
command -v docker >/dev/null || die "docker nicht installiert"
|
||||
docker info >/dev/null 2>&1 || die "Docker-Daemon läuft nicht oder keine Rechte (Gruppe 'docker'?)"
|
||||
docker compose version >/dev/null 2>&1 || die "docker compose (v2) fehlt"
|
||||
ok "Docker $(docker version --format '{{.Server.Version}}')"
|
||||
|
||||
[[ -e /dev/kfd && -d /dev/dri ]] || die "/dev/kfd oder /dev/dri fehlt – AMD-GPU-Treiber (amdgpu/ROCm) prüfen"
|
||||
ok "GPU-Geräte vorhanden"
|
||||
|
||||
if [[ ! -f .env ]]; then
|
||||
warn ".env fehlt – wird aus .env.example erzeugt"
|
||||
cp .env.example .env
|
||||
sed -i "s/^LLM_API_KEY=.*/LLM_API_KEY=$(openssl rand -hex 32)/" .env
|
||||
sed -i "s/^HOST_UID=.*/HOST_UID=$(id -u)/; s/^HOST_GID=.*/HOST_GID=$(id -g)/" .env
|
||||
rg=$(getent group render | cut -d: -f3 || true); vg=$(getent group video | cut -d: -f3 || true)
|
||||
[[ -n "$rg" ]] && sed -i "s/^RENDER_GID=.*/RENDER_GID=$rg/" .env
|
||||
[[ -n "$vg" ]] && sed -i "s/^VIDEO_GID=.*/VIDEO_GID=$vg/" .env
|
||||
chmod 600 .env
|
||||
warn "Bitte ALLOWED_NETS in .env prüfen (LAN-/NetBird-Netz)"
|
||||
fi
|
||||
|
||||
set -a; . ./.env; set +a
|
||||
LISTEN_PORT="${LISTEN_PORT:-8800}"
|
||||
BACKEND_PORT="${BACKEND_PORT:-8080}"
|
||||
[[ -n "${LLM_API_KEY:-}" && "$LLM_API_KEY" != "CHANGE_ME" ]] || die "LLM_API_KEY in .env setzen (openssl rand -hex 32)"
|
||||
ok ".env geladen"
|
||||
|
||||
# Ports dürfen nur von unserem eigenen Stack belegt sein
|
||||
running=$(docker compose ps --status running -q 2>/dev/null | wc -l)
|
||||
if [[ "$running" -eq 0 ]]; then
|
||||
for p in "$LISTEN_PORT" "$BACKEND_PORT"; do
|
||||
if ss -Htln "sport = :$p" | grep -q .; then
|
||||
die "Port $p ist bereits belegt: $(ss -Htlnp "sport = :$p" | awk '{print $NF}' | head -1) – in .env ändern"
|
||||
fi
|
||||
done
|
||||
ok "Ports $LISTEN_PORT und $BACKEND_PORT frei"
|
||||
fi
|
||||
|
||||
if systemctl is-active --quiet ollama 2>/dev/null; then
|
||||
warn "Host-Ollama läuft noch (belegt ggf. VRAM, lauscht auf dem Netz) → sudo systemctl disable --now ollama"
|
||||
fi
|
||||
|
||||
if command -v ufw >/dev/null && systemctl is-active --quiet ufw 2>/dev/null; then
|
||||
if [[ -r /etc/ufw/user.rules ]] && ! grep -q "dport $LISTEN_PORT" /etc/ufw/user.rules; then
|
||||
warn "Keine ufw-Freigabe für Port $LISTEN_PORT gefunden → sudo ./scripts/firewall-ufw.sh"
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p models
|
||||
free_gb=$(df -BG --output=avail models | tail -1 | tr -dc '0-9')
|
||||
(( free_gb < 30 )) && warn "Nur ${free_gb} GB frei – Modell-Downloads brauchen bis zu 25 GB"
|
||||
|
||||
# ------------------------------------------------------------------- Start
|
||||
step "Starte Container"
|
||||
(( PULL )) && docker compose pull
|
||||
docker compose up -d --remove-orphans
|
||||
|
||||
step "Warte auf Bereitschaft"
|
||||
for i in $(seq 1 60); do
|
||||
curl -fsS -m 2 "http://127.0.0.1:$BACKEND_PORT/health" >/dev/null 2>&1 && break
|
||||
(( i == 60 )) && die "llama-swap antwortet nicht – docker compose logs llama-swap"
|
||||
sleep 1
|
||||
done
|
||||
ok "llama-swap bereit"
|
||||
|
||||
for i in $(seq 1 30); do
|
||||
code=$(curl -s -o /dev/null -m 2 -w '%{http_code}' "http://127.0.0.1:$LISTEN_PORT/v1/models" || true)
|
||||
[[ "$code" == "401" ]] && break
|
||||
(( i == 30 )) && die "Caddy antwortet nicht wie erwartet (HTTP $code) – docker compose logs caddy"
|
||||
sleep 1
|
||||
done
|
||||
code=$(curl -s -o /dev/null -m 5 -w '%{http_code}' -H "Authorization: Bearer $LLM_API_KEY" "http://127.0.0.1:$LISTEN_PORT/v1/models")
|
||||
[[ "$code" == "200" ]] || die "API mit Key liefert HTTP $code"
|
||||
ok "Caddy bereit (Auth aktiv)"
|
||||
|
||||
# ---------------------------------------------------------------- Preload
|
||||
if (( PRELOAD )); then
|
||||
step "Lade Modelle vor (erster Start lädt ggf. von Hugging Face herunter)"
|
||||
preload() { # $1 = Modell, $2 = Endpoint, $3 = JSON-Body
|
||||
local t0=$SECONDS code
|
||||
printf ' %-22s … ' "$1"
|
||||
code=$(curl -s -o /dev/null -m 3600 -w '%{http_code}' -H 'Content-Type: application/json' \
|
||||
"http://127.0.0.1:$BACKEND_PORT$2" -d "$3" || true)
|
||||
if [[ "$code" == "200" ]]; then echo "${G}ok${N} ($((SECONDS - t0)) s)"; else echo "${R}HTTP $code${N}"; fi
|
||||
}
|
||||
preload qwen2.5-coder-1.5b /v1/completions '{"model":"qwen2.5-coder-1.5b","prompt":"x","max_tokens":1}'
|
||||
preload nomic-embed /v1/embeddings '{"model":"nomic-embed","input":"x"}'
|
||||
preload "$MODEL" /v1/chat/completions "{\"model\":\"$MODEL\",\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}],\"max_tokens\":1}"
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------ Status
|
||||
step "Status"
|
||||
docker compose ps --format '{{.Service}}: {{.Status}}' | sed 's/^/ /'
|
||||
loaded=$(curl -s -m 5 "http://127.0.0.1:$BACKEND_PORT/running" \
|
||||
| python3 -c 'import sys,json; print(", ".join(m["model"] for m in json.load(sys.stdin)["running"]) or "-")' 2>/dev/null || echo "?")
|
||||
echo " geladen: $loaded"
|
||||
command -v rocm-smi >/dev/null && rocm-smi --showmeminfo vram 2>/dev/null \
|
||||
| awk '/GPU\[0\].*Total Memory/{t=$NF} /GPU\[0\].*Used Memory/{u=$NF} END{if(t) printf " VRAM: %.1f / %.1f GB\n", u/1e9, t/1e9}'
|
||||
|
||||
step "Verbindung"
|
||||
lan_ip=$(ip -4 -o addr show scope global | awk '$2 !~ /^(wt|docker|virbr|br-|veth)/ {split($4,a,"/"); print a[1]; exit}')
|
||||
nb_ip=$(ip -4 -o addr show wt0 2>/dev/null | awk '{split($4,a,"/"); print a[1]}')
|
||||
nb_fqdn=$(netbird status 2>/dev/null | awk '/^FQDN:/{print $2}' || true)
|
||||
echo " lokal: http://127.0.0.1:$LISTEN_PORT/v1"
|
||||
[[ -n "$lan_ip" ]] && echo " LAN: http://$lan_ip:$LISTEN_PORT/v1"
|
||||
[[ -n "$nb_ip" ]] && echo " NetBird: http://$nb_ip:$LISTEN_PORT/v1${nb_fqdn:+ (http://$nb_fqdn:$LISTEN_PORT/v1)}"
|
||||
if (( SHOW_KEY )); then
|
||||
echo " API-Key: $LLM_API_KEY"
|
||||
else
|
||||
echo " API-Key: ${LLM_API_KEY:0:6}… (vollständig: --show-key)"
|
||||
fi
|
||||
echo " Web-UI: http://127.0.0.1:$BACKEND_PORT/ui (nur lokal)"
|
||||
echo
|
||||
echo " Clients einrichten: docs/opencode.md, docs/continue.md"
|
||||
Reference in new issue
Block a user