Stand up open-webui on dankclaw (quadlet, boot-persistent)
Replace the dead dankserver open-webui (bare container, :3000, pointed at a dead :9090 backend) with a boot-persistent quadlet on dankclaw: - CPU image (:main); dankclaw is CPU-only, so the :cuda image is wasted. - Isolated on slirp4netns; publishes 3005 (3000 is Gitea's HTTP port). - Default LLM backend is the local vLLM on 52625 (frank-default-large). - Data persists under /home/dank/Containers/open-webui/data. - Caddy route now proxies to the published LAN port (the container is not on Caddy's network). Also corrects the model/port mapping in the docs: the live state (verified 2026-08-19) is frank-default-large on vLLM :52625 (dankclaw) and frank-default-small on llama.cpp :7770 (dankserver) — the reverse of the 2026-08-17 note.
This commit is contained in:
+3
-1
@@ -31,11 +31,13 @@ comfyui.dankclaw.local {
|
||||
}
|
||||
|
||||
# --- OpenWebUI ---
|
||||
# open-webui is a quadlet container on slirp4netns (isolated), so Caddy cannot
|
||||
# reach it by container name. Proxy to its published LAN port instead.
|
||||
chat.dankclaw.local {
|
||||
basicauth {
|
||||
dank $2a$14$REPLACE_WITH_HASH
|
||||
}
|
||||
reverse_proxy openwebui:8080
|
||||
reverse_proxy 192.168.0.143:3005
|
||||
}
|
||||
|
||||
# --- Hermes ---
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# Quadlet unit: open-webui (rootless, boot-persistent).
|
||||
#
|
||||
# Install path on dankclaw: ~/.config/containers/systemd/open-webui.container
|
||||
# Unit name: open-webui.service (container name: open-webui)
|
||||
#
|
||||
# Chat UI for the LLM fleet. It runs on the CPU image (dankclaw is CPU-only, so
|
||||
# the :cuda image would waste a large pull for a GPU it cannot use) and points
|
||||
# its OpenAI connection at the live vLLM backend on this host
|
||||
# (host.containers.internal:52625, model frank-default-large). It is isolated on
|
||||
# slirp4netns and publishes 3005 (3000 is Gitea's HTTP port). Data persists under
|
||||
# /home/dank/Containers/open-webui/data. See docs/Dankclaw/OpenWebUI.md.
|
||||
|
||||
[Unit]
|
||||
Description=open-webui chat UI container
|
||||
After=network.target
|
||||
|
||||
[Container]
|
||||
ContainerName=open-webui
|
||||
Network=slirp4netns
|
||||
Image=ghcr.io/open-webui/open-webui:main
|
||||
PublishPort=3005:8080
|
||||
Environment=OPENAI_API_BASE_URL=http://host.containers.internal:52625
|
||||
Environment=ANONYMIZED_TELEMETRY=False
|
||||
Environment=DO_NOT_TRACK=True
|
||||
Volume=/home/dank/Containers/open-webui/data:/app/backend/data
|
||||
|
||||
[Service]
|
||||
TimeoutStartSec=900
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -1,10 +1,10 @@
|
||||
# Dankclaw Stack
|
||||
|
||||
Dankclaw (192.168.0.143) runs the AI, search, and development stack. It
|
||||
hosts the vLLM inference endpoint (`frank-default-small`, `:52625`, from
|
||||
hosts the vLLM inference endpoint (`frank-default-large`, `:52625`, from
|
||||
the `vllmScripts` repo), the agent tools, the privacy search, and the
|
||||
observability stack. The llama.cpp endpoint (`frank-default-large`,
|
||||
`:7770`) runs on Dankserver (192.168.0.235) since the 2026-08-17 redo.
|
||||
observability stack. The llama.cpp endpoint (`frank-default-small`,
|
||||
`:7770`) runs on Dankserver (192.168.0.235).
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
@@ -26,25 +26,26 @@ graph LR
|
||||
|---------|----------|------|------|
|
||||
| SearXNG | search.dankclaw.local | 8081 | [[SearXNG]] |
|
||||
| ComfyUI | comfyui.dankclaw.local | 8188 | [[ComfyUI]] |
|
||||
| OpenWebUI | chat.dankclaw.local | 3000 | [[OpenWebUI]] |
|
||||
| OpenWebUI | chat.dankclaw.local | 3005 | [[OpenWebUI]] |
|
||||
| Hermes | hermes.dankclaw.local | 8090 | [[Hermes]] |
|
||||
| Gitea | gitea.dankclaw.local | 3000 | [[Gitea]] |
|
||||
| Grafana | grafana.dankclaw.local | 3002 | [[Grafana]] |
|
||||
| Prometheus | prometheus.dankclaw.local | 9090 | [[Prometheus]] |
|
||||
| Homepage | dankclaw.local | 80/443 | — |
|
||||
| frank-default-large (host service, Dankserver) | — | 7770 | [vllmScripts](../../vllmScripts/docs/index.md) |
|
||||
| frank-default-small (host service) | — | 52625 | [vllmScripts](../../vllmScripts/docs/index.md) |
|
||||
| frank-default-large (host service, Dankclaw) | — | 52625 | [vllmScripts](../../vllmScripts/docs/index.md) |
|
||||
| frank-default-small (host service, Dankserver) | — | 7770 | [vllmScripts](../../vllmScripts/docs/index.md) |
|
||||
|
||||
The last two rows are bare-metal systemd services, not containers. The
|
||||
`vllmScripts` repository owns their scripts and knowledge base. Since the
|
||||
2026-08-17 redo, `frank-default-large` runs llama.cpp FastMTP on
|
||||
Dankserver; `frank-default-small` runs vLLM on Dankclaw.
|
||||
`vllmScripts` repository owns their scripts and knowledge base. The live
|
||||
mapping (verified 2026-08-19) is the reverse of the 2026-08-17 note:
|
||||
`frank-default-large` runs vLLM on Dankclaw; `frank-default-small` runs
|
||||
llama.cpp FastMTP on Dankserver.
|
||||
|
||||
## Boot Persistence
|
||||
|
||||
The Gitea trio and the SearXNG trio (gluetun, searxng, valkey) run as quadlet units, so they start at boot. User linger is on. ComfyUI runs as a systemd unit around a bare process. The remaining services in the table are defined in `compose.yaml` and are not yet boot-persistent.
|
||||
The Gitea trio, the SearXNG trio (gluetun, searxng, valkey), and open-webui run as quadlet units, so they start at boot. User linger is on. ComfyUI runs as a systemd unit around a bare process. The remaining services in the table are defined in `compose.yaml` and are not yet boot-persistent.
|
||||
|
||||
Deploy the SearXNG trio with `scripts/deploy-searxng-quadlet.sh dankclaw`. The Gitea units install to `~/.config/containers/systemd/` (see [[Gitea]]).
|
||||
Deploy the SearXNG trio with `scripts/deploy-searxng-quadlet.sh dankclaw` and open-webui with `scripts/deploy-open-webui-quadlet.sh dankclaw`. The Gitea units install to `~/.config/containers/systemd/` (see [[Gitea]]).
|
||||
|
||||
## Routing Rules
|
||||
|
||||
|
||||
+38
-32
@@ -1,60 +1,66 @@
|
||||
# OpenWebUI
|
||||
|
||||
OpenWebUI is a self-hosted chat interface for LLMs. It connects to OpenAI-compatible endpoints, Ollama, llama.cpp, and vLLM, and adds RAG, plugins, tool calling, and multi-user support.
|
||||
OpenWebUI is a self-hosted chat interface for LLMs. It connects to OpenAI-compatible endpoints (vLLM, llama.cpp, Ollama) and adds RAG, plugins, tool calling, and multi-user support.
|
||||
|
||||
**Host:** Dankclaw — **Hostname:** `chat.dankclaw.local` — **Port:** 3000 — **Docs:** https://github.com/open-webui/open-webui
|
||||
**Host:** Dankclaw — **Port:** 3005 (host) → 8080 (container) — **Boot-persistent:** Quadlet — **Docs:** https://github.com/open-webui/open-webui
|
||||
|
||||
## Facts (from research, primary source)
|
||||
## Facts
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| Image | `ghcr.io/open-webui/open-webui:main` (CPU build for non-NVIDIA hosts) |
|
||||
| Port | 3000 (host) → 8080 (container) |
|
||||
| Data volume | config, sessions, RAG indexes |
|
||||
| Backend | OpenAI-compatible endpoint at `http://192.168.0.235:7770/v1` — llama.cpp FastMTP on Dankserver (`frank-default-large`, Qwen3.8-27B IQ4_XS, 128K ctx) |
|
||||
| Image | `ghcr.io/open-webui/open-webui:main` (CPU build; Dankclaw is CPU-only) |
|
||||
| Port | 3005 (host) → 8080 (container). Port 3000 is Gitea's HTTP, so it is not usable. |
|
||||
| Data volume | `/home/dank/Containers/open-webui/data` → `/app/backend/data` |
|
||||
| Default backend | vLLM on Dankclaw `:52625` = `frank-default-large` (via `host.containers.internal:52625`) |
|
||||
| Network | `slirp4netns` (isolated) |
|
||||
| Persistence | Quadlet unit `open-webui.service`; `[Install] WantedBy=default.target` |
|
||||
| Deploy | `scripts/deploy-open-webui-quadlet.sh dankclaw` |
|
||||
|
||||
> Note: the `:cuda` image tag was previously used on Dankserver. Dankclaw is AMD; use the default (CPU) image, NOT the cuda tag.
|
||||
## The LLM backends
|
||||
|
||||
## Connecting to llama.cpp
|
||||
The fleet runs two OpenAI-compatible endpoints. This mapping is the **live** state (verified 2026-08-19); it is the reverse of the 2026-08-17 note, which is now out of date.
|
||||
|
||||
OpenWebUI talks to the llama.cpp server on Dankserver through its OpenAI-compatible API. Configure a new connection in the UI or set the base URL:
|
||||
| Endpoint | Model name | Host | Capability |
|
||||
|----------|-----------|------|-----------|
|
||||
| `:52625` (vLLM) | `frank-default-large` | Dankclaw | completion |
|
||||
| `:7770` (llama.cpp FastMTP) | `frank-default-small` | Dankserver | completion, multimodal |
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
OPENAI_API_BASE_URL: "http://192.168.0.235:7770/v1"
|
||||
OPENAI_API_KEY: "none"
|
||||
```
|
||||
The default connection points at the local vLLM (`frank-default-large`), so chat stays on-host. To add the other backend, open **Admin → Connections** in the UI and point an OpenAI connection at `http://192.168.0.235:7770/v1` (key `none`).
|
||||
|
||||
The endpoint moved from Dankclaw to Dankserver in the 2026-08-17 redo; use the LAN address, not `host.containers.internal` (which only reaches the podman host).
|
||||
> Both endpoints are bare-metal systemd services managed by the sibling `vllmScripts` repository (not this repo). Model swaps, context sizing, and spec tuning happen there. See the [vllmScripts knowledge base](../../vllmScripts/docs/index.md).
|
||||
|
||||
> The `:7770` endpoint is the `frank-default-large` service on Dankserver,
|
||||
> managed by the sibling `vllmScripts` repository (not this repo). Model
|
||||
> swaps, context sizing, and spec tuning happen there. See the
|
||||
> [vllmScripts knowledge base](../../vllmScripts/docs/index.md).
|
||||
>
|
||||
> Alternative backend: `frank-default-small` on port 52625 (vLLM on
|
||||
> Dankclaw, nameistoken Qwen3.6-35B-A3B Quark W8A8 INT8, 262K ctx,
|
||||
> tool calling). Same OpenAI-compatible API shape. Point
|
||||
> `OPENAI_API_BASE_URL` at `http://192.168.0.143:52625/v1`.
|
||||
## Boot persistence (Quadlet)
|
||||
|
||||
The unit lives at `dankclaw/open-webui/quadlet/open-webui.container`. On the host it installs to `~/.config/containers/systemd/open-webui.container`. The generator turns it into `open-webui.service`. Because the unit sets `[Install] WantedBy=default.target` and user linger is on, it starts at boot. `systemctl --user is-enabled open-webui.service` reports `generated` (the quadlet boot-start state).
|
||||
|
||||
## Volumes
|
||||
|
||||
| Container path | Host path (convention) |
|
||||
|----------------|------------------------|
|
||||
| `/app/backend/data` | `/opt/openwebui/data` |
|
||||
| Container path | Host path |
|
||||
|----------------|-----------|
|
||||
| `/app/backend/data` | `/home/dank/Containers/open-webui/data` |
|
||||
|
||||
The data dir holds the database, RAG indexes, and uploads. The deploy script creates it if missing and preserves it across reruns.
|
||||
|
||||
## Healthcheck
|
||||
|
||||
OpenWebUI responds on `/health` with 200.
|
||||
OpenWebUI responds on `/health` with 200. Probe it at `http://dankclaw:3005/health`.
|
||||
|
||||
## Retired instances
|
||||
|
||||
- The compose `open-webui` service (port 3000) was never run: 3000 is Gitea's HTTP port, so it could not bind.
|
||||
- The bare-container `open-webui` on Dankserver (port 3000) pointed at a dead backend (`:9090`) and was removed. Its data dir is preserved on Dankserver in case it is needed.
|
||||
- The bare-container `searxng` on Dankserver (port 8081) was removed. SearXNG now runs only on Dankclaw behind Caddy.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Use the non-CUDA image on AMD hosts.
|
||||
- Use the non-CUDA image on this CPU-only host.
|
||||
- The default admin account is created on first login; secure it immediately.
|
||||
- If the backend (llama.cpp) is slow to load, OpenWebUI may show connection errors until the model is ready — that matches the known 3–8 minute model load time.
|
||||
- If the backend is loading (3–8 minute model load), OpenWebUI shows connection errors until the model is ready.
|
||||
- `host.containers.internal` reaches only the podman host. To reach the Dankserver backend, use the LAN address `192.168.0.235:7770`.
|
||||
|
||||
## See Also
|
||||
|
||||
- [[Dankclaw Stack]]
|
||||
- [[Hermes]] — the agent OpenWebUI can talk to
|
||||
- [vllmScripts knowledge base](../../vllmScripts/docs/index.md) — the llama.cpp FastMTP and vLLM endpoints (Dankserver :7770, Dankclaw :52625)
|
||||
- [[SearXNG]] — the search engine on this host
|
||||
- [vllmScripts knowledge base](../../vllmScripts/docs/index.md) — the vLLM and llama.cpp endpoints
|
||||
|
||||
Executable
+103
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env bash
|
||||
# deploy-open-webui-quadlet.sh — Install and (re)start open-webui on dankclaw.
|
||||
#
|
||||
# open-webui is boot-persistent via Quadlet (see AGENTS.md). This script:
|
||||
# 1. Pushes main to origin
|
||||
# 2. Pulls the repo on the target
|
||||
# 3. Creates the data dir and copies the unit into ~/.config/containers/systemd/
|
||||
# 4. daemon-reloads and starts open-webui (first run pulls the image)
|
||||
#
|
||||
# It is idempotent: it stops any running unit and removes any container with the
|
||||
# name (a prior run or a bare-container leftover) before reinstalling and
|
||||
# starting. The data dir is preserved across reruns, so existing chats survive.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/deploy-open-webui-quadlet.sh <target> [--dry-run]
|
||||
# ./scripts/deploy-open-webui-quadlet.sh dankclaw
|
||||
set -euo pipefail
|
||||
|
||||
REMOTE_USER="dank"
|
||||
REMOTE_HOST="dankclaw"
|
||||
REMOTE_DIR="/home/dank/serverAppConfigs"
|
||||
QDIR="dankclaw/open-webui/quadlet"
|
||||
UNIT="open-webui.service"
|
||||
DATA_DIR="/home/dank/Containers/open-webui/data"
|
||||
|
||||
TARGET="${1:-}"
|
||||
DRY_RUN=0
|
||||
if [ "${2:-}" = "--dry-run" ]; then
|
||||
DRY_RUN=1
|
||||
fi
|
||||
|
||||
if [ -z "${TARGET:-}" ]; then
|
||||
echo "Usage: $0 <target> [--dry-run]"
|
||||
exit 1
|
||||
fi
|
||||
if [ "${TARGET}" != "dankclaw" ]; then
|
||||
echo "This stack runs only on dankclaw (got: ${TARGET})"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Local pre-flight
|
||||
# ---------------------------------------------------------------------------
|
||||
cd "$(dirname "$0")/.."
|
||||
if ! git diff --quiet HEAD; then
|
||||
echo "FAIL: Local working tree has uncommitted changes. Commit first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$DRY_RUN" = "1" ]; then
|
||||
echo "[dry-run] Would push main, pull on ${REMOTE_HOST}, install ${UNIT}, and start it (data at ${DATA_DIR})."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "=== Deploying open-webui quadlet to ${REMOTE_HOST} ==="
|
||||
echo "[1/3] Pushing local main to origin..."
|
||||
git push origin main
|
||||
|
||||
REMOTE="${REMOTE_USER}@${REMOTE_HOST}"
|
||||
|
||||
echo "[2/3] Installing and starting ${UNIT} on ${REMOTE_HOST}..."
|
||||
ssh "$REMOTE" bash -s <<REMOTE_EOF
|
||||
set -euo pipefail
|
||||
cd '${REMOTE_DIR}'
|
||||
|
||||
echo ' Pulling repo...'
|
||||
git pull origin main
|
||||
|
||||
echo ' Stopping any running unit (idempotent)...'
|
||||
systemctl --user stop '${UNIT}' 2>/dev/null || true
|
||||
|
||||
echo ' Removing any container with the name (prior run or bare-container leftover)...'
|
||||
podman rm -f open-webui 2>/dev/null || true
|
||||
|
||||
echo ' Creating the data dir (preserved across reruns)...'
|
||||
mkdir -p '${DATA_DIR}'
|
||||
|
||||
echo ' Copying the quadlet unit into ~/.config/containers/systemd/ ...'
|
||||
mkdir -p ~/.config/containers/systemd
|
||||
cp ${QDIR}/open-webui.container ~/.config/containers/systemd/
|
||||
|
||||
echo ' daemon-reload (generates the unit)...'
|
||||
# Quadlet units are generator-managed: systemctl enable fails on them. The
|
||||
# [Install] WantedBy=default.target in the .container file is itself the
|
||||
# enablement (is-enabled reports "generated"); with user linger on it starts at
|
||||
# boot. So: daemon-reload, then start.
|
||||
systemctl --user daemon-reload
|
||||
|
||||
echo ' Starting (first run pulls the image; this can take a while)...'
|
||||
systemctl --user start '${UNIT}'
|
||||
|
||||
echo ' Unit state:'
|
||||
systemctl --user --no-pager status '${UNIT}' 2>&1 | grep -E "●|Active:" || true
|
||||
REMOTE_EOF
|
||||
|
||||
echo "[3/3] Verifying open-webui is up..."
|
||||
ssh "$REMOTE" '
|
||||
echo " is-enabled: $(systemctl --user is-enabled open-webui.service 2>&1) (boot-persistent)"
|
||||
echo " port 3005:"; (ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null) | grep -E ":3005\b" || echo " (not listening yet)"
|
||||
echo " HTTP probe:"; curl -s -o /dev/null -w " http://127.0.0.1:3005 -> %{http_code}\n" -m 10 http://127.0.0.1:3005/ || echo " (no HTTP yet)"
|
||||
' || true
|
||||
|
||||
echo "=== open-webui quadlet deployed to ${REMOTE_HOST} ==="
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bats
|
||||
# Seam: open-webui on dankclaw, boot-persistent via Quadlet.
|
||||
#
|
||||
# The old open-webui was a bare container on dankserver (port 3000) pointing at a
|
||||
# dead LLM backend (:9090). This unit stands one up on dankclaw on a free port
|
||||
# (3005), on the CPU image (dankclaw is CPU-only), wired to the live vLLM
|
||||
# backend (host.containers.internal:52625, model frank-default-large).
|
||||
|
||||
load helpers
|
||||
|
||||
QDIR="$REPO_ROOT/dankclaw/open-webui/quadlet"
|
||||
|
||||
@test "the open-webui quadlet file exists" {
|
||||
[ -f "$QDIR/open-webui.container" ]
|
||||
}
|
||||
|
||||
@test "open-webui uses the CPU image, not :cuda (dankclaw is CPU-only)" {
|
||||
grep -qE '^Image=ghcr\.io/open-webui/open-webui:main$' "$QDIR/open-webui.container" \
|
||||
|| { echo "open-webui.container must use the :main (CPU) image"; return 1; }
|
||||
if grep -qE '^Image=.*:cuda' "$QDIR/open-webui.container"; then
|
||||
echo "open-webui.container must not use the :cuda image on a CPU-only host"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
@test "open-webui is isolated on slirp4netns" {
|
||||
grep -qE '^Network=slirp4netns' "$QDIR/open-webui.container" \
|
||||
|| { echo "open-webui.container must set Network=slirp4netns"; return 1; }
|
||||
}
|
||||
|
||||
@test "open-webui publishes 3005:8080 (a free port; 3000 is Gitea)" {
|
||||
grep -qE '^PublishPort=3005:8080' "$QDIR/open-webui.container" \
|
||||
|| { echo "open-webui.container must publish 3005:8080"; return 1; }
|
||||
}
|
||||
|
||||
@test "open-webui points its LLM at the live vLLM backend (52625)" {
|
||||
grep -qE '^Environment=OPENAI_API_BASE_URL=http://host\.containers\.internal:52625' \
|
||||
"$QDIR/open-webui.container" \
|
||||
|| { echo "open-webui.container must set OPENAI_API_BASE_URL to the vLLM on 52625"; return 1; }
|
||||
}
|
||||
|
||||
@test "open-webui persists its data under /home/dank/Containers/open-webui/data" {
|
||||
grep -qE '^Volume=/home/dank/Containers/open-webui/data:/app/backend/data' \
|
||||
"$QDIR/open-webui.container" \
|
||||
|| { echo "open-webui.container must mount the data dir at /app/backend/data"; return 1; }
|
||||
}
|
||||
|
||||
@test "open-webui is installed for boot (WantedBy=default.target)" {
|
||||
grep -qE '^WantedBy=default\.target' "$QDIR/open-webui.container" \
|
||||
|| { echo "open-webui.container: [Install] must set WantedBy=default.target"; return 1; }
|
||||
}
|
||||
|
||||
@test "no secret material in the quadlet file" {
|
||||
# The LLM backend is a local LAN endpoint; no key is required. No 44-char
|
||||
# base64 secret-like value may appear in the tracked file.
|
||||
if grep -qE '[A-Za-z0-9+/]{44}=' "$QDIR/open-webui.container"; then
|
||||
echo "open-webui.container: contains a secret-like value"
|
||||
return 1
|
||||
fi
|
||||
has_no_leaked_secret "$QDIR/open-webui.container"
|
||||
}
|
||||
Reference in New Issue
Block a user