Merge pull request #5489 from resker/fix/autostart-lima-home

fix(autostart): propagate LIMA_HOME into generated launchd/systemd units
This commit is contained in:
Akihiro Suda authored and GitHub committed 2026-09-12 04:37:21 +09:00
commit a85206649d
7 files changed
+97 -6

No files matched your search

+21 -6
View File
@@ -15,6 +15,7 @@ import (
"github.com/lima-vm/lima/v2/pkg/autostart"
"github.com/lima-vm/lima/v2/pkg/autostart/launchd"
"github.com/lima-vm/lima/v2/pkg/limatype/dirnames"
"github.com/lima-vm/lima/v2/pkg/store"
"github.com/lima-vm/lima/v2/pkg/textutil"
)
@@ -95,22 +96,36 @@ func autostartDisableAction(cmd *cobra.Command, args []string) error {
return nil
}
func daemonInstall(ctx context.Context, instName, workDir, userName string, keepAlive bool) error {
selfExe, err := os.Executable()
// renderDaemonPlist renders the system LaunchDaemon plist for an instance. Every key the
// template references must be supplied here: a missing one renders as the literal string
// "<no value>" rather than failing, which would be written into the installed plist.
func renderDaemonPlist(binary, instName, workDir, userName string, keepAlive bool) ([]byte, error) {
// The daemon inherits no environment from the shell that registers it, so LIMA_HOME
// has to be recorded in the plist for the instance to be found at boot.
limaHome, err := dirnames.LimaDir()
if err != nil {
return fmt.Errorf("could not determine limactl path: %w", err)
return nil, err
}
vars := map[string]string{
"Binary": selfExe,
"Binary": binary,
"Instance": instName,
"LimaHome": limaHome,
"WorkDir": workDir,
"UserName": userName,
}
if keepAlive {
vars["KeepAlive"] = "true"
}
content, err := textutil.ExecuteTemplate(launchd.DaemonTemplate, vars)
return textutil.ExecuteTemplate(launchd.DaemonTemplate, vars)
}
func daemonInstall(ctx context.Context, instName, workDir, userName string, keepAlive bool) error {
selfExe, err := os.Executable()
if err != nil {
return fmt.Errorf("could not determine limactl path: %w", err)
}
content, err := renderDaemonPlist(selfExe, instName, workDir, userName, keepAlive)
if err != nil {
return fmt.Errorf("failed to render daemon plist: %w", err)
}
+30
View File
@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0
package main
import (
"strings"
"testing"
"gotest.tools/v3/assert"
)
// The daemon plist is rendered from a map, and text/template turns a key the template
// references but the map omits into the literal string "<no value>" instead of failing.
// That would be written into /Library/LaunchDaemons verbatim, so assert that the rendered
// plist carries the real LIMA_HOME and no placeholder.
func TestRenderDaemonPlist(t *testing.T) {
t.Setenv("LIMA_HOME", "/some/lima/home")
for _, keepAlive := range []bool{false, true} {
got, err := renderDaemonPlist("/limactl", "colima", "/some/path", "containerserver", keepAlive)
assert.NilError(t, err)
plist := string(got)
assert.Assert(t, !strings.Contains(plist, "<no value>"),
"plist has an unresolved template key:\n%s", plist)
assert.Assert(t, strings.Contains(plist, "<key>LIMA_HOME</key>\n\t\t<string>/some/lima/home</string>"),
"plist does not carry LIMA_HOME:\n%s", plist)
assert.Assert(t, strings.Contains(plist, "<string>containerserver</string>"))
assert.Equal(t, strings.Contains(plist, "<key>KeepAlive</key>"), keepAlive)
}
}
+25
View File
@@ -83,6 +83,11 @@ func TestRenderTemplate(t *testing.T) {
<dict>
<key>Label</key>
<string>io.lima-vm.autostart.default</string>
<key>EnvironmentVariables</key>
<dict>
<key>LIMA_HOME</key>
<string>/some/lima/home</string>
</dict>
<key>ProgramArguments</key>
<array>
<string>/limactl</string>
@@ -120,6 +125,11 @@ func TestRenderTemplate(t *testing.T) {
<string>io.lima-vm.daemon.k3s</string>
<key>UserName</key>
<string>alice</string>
<key>EnvironmentVariables</key>
<dict>
<key>LIMA_HOME</key>
<string>/some/lima/home</string>
</dict>
<key>ProgramArguments</key>
<array>
<string>/limactl</string>
@@ -155,6 +165,11 @@ func TestRenderTemplate(t *testing.T) {
<dict>
<key>Label</key>
<string>io.lima-vm.autostart.default</string>
<key>EnvironmentVariables</key>
<dict>
<key>LIMA_HOME</key>
<string>/some/lima/home</string>
</dict>
<key>ProgramArguments</key>
<array>
<string>/limactl</string>
@@ -197,6 +212,11 @@ func TestRenderTemplate(t *testing.T) {
<string>io.lima-vm.daemon.k3s</string>
<key>UserName</key>
<string>alice</string>
<key>EnvironmentVariables</key>
<dict>
<key>LIMA_HOME</key>
<string>/some/lima/home</string>
</dict>
<key>ProgramArguments</key>
<array>
<string>/limactl</string>
@@ -236,6 +256,7 @@ Description=Lima - Linux virtual machines, with a focus on running containers.
Documentation=man:lima(1)
[Service]
Environment=LIMA_HOME=/some/lima/home
ExecStart=/limactl start %i --foreground
WorkingDirectory=%h
Type=simple
@@ -259,6 +280,7 @@ Description=Lima - Linux virtual machines, with a focus on running containers.
Documentation=man:lima(1)
[Service]
Environment=LIMA_HOME=/some/lima/home
ExecStart=/limactl start %i --foreground
WorkingDirectory=%h
Type=simple
@@ -276,6 +298,9 @@ WantedBy=default.target
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
// Pin LIMA_HOME so the rendered unit does not depend on the host running the test.
// The directory does not exist, so LimaDir() returns it without resolving symlinks.
t.Setenv("LIMA_HOME", "/some/lima/home")
tmpl, err := tt.Manager.renderTemplate(tt.InstanceName, tt.WorkDir, tt.GetExecutable)
assert.NilError(t, err)
assert.Equal(t, string(tmpl), tt.Expected)
@@ -4,6 +4,11 @@
<dict>
<key>Label</key>
<string>io.lima-vm.autostart.{{ .Instance }}</string>
<key>EnvironmentVariables</key>
<dict>
<key>LIMA_HOME</key>
<string>{{ .LimaHome }}</string>
</dict>
<key>ProgramArguments</key>
<array>
<string>{{ .Binary }}</string>
@@ -6,6 +6,11 @@
<string>io.lima-vm.daemon.{{ .Instance }}</string>
<key>UserName</key>
<string>{{ .UserName }}</string>
<key>EnvironmentVariables</key>
<dict>
<key>LIMA_HOME</key>
<string>{{ .LimaHome }}</string>
</dict>
<key>ProgramArguments</key>
<array>
<string>{{ .Binary }}</string>
+10
View File
@@ -14,6 +14,7 @@ import (
"runtime"
"github.com/lima-vm/lima/v2/pkg/limatype"
"github.com/lima-vm/lima/v2/pkg/limatype/dirnames"
"github.com/lima-vm/lima/v2/pkg/textutil"
)
@@ -120,9 +121,18 @@ func (t *TemplateFileBasedManager) renderTemplate(instName, workDir string, getE
if err != nil {
return nil, err
}
// The generated unit must carry LIMA_HOME explicitly: neither launchd nor systemd
// inherits the environment of the shell that registered the instance, so without it
// the autostarted `limactl start` would look for the instance under the default
// ~/.lima and not find one that lives anywhere else.
limaHome, err := dirnames.LimaDir()
if err != nil {
return nil, err
}
data := map[string]string{
"Binary": selfExeAbs,
"Instance": instName,
"LimaHome": limaHome,
"WorkDir": workDir,
}
maps.Copy(data, t.extraTemplateVars)
@@ -3,6 +3,7 @@ Description=Lima - Linux virtual machines, with a focus on running containers.
Documentation=man:lima(1)
[Service]
Environment=LIMA_HOME={{.LimaHome}}
ExecStart={{.Binary}} start %i --foreground
WorkingDirectory=%h
Type=simple