docs: v0.84.0 REPORT/CONTEXT/README — catalog-driven initial-login extraction

This commit is contained in:
2026-06-26 11:03:56 +02:00
parent 1705d71dd5
commit e0cf78bb90
3 changed files with 67 additions and 55 deletions
+13 -1
View File
@@ -7,7 +7,19 @@
> >
> Ask Claude Code: "Please update CONTEXT.md with what we did today" > Ask Claude Code: "Please update CONTEXT.md with what we did today"
Last updated: 2026-06-26 (v0.83.0 — Traefik scoped serversTransport for self-signed HTTPS backends; crafty 502 fix) Last updated: 2026-06-26 (v0.84.0 — catalog-driven initial-login extraction; show an app's auto-generated password on its page)
> **2026-06-26 — v0.84.0 (deployed on 9201): show an app's auto-generated first-login on its page.**
> General, catalog-driven mechanism: `.felhom.yml initial_credentials: {file, format json|regex|plain,
> username_key/password_key | username_pattern/password_pattern, note}`. The controller reads the file
> **live** from the container (`internal/stacks/initialcreds.go` `ReadInitialCredentials` + pure
> `parseInitialCreds`, unit-tested), never persists it, and renders a "Kezdeti belépési adatok" card on
> `/apps/{slug}` (masked password + reveal/copy). First consumer: crafty-controller (Crafty writes a
> random admin password to `/crafty/app/config/default-creds.txt`). Live-verified on 9201: card shows
> username `admin` + the real extracted password. Reuse for any future self-seeding app. Security: same
> exposure class as the existing post-deploy reveal / default_creds card — relies on the prod dashboard
> being auth-gated (demo public-unauth is the separate tracked issue). Backlog idea unchanged: a
> `backend_scheme` hint for TLS backends (v0.83.0 line).
> **2026-06-26 — v0.83.0 (deployed on 9201): scoped Traefik backend transport for self-signed HTTPS apps.** > **2026-06-26 — v0.83.0 (deployed on 9201): scoped Traefik backend transport for self-signed HTTPS apps.**
> Crafty is the **first/only catalog app with an HTTPS backend** (self-signed TLS on `:8443`, no plain-HTTP > Crafty is the **first/only catalog app with an HTTPS backend** (self-signed TLS on `:8443`, no plain-HTTP
+44 -54
View File
@@ -1,67 +1,57 @@
# REPORT — Traefik scoped serversTransport for self-signed HTTPS backends (crafty 502 fix) # REPORT — Catalog-driven initial-login extraction (show an app's auto-generated password)
**Repo:** `felhom-controller` · **Version:** `v0.82.0`**`v0.83.0`** · **Date:** 2026-06-26 **Repo:** `felhom-controller` · **Version:** `v0.83.0`**`v0.84.0`** · **Date:** 2026-06-26
**Commit:** `2d6df0f` · **Paired catalog commit:** `app-catalog-felhom.eu` `1216a8a` **Commit:** `1705d71` · **Paired catalog commit:** `app-catalog-felhom.eu` `8fd9c00`
**Deployed:** guest 9201 (`demo-felhom`) on `felhom-pve`, golden/bootstrap mechanism. **Deployed:** guest 9201 (`demo-felhom`) on `felhom-pve`, golden/bootstrap mechanism.
## Problem ## Problem
The crafty-controller healthcheck fix (catalog `68ce009`) un-withheld its Traefik route, exposing a Some apps generate a random first-login password into a file at first boot instead of taking it from a
pre-existing **502**: Traefik proxied **HTTP** to Crafty's **HTTPS-only** self-signed backend on `:8443`. deploy field — Crafty writes one to `/crafty/app/config/default-creds.txt` (its built-in default is
Crafty is the only catalog app with an HTTPS backend (all others serve plain HTTP, so Traefik's default rejected as "too short"). Customers had to read the container logs to find it; the static
HTTP transport works for them). In Traefik v3 `insecureSkipVerify` cannot be set via Docker labels — it `app_info.default_creds` hint can't carry a per-install secret.
must live in static/file config — so the fix necessarily touched the controller (which generates the
Traefik config), not just the catalog.
## Fix (Option B — scoped; verification stays ON by default) ## Fix — a general, catalog-driven mechanism (Crafty is the first consumer)
A **named** serversTransport, opted into per-service — no global `insecureSkipVerify`. - **`.felhom.yml`** gains an optional `initial_credentials` block:
`{file, format: json|regex|plain, container?, username_key/password_key (json),
username_pattern/password_pattern (regex), note}`.
- **`internal/stacks/metadata.go`** — new `InitialCredentials` struct + `Metadata.InitialCreds`
(deep-copied in `deepCopyStack`).
- **`internal/stacks/initialcreds.go`** — `ReadInitialCredentials(stack)` reads the file **live** from
the running container (`docker exec <c> cat <file>`; path passed as a single arg, no shell) and parses
it via the pure, unit-tested `parseInitialCreds` (json/regex/plain). Container defaults to the stack's
main container (`findProbeContainer`). Returns a non-Available result (card hidden) when the container
is down / file missing / parse fails. **Never persists the secret** to `app.yaml` — read on demand.
- **`internal/web/handlers.go`** — `appDetailHandler` populates `InitialCreds` for deployed apps with a
spec; **`app_info.html`** renders a "Kezdeti belépési adatok" card: username + masked password with
Megjelenítés/Másolás (value read from a hidden element — never inlined into a JS literal), clearly
labelled as the **initial** password (valid only until the customer changes it in-app).
- **Tests:** `parseInitialCreds` json (+ default key) / regex (+ missing-pattern) / plain / unknown-format.
`go build`, `go vet`, `go test ./internal/stacks/...` all green.
- `internal/infra/infra.go` — new pure `RenderServersTransports()` + exported const ## Catalog (`8fd9c00`)
`ServersTransportInsecure = "insecure-skip-verify"`. Emits: crafty-controller `.felhom.yml` gains the `initial_credentials` block (json, username/password keys, a
```yaml note) and `first_steps` now points at the app page instead of "find it in the logs".
http:
serversTransports:
insecure-skip-verify:
insecureSkipVerify: true
```
- `internal/stacks/infra.go` — new `ensureServersTransports(traefikDir)` writes
`dynamic/serverstransports.yml` (0644) idempotently (write-only-on-change, like `wireController`, so the
traefik file-watcher doesn't reload each self-heal tick). Called from `EnsureBaseStack` **outside**
`ensureTraefik` (which early-returns when traefik is already running) so an established node still
materializes the file on the next self-heal tick / restart; the file-provider `watch: true` hot-loads it
(no traefik restart).
- Catalog (`1216a8a`): crafty service gains `loadbalancer.server.scheme=https` +
`loadbalancer.serverstransport=insecure-skip-verify@file`.
- Tests: `TestServersTransports` (named transport + insecureSkipVerify + valid YAML) and the new render
added to the YAML-parse / no-`:latest` matrix. `go test ./internal/infra/... ./internal/stacks/...` green.
## Deploy ## Security posture
Commit → build+push `felhom-controller:0.83.0` on 192.168.0.180 → guest 9201 (`docker pull` → This surfaces a **live working credential** on the app page — same exposure class as the existing
`/etc/felhom-controller-image` → restart bootstrap svc). On controller startup, `EnsureBaseStack` wrote post-deploy password reveal and the `default_creds` card, and rendered with the same mask/reveal UX. It
`dynamic/serverstransports.yml` automatically (logged: `[infra] wrote backend transports → … (insecure-skip-verify)`) relies on the dashboard being **auth-gated in production**; the demo's public-unauth dashboard is a
— no manual file drop needed. Catalog sync (`POST /api/sync`) pulled the two labels; crafty recreated via separate, pre-existing tracked issue (not introduced here). The secret is never written to controller
`POST /api/stacks/crafty-controller/update`. state — only read live from the volume where it already lives.
## Verification (live, guest 9201) ## Verification (live, guest 9201, controller v0.84.0)
| Check | Result | | Check | Result |
|---|---| |---|---|
| Dynamic file present + loaded | `/opt/docker/stacks/traefik/dynamic/serverstransports.yml` written, no traefik parse error | | Spec synced + parsed | on-disk `.felhom.yml` carries `initial_credentials`; `/api/stacks/crafty-controller` meta shows it |
| **A — public URL** | `https://minecraft.demo-felhom.eu` → **302** (was 502), via Cloudflare edge + in-guest Traefik | | Card renders | `/apps/crafty-controller` HTML contains the "Kezdeti belépési adatok" card |
| **A — Traefik backend** | access log: `302 … "crafty-controller@docker" "https://172.18.0.6:8443"` (before: `502 … "http://172.18.0.6:8443"`) | | Username | `initcred-user` cell renders `admin` |
| **B — other apps unaffected** | filebrowser (HTTP backend) still **200** via Traefik — scoped, no global change | | Password (live-extracted) | hidden value element carries the exact password from the container's `default-creds.txt` (HTML-escaped `&``&amp;`, decoded back by JS `textContent`) |
| **C — scoping proof** | **no** `insecureSkipVerify` in static `traefik.yml`; present ONLY in `dynamic/serverstransports.yml` | | Controls | `Felhasználónév` label + `icRevealPw`/`icCopyPw` reveal/copy wired |
| crafty health | `Up (healthy)`, dashboard state `running` |
## Deployed versions (`docker ps`) **Method:** fetched the exact HTML the browser receives via the in-guest API (Host: felhom.demo-felhom.eu).
``` The reveal/copy is client-side JS — wiring confirmed in markup, not browser-clicked this run.
felhom-controller gitea.dooplex.hu/admin/felhom-controller:0.83.0 Up (healthy)
crafty-controller registry.gitlab.com/crafty-controller/crafty-4:4.4.8 Up (healthy)
```
## Verification method
curl to the Cloudflare public edge + in-guest Traefik probe (`--resolve …:127.0.0.1`) + Traefik access
logs. (No browser tool used this run.)
## Observation (carry, not acted on) ## Observation (carry, not acted on)
Generalize a `backend_scheme`/`backend_tls` hint in `.felhom.yml` so future HTTPS-backend apps get the The whole-dashboard public-unauth exposure on the demo remains the right place to fix credential
`scheme=https` + `serverstransport=…@file` labels via the catalog convention rather than hand-added — visibility broadly — tracked separately; this feature should not be read as endorsing an unauthenticated
own task. dashboard.
+10
View File
@@ -207,6 +207,16 @@ Each app can define rich metadata in `.felhom.yml`:
- `app_info`: tagline, use_cases, first_steps, prerequisites, default_creds, docs_url - `app_info`: tagline, use_cases, first_steps, prerequisites, default_creds, docs_url
- `optional_config`: groups of post-deploy configurable env vars (e.g., API keys for metadata providers) - `optional_config`: groups of post-deploy configurable env vars (e.g., API keys for metadata providers)
- `resources`: mem_request, mem_limit, pi_compatible, needs_hdd, hungarian_ui - `resources`: mem_request, mem_limit, pi_compatible, needs_hdd, hungarian_ui
- `initial_credentials` (v0.84.0): for apps that **auto-generate a first-login password into a file** at
first boot (vs taking it from a deploy field) — e.g. Crafty → `/crafty/app/config/default-creds.txt`.
Fields: `file` (path inside the container), `format` (`json`|`regex`|`plain`), optional `container`
(defaults to the stack's main container), `username_key`/`password_key` (json), `username_pattern`/
`password_pattern` (regex, first capture group), `note`. `ReadInitialCredentials`
(`internal/stacks/initialcreds.go`) reads the file **live** via `docker exec … cat` and parses it with
the pure `parseInitialCreds`; the value is **never persisted** to `app.yaml`. `appDetailHandler`
surfaces it on `/apps/{slug}` as a "Kezdeti belépési adatok" card (masked password + reveal/copy),
labelled as the *initial* credential (stays valid only until the customer changes it in-app). Hidden
when the container is down / file missing / parse fails. Reuse for any future self-seeding app.
The `/apps/{slug}` page renders hero section, screenshots, setup guide, and optional config form. The `/apps/{slug}` page renders hero section, screenshots, setup guide, and optional config form.