Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions cmd/lima-guestagent/daemon_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0

package main

import (
"errors"
"net"
"os"
"os/signal"
"syscall"
"time"

"github.com/mdlayher/vsock"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/lima-vm/lima/v2/pkg/guestagent"
"github.com/lima-vm/lima/v2/pkg/guestagent/api/server"
"github.com/lima-vm/lima/v2/pkg/guestagent/ticker"
"github.com/lima-vm/lima/v2/pkg/portfwdserver"
)

const hostCID = 2

type cidFilteredListener struct {
*vsock.Listener
}

func (l *cidFilteredListener) Accept() (net.Conn, error) {
for {
conn, err := l.Listener.Accept()
if err != nil {
return nil, err
}
if vsockConn, ok := conn.(*vsock.Conn); ok {
if addr, ok := vsockConn.RemoteAddr().(*vsock.Addr); ok {
if addr.ContextID != hostCID {
logrus.Warnf("rejected vsock connection from unauthorized CID %d", addr.ContextID)
conn.Close()
continue
}
}
}
return conn, nil
}
}

func newDaemonCommand() *cobra.Command {
daemonCommand := &cobra.Command{
Use: "daemon",
Short: "Run the daemon",
RunE: daemonAction,
}
daemonCommand.Flags().String("runtime-dir", "/var/run/lima-guestagent", "Directory to store runtime state")
daemonCommand.Flags().Duration("tick", 3*time.Second, "Tick for polling events")
daemonCommand.Flags().Int("vsock-port", 0, "vsock port to listen on")
return daemonCommand
}

func daemonAction(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
runtimeDir, err := cmd.Flags().GetString("runtime-dir")
if err != nil {
return err
}
if err := os.MkdirAll(runtimeDir, 0o755); err != nil {
return err
}
tick, err := cmd.Flags().GetDuration("tick")
if err != nil {
return err
}
vSockPort, err := cmd.Flags().GetInt("vsock-port")
if err != nil {
return err
}
if tick == 0 {
return errors.New("tick must be specified")
}
if vSockPort == 0 {
return errors.New("vsock-port must be specified for macOS guests")
}
if os.Geteuid() != 0 {
return errors.New("must run as the root user")
}

logrus.Infof("event tick: %v", tick)
tickerInst := ticker.NewSimpleTicker(time.NewTicker(tick))

ctx, stop := signal.NotifyContext(ctx, syscall.SIGTERM)
defer stop()
go func() {
<-ctx.Done()
logrus.Debug("Received SIGTERM, shutting down the guest agent")
}()

agent, err := guestagent.New(ctx, tickerInst, runtimeDir)
if err != nil {
return err
}

vsockL, err := vsock.Listen(uint32(vSockPort), nil)
if err != nil {
return err
}
l := &cidFilteredListener{Listener: vsockL}
logrus.Infof("serving the guest agent on vsock port: %d (host CID only)", vSockPort)

defer logrus.Debug("exiting lima-guestagent daemon")
return server.StartServer(ctx, l, &server.GuestServer{Agent: agent, TunnelS: portfwdserver.NewTunnelServer()})
}
110 changes: 110 additions & 0 deletions cmd/lima-guestagent/install_launchd_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0

package main

import (
"bytes"
_ "embed"
"errors"
"fmt"
"os"
"os/exec"
"strings"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/lima-vm/lima/v2/pkg/textutil"
)

func newInstallLaunchdCommand() *cobra.Command {
installLaunchdCommand := &cobra.Command{
Use: "install-launchd",
Short: "Install a launchd LaunchDaemon",
RunE: installLaunchdAction,
}
installLaunchdCommand.Flags().Bool("guestagent-updated", false, "Indicate that the guest agent has been updated")
installLaunchdCommand.Flags().Int("vsock-port", 0, "Use vsock server on specified port")
return installLaunchdCommand
}

func installLaunchdAction(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
guestAgentUpdated, err := cmd.Flags().GetBool("guestagent-updated")
if err != nil {
return err
}
vsockPort, err := cmd.Flags().GetInt("vsock-port")
if err != nil {
return err
}
debug, err := cmd.Flags().GetBool("debug")
if err != nil {
return err
}
plist, err := generateLaunchdPlist(vsockPort, debug)
if err != nil {
return err
}
plistPath := "/Library/LaunchDaemons/io.lima-vm.lima-guestagent.plist"
plistFileChanged := true
if _, err := os.Stat(plistPath); !errors.Is(err, os.ErrNotExist) {
if existingPlist, err := os.ReadFile(plistPath); err == nil && bytes.Equal(plist, existingPlist) {
logrus.Infof("File %q is up-to-date", plistPath)
plistFileChanged = false
} else {
logrus.Infof("File %q needs update", plistPath)
}
}
if plistFileChanged {
if err := os.WriteFile(plistPath, plist, 0o644); err != nil {
return err
}
logrus.Infof("Written file %q", plistPath)
} else if !guestAgentUpdated {
logrus.Info("io.lima-vm.lima-guestagent already up-to-date")
return nil
}
// plistFileChanged || guestAgentUpdated
// Unload existing service (ignore errors if not currently loaded)
unloadCmd := exec.CommandContext(ctx, "launchctl", "unload", plistPath)
logrus.Infof("Executing: %s", strings.Join(unloadCmd.Args, " "))
if err := unloadCmd.Run(); err != nil {
logrus.Debugf("launchctl unload (expected on first install): %v", err)
}

loadCmd := exec.CommandContext(ctx, "launchctl", "load", "-w", plistPath)
loadCmd.Stdout = os.Stdout
loadCmd.Stderr = os.Stderr
logrus.Infof("Executing: %s", strings.Join(loadCmd.Args, " "))
if err := loadCmd.Run(); err != nil {
return err
}
logrus.Info("Done")
return nil
}

//go:embed lima-guestagent.TEMPLATE.plist
var launchdPlistTemplate string

func generateLaunchdPlist(vsockPort int, debug bool) ([]byte, error) {
selfExeAbs, err := os.Executable()
if err != nil {
return nil, err
}

var extraArgs []string
if vsockPort != 0 {
extraArgs = append(extraArgs, "--vsock-port", fmt.Sprintf("%d", vsockPort))
}
if debug {
extraArgs = append(extraArgs, "--debug")
}

m := map[string]any{
"Binary": selfExeAbs,
"ExtraArgs": extraArgs,
}
return textutil.ExecuteTemplate(launchdPlistTemplate, m)
}
25 changes: 25 additions & 0 deletions cmd/lima-guestagent/lima-guestagent.TEMPLATE.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>io.lima-vm.lima-guestagent</string>
<key>ProgramArguments</key>
<array>
<string>{{.Binary}}</string>
<string>daemon</string>
{{- range .ExtraArgs}}
<string>{{.}}</string>
{{- end}}
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/var/log/lima-guestagent.log</string>
<key>StandardErrorPath</key>
<string>/var/log/lima-guestagent.log</string>
</dict>
</plist>
2 changes: 2 additions & 0 deletions cmd/lima-guestagent/root_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ import (
func addRootCommands(rootCmd *cobra.Command) {
rootCmd.AddCommand(
newFakeCloudInitCommand(),
newDaemonCommand(),
newInstallLaunchdCommand(),
)
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,5 @@ require (
// gomodjail:unconfined
gvisor.dev/gvisor v0.0.0-20240916094835-a174eb65023f // indirect
)

replace github.com/mdlayher/vsock => github.com/Fred78290/vsock v0.0.1
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ github.com/Code-Hex/go-infinity-channel v1.0.0 h1:M8BWlfDOxq9or9yvF9+YkceoTkDI1p
github.com/Code-Hex/go-infinity-channel v1.0.0/go.mod h1:5yUVg/Fqao9dAjcpzoQ33WwfdMWmISOrQloDRn3bsvY=
github.com/Code-Hex/vz/v3 v3.7.1 h1:EN1yNiyrbPq+dl388nne2NySo8I94EnPppvqypA65XM=
github.com/Code-Hex/vz/v3 v3.7.1/go.mod h1:1LsW0jqW0r0cQ+IeR4hHbjdqOtSidNCVMWhStMHGho8=
github.com/Fred78290/vsock v0.0.1 h1:FJGDEHHmRQbVNJb9Ef9UQ0nYvkrEm+0cNEhtRck25j0=
github.com/Fred78290/vsock v0.0.1/go.mod h1:LrkNgswZMZAeUr5cCxHIIwY/aIzLlqYqYwwyjGKbqY8=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
Expand Down Expand Up @@ -176,8 +178,6 @@ github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY
github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ=
github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
Expand Down
Loading
Loading