Mechanical identity rename across the knowledge base, AGENTS.md, README, the
hermes config example, and homepage services.json. No behavior change; served
aliases on the LLM endpoints change with the deploy of commit 69f87c8.
9.8 KiB
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); 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).
The per-service /docs/ pages, the *arr stack, and the optional /how/ page remain separate threads.
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).
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.
stateDiagram-v2
[*] --> Up
[*] --> Down
[*] --> WIP
Up --> Down: probe fails
Down --> Up: probe succeeds
WIP --> Up: work completes
- Up — the probe reached the backend.
- Down — the probe failed (connection refused or timeout).
- WIP — a static override. The service is intentionally not finished. 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.
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): SearXNG, ComfyUI, OpenWebUI, Gitea, Calibre-Web, RomM, Copyparty, Plex, Jellyfin.
Admin services (/admin/): Hermes, Grafana, Prometheus, Loki, Caddy, Pi-hole, Gluetun, the LLM fleet, and the low-level backends (mariadb, redis, Postgres, node-exporter, alloy). The backends show status only. They get a link only where a UI is meaningful.
Excluded from the listing: the *arr stack + qBittorrent (Prowlarr, Sonarr, Radarr, Lidarr, Readarr, qB) — live on FrankBook-Pro but operator-only: no vhosts, direct IP:port access only. See FrankBook-Pro.
Layout
shared/homepage/
├── index.html # root page (user services)
├── admin.html # admin + infrastructure services
├── how.html # deferred (Q6)
├── 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
status.json is generated at runtime in /srv/homepage/. It is not committed.
Rendering
The page renders a grid of TUI-style panels — one panel per service. Each panel shows the state badge, the name, the description, and the Open and Docs links. 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 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
{
"name": "SearXNG",
"category": "user",
"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—useroradmin. Selects the page.url— the "Open" link (the Caddy site).docs— the per-service docs page (/docs/<svc>/).probe— the backendhost:portthe checker probes.method—tcporhttp. The probe type for this service (Q10).wip— when true, the badge shows WIP and the checker skips the probe.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