Files
ferrum/internal/api/bootstrap.go
T
Anand d04b769670 Fix storage metrics double-counting, sign-out, SMTP plaintext, and OIDC gaps
- Fleet Overview's Storage KPI and per-connection totals (internal/api/
  overview.go) were summing shared storage once per node that mounts it,
  since PVE's own Shared flag is unreliable for storage added per-node
  without ticking it. New dedupeSharedStorage collapses those by an exact
  (name, total, used) match within a connection; local-only plugin types
  are never touched. Mirrored on the Storage page, which also now
  disambiguates same-named local pools (e.g. every node's "local-lvm") by
  node so a bar chart's category axis never gets duplicate labels.
- Sign-out: React Query keeps a query's last-successful data through a
  failed refetch, so the cached signed-in user survived the post-logout
  401 and the app never noticed. signOut() now pins ["auth","me"] to null
  directly instead of invalidating-and-hoping a refetch lands in time.
- SMTP: net/smtp.SendMail opportunistically attempts STARTTLS whenever the
  server advertises it regardless of the admin's "Use STARTTLS" toggle, so
  an internal relay with a bad cert failed sends the admin explicitly asked
  to be plaintext. A dedicated sendPlain path never attempts TLS.
- OIDC: added an "Auto-create new accounts" toggle — when off, a
  first-time SSO login for an unrecognized identity is refused instead of
  silently provisioning one. Also added RP-Initiated Logout: signing out of
  an SSO session now also ends the session at the identity provider when it
  advertises support for it (end_session_endpoint), via a stored id_token
  on OIDC sessions (new sessions.oidc_id_token column).
- ResourceAreaChart tooltips now fall back to the same yTickFormatter given
  to the axis when a series has no formatter of its own — fixes raw
  unformatted numbers in Fleet Trend and every RRD chart built on it
  (Node/Guest detail) that only ever set the axis formatter.
2026-09-03 23:17:38 +05:30

59 lines
1.7 KiB
Go

package api
import (
"context"
"database/sql"
"ferrum/internal/auth"
)
// BootstrapSettings loads OIDC and notification settings from the database
// and applies them (live OIDC client, live Notifier) at startup.
//
// seedOIDC is the config.yaml/env OIDC config (internal/config.OIDCConfig,
// converted by the caller) — config.yaml was the only way to set this up
// before the Settings UI existed. The very first boot after upgrading copies
// it into the database once; every boot after that (and every save from the
// UI) uses the database exclusively, so config.yaml stops being read for
// OIDC at all once a row exists.
func (s *Server) BootstrapSettings(ctx context.Context, seedEnabled bool, seedOIDC auth.OIDCConfig) error {
var exists int
err := s.db.QueryRowContext(ctx, `SELECT 1 FROM oidc_settings WHERE id = 1`).Scan(&exists)
if err == sql.ErrNoRows {
enc, encErr := s.secrets.Encrypt(seedOIDC.ClientSecret)
if encErr != nil {
return encErr
}
if err := s.saveOIDCRow(ctx, oidcRow{
enabled: seedEnabled, displayName: seedOIDC.DisplayName, issuerURL: seedOIDC.IssuerURL,
clientID: seedOIDC.ClientID, clientSecretEnc: enc, redirectURL: seedOIDC.RedirectURL,
allowAutoProvision: true, // matches the pre-existing always-on behavior config.yaml-only deployments already had
}); err != nil {
return err
}
} else if err != nil {
return err
}
row, err := s.loadOIDCRow(ctx)
if err != nil {
return err
}
s.applyOIDCRow(row)
notifRow, err := s.loadNotificationRow(ctx)
if err != nil {
return err
}
if err := s.applyNotificationRow(notifRow); err != nil {
return err
}
secRow, err := s.loadSecurityRow(ctx)
if err != nil {
return err
}
s.applySecurityRow(secRow)
return nil
}