Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/cmd/limactl/main.go
1645 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package main
5
6
import (
7
"context"
8
"errors"
9
"fmt"
10
"os"
11
"os/exec"
12
"path/filepath"
13
"runtime"
14
"strings"
15
16
"github.com/mattn/go-isatty"
17
"github.com/sirupsen/logrus"
18
"github.com/spf13/cobra"
19
20
"github.com/lima-vm/lima/v2/cmd/yq"
21
"github.com/lima-vm/lima/v2/pkg/debugutil"
22
"github.com/lima-vm/lima/v2/pkg/driver/external/server"
23
"github.com/lima-vm/lima/v2/pkg/fsutil"
24
"github.com/lima-vm/lima/v2/pkg/limatype/dirnames"
25
"github.com/lima-vm/lima/v2/pkg/osutil"
26
"github.com/lima-vm/lima/v2/pkg/version"
27
)
28
29
const (
30
DefaultInstanceName = "default"
31
basicCommand = "basic"
32
advancedCommand = "advanced"
33
)
34
35
func main() {
36
yq.MaybeRunYQ()
37
if runtime.GOOS == "windows" {
38
extras, hasExtra := os.LookupEnv("_LIMA_WINDOWS_EXTRA_PATH")
39
if hasExtra && strings.TrimSpace(extras) != "" {
40
p := os.Getenv("PATH")
41
err := os.Setenv("PATH", strings.TrimSpace(extras)+string(filepath.ListSeparator)+p)
42
if err != nil {
43
logrus.Warning("Can't add extras to PATH, relying entirely on system PATH")
44
}
45
}
46
}
47
rootCmd := newApp()
48
if err := executeWithPluginSupport(rootCmd, os.Args[1:]); err != nil {
49
server.StopAllExternalDrivers()
50
handleExitError(err)
51
logrus.Fatal(err)
52
}
53
54
server.StopAllExternalDrivers()
55
}
56
57
func newApp() *cobra.Command {
58
templatesDir := "$PREFIX/share/lima/templates"
59
if exe, err := os.Executable(); err == nil {
60
binDir := filepath.Dir(exe)
61
prefixDir := filepath.Dir(binDir)
62
templatesDir = filepath.Join(prefixDir, "share/lima/templates")
63
}
64
65
rootCmd := &cobra.Command{
66
Use: "limactl",
67
Short: "Lima: Linux virtual machines",
68
Version: strings.TrimPrefix(version.Version, "v"),
69
Example: fmt.Sprintf(` Start the default instance:
70
$ limactl start
71
72
Open a shell:
73
$ lima
74
75
Run a container:
76
$ lima nerdctl run -d --name nginx -p 8080:80 nginx:alpine
77
78
Stop the default instance:
79
$ limactl stop
80
81
See also template YAMLs: %s`, templatesDir),
82
SilenceUsage: true,
83
SilenceErrors: true,
84
DisableAutoGenTag: true,
85
}
86
rootCmd.PersistentFlags().String("log-level", "", "Set the logging level [trace, debug, info, warn, error]")
87
rootCmd.PersistentFlags().String("log-format", "text", "Set the logging format [text, json]")
88
rootCmd.PersistentFlags().Bool("debug", false, "Debug mode")
89
// TODO: "survey" does not support using cygwin terminal on windows yet
90
rootCmd.PersistentFlags().Bool("tty", isatty.IsTerminal(os.Stdout.Fd()), "Enable TUI interactions such as opening an editor. Defaults to true when stdout is a terminal. Set to false for automation.")
91
rootCmd.PersistentFlags().BoolP("yes", "y", false, "Alias of --tty=false")
92
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, _ []string) error {
93
l, _ := cmd.Flags().GetString("log-level")
94
if l != "" {
95
lvl, err := logrus.ParseLevel(l)
96
if err != nil {
97
return err
98
}
99
logrus.SetLevel(lvl)
100
}
101
102
logFormat, _ := cmd.Flags().GetString("log-format")
103
switch logFormat {
104
case "json":
105
formatter := new(logrus.JSONFormatter)
106
logrus.StandardLogger().SetFormatter(formatter)
107
case "text":
108
// logrus use text format by default.
109
if runtime.GOOS == "windows" && isatty.IsCygwinTerminal(os.Stderr.Fd()) {
110
formatter := new(logrus.TextFormatter)
111
// the default setting does not recognize cygwin on windows
112
formatter.ForceColors = true
113
logrus.StandardLogger().SetFormatter(formatter)
114
}
115
default:
116
return fmt.Errorf("unsupported log-format: %q", logFormat)
117
}
118
119
debug, _ := cmd.Flags().GetBool("debug")
120
if debug {
121
logrus.SetLevel(logrus.DebugLevel)
122
debugutil.Debug = true
123
}
124
125
if osutil.IsBeingRosettaTranslated() && cmd.Parent().Name() != "completion" && cmd.Name() != "generate-doc" && cmd.Name() != "validate" {
126
// running under rosetta would provide inappropriate runtime.GOARCH info, see: https://github.com/lima-vm/lima/issues/543
127
// allow commands that are used for packaging to run under rosetta to allow cross-architecture builds
128
return errors.New("limactl is running under rosetta, please reinstall lima with native arch")
129
}
130
131
if os.Geteuid() == 0 && cmd.Name() != "generate-doc" {
132
return errors.New("must not run as the root user")
133
}
134
// Make sure either $HOME or $LIMA_HOME is defined, so we don't need
135
// to check for errors later
136
dir, err := dirnames.LimaDir()
137
if err != nil {
138
return err
139
}
140
nfs, err := fsutil.IsNFS(dir)
141
if err != nil {
142
return err
143
}
144
if nfs {
145
return errors.New("must not run on NFS dir")
146
}
147
148
if cmd.Flags().Changed("yes") && cmd.Flags().Changed("tty") {
149
return errors.New("cannot use both --tty and --yes flags at the same time")
150
}
151
152
if cmd.Flags().Changed("yes") {
153
// Sets the value of the yesValue flag by using the yes flag.
154
yesValue, _ := cmd.Flags().GetBool("yes")
155
if yesValue {
156
// Sets to the default value false
157
err := cmd.Flags().Set("tty", "false")
158
if err != nil {
159
return err
160
}
161
}
162
}
163
return nil
164
}
165
rootCmd.AddGroup(&cobra.Group{ID: "basic", Title: "Basic Commands:"})
166
rootCmd.AddGroup(&cobra.Group{ID: "advanced", Title: "Advanced Commands:"})
167
rootCmd.AddCommand(
168
newCreateCommand(),
169
newStartCommand(),
170
newStopCommand(),
171
newShellCommand(),
172
newCopyCommand(),
173
newListCommand(),
174
newDeleteCommand(),
175
newValidateCommand(),
176
newPruneCommand(),
177
newHostagentCommand(),
178
newGuestInstallCommand(),
179
newInfoCommand(),
180
newShowSSHCommand(),
181
newDebugCommand(),
182
newEditCommand(),
183
newFactoryResetCommand(),
184
newDiskCommand(),
185
newUsernetCommand(),
186
newGenDocCommand(),
187
newGenSchemaCommand(),
188
newSnapshotCommand(),
189
newProtectCommand(),
190
newUnprotectCommand(),
191
newTunnelCommand(),
192
newTemplateCommand(),
193
newRestartCommand(),
194
newSudoersCommand(),
195
newStartAtLoginCommand(),
196
newNetworkCommand(),
197
newCloneCommand(),
198
)
199
200
return rootCmd
201
}
202
203
func handleExitError(err error) {
204
if err == nil {
205
return
206
}
207
208
var exitErr *exec.ExitError
209
if errors.As(err, &exitErr) {
210
os.Exit(exitErr.ExitCode()) //nolint:revive // it's intentional to call os.Exit in this function
211
return
212
}
213
}
214
215
// executeWithPluginSupport handles command execution with plugin support.
216
func executeWithPluginSupport(rootCmd *cobra.Command, args []string) error {
217
if len(args) > 0 {
218
cmd, _, err := rootCmd.Find(args)
219
if err != nil || cmd == rootCmd {
220
// Function calls os.Exit() if it found and executed the plugin
221
runExternalPlugin(rootCmd.Context(), args[0], args[1:])
222
}
223
}
224
225
rootCmd.SetArgs(args)
226
return rootCmd.Execute()
227
}
228
229
func runExternalPlugin(ctx context.Context, name string, args []string) {
230
if ctx == nil {
231
ctx = context.Background()
232
}
233
234
if err := updatePathEnv(); err != nil {
235
logrus.Warnf("failed to update PATH environment: %v", err)
236
// PATH update failure shouldn't prevent plugin execution
237
}
238
239
externalCmd := "limactl-" + name
240
execPath, err := exec.LookPath(externalCmd)
241
if err != nil {
242
return
243
}
244
245
cmd := exec.CommandContext(ctx, execPath, args...)
246
cmd.Stdin = os.Stdin
247
cmd.Stdout = os.Stdout
248
cmd.Stderr = os.Stderr
249
cmd.Env = os.Environ()
250
251
err = cmd.Run()
252
handleExitError(err)
253
if err == nil {
254
os.Exit(0) //nolint:revive // it's intentional to call os.Exit in this function
255
}
256
logrus.Fatalf("external command %q failed: %v", execPath, err)
257
}
258
259
func updatePathEnv() error {
260
exe, err := os.Executable()
261
if err != nil {
262
return fmt.Errorf("failed to get executable path: %w", err)
263
}
264
265
binDir := filepath.Dir(exe)
266
currentPath := os.Getenv("PATH")
267
newPath := binDir + string(filepath.ListSeparator) + currentPath
268
269
if err := os.Setenv("PATH", newPath); err != nil {
270
return fmt.Errorf("failed to set PATH environment: %w", err)
271
}
272
273
logrus.Debugf("updated PATH to prioritize %s", binDir)
274
275
return nil
276
}
277
278
// WrapArgsError annotates cobra args error with some context, so the error message is more user-friendly.
279
func WrapArgsError(argFn cobra.PositionalArgs) cobra.PositionalArgs {
280
return func(cmd *cobra.Command, args []string) error {
281
err := argFn(cmd, args)
282
if err == nil {
283
return nil
284
}
285
286
return fmt.Errorf("%q %s.\nSee '%s --help'.\n\nUsage: %s\n\n%s",
287
cmd.CommandPath(), err.Error(),
288
cmd.CommandPath(),
289
cmd.UseLine(), cmd.Short,
290
)
291
}
292
}
293
294