feat(scripts): fold model downloads into update.sh, skip if already present

Today's embedding-server crash-loop (missing nomic-embed-text GGUF) was a
manual step nobody ran. update.sh now runs both downloader profiles
itself, every time, before bringing services up -- no separate command to
remember.

- docker-compose.yml: downloader/downloader-embedding commands gain a
  `test -f ... && skip || curl ...` guard, so re-running update.sh never
  re-downloads an existing model file.
- scripts/update.sh: runs both profiles after image pull/build, before
  service recreation.
- scripts/download-model.sh removed -- folded in, redundant standalone
  script.
- README.md / docs/memory-knowledgebase.md updated accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018WHfjWrSEcGhCoeu6dQfDa
This commit is contained in:
2026-09-02 23:07:53 +02:00
co-authored by Claude-Bot
parent fb7cfc9148
commit e2dd106f74
5 changed files with 17 additions and 17 deletions
+1 -2
View File
@@ -7,11 +7,10 @@ See the wayfinder map ([issue #1](https://git.arthurerlich.de/haylan/LLM-Server/
## Quickstart ## Quickstart
```bash ```bash
./scripts/download-model.sh
./scripts/update.sh ./scripts/update.sh
``` ```
`update.sh` creates `.env` from `.env.example` if missing, fills in every secret and per-workload virtual key it can generate itself (random secrets via `openssl`, `OPENWEBUI_LITELLM_KEY`/`LITELLM_PGVECTOR_EMBEDDING_KEY` minted through LiteLLM's own `/key/generate` API, `SEARXNG_LAN_IP` resolved from `search.home` on this host), then pulls/builds/brings up the whole stack. Safe to re-run any time — it only fills in what's still blank and only recreates what changed. See [`docs/proxy-key-onboarding.md`](docs/proxy-key-onboarding.md) if a key mint fails and needs doing by hand. `update.sh` creates `.env` from `.env.example` if missing, fills in every secret and per-workload virtual key it can generate itself (random secrets via `openssl`, `OPENWEBUI_LITELLM_KEY`/`LITELLM_PGVECTOR_EMBEDDING_KEY` minted through LiteLLM's own `/key/generate` API, `SEARXNG_LAN_IP` resolved from `search.home` on this host), downloads both model GGUFs into the `models` volume if they're not there yet, then pulls/builds/brings up the whole stack. Safe to re-run any time — it only fills in what's still blank, skips models already downloaded, and only recreates what changed. See [`docs/proxy-key-onboarding.md`](docs/proxy-key-onboarding.md) if a key mint fails and needs doing by hand.
- Open WebUI: `http://<this-machine>:3000` locally, or `ai.home` / `ai.haylan.ch` once routed through Nginx Proxy Manager — see [`docs/network-access.md`](docs/network-access.md). First signup becomes the admin account (`WEBUI_AUTH` is on). - Open WebUI: `http://<this-machine>:3000` locally, or `ai.home` / `ai.haylan.ch` once routed through Nginx Proxy Manager — see [`docs/network-access.md`](docs/network-access.md). First signup becomes the admin account (`WEBUI_AUTH` is on).
- llama.cpp's own API is internal-only now — everything routes through the AI proxy below. - llama.cpp's own API is internal-only now — everything routes through the AI proxy below.
+10 -3
View File
@@ -66,8 +66,10 @@ services:
networks: [ai-stack] networks: [ai-stack]
# ponytail: one-off downloader, not a standing service — run via # ponytail: one-off downloader, not a standing service — run via
# `docker compose --profile tools run --rm downloader` (see scripts/download-model.sh). # `docker compose --profile tools run --rm downloader`. Folded into
# Keeps the model file inside the named `models` volume instead of a host bind-mount. # scripts/update.sh, which runs this every time; the `test -f` guard is
# what makes that safe to re-run without re-downloading. Keeps the model
# file inside the named `models` volume instead of a host bind-mount.
downloader: downloader:
image: curlimages/curl:latest image: curlimages/curl:latest
profiles: ["tools"] profiles: ["tools"]
@@ -79,11 +81,14 @@ services:
entrypoint: ["sh", "-c"] entrypoint: ["sh", "-c"]
command: command:
- > - >
test -f /models/${LLAMA_MODEL_FILE:-Qwen3.8-27B-UD-Q4_K_XL.gguf} &&
echo "already downloaded, skipping" ||
curl -L --fail --create-dirs -o /models/${LLAMA_MODEL_FILE:-Qwen3.8-27B-UD-Q4_K_XL.gguf} curl -L --fail --create-dirs -o /models/${LLAMA_MODEL_FILE:-Qwen3.8-27B-UD-Q4_K_XL.gguf}
https://huggingface.co/unsloth/Qwen3.8-27B-GGUF/resolve/main/${LLAMA_MODEL_FILE:-Qwen3.8-27B-UD-Q4_K_XL.gguf} https://huggingface.co/unsloth/Qwen3.8-27B-GGUF/resolve/main/${LLAMA_MODEL_FILE:-Qwen3.8-27B-UD-Q4_K_XL.gguf}
# ponytail: same one-off pattern as `downloader`, for the embedding model — # ponytail: same one-off pattern as `downloader`, for the embedding model —
# run via `docker compose --profile tools run --rm downloader-embedding`. # run via `docker compose --profile tools run --rm downloader-embedding`,
# also folded into scripts/update.sh.
downloader-embedding: downloader-embedding:
image: curlimages/curl:latest image: curlimages/curl:latest
profiles: ["tools"] profiles: ["tools"]
@@ -93,6 +98,8 @@ services:
entrypoint: ["sh", "-c"] entrypoint: ["sh", "-c"]
command: command:
- > - >
test -f /models/${EMBEDDING_MODEL_FILE:-nomic-embed-text-v1.5.Q8_0.gguf} &&
echo "already downloaded, skipping" ||
curl -L --fail --create-dirs -o /models/${EMBEDDING_MODEL_FILE:-nomic-embed-text-v1.5.Q8_0.gguf} curl -L --fail --create-dirs -o /models/${EMBEDDING_MODEL_FILE:-nomic-embed-text-v1.5.Q8_0.gguf}
https://huggingface.co/nomic-ai/nomic-embed-text-v1.5-GGUF/resolve/main/${EMBEDDING_MODEL_FILE:-nomic-embed-text-v1.5.Q8_0.gguf} https://huggingface.co/nomic-ai/nomic-embed-text-v1.5-GGUF/resolve/main/${EMBEDDING_MODEL_FILE:-nomic-embed-text-v1.5.Q8_0.gguf}
+2
View File
@@ -32,6 +32,8 @@ New pieces:
### First-time setup ### First-time setup
`./scripts/update.sh` fetches the embedding model automatically (skips it if already downloaded). To do it by hand instead:
```bash ```bash
docker compose --profile tools run --rm downloader-embedding # fetch the embedding model docker compose --profile tools run --rm downloader-embedding # fetch the embedding model
docker compose up -d embedding-server pgvector-db litellm-pgvector docker compose up -d embedding-server pgvector-db litellm-pgvector
-12
View File
@@ -1,12 +0,0 @@
#!/usr/bin/env bash
# Downloads the model GGUF straight into the `models` named volume via a
# one-off container — no huggingface-cli or host bind-mount needed.
#
# ponytail: hardcodes the one model this stack is built for (see the
# `downloader` service in docker-compose.yml for the actual URL/filename).
# Set LLAMA_MODEL_FILE in .env first if you're using a different quant.
set -euo pipefail
cd "$(dirname "$0")/.."
docker compose --profile tools run --rm downloader
echo "Model downloaded into the 'models' volume."
+4
View File
@@ -63,6 +63,10 @@ docker compose pull --ignore-buildable
echo "==> rebuilding local-build services" echo "==> rebuilding local-build services"
docker compose build --pull docker compose build --pull
echo "==> ensuring models are downloaded (skips already-present files)"
docker compose --profile tools run --rm downloader
docker compose --profile tools run --rm downloader-embedding
echo "==> bringing up litellm (needed to mint virtual keys below)" echo "==> bringing up litellm (needed to mint virtual keys below)"
docker compose up -d --wait litellm-db litellm docker compose up -d --wait litellm-db litellm