Calibre-Web, RomM and Seerr sat inside "Media Library Apps" carrying a WIP badge and a "not open to family users yet" sentence each, so the household grid mixed doors they can use with doors they cannot. They now render under their own "Work in progress" heading, placed after AI on the user page, and the copy drops the restated "Work in progress:" for the ask-Frank line the section heading no longer needs to repeat. - app.js: SECTION_ORDER.user gains "Work in progress" last. - services.json: the three cards' section, plus one-line copy trim. - homepage_config.bats / homepage_pages.bats: the section contract now includes the new heading and pins the new order (the old test pinned AI last, which this change deliberately overturns). - Homepage.md: the classification list and the SECTION_ORDER/wip contract lines catch up (the user order in the page was already stale, and "down" was an undocumented wip value). The admin page is untouched: its WIP cards (Gluetun, mariadb, valkey, Postgres) are plumbing inside Infrastructure, not applications.
478 lines
27 KiB
Markdown
478 lines
27 KiB
Markdown
---
|
||
type: reference
|
||
description: "The password-protected service list on Dankclaw: what each card shows and where its data comes from."
|
||
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 **Web Apps, Media Library
|
||
Apps, AI, Work in progress** (the three not-open-yet apps moved under their
|
||
own heading, below AI, on 2026-11-04); 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.
|
||
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
|
||
`: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:
|
||
- **Web Apps** — SearXNG, Gitea, Copyparty, Marreta.
|
||
- **Media Library Apps** — Plex, Jellyfin, Audiobookshelf (moved up from the admin page 2026-11-03, once Authentik SSO made it a household app).
|
||
- **AI** — OpenWebUI, and the three LLM fleet cards (frank-dragoon, frank-archon, frank-zealot — status + docs link, no Open link); their model table renders under this heading.
|
||
- **Work in progress** — Calibre-Web, RomM, Seerr. WIP badge, no Open link, no household account yet: the ask-Frank door. See the docs page's *Three doors that are not open to the household yet*.
|
||
|
||
**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.
|
||
|
||
**The registry is the authority.** `shared/homepage/services.json` decides which
|
||
page a card renders on (`category`), whether it shows a WIP badge instead of live
|
||
status (`wip`), and whether it gets an Open link (`showLink` + `url`). The two
|
||
lists above are a locator only; when they disagree with the registry, the page is
|
||
the bug — fix the page.
|
||
|
||
## 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: Web Apps, Media Library Apps, AI, Work in
|
||
progress; 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"` / `"down"`. When set, the badge shows
|
||
**WIP: OPEN** / **WIP: CLOSED** / **WIP: DOWN** 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
|