Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/cmd/limactl/hostagent.go
2609 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package main
5
6
import (
7
"errors"
8
"fmt"
9
"io"
10
"net"
11
"net/http"
12
"os"
13
"os/signal"
14
"runtime"
15
"strconv"
16
"syscall"
17
18
"github.com/sirupsen/logrus"
19
"github.com/spf13/cobra"
20
21
"github.com/lima-vm/lima/v2/pkg/hostagent"
22
"github.com/lima-vm/lima/v2/pkg/hostagent/api/server"
23
"github.com/lima-vm/lima/v2/pkg/store"
24
)
25
26
func newHostagentCommand() *cobra.Command {
27
hostagentCommand := &cobra.Command{
28
Use: "hostagent INSTANCE",
29
Short: "Run hostagent",
30
Args: WrapArgsError(cobra.ExactArgs(1)),
31
RunE: hostagentAction,
32
Hidden: true,
33
}
34
hostagentCommand.Flags().StringP("pidfile", "p", "", "Write PID to file")
35
hostagentCommand.Flags().String("socket", "", "Path of hostagent socket")
36
hostagentCommand.Flags().Bool("run-gui", false, "Run GUI synchronously within hostagent")
37
hostagentCommand.Flags().String("guestagent", "", "Local file path (not URL) of lima-guestagent.OS-ARCH[.gz]")
38
hostagentCommand.Flags().String("nerdctl-archive", "", "Local file path (not URL) of nerdctl-full-VERSION-GOOS-GOARCH.tar.gz")
39
hostagentCommand.Flags().Bool("progress", false, "Show provision script progress by monitoring cloud-init logs")
40
return hostagentCommand
41
}
42
43
func hostagentAction(cmd *cobra.Command, args []string) error {
44
ctx := cmd.Context()
45
pidfile, err := cmd.Flags().GetString("pidfile")
46
if err != nil {
47
return err
48
}
49
if pidfile != "" {
50
if existingPID, err := store.ReadPIDFile(pidfile); existingPID != 0 {
51
return fmt.Errorf("another hostagent may already be running with pid %d (pidfile %q)", existingPID, pidfile)
52
} else if err != nil {
53
return fmt.Errorf("failed to determine if another hostagent is running: %w", err)
54
}
55
if err := os.WriteFile(pidfile, []byte(strconv.Itoa(os.Getpid())+"\n"), 0o644); err != nil {
56
return err
57
}
58
defer os.RemoveAll(pidfile)
59
}
60
socket, err := cmd.Flags().GetString("socket")
61
if err != nil {
62
return err
63
}
64
if socket == "" {
65
return errors.New("socket must be specified (limactl version mismatch?)")
66
}
67
68
instName := args[0]
69
70
runGUI, err := cmd.Flags().GetBool("run-gui")
71
if err != nil {
72
return err
73
}
74
if runGUI {
75
// Without this the call to vz.RunGUI fails. Adding it here, as this has to be called before the vz cgo loads.
76
runtime.LockOSThread()
77
defer runtime.UnlockOSThread()
78
}
79
80
signalCh := make(chan os.Signal, 1)
81
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
82
83
stdout := &syncWriter{w: cmd.OutOrStdout()}
84
stderr := &syncWriter{w: cmd.ErrOrStderr()}
85
86
initLogrus(stderr)
87
var opts []hostagent.Opt
88
guestagentBinary, err := cmd.Flags().GetString("guestagent")
89
if err != nil {
90
return err
91
}
92
if guestagentBinary != "" {
93
opts = append(opts, hostagent.WithGuestAgentBinary(guestagentBinary))
94
}
95
nerdctlArchive, err := cmd.Flags().GetString("nerdctl-archive")
96
if err != nil {
97
return err
98
}
99
if nerdctlArchive != "" {
100
opts = append(opts, hostagent.WithNerdctlArchive(nerdctlArchive))
101
}
102
showProgress, err := cmd.Flags().GetBool("progress")
103
if err != nil {
104
return err
105
}
106
if showProgress {
107
opts = append(opts, hostagent.WithCloudInitProgress(showProgress))
108
}
109
ha, err := hostagent.New(ctx, instName, stdout, signalCh, opts...)
110
if err != nil {
111
return err
112
}
113
114
backend := &server.Backend{
115
Agent: ha,
116
}
117
r := http.NewServeMux()
118
server.AddRoutes(r, backend)
119
srv := &http.Server{Handler: r}
120
err = os.RemoveAll(socket)
121
if err != nil {
122
return err
123
}
124
var lc net.ListenConfig
125
l, err := lc.Listen(ctx, "unix", socket)
126
logrus.Infof("hostagent socket created at %s", socket)
127
if err != nil {
128
return err
129
}
130
go func() {
131
if serveErr := srv.Serve(l); serveErr != http.ErrServerClosed {
132
logrus.WithError(serveErr).Warn("hostagent API server exited with an error")
133
}
134
}()
135
defer srv.Close()
136
return ha.Run(cmd.Context())
137
}
138
139
// syncer is implemented by *os.File.
140
type syncer interface {
141
Sync() error
142
}
143
144
type syncWriter struct {
145
w io.Writer
146
}
147
148
func (w *syncWriter) Write(p []byte) (int, error) {
149
written, err := w.w.Write(p)
150
if err == nil {
151
if s, ok := w.w.(syncer); ok {
152
_ = s.Sync()
153
}
154
}
155
return written, err
156
}
157
158
func initLogrus(stderr io.Writer) {
159
logrus.SetOutput(stderr)
160
// JSON logs are parsed in pkg/hostagent/events.Watcher()
161
logrus.SetFormatter(new(logrus.JSONFormatter))
162
// HostAgent logging is one level more verbose than the start command itself
163
if logrus.GetLevel() == logrus.DebugLevel {
164
logrus.SetLevel(logrus.TraceLevel)
165
} else {
166
logrus.SetLevel(logrus.DebugLevel)
167
}
168
}
169
170