frank@program:~/homepage$ grilling --progress

Frank Program · homepage design grilling · overnight progress report · 2026-08-22 · from the chief designer

summary   settled: 5 decisions + styling   open: 38 questions (Q6-Q43)
baseline  dankclaw: UP (10/10)   dankserver: media stack DOWN (no .env)
state     WIP — /srv/homepage/ not deployed; Caddy site live (401 / 302)
next      answer Q6-Q43 in the morning; then build order is fixed

What is settled

#DecisionValue
Q1How the page learns up/downServer-side checker writes status.json; the page reads only that file. (Eventual target: client-side probing — documented.)
Q2How the checker runssystemd timer, rewrites status.json every 3 minutes.
Q3Page modelSingle site on Dankclaw. Root = user services (no host labels). /admin/ = admin services, second operator-only password. Optional /how/ tourist page.
Q5Service splitUser: 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). *arr stack excluded (separate effort).
—StylingTerminal TUI. Terminus (monospace fallback). Green-on-black. 1995 phosphor palette.

Palette: bg #050805 · text #33ff66 · up #33ff66 · down #ff5555 · WIP #ffcc33 · links #33ccff · headers #aaffcc.

Stack baseline (verified 2026-08-22)

Source: real HTTP fetch through Caddy + ss -ltn (they agree). The earlier /dev/tcp probe was the false one — it reported every port down while Caddy returned 200.

Dankclaw 192.168.0.143 — AI / search / dev / observability

ServiceSiteHTTPPortState
Homepagedankclaw401—WIP auth-gated, not deployed
SearXNGsearch4018081 ✓UP (auth-gated)
ComfyUIcomfyui2008188 ✓UP
OpenWebUIchat4013005 ✓UP (auth-gated)
Hermeshermes4018090 ✓UP (auth-gated)
Giteagitea2003000 ✓UP
Grafanagrafana4013002 ✓UP (auth-gated)
Prometheusprometheus4019090 ✓UP (auth-gated)
Lokiloki4043100 ✓UP (no root page)
frank-default-large:7770ok7770 ✓UP

Dankserver 192.168.0.235 — media

ServiceSiteHTTPPortState
Calibre-Webbooks3028083 ✓UP
frank-default-small:7770ok7770 ✓UP
Plexplex502 / 00032400 boundUNDETERMINED port bound, no HTTP reply
RomMromm4018085 ✗DOWN (401 = auth challenge only)
Copypartyfiles4013923 ✗DOWN (401 = auth challenge only)
Jellyfinjellyfin5028096 ✗DOWN
Radarr / Sonarr / Lidarr / Readarr / Prowlarr / qBittorrent—502✗DOWN (not started — *arr effort)

My processing — what I worked out overnight

1The port-probe contradiction is resolved. My first probe (/dev/tcp nested in bash -c over SSH) reported every port down while Caddy returned 200 in the same call. The ss -ltn + HTTP-fetch pair is the ground truth and they agree. Do not trust the /dev/tcp one-liner in an SSH'd subshell.

2The 401 trap. For basicauth sites, Caddy serves the 401 challenge before the proxy. So a 401 does not prove the backend is up — RomM and Copyparty return 401 while their ports are down. This is why the checker must probe the backend host:port directly, never the Caddy site.

3The homepage's core mechanism is architecturally blocked. The page is https://dankclaw.franksdank.cloud; the service URLs are http://<name>.franksdank.cloud. A browser fetch of those is cross-origin (CORS-blocked), mixed content (https→http, hard-blocked), and auth-gated (401). Three independent failures. Hence the server-side status.json design.

4The Dankserver media stack is down, and the cause is a missing .env. Only Calibre-Web (no secret deps) and the LLM (separate systemd unit) are up. compose.yaml needs MARIADB_ROOT_PASSWORD, ROMM_*, COPAY_PASS, … — none present on the target. This is a separate, urgent work item, distinct from the homepage.

5Plex is in an odd state. Port 32400 is bound on 0.0.0.0 but a direct HTTP request gets no reply (000). Needs a look — it may be a stale socket or a non-HTTP listener. Flagged as UNDETERMINED, not counted up or down.

6LLM "loading" nuance. During model load the LLM /health returns 503. A TCP-connect probe still reads "up" (the port is listening). Kept to three states for now; a dedicated "loading" state is a later refinement.

7My host is outside the LAN. *.franksdank.cloud resolves to the public IP here; the services are LAN-only (dnsmasq maps them to 192.168.0.143 for LAN clients). So page loads and probes must run from Dankclaw.

Work threads identified

ThreadWhat it isStatus
1 · HomepageThis design. Server-side status, user + admin pages, terminal TUI.GRILLING 38 open questions
2 · Dankserver media stackDown. Needs a .env (secrets) + a compose deploy.BLOCKED separate, urgent
3 · *arr stackRadarr/Sonarr/Lidarr/Readarr/Prowlarr/qBittorrent. Not started; possibly a different host.NOT STARTED
4 · Usage-docsPer-service how-to pages, maintained separately from the reference docs.NOT STARTED
5 · "How it's built"Optional public architecture page for tourists.OPTIONAL
6 · PlexInvestigate the bound-but-silent port 32400.INVESTIGATE
7 · Client-side probingDocumented future intent: same-origin https paths + per-service health + CORS.LATER

Open questions — answer these in the morning

Each carries a recommendation. Reply AGREE to accept it, or state the change. Group by category letter.

A · Architecture and structure

Q6 — The optional /how/ "tourist" page: build now or defer?Rec: defer. When built, make it public (no auth) so a visitor can see the architecture; render the mermaid diagrams from this repo.verdict: AGREE · or change to: ____
Q7 — One status.json or two (user-status.json + admin-status.json)?Rec: one status.json; each service carries a category field; each page filters client-side.verdict: AGREE · or change to: ____
Q8 — One services.json or split by page?Rec: one services.json with a category field; the checker reads one file.verdict: AGREE · or change to: ____

B · The checker

Q9 — What language for the checker script?Rec: python3 (available on Dankclaw, no deps, clean JSON). A bash+nc variant works but hand-rolled JSON is error-prone.verdict: AGREE · or change to: ____
Q10 — Probe method: TCP connect or HTTP GET?Rec: TCP connect to host:port. Uniform, works for non-HTTP backends, matches the verified ground truth.verdict: AGREE · or change to: ____
Q11 — Probe timeout per service?Rec: 3 s. A dead port refuses fast; a black-holed one times out at 3 s. Well under the 3-minute interval.verdict: AGREE · or change to: ____
Q12 — LLM "loading" state (health 503 during model load)?Rec: keep three states; a listening port is Up. Optionally probe /health and treat 503 as Up. A "loading" state is a later refinement.verdict: AGREE · or change to: ____
Q13 — Where does the checker live in the repo?Rec: scripts/homepage-status.py + scripts/homepage-status.service + scripts/homepage-status.timer, tracked in git.verdict: AGREE · or change to: ____
Q14 — How does the checker reach the Dankserver backends?Rec: the probe field is a full host:port — 127.0.0.1:PORT for Dankclaw, 192.168.0.235:PORT for Dankserver. Verified reachable over the LAN.verdict: AGREE · or change to: ____

C · The status model

Q15 — How is a service marked WIP?Rec: a static "wip": true field; the checker skips the probe. Drop the old self-referential "Homepage" entry.verdict: AGREE · or change to: ____
Q16 — Staleness: what if the timer fails and status.json is old?Rec: write a generated_at timestamp; the page warns "status may be stale" when it is older than 10 minutes.verdict: AGREE · or change to: ____
Q17 — Atomic writes?Rec: write to a temp file, then rename over status.json. A crash never leaves a half-written file.verdict: AGREE · or change to: ____

D · Auth

Q18 — The admin password: a second .env variable?Rec: yes — CADDY_ADMIN_HASH in the host .env (git-ignored), rendered at deploy, operator-only. Root keeps CADDY_BASICAUTH_HASH.verdict: AGREE · or change to: ____
Q19 — Per-path basicauth in Caddy v2?Rec: an @admin path /admin/* matcher with a second basicauth @admin block. Confirm exact syntax against the Caddy docs during implementation.verdict: AGREE · or change to: ____

E · The pages and content

Q20 — Where do the one-line descriptions come from?Rec: hand-written one-liners in services.json, drafted from the existing reference docs.verdict: AGREE · or change to: ____
Q21 — The "Open" link: the Caddy site or the direct backend?Rec: the Caddy site URL (what a user opens). Status-only backends set showLink: false.verdict: AGREE · or change to: ____
Q22 — The "docs" link: usage page or the existing reference doc?Rec: link to the existing reference doc as a placeholder; swap to the per-service usage page when that workstream is built.verdict: AGREE · or change to: ____
Q23 — Page layout: cards or a terminal-style list?Rec: a terminal-style list (one service per line, like systemctl list-units). Columns: STATUS | NAME | description | (open) (docs).verdict: AGREE · or change to: ____
Q24 — A summary line at the top?Rec: yes — one line per page, e.g. UP 9 DOWN 2 WIP 1.verdict: AGREE · or change to: ____

F · Styling

Q25 — Terminus: bundle the font or use a fallback?Rec: bundle Terminus (woff2) via @font-face, with a monospace fallback. Confirm the license (freely distributable) and drop files in fonts/.verdict: AGREE · or change to: ____
Q26 — Confirm the palette?Rec: as proposed (green #33ff66, red #ff5555, amber #ffcc33, cyan #33ccff, bg #050805). Tune for contrast.verdict: AGREE · or change to: ____
Q27 — CRT effects (scanlines + glow)?Rec: subtle scanlines + a soft green text-shadow. Make it toggleable (?crt=0).verdict: AGREE · or change to: ____
Q28 — Blinking cursor / animation?Rec: a subtle CSS-only blinking block cursor on the header; respect prefers-reduced-motion.verdict: AGREE · or change to: ____

G · The usage-docs workstream

Q29 — Scope of the per-service usage pages?Rec: concise how-to guides (what it is, how to open it, the common tasks). Distinct from the deep reference docs.verdict: AGREE · or change to: ____
Q30 — Where do the usage pages live?Rec: served from the homepage site, same origin, at /usage/<svc>/; source under shared/homepage/usage/<svc>/.verdict: AGREE · or change to: ____
Q31 — Are the usage pages password-protected?Rec: yes — user pages behind the shared password, admin pages behind the operator password.verdict: AGREE · or change to: ____
Q32 — Is the usage-docs workstream a separate effort?Rec: yes, after the core homepage is live. For now the "docs" link points to the existing reference doc (Q22).verdict: AGREE · or change to: ____

H · Deployment and operations

Q33 — How is the homepage deployed?Rec: extend deploy.sh (Dankclaw path): copy shared/homepage/ to /srv/homepage/, install the timer + service, render the Caddyfile (both hashes), reload Caddy.verdict: AGREE · or change to: ____
Q34 — Who creates /srv/homepage/?Rec: deploy.sh creates it and copies the assets; the checker writes status.json there at runtime.verdict: AGREE · or change to: ____
Q35 — Is the timer enabled at boot?Rec: yes — a systemd timer + service, enabled, survives reboots, runs every 3 minutes.verdict: AGREE · or change to: ____

I · Testing

Q36 — Tests for the checker?Rec: 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.verdict: AGREE · or change to: ____
Q37 — Tests for the pages and config?Rec: yes — validate the services.json schema, run caddy validate, and check the pages reference status.json.verdict: AGREE · or change to: ____
Q38 — A security test for the two passwords?Rec: yes — assert the Caddyfile carries two distinct basicauth hashes (root vs /admin/).verdict: AGREE · or change to: ____

J · Edge cases and robustness

Q39 — A backend moves hosts (e.g. the *arr stack to a new box)?Rec: no code change — the probe field is a full host:port; a move is a config edit.verdict: AGREE · or change to: ____
Q40 — A slow LAN hop to a Dankserver backend?Rec: the 3 s timeout handles it — a slow-but-alive backend connects (Up); a dead one times out (Down).verdict: AGREE · or change to: ____
Q41 — Client-side auto-refresh?Rec: the page polls status.json every 60 s (the file updates every 3 min).verdict: AGREE · or change to: ____
Q42 — Mobile responsiveness?Rec: yes — stack the columns and shrink the font on narrow screens.verdict: AGREE · or change to: ____
Q43 — A "last checked" timestamp on the page?Rec: yes — the footer shows last checked HH:MM:SS from generated_at.verdict: AGREE · or change to: ____

What I changed (knowledge base)

FileChange
serverAppConfigs-docs/Infrastructure/Homepage.mdRewritten to the settled design: structure, status model, checker, auth, classification, layout. Added three mermaid diagrams (site structure, status states, checker flow).
serverAppConfigs-docs/Infrastructure/Homepage Design Grilling.mdNew. The decision log: settled Q1–Q5 + the 38 open questions (Q6–Q43) with recommendations + the build-order diagram.
serverAppConfigs-docs/index.mdLinked the new grilling page (satisfies the "index links every page" test).
~/.pi/agent/docs/log.mdAdded a homepage-design bullet to the 2026-08-22 entry (design, baseline, threads).
report/homepage-grilling-2026-08-22.htmlThis report. Self-contained, terminal-TUI styled.

How to continue

Reply to the open questions (Q6–Q43) — AGREE or the change, grouped by category. Once settled, the build order is fixed:

settle Q6-Q43  ->  write services.json + checker  ->  write pages + theme
   ->  extend deploy.sh + Caddyfile  ->  tests green  ->  deploy to Dankclaw
   ->  verify in browser

Separate threads to schedule (not part of the homepage build): the Dankserver media stack (missing .env), the *arr stack, the usage-docs workstream, the optional "how it's built" page, and the Plex investigation.

Report is self-contained (no network needed). A copy is committed at report/homepage-grilling-2026-08-22.html and served at the URL below.

generated 2026-08-22 (night) · frank program · chief designer · tests: 97/97 green