mirror of
https://github.com/mudler/LocalAI.git
synced 2026-09-28 17:15:02 -04:00
* feat(gallery): publish signed OCI fallbacks Publish both official gallery indexes with their local base configs so an outage of the HTTP and GitHub sources can fall back to Quay. Keep artifact signing policies separate from backend image policies, and expose each moving gallery tag only after its digest is signed. Assisted-by: Codex:gpt-6 * fix(gallery): confine packaged files to selected roots Use directory-scoped file access to reject symlink escapes during gallery packaging. Create private bundle files for the publishing runner. Assisted-by: Codex:GPT-6 --------- Co-authored-by: localai-org-maint-bot <306269227+localai-org-maint-bot@users.noreply.github.com>
108 lines
5.3 KiB
Go
108 lines
5.3 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"github.com/mudler/LocalAI/pkg/vrambudget"
|
|
"github.com/mudler/LocalAI/pkg/xsysinfo"
|
|
)
|
|
|
|
// DefaultGalleriesJSON / DefaultBackendGalleriesJSON are the gallery lists
|
|
// an option-less `local-ai run` gets. cmd/local-ai feeds them to kong as
|
|
// the "${galleries}" / "${backends}" vars, and DefaultRuntimeBaseline uses
|
|
// them to tell "kong default" apart from "user-configured" at settings-load
|
|
// time - they must stay the single source for both.
|
|
//
|
|
// The primary is served by index-server (github.com/localai-org/index-server),
|
|
// a caching mirror of the files below. The GitHub URI stays as a mirror so an
|
|
// install still resolves its gallery unchanged whenever the primary is
|
|
// unreachable - see the fallback chain in core/gallery/gallery_mirrors.go.
|
|
const DefaultGalleriesJSON = `[{"name":"localai","url":"https://index.localai.io/models","mirrors":["github:mudler/LocalAI/gallery/index.yaml@master","oci://quay.io/go-skynet/local-ai-backends:gallery-models"],"artifact_verification":{"issuer":"https://token.actions.githubusercontent.com","identity":"https://github.com/mudler/LocalAI/.github/workflows/gallery_publish.yml@refs/heads/master"}}]`
|
|
const DefaultBackendGalleriesJSON = `[{"name":"localai","url":"https://index.localai.io/backends","mirrors":["github:mudler/LocalAI/backend/index.yaml@master","oci://quay.io/go-skynet/local-ai-backends:gallery-backends"],"artifact_verification":{"issuer":"https://token.actions.githubusercontent.com","identity":"https://github.com/mudler/LocalAI/.github/workflows/gallery_publish.yml@refs/heads/master"}}]`
|
|
|
|
func mustGalleries(jsonList string) []Gallery {
|
|
var g []Gallery
|
|
if err := json.Unmarshal([]byte(jsonList), &g); err != nil {
|
|
// Both inputs are compile-time constants above; failing loudly at
|
|
// init beats silently mis-detecting every gallery as env-set.
|
|
panic("invalid built-in gallery JSON: " + err.Error())
|
|
}
|
|
return g
|
|
}
|
|
|
|
// DefaultRuntimeBaseline is the ApplicationConfig an option-less `local-ai
|
|
// run` produces: NewApplicationConfig plus the flag defaults kong injects
|
|
// even when the user passes nothing (see the default:"..." tags in
|
|
// core/cli/run.go). ApplyRuntimeSettingsAtStartup compares the live config
|
|
// against it to decide whether env/CLI claimed a field (env > file).
|
|
func DefaultRuntimeBaseline() *ApplicationConfig {
|
|
o := NewApplicationConfig()
|
|
// WithDebug(log level) is always applied by run.go; the default level
|
|
// is not debug, so an option-less run boots with Debug=false even
|
|
// though the NewApplicationConfig literal says true.
|
|
o.Debug = false
|
|
o.Galleries = mustGalleries(DefaultGalleriesJSON)
|
|
o.BackendGalleries = mustGalleries(DefaultBackendGalleriesJSON)
|
|
o.AutoloadGalleries = true
|
|
o.AutoloadBackendGalleries = true
|
|
o.VRAMPersistentCache = true
|
|
// core/cli/run.go injects WithMemoryReclaimer(enabled, threshold)
|
|
// unconditionally, so the kong threshold default (0.95) reaches the
|
|
// config even when the reclaimer flag is off - this overlay must match
|
|
// that contract or a UI-persisted threshold looks env-set and is
|
|
// skipped at boot.
|
|
o.MemoryReclaimerThreshold = 0.95
|
|
return o
|
|
}
|
|
|
|
// ApplyRuntimeSettingsAtStartup merges persisted settings into o with
|
|
// env-over-file precedence: a field is taken from the file only when the
|
|
// live value still equals the option-less baseline (env/CLI did not touch
|
|
// it) or when the file is that field's only source (fileAuthoritative).
|
|
// Used at boot (application.New) and by the runtime_settings.json file
|
|
// watcher, so a manual file edit behaves exactly like a boot-time load.
|
|
//
|
|
// Known limitation (accepted): an env var explicitly set to its default
|
|
// value is indistinguishable from "not set", so the file wins there; and a
|
|
// field previously changed via the API looks env-set to the watcher, so a
|
|
// manual file edit of that field lands on the next restart.
|
|
func (o *ApplicationConfig) ApplyRuntimeSettingsAtStartup(settings *RuntimeSettings) {
|
|
if settings == nil {
|
|
return
|
|
}
|
|
baseline := DefaultRuntimeBaseline()
|
|
for _, f := range runtimeSettingsFields {
|
|
if f.snapshotOnly || !f.isSet(settings) {
|
|
continue
|
|
}
|
|
if !f.fileAuthoritative && f.envSet(o, baseline) {
|
|
continue
|
|
}
|
|
f.apply(o, settings)
|
|
}
|
|
// Startup invariant, mirroring ApplyRuntimeSettings and the gating in
|
|
// startWatchdog: enabled idle/busy checks or the memory reclaimer imply
|
|
// the watchdog master flag. Never forced off here - an explicit
|
|
// watchdog_enabled=false row already applied above when unclaimed.
|
|
if o.WatchDogIdle || o.WatchDogBusy || o.MemoryReclaimerEnabled {
|
|
o.WatchDog = true
|
|
}
|
|
// VRAM budget post-processing, mirroring ApplyRuntimeSettings: at boot
|
|
// only run.go's env path installs the process-wide cap, so a
|
|
// file-persisted budget applied by the loop above would set
|
|
// o.VRAMBudget without ever capping allocations. When env set the
|
|
// budget the loop skipped the file value and this re-installs the env
|
|
// value - idempotent. Fail-open on a malformed persisted value so it
|
|
// cannot wedge startup.
|
|
if settings.VRAMBudget != nil {
|
|
if b, err := vrambudget.Parse(o.VRAMBudget); err == nil {
|
|
xsysinfo.SetDefaultVRAMBudget(b)
|
|
}
|
|
}
|
|
if settings.ArtifactDownloadConcurrency != nil {
|
|
if configurable, ok := o.ModelArtifactMaterializer.(interface{ SetDownloadConcurrency(int) }); ok {
|
|
configurable.SetDownloadConcurrency(o.ArtifactDownloadConcurrency)
|
|
}
|
|
}
|
|
}
|