Homepage: settle design (grilling Q1-Q5), log open questions Q6-Q43, progress report

- Homepage.md: rewritten to the settled design (single site, user + /admin/
  pages, server-side status.json via a 3-min systemd-timer checker, terminal
  TUI theme). Added mermaid diagrams: site structure, status states, checker
  flow. Recorded the classification (user vs admin; *arr excluded; LLM admin;
  backends status-only) and the documented future shift to client-side probing.
- Homepage Design Grilling.md (new): the decision log — settled Q1-Q5 plus 38
  open questions (Q6-Q43) across 10 categories, each with a recommendation.
- docs/index.md: link the grilling page (satisfies the index-links-every-page test).
- report/homepage-grilling-2026-08-22.html: self-contained terminal-TUI progress
  report (settled decisions, verified stack baseline, overnight processing, work
  threads, the open questions, and how to continue).

Baseline recorded: Dankclaw fully up (10/10); the Dankserver media stack is down
(only Calibre-Web + the LLM are up; no .env on the target) — a separate thread.
No homepage code built yet; the design is pending the operator's answers to Q6-Q43.
This commit is contained in:
Frank
2026-08-23 02:59:48 -07:00
parent 6a9edd4ee0
commit cdae928f37
4 changed files with 499 additions and 21 deletions
@@ -0,0 +1,167 @@
# Homepage Design Grilling
The decision log for the homepage design. It records what is settled and the open questions that remain. Answer the open questions in the morning report; the design is built only after they are settled.
## Decided
| # | Decision | Value |
|---|----------|-------|
| Q1 | How the page learns up/down | **Server-side** checker writes `status.json`; the page reads only that file. (Eventual target: client-side probing.) |
| Q2 | How the checker runs | **systemd timer**, rewrites `status.json` every **3 minutes**. |
| Q3 | Page model | **Single site** on Dankclaw. Root = user services (no host labels). `/admin/` = admin services, separate password. Optional `/how/` tourist page. |
| Q4 | (superseded by Q3 restructure) | — |
| Q5 | Service split | User: SearXNG, ComfyUI, OpenWebUI, Gitea, Calibre-Web, RomM, Copyparty, Plex, Jellyfin. Admin: Hermes, Grafana, Prometheus, Loki, Caddy, dnsmasq, Gluetun, LLM fleet, low-level backends (status-only). The *arr stack is **excluded** (separate effort, not started). |
Styling is settled in principle: terminal TUI, Terminus (monospace fallback), green-on-black, 1995 phosphor palette (green `#33ff66`, red `#ff5555`, amber `#ffcc33`, cyan `#33ccff`, bg `#050805`).
## Open questions
Each question carries a recommendation. Answer them in the morning report.
### A. Architecture and structure
**Q6 — The optional `/how/` "tourist" page: build now or defer?**
Recommendation: **defer** to a later effort. When built, make it **public** (no auth) so a visitor can see the architecture. It can render the mermaid diagrams from this repo.
**Q7 — One `status.json` or two (`user-status.json` + `admin-status.json`)?**
Recommendation: **one** `status.json`. Each service carries a `category` field. Each page filters client-side. One file, one checker output, one source of truth.
**Q8 — One `services.json` or split by page?**
Recommendation: **one** `services.json` with all services and a `category` field. The checker reads one file. Simpler to maintain.
### B. The checker
**Q9 — What language for the checker script?**
Recommendation: **python3** (available on Dankclaw, no dependencies, emits valid JSON cleanly). A bash + `nc` variant works too, but JSON by hand is error-prone.
**Q10 — Probe method: TCP connect or HTTP GET?**
Recommendation: **TCP connect** to `host:port`. Uniform, works for non-HTTP backends, and "port listening = up" matches the verified ground truth.
**Q11 — Probe timeout per service?**
Recommendation: **3 seconds**. A dead port refuses fast; a black-holed one times out at 3 s. With ~25 services the worst-case run is well under the 3-minute interval.
**Q12 — LLM "loading" state (health returns 503 during model load)?**
Recommendation: keep **three** states. A listening port shows **Up**. Optionally probe `/health` and treat 503 as Up (the process is alive). A dedicated "loading" state is a later refinement.
**Q13 — Where does the checker live in the repo?**
Recommendation: `scripts/homepage-status.py` plus `scripts/homepage-status.service` and `scripts/homepage-status.timer`, all tracked in git like the other units.
**Q14 — How does the checker reach the Dankserver backends?**
Recommendation: the `probe` field carries a full `host:port`. Dankclaw services use `127.0.0.1:PORT`. Dankserver services use `192.168.0.235:PORT`. The checker (on Dankclaw) connects to each over the LAN. Verified reachable.
### C. The status model
**Q15 — How is a service marked WIP?**
Recommendation: a static `"wip": true` field. The checker skips the probe and the page shows WIP. Drop the old self-referential "Homepage" entry (a page does not list itself).
**Q16 — Staleness: what if the timer fails and `status.json` is old?**
Recommendation: write a `generated_at` timestamp into `status.json`. The page shows a "status may be stale" warning when it is older than 10 minutes.
**Q17 — Atomic writes?**
Recommendation: the checker writes to a temp file, then renames over `status.json`. A crash never leaves a half-written file. The last good status survives.
### D. Auth
**Q18 — The admin password: a second `.env` variable?**
Recommendation: yes. `CADDY_ADMIN_HASH` in the host `.env` (git-ignored), rendered into the Caddyfile at deploy, operator-only. The root page keeps `CADDY_BASICAUTH_HASH`.
**Q19 — Per-path basicauth in Caddy v2?**
Recommendation: a `@admin path /admin/*` matcher with a second `basicauth @admin` block. Confirm the exact syntax against the Caddy docs during implementation.
### E. The pages and content
**Q20 — Where do the one-line descriptions come from?**
Recommendation: hand-written one-liners in `services.json`, drafted from the existing reference docs.
**Q21 — The "Open" link target: the Caddy site or the direct backend?**
Recommendation: the **Caddy site** URL (what a user actually opens). Status-only backends set `showLink: false` (no Open link).
**Q22 — The "docs" link: usage page or the existing reference doc?**
Recommendation: link to the **existing** reference doc (`docs/.../X.md`) as a placeholder. Swap to the per-service usage page when that workstream is built.
**Q23 — Page layout: cards or a terminal-style list?**
Recommendation: a **terminal-style list** (one service per line, like `systemctl list-units`). Columns: `STATUS | NAME | description | (open) (docs)`. Fits the TUI aesthetic better than cards.
**Q24 — A summary line at the top?**
Recommendation: yes. One line, e.g. `UP 9 DOWN 2 WIP 1`, per page.
### F. Styling
**Q25 — Terminus: bundle the font or use a fallback?**
Recommendation: **bundle** Terminus (woff2) via `@font-face`, with a monospace fallback stack. Confirm the license (Terminus is freely distributable) and drop the files in `fonts/`.
**Q26 — Confirm the palette?**
Recommendation: as proposed (green `#33ff66`, red `#ff5555`, amber `#ffcc33`, cyan `#33ccff`, bg `#050805`). Tune for contrast and readability.
**Q27 — CRT effects (scanlines + glow)?**
Recommendation: subtle scanlines + a soft green `text-shadow`. Make it toggleable (a `?crt=0` param) for those who find it much.
**Q28 — Blinking cursor / animation?**
Recommendation: a subtle CSS-only blinking block cursor on the header. Respect `prefers-reduced-motion`.
### G. The usage-docs workstream
**Q29 — Scope of the per-service usage pages?**
Recommendation: concise **how-to** guides (what it is, how to open it, the common tasks). Distinct from the deep reference docs in `docs/`.
**Q30 — Where do the usage pages live?**
Recommendation: served from the homepage site, same origin, at `/usage/<svc>/`. Source in the repo under `shared/homepage/usage/<svc>/`. Same origin keeps the "docs" link working.
**Q31 — Are the usage pages password-protected?**
Recommendation: yes. User usage pages behind the shared password, admin usage pages behind the operator password. Consistent with the pages they extend.
**Q32 — Is the usage-docs workstream a separate effort?**
Recommendation: yes, after the core homepage is live. For now the "docs" link points to the existing reference doc (Q22).
### H. Deployment and operations
**Q33 — How is the homepage deployed?**
Recommendation: extend `deploy.sh` (Dankclaw path): copy `shared/homepage/` to `/srv/homepage/`, install the timer + service, render the Caddyfile (both hashes), reload Caddy.
**Q34 — Who creates `/srv/homepage/`?**
Recommendation: `deploy.sh` creates it and copies the assets. The checker writes `status.json` there at runtime.
**Q35 — Is the timer enabled at boot?**
Recommendation: yes. A systemd timer + service, enabled, so it survives reboots and runs every 3 minutes.
### I. Testing
**Q36 — Tests for the checker?**
Recommendation: yes. A bats test runs the checker against a fake up port, a fake down port, and a WIP service, then validates the `status.json` schema. TDD, per repo rules.
**Q37 — Tests for the pages and config?**
Recommendation: yes. Validate the `services.json` schema (required fields, valid categories), run `caddy validate` on the Caddyfile, and check the pages reference `status.json`.
**Q38 — A security test for the two passwords?**
Recommendation: yes. Assert the Caddyfile carries two distinct basicauth hashes (root vs `/admin/`).
### J. Edge cases and robustness
**Q39 — A backend moves hosts (e.g. the *arr stack to a new box)?**
Recommendation: no code change. The `probe` field is a full `host:port`. A move is a config edit.
**Q40 — A slow LAN hop to a Dankserver backend?**
Recommendation: the 3 s timeout handles it. A slow-but-alive backend connects (Up); a dead one times out (Down).
**Q41 — Client-side auto-refresh?**
Recommendation: the page polls `status.json` every **60 s** (the file updates every 3 min; 60 s is responsive without hammering).
**Q42 — Mobile responsiveness?**
Recommendation: yes. Stack the columns and shrink the font on narrow screens.
**Q43 — A "last checked" timestamp on the page?**
Recommendation: yes. The footer shows `last checked HH:MM:SS` from `generated_at`.
## How to continue
Answer the open questions (Q6–Q43) in the morning report. Group the answers by category. Once settled, the build order is:
```mermaid
flowchart LR
A[settle Q6-Q43] --> B[write services.json + checker]
B --> C[write the pages + theme]
C --> D[extend deploy.sh + Caddyfile]
D --> E[tests green]
E --> F[deploy to Dankclaw]
F --> G[verify in browser]
```
+112 -21
View File
@@ -1,56 +1,147 @@
# Homepage
A password-protected homepage on each host lists the services that are up, with a short description of each and a link to both the service and its documentation.
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` + `basicauth` — **Docs:** n/a (static page in this repo)
**Served by:** Caddy, `file_server` + `basicauth` — **Docs:** this page
## Purpose
## Status — 2026-08-22: work in progress
- One landing page per host instead of a bookmark folder.
- Shows at a glance which services are currently up and active.
- Answers "what is this?" for every service with a one-line description.
- Links to the service and to this repo's docs page for that service.
The homepage is not finished. The static assets exist in `shared/homepage/`. They are not yet copied to `/srv/homepage/` on the target. Caddy serves the `basicauth` challenge (HTTP 401) and the `:80` redirect (HTTP 302). The page content is absent.
Do not treat the missing page as an outage. The Caddy site block and the redirect are live. Only the content and the deploy step remain.
## 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 optional and undecided.
The client-side probe design is blocked in this topology. The page origin is `https://dankclaw.franksdank.cloud`. The service URLs are `http://<name>.franksdank.cloud`. That is cross-origin, mixed content, and auth-gated (HTTP 401). All three fail a browser `fetch`.
Intended direction: move to client-side probing. Serve every service as a same-origin `https` path under `dankclaw.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 three pages. The root page lists the user services. The `/admin/` page lists the admin and infrastructure services. The `/how/` page (optional) shows the architecture to visitors.
```mermaid
graph TD
subgraph site["dankclaw.franksdank.cloud (Caddy)"]
ROOT["/ user services<br/>(shared password)"]
ADMIN["/admin/ admin services<br/>(operator password)"]
HOW["/how/ how it is built<br/>(optional)"]
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
[*] --> 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.
```mermaid
flowchart TD
A[timer fires] --> B[read service config]
B --> C{for each service}
C -->|wip| D[mark WIP]
C -->|probe| E[TCP connect to host:port]
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, dnsmasq, 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 for now:** the *arr stack (Radarr, Sonarr, Lidarr, Readarr, Prowlarr, qBittorrent). It is not started. It is a separate effort, possibly on a different host.
## Layout
```
shared/
└── homepage/
├── index.html # the page, shared logic, per-host service list injected
├── style.css
└── services.json # service list: name, hostname, port, description, docs link
shared/homepage/
├── index.html # root page (user services)
├── admin.html # admin + infrastructure services
├── how.html # optional architecture page
├── style.css # terminal TUI theme
├── app.js # loads status.json, renders the badges
├── services.json # service config (user + admin)
└── fonts/ # Terminus (woff2) for @font-face
```
The page fetches `services.json` and pings each service's health endpoint (or the proxy hostname) to set an up/down badge. It is static; the status check runs in the browser via `fetch`.
`status.json` is generated at runtime in `/srv/homepage/`. It is not committed.
## Auth
Caddy wraps the homepage with HTTP basic auth:
Caddy wraps the site with HTTP basic auth. The root page uses the shared password. The `/admin/` page uses a second, operator-only password. The hashes come from the host `.env` (git-ignored). Do not commit the hash or the password.
```
dankclaw.local {
dankclaw.franksdank.cloud {
@admin path /admin/*
basicauth {
dank $2a$14$HASH
dank {env.CADDY_BASICAUTH_HASH}
}
basicauth @admin {
frank {env.CADDY_ADMIN_HASH}
}
root * /srv/homepage
file_server
}
```
Generate the hash with `caddy hash-password` at deploy time. Do not commit the hash or the password.
## Service Entry Format
## Service entry format
```json
{
"name": "SearXNG",
"hostname": "http://search.dankclaw.local",
"category": "user",
"description": "Privacy-respecting metasearch engine.",
"docs": "/docs/Dankclaw/SearXNG.md"
"url": "http://search.franksdank.cloud",
"docs": "/usage/searxng/",
"probe": "127.0.0.1:8081",
"wip": false,
"showLink": true
}
```
- `category` — `user` or `admin`. Selects the page.
- `url` — the "Open" link (the Caddy site).
- `docs` — the per-service usage page.
- `probe` — the backend `host:port` the checker probes.
- `wip` — when true, the badge shows WIP and the checker skips the probe.
- `showLink` — when false, the card shows status only (no "Open" link).
## See Also
- [[Homepage Design Grilling]] — the decision log and open questions
- [[Caddy]] — the proxy that serves it
- [[Dankclaw Stack]], [[Dankserver Stack]] — the services it lists
+1
View File
@@ -65,6 +65,7 @@ covers both workloads).
- [[LAN-DNS]] — name resolution, dnsmasq, router DNS hand-off
- [[Caddy]] — reverse proxy and TLS
- [[Homepage]] — password-protected service index
- [[Homepage Design Grilling]] — homepage decision log and open questions
- [[Observability]] — Grafana, Prometheus, Loki, Alloy
- [[Grafana]] — dashboards
- [[Prometheus]] — metrics collection