diff --git a/scripts/deploy-remote.sh b/scripts/deploy-remote.sh index a11fa1d..51e23b5 100755 --- a/scripts/deploy-remote.sh +++ b/scripts/deploy-remote.sh @@ -322,6 +322,9 @@ deploy_homepage() { if [[ -d shared/homepage/fonts ]]; then run_privileged cp -r shared/homepage/fonts /srv/homepage/ fi + if [[ -d shared/homepage/img ]]; then + run_privileged cp -r shared/homepage/img /srv/homepage/ + fi local deployed_at deployed_at="$(git log -1 --format=%cI)" printf 'window.DEPLOYED_AT = "%s";\n' "$deployed_at" > /tmp/deployed.js diff --git a/serverAppConfigs-docs/Dankclaw/OpenWebUI.md b/serverAppConfigs-docs/Dankclaw/OpenWebUI.md index 173d88a..d895813 100644 --- a/serverAppConfigs-docs/Dankclaw/OpenWebUI.md +++ b/serverAppConfigs-docs/Dankclaw/OpenWebUI.md @@ -46,6 +46,17 @@ The `model` table carries exactly one decoration row, `frank-secondary` (context length 200,704 = the per-slot window; description: Qwen3.5-9B Q6_K + BF16 vision mmproj). +A third gate sits *after* the table, and it is the one that bites family +accounts (2026-10-07): a model row is only visible to a non-admin user when +its **access permission** grants them. A seeded row defaults to admin-only, +so the picker renders "No results found" for every regular user even though +the connection is enabled, `model_ids` is correct, and the endpoint answers +`/v1/models` — and `GET /api/v1/models` returns `[]` for that user while an +admin token returns the model. Fix: set the row's `access_control` to public +(the `access` column on the `model` row) via the admin UI (Admin Panel → +Models → visibility) or the DB. The verification battery below must therefore +run **as a non-admin user**, not only as an admin. + Two gotchas make the wiring non-obvious: - **llama.cpp `/v1/models` is Ollama-shaped.** llama.cpp returns @@ -113,8 +124,9 @@ The Ollama connection is disabled (`ollama.enable=false`). `host.containers.internal`), not a model problem. **Durability:** the model table + connections are DB-only (not git-managed); -a fresh data volume loses them. For reproducibility, add an idempotent -first-start seed script or document the setup in the service doc. +a fresh data volume loses them — including the public access grant above. +For reproducibility, add an idempotent first-start seed script or document +the setup in the service doc. ## API access (auth-gated) diff --git a/serverAppConfigs-docs/Infrastructure/Homepage.md b/serverAppConfigs-docs/Infrastructure/Homepage.md index a05ad12..a9bf7d4 100644 --- a/serverAppConfigs-docs/Infrastructure/Homepage.md +++ b/serverAppConfigs-docs/Infrastructure/Homepage.md @@ -36,6 +36,20 @@ deliberately unprobeable) via the `wip` value (`"open"` / `"closed"`). Tautulli (Plex statistics, `192.168.0.235:8181`) joined the admin page's Media Library Apps section. +**Amber CRT theme + API docs (2026-10-07):** the palette moved from green +phosphor to amber CRT-AMBIOS (`--fg #ffb000` on `--bg #0a0600`), and the font +stack now leads with **VT323** loaded from the Google Fonts CDN (the deploy +also ships `shared/homepage/img/` beside the page, so `/img/*` serves the docs +screenshots). The docs page gained a full **API access** walkthrough +(`/docs#api-access`): per-user OpenWebUI API keys (User menu → Settings → +Account → API keys), the base URL `https://ai.franksdank.cloud/api/v1`, and +copy-paste configs for **Opencode** (`~/.config/opencode/opencode.json`) and +**Pi** (`~/.pi/agent/models.json`) pointing at `frank-secondary`. Shipped +together with the OpenWebUI side: `features.api_keys=true` for regular users +and the restored `frank-secondary` model row + public `read` access grant +without which the picker showed "No results found" for every family account +(see [[OpenWebUI]]). + ## Design decision — 2026-08-22 The live up/down check runs server-side. A checker on Dankclaw probes each backend and writes a same-origin `status.json`. The page fetches only that file and renders the badges. The browser never calls a backend directly. The checker runs as a systemd timer every 3 minutes. @@ -111,7 +125,7 @@ flowchart TD ## Service classification **User services** (root page), in section order: -- **AI** — OpenWebUI, and the three LLM fleet cards (frank-large, frank-medium, frank-secondary — status + docs link, no Open link). +- **AI** — OpenWebUI, and the three LLM fleet cards (frank-large, frank-primary, frank-secondary — status + docs link, no Open link). - **Web Apps** — SearXNG, Gitea, Copyparty, Marreta. - **Media Library Apps** — Calibre-Web, RomM, Plex, Jellyfin, Seerr. @@ -131,7 +145,8 @@ shared/homepage/ ├── style.css # full theme; a toggle strips to bare HTML ├── app.js # loads status.json once, renders the panels ├── services.json # service config (user + admin) -└── fonts/ # Terminus (woff2) for @font-face +├── img/ # screenshots served at /img (docs page; deployed with the page) +└── fonts/ # optional self-hosted fonts (deployed with the page; VT323 comes from the Google Fonts CDN, this dir is absent) ``` `status.json` is generated at runtime in `/srv/homepage/`. It is not committed. diff --git a/shared/homepage/admin.html b/shared/homepage/admin.html index 102f3e4..f79c122 100644 --- a/shared/homepage/admin.html +++ b/shared/homepage/admin.html @@ -5,6 +5,9 @@
The everyday front door is Open WebUI - (it has its own login the first time; use the same email you use everywhere). - Pick a model in the chat's model selector — the three models the fleet runs are - described below. Open WebUI keeps your chat history; the raw - model endpoints do not.
-Images go to frank-primary or frank-secondary
- (both see them). Audio (WAV) goes to frank-secondary only.
The everyday front door is Open WebUI, + the family chat app. Open the address, click Continue with authentik, + and use the same single sign-on as everything else — no separate password. Your + conversations are saved to your account, so history follows you between devices.
+
+ Below the text box, the model box on the right chooses which model answers you.
+ Family accounts get frank-secondary — the fleet's fast all-rounder,
+ which also sees images: drag a photo or screenshot straight into the chat box.
+ Frank's account can additionally reach the bigger models.
+ Open WebUI keeps your chat history; the raw model endpoints (used by developer + tools, see API access) do not.
Which one to pick? Small utility work, RAG, search, and
@@ -76,6 +83,82 @@
frank-large (with Frank's permission).
Developer assistants like Opencode and Pi are + chat programs that run on your own computer and can read your files, run commands, + and edit code while they talk. They are all built to speak the same standard + language as the big commercial products (the "OpenAI-compatible" API), and our + server speaks it too. You give the program three things — an address, a personal + key, and a model name — and your laptop thinks through our hardware.
+ +sk-3f2a….
+ The key is a password. Anyone who has it can use our AI as + you. Paste it only into the program's config file — never into a shared chat, + a screenshot, or a git repository.
+ +https://ai.franksdank.cloud/api/v1frank-secondarysk-… from step 1 — sent as the header Authorization: Bearer sk-…Proof that it works, from any terminal (replace the key):
+curl https://ai.franksdank.cloud/api/v1/chat/completions \
+ -H "Authorization: Bearer PASTE-YOUR-KEY" \
+ -H "Content-Type: application/json" \
+ -d '{"model": "frank-secondary", "messages": [{"role": "user", "content": "Hello!"}]}'
+
+ Install once (npm install -g opencode-ai), then create the file
+ ~/.config/opencode/opencode.json with:
{
+ "$schema": "https://opencode.ai/config.json",
+ "provider": {
+ "frank": {
+ "npm": "@ai-sdk/openai-compatible",
+ "name": "Frank fleet",
+ "options": {
+ "baseURL": "https://ai.franksdank.cloud/api/v1",
+ "apiKey": "PASTE-YOUR-KEY"
+ },
+ "models": {
+ "frank-secondary": { "name": "frank-secondary" }
+ }
+ }
+ }
+}
+ Start opencode and pick the model with /model —
+ it appears as frank/frank-secondary.
Create the file ~/.pi/agent/models.json with:
{
+ "providers": {
+ "frank": {
+ "baseUrl": "https://ai.franksdank.cloud/api/v1",
+ "api": "openai-completions",
+ "apiKey": "PASTE-YOUR-KEY",
+ "models": [
+ { "id": "frank-secondary" }
+ ]
+ }
+ }
+}
+ Then start Pi with pi --model frank/frank-secondary.
The key works only for the family AI — it is not your + Authentik password, and losing it costs nothing beyond asking Frank to rotate it. + Want a bigger model (frank-primary, frank-large)? Ask Frank to raise your access.
+