mirror of
https://github.com/mudler/LocalAI.git
synced 2026-07-07 06:49:49 -04:00
The `--generated-content-path` and `--upload-path` defaults were the fixed
shared locations `/tmp/generated/content` and `/tmp/localai/upload`. On any
multi-user host these collide across accounts: macOS routes `/tmp` to the
shared `/private/tmp` for every user, so whichever account starts LocalAI
first creates the parent with 0750 perms and every other account then fails
startup with:
unable to create ImageDir: "mkdir /tmp/generated/content: permission denied"
unable to create UploadDir: "mkdir /tmp/localai/upload: permission denied"
The same happens on Linux once a stale root-owned `/tmp/generated` (e.g. from
a prior `sudo` run) is left behind. This bites the desktop launcher and any
app embedding the raw binary (Wingman, nib-desktop), which start `local-ai
run` with no path flags.
Default both paths under the OS temp dir (`os.TempDir()`, honoring `$TMPDIR`;
already per-user on macOS) namespaced by the current UID
(`TMPDIR/localai-<uid>/...`), so accounts never collide while the paths stay
ephemeral. Wired via new kong vars in main.go so every consumer of the raw
binary inherits the fix. All content subdirs (audio, images) derive from
`GeneratedContentDir`, so they are fixed transitively.
As defense in depth, the launcher also anchors these two paths under its own
per-user data directory (mirroring the #10610 fix for data/config), extracted
into a testable `BuildRunArgs`.
Assisted-by: Claude:claude-opus-4-8 [Claude Code]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
51 lines
1.7 KiB
Go
51 lines
1.7 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
// Regression for the startup failure observed when a second OS account (or a
|
|
// leftover root-owned directory) already created the shared /tmp locations:
|
|
//
|
|
// unable to create ImageDir: "mkdir /tmp/generated/content: permission denied"
|
|
//
|
|
// The historical defaults (/tmp/generated/content and /tmp/localai/upload) are
|
|
// shared across every user of a machine. On macOS /tmp is routed to the shared
|
|
// /private/tmp for all accounts, so the first account to run LocalAI creates the
|
|
// parent with 0750 perms and locks everyone else out. The defaults must instead
|
|
// be scoped to the current user so unrelated accounts never collide.
|
|
var _ = Describe("default writable paths", func() {
|
|
userScope := fmt.Sprintf("localai-%d", os.Getuid())
|
|
|
|
Describe("DefaultGeneratedContentPath", func() {
|
|
It("is scoped to the current user under the OS temp dir", func() {
|
|
p := DefaultGeneratedContentPath()
|
|
Expect(p).To(HavePrefix(os.TempDir()))
|
|
Expect(p).To(ContainSubstring(userScope))
|
|
Expect(p).To(HaveSuffix(filepath.Join("generated", "content")))
|
|
})
|
|
|
|
It("is not the historical shared path", func() {
|
|
Expect(DefaultGeneratedContentPath()).ToNot(Equal("/tmp/generated/content"))
|
|
})
|
|
})
|
|
|
|
Describe("DefaultUploadPath", func() {
|
|
It("is scoped to the current user under the OS temp dir", func() {
|
|
p := DefaultUploadPath()
|
|
Expect(p).To(HavePrefix(os.TempDir()))
|
|
Expect(p).To(ContainSubstring(userScope))
|
|
Expect(p).To(HaveSuffix("upload"))
|
|
})
|
|
|
|
It("is not the historical shared path", func() {
|
|
Expect(DefaultUploadPath()).ToNot(Equal("/tmp/localai/upload"))
|
|
})
|
|
})
|
|
})
|