diff options
Diffstat (limited to 'pkg/machine')
-rw-r--r-- | pkg/machine/config.go | 9 | ||||
-rw-r--r-- | pkg/machine/fcos.go | 1 | ||||
-rw-r--r-- | pkg/machine/fedora.go | 6 | ||||
-rw-r--r-- | pkg/machine/ignition.go | 28 | ||||
-rw-r--r-- | pkg/machine/ignition_darwin.go | 3 | ||||
-rw-r--r-- | pkg/machine/ignition_schema.go | 1 | ||||
-rw-r--r-- | pkg/machine/ignition_windows.go | 3 | ||||
-rw-r--r-- | pkg/machine/keys.go | 1 | ||||
-rw-r--r-- | pkg/machine/machine_unsupported.go | 1 | ||||
-rw-r--r-- | pkg/machine/pull.go | 5 | ||||
-rw-r--r-- | pkg/machine/qemu/config.go | 9 | ||||
-rw-r--r-- | pkg/machine/qemu/machine.go | 193 | ||||
-rw-r--r-- | pkg/machine/qemu/machine_unsupported.go | 1 | ||||
-rw-r--r-- | pkg/machine/qemu/options_darwin_arm64.go | 1 | ||||
-rw-r--r-- | pkg/machine/wsl/machine_unsupported.go | 1 |
15 files changed, 192 insertions, 71 deletions
diff --git a/pkg/machine/config.go b/pkg/machine/config.go index efb1eda15..aaf8da872 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -1,3 +1,4 @@ +//go:build amd64 || arm64 // +build amd64 arm64 package machine @@ -28,14 +29,16 @@ type InitOptions struct { Username string ReExec bool Rootful bool + // The numberical userid of the user that called machine + UID string } type QemuMachineStatus = string const ( - // Running indicates the qemu vm is running + // Running indicates the qemu vm is running. Running QemuMachineStatus = "running" - // Stopped indicates the vm has stopped + // Stopped indicates the vm has stopped. Stopped QemuMachineStatus = "stopped" DefaultMachineName string = "podman-machine-default" ) @@ -144,7 +147,7 @@ func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url } // GetDataDir returns the filepath where vm images should -// live for podman-machine +// live for podman-machine. func GetDataDir(vmType string) (string, error) { data, err := homedir.GetDataHome() if err != nil { diff --git a/pkg/machine/fcos.go b/pkg/machine/fcos.go index 4d3e2edf4..6215ae08f 100644 --- a/pkg/machine/fcos.go +++ b/pkg/machine/fcos.go @@ -1,3 +1,4 @@ +//go:build amd64 || arm64 // +build amd64 arm64 package machine diff --git a/pkg/machine/fedora.go b/pkg/machine/fedora.go index cd713dde7..bed45c6da 100644 --- a/pkg/machine/fedora.go +++ b/pkg/machine/fedora.go @@ -1,3 +1,4 @@ +//go:build amd64 || arm64 // +build amd64 arm64 package machine @@ -58,7 +59,10 @@ func (f FedoraDownload) Get() *Download { func (f FedoraDownload) HasUsableCache() (bool, error) { info, err := os.Stat(f.LocalPath) if err != nil { - return false, nil + if errors.Is(err, os.ErrNotExist) { + return false, nil + } + return false, err } return info.Size() == f.Size, nil } diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go index 47b1836f0..b2dabb689 100644 --- a/pkg/machine/ignition.go +++ b/pkg/machine/ignition.go @@ -51,6 +51,7 @@ type DynamicIgnition struct { Name string Key string TimeZone string + UID int VMName string WritePath string } @@ -63,12 +64,13 @@ func NewIgnitionFile(ign DynamicIgnition) error { ignVersion := Ignition{ Version: "3.2.0", } - ignPassword := Passwd{ Users: []PasswdUser{ { Name: ign.Name, SSHAuthorizedKeys: []SSHAuthorizedKey{SSHAuthorizedKey(ign.Key)}, + // Set the UID of the core user inside the machine + UID: intToPtr(ign.UID), }, { Name: "root", @@ -289,9 +291,7 @@ func getDirs(usrName string) []Directory { } func getFiles(usrName string) []File { - var ( - files []File - ) + files := make([]File, 0) lingerExample := `[Unit] Description=A systemd user unit demo @@ -310,6 +310,7 @@ machine_enabled=true delegateConf := `[Service] Delegate=memory pids cpu io ` + subUID := `%s:100000:1000000` // Add a fake systemd service to get the user socket rolling files = append(files, File{ @@ -344,6 +345,25 @@ Delegate=memory pids cpu io }, }) + // Setup /etc/subuid and /etc/subgid + for _, sub := range []string{"/etc/subuid", "/etc/subgid"} { + files = append(files, File{ + Node: Node{ + Group: getNodeGrp("root"), + Path: sub, + User: getNodeUsr("root"), + Overwrite: boolToPtr(true), + }, + FileEmbedded1: FileEmbedded1{ + Append: nil, + Contents: Resource{ + Source: encodeDataURLPtr(fmt.Sprintf(subUID, usrName)), + }, + Mode: intToPtr(0744), + }, + }) + } + // Set delegate.conf so cpu,io subsystem is delegated to non-root users as well for cgroupv2 // by default files = append(files, File{ diff --git a/pkg/machine/ignition_darwin.go b/pkg/machine/ignition_darwin.go index 9ede4b026..b9fbf218d 100644 --- a/pkg/machine/ignition_darwin.go +++ b/pkg/machine/ignition_darwin.go @@ -1,4 +1,5 @@ -//+build darwin +//go:build darwin +// +build darwin package machine diff --git a/pkg/machine/ignition_schema.go b/pkg/machine/ignition_schema.go index 8cfb0d04e..d6b86229c 100644 --- a/pkg/machine/ignition_schema.go +++ b/pkg/machine/ignition_schema.go @@ -1,3 +1,4 @@ +//go:build amd64 || arm64 // +build amd64 arm64 package machine diff --git a/pkg/machine/ignition_windows.go b/pkg/machine/ignition_windows.go index c0de48bd3..0fcc06273 100644 --- a/pkg/machine/ignition_windows.go +++ b/pkg/machine/ignition_windows.go @@ -1,4 +1,5 @@ -//+build windows +//go:build windows +// +build windows package machine diff --git a/pkg/machine/keys.go b/pkg/machine/keys.go index 711b091f0..15c1f73d8 100644 --- a/pkg/machine/keys.go +++ b/pkg/machine/keys.go @@ -1,3 +1,4 @@ +//go:build amd64 || arm64 // +build amd64 arm64 package machine diff --git a/pkg/machine/machine_unsupported.go b/pkg/machine/machine_unsupported.go index da1437984..a12140e16 100644 --- a/pkg/machine/machine_unsupported.go +++ b/pkg/machine/machine_unsupported.go @@ -1,3 +1,4 @@ +//go:build !amd64 && !arm64 // +build !amd64,!arm64 package machine diff --git a/pkg/machine/pull.go b/pkg/machine/pull.go index 280b47f96..26abedfcd 100644 --- a/pkg/machine/pull.go +++ b/pkg/machine/pull.go @@ -1,3 +1,4 @@ +//go:build amd64 || arm64 // +build amd64 arm64 package machine @@ -19,8 +20,8 @@ import ( "github.com/containers/storage/pkg/archive" "github.com/sirupsen/logrus" "github.com/ulikunitz/xz" - "github.com/vbauerster/mpb/v6" - "github.com/vbauerster/mpb/v6/decor" + "github.com/vbauerster/mpb/v7" + "github.com/vbauerster/mpb/v7/decor" ) // GenericDownload is used when a user provides a URL diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go index c619b7dd4..211d96ccb 100644 --- a/pkg/machine/qemu/config.go +++ b/pkg/machine/qemu/config.go @@ -1,8 +1,11 @@ +//go:build (amd64 && !windows) || (arm64 && !windows) // +build amd64,!windows arm64,!windows package qemu -import "time" +import ( + "time" +) type Provider struct{} @@ -35,6 +38,8 @@ type MachineVM struct { RemoteUsername string // Whether this machine should run in a rootful or rootless manner Rootful bool + // UID is the numerical id of the user that called machine + UID int } type Mount struct { @@ -56,6 +61,6 @@ type Monitor struct { var ( // defaultQMPTimeout is the timeout duration for the - // qmp monitor interactions + // qmp monitor interactions. defaultQMPTimeout time.Duration = 2 * time.Second ) diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index 9beec2173..3b14572a6 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -34,7 +34,7 @@ import ( var ( qemuProvider = &Provider{} - // vmtype refers to qemu (vs libvirt, krun, etc) + // vmtype refers to qemu (vs libvirt, krun, etc). vmtype = "qemu" ) @@ -88,11 +88,16 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) { vm.Memory = opts.Memory vm.DiskSize = opts.DiskSize - // Look up the executable - execPath, err := exec.LookPath(QemuCommand) + // Find the qemu executable + cfg, err := config.Default() + if err != nil { + return nil, err + } + execPath, err := cfg.FindHelperBinary(QemuCommand, true) if err != nil { return nil, err } + cmd := append([]string{execPath}) // Add memory cmd = append(cmd, []string{"-m", strconv.Itoa(int(vm.Memory))}...) @@ -129,7 +134,7 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) { // LoadByName reads a json file that describes a known qemu vm // and returns a vm instance func (p *Provider) LoadVMByName(name string) (machine.VM, error) { - vm := new(MachineVM) + vm := &MachineVM{UID: -1} // posix reserves -1, so use it to signify undefined vmConfigDir, err := machine.GetConfDir(vmtype) if err != nil { return nil, err @@ -245,12 +250,13 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { } } v.Mounts = mounts + v.UID = os.Getuid() // Add location of bootable image v.CmdLine = append(v.CmdLine, "-drive", "if=virtio,file="+v.ImagePath) // This kind of stinks but no other way around this r/n if len(opts.IgnitionPath) < 1 { - uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/user/1000/podman/podman.sock", strconv.Itoa(v.Port), v.RemoteUsername) + uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", fmt.Sprintf("/run/user/%d/podman/podman.sock", v.UID), strconv.Itoa(v.Port), v.RemoteUsername) uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root") identity := filepath.Join(sshDir, v.Name) @@ -272,7 +278,9 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { fmt.Println("An ignition path was provided. No SSH connection was added to Podman") } // Write the JSON file - v.writeConfig() + if err := v.writeConfig(); err != nil { + return false, fmt.Errorf("writing JSON file: %w", err) + } // User has provided ignition file so keygen // will be skipped. @@ -296,7 +304,16 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { // only if the virtualdisk size is less than // the given disk size if opts.DiskSize<<(10*3) > originalDiskSize { - resize := exec.Command("qemu-img", []string{"resize", v.ImagePath, strconv.Itoa(int(opts.DiskSize)) + "G"}...) + // Find the qemu executable + cfg, err := config.Default() + if err != nil { + return false, err + } + resizePath, err := cfg.FindHelperBinary("qemu-img", true) + if err != nil { + return false, err + } + resize := exec.Command(resizePath, []string{"resize", v.ImagePath, strconv.Itoa(int(opts.DiskSize)) + "G"}...) resize.Stdout = os.Stdout resize.Stderr = os.Stderr if err := resize.Run(); err != nil { @@ -319,6 +336,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { VMName: v.Name, TimeZone: opts.TimeZone, WritePath: v.IgnitionFilePath, + UID: v.UID, } err = machine.NewIgnitionFile(ign) return err == nil, err @@ -354,9 +372,13 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { conn net.Conn err error qemuSocketConn net.Conn - wait time.Duration = time.Millisecond * 500 + wait = time.Millisecond * 500 ) + if v.isIncompatible() { + logrus.Errorf("machine %q is incompatible with this release of podman and needs to be recreated, starting for recovery only", v.Name) + } + forwardSock, forwardState, err := v.startHostNetworking() if err != nil { return errors.Errorf("unable to start host networking: %q", err) @@ -459,7 +481,17 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { for _, mount := range v.Mounts { fmt.Printf("Mounting volume... %s:%s\n", mount.Source, mount.Target) // create mountpoint directory if it doesn't exist - err = v.SSH(name, machine.SSHOptions{Args: []string{"-q", "--", "sudo", "mkdir", "-p", mount.Target}}) + // because / is immutable, we have to monkey around with permissions + // if we dont mount in /home or /mnt + args := []string{"-q", "--"} + if !strings.HasPrefix(mount.Target, "/home") || !strings.HasPrefix(mount.Target, "/mnt") { + args = append(args, "sudo", "chattr", "-i", "/", ";") + } + args = append(args, "sudo", "mkdir", "-p", mount.Target) + if !strings.HasPrefix(mount.Target, "/home") || !strings.HasPrefix(mount.Target, "/mnt") { + args = append(args, ";", "sudo", "chattr", "+i", "/", ";") + } + err = v.SSH(name, machine.SSHOptions{Args: args}) if err != nil { return err } @@ -480,7 +512,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { } } - waitAPIAndPrintInfo(forwardState, forwardSock, v.Rootful, v.Name) + v.waitAPIAndPrintInfo(forwardState, forwardSock) return nil } @@ -596,7 +628,8 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error { } if err := qmpMonitor.Disconnect(); err != nil { - return nil + // FIXME: this error should probably be returned + return nil // nolint: nilerr } disconnected = true @@ -653,7 +686,7 @@ func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, fun if err != nil { return "", nil, err } - if running { + if running && !opts.Force { return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name) } @@ -667,6 +700,11 @@ func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, fun if !opts.SaveImage { files = append(files, v.ImagePath) } + socketPath, err := v.getForwardSocketPath() + if err != nil { + logrus.Error(err) + } + files = append(files, socketPath) files = append(files, v.archRemovalFiles()...) if err := machine.RemoveConnection(v.Name); err != nil { @@ -720,7 +758,8 @@ func (v *MachineVM) isRunning() (bool, error) { // Check if we can dial it monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address, v.QMPMonitor.Timeout) if err != nil { - return false, nil + // FIXME: this error should probably be returned + return false, nil // nolint: nilerr } if err := monitor.Connect(); err != nil { return false, err @@ -743,7 +782,7 @@ func (v *MachineVM) isRunning() (bool, error) { func (v *MachineVM) isListening() bool { // Check if we can dial it - conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", "localhost", v.Port), 10*time.Millisecond) + conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", v.Port), 10*time.Millisecond) if err != nil { return false } @@ -790,7 +829,16 @@ func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error { // executes qemu-image info to get the virtual disk size // of the diskimage func getDiskSize(path string) (uint64, error) { - diskInfo := exec.Command("qemu-img", "info", "--output", "json", path) + // Find the qemu executable + cfg, err := config.Default() + if err != nil { + return 0, err + } + qemuPathDir, err := cfg.FindHelperBinary("qemu-img", true) + if err != nil { + return 0, err + } + diskInfo := exec.Command(qemuPathDir, "info", "--output", "json", path) stdout, err := diskInfo.StdoutPipe() if err != nil { return 0, err @@ -935,7 +983,11 @@ func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) { // Add the ssh port cmd = append(cmd, []string{"-ssh-port", fmt.Sprintf("%d", v.Port)}...) - cmd, forwardSock, state := v.setupAPIForwarding(cmd) + var forwardSock string + var state apiForwardingState + if !v.isIncompatible() { + cmd, forwardSock, state = v.setupAPIForwarding(cmd) + } if logrus.GetLevel() == logrus.DebugLevel { cmd = append(cmd, "--debug") @@ -952,7 +1004,7 @@ func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwa return cmd, "", noForwarding } - destSock := "/run/user/1000/podman/podman.sock" + destSock := fmt.Sprintf("/run/user/%d/podman/podman.sock", v.UID) forwardUser := "core" if v.Rootful { @@ -1003,6 +1055,10 @@ func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwa return cmd, dockerSock, dockerGlobal } +func (v *MachineVM) isIncompatible() bool { + return v.UID == -1 +} + func (v *MachineVM) getForwardSocketPath() (string, error) { path, err := machine.GetDataDir(v.Name) if err != nil { @@ -1045,10 +1101,13 @@ func waitAndPingAPI(sock string) { Transport: &http.Transport{ DialContext: func(context.Context, string, string) (net.Conn, error) { con, err := net.DialTimeout("unix", sock, apiUpTimeout) - if err == nil { - con.SetDeadline(time.Now().Add(apiUpTimeout)) + if err != nil { + return nil, err + } + if err := con.SetDeadline(time.Now().Add(apiUpTimeout)); err != nil { + return nil, err } - return con, err + return con, nil }, }, } @@ -1062,46 +1121,66 @@ func waitAndPingAPI(sock string) { } } -func waitAPIAndPrintInfo(forwardState apiForwardingState, forwardSock string, rootFul bool, name string) { - if forwardState != noForwarding { - waitAndPingAPI(forwardSock) - if !rootFul { - fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n") - fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n") - fmt.Printf("issues with non-podman clients, you can switch using the following command: \n") - - suffix := "" - if name != machine.DefaultMachineName { - suffix = " " + name - } - fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix) - } +func (v *MachineVM) waitAPIAndPrintInfo(forwardState apiForwardingState, forwardSock string) { + suffix := "" + if v.Name != machine.DefaultMachineName { + suffix = " " + v.Name + } - fmt.Printf("API forwarding listening on: %s\n", forwardSock) - if forwardState == dockerGlobal { - fmt.Printf("Docker API clients default to this address. You do not need to set DOCKER_HOST.\n\n") - } else { - stillString := "still " - switch forwardState { - case notInstalled: - fmt.Printf("\nThe system helper service is not installed; the default Docker API socket\n") - fmt.Printf("address can't be used by podman. ") - if helper := findClaimHelper(); len(helper) > 0 { - fmt.Printf("If you would like to install it run the\nfollowing command:\n") - fmt.Printf("\n\tsudo %s install\n\n", helper) - } - case machineLocal: - fmt.Printf("\nAnother process was listening on the default Docker API socket address.\n") - case claimUnsupported: - fallthrough - default: - stillString = "" - } + if v.isIncompatible() { + fmt.Fprintf(os.Stderr, "\n!!! ACTION REQUIRED: INCOMPATIBLE MACHINE !!!\n") + + fmt.Fprintf(os.Stderr, "\nThis machine was created by an older podman release that is incompatible\n") + fmt.Fprintf(os.Stderr, "with this release of podman. It has been started in a limited operational\n") + fmt.Fprintf(os.Stderr, "mode to allow you to copy any necessary files before recreating it. This\n") + fmt.Fprintf(os.Stderr, "can be accomplished with the following commands:\n\n") + fmt.Fprintf(os.Stderr, "\t# Login and copy desired files (Optional)\n") + fmt.Fprintf(os.Stderr, "\t# podman machine ssh%s tar cvPf - /path/to/files > backup.tar\n\n", suffix) + fmt.Fprintf(os.Stderr, "\t# Recreate machine (DESTRUCTIVE!) \n") + fmt.Fprintf(os.Stderr, "\tpodman machine stop%s\n", suffix) + fmt.Fprintf(os.Stderr, "\tpodman machine rm -f%s\n", suffix) + fmt.Fprintf(os.Stderr, "\tpodman machine init --now%s\n\n", suffix) + fmt.Fprintf(os.Stderr, "\t# Copy back files (Optional)\n") + fmt.Fprintf(os.Stderr, "\t# cat backup.tar | podman machine ssh%s tar xvPf - \n\n", suffix) + } + + if forwardState == noForwarding { + return + } - fmt.Printf("You can %sconnect Docker API clients by setting DOCKER_HOST using the\n", stillString) - fmt.Printf("following command in your terminal session:\n") - fmt.Printf("\n\texport DOCKER_HOST='unix://%s'\n\n", forwardSock) + waitAndPingAPI(forwardSock) + if !v.Rootful { + fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n") + fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n") + fmt.Printf("issues with non-podman clients, you can switch using the following command: \n") + fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix) + } + + fmt.Printf("API forwarding listening on: %s\n", forwardSock) + if forwardState == dockerGlobal { + fmt.Printf("Docker API clients default to this address. You do not need to set DOCKER_HOST.\n\n") + } else { + stillString := "still " + switch forwardState { + case notInstalled: + fmt.Printf("\nThe system helper service is not installed; the default Docker API socket\n") + fmt.Printf("address can't be used by podman. ") + if helper := findClaimHelper(); len(helper) > 0 { + fmt.Printf("If you would like to install it run the\nfollowing commands:\n") + fmt.Printf("\n\tsudo %s install\n", helper) + fmt.Printf("\tpodman machine stop%s; podman machine start%s\n\n", suffix, suffix) + } + case machineLocal: + fmt.Printf("\nAnother process was listening on the default Docker API socket address.\n") + case claimUnsupported: + fallthrough + default: + stillString = "" } + + fmt.Printf("You can %sconnect Docker API clients by setting DOCKER_HOST using the\n", stillString) + fmt.Printf("following command in your terminal session:\n") + fmt.Printf("\n\texport DOCKER_HOST='unix://%s'\n\n", forwardSock) } } diff --git a/pkg/machine/qemu/machine_unsupported.go b/pkg/machine/qemu/machine_unsupported.go index e3ce05e3d..794e710f9 100644 --- a/pkg/machine/qemu/machine_unsupported.go +++ b/pkg/machine/qemu/machine_unsupported.go @@ -1,3 +1,4 @@ +//go:build (!amd64 && !arm64) || windows // +build !amd64,!arm64 windows package qemu diff --git a/pkg/machine/qemu/options_darwin_arm64.go b/pkg/machine/qemu/options_darwin_arm64.go index 727a275d2..5b6cdc86d 100644 --- a/pkg/machine/qemu/options_darwin_arm64.go +++ b/pkg/machine/qemu/options_darwin_arm64.go @@ -45,6 +45,7 @@ func getOvmfDir(imagePath, vmName string) string { */ func getEdk2CodeFd(name string) string { dirs := []string{ + "/opt/homebrew/opt/podman/libexec/share/qemu", "/usr/local/share/qemu", "/opt/homebrew/share/qemu", } diff --git a/pkg/machine/wsl/machine_unsupported.go b/pkg/machine/wsl/machine_unsupported.go index 043c5d729..856f9dd0c 100644 --- a/pkg/machine/wsl/machine_unsupported.go +++ b/pkg/machine/wsl/machine_unsupported.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package wsl |