docs: v0.77.0 open_path — REPORT + README
This commit is contained in:
@@ -1,49 +1,36 @@
|
||||
# REPORT — controller v0.76.0: campaign-#3 hardening (3 fixes)
|
||||
# REPORT — controller v0.77.0: per-app `open_path` for the "Megnyitás" link
|
||||
|
||||
## Baseline → target
|
||||
`felhom-controller` `main` v0.75.0 → **v0.76.0**. Controller-only; agent/hub untouched.
|
||||
## Problem
|
||||
The dashboard / deploy / app-info **"Megnyitás"** (open app) button was hardcoded to the bare subdomain
|
||||
root `https://{subdomain}.{domain}` for every app. Apps whose useful UI isn't at `/` opened to the wrong
|
||||
place — e.g. Gokapi's `/` redirects away (its admin is `/admin`), Ghost's `/` is the public blog (admin
|
||||
is `/ghost/`).
|
||||
|
||||
## What changed (commit `b0dd131`)
|
||||
Three findings from chaos campaign #3, severity-ordered:
|
||||
## Change (commit `904e787`)
|
||||
- `internal/stacks/metadata.go`: new optional `Metadata.OpenPath` (`open_path` in `.felhom.yml`),
|
||||
appended to the open-app URL. Empty = bare root (unchanged behaviour for all other apps).
|
||||
- Wired into all three link sites via `.Meta.OpenPath` — **no handler changes** (all three templates
|
||||
already carry `.Meta`):
|
||||
- `dashboard.html:173` (range over stacks → `.Meta.OpenPath`)
|
||||
- `deploy.html:18` (`.Meta` from the deploy handler)
|
||||
- `app_info.html:13` (`.Meta` from the app-info handler)
|
||||
|
||||
- **S1 [MEDIUM] — corrupt `settings.json` no longer crash-loops the controller.**
|
||||
`internal/settings/settings.go`: `save()` writes a last-known-good `<path>.bak` **after** the primary
|
||||
rename succeeds (best-effort). `Load()` on a parse error recovers from `.bak` (re-promotes to primary),
|
||||
else preserves the corrupt file as `*.corrupt-<ts>` and starts on safe defaults — never returns the
|
||||
error that drove `main.go`'s `Fatalf`/crash-loop. New `Settings.LoadWarning` → dashboard banner
|
||||
(`handlers.go` + `dashboard.html`). `main.go` `Fatalf` retained (now only the IO-unreadable path).
|
||||
- **F2 [MEDIUM, defense-in-depth] — `stack_name` validated against path traversal.**
|
||||
`internal/web/validate.go` `validStackName` (single segment; rejects `/ \ .. NUL`), gated in
|
||||
`backupRestoreHandler` (`handlers.go`) and `apiExportStart` (`handler_export.go`) before any
|
||||
restore/export work.
|
||||
- **S3 [LOW] — corrupt quiesce marker quarantined, not silently dropped.**
|
||||
`internal/quiesce/quiesce.go` `readMarker` logs a `[WARN]` + renames a bad-JSON marker to
|
||||
`*.corrupt-<ts>` (still returns "no marker" → no recovery, the correct contract).
|
||||
## Catalog (app-catalog-felhom.eu, commits `6a71c5a`)
|
||||
Audited all 53 templates (subagent + cross-check). No Traefik `PathPrefix` anywhere; the only apps whose
|
||||
bare `/` is not a usable landing:
|
||||
- `gokapi` → `open_path: "/admin"` (file-share index redirects away)
|
||||
- `ghost` → `open_path: "/ghost/"` (bare `/` is the public blog; admin/setup at `/ghost/`)
|
||||
All other 51 serve or redirect usefully at `/` (e.g. plex `/`→`/web/`, radarr/sonarr/gitea/vaultwarden
|
||||
`/`→`/login`). `onlyoffice` is API-only (status page at `/`) — left at root.
|
||||
|
||||
## Tests (green; all red-proofed)
|
||||
`go build ./... && go vet ./... && go test ./...` — green.
|
||||
- T-S1a recover-from-bak, T-S1b unrecoverable→safe-defaults+preserve, T-S1c save-writes-bak, T-S1d happy.
|
||||
Red-proof: revert `Load` to error-on-parse → T-S1a/b FAIL.
|
||||
- T-F2a `validStackName` (pure), T-F2b restore handler rejects traversal (no nil-deref), T-F2c export
|
||||
handler → 400. Red-proof: drop the restore gate → traversal falls through to the nil-backupMgr
|
||||
redirect → T-F2b FAIL.
|
||||
- T-S3a quarantine+log, T-S3b happy. Red-proof: revert `readMarker` to silent → T-S3a FAIL.
|
||||
## Validation (live, guest 9201, v0.77.0 healthy)
|
||||
- Rendered `Megnyitás` href on the gokapi **Beállítások** page: `https://share.demo-felhom.eu/admin` ✓
|
||||
- Dashboard hrefs: `share.demo-felhom.eu/admin` for gokapi; **all other apps unchanged** at bare root
|
||||
(arcade, audiobooks, books, budget, komga, media, paperless, paste, photos, radarr, recipes, status,
|
||||
tasks) — no regression ✓
|
||||
- (Earlier, same session: gokapi's index `RedirectUrl` was also repointed `/`→`/admin` so direct-URL
|
||||
visitors to the bare root reach the login too.)
|
||||
|
||||
## Deploy
|
||||
Built+pushed `:0.76.0` on 192.168.0.180 (build-server `git pull` done first), deployed to 9201 via
|
||||
bootstrap. `docker inspect`: `image=:0.76.0 running healthy`.
|
||||
|
||||
## LIVE re-test — the three campaign-#3 scenarios, now fixed
|
||||
1. **S1:** ensured `.bak`, truncated `settings.json` → restart → `status=running RestartCount=0`
|
||||
(no crash-loop), logs `[ERROR] primary settings corrupt … — attempting recovery from .bak` →
|
||||
`[WARN] recovered settings from .bak; re-promoting to primary`; config intact (3 storage_paths), 25/0.
|
||||
2. **F2:** `POST /backup/restore stack_name=../../../etc` → `flash_error=Érvénytelen alkalmazásnév`,
|
||||
log `[WARN] restore rejected: invalid stack_name "../../../etc"`, **no "Starting app-data restore"**
|
||||
(gate fired before the restore logic); `/etc/passwd` intact (24 lines).
|
||||
3. **S3:** planted a corrupt `quiesce-state.json` → restart → `[WARN] [quiesce] marker … is corrupt …
|
||||
— quarantining; stacks not auto-recovered`, original renamed to `*.corrupt-<ts>`, `RestartCount=0`,
|
||||
25/0 (stacks not stranded).
|
||||
|
||||
## Still-open campaign-#3 deferrals (NOT in this task)
|
||||
Time-chaos on a dedicated VM / injectable clock; host-reboot (supervised); `.fab` import
|
||||
compose-fuzzing. Agent/hub untouched.
|
||||
## Notes
|
||||
- `open_path` must start with `/`. It's purely cosmetic (the open link); routing is unchanged.
|
||||
- Build/deploy: build-server `git pull` first (build.sh doesn't pull), `:0.77.0` via bootstrap.
|
||||
|
||||
@@ -199,6 +199,8 @@ When a user visits a stopped or undeployed app's subdomain (e.g., `travel.demo-f
|
||||
|
||||
Running apps on the Vezérlőpult now show a "Megnyitás ↗" button that opens the app's subdomain in a new tab. The `Subdomains` map is built in `dashboardHandler` from `app.yaml` env or metadata fallback.
|
||||
|
||||
The optional `.felhom.yml` **`open_path`** field (`Metadata.OpenPath`) is appended to that URL for apps whose UI isn't at `/` — e.g. `gokapi` → `/admin`, `ghost` → `/ghost/`. Empty = bare root. Rendered via `.Meta.OpenPath` in `dashboard.html`, `deploy.html`, and `app_info.html` (the same field flows through all three open-link sites; no handler change needed). Must start with `/`; cosmetic only (does not affect routing).
|
||||
|
||||
#### App Info Pages
|
||||
|
||||
Each app can define rich metadata in `.felhom.yml`:
|
||||
|
||||
Reference in New Issue
Block a user