diff --git a/CHANGELOG.md b/CHANGELOG.md index 67c3fcb..3e4ea6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ ## Changelog +### v0.30.2 — Report geo-restriction + logo/favicon update (2026-02-25) + +#### Added +- **Geo-restriction in reports** (`internal/report/`) — New `GeoRestrictionReport` struct and `geo_restriction` field in the Report JSON. Hub can now display current geo-blocking status (enabled, allowed countries, per-app overrides, sync state) on customer detail pages. +- **Favicon route** (`/static/favicon.svg`) — Separate favicon SVG served from synced assets or embedded fallback. Uses the cloud icon from `logo_favicon_2.svg`. +- **Hub Bearer auth for geo API** — `/api/geo/` routes now accept `selfUpdateAuthMiddleware` (session auth OR Hub API key), allowing the Hub to send geo-disable commands to controllers. + +#### Changed +- **Logo SVG updated** (`internal/web/templates.go`) — Replaced embedded logo with the latest `logo.svg` from the website (white text variant). +- **Favicon link** — Layout and catch-all templates now reference `/static/favicon.svg` instead of the full logo. + ### v0.30.1 — Geo-Restriction fix (2026-02-25) #### Fixed diff --git a/controller/README.md b/controller/README.md index 986c87c..19037b9 100644 --- a/controller/README.md +++ b/controller/README.md @@ -4,7 +4,7 @@ A single, lightweight Go container that replaces Portainer + scattered systemd scripts with a unified, Hungarian-language web dashboard for managing Docker Compose stacks, backups, storage, monitoring, and notifications on customer hardware. -**Current version: v0.30.1** +**Current version: v0.30.2** --- @@ -1170,7 +1170,7 @@ Thread-safe access via `GetGeoRestriction()`, `SetGeoRestriction()`, `SetGeoAppO | POST | `/api/stacks/{name}/geo/override` | Set per-app country override | | DELETE | `/api/stacks/{name}/geo/override` | Remove per-app override | -All mutating endpoints trigger an async Cloudflare sync. +All mutating endpoints trigger an async Cloudflare sync. The `/api/geo/` path accepts both session auth and Hub Bearer token auth (via `selfUpdateAuthMiddleware`), enabling Hub-side geo-disable for lockout recovery. #### Sync Triggers diff --git a/controller/cmd/controller/main.go b/controller/cmd/controller/main.go index 7408ead..9c28831 100644 --- a/controller/cmd/controller/main.go +++ b/controller/cmd/controller/main.go @@ -368,7 +368,7 @@ func main() { pushInterval = 15 * time.Minute } sched.Every("hub-report", pushInterval, func(ctx context.Context) error { - r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), logger) + r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), sett.GetGeoRestriction(), logger) if err := hubPusher.Push(r); err != nil { return err } @@ -434,7 +434,7 @@ func main() { }) if hubPusher != nil { storageWatchdog.SetHubReportPusher(func() { - r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), logger) + r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), sett.GetGeoRestriction(), logger) hubPusher.Push(r) }) } @@ -515,7 +515,7 @@ func main() { // Hub report if hubPusher != nil { if cfg.Hub.Enabled { - r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), logger) + r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), sett.GetGeoRestriction(), logger) var pushErr error for attempt := 1; attempt <= 3; attempt++ { pushErr = hubPusher.Push(r) @@ -674,7 +674,7 @@ func main() { dc := &web.DebugCallbacks{} if hubPusher != nil { dc.TriggerHubReportPush = func() error { - r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), logger) + r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), sett.GetGeoRestriction(), logger) return hubPusher.Push(r) } dc.TriggerHubInfraPush = func() error { @@ -740,7 +740,7 @@ func main() { } if hubPusher != nil { driveMigrator.PushHubReport = func() { - r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), logger) + r := report.BuildReport(cfg, *configPath, stackMgr, backupMgr, cpuCollector, metricsStore, Version, sett.GetStoragePaths(), sett.GetGeoRestriction(), logger) hubPusher.Push(r) } driveMigrator.PushInfraBackup = func() { @@ -771,6 +771,8 @@ func main() { mux.Handle("/api/selfupdate/", selfUpdateAuthMiddleware(cfg, webServer, webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP)))) // Config API — accepts session auth OR hub API key (for Hub config push) mux.Handle("/api/config/", selfUpdateAuthMiddleware(cfg, webServer, webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP)))) + // Geo API — accepts session auth OR hub API key (for Hub geo-disable) + mux.Handle("/api/geo/", selfUpdateAuthMiddleware(cfg, webServer, webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP)))) mux.Handle("/api/", webServer.RequireAuth(webServer.CsrfProtect(http.HandlerFunc(apiRouter.ServeHTTP)))) // Web UI routes (auth required) diff --git a/controller/internal/report/builder.go b/controller/internal/report/builder.go index 7f8c902..0cb94c0 100644 --- a/controller/internal/report/builder.go +++ b/controller/internal/report/builder.go @@ -30,6 +30,7 @@ func BuildReport( metricsStore *metrics.MetricsStore, version string, storagePaths []settings.StoragePath, + geoRestriction *settings.GeoRestriction, logger *log.Logger, ) *Report { debug := cfg.Logging.Level == "debug" @@ -153,6 +154,23 @@ func BuildReport( // App telemetry (metrics + log scan) r.AppTelemetry = buildAppTelemetrySection(stackMgr, metricsStore, logger) + // Geo-restriction status + if geoRestriction != nil { + gr := &GeoRestrictionReport{ + Enabled: geoRestriction.Enabled, + AllowedCountries: geoRestriction.AllowedCountries, + LastSync: geoRestriction.LastSync, + LastSyncError: geoRestriction.LastSyncError, + } + if len(geoRestriction.AppOverrides) > 0 { + gr.AppOverrides = make(map[string]GeoAppOverrideReport, len(geoRestriction.AppOverrides)) + for k, v := range geoRestriction.AppOverrides { + gr.AppOverrides[k] = GeoAppOverrideReport{AllowedCountries: v.AllowedCountries} + } + } + r.GeoRestriction = gr + } + if debug && logger != nil { logger.Printf("[DEBUG] BuildReport: complete — containers=%d, health=%s, deployed=%d, available=%d, app_telemetry=%d", r.Containers.Total, r.Health.Status, len(r.Stacks.Deployed), len(r.Stacks.Available), len(r.AppTelemetry)) diff --git a/controller/internal/report/types.go b/controller/internal/report/types.go index 44d6d76..088936f 100644 --- a/controller/internal/report/types.go +++ b/controller/internal/report/types.go @@ -22,7 +22,8 @@ type Report struct { Backup BackupReport `json:"backup"` Health HealthReport `json:"health"` Stacks StacksReport `json:"stacks"` - AppTelemetry []AppTelemetry `json:"app_telemetry,omitempty"` + AppTelemetry []AppTelemetry `json:"app_telemetry,omitempty"` + GeoRestriction *GeoRestrictionReport `json:"geo_restriction,omitempty"` } // SystemReport holds host-level system info. @@ -97,6 +98,20 @@ type StacksReport struct { Available []string `json:"available"` } +// GeoRestrictionReport holds geo-restriction status for hub display. +type GeoRestrictionReport struct { + Enabled bool `json:"enabled"` + AllowedCountries []string `json:"allowed_countries"` + AppOverrides map[string]GeoAppOverrideReport `json:"app_overrides,omitempty"` + LastSync string `json:"last_sync,omitempty"` + LastSyncError string `json:"last_sync_error,omitempty"` +} + +// GeoAppOverrideReport holds per-app country override. +type GeoAppOverrideReport struct { + AllowedCountries []string `json:"allowed_countries"` +} + // AppTelemetry holds per-app (per-stack) resource and log telemetry. type AppTelemetry struct { AppName string `json:"app_name"` diff --git a/controller/internal/web/server.go b/controller/internal/web/server.go index 9a40198..1f4f241 100644 --- a/controller/internal/web/server.go +++ b/controller/internal/web/server.go @@ -275,6 +275,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.serveChartJSHandler(w, r) case path == "/static/felhom-logo.svg": s.serveLogoHandler(w, r) + case path == "/static/favicon.svg": + s.serveFaviconHandler(w, r) case strings.HasPrefix(path, "/static/assets/"): s.serveAsset(w, r, strings.TrimPrefix(path, "/static/assets/")) case strings.HasPrefix(path, "/apps/"): @@ -446,6 +448,22 @@ func (s *Server) serveLogoHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, FelhomLogoSVG) } +func (s *Server) serveFaviconHandler(w http.ResponseWriter, r *http.Request) { + // Try synced asset first + if s.assetsSyncer != nil { + path := s.assetsSyncer.Resolve("felhom-favicon.svg") + if _, err := os.Stat(path); err == nil { + w.Header().Set("Cache-Control", "public, max-age=86400") + http.ServeFile(w, r, path) + return + } + } + // Fallback to embedded favicon + w.Header().Set("Content-Type", "image/svg+xml") + w.Header().Set("Cache-Control", "public, max-age=86400") + fmt.Fprint(w, FelhomFaviconSVG) +} + // serveAsset serves baked-in app assets (logos, screenshots) from /usr/share/felhom/assets/ const assetsDir = "/usr/share/felhom/assets" diff --git a/controller/internal/web/templates.go b/controller/internal/web/templates.go index 4b3afbe..e67a3d1 100644 --- a/controller/internal/web/templates.go +++ b/controller/internal/web/templates.go @@ -1,35 +1,379 @@ package web // FelhomLogoSVG is the felhom.eu logo, served at /static/felhom-logo.svg. -// Cleaned from the original Inkscape SVG, removing editor metadata. +// Updated from website/assets/logo.svg (white text variant). const FelhomLogoSVG = ` - - - - - - - - - - - - - - -felhom -eu - - - - - - - - - - - - - -` +felhomeu +` + +// FelhomFaviconSVG is the felhom.eu favicon (cloud icon only), served at /static/favicon.svg. +const FelhomFaviconSVG = ` + +` diff --git a/controller/internal/web/templates/catchall.html b/controller/internal/web/templates/catchall.html index d226926..d17728c 100644 --- a/controller/internal/web/templates/catchall.html +++ b/controller/internal/web/templates/catchall.html @@ -5,7 +5,7 @@ {{if .AppName}}{{.AppName}} — {{end}}felhom.eu - +