mirror of
https://github.com/mudler/LocalAI.git
synced 2026-10-28 17:18:49 -04:00
feat(addons): default to local-owner when auth is off
A server without authentication has no accounts, so named-user left the add-on owner off. Resolve the unset mode in one place: local-owner when authentication is off, named-user when it is on. A flag or environment value still wins, and an unknown value is still refused. Assisted-by: Claude Code:claude-sonnet-5-5 go Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
5 files changed
+63
-13
No files matched your search
+3
-1
@@ -35,7 +35,8 @@ import (
|
||||
type RunCMD struct {
|
||||
AddonHostOps string `name:"addon-host-ops" env:"LOCALAI_ADDON_HOST_OPS" default:"fine-tuning=finetune.,quantization=quantize." help:"Add-ons that may call host operations, as a comma list of addon-id=operation-prefix. An add-on that is not listed gets no host operation channel. Empty gives none" group:"addons"`
|
||||
AddonsEnabled bool `name:"addons-enabled" env:"LOCALAI_ADDONS_ENABLED" default:"true" help:"Run the add-on owner, which installs and supervises add-ons. Turn it off with --addons-enabled=false" group:"addons"`
|
||||
AddonsOwnershipMode string `name:"addons-ownership-mode" env:"LOCALAI_ADDONS_OWNERSHIP_MODE" default:"named-user" help:"Who may use add-ons: named-user (each account has its own access; needs authentication) or local-owner (every caller shares one owner)" group:"addons"`
|
||||
AddonsOwnershipMode string `name:"addons-ownership-mode" env:"LOCALAI_ADDONS_OWNERSHIP_MODE" help:"Who may use add-ons: named-user (each account has its own access; needs authentication) or local-owner (every caller shares one owner). Default: local-owner when authentication is off, named-user when it is on" group:"addons"`
|
||||
AddonsOfficialLibrary bool `name:"addons-official-library" env:"LOCALAI_ADDONS_OFFICIAL_LIBRARY" default:"true" help:"Register the official add-on library on the first start, when the add-on owner has no sources.json yet. A library the admin removes later stays removed. Turn it off with --addons-official-library=false" group:"addons"`
|
||||
Pprof bool `name:"pprof" env:"LOCALAI_PPROF" default:"false" help:"Enable the private loopback profiling listener" group:"diagnostics"`
|
||||
PprofAddress string `name:"pprof-address" env:"LOCALAI_PPROF_ADDRESS" default:"127.0.0.1:6060" help:"Numeric loopback address and port for profiling" group:"diagnostics"`
|
||||
PprofMutexProfileFraction int `name:"pprof-mutex-profile-fraction" env:"LOCALAI_PPROF_MUTEX_PROFILE_FRACTION" default:"0" help:"Mutex sampling fraction (0 disables sampling); requires pprof" group:"diagnostics"`
|
||||
@@ -662,6 +663,7 @@ func (r *RunCMD) Run(ctx *cliContext.Context) (result error) {
|
||||
return err
|
||||
}
|
||||
if addonCfg.Enabled {
|
||||
addonCfg.OfficialLibrary = r.AddonsOfficialLibrary
|
||||
opts = append(opts, config.WithAddons(addonCfg))
|
||||
}
|
||||
if authEnabled {
|
||||
|
||||
+18
-4
@@ -19,14 +19,28 @@ const localAddonOwnerID = "local"
|
||||
// the run flags. Only the on/off switch and the ownership mode are flags; the
|
||||
// rest follows from the node being its own owner.
|
||||
//
|
||||
// The default mode, named-user, needs the account database. Without
|
||||
// authentication there is no such database, so the owner stays off with a
|
||||
// warning instead of stopping the server: local-owner mode is never inferred
|
||||
// and an operator must select it.
|
||||
// An empty mode takes the documented default: local-owner when authentication
|
||||
// is off, named-user when it is on. An explicit mode always wins. Named-user
|
||||
// needs the account database, so an explicit named-user without
|
||||
// authentication leaves the owner off with a warning instead of stopping the
|
||||
// server.
|
||||
func addonConfigFor(enabled bool, mode, dataPath string, authEnabled bool) (config.AddonConfig, error) {
|
||||
if !enabled {
|
||||
return config.AddonConfig{}, nil
|
||||
}
|
||||
if mode == "" {
|
||||
// The documented default for an install that did not choose a mode.
|
||||
// With authentication off there are no accounts, so the one shared
|
||||
// local owner is what makes add-ons usable; with authentication on,
|
||||
// every account keeps its own access. This is the one place that
|
||||
// resolves the default. It is an explicit, documented default for
|
||||
// auth-off installs, not an inference from the request or the network,
|
||||
// and a flag or environment value always wins over it.
|
||||
mode = auth.AddonModeNamedUser
|
||||
if !authEnabled {
|
||||
mode = auth.AddonModeLocalOwner
|
||||
}
|
||||
}
|
||||
switch mode {
|
||||
case auth.AddonModeNamedUser, auth.AddonModeLocalOwner:
|
||||
default:
|
||||
|
||||
@@ -32,7 +32,7 @@ var _ = Describe("Add-on owner flags", Serial, func() {
|
||||
return &command.Run, err
|
||||
}
|
||||
BeforeEach(func() {
|
||||
for _, key := range []string{"LOCALAI_ADDONS_ENABLED", "LOCALAI_ADDONS_OWNERSHIP_MODE"} {
|
||||
for _, key := range []string{"LOCALAI_ADDONS_ENABLED", "LOCALAI_ADDONS_OWNERSHIP_MODE", "LOCALAI_ADDONS_OFFICIAL_LIBRARY"} {
|
||||
value, present := os.LookupEnv(key)
|
||||
DeferCleanup(func() {
|
||||
if present {
|
||||
@@ -45,11 +45,12 @@ var _ = Describe("Add-on owner flags", Serial, func() {
|
||||
}
|
||||
})
|
||||
|
||||
It("turns the owner on by default, in named-user mode", func() {
|
||||
It("turns the owner on by default, in named-user mode when authentication is on", func() {
|
||||
r, err := parse()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(r.AddonsEnabled).To(BeTrue())
|
||||
Expect(r.AddonsOwnershipMode).To(Equal("named-user"))
|
||||
Expect(r.AddonsOwnershipMode).To(BeEmpty())
|
||||
Expect(r.AddonsOfficialLibrary).To(BeTrue())
|
||||
dir := GinkgoT().TempDir()
|
||||
c, err := addonConfigFor(r.AddonsEnabled, r.AddonsOwnershipMode, dir, true)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@@ -65,6 +66,32 @@ var _ = Describe("Add-on owner flags", Serial, func() {
|
||||
Expect(c.Transport.OwnerAddress).To(Equal(c.OwnerAddress))
|
||||
})
|
||||
|
||||
It("defaults to local-owner when authentication is off", func() {
|
||||
r, err := parse()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
c, err := addonConfigFor(r.AddonsEnabled, r.AddonsOwnershipMode, GinkgoT().TempDir(), false)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(c.Enabled).To(BeTrue())
|
||||
Expect(c.OwnershipMode).To(Equal("local-owner"))
|
||||
})
|
||||
|
||||
It("lets an explicit named-user win over the auth-off default", func() {
|
||||
r, err := parse("--addons-ownership-mode=named-user")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
c, err := addonConfigFor(r.AddonsEnabled, r.AddonsOwnershipMode, GinkgoT().TempDir(), false)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(c).To(Equal(config.AddonConfig{}))
|
||||
})
|
||||
|
||||
It("reads the ownership mode and the official library from the environment", func() {
|
||||
Expect(os.Setenv("LOCALAI_ADDONS_OWNERSHIP_MODE", "named-user")).To(Succeed())
|
||||
Expect(os.Setenv("LOCALAI_ADDONS_OFFICIAL_LIBRARY", "false")).To(Succeed())
|
||||
r, err := parse()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(r.AddonsOwnershipMode).To(Equal("named-user"))
|
||||
Expect(r.AddonsOfficialLibrary).To(BeFalse())
|
||||
})
|
||||
|
||||
It("turns the owner off completely with --addons-enabled=false", func() {
|
||||
r, err := parse("--addons-enabled=false")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
@@ -26,12 +26,18 @@ type AddonConfig struct {
|
||||
InstanceID, OwnerID, OwnerAddress, StorageID, Root string
|
||||
SharedStorage bool
|
||||
// OwnershipMode is "named-user" (the default when empty) or "local-owner".
|
||||
// Local-owner mode is explicit opt-in and never inferred: every caller that
|
||||
// reaches the addon routes shares one authority and sharing is disabled.
|
||||
// Local-owner mode is never inferred from a request. The CLI sets it as the
|
||||
// documented default when authentication is off and the operator chose no
|
||||
// mode. Every caller that reaches the addon routes shares one authority and
|
||||
// sharing is disabled.
|
||||
// Every frontend of a deployment must use the same value.
|
||||
OwnershipMode string
|
||||
Host contract.HostCompatibility
|
||||
AdminCheck func(context.Context, contract.Actor) error `json:"-" yaml:"-"`
|
||||
// OfficialLibrary registers the official add-on library on the first start,
|
||||
// when the owner has no sources.json yet. A sources.json that exists is
|
||||
// never changed by it, so a library the admin removed stays removed.
|
||||
OfficialLibrary bool
|
||||
Host contract.HostCompatibility
|
||||
AdminCheck func(context.Context, contract.Actor) error `json:"-" yaml:"-"`
|
||||
// BackupKeep is how many backups of one add-on the owner keeps; zero is
|
||||
// three. BackupMaxAge removes older ones; zero keeps them by count only.
|
||||
// The newest verified backup and the recovery copy of an upgrade are
|
||||
|
||||
@@ -60,7 +60,8 @@ type Option func(*settings)
|
||||
type settings struct{ mode string }
|
||||
|
||||
// WithOwnership selects the ownership mode the deployment configured. The
|
||||
// default is named-user; local-owner is never inferred from a request.
|
||||
// default is named-user; local-owner is never inferred from a request. The
|
||||
// CLI selects it as the documented default of an install with authentication off.
|
||||
func WithOwnership(mode string) Option {
|
||||
return func(s *settings) { s.mode = mode }
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user