From 8052c950cfd923ca294bc09a7742994654decebb Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:35:23 +0200 Subject: [PATCH] fix(cli): ignore a half-populated socket activation environment (#11394) A container engine started from a socket-activated system unit leaks a bare LISTEN_PID into every container it spawns, with no matching LISTEN_FDS. LocalAI read that as a malformed activation attempt and refused to start: ERROR Error running the application error=loading systemd socket activation listeners: invalid LISTEN_FDS "" systemd's own sd_listen_fds() treats either variable being absent as "not activated" rather than as an error, so do the same and fall back to ordinary --address binding. A value that is present but malformed is still rejected, so a real activation attempt cannot silently bind the wrong socket. Fixes #11390 Assisted-by: Claude:claude-opus-5 [golangci-lint] Signed-off-by: Ettore Di Giacinto Co-authored-by: Ettore Di Giacinto --- core/cli/run_socket_activation_linux.go | 8 +++++++ core/cli/run_socket_activation_test.go | 28 +++++++++++++++++++++++++ docs/content/getting-started/linux.md | 5 +++++ 3 files changed, 41 insertions(+) diff --git a/core/cli/run_socket_activation_linux.go b/core/cli/run_socket_activation_linux.go index 35aade6cb..8e554293a 100644 --- a/core/cli/run_socket_activation_linux.go +++ b/core/cli/run_socket_activation_linux.go @@ -24,6 +24,14 @@ func systemdActivatedListeners() ([]net.Listener, error) { } }() + // A half-populated environment is not an activation attempt. Container runtimes + // started from a socket-activated system unit leak a bare LISTEN_PID into every + // container they spawn, and systemd's own sd_listen_fds() treats either variable + // being absent as "not activated" rather than as an error. + if listenPID == "" || listenFDs == "" { + return nil, nil + } + pid, err := strconv.Atoi(listenPID) if err != nil { return nil, fmt.Errorf("invalid LISTEN_PID %q: %w", listenPID, err) diff --git a/core/cli/run_socket_activation_test.go b/core/cli/run_socket_activation_test.go index 09f973596..f3b6b59e0 100644 --- a/core/cli/run_socket_activation_test.go +++ b/core/cli/run_socket_activation_test.go @@ -85,6 +85,34 @@ var _ = Describe("systemdActivatedListeners", func() { Expect(os.Getenv("LISTEN_FDNAMES")).To(BeEmpty()) }) + It("binds normally when the environment leaks LISTEN_PID without LISTEN_FDS", func() { + Expect(os.Setenv("LISTEN_PID", strconv.Itoa(os.Getpid()))).To(Succeed()) + Expect(os.Unsetenv("LISTEN_FDS")).To(Succeed()) + DeferCleanup(func() { + _ = os.Unsetenv("LISTEN_PID") + }) + + listeners, err := systemdActivatedListeners() + + Expect(err).NotTo(HaveOccurred()) + Expect(listeners).To(BeEmpty()) + Expect(os.Getenv("LISTEN_PID")).To(BeEmpty()) + }) + + It("binds normally when the environment leaks LISTEN_FDS without LISTEN_PID", func() { + Expect(os.Unsetenv("LISTEN_PID")).To(Succeed()) + Expect(os.Setenv("LISTEN_FDS", "1")).To(Succeed()) + DeferCleanup(func() { + _ = os.Unsetenv("LISTEN_FDS") + }) + + listeners, err := systemdActivatedListeners() + + Expect(err).NotTo(HaveOccurred()) + Expect(listeners).To(BeEmpty()) + Expect(os.Getenv("LISTEN_FDS")).To(BeEmpty()) + }) + It("reports malformed activation metadata instead of silently binding another socket", func() { Expect(os.Setenv("LISTEN_PID", strconv.Itoa(os.Getpid()))).To(Succeed()) Expect(os.Setenv("LISTEN_FDS", "not-a-number")).To(Succeed()) diff --git a/docs/content/getting-started/linux.md b/docs/content/getting-started/linux.md index af4389328..22a670388 100644 --- a/docs/content/getting-started/linux.md +++ b/docs/content/getting-started/linux.md @@ -111,6 +111,11 @@ For a Podman-managed container, configure Podman to preserve and pass the systemd socket file descriptor into the container. The LocalAI process inside the container consumes the same activation protocol. +Activation needs both `LISTEN_PID` and `LISTEN_FDS`. If only one of them is set, +LocalAI ignores them and binds `--address` as usual. A container engine started +from a socket-activated system unit can leak a bare `LISTEN_PID` into every +container it spawns, and that is not an activation attempt. + ## Next Steps - [Try it out with examples](/basics/try/)