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