Files
serverAppConfigs/serverAppConfigs-docs/Infrastructure/Homepage.md
T
Frank cee90eea52 homepage: amber CRT theme, VT323, current-fleet docs + API access guide
- style.css: green phosphor -> amber CRT-AMBIOS palette; docs styles for
  screenshots, code blocks, key warning (full + bare mode)
- index/admin/docs: VT323 from the Google Fonts CDN
- docs.html: rewrite #chat/#models for the live fleet (flash-server v2,
  radiance native FP8, Qwen3.5-9B Q6_K); new #api-access with OpenWebUI
  key walkthrough + Opencode and Pi configs; three OpenWebUI screenshots
- services.json: AI card details match the current fleet
- deploy-remote.sh: ship shared/homepage/img/ to /srv/homepage/
- docs: Homepage.md theme wave + OpenWebUI.md picker access-control law
2026-10-07 23:27:23 -07:00

279 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
type: reference
description: "A password-protected web site on Dankclaw lists the services that are up, with a short description of each and a link to both the service and its usage documentation."
tags:
- infrastructure
- homepage
timestamp: 2026-09-21
---
# Homepage
A password-protected web site on Dankclaw lists the services that are up, with a short description of each and a link to both the service and its usage documentation.
**Served by:** Caddy, `file_server` + `forward_auth` (Authentik SSO) — **Docs:** this page
## Status — deployed to production (2026-08-23), on the naked domain since 2026-08-24
The homepage is live on Dankclaw. `./scripts/deploy.sh dankclaw` copies the static assets to `/srv/homepage/`, installs the status checker (systemd timer, every 3 min, writes `status.json`), writes `deployed.js` (the last master deployment datetime), renders the Caddyfile (the shared basicauth hash from the host `.env`), reloads Caddy, and checks health. The Caddyfile auth uses a `route` block with `forward_auth` (see [Auth](#auth)); the caddy container mounts the host's `/srv/homepage` (not the repo source) so the generated files serve.
**Moved to the naked domain (2026-08-24):** the homepage now serves `franksdank.cloud` and `www.franksdank.cloud`. The old names `dankclaw.franksdank.cloud` and `dankserver.franksdank.cloud` redirect to it. Authentication moved from per-page basicauth to Authentik `forward_auth` (see [Authentik](Authentik.md)).
**Docs page shipped (2026-09-17):** `/docs` serves a static user documentation
page (`shared/homepage/docs.html`) — access walkthrough, the running-model
guide, media how-tos, and troubleshooting. Per-service `/docs/<svc>/` pages
remain a separate thread. The three LLM cards (`frank-large`, `frank-medium`,
`frank-secondary`) moved from admin-only to the **user** page with live model
descriptions and a `docs` link to `/docs#models`.
**Sections + WIP split (2026-09-21):** both pages now group their panels
under section headings rendered by shared `renderSection()`/`renderPanel()`
components in `app.js`. The user page orders **AI, Web Apps, Media Library
Apps**; the admin page orders **Media Library Apps, Observability, AI,
Infrastructure**. The WIP badge split into **WIP: OPEN** (work in progress)
and **WIP: CLOSED** (work finished by decision — e.g. a card that is
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.
The site has two pages. The root lists user-facing services only, with no host labels. An `/admin/` subpage lists the admin and infrastructure services behind a separate password for the operator. Gitea is a user service. Hermes is an admin service. A "how it is built" page for visitors is deferred (Q6).
The client-side probe design is blocked in this topology. The page origin is `https://franksdank.cloud`. The service URLs are `https://<name>.franksdank.cloud`. That is cross-origin and auth-gated. Both fail a browser `fetch`.
Intended direction: move to client-side probing. Serve every service as a same-origin `https` path under `franksdank.cloud`. Add a health endpoint and CORS headers to each backend. Then the page probes each service directly. Keep this note until that shift is complete.
## Structure
The site has two pages. The root page lists the user services. The `/admin/` page lists the admin and infrastructure services. The optional `/how/` page is deferred (Q6).
```mermaid
graph TD
subgraph site["franksdank.cloud + www (Caddy)"]
ROOT["/ user services<br/>(shared password)"]
ADMIN["/admin/ admin services<br/>(operator password)"]
HOW["/how/ how it is built<br/>(deferred)"]
STATUS["/status.json"]
end
TIMER["systemd timer<br/>every 3 minutes"]
CHECKER["checker script<br/>probes each backend"]
BACKENDS["backends on both hosts"]
TIMER --> CHECKER
CHECKER -->|writes| STATUS
STATUS -->|polled| ROOT
STATUS -->|polled| ADMIN
CHECKER -->|probes| BACKENDS
```
## Status model
Each service shows one of three states.
```mermaid
stateDiagram-v2
[*] --> Up
[*] --> Down
[*] --> WipOpen: wip = "open"
[*] --> WipClosed: wip = "closed"
Up --> Down: probe fails
Down --> Up: probe succeeds
WipOpen --> Up: work completes
WipClosed --> Up: card becomes probeable
```
- **Up** — the probe reached the backend.
- **Down** — the probe failed (connection refused or timeout).
- **WIP: Open** — work is in progress. The checker does not probe it.
- **WIP: Closed** — the work is closed by decision (for example a card that
is deliberately unprobeable, with the reason in its description). The
checker does not probe it.
## Checker
The checker runs on Dankclaw as a systemd timer. Every run probes each backend directly, then writes `status.json`. It probes the backend `host:port`, not the Caddy site. A Caddy `502` or `401` cannot tell up from down. The probe method is per service (`tcp` or `http`). The timeout is 5 s. The page reads `status.json` once on load. It does not auto-refresh.
```mermaid
flowchart TD
A[timer fires] --> B[read service config]
B --> C{for each service}
C -->|wip| D[mark WIP]
C -->|probe| E[probe: tcp or http]
E -->|connected| F[mark Up]
E -->|refused / timeout| G[mark Down]
D --> H[write status.json]
F --> H
G --> H
```
## Service classification
**User services** (root page), in section order:
- **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.
**Admin services** (`/admin/`), in section order:
- **Media Library Apps** — the *arr stack (Prowlarr, Sonarr, Radarr, Lidarr, Readarr), qBittorrent, and Tautulli — live on FrankBook-Pro and Dankserver, operator-only: no vhosts (Tautulli included), direct IP:port access only. See [[FrankBook-Pro]].
- **Observability** — Grafana, Prometheus, Loki, node-exporter, alloy, alertmanager.
- **AI** — Hermes, ComfyUI.
- **Infrastructure** — Caddy, dnsmasq, Gluetun, and the low-level backends (mariadb, valkey, Postgres). The backends show status only. They get a link only where a UI is meaningful.
## Layout
```
shared/homepage/
├── index.html # root page (user services)
├── admin.html # admin + infrastructure services
├── docs.html # user documentation page, served at /docs (2026-09-17)
├── 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)
├── 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.
## Rendering
The page renders **sections of TUI-style panels** — one panel per service,
grouped under a section heading. Each panel shows the state badge, the name,
the description, and the `Open` and `Docs` links. Section and panel are
shared components (`renderSection()` / `renderPanel()` in `app.js`); each
page's section order is fixed by `SECTION_ORDER`. A summary line at the top
counts the states. The footer shows the last master deployment datetime (Q43).
The page reads `status.json` **once on load**. It does not auto-refresh (Q41).
Styling is **toggleable** (Q27). The default is the full terminal theme. A toggle strips the page to bare, unstyled HTML (motherfuckingwebsite.com style). The page does not alter the user's cursor (Q28).
## Auth
Caddy wraps the site with `forward_auth` (Authentik SSO). A single Authentik
session covers the whole site: the user page, the `/admin/` page, and the
shared data + assets. The browser's Authentik cookie is reused for the data
fetches, so no per-page credential is needed. See [Authentik](Authentik.md) for
the provider, the outpost, and the first-time setup.
The Caddyfile pattern (one of the forward_auth vhosts):
```
franksdank.cloud www.franksdank.cloud {
root * /srv/homepage
route {
# 1. The outpost path is proxied to Authentik (login UI + session).
reverse_proxy /outpost.goauthentik.io/* authentik-server:9000
# 2. forward_auth checks the session before serving the page.
forward_auth authentik-server:9000 {
uri /outpost.goauthentik.io/auth/caddy
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name
trusted_proxies private_ranges
}
# 3. The pages, in file order. 'handle' preserves order; a top-level
# 'rewrite' would be sorted first and break the path matchers.
handle /admin* {
try_files /admin.html
file_server
}
@data path /services.json /status.json /app.js /style.css /deployed.js
handle @data {
file_server
}
handle {
try_files /index.html
file_server
}
}
}
```
A `handle` inline matcher is a single token; the multi-path `@data` match is a
named matcher. The caddy container mounts the host's `/srv/homepage` (where the
deploy writes the static files and the checker writes `status.json`), not the
repo's `shared/homepage`. A compose change needs a container recreate (`podman
compose up -d caddy`), not a reload. The Caddyfile syntax is documented in the
agent knowledge base (`~/.pi/agent/docs/caddy/caddyfile.md`).
## Service entry format
```json
{
"name": "SearXNG",
"category": "user",
"section": "Web Apps",
"description": "Privacy-respecting metasearch engine.",
"url": "http://search.franksdank.cloud",
"docs": "/docs/searxng/",
"probe": "127.0.0.1:8081",
"method": "http",
"wip": false,
"showLink": true
}
```
- `category` — `user` or `admin`. Selects the page.
- `section` — the heading the panel renders under. Must be one of the page's
`SECTION_ORDER` entries (user: AI, Web Apps, Media Library Apps; admin:
Media Library Apps, Observability, AI, Infrastructure), or the panel is
silently dropped from the page.
- `url` — the "Open" link (the Caddy site).
- `docs` — the per-service docs page (`/docs/<svc>/`).
- `probe` — the backend `host:port` the checker probes.
- `method` — `tcp` or `http`. The probe type for this service (Q10).
- `wip` — `false`, or `"open"` / `"closed"`. When set, the badge shows
**WIP: OPEN** or **WIP: CLOSED** and the checker skips the probe.
**A card must be probeable from the monitor's host, or it must be `wip`.** The
checker runs on Dankclaw, so its reach is: that host's own loopback, its own
container names, and LAN addresses that are actually *published*. A card whose
probe is a container-internal port of another host can never turn green — it
reports a red dot for a healthy service, which is how the board stops being
worth reading (the same lesson as the Prometheus jobs that scrape HTML pages).
Three cards were exactly that until 2026-09-16 — `mariadb`
(`192.168.0.235:3306`), `valkey` and `Postgres` (`127.0.0.1:6379`/`:5432`) — all
probing unpublished database ports. Publishing a database on the LAN to make a
badge green is not a trade this fleet makes, so they are `wip` with the reason in
their description, and the app that fronts each database carries the status
(Glutun, a WireGuard UDP service, was the pre-existing example of the same
call).
- `showLink` — when false, the panel shows status only (no "Open" link).
## Deployment and testing
The deploy is **git-driven**. Master always reflects the currently deployed state (Q33). `deploy.sh` copies the assets to `/srv/homepage/`, installs the checker timer, renders the Caddyfile (both hashes), and reloads Caddy. It stamps the deployment datetime the footer shows (Q43).
The build is **full TDD** (Q36–Q38). Tests exist before the code: the checker (a fake up port, a down port, a WIP service, and the JSON schema), the pages and config (the `services.json` schema, `caddy validate`, the `status.json` reference), and the auth check (the forward_auth block and the renderable basicauth placeholder).
## Per-service docs (separate workstream)
Each service gets a docs page at `/docs/<svc>/` (Q30). The content is a **how-to for a regular user** — no maintenance details (Q29). The pages are password-protected by the same auth as the homepage section they belong to (Q31). They are built as each service goes active (Q32). The `docs` link on a service points to its page. The existing reference docs in `serverAppConfigs-docs/` are not linked from the homepage. They may contain secrets (Q22).
## See Also
- [[Homepage Design Grilling]] — the decision log
- [[Caddy]] — the proxy that serves it
- [[Dankclaw Stack]], [[Dankserver Stack]] — the services it lists