From 975b54dfc5732e10cfece20a32f1272491727405 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 24 Jun 2026 22:03:56 +0000 Subject: [PATCH] feat(config): generalize LOCALAI_BASE_URL to ExternalBaseURL LOCALAI_BASE_URL now sets a single instance-wide external base URL used for OAuth callbacks and all self-referential links. A Pre middleware stamps it into the request context for middleware.BaseURL. Refs #10482 Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Ettore Di Giacinto --- core/cli/run.go | 11 +++++++---- core/config/application_config.go | 12 +++++++++--- core/http/app.go | 12 ++++++++++++ core/http/routes/auth.go | 2 +- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/core/cli/run.go b/core/cli/run.go index abb0cdbf1..75a521572 100644 --- a/core/cli/run.go +++ b/core/cli/run.go @@ -140,7 +140,7 @@ type RunCMD struct { OIDCIssuer string `env:"LOCALAI_OIDC_ISSUER" help:"OIDC issuer URL for auto-discovery" group:"auth"` OIDCClientID string `env:"LOCALAI_OIDC_CLIENT_ID" help:"OIDC Client ID (auto-enables auth)" group:"auth"` OIDCClientSecret string `env:"LOCALAI_OIDC_CLIENT_SECRET" help:"OIDC Client Secret" group:"auth"` - AuthBaseURL string `env:"LOCALAI_BASE_URL" help:"Base URL for OAuth callbacks (e.g. http://localhost:8080)" group:"auth"` + ExternalBaseURL string `env:"LOCALAI_BASE_URL" help:"External base URL of this instance (e.g. https://localhost:8080). Used for OAuth callbacks and self-referential links (generated images/videos, job status). When unset, derived from X-Forwarded-Proto/Host or Forwarded headers." group:"auth"` AuthAdminEmail string `env:"LOCALAI_ADMIN_EMAIL" help:"Email address to auto-promote to admin role" group:"auth"` AuthRegistrationMode string `env:"LOCALAI_REGISTRATION_MODE" default:"open" help:"Registration mode: 'open' (default), 'approval', or 'invite' (invite code required)" group:"auth"` DisableLocalAuth bool `env:"LOCALAI_DISABLE_LOCAL_AUTH" default:"false" help:"Disable local email/password registration and login (use with OAuth/OIDC-only setups)" group:"auth"` @@ -503,9 +503,6 @@ func (r *RunCMD) Run(ctx *cliContext.Context) error { opts = append(opts, config.WithAuthOIDCClientID(r.OIDCClientID)) opts = append(opts, config.WithAuthOIDCClientSecret(r.OIDCClientSecret)) } - if r.AuthBaseURL != "" { - opts = append(opts, config.WithAuthBaseURL(r.AuthBaseURL)) - } if r.AuthAdminEmail != "" { opts = append(opts, config.WithAuthAdminEmail(r.AuthAdminEmail)) } @@ -523,6 +520,12 @@ func (r *RunCMD) Run(ctx *cliContext.Context) error { } } + // Applied unconditionally: the external base URL governs all self-referential + // links (not just OAuth callbacks), so it must take effect even when auth is off. + if r.ExternalBaseURL != "" { + opts = append(opts, config.WithExternalBaseURL(r.ExternalBaseURL)) + } + if idleWatchDog || busyWatchDog { opts = append(opts, config.EnableWatchDog) if idleWatchDog { diff --git a/core/config/application_config.go b/core/config/application_config.go index 87acd6bd5..1821a8441 100644 --- a/core/config/application_config.go +++ b/core/config/application_config.go @@ -49,6 +49,13 @@ type ApplicationConfig struct { P2PNetworkID string Federated bool + // ExternalBaseURL is the externally visible base URL of this instance + // (scheme+host[:port]), set via LOCALAI_BASE_URL. When non-empty it is + // authoritative for every self-referential URL LocalAI emits (OAuth + // callbacks, generated image/video links, async job StatusURLs), + // overriding proxy-header detection. Empty = derive from request headers. + ExternalBaseURL string + // DisableStats turns off per-request token tracking. By default the // routing module's billing recorder runs in every mode (including // no-auth single-user) so dashboards and `/api/usage` are immediately @@ -196,7 +203,6 @@ type AuthConfig struct { OIDCIssuer string // OIDC issuer URL for auto-discovery (e.g. https://accounts.google.com) OIDCClientID string OIDCClientSecret string - BaseURL string // for OAuth callback URLs (e.g. "http://localhost:8080") AdminEmail string // auto-promote to admin on login RegistrationMode string // "open", "approval" (default when empty), "invite" DisableLocalAuth bool // disable local email/password registration and login @@ -950,9 +956,9 @@ func WithAuthGitHubClientSecret(clientSecret string) AppOption { } } -func WithAuthBaseURL(baseURL string) AppOption { +func WithExternalBaseURL(url string) AppOption { return func(o *ApplicationConfig) { - o.Auth.BaseURL = baseURL + o.ExternalBaseURL = url } } diff --git a/core/http/app.go b/core/http/app.go index 9ec0711fb..ee5cd99eb 100644 --- a/core/http/app.go +++ b/core/http/app.go @@ -149,6 +149,18 @@ func API(application *application.Application) (*echo.Echo, error) { // Middleware - StripPathPrefix must be registered early as it uses Rewrite which runs before routing e.Pre(httpMiddleware.StripPathPrefix()) + // Stamp the configured external base URL into each request context so + // middleware.BaseURL can treat it as authoritative for self-referential + // links. Registered as Pre so it runs before routing and handlers. + if extBaseURL := application.ApplicationConfig().ExternalBaseURL; extBaseURL != "" { + e.Pre(func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + c.Set("_external_base_url", extBaseURL) + return next(c) + } + }) + } + e.Pre(middleware.RemoveTrailingSlash()) if application.ApplicationConfig().MachineTag != "" { diff --git a/core/http/routes/auth.go b/core/http/routes/auth.go index ef8372fff..b4144e0a1 100644 --- a/core/http/routes/auth.go +++ b/core/http/routes/auth.go @@ -268,7 +268,7 @@ func RegisterAuthRoutes(e *echo.Echo, app *application.Application) { // Set up OAuth manager when any OAuth/OIDC provider is configured if appConfig.Auth.GitHubClientID != "" || appConfig.Auth.OIDCClientID != "" { oauthMgr, err := auth.NewOAuthManager( - appConfig.Auth.BaseURL, + appConfig.ExternalBaseURL, auth.OAuthParams{ GitHubClientID: appConfig.Auth.GitHubClientID, GitHubClientSecret: appConfig.Auth.GitHubClientSecret,