feat: initialize serverAppConfigs with podman stacks for Dankserver and Dankclaw

- Dankserver stack: RomM, Copyparty, Calibre-Web, node-exporter
- Dankclaw stack: Gluetun VPN (SearXNG tunnel), SearXNG+Valkey, ComfyUI
  (CPU backend), OpenWebUI, Hermes, Gitea+Postgres (migrated), Caddy
  reverse proxy, Grafana+Prometheus+Loki+Alloy observability
- Caddyfiles with .local hostname convention and basicauth homepage
- bind-mount storage policy, no named volumes
- deploy.sh mirroring vllmScripts git workflow (push -> pull -> restart -> health)
- docs/ Obsidian vault: per-service docs from frank-default-small research
  subagents (promtail EOL -> Alloy; gitea SECRET_KEY preservation; ComfyUI --cpu)
- bats test suite at 6 seams — 22 tests green
This commit is contained in:
Frank committed 2026-08-13 20:19:41 -07:00
commit e3eb5334cb
57 files changed
+3033

No files matched your search

+54
View File
@@ -0,0 +1,54 @@
# ComfyUI
ComfyUI is a node-based AI image generation engine. Workflows are built from nodes that load models, sample latents, and post-process images; everything is scriptable through a web UI and an API.
**Host:** Dankclaw — **Hostname:** `comfyui.dankclaw.local` — **Port:** 8188 — **Docs:** https://github.com/Comfy-Org/ComfyUI
## Facts (from research, primary source)
| Item | Value |
|------|-------|
| Image | `ghcr.io/comfyanonymous/comfyui:latest` (community CPU build) |
| Port | 8188 (host) → 8188 (container) |
| Models volume | bind mount to the model collection |
| CPU flag | `--cpu` — confirmed in `cli_args.py`: "To use the CPU for everything (slow)" |
| Known issue | ROCm wheels do not ship kernels for gfx1151 (Strix Halo) — CPU backend required |
## Why CPU on Dankclaw
Dankclaw is an AMD Strix Halo APU (gfx1151). Precompiled ROCm PyTorch wheels do not support this iGPU. This matches the existing knowledge base entry for ComfyUI on Dankclaw: the running instance uses `--cpu`. The Docker image for CPU execution is the `comfyui` CPU build; the GPU images (`comfyui-cuda`) must not be used.
## Command Line (from `cli_args.py`)
| Flag | Meaning |
|------|---------|
| `--cpu` | use CPU for everything |
| `--cpu-vae` | run the VAE on CPU only |
| `--lowvram` | text encoders on CPU (dynamic VRAM default handles this) |
| `--listen` | bind address (0.0.0.0 for LAN) |
| `--port` | listen port (8188) |
## Volumes
| Container path | Host path (convention) |
|----------------|------------------------|
| `/workspace` | `/opt/comfyui` (models, input, output, custom_nodes) |
## Start Fresh
The model collection starts from scratch. The first boot creates the directory structure; models are added per the knowledge base procedure.
## Healthcheck
The web UI responds on the root path `/`. ComfyUI also exposes `/system_stats`.
## Gotchas
- Expect minutes per image on CPU; that is the documented behavior for this APU.
- ComfyUI-Manager installs custom nodes into `custom_nodes/` — persist that directory.
- Do not pull the CUDA image; it fails to find an NVIDIA driver.
## See Also
- [[Dankclaw Stack]]
- Knowledge base: [[ComfyUI]] (tools), [[ComfyUI Troubleshooting]]
+41
View File
@@ -0,0 +1,41 @@
# Dankclaw Stack
Dankclaw (192.168.0.143) runs the AI, search, and development stack. It hosts the inference endpoint (llama.cpp, port 7770, from the `vllmScripts` repo), the agent tools, the privacy search, and the observability stack.
```mermaid
graph LR
U[User] --> C[Caddy :80/:443]
C --> O[OpenWebUI]
C --> S[SearXNG]
C --> G[Gitea]
C --> F[ComfyUI]
C --> D[Grafana]
S --> V[Gluetun VPN]
O --> L[llama.cpp :7770]
O --> H[Hermes]
```
## Services
| Service | Hostname | Port | Docs |
|---------|----------|------|------|
| SearXNG | search.dankclaw.local | 8081 | [[SearXNG]] |
| ComfyUI | comfyui.dankclaw.local | 8188 | [[ComfyUI]] |
| OpenWebUI | chat.dankclaw.local | 3000 | [[OpenWebUI]] |
| Hermes | hermes.dankclaw.local | 8090 | [[Hermes]] |
| Gitea | gitea.dankclaw.local | 3001 | [[Gitea]] |
| Grafana | grafana.dankclaw.local | 3002 | [[Grafana]] |
| Prometheus | prometheus.dankclaw.local | 9090 | [[Prometheus]] |
| Homepage | dankclaw.local | 80/443 | — |
## Routing Rules
- SearXNG outbound traffic exits through the [[Gluetun]] VPN container.
- Every other service uses the normal LAN route.
- The homepage lists all services with status.
## See Also
- [[index]] — repository index
- [[Network Architecture]] — routing and proxy
- [[Observability]] — Grafana, Prometheus, logs
+63
View File
@@ -0,0 +1,63 @@
# Gitea
Gitea is a lightweight self-hosted Git service with issues, pull requests, and a web UI. It already runs on Dankclaw as a podman-compose stack at `/opt/gitea/` and is being migrated into this repository.
**Host:** Dankclaw — **Hostname:** `gitea.dankclaw.local` — **Port:** 3001 — **Docs:** https://docs.gitea.com
## Facts (from live inventory + knowledge base)
| Item | Value |
|------|-------|
| Image | `docker.io/gitea/gitea:latest` |
| DB image | `docker.io/library/postgres:16-alpine` |
| Gitea port | 3000 → 3001 (host, proxied) |
| SSH port | 2222 → 22 (host) |
| DB | postgres on the podman network (internal 5432) |
| Current location | `/opt/gitea/podman-compose.yml` on Dankclaw |
## Migration Plan
1. Copy the live `/opt/gitea/podman-compose.yml` into `dankclaw/compose.yaml` (redacted: secrets moved to `.env`).
2. Keep the bind-mount data at `/opt/gitea/data/` so repos survive.
3. Commit the compose, push, and pull on Dankclaw.
4. Replace the live stack by bringing up the repo-managed compose.
## Env (from the live compose, redacted)
```yaml
environment:
DATABASE_TYPE: postgres
DATABASE_HOST: postgres
DATABASE_NAME: gitea
DATABASE_USER: gitea
DATABASE_PASSWD: "${GITEA_DB_PASSWORD}" # from .env, never committed
USER_UID: "1000"
USER_GID: "1000"
```
## Volumes
| Container path | Host path (live) |
|----------------|------------------|
| `/data` | `/opt/gitea/data/gitea-gitea` |
| `/var/lib/postgresql/data` | `/opt/gitea/data/gitea-postgres` |
| `/root/.local/share/caddy` | `/opt/gitea/data/caddy/data` |
## Healthcheck
Gitea serves `/api/v1/version` and the web UI on `/`.
## Backup
Back up `/opt/gitea/data/` (tar) or `pg_dump` the gitea database. See the knowledge base page [[Gitea Data Persistence]].
## Gotchas
- The live compose contains a real DB password — never commit it (test-enforced).
- Gitea and postgres both run as UID 1000; keep the bind mounts owned by `dank`.
- DNS `gitea.franksdank.cloud` → 71.227.227.172 is pending; the LAN hostname works now.
## See Also
- [[Dankclaw Stack]]
- Knowledge base: [[Gitea]] (tools), [[Gitea Data Persistence]], [[Gitea Architecture]]
+70
View File
@@ -0,0 +1,70 @@
# Gluetun
Gluetun is a lightweight VPN client container. It connects to a VPN provider (WireGuard or OpenVPN) and shares that tunnel with other containers that attach to its network namespace.
**Host:** Dankclaw — **Docs:** https://github.com/qdm12/gluetun
## Facts (from research, primary source)
| Item | Value |
|------|-------|
| Image | `qmcgaw/gluetun:latest` |
| Capabilities | `NET_ADMIN` required |
| Device | `/dev/net/tun:/dev/net/tun` |
| Ports published | searxng's 8081 → 8080 lives here |
| Healthcheck | internal on `8000` (built-in) |
## Required Environment
```yaml
environment:
VPN_SERVICE_PROVIDER: "${VPN_SERVICE_PROVIDER}" # e.g. mullvad, protonvpn
VPN_TYPE: wireguard
WIREGUARD_PRIVATE_KEY: "${WIREGUARD_PRIVATE_KEY}" # from .env, never committed
```
- `VPN_TYPE` default is `openvpn`; WireGuard preferred when the provider supports it.
- Provider-specific variables come from the Gluetun wiki; set them in `.env`.
## Optional DNS (from research)
| Variable | Default | Purpose |
|----------|---------|---------|
| `DNS_UPSTREAM_RESOLVER_TYPE` | `dot` | DNS over TLS |
| `DNS_UPSTREAM_RESOLVERS` | `cloudflare` | upstream DoT providers |
| `DNS_CACHING` | `on` | cache queries |
| `BLOCK_MALICIOUS` | `on` | block malicious hostnames |
Warning from the wiki: do NOT set `DNS_UPSTREAM_PLAIN_ADDRESSES` to a LAN/bridge address — DNS queries leak out of the tunnel.
## Attaching a Container
```yaml
services:
searxng:
network_mode: "service:gluetun"
depends_on:
- gluetun
```
There is no need for `depends_on` for the network stack, but it orders startup cleanly.
## Verify the Tunnel
```bash
podman exec gluetun curl -s ifconfig.me
```
The returned IP must differ from the host IP.
## Gotchas
- The container that holds the published port must be the one with the ports mapping (gluetun).
- `restart: always` on gluetun so the tunnel reconnects after VPN drops.
- When gluetun restarts, attached containers inherit the new network state; searxng may need a restart to re-resolve DNS.
## See Also
- [[SearXNG]] — the consumer of the tunnel
- [[Dankclaw Stack]]
- Knowledge base: [[VPN Routing for SearXNG]]
+40
View File
@@ -0,0 +1,40 @@
# Hermes
Hermes is the self-improving AI agent from Nous Research. It runs in a terminal, Telegram, Discord, Slack, WhatsApp, or Signal, and keeps a persistent learning loop: it creates skills from experience, searches past conversations, and builds a model of the user across sessions.
**Host:** Dankclaw — **Hostname:** `hermes.dankclaw.local` — **Docs:** https://hermes-agent.nousresearch.com/docs
## Facts (from research, primary source)
| Item | Value |
|------|-------|
| Image | built from `NousResearch/hermes-agent` (official Dockerfile in repo) |
| License | MIT |
| Model backends | Nous Portal, OpenRouter, OpenAI, or any custom endpoint |
| Interfaces | TUI, Telegram, Discord, Slack, WhatsApp, Signal |
| Memory | FTS5 session search, Honcho user modeling |
| Skills | self-created, self-improving during use |
## Configuration
- Point Hermes at the local model endpoint (`http://localhost:7770/v1`) or an external provider through its provider config.
- The gateway process exposes one port for chat integration; the exact port is set in the Hermes config (`8090` convention in this repo).
- The data volume persists skills, memory, and session history.
## Volumes
| Container path | Host path (convention) |
|----------------|------------------------|
| `/data` | `/opt/hermes/data` |
## Gotchas
- Hermes is under active development; pin the image tag for reproducibility.
- Model choice drives quality; the local `frank-default-large` (Dankclaw, 7770) is the intended backend.
- Do not expose the gateway without auth — put it behind [[Caddy]] with basic auth.
## See Also
- [[Dankclaw Stack]]
- [[OpenWebUI]] — the chat UI that pairs with Hermes
- Knowledge base: [[Agentic Search Best Practices]] (Hermes as the search agent)
+50
View File
@@ -0,0 +1,50 @@
# 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.
**Host:** Dankclaw — **Hostname:** `chat.dankclaw.local` — **Port:** 3000 — **Docs:** https://github.com/open-webui/open-webui
## Facts (from research, primary source)
| 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://localhost:7770/v1` (llama.cpp) |
> Note: the `:cuda` image tag was previously used on Dankserver. Dankclaw is AMD; use the default (CPU) image, NOT the cuda tag.
## Connecting to llama.cpp
OpenWebUI talks to the local llama.cpp server through its OpenAI-compatible API. Configure a new connection in the UI or set the base URL:
```yaml
environment:
OPENAI_API_BASE_URL: "http://host.containers.internal:7770/v1"
OPENAI_API_KEY: "none"
```
The hostname `host.containers.internal` reaches the podman host where llama.cpp listens on 7770.
## Volumes
| Container path | Host path (convention) |
|----------------|------------------------|
| `/app/backend/data` | `/opt/openwebui/data` |
## Healthcheck
OpenWebUI responds on `/health` with 200.
## Gotchas
- Use the non-CUDA image on AMD hosts.
- 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.
## See Also
- [[Dankclaw Stack]]
- [[Hermes]] — the agent OpenWebUI can talk to
- Knowledge base: [[llama.cpp Server]]
+68
View File
@@ -0,0 +1,68 @@
# Postgres
PostgreSQL is the database backend for Gitea. It runs on the same podman network as Gitea and holds the repository metadata, users, and issue data.
**Host:** Dankclaw — **Docs:** https://www.postgresql.org/docs
## Facts
| Item | Value |
|------|-------|
| Image | `docker.io/library/postgres:16-alpine` |
| Port | 5432 (internal only) |
| Data volume | bind mount at `/var/lib/postgresql/data` |
## Configuration
```yaml
services:
postgres:
image: docker.io/library/postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: gitea
POSTGRES_PASSWORD: "${GITEA_DB_PASSWORD}"
POSTGRES_DB: gitea
volumes:
- /opt/gitea/data/gitea-postgres:/var/lib/postgresql/data
```
## Gitea Connection
Gitea reaches the database over the podman network:
```yaml
environment:
DATABASE_TYPE: postgres
DATABASE_HOST: postgres
DATABASE_NAME: gitea
DATABASE_USER: gitea
DATABASE_PASSWD: "${GITEA_DB_PASSWORD}"
```
## Migration
The live Gitea stack on Dankclaw already runs this image with the data at `/opt/gitea/data/gitea-postgres`. The migration keeps the bind mount in place; see [[Gitea]].
## Healthcheck
Use `pg_isready`:
```yaml
healthcheck:
test: ["CMD-SHELL", "pg_isready -U gitea"]
interval: 30s
timeout: 5s
retries: 3
```
## Gotchas
- The database password must match between the `postgres` service and Gitea's `DATABASE_PASSWD`; both come from the same `.env` variable.
- Do not change the Gitea `SECRET_KEY` or `INTERNAL_TOKEN` after migration — encrypted data becomes unreadable.
- Back up with `pg_dump`; see [[Gitea Data Persistence]] (knowledge base).
## See Also
- [[Gitea]]
- [[Dankclaw Stack]]
+77
View File
@@ -0,0 +1,77 @@
# SearXNG
SearXNG is a privacy-respecting metasearch engine. It aggregates results from many upstream search engines into one result set and serves both an HTML UI and a JSON API for agents.
**Host:** Dankclaw — **Hostname:** `search.dankclaw.local` — **Docs:** https://docs.searxng.org
## Facts (from research, primary source)
| Item | Value |
|------|-------|
| Image | `ghcr.io/searxng/searxng:latest` (or DockerHub mirror) |
| Port | 8081 (host, on Gluetun) → 8080 (container) |
| Config | `/etc/searxng/settings.yml` (bind mount) |
| Cache | Valkey (replaces Redis) — `valkey/valkey:9-alpine` |
| Formats | `html`, `json` (agent API) |
## VPN Routing (critical)
All SearXNG outbound traffic exits through the Gluetun VPN container. The container attaches to Gluetun's network namespace:
```yaml
services:
searxng:
network_mode: "service:gluetun"
depends_on:
- gluetun
```
Port publishing happens on the **gluetun** service, not on searxng. See [[Gluetun]] and the knowledge base page [[VPN Routing for SearXNG]] (KB Pattern 2 is the one used here).
## settings.yml Keys
```yaml
use_default_settings: true
server:
secret_key: "${SEARXNG_SECRET}" # from .env
base_url: "http://search.dankclaw.local/"
limiter: true
method: "POST"
search:
default_lang: "en"
formats:
- html
- json
valkey:
url: "valkey://searxng-valkey:6379/0"
outgoing:
request_timeout: 3.0
max_request_timeout: 10.0
retries: 3
```
## Volumes
| Container path | Host path (convention) |
|----------------|------------------------|
| `/etc/searxng` | `/opt/searxng/config` |
| `/etc/searxng/valkey` | (valkey container data) |
## Healthcheck
SearXNG responds on `/healthz` with 200 when ready.
## Gotchas
- The JSON API is rate-limited hard by the limiter (4 per 3600s per IP for untrusted). Add the agent's IP to the trusted group in `settings.yml` if it polls.
- `base_url` must match the public address or proxy rewrites break.
- Keep the valkey cache container on the same podman network as searxng.
## See Also
- [[Dankclaw Stack]]
- [[Gluetun]] — the VPN container
- Knowledge base: [[VPN Routing for SearXNG]], [[SearXNG Tuning]], [[Agentic Search Best Practices]]
+51
View File
@@ -0,0 +1,51 @@
# Valkey
Valkey is the cache and limiter store for SearXNG. It replaces Redis as the default in the official SearXNG deployment.
**Host:** Dankclaw — **Docs:** https://valkey.io
## Facts (from research)
| Item | Value |
|------|-------|
| Image | `valkey/valkey:9-alpine` |
| Port | 6379 (internal, on the SearXNG network) |
| Role | SearXNG limiter + result cache |
## Why Valkey
The official SearXNG docker-compose ships a `searxng-valkey` container. Valkey is the Redis fork under the Linux Foundation umbrella; the SearXNG project moved to it as the default cache.
## Configuration
```yaml
services:
valkey:
image: valkey/valkey:9-alpine
restart: always
volumes:
- /opt/searxng/valkey:/data
```
SearXNG connects to it via `settings.yml`:
```yaml
valkey:
url: "valkey://searxng-valkey:6379/0"
```
## Volumes
| Container path | Host path (convention) |
|----------------|------------------------|
| `/data` | `/opt/searxng/valkey` |
## Gotchas
- The limiter relies on Valkey; without it SearXNG falls back to in-memory state and rate limiting is per-restart.
- Keep Valkey on the same podman network as SearXNG.
## See Also
- [[SearXNG]] — the consumer
- [[Dankclaw Stack]]