diff --git a/hub/CHANGELOG.md b/hub/CHANGELOG.md new file mode 100644 index 0000000..ccbcb57 --- /dev/null +++ b/hub/CHANGELOG.md @@ -0,0 +1,127 @@ +# Felhom Hub — Changelog + +## v0.2.0 (2026-02-20) + +**Customer Configuration Management** + +New "Configurations" section for pre-provisioning customer nodes. Operators can configure +customer settings in the Hub web UI, then `docker-setup.sh` downloads a ready-made +`controller.yaml` — reducing deployment to a customer ID and password. + +### New features + +- **Web UI — `/configs` pages:** + - List all customer configurations in a table + - Create new configuration: customer identity, infrastructure secrets (CF tunnel/API tokens), + git sync credentials, monitoring UUIDs — organized in collapsible sections + - Detail page: shows credentials (retrieval password, per-customer API key) with copy-to-clipboard, + setup commands (`docker-setup.sh` and `curl`), live YAML preview + - Edit and delete configurations + - Navigation tabs (Dashboard / Configurations) on all pages + +- **Config retrieval API — `GET /api/v1/config/{customer_id}`:** + - Authenticated via `X-Retrieval-Password` header (separate from Bearer token) + - Generates complete `controller.yaml` by deep-merging template with customer overrides + - Template sourced from `controller.yaml.example` (fetched from Gitea repo periodically) + - Falls back to embedded default template if fetcher not configured + +- **Per-customer API keys:** + - Each customer config gets its own API key (auto-generated, 64 hex chars) + - Controllers can authenticate with per-customer key instead of the shared global key + - Backward compatible — global `report_api_key` continues to work alongside per-customer keys + +- **YAML generation (`internal/configgen` package):** + - Deep-merge of template + customer-specific overrides + - Programmatic injection: customer identity, hub config, session secret + - Shared by both API handler and web UI preview + +- **Template fetcher (background goroutine):** + - Periodically fetches `controller.yaml.example` from Gitea (configurable interval) + - Requires `registry.username` + `registry.token` in hub.yaml + - Falls back to `go:embed` default template when not configured + +- **Data layer:** + - New `customer_configs` SQLite table + - 6 CRUD methods: Save, Get, List, Delete, GetByAPIKey, UpdateRetrievalPassword + +### Configuration + +New `registry` section in `hub.yaml`: + +```yaml +registry: + image: "gitea.dooplex.hu/admin/felhom-controller" + username: "" # Gitea credentials (for version checker + template fetcher) + token: "" + check_interval: "6h" + template_interval: "1h" # How often to refresh controller.yaml.example +``` + +### Files added + +- `internal/configgen/configgen.go` — shared YAML generation package +- `internal/web/configs.go` — web handlers for config CRUD +- `internal/web/templatefetcher.go` — background template refresh +- `internal/web/controller.yaml.default` — embedded fallback template +- `internal/web/templates/configs.html` — config list page +- `internal/web/templates/config_form.html` — create/edit form +- `internal/web/templates/config_detail.html` — detail + credentials page + +### Files modified + +- `internal/store/store.go` — customer_configs table + CRUD methods +- `internal/api/handler.go` — config retrieval endpoint, per-customer auth, `ConfigTemplateProvider` interface +- `internal/web/server.go` — `/configs/*` routes, `SetTemplateFetcher()` +- `internal/web/embed.go` — embedded default template +- `internal/web/templates/dashboard.html` — navigation bar +- `internal/web/templates/customer.html` — navigation bar +- `internal/web/templates/style.css` — form, nav, button, credential styles +- `cmd/hub/main.go` — template fetcher wiring, `TemplateInterval` config +- `configs/hub.yaml.example` — registry section + +--- + +## v0.1.8 (2026-02-16) + +- Controller update trigger: "Update" button on customer detail page calls controller's self-update endpoint +- Registry version checker: background goroutine checks Gitea registry for latest controller image tag +- Update available indicator on customer detail page + +## v0.1.7 (2026-02-15) + +- Infrastructure backup endpoints for disaster recovery (POST + GET `/api/v1/infra-backup`) + +## v0.1.6 (2026-02-14) + +- Handle disabled reporting status +- Storage labels display +- Date in history table + +## v0.1.5 (2026-02-13) + +- Notification preferences sync endpoint (`POST /api/v1/preferences`) +- Notification display on customer detail page + +## v0.1.4 (2026-02-12) + +- Resend API key support for email notifications +- Notification endpoint (`POST /api/v1/notify`) + +## v0.1.3 (2026-02-11) + +- Customer detail page: system info, storage bars, container table +- 24h history graphs + +## v0.1.2 (2026-02-10) + +- Dashboard auto-refresh (60s cycle) +- Status logic (green/yellow/red based on report age + health) + +## v0.1.1 (2026-02-09) + +- Basic dashboard with customer overview table +- Report ingest API + +## v0.1.0 (2026-02-08) + +- Initial release: SQLite store, report API, basic web dashboard diff --git a/manifests/hub.yaml b/manifests/hub.yaml index 13929d6..d56ddba 100644 --- a/manifests/hub.yaml +++ b/manifests/hub.yaml @@ -9,7 +9,7 @@ # # PREREQUISITES: # 1. Build and push the hub image: -# cd ~/build/felhom-hub && ./build.sh 0.1.0 --push +# cd ~/build/felhom-hub && ./build.sh v0.2.0 --push # # 2. Generate a bcrypt password hash for dashboard login: # htpasswd -nbBC 10 "" "your-password" | cut -d: -f2 @@ -82,6 +82,12 @@ data: stale_threshold: "30m" notifications: resend_api_key: "re_XZZenCJs_LyJnU12jZWfEn9rK85Gc83DK" + registry: + image: "gitea.dooplex.hu/admin/felhom-controller" + username: "admin" + token: "e93ef87f90cc13a476964ee965bfe2e75d945a33" + check_interval: "6h" + template_interval: "1h" server: listen: ":8080" data_dir: "/data" @@ -111,7 +117,7 @@ spec: spec: containers: - name: hub - image: gitea.dooplex.hu/admin/felhom-hub:latest + image: gitea.dooplex.hu/admin/felhom-hub:v0.2.0 ports: - containerPort: 8080 name: http @@ -130,10 +136,6 @@ spec: mountPath: /data - name: config mountPath: /etc/felhom-hub - # NOTE: When password_hash is set, GET / returns 401 for unauthenticated - # requests. The httpGet probe accepts 200-399 only, so it would fail. - # TODO: Add a /healthz endpoint in the hub code that bypasses auth. - # For now, probes work because password_hash is empty (no auth). livenessProbe: httpGet: path: /healthz