18 pages cut their per-endpoint shape, payload, port and window claims and
link [[frank-archon]] / [[frank-dragoon]] / [[frank-zealot]] instead. Several
of the cut claims had already drifted -- the stack page still described the
kingjones ROCmFP4 era on ROCmFPX, OpenWebUI carried the 27B/3-slot bench
narrative, Hermes named a retired max_model_len and the old service name, and
the OMP harness page's live-window block had drifted off frank-zealot's real
262,144 (it is now labelled a dated worked example whose authority is the
endpoint page).
Two facts gain a single owner instead of two: the up{job=~"frank-.*"} scope
lives in Infrastructure/Prometheus.md, and the blackbox/Anubis probe form in
Architecture/Stale Signal Audit.md, each cited from the other page.
Voice Packs and Frank TTS carried live-state claims that contradicted the
engine page's 2026-10-18 deploy record (nothing listening on 7772/8880, model
dir absent); they now point at the pages that own host state.
470 lines
26 KiB
Markdown
470 lines
26 KiB
Markdown
---
|
||
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-dragoon`, `frank-medium`,
|
||
`frank-zealot`) 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 post-auth listener serves files only through the `@data`
|
||
path allowlist, so new top-level assets must be added there (the `@home`
|
||
catch-all answers anything else with `index.html`, i.e. a PNG arrives as
|
||
`200 text/html` and renders broken; pinned in `homepage_caddy.bats` — this
|
||
exact bug bit the screenshots on 2026-10-08). The docs page gained a full
|
||
(`/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-zealot`. Shipped
|
||
together with the OpenWebUI side: `features.api_keys=true` for regular users
|
||
and the restored `frank-zealot` model row + public `read` access grant
|
||
without which the picker showed "No results found" for every family account
|
||
(see [[OpenWebUI]]).
|
||
|
||
**Capability equivalence notes (2026-10-08):** each fleet card (and the
|
||
`/docs#models` section) carries a `details.equiv` line — frank-dragoon
|
||
"equivalent to Claude Sonnet 5 / OpenAI-Luna (Summer 2026 frontier
|
||
intelligence)", frank-archon "equivalent to Claude Opus 4.8 / OpenAI-Terra
|
||
(same era)", frank-zealot "equivalent to last year's frontier level
|
||
(2025)". Rendered by `descHTML()` as `.desc-equiv` (head color, italic);
|
||
pinned in `homepage_config.bats`.
|
||
|
||
**Protoss rename + Model Information component (2026-10-08):** the model
|
||
cards became the StarCraft trio — `frank-dragoon` → **frank-dragoon**
|
||
(iGPU), `frank-archon` → **frank-archon** (Dankserver), `frank-zealot`
|
||
→ **frank-zealot** (3090). The endpoint/API ids did NOT change in this wave; only the
|
||
family-facing names. The three cards fused into one **Model Information**
|
||
component (`app.js` `renderModelInfo()`, `.model-info`/`.mi-*` styles):
|
||
Protoss unit art (`/img/unit-<unit>.jpg`, hex-clipped), per-unit accent
|
||
colours (Dragoon gold, Archon cyan, Zealot blue), smaller type (15px vs
|
||
19px body), and clickable unit-voice lines that play the original
|
||
StarCraft `.ogg` quotations from `/sfx/` (fetched from
|
||
starcraft.fandom.com via its MediaWiki API; the quote→file mapping lives
|
||
in the `model.sfx`/`model.voice.sfx` fields). New `services.json`
|
||
per-model fields: `unit`, `tagline`, `sfx[]`, `voice`, `supervision`
|
||
(dragoon/archon autonomous-safe, zealot tight-supervision), and `warn`
|
||
(uncensored legal note, rendered as a hover tooltip, not wall text).
|
||
A compaction-model recommendation for frank-zealot rides in its `use`
|
||
text. The Caddy `@data` allowlist grew `/sfx/*`; `deploy_homepage` copies
|
||
`sfx/`. A `wip: "down"` badge state ("WIP: DOWN", red) was added; Seerr,
|
||
Calibre-Web, RomM, and Jellyfin are WIP: DOWN with their Open links
|
||
removed. Card notes added: Copyparty holds every other media app's
|
||
content (including the WIP ones); Gitea and Plex need logins separate
|
||
from the dankcloud SSO; SearXNG is obfuscated, not anonymous (upstream
|
||
API keys tie to Frank). The user docs `/docs#models` section carries the
|
||
same renames, api-id mapping, supervision and compaction notes.
|
||
|
||
**Icons, card polish, admin identity gate (2026-10-08, later wave):** every
|
||
app card now carries hex-clipped art beside its name (`services.json` `art`
|
||
field, files under `img/apps/`): 19 brand marks tinted amber from the
|
||
Simple Icons CDN plus 19 hand-improvised monogram SVGs (amber hex + VT323
|
||
initials) for apps with no mark — Copyparty, Marreta, Hermes (+dashboard),
|
||
node-exporter, Gluetun, OpenWebUI, Calibre-Web, RomM, Seerr, Loki, Alloy,
|
||
Alertmanager, Valkey, Prowlarr, Readarr, Lidarr, Tautulli, ComfyUI. Model
|
||
cards render in the order zealot → dragoon → archon (registry order is the
|
||
contract; `homepage_config.bats` pins it). Dragoon and zealot share one
|
||
common uncensored legal warning (adult content, harmful/hateful/illegal
|
||
descriptions, "sensitive offensive cybersecurity topics", severe legal
|
||
risk, "don't ruin this server for everybody"), rendered as the hover
|
||
label; archon's ask-Frank rule is a bordered red `.mi-perm` callout.
|
||
SearXNG's obfuscation-not-anonymity note moved out of its description into
|
||
a card-level `warn` (`.svc-warn` hover label) and the same hover treatment
|
||
in the docs. Gitea's note now documents self-service signup — no need to
|
||
ask Frank. Footer: docs / admin / account settings are bordered glowing
|
||
`.foot-btn` buttons; **the admin button is identity-gated** — Caddy's
|
||
`/whoami` echoes the outpost-injected `X-Authentik-Username`/`X-Authentik-Groups`
|
||
headers (the outpost strips client-supplied copies, so the echo is
|
||
spoof-proof), and `app.js` shows the button only to `homepage-admins`
|
||
members. The `/admin*` 403 stays the real gate; the e2e specs pin both
|
||
sides (standard user: hidden + 403; admin: visible). Docs were swept for
|
||
naming consistency: Protoss names primary everywhere, bracketed api ids
|
||
at the model headings, and a note that a future switchover renames the
|
||
ids themselves (OpenWebUI picker + raw API + models.yml all move together
|
||
— not done yet).
|
||
|
||
**Icons v2, clickable tiles, header nav (2026-10-08, third wave):** five
|
||
icons replaced on request — Copyparty gets an improvised amber folder,
|
||
Marreta a crumbling wall (bricks + red crack), Calibre-Web a book,
|
||
OpenWebUI its official logo (`openwebui.com/logo.png`), Seerr the official
|
||
`seerr.dev/os_logo_filled.svg`. Tiles with an open link are now clickable
|
||
whole (`.panel.clickable`, hover lift + glow; clicks on inner links pass
|
||
through). Any panel whose live state is DOWN gets a red gradient wash +
|
||
slow pulsing glow (`.panel.down`, and `.mi-card:has(.badge.down)` for
|
||
model cards). docs/admin/account-settings moved from the footer to the
|
||
header (`nav.top-links`). The zealot compaction advice moved out of the
|
||
Use paragraph into a bordered glowing `model.compaction` callout
|
||
(`.mi-comp`), pinned by the component seam test.
|
||
|
||
**Protoss theme, brand-color icons, portraits (2026-10-08, fourth wave):**
|
||
the full-page angular treatment: chamfered 14 px-cut panels and model
|
||
cards, hex-lattice + amber-glow backdrop (`body::after`), scanline
|
||
overlay (`body::before`), diamond-glyph section titles with gradient
|
||
underlines, the gradient MODEL INFORMATION gateway, a blinking cursor
|
||
beside the title, amber scrollbars, cyan text selection, cyan hover glow
|
||
on clickable tiles, and a red pulsing glow on down tiles/cards (the
|
||
`data-state="down"` attribute `applyStatus` now sets). Icons recut to
|
||
official brand colors (Simple Icons CDN): SearXNG, Gitea, Plex, Jellyfin,
|
||
Sonarr, Radarr, Lidarr, Readarr, qBittorrent, Prowlarr, Seerr, OpenWebUI,
|
||
Calibre-Web (amber glyph on amber tile). Handcrafted monograms carry
|
||
per-app colors: Copyparty gold, Marreta brick, Grafana orange, Loki
|
||
indigo, Neo4j network, Alloy violet, Promtail pink, Alertmanager red,
|
||
Prometheus orange, Node Exporter teal, cAdvisor sky, Guake yellow,
|
||
Authentik blue, Seer green radar, RomM a purple GameBoy. New per-service
|
||
fields: `art_bg` (art backdrop — OpenWebUI's dark logo needs `#f5f5f5`),
|
||
`warn` on OpenWebUI (searches ride the Seerr→SearXNG path),
|
||
`model.compaction` (the amber "set this as compaction model" callout
|
||
frank-zealot carries). frank-dragoon's card portrait is the SC2 unit
|
||
portrait (fandom wiki `Dragoon_portrait.jpg`, webp→jpeg). The whole
|
||
effect layer is stripped under `data-bare="1"` (the e2e bare page).
|
||
Unit: `tests/homepage_config.bats` (referenced art files exist, model
|
||
blocks well-formed, deployer ships the `img/` tree).
|
||
|
||
**Docs reorder + model source links (2026-10-08, fifth wave):** the docs
|
||
page section order is now getting-in → troubleshooting → media → chat →
|
||
models → api-access (non-AI first, AI last). The *arr stack (Prowlarr,
|
||
Sonarr, Radarr, Lidarr, Readarr, qBittorrent) is out of the family docs
|
||
entirely — admin tooling; Seerr is presented as the front door for
|
||
requests. Each fleet model gained a Hugging Face source link: card field
|
||
`model.hf` rendered as the cyan `model on huggingface ↗` line under the
|
||
role, and the docs `Model:` bullets link the checkpoint names — which
|
||
checkpoint each endpoint runs is payload state on
|
||
[[frank-dragoon]], [[frank-archon]] and [[frank-zealot]].
|
||
|
||
**Navy Protoss console, maximal cornering, font swap (2026-10-08, sixth
|
||
wave):** the amber CRT palette is retired. The page is now deep space
|
||
navy (`#050d16`) with warp-blue/psychic-cyan accents (`--proto-blue
|
||
#3fa9ff`, `--proto-cyan #7fe7ff`) — StarCraft Protoss console colours.
|
||
Cornering went maximal: every panel, model card, badge, button, callout
|
||
and the MODEL INFORMATION gateway are octagon-cut (all four corners
|
||
chamfered, 18–24 px), and panels/model cards gained a second inset
|
||
octagonal frame line (`::before`) — a double-bevel gateway look. Section
|
||
titles are chamfered plates with the diamond glyph inside the plate.
|
||
Fonts: body is **Share Tech Mono** (legible retro terminal), headings
|
||
are **Rajdhani 600** (the StarCraft UI face) — VT323 is retired; all
|
||
three pages load the pair from Google Fonts. Four tiles that had
|
||
reverted to initials got pictograms: Calibre-Web an open book (orange),
|
||
RomM a purple GameBoy, Copyparty a blue folder with upload arrow,
|
||
Marreta a crumbling brick wall with a red crack (`img/apps/app-*.svg`).
|
||
frank-dragoon's card accent is phase-crystal teal (`#5fe0d0`), matching
|
||
the new palette. Bare mode strips the frames, octagon cuts and fonts.
|
||
|
||
**Simplified console (2026-10-08, seventh wave):** "too busy — clean up
|
||
and simplify/solidify." The sixth-wave effect stack was stripped to
|
||
essentials. Removed: the scanline overlay (`body::before`), the hex
|
||
lattice + twin radial backdrop (one quiet pylon glow above the horizon
|
||
remains), the panel/model-card inner frame lines, all ambient text
|
||
glows (body, title, model names, callouts), the blinking title cursor,
|
||
the gradient section-title plates (headings are plain Rajdhani with a
|
||
hollow diamond glyph + steel underline), the gradient `border-image`
|
||
header rule (solid 2px warp-blue), the pulsing down-state animation
|
||
(down tiles and cards now show a STATIC red border + tint), and the
|
||
button/hover glows. Kept: octagon-cut panels (softer 14 px), unit-color
|
||
model cards, the pictograms, and the fonts. Motion is reserved for
|
||
state — the only animation left is the speaking-voice pulse. The result
|
||
is flat navy panels on crisp 1px borders: same Protoss direction, half
|
||
the pixels.
|
||
|
||
**Model ids official (2026-10-08):** the card ids `frank-zealot`,
|
||
`frank-dragoon`, `frank-archon` are now the harness's official model ids
|
||
in `models.yml`/`config.yml` (see [[OMP Harness]]); the
|
||
systemd services keep the `frank-archon`/`frank-dragoon`/
|
||
`frank-zealot` names. Homepage, docs page and harness now agree on
|
||
one name per model.
|
||
|
||
**Api ids renamed on the wire (2026-10-09):** the promise in
|
||
`docs.html` is kept — the served model ids are now `frank-zealot`,
|
||
`frank-dragoon`, `frank-archon`, so the docs page no longer carries
|
||
`api id:` annotations, and the curl/opencode/Pi examples send
|
||
`frank-zealot`. The systemd services keep the old names. `docs.html`
|
||
names the fleet trio once each; `homepage_config.bats` pins the card
|
||
ids, which did not change.
|
||
|
||
**Console polish (2026-10-08, eighth wave):** five fixes — octagon
|
||
clip-paths replaced with `border-radius` (the clipped corners read as
|
||
gaps in the 1px border); section-title diamonds moved from absolute
|
||
positioning to a fixed-width inline glyph (aligned, wrap-safe); closed
|
||
WIP items grey out (dim type, desaturated art, muted border via
|
||
`:has(.badge.wip-closed)`); the Dragoon tagline is one spliced track
|
||
(`sfx/dragoon-tagline.ogg` — loudnorm-matched, silence-trimmed, 350 ms
|
||
beat; the two SC2 source oggs are trashed); privacy notes became
|
||
**`<details>` disclosure chips** ("⚠ privacy note ▸") pinned to the
|
||
bottom of each box (`margin-top:auto` in a flex-column panel/card) —
|
||
collapsed by default, tap-to-expand, the touch-safe replacement for the
|
||
unreachable hover tooltips. The panel click-to-open handler ignores
|
||
`.svc-warn` taps so the chip never navigates. Same chips on the docs
|
||
page (privacy + uncensored-model warnings). The API-access section was
|
||
reblessed against the live Open WebUI 0.6 UI: steps now match the real
|
||
Show/copy/rotate flow and `img/openwebui-api-keys.png` is a fresh
|
||
screenshot (old shot trashed). The troubleshooting DNS bullet dropped
|
||
its Pi-hole specifics — family members off the LAN cannot use
|
||
house-DNS advice; the bullet now just says the names resolve on the
|
||
home network.
|
||
|
||
**Archon card runs the fleet's own model (2026-10-08):** the
|
||
`frank-archon` homepage card and docs Model bullet swapped the FP8-era
|
||
base-model link for our own HF publication, and the capacity line for the
|
||
endpoint's own concurrency figure; the payload, its publication and the slot
|
||
count all live on [[frank-archon]].
|
||
|
||
## 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-dragoon, frank-archon, frank-zealot — 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/ # served at /img: docs screenshots, unit portraits
|
||
│ ├── apps/ # (unit-*.jpg) and app card icons (apps/*.svg)
|
||
├── sfx/ # StarCraft unit voice .ogg files served at /sfx
|
||
└── 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
|