Skip to content

Commit 481d023

Browse files
committed
feat(autostart): add --keep-alive flag to limactl autostart enable
Add --keep-alive (default: true) to both LaunchAgent and LaunchDaemon plist templates. When enabled, launchd restarts the Lima host agent automatically if it exits unexpectedly, preventing instances from remaining in a Broken state until the next login or reboot. The flag is accepted on all platforms; on Linux it is a no-op since systemd restart behavior is configured separately in the unit file. Closes: #4992 Signed-off-by: Robert Esker <resker@gmail.com>
1 parent f3b3abb commit 481d023

9 files changed

Lines changed: 68 additions & 8 deletions

File tree

cmd/limactl/autostart.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ func newAutostartEnableCommand() *cobra.Command {
3434
"user", "",
3535
"macOS username to run the instance as when --condition=boot (default: $USER)",
3636
)
37+
flags.Bool(
38+
"keep-alive", true,
39+
"Restart the instance automatically if the host agent exits unexpectedly",
40+
)
3741
return cmd
3842
}
3943

cmd/limactl/autostart_darwin.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ import (
2020
)
2121

2222
func autostartEnableAction(cmd *cobra.Command, args []string) error {
23-
condition, err := cmd.Flags().GetString("condition")
23+
flags := cmd.Flags()
24+
condition, err := flags.GetString("condition")
25+
if err != nil {
26+
return err
27+
}
28+
keepAlive, err := flags.GetBool("keep-alive")
2429
if err != nil {
2530
return err
2631
}
@@ -36,12 +41,13 @@ func autostartEnableAction(cmd *cobra.Command, args []string) error {
3641

3742
switch condition {
3843
case "login":
39-
if err := autostart.RegisterToStartAtLogin(ctx, inst); err != nil {
44+
mgr := autostart.ManagerWith(keepAlive)
45+
if err := mgr.RegisterToStartAtLogin(ctx, inst); err != nil {
4046
return fmt.Errorf("failed to register instance %#q to start at login: %w", inst.Name, err)
4147
}
4248
logrus.Infof("Instance %#q registered to start at login", inst.Name)
4349
case "boot":
44-
userName, err := cmd.Flags().GetString("user")
50+
userName, err := flags.GetString("user")
4551
if err != nil {
4652
return err
4753
}
@@ -51,7 +57,7 @@ func autostartEnableAction(cmd *cobra.Command, args []string) error {
5157
if userName == "" {
5258
return errors.New("could not determine user; pass --user")
5359
}
54-
return daemonInstall(ctx, inst.Name, inst.Dir, userName)
60+
return daemonInstall(ctx, inst.Name, inst.Dir, userName, keepAlive)
5561
default:
5662
return fmt.Errorf("unknown condition %q: must be \"login\" or \"boot\"", condition)
5763
}
@@ -89,18 +95,22 @@ func autostartDisableAction(cmd *cobra.Command, args []string) error {
8995
return nil
9096
}
9197

92-
func daemonInstall(ctx context.Context, instName, workDir, userName string) error {
98+
func daemonInstall(ctx context.Context, instName, workDir, userName string, keepAlive bool) error {
9399
selfExe, err := os.Executable()
94100
if err != nil {
95101
return fmt.Errorf("could not determine limactl path: %w", err)
96102
}
97103

98-
content, err := textutil.ExecuteTemplate(launchd.DaemonTemplate, map[string]string{
104+
vars := map[string]string{
99105
"Binary": selfExe,
100106
"Instance": instName,
101107
"WorkDir": workDir,
102108
"UserName": userName,
103-
})
109+
}
110+
if keepAlive {
111+
vars["KeepAlive"] = "true"
112+
}
113+
content, err := textutil.ExecuteTemplate(launchd.DaemonTemplate, vars)
104114
if err != nil {
105115
return fmt.Errorf("failed to render daemon plist: %w", err)
106116
}

cmd/limactl/autostart_others.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ func autostartEnableAction(cmd *cobra.Command, args []string) error {
3535
return err
3636
}
3737

38-
if err := autostart.RegisterToStartAtLogin(ctx, inst); err != nil {
38+
keepAlive, err := cmd.Flags().GetBool("keep-alive")
39+
if err != nil {
40+
return err
41+
}
42+
mgr := autostart.ManagerWith(keepAlive)
43+
if err := mgr.RegisterToStartAtLogin(ctx, inst); err != nil {
3944
return fmt.Errorf("failed to register instance %#q to start at login: %w", inst.Name, err)
4045
}
4146
logrus.Infof("Instance %#q registered to start at login", inst.Name)

pkg/autostart/launchd/io.lima-vm.autostart.INSTANCE.plist

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
</array>
1414
<key>RunAtLoad</key>
1515
<true/>
16+
{{ if .KeepAlive }} <key>KeepAlive</key>
17+
<true/>
18+
{{ end }}
1619
<key>StandardErrorPath</key>
1720
<string>launchd.stderr.log</string>
1821
<key>StandardOutPath</key>

pkg/autostart/launchd/io.lima-vm.daemon.INSTANCE.plist

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
</array>
1616
<key>RunAtLoad</key>
1717
<true/>
18+
{{ if .KeepAlive }} <key>KeepAlive</key>
19+
<true/>
20+
{{ end }}
1821
<key>StandardErrorPath</key>
1922
<string>launchd.stderr.log</string>
2023
<key>StandardOutPath</key>

pkg/autostart/managers_darwin.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,23 @@ import "github.com/lima-vm/lima/v2/pkg/autostart/launchd"
77

88
// Manager returns the autostart manager for Darwin.
99
func Manager() autoStartManager {
10+
return ManagerWith(true)
11+
}
12+
13+
// ManagerWith returns the autostart manager for Darwin with the given options.
14+
func ManagerWith(keepAlive bool) autoStartManager {
15+
extra := map[string]string{}
16+
if keepAlive {
17+
extra["KeepAlive"] = "true"
18+
}
1019
return &TemplateFileBasedManager{
1120
filePath: launchd.GetPlistPath,
1221
template: launchd.Template,
1322
enabler: launchd.EnableDisableService,
1423
autoStartedIdentifier: launchd.AutoStartedServiceName,
1524
requestStart: launchd.RequestStart,
1625
requestStop: launchd.RequestStop,
26+
extraTemplateVars: extra,
1727
}
1828
}
1929

pkg/autostart/managers_linux.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ func DaemonManager(_ string) autoStartManager {
1010
return &notSupportedManager{}
1111
}
1212

13+
// ManagerWith returns the autostart manager for Linux.
14+
// The keepAlive parameter is accepted for API compatibility but ignored;
15+
// systemd restart behavior is configured separately via the unit file.
16+
func ManagerWith(_ bool) autoStartManager {
17+
return Manager()
18+
}
19+
1320
// Manager returns the autostart manager for Linux.
1421
func Manager() autoStartManager {
1522
if systemd.IsRunningSystemd() {

pkg/autostart/managers_others.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,8 @@ func Manager() autoStartManager {
1414
func DaemonManager(_ string) autoStartManager {
1515
return &notSupportedManager{}
1616
}
17+
18+
// ManagerWith is not supported on this OS.
19+
func ManagerWith(_ bool) autoStartManager {
20+
return &notSupportedManager{}
21+
}

website/content/en/docs/usage/autostart.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,19 @@ The `--user` flag specifies which macOS user the instance runs as (default:
4848
`$USER`). The plist is installed to
4949
`/Library/LaunchDaemons/io.lima-vm.daemon.<instance>.plist`.
5050

51+
## Keep-alive behavior
52+
53+
By default (`--keep-alive=true`), launchd will automatically restart the Lima
54+
host agent if it exits unexpectedly. To disable this:
55+
56+
```bash
57+
limactl autostart enable --keep-alive=false default
58+
```
59+
60+
This applies to both `--condition=login` (macOS LaunchAgent) and
61+
`--condition=boot` (macOS LaunchDaemon). On Linux, restart behavior is
62+
configured separately in the systemd unit file.
63+
5164
## Lima < 2.2
5265

5366
Use `limactl start-at-login` (equivalent to `limactl autostart enable --condition=login`):

0 commit comments

Comments
 (0)