Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/pkg/autostart/launchd/launchd.go
2609 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package launchd
5
6
import (
7
"context"
8
_ "embed"
9
"fmt"
10
"os"
11
"os/exec"
12
"sync"
13
14
"github.com/sirupsen/logrus"
15
16
"github.com/lima-vm/lima/v2/pkg/limatype"
17
)
18
19
//go:embed io.lima-vm.autostart.INSTANCE.plist
20
var Template string
21
22
// GetPlistPath returns the path to the launchd plist file for the given instance name.
23
func GetPlistPath(instName string) string {
24
return fmt.Sprintf("%s/Library/LaunchAgents/%s.plist", os.Getenv("HOME"), ServiceNameFrom(instName))
25
}
26
27
// ServiceNameFrom returns the launchd service name for the given instance name.
28
func ServiceNameFrom(instName string) string {
29
return fmt.Sprintf("io.lima-vm.autostart.%s", instName)
30
}
31
32
// EnableDisableService enables or disables the launchd service for the given instance name.
33
func EnableDisableService(ctx context.Context, enable bool, instName string) error {
34
action := "enable"
35
if !enable {
36
action = "disable"
37
}
38
return launchctl(ctx, action, serviceTarget(instName))
39
}
40
41
func launchctl(ctx context.Context, args ...string) error {
42
cmd := exec.CommandContext(ctx, "launchctl", args...)
43
cmd.Stdout = os.Stdout
44
cmd.Stderr = os.Stderr
45
logrus.Debugf("running command: %v", cmd.Args)
46
return cmd.Run()
47
}
48
49
func launchctlWithoutOutput(ctx context.Context, args ...string) error {
50
cmd := exec.CommandContext(ctx, "launchctl", args...)
51
logrus.Debugf("running command without output: %v", cmd.Args)
52
return cmd.Run()
53
}
54
55
// AutoStartedServiceName returns the launchd service name if the instance is started by launchd.
56
func AutoStartedServiceName() string {
57
// Assume the instance is started by launchd if XPC_SERVICE_NAME is set and not "0".
58
// To confirm it is actually started by launchd, it needs to use `launch_activate_socket`.
59
// But that requires actual socket activation setup in the plist file.
60
// So we just check XPC_SERVICE_NAME here.
61
if xpcServiceName := os.Getenv("XPC_SERVICE_NAME"); xpcServiceName != "0" {
62
return xpcServiceName
63
}
64
return ""
65
}
66
67
var domainTarget = sync.OnceValue(func() string {
68
return fmt.Sprintf("gui/%d", os.Getuid())
69
})
70
71
func serviceTarget(instName string) string {
72
return fmt.Sprintf("%s/%s", domainTarget(), ServiceNameFrom(instName))
73
}
74
75
func RequestStart(ctx context.Context, inst *limatype.Instance) error {
76
// Call `launchctl bootout` first, because instance may be stopped without unloading the plist file.
77
// If the plist file is not unloaded, `launchctl bootstrap` will fail.
78
_ = launchctlWithoutOutput(ctx, "bootout", serviceTarget(inst.Name))
79
// If disabled, `launchctl bootstrap` will fail.
80
_ = EnableDisableService(ctx, true, inst.Name)
81
if err := launchctl(ctx, "bootstrap", domainTarget(), GetPlistPath(inst.Name)); err != nil {
82
return fmt.Errorf("failed to start the instance %q via launchctl: %w", inst.Name, err)
83
}
84
return nil
85
}
86
87
func RequestStop(ctx context.Context, inst *limatype.Instance) (bool, error) {
88
logrus.Debugf("AutoStartedIdentifier=%q, ServiceNameFrom=%q", inst.AutoStartedIdentifier, ServiceNameFrom(inst.Name))
89
if inst.AutoStartedIdentifier == ServiceNameFrom(inst.Name) {
90
logrus.Infof("Stopping the instance %q started by launchd", inst.Name)
91
if err := launchctl(ctx, "bootout", serviceTarget(inst.Name)); err != nil {
92
return false, fmt.Errorf("failed to stop the instance %q via launchctl: %w", inst.Name, err)
93
}
94
return true, nil
95
}
96
return false, nil
97
}
98
99