From d7cb66492bed0fe8d6bc6839329455d967094d2e Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Thu, 1 Feb 2024 09:00:36 -0600 Subject: [PATCH] wsl - wip Signed-off-by: Brent Baude --- pkg/machine/applehv/stubber.go | 9 + pkg/machine/define/config.go | 6 +- pkg/machine/e2e/machine_test.go | 18 +- pkg/machine/e2e/set_test.go | 3 + pkg/machine/hyperv/stubber.go | 10 + pkg/machine/machine_windows.go | 7 + pkg/machine/provider/platform_windows.go | 9 +- pkg/machine/qemu/stubber.go | 12 +- pkg/machine/shim/diskpull/diskpull.go | 32 + pkg/machine/shim/host.go | 83 +- pkg/machine/shim/networking.go | 82 +- pkg/machine/stdpull/url.go | 5 +- pkg/machine/vmconfigs/config.go | 5 + pkg/machine/vmconfigs/config_windows.go | 3 +- pkg/machine/vmconfigs/machine.go | 1 + pkg/machine/wsl/declares.go | 247 ++++++ pkg/machine/wsl/fedora.go | 20 +- pkg/machine/wsl/machine.go | 996 ++--------------------- pkg/machine/wsl/stubber.go | 362 ++++++++ pkg/machine/wsl/usermodenet.go | 51 +- 20 files changed, 891 insertions(+), 1070 deletions(-) create mode 100644 pkg/machine/shim/diskpull/diskpull.go create mode 100644 pkg/machine/wsl/declares.go create mode 100644 pkg/machine/wsl/stubber.go diff --git a/pkg/machine/applehv/stubber.go b/pkg/machine/applehv/stubber.go index be1d1179c2..78095dcfab 100644 --- a/pkg/machine/applehv/stubber.go +++ b/pkg/machine/applehv/stubber.go @@ -16,6 +16,7 @@ import ( "github.com/containers/podman/v5/pkg/machine/applehv/vfkit" "github.com/containers/podman/v5/pkg/machine/define" "github.com/containers/podman/v5/pkg/machine/ignition" + "github.com/containers/podman/v5/pkg/machine/shim/diskpull" "github.com/containers/podman/v5/pkg/machine/sockets" "github.com/containers/podman/v5/pkg/machine/vmconfigs" "github.com/containers/podman/v5/utils" @@ -38,6 +39,10 @@ type AppleHVStubber struct { vmconfigs.AppleHVConfig } +func (a AppleHVStubber) UserModeNetworkEnabled(_ *vmconfigs.MachineConfig) bool { + return true +} + func (a AppleHVStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineConfig, ignBuilder *ignition.IgnitionBuilder) error { mc.AppleHypervisor = new(vmconfigs.AppleHVConfig) mc.AppleHypervisor.Vfkit = vfkit.VfkitHelper{} @@ -317,3 +322,7 @@ func (a AppleHVStubber) PrepareIgnition(_ *vmconfigs.MachineConfig, _ *ignition. func (a AppleHVStubber) PostStartNetworking(mc *vmconfigs.MachineConfig) error { return nil } + +func (a AppleHVStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error { + return diskpull.GetDisk(userInputPath, dirs, mc.ImagePath, a.VMType(), mc.Name) +} diff --git a/pkg/machine/define/config.go b/pkg/machine/define/config.go index c7217ac234..b03da89bdf 100644 --- a/pkg/machine/define/config.go +++ b/pkg/machine/define/config.go @@ -10,8 +10,10 @@ var ( ) type CreateVMOpts struct { - Name string - Dirs *MachineDirs + Name string + Dirs *MachineDirs + ReExec bool + UserModeNetworking bool } type MachineDirs struct { diff --git a/pkg/machine/e2e/machine_test.go b/pkg/machine/e2e/machine_test.go index e109532bac..918176fa50 100644 --- a/pkg/machine/e2e/machine_test.go +++ b/pkg/machine/e2e/machine_test.go @@ -2,6 +2,7 @@ package e2e_test import ( "fmt" + "github.com/containers/podman/v4/pkg/machine/wsl" "io" url2 "net/url" "os" @@ -61,9 +62,20 @@ var _ = BeforeSuite(func() { downloadLocation := os.Getenv("MACHINE_IMAGE") if downloadLocation == "" { - downloadLocation, err = GetDownload(testProvider.VMType()) - if err != nil { - Fail("unable to derive download disk from fedora coreos") + // TODO so beautifully gross ... ideally we can spend some time + // here making life easier on the next person + switch testProvider.VMType() { + case define.WSLVirt: + dl, _, _, _, err := wsl.GetFedoraDownloadForWSL() + if err != nil { + Fail("unable to determine WSL download") + } + downloadLocation = dl.String() + default: + downloadLocation, err = GetDownload(testProvider.VMType()) + if err != nil { + Fail("unable to derive download disk from fedora coreos") + } } } diff --git a/pkg/machine/e2e/set_test.go b/pkg/machine/e2e/set_test.go index 6f49e82ef9..439539746b 100644 --- a/pkg/machine/e2e/set_test.go +++ b/pkg/machine/e2e/set_test.go @@ -171,6 +171,9 @@ var _ = Describe("podman machine set", func() { if testProvider.VMType() != define.WSLVirt { Skip("Test is only for WSL") } + // TODO - this currently fails + Skip("test fails bc usermode network needs plumbing for WSL") + name := randomString() i := new(initMachine) session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run() diff --git a/pkg/machine/hyperv/stubber.go b/pkg/machine/hyperv/stubber.go index 21d436eb7c..1d4fddf9da 100644 --- a/pkg/machine/hyperv/stubber.go +++ b/pkg/machine/hyperv/stubber.go @@ -10,6 +10,8 @@ import ( "os/exec" "path/filepath" + "github.com/containers/podman/v5/pkg/machine/shim/diskpull" + "github.com/Microsoft/go-winio" "github.com/containers/common/pkg/strongunits" gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types" @@ -27,6 +29,10 @@ type HyperVStubber struct { vmconfigs.HyperVConfig } +func (h HyperVStubber) UserModeNetworkEnabled(mc *vmconfigs.MachineConfig) bool { + return true +} + func (h HyperVStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineConfig, builder *ignition.IgnitionBuilder) error { var ( err error @@ -459,6 +465,10 @@ func (h HyperVStubber) PostStartNetworking(mc *vmconfigs.MachineConfig) error { return err } +func (h HyperVStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error { + return diskpull.GetDisk(userInputPath, dirs, mc.ImagePath, h.VMType(), mc.Name) +} + func resizeDisk(newSize strongunits.GiB, imagePath *define.VMFile) error { resize := exec.Command("powershell", []string{"-command", fmt.Sprintf("Resize-VHD %s %d", imagePath.GetPath(), newSize.ToBytes())}...) logrus.Debug(resize.Args) diff --git a/pkg/machine/machine_windows.go b/pkg/machine/machine_windows.go index 68ff8785ff..3e5ad1168e 100644 --- a/pkg/machine/machine_windows.go +++ b/pkg/machine/machine_windows.go @@ -134,6 +134,13 @@ func launchWinProxy(opts WinProxyOpts) (bool, string, error) { cmd := exec.Command(command, args...) logrus.Debugf("winssh command: %s %v", command, args) + f, err := os.Open("c:\\Users\\baude\\sshproxy.log") + if err != nil { + return false, "", err + } + cmd.Stderr = f + cmd.Stdout = f + defer f.Close() if err := cmd.Start(); err != nil { return globalName, "", err } diff --git a/pkg/machine/provider/platform_windows.go b/pkg/machine/provider/platform_windows.go index d7caf791d0..943a66926c 100644 --- a/pkg/machine/provider/platform_windows.go +++ b/pkg/machine/provider/platform_windows.go @@ -2,9 +2,11 @@ package provider import ( "fmt" - "github.com/containers/podman/v5/pkg/machine/vmconfigs" "os" + "github.com/containers/podman/v5/pkg/machine/vmconfigs" + "github.com/containers/podman/v5/pkg/machine/wsl" + "github.com/containers/common/pkg/config" "github.com/containers/podman/v5/pkg/machine/define" "github.com/containers/podman/v5/pkg/machine/hyperv" @@ -27,9 +29,8 @@ func Get() (vmconfigs.VMProvider, error) { logrus.Debugf("Using Podman machine with `%s` virtualization provider", resolvedVMType.String()) switch resolvedVMType { - // TODO re-enable this with WSL - //case define.WSLVirt: - // return wsl.VirtualizationProvider(), nil + case define.WSLVirt: + return new(wsl.WSLStubber), nil case define.HyperVVirt: return new(hyperv.HyperVStubber), nil default: diff --git a/pkg/machine/qemu/stubber.go b/pkg/machine/qemu/stubber.go index ed5934ed0e..3fed30c955 100644 --- a/pkg/machine/qemu/stubber.go +++ b/pkg/machine/qemu/stubber.go @@ -11,14 +11,14 @@ import ( "strings" "time" - "github.com/containers/podman/v5/pkg/machine/ignition" - "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/strongunits" gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types" "github.com/containers/podman/v5/pkg/machine" "github.com/containers/podman/v5/pkg/machine/define" + "github.com/containers/podman/v5/pkg/machine/ignition" "github.com/containers/podman/v5/pkg/machine/qemu/command" + "github.com/containers/podman/v5/pkg/machine/shim/diskpull" "github.com/containers/podman/v5/pkg/machine/sockets" "github.com/containers/podman/v5/pkg/machine/vmconfigs" "github.com/sirupsen/logrus" @@ -30,6 +30,10 @@ type QEMUStubber struct { Command command.QemuCmd } +func (q QEMUStubber) UserModeNetworkEnabled(*vmconfigs.MachineConfig) bool { + return true +} + func (q *QEMUStubber) setQEMUCommandLine(mc *vmconfigs.MachineConfig) error { qemuBinary, err := findQEMUBinary() if err != nil { @@ -326,3 +330,7 @@ func (q *QEMUStubber) MountType() vmconfigs.VolumeMountType { func (q *QEMUStubber) PostStartNetworking(mc *vmconfigs.MachineConfig) error { return nil } + +func (q *QEMUStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error { + return diskpull.GetDisk(userInputPath, dirs, mc.ImagePath, q.VMType(), mc.Name) +} diff --git a/pkg/machine/shim/diskpull/diskpull.go b/pkg/machine/shim/diskpull/diskpull.go new file mode 100644 index 0000000000..302c40d155 --- /dev/null +++ b/pkg/machine/shim/diskpull/diskpull.go @@ -0,0 +1,32 @@ +package diskpull + +import ( + "context" + "strings" + + "github.com/containers/podman/v5/pkg/machine/define" + "github.com/containers/podman/v5/pkg/machine/ocipull" + "github.com/containers/podman/v5/pkg/machine/stdpull" +) + +func GetDisk(userInputPath string, dirs *define.MachineDirs, imagePath *define.VMFile, vmType define.VMType, name string) error { + var ( + err error + mydisk ocipull.Disker + ) + + if userInputPath == "" { + mydisk, err = ocipull.NewVersioned(context.Background(), dirs.DataDir, name, vmType.String(), imagePath) + } else { + if strings.HasPrefix(userInputPath, "http") { + // TODO probably should use tempdir instead of datadir + mydisk, err = stdpull.NewDiskFromURL(userInputPath, imagePath, dirs.DataDir, nil) + } else { + mydisk, err = stdpull.NewStdDiskPull(userInputPath, imagePath) + } + } + if err != nil { + return err + } + return mydisk.Get() +} diff --git a/pkg/machine/shim/host.go b/pkg/machine/shim/host.go index ece54f4dd5..807c60741c 100644 --- a/pkg/machine/shim/host.go +++ b/pkg/machine/shim/host.go @@ -1,12 +1,10 @@ package shim import ( - "context" "errors" "fmt" "os" "runtime" - "strings" "time" "github.com/containers/common/pkg/util" @@ -14,30 +12,13 @@ import ( "github.com/containers/podman/v5/pkg/machine/connection" machineDefine "github.com/containers/podman/v5/pkg/machine/define" "github.com/containers/podman/v5/pkg/machine/ignition" - "github.com/containers/podman/v5/pkg/machine/ocipull" - "github.com/containers/podman/v5/pkg/machine/stdpull" "github.com/containers/podman/v5/pkg/machine/vmconfigs" "github.com/sirupsen/logrus" ) -/* -Host - ├ Info - ├ OS Apply - ├ SSH - ├ List - ├ Init - ├ VMExists - ├ CheckExclusiveActiveVM *HyperV/WSL need to check their hypervisors as well -*/ - -func Info() {} -func OSApply() {} -func SSH() {} - // List is done at the host level to allow for a *possible* future where // more than one provider is used -func List(vmstubbers []vmconfigs.VMProvider, opts machine.ListOptions) ([]*machine.ListResponse, error) { +func List(vmstubbers []vmconfigs.VMProvider, _ machine.ListOptions) ([]*machine.ListResponse, error) { var ( lrs []*machine.ListResponse ) @@ -114,6 +95,10 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) (*vmconfigs.M Dirs: dirs, } + if umn := opts.UserModeNetworking; umn != nil { + createOpts.UserModeNetworking = *umn + } + // Get Image // TODO This needs rework bigtime; my preference is most of below of not living in here. // ideally we could get a func back that pulls the image, and only do so IF everything works because @@ -137,8 +122,7 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) (*vmconfigs.M if err != nil { return nil, err } - - var mydisk ocipull.Disker + mc.ImagePath = imagePath // TODO The following stanzas should be re-written in a differeent place. It should have a custom // parser for our image pulling. It would be nice if init just got an error and mydisk back. @@ -149,25 +133,12 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) (*vmconfigs.M // "/path // "docker://quay.io/something/someManifest - if opts.ImagePath == "" { - mydisk, err = ocipull.NewVersioned(context.Background(), dirs.DataDir, opts.Name, mp.VMType().String(), imagePath) - } else { - if strings.HasPrefix(opts.ImagePath, "http") { - // TODO probably should use tempdir instead of datadir - mydisk, err = stdpull.NewDiskFromURL(opts.ImagePath, imagePath, dirs.DataDir) - } else { - mydisk, err = stdpull.NewStdDiskPull(opts.ImagePath, imagePath) - } - } - if err != nil { - return nil, err - } - err = mydisk.Get() + // TODO Ideally this changes into some way better ... + err = mp.GetDisk(opts.ImagePath, dirs, mc) if err != nil { return nil, err } - mc.ImagePath = imagePath callbackFuncs.Add(mc.ImagePath.Delete) logrus.Debugf("--> imagePath is %q", imagePath.GetPath()) @@ -182,8 +153,18 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) (*vmconfigs.M uid = 1000 } + // TODO the definition of "user" should go into + // common for WSL + userName := opts.Username + if mp.VMType() == machineDefine.WSLVirt { + if opts.Username == "core" { + userName = "user" + mc.SSH.RemoteUsername = "user" + } + } + ignBuilder := ignition.NewIgnitionBuilder(ignition.DynamicIgnition{ - Name: opts.Username, + Name: userName, Key: sshKey, TimeZone: opts.TimeZone, UID: uid, @@ -223,7 +204,9 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) (*vmconfigs.M ignBuilder.WithUnit(readyUnit) // Mounts - mc.Mounts = CmdLineVolumesToMounts(opts.Volumes, mp.MountType()) + if mp.VMType() != machineDefine.WSLVirt { + mc.Mounts = CmdLineVolumesToMounts(opts.Volumes, mp.MountType()) + } // TODO AddSSHConnectionToPodmanSocket could take an machineconfig instead if err := connection.AddSSHConnectionsToPodmanSocket(mc.HostUser.UID, mc.SSH.Port, mc.SSH.IdentityPath, mc.Name, mc.SSH.RemoteUsername, opts); err != nil { @@ -347,21 +330,23 @@ func Stop(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, dirs *machineDef } // Stop GvProxy and remove PID file - gvproxyPidFile, err := dirs.RuntimeDir.AppendToNewVMFile("gvproxy.pid", nil) - if err != nil { - return err - } - - defer func() { - if err := machine.CleanupGVProxy(*gvproxyPidFile); err != nil { - logrus.Errorf("unable to clean up gvproxy: %q", err) + if mp.UserModeNetworkEnabled(mc) { + gvproxyPidFile, err := dirs.RuntimeDir.AppendToNewVMFile("gvproxy.pid", nil) + if err != nil { + return err } - }() + + defer func() { + if err := machine.CleanupGVProxy(*gvproxyPidFile); err != nil { + logrus.Errorf("unable to clean up gvproxy: %q", err) + } + }() + } return nil } -func Start(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, dirs *machineDefine.MachineDirs, opts machine.StartOptions) error { +func Start(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, _ *machineDefine.MachineDirs, opts machine.StartOptions) error { defaultBackoff := 500 * time.Millisecond maxBackoffs := 6 diff --git a/pkg/machine/shim/networking.go b/pkg/machine/shim/networking.go index 28dc2db01b..dfb39008ba 100644 --- a/pkg/machine/shim/networking.go +++ b/pkg/machine/shim/networking.go @@ -23,16 +23,12 @@ const ( dockerConnectTimeout = 5 * time.Second ) -func startNetworking(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvider) (string, machine.APIForwardingState, error) { - var ( - forwardingState machine.APIForwardingState - forwardSock string - ) - // the guestSock is "inside" the guest machine - guestSock := fmt.Sprintf(defaultGuestSock, mc.HostUser.UID) +func startUserModeNetworking(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvider, dirs *define.MachineDirs, hostSocket *define.VMFile) error { forwardUser := mc.SSH.RemoteUsername - // TODO should this go up the stack higher + // TODO should this go up the stack higher or + // the guestSock is "inside" the guest machine + guestSock := fmt.Sprintf(defaultGuestSock, mc.HostUser.UID) if mc.HostUser.Rootful { guestSock = "/run/podman/podman.sock" forwardUser = "root" @@ -40,38 +36,18 @@ func startNetworking(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvider) cfg, err := config.Default() if err != nil { - return "", 0, err + return err } binary, err := cfg.FindHelperBinary(machine.ForwarderBinaryName, false) if err != nil { - return "", 0, err - } - - dataDir, err := mc.DataDir() - if err != nil { - return "", 0, err - } - hostSocket, err := dataDir.AppendToNewVMFile("podman.sock", nil) - if err != nil { - return "", 0, err - } - - runDir, err := mc.RuntimeDir() - if err != nil { - return "", 0, err - } - - linkSocketPath := filepath.Dir(dataDir.GetPath()) - linkSocket, err := define.NewMachineFile(filepath.Join(linkSocketPath, "podman.sock"), nil) - if err != nil { - return "", 0, err + return err } cmd := gvproxy.NewGvproxyCommand() // GvProxy PID file path is now derived - cmd.PidFile = filepath.Join(runDir.GetPath(), "gvproxy.pid") + cmd.PidFile = filepath.Join(dirs.RuntimeDir.GetPath(), "gvproxy.pid") // TODO This can be re-enabled when gvisor-tap-vsock #305 is merged // debug is set, we dump to a logfile as well @@ -94,6 +70,36 @@ func startNetworking(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvider) // This allows a provider to perform additional setup as well as // add in any provider specific options for gvproxy if err := provider.StartNetworking(mc, &cmd); err != nil { + return err + } + + c := cmd.Cmd(binary) + + logrus.Debugf("gvproxy command-line: %s %s", binary, strings.Join(cmd.ToCmdline(), " ")) + if err := c.Start(); err != nil { + return fmt.Errorf("unable to execute: %q: %w", cmd.ToCmdline(), err) + } + + return nil +} + +func startNetworking(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvider) (string, machine.APIForwardingState, error) { + var ( + forwardingState machine.APIForwardingState + forwardSock string + ) + dirs, err := machine.GetMachineDirs(provider.VMType()) + if err != nil { + return "", 0, err + } + hostSocket, err := dirs.DataDir.AppendToNewVMFile("podman.sock", nil) + if err != nil { + return "", 0, err + } + + linkSocketPath := filepath.Dir(dirs.DataDir.GetPath()) + linkSocket, err := define.NewMachineFile(filepath.Join(linkSocketPath, "podman.sock"), nil) + if err != nil { return "", 0, err } @@ -101,21 +107,15 @@ func startNetworking(mc *vmconfigs.MachineConfig, provider vmconfigs.VMProvider) forwardSock, forwardingState = setupAPIForwarding(hostSocket, linkSocket) } - c := cmd.Cmd(binary) - - logrus.Debugf("gvproxy command-line: %s %s", binary, strings.Join(cmd.ToCmdline(), " ")) - if err := c.Start(); err != nil { - return forwardSock, 0, fmt.Errorf("unable to execute: %q: %w", cmd.ToCmdline(), err) + if provider.UserModeNetworkEnabled(mc) { + if err := startUserModeNetworking(mc, provider, dirs, hostSocket); err != nil { + return "", 0, err + } } return forwardSock, forwardingState, nil } -type apiOptions struct { //nolint:unused - socketpath, destinationSocketPath *define.VMFile - fowardUser string -} - func setupAPIForwarding(hostSocket, linkSocket *define.VMFile) (string, machine.APIForwardingState) { // The linking pattern is /var/run/docker.sock -> user global sock (link) -> machine sock (socket) // This allows the helper to only have to maintain one constant target to the user, which can be diff --git a/pkg/machine/stdpull/url.go b/pkg/machine/stdpull/url.go index cc271329c3..a8cc27a3f8 100644 --- a/pkg/machine/stdpull/url.go +++ b/pkg/machine/stdpull/url.go @@ -23,7 +23,7 @@ type DiskFromURL struct { tempLocation *define.VMFile } -func NewDiskFromURL(inputPath string, finalPath *define.VMFile, tempDir *define.VMFile) (*DiskFromURL, error) { +func NewDiskFromURL(inputPath string, finalPath *define.VMFile, tempDir *define.VMFile, optionalTempFileName *string) (*DiskFromURL, error) { var ( err error ) @@ -40,6 +40,9 @@ func NewDiskFromURL(inputPath string, finalPath *define.VMFile, tempDir *define. } remoteImageName := path.Base(inputPath) + if optionalTempFileName != nil { + remoteImageName = *optionalTempFileName + } if remoteImageName == "" { return nil, fmt.Errorf("invalid url: unable to determine image name in %q", inputPath) } diff --git a/pkg/machine/vmconfigs/config.go b/pkg/machine/vmconfigs/config.go index 44179c0fc0..a5449b56cd 100644 --- a/pkg/machine/vmconfigs/config.go +++ b/pkg/machine/vmconfigs/config.go @@ -107,6 +107,10 @@ func (f fcosMachineImage) path() string { type VMProvider interface { //nolint:interfacebloat CreateVM(opts define.CreateVMOpts, mc *MachineConfig, builder *ignition.IgnitionBuilder) error + // GetDisk should be only temporary. It is largely here only because WSL disk pulling is different + // TODO + // Let's deprecate this ASAP + GetDisk(userInputPath string, dirs *define.MachineDirs, mc *MachineConfig) error PrepareIgnition(mc *MachineConfig, ignBuilder *ignition.IgnitionBuilder) (*ignition.ReadyUnitOpts, error) GetHyperVisorVMs() ([]string, error) MountType() VolumeMountType @@ -121,6 +125,7 @@ type VMProvider interface { //nolint:interfacebloat StopVM(mc *MachineConfig, hardStop bool) error StopHostNetworking(mc *MachineConfig, vmType define.VMType) error VMType() define.VMType + UserModeNetworkEnabled(mc *MachineConfig) bool } // HostUser describes the host user diff --git a/pkg/machine/vmconfigs/config_windows.go b/pkg/machine/vmconfigs/config_windows.go index aa24dd707d..0562490c7c 100644 --- a/pkg/machine/vmconfigs/config_windows.go +++ b/pkg/machine/vmconfigs/config_windows.go @@ -13,7 +13,8 @@ type HyperVConfig struct { } type WSLConfig struct { - //wslstuff *aThing + // Uses usermode networking + UserModeNetworking bool } // Stubs diff --git a/pkg/machine/vmconfigs/machine.go b/pkg/machine/vmconfigs/machine.go index 1176c92109..463b017fd8 100644 --- a/pkg/machine/vmconfigs/machine.go +++ b/pkg/machine/vmconfigs/machine.go @@ -77,6 +77,7 @@ func NewMachineConfig(opts define.InitOptions, dirs *define.MachineDirs, sshIden } mc.Resources = mrc + // TODO WSL had a locking port mechanism, we should consider this. sshPort, err := utils.GetRandomPort() if err != nil { return nil, err diff --git a/pkg/machine/wsl/declares.go b/pkg/machine/wsl/declares.go new file mode 100644 index 0000000000..1c59d5f421 --- /dev/null +++ b/pkg/machine/wsl/declares.go @@ -0,0 +1,247 @@ +package wsl + +const ( + ErrorSuccessRebootInitiated = 1641 + ErrorSuccessRebootRequired = 3010 + currentMachineVersion = 3 +) + +const containersConf = `[containers] + +[engine] +cgroup_manager = "cgroupfs" +` + +const registriesConf = `unqualified-search-registries=["docker.io"] +` + +const appendPort = `grep -q Port\ %d /etc/ssh/sshd_config || echo Port %d >> /etc/ssh/sshd_config` + +const changePort = `sed -E -i 's/^Port[[:space:]]+[0-9]+/Port %d/' /etc/ssh/sshd_config` + +const configServices = `ln -fs /usr/lib/systemd/system/sshd.service /etc/systemd/system/multi-user.target.wants/sshd.service +ln -fs /usr/lib/systemd/system/podman.socket /etc/systemd/system/sockets.target.wants/podman.socket +rm -f /etc/systemd/system/getty.target.wants/console-getty.service +rm -f /etc/systemd/system/getty.target.wants/getty@tty1.service +rm -f /etc/systemd/system/multi-user.target.wants/systemd-resolved.service +rm -f /etc/systemd/system/sysinit.target.wants//systemd-resolved.service +rm -f /etc/systemd/system/dbus-org.freedesktop.resolve1.service +ln -fs /dev/null /etc/systemd/system/console-getty.service +ln -fs /dev/null /etc/systemd/system/systemd-oomd.socket +mkdir -p /etc/systemd/system/systemd-sysusers.service.d/ +echo CREATE_MAIL_SPOOL=no >> /etc/default/useradd +adduser -m [USER] -G wheel +mkdir -p /home/[USER]/.config/systemd/[USER]/ +chown [USER]:[USER] /home/[USER]/.config +` + +const sudoers = `%wheel ALL=(ALL) NOPASSWD: ALL +` + +const bootstrap = `#!/bin/bash +ps -ef | grep -v grep | grep -q systemd && exit 0 +nohup unshare --kill-child --fork --pid --mount --mount-proc --propagation shared /lib/systemd/systemd >/dev/null 2>&1 & +sleep 0.1 +` + +const wslmotd = ` +You will be automatically entered into a nested process namespace where +systemd is running. If you need to access the parent namespace, hit ctrl-d +or type exit. This also means to log out you need to exit twice. + +` + +const sysdpid = "SYSDPID=`ps -eo cmd,pid | grep -m 1 ^/lib/systemd/systemd | awk '{print $2}'`" + +const profile = sysdpid + ` +if [ ! -z "$SYSDPID" ] && [ "$SYSDPID" != "1" ]; then + cat /etc/wslmotd + /usr/local/bin/enterns +fi +` + +const enterns = "#!/bin/bash\n" + sysdpid + ` +if [ ! -z "$SYSDPID" ] && [ "$SYSDPID" != "1" ]; then + NSENTER=("nsenter" "-m" "-p" "-t" "$SYSDPID" "--wd=$PWD") + + if [ "$UID" != "0" ]; then + NSENTER=("sudo" "${NSENTER[@]}") + if [ "$#" != "0" ]; then + NSENTER+=("sudo" "-u" "$USER") + else + NSENTER+=("su" "-l" "$USER") + fi + fi + "${NSENTER[@]}" "$@" +fi` + +const waitTerm = sysdpid + ` +if [ ! -z "$SYSDPID" ]; then + timeout 60 tail -f /dev/null --pid $SYSDPID +fi +` + +const wslConf = `[user] +default=[USER] +` + +const wslConfUserNet = ` +[network] +generateResolvConf = false +` + +const resolvConfUserNet = ` +nameserver 192.168.127.1 +` + +// WSL kernel does not have sg and crypto_user modules +const overrideSysusers = `[Service] +LoadCredential= +` + +const lingerService = `[Unit] +Description=A systemd user unit demo +After=network-online.target +Wants=network-online.target podman.socket +[Service] +ExecStart=/usr/bin/sleep infinity +` + +const lingerSetup = `mkdir -p /home/[USER]/.config/systemd/user/default.target.wants +ln -fs /home/[USER]/.config/systemd/user/linger-example.service \ + /home/[USER]/.config/systemd/user/default.target.wants/linger-example.service +` + +const bindMountSystemService = ` +[Unit] +Description=Bind mount for system podman sockets +After=podman.socket + +[Service] +RemainAfterExit=true +Type=oneshot +# Ensure user services can register sockets as well +ExecStartPre=mkdir -p -m 777 /mnt/wsl/podman-sockets +ExecStartPre=mkdir -p -m 777 /mnt/wsl/podman-sockets/%[1]s +ExecStartPre=touch /mnt/wsl/podman-sockets/%[1]s/podman-root.sock +ExecStart=mount --bind %%t/podman/podman.sock /mnt/wsl/podman-sockets/%[1]s/podman-root.sock +ExecStop=umount /mnt/wsl/podman-sockets/%[1]s/podman-root.sock +` + +const bindMountUserService = ` +[Unit] +Description=Bind mount for user podman sockets +After=podman.socket + +[Service] +RemainAfterExit=true +Type=oneshot +# Consistency with system service (supports racing) +ExecStartPre=mkdir -p -m 777 /mnt/wsl/podman-sockets +ExecStartPre=mkdir -p -m 777 /mnt/wsl/podman-sockets/%[1]s +ExecStartPre=touch /mnt/wsl/podman-sockets/%[1]s/podman-user.sock +# Relies on /etc/fstab entry for user mounting +ExecStart=mount /mnt/wsl/podman-sockets/%[1]s/podman-user.sock +ExecStop=umount /mnt/wsl/podman-sockets/%[1]s/podman-user.sock +` + +const bindMountFsTab = `/run/user/1000/podman/podman.sock /mnt/wsl/podman-sockets/%s/podman-user.sock none noauto,user,bind,defaults 0 0 +` +const ( + defaultTargetWants = "default.target.wants" + userSystemdPath = "/home/%[1]s/.config/systemd/user" + sysSystemdPath = "/etc/systemd/system" + userSystemdWants = userSystemdPath + "/" + defaultTargetWants + sysSystemdWants = sysSystemdPath + "/" + defaultTargetWants + bindUnitFileName = "podman-mnt-bindings.service" + bindUserUnitPath = userSystemdPath + "/" + bindUnitFileName + bindUserUnitWant = userSystemdWants + "/" + bindUnitFileName + bindSysUnitPath = sysSystemdPath + "/" + bindUnitFileName + bindSysUnitWant = sysSystemdWants + "/" + bindUnitFileName + podmanSocketDropin = "podman.socket.d" + podmanSocketDropinPath = sysSystemdPath + "/" + podmanSocketDropin +) + +const configBindServices = "mkdir -p " + userSystemdWants + " " + sysSystemdWants + " " + podmanSocketDropinPath + "\n" + + "ln -fs " + bindUserUnitPath + " " + bindUserUnitWant + "\n" + + "ln -fs " + bindSysUnitPath + " " + bindSysUnitWant + "\n" + +const overrideSocketGroup = ` +[Socket] +SocketMode=0660 +SocketGroup=wheel +` + +const proxyConfigSetup = `#!/bin/bash + +SYSTEMD_CONF=/etc/systemd/system.conf.d/default-env.conf +ENVD_CONF=/etc/environment.d/default-env.conf +PROFILE_CONF=/etc/profile.d/default-env.sh + +IFS="|" +read proxies + +mkdir -p /etc/profile.d /etc/environment.d /etc/systemd/system.conf.d/ +rm -f $SYSTEMD_CONF +for proxy in $proxies; do + output+="$proxy " +done +echo "[Manager]" >> $SYSTEMD_CONF +echo -ne "DefaultEnvironment=" >> $SYSTEMD_CONF + +echo $output >> $SYSTEMD_CONF +rm -f $ENVD_CONF +for proxy in $proxies; do + echo "$proxy" >> $ENVD_CONF +done +rm -f $PROFILE_CONF +for proxy in $proxies; do + echo "export $proxy" >> $PROFILE_CONF +done +` + +const proxyConfigAttempt = `if [ -f /usr/local/bin/proxyinit ]; \ +then /usr/local/bin/proxyinit; \ +else exit 42; \ +fi` + +const clearProxySettings = `rm -f /etc/systemd/system.conf.d/default-env.conf \ + /etc/environment.d/default-env.conf \ + /etc/profile.d/default-env.sh` + +const wslInstallError = `Could not %s. See previous output for any potential failure details. +If you can not resolve the issue, and rerunning fails, try the "wsl --install" process +outlined in the following article: + +http://docs.microsoft.com/en-us/windows/wsl/install + +` + +const wslKernelError = `Could not %s. See previous output for any potential failure details. +If you can not resolve the issue, try rerunning the "podman machine init command". If that fails +try the "wsl --update" command and then rerun "podman machine init". Finally, if all else fails, +try following the steps outlined in the following article: + +http://docs.microsoft.com/en-us/windows/wsl/install + +` + +const wslInstallKernel = "install the WSL Kernel" + +const wslOldVersion = `Automatic installation of WSL can not be performed on this version of Windows +Either update to Build 19041 (or later), or perform the manual installation steps +outlined in the following article: + +http://docs.microsoft.com/en-us/windows/wsl/install\ + +` + +const ( + gvProxy = "gvproxy.exe" + winSShProxy = "win-sshproxy.exe" + pipePrefix = "npipe:////./pipe/" + globalPipe = "docker_engine" + userModeDist = "podman-net-usermode" + rootfulSock = "/run/podman/podman.sock" + rootlessSock = "/run/user/1000/podman/podman.sock" +) diff --git a/pkg/machine/wsl/fedora.go b/pkg/machine/wsl/fedora.go index 5ad76acb2a..859d4221fa 100644 --- a/pkg/machine/wsl/fedora.go +++ b/pkg/machine/wsl/fedora.go @@ -1,10 +1,9 @@ -//go:build windows - package wsl import ( "errors" "fmt" + "github.com/sirupsen/logrus" "io" "net/http" "net/url" @@ -27,8 +26,10 @@ type FedoraDownload struct { machine.Download } +// NewFedoraDownloader +// deprecated func NewFedoraDownloader(vmType define.VMType, vmName, releaseStream string) (machine.DistributionDownload, error) { - downloadURL, version, arch, size, err := getFedoraDownload() + downloadURL, version, arch, size, err := GetFedoraDownloadForWSL() if err != nil { return nil, err } @@ -82,7 +83,7 @@ func (f FedoraDownload) CleanCache() error { return machine.RemoveImageAfterExpire(f.CacheDir, expire) } -func getFedoraDownload() (*url.URL, string, string, int64, error) { +func GetFedoraDownloadForWSL() (*url.URL, string, string, int64, error) { var releaseURL string arch := machine.DetermineMachineArch() switch arch { @@ -118,11 +119,14 @@ func getFedoraDownload() (*url.URL, string, string, int64, error) { return nil, "", "", -1, fmt.Errorf("get request failed: %s: %w", verURL.String(), err) } - defer resp.Body.Close() - bytes, err := io.ReadAll(&io.LimitedReader{R: resp.Body, N: 1024}) + defer func() { + if err := resp.Body.Close(); err != nil { + logrus.Errorf("error closing http boddy: %q", err) + } + }() + b, err := io.ReadAll(&io.LimitedReader{R: resp.Body, N: 1024}) if err != nil { return nil, "", "", -1, fmt.Errorf("failed reading: %s: %w", verURL.String(), err) } - - return downloadURL, strings.TrimSpace(string(bytes)), arch, contentLen, nil + return downloadURL, strings.TrimSpace(string(b)), arch, contentLen, nil } diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go index 42a6331bbe..e92b39c868 100644 --- a/pkg/machine/wsl/machine.go +++ b/pkg/machine/wsl/machine.go @@ -4,11 +4,9 @@ package wsl import ( "bufio" - "encoding/json" "errors" "fmt" "io" - "net/url" "os" "os/exec" "path/filepath" @@ -16,8 +14,6 @@ import ( "strings" "time" - "github.com/containers/podman/v5/pkg/machine/connection" - "github.com/containers/common/pkg/config" "github.com/containers/podman/v5/pkg/machine" "github.com/containers/podman/v5/pkg/machine/define" @@ -26,7 +22,6 @@ import ( "github.com/containers/podman/v5/pkg/machine/wsl/wutil" "github.com/containers/podman/v5/utils" "github.com/containers/storage/pkg/homedir" - "github.com/containers/storage/pkg/lockfile" "github.com/sirupsen/logrus" "golang.org/x/text/encoding/unicode" "golang.org/x/text/transform" @@ -37,277 +32,6 @@ var ( vmtype = define.WSLVirt ) -const ( - ErrorSuccessRebootInitiated = 1641 - ErrorSuccessRebootRequired = 3010 - currentMachineVersion = 3 -) - -const containersConf = `[containers] - -[engine] -cgroup_manager = "cgroupfs" -` - -const registriesConf = `unqualified-search-registries=["docker.io"] -` - -const appendPort = `grep -q Port\ %d /etc/ssh/sshd_config || echo Port %d >> /etc/ssh/sshd_config` - -const changePort = `sed -E -i 's/^Port[[:space:]]+[0-9]+/Port %d/' /etc/ssh/sshd_config` - -const configServices = `ln -fs /usr/lib/systemd/system/sshd.service /etc/systemd/system/multi-user.target.wants/sshd.service -ln -fs /usr/lib/systemd/system/podman.socket /etc/systemd/system/sockets.target.wants/podman.socket -rm -f /etc/systemd/system/getty.target.wants/console-getty.service -rm -f /etc/systemd/system/getty.target.wants/getty@tty1.service -rm -f /etc/systemd/system/multi-user.target.wants/systemd-resolved.service -rm -f /etc/systemd/system/sysinit.target.wants//systemd-resolved.service -rm -f /etc/systemd/system/dbus-org.freedesktop.resolve1.service -ln -fs /dev/null /etc/systemd/system/console-getty.service -ln -fs /dev/null /etc/systemd/system/systemd-oomd.socket -mkdir -p /etc/systemd/system/systemd-sysusers.service.d/ -echo CREATE_MAIL_SPOOL=no >> /etc/default/useradd -adduser -m [USER] -G wheel -mkdir -p /home/[USER]/.config/systemd/[USER]/ -chown [USER]:[USER] /home/[USER]/.config -` - -const sudoers = `%wheel ALL=(ALL) NOPASSWD: ALL -` - -const bootstrap = `#!/bin/bash -ps -ef | grep -v grep | grep -q systemd && exit 0 -nohup unshare --kill-child --fork --pid --mount --mount-proc --propagation shared /lib/systemd/systemd >/dev/null 2>&1 & -sleep 0.1 -` - -const wslmotd = ` -You will be automatically entered into a nested process namespace where -systemd is running. If you need to access the parent namespace, hit ctrl-d -or type exit. This also means to log out you need to exit twice. - -` - -const sysdpid = "SYSDPID=`ps -eo cmd,pid | grep -m 1 ^/lib/systemd/systemd | awk '{print $2}'`" - -const profile = sysdpid + ` -if [ ! -z "$SYSDPID" ] && [ "$SYSDPID" != "1" ]; then - cat /etc/wslmotd - /usr/local/bin/enterns -fi -` - -const enterns = "#!/bin/bash\n" + sysdpid + ` -if [ ! -z "$SYSDPID" ] && [ "$SYSDPID" != "1" ]; then - NSENTER=("nsenter" "-m" "-p" "-t" "$SYSDPID" "--wd=$PWD") - - if [ "$UID" != "0" ]; then - NSENTER=("sudo" "${NSENTER[@]}") - if [ "$#" != "0" ]; then - NSENTER+=("sudo" "-u" "$USER") - else - NSENTER+=("su" "-l" "$USER") - fi - fi - "${NSENTER[@]}" "$@" -fi` - -const waitTerm = sysdpid + ` -if [ ! -z "$SYSDPID" ]; then - timeout 60 tail -f /dev/null --pid $SYSDPID -fi -` - -const wslConf = `[user] -default=[USER] -` - -const wslConfUserNet = ` -[network] -generateResolvConf = false -` - -const resolvConfUserNet = ` -nameserver 192.168.127.1 -` - -// WSL kernel does not have sg and crypto_user modules -const overrideSysusers = `[Service] -LoadCredential= -` - -const lingerService = `[Unit] -Description=A systemd user unit demo -After=network-online.target -Wants=network-online.target podman.socket -[Service] -ExecStart=/usr/bin/sleep infinity -` - -const lingerSetup = `mkdir -p /home/[USER]/.config/systemd/user/default.target.wants -ln -fs /home/[USER]/.config/systemd/user/linger-example.service \ - /home/[USER]/.config/systemd/user/default.target.wants/linger-example.service -` - -const bindMountSystemService = ` -[Unit] -Description=Bind mount for system podman sockets -After=podman.socket - -[Service] -RemainAfterExit=true -Type=oneshot -# Ensure user services can register sockets as well -ExecStartPre=mkdir -p -m 777 /mnt/wsl/podman-sockets -ExecStartPre=mkdir -p -m 777 /mnt/wsl/podman-sockets/%[1]s -ExecStartPre=touch /mnt/wsl/podman-sockets/%[1]s/podman-root.sock -ExecStart=mount --bind %%t/podman/podman.sock /mnt/wsl/podman-sockets/%[1]s/podman-root.sock -ExecStop=umount /mnt/wsl/podman-sockets/%[1]s/podman-root.sock -` - -const bindMountUserService = ` -[Unit] -Description=Bind mount for user podman sockets -After=podman.socket - -[Service] -RemainAfterExit=true -Type=oneshot -# Consistency with system service (supports racing) -ExecStartPre=mkdir -p -m 777 /mnt/wsl/podman-sockets -ExecStartPre=mkdir -p -m 777 /mnt/wsl/podman-sockets/%[1]s -ExecStartPre=touch /mnt/wsl/podman-sockets/%[1]s/podman-user.sock -# Relies on /etc/fstab entry for user mounting -ExecStart=mount /mnt/wsl/podman-sockets/%[1]s/podman-user.sock -ExecStop=umount /mnt/wsl/podman-sockets/%[1]s/podman-user.sock -` - -const bindMountFsTab = `/run/user/1000/podman/podman.sock /mnt/wsl/podman-sockets/%s/podman-user.sock none noauto,user,bind,defaults 0 0 -` -const ( - defaultTargetWants = "default.target.wants" - userSystemdPath = "/home/%[1]s/.config/systemd/user" - sysSystemdPath = "/etc/systemd/system" - userSystemdWants = userSystemdPath + "/" + defaultTargetWants - sysSystemdWants = sysSystemdPath + "/" + defaultTargetWants - bindUnitFileName = "podman-mnt-bindings.service" - bindUserUnitPath = userSystemdPath + "/" + bindUnitFileName - bindUserUnitWant = userSystemdWants + "/" + bindUnitFileName - bindSysUnitPath = sysSystemdPath + "/" + bindUnitFileName - bindSysUnitWant = sysSystemdWants + "/" + bindUnitFileName - podmanSocketDropin = "podman.socket.d" - podmanSocketDropinPath = sysSystemdPath + "/" + podmanSocketDropin -) - -const configBindServices = "mkdir -p " + userSystemdWants + " " + sysSystemdWants + " " + podmanSocketDropinPath + "\n" + - "ln -fs " + bindUserUnitPath + " " + bindUserUnitWant + "\n" + - "ln -fs " + bindSysUnitPath + " " + bindSysUnitWant + "\n" - -const overrideSocketGroup = ` -[Socket] -SocketMode=0660 -SocketGroup=wheel -` - -const proxyConfigSetup = `#!/bin/bash - -SYSTEMD_CONF=/etc/systemd/system.conf.d/default-env.conf -ENVD_CONF=/etc/environment.d/default-env.conf -PROFILE_CONF=/etc/profile.d/default-env.sh - -IFS="|" -read proxies - -mkdir -p /etc/profile.d /etc/environment.d /etc/systemd/system.conf.d/ -rm -f $SYSTEMD_CONF -for proxy in $proxies; do - output+="$proxy " -done -echo "[Manager]" >> $SYSTEMD_CONF -echo -ne "DefaultEnvironment=" >> $SYSTEMD_CONF - -echo $output >> $SYSTEMD_CONF -rm -f $ENVD_CONF -for proxy in $proxies; do - echo "$proxy" >> $ENVD_CONF -done -rm -f $PROFILE_CONF -for proxy in $proxies; do - echo "export $proxy" >> $PROFILE_CONF -done -` - -const proxyConfigAttempt = `if [ -f /usr/local/bin/proxyinit ]; \ -then /usr/local/bin/proxyinit; \ -else exit 42; \ -fi` - -const clearProxySettings = `rm -f /etc/systemd/system.conf.d/default-env.conf \ - /etc/environment.d/default-env.conf \ - /etc/profile.d/default-env.sh` - -const wslInstallError = `Could not %s. See previous output for any potential failure details. -If you can not resolve the issue, and rerunning fails, try the "wsl --install" process -outlined in the following article: - -http://docs.microsoft.com/en-us/windows/wsl/install - -` - -const wslKernelError = `Could not %s. See previous output for any potential failure details. -If you can not resolve the issue, try rerunning the "podman machine init command". If that fails -try the "wsl --update" command and then rerun "podman machine init". Finally, if all else fails, -try following the steps outlined in the following article: - -http://docs.microsoft.com/en-us/windows/wsl/install - -` - -const wslInstallKernel = "install the WSL Kernel" - -const wslOldVersion = `Automatic installation of WSL can not be performed on this version of Windows -Either update to Build 19041 (or later), or perform the manual installation steps -outlined in the following article: - -http://docs.microsoft.com/en-us/windows/wsl/install\ - -` - -const ( - gvProxy = "gvproxy.exe" - winSShProxy = "win-sshproxy.exe" - pipePrefix = "npipe:////./pipe/" - globalPipe = "docker_engine" - userModeDist = "podman-net-usermode" - rootfulSock = "/run/podman/podman.sock" - rootlessSock = "/run/user/1000/podman/podman.sock" -) - -type MachineVM struct { - // ConfigPath is the path to the configuration file - ConfigPath string - // Created contains the original created time instead of querying the file mod time - Created time.Time - // ImageStream is the version of fcos being used - ImageStream string - // ImagePath is the fq path to - ImagePath string - // LastUp contains the last recorded uptime - LastUp time.Time - // Name of the vm - Name string - // Whether this machine should run in a rootful or rootless manner - Rootful bool - // SSH identity, username, etc - vmconfigs.SSHConfig - // machine version - Version int - // Whether to use user-mode networking - UserModeNetworking bool - // Used at runtime for serializing write operations - lock *lockfile.LockFile -} - type ExitCodeError struct { code uint } @@ -329,153 +53,13 @@ func getConfigPathExt(name string, extension string) (string, error) { return filepath.Join(vmConfigDir, fmt.Sprintf("%s.%s", name, extension)), nil } -// readAndMigrate returns the content of the VM's -// configuration file in json -func readAndMigrate(configPath string, name string) (*MachineVM, error) { - vm := new(MachineVM) - b, err := os.ReadFile(configPath) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - return nil, fmt.Errorf("%v: %w", name, define.ErrNoSuchVM) - } - return vm, err - } - err = json.Unmarshal(b, vm) - if err == nil && vm.Version < currentMachineVersion { - err = vm.migrateMachine(configPath) - } - - return vm, err -} - -func (v *MachineVM) migrateMachine(configPath string) error { - if v.Created.IsZero() { - if err := v.migrate40(configPath); err != nil { - return err - } - } - - // Update older machines to use lingering - if err := enableUserLinger(v, toDist(v.Name)); err != nil { - return err - } - - // Update older machines missing unqualified search config - if err := configureRegistries(v, toDist(v.Name)); err != nil { - return err - } - - v.Version = currentMachineVersion - return v.writeConfig() -} - -func (v *MachineVM) migrate40(configPath string) error { - v.ConfigPath = configPath - fi, err := os.Stat(configPath) - if err != nil { - return err - } - v.Created = fi.ModTime() - v.LastUp = getLegacyLastStart(v) - return nil -} - -func getLegacyLastStart(vm *MachineVM) time.Time { - vmDataDir, err := machine.GetDataDir(vmtype) - if err != nil { - return vm.Created - } - distDir := filepath.Join(vmDataDir, "wsldist") - start := filepath.Join(distDir, vm.Name, "laststart") - info, err := os.Stat(start) - if err != nil { - return vm.Created - } - return info.ModTime() -} - -// Init writes the json configuration file to the filesystem for -// other verbs (start, stop) -func (v *MachineVM) Init(opts define.InitOptions) (bool, error) { - var ( - err error - ) - // cleanup half-baked files if init fails at any point - callbackFuncs := machine.InitCleanup() - defer callbackFuncs.CleanIfErr(&err) - go callbackFuncs.CleanOnSignal() - - if cont, err := checkAndInstallWSL(opts); !cont { - appendOutputIfError(opts.ReExec, err) - return cont, err - } - - _ = setupWslProxyEnv() - v.IdentityPath, err = machine.GetSSHIdentityPath(define.DefaultIdentityName) - if err != nil { - return false, err - } - v.Rootful = opts.Rootful - v.Version = currentMachineVersion - - if v.UserModeNetworking { - if err = verifyWSLUserModeCompat(); err != nil { - return false, err - } - } - - if err = downloadDistro(v, opts); err != nil { - return false, err - } - callbackFuncs.Add(v.removeMachineImage) - - const prompt = "Importing operating system into WSL (this may take a few minutes on a new WSL install)..." - dist, err := provisionWSLDist(v.Name, v.ImagePath, prompt) - if err != nil { - return false, err - } - callbackFuncs.Add(v.unprovisionWSL) - - if v.UserModeNetworking { - if err = installUserModeDist(dist, v.ImagePath); err != nil { - _ = unregisterDist(dist) - return false, err - } - } - - fmt.Println("Configuring system...") - if err = configureSystem(v, dist); err != nil { - return false, err - } - - if err = installScripts(dist); err != nil { - return false, err - } - - if err = createKeys(v, dist); err != nil { - return false, err - } - - // Cycle so that user change goes into effect - _ = terminateDist(dist) - - if err = v.writeConfig(); err != nil { - return false, err - } - callbackFuncs.Add(v.removeMachineConfig) - - if err = setupConnections(v, opts); err != nil { - return false, err - } - callbackFuncs.Add(v.removeSystemConnections) - return true, nil -} - -func (v *MachineVM) unprovisionWSL() error { - if err := terminateDist(toDist(v.Name)); err != nil { +// TODO like provisionWSL, i think this needs to be pushed to use common +// paths and types where possible +func unprovisionWSL(mc *vmconfigs.MachineConfig) error { + if err := terminateDist(mc.Name); err != nil { logrus.Error(err) } - if err := unregisterDist(toDist(v.Name)); err != nil { + if err := unregisterDist(mc.Name); err != nil { logrus.Error(err) } @@ -484,84 +68,13 @@ func (v *MachineVM) unprovisionWSL() error { return err } distDir := filepath.Join(vmDataDir, "wsldist") - distTarget := filepath.Join(distDir, v.Name) + distTarget := filepath.Join(distDir, mc.Name) return utils.GuardedRemoveAll(distTarget) } -func (v *MachineVM) removeMachineConfig() error { - return utils.GuardedRemoveAll(v.ConfigPath) -} - -func (v *MachineVM) removeMachineImage() error { - return utils.GuardedRemoveAll(v.ImagePath) -} - -func (v *MachineVM) removeSystemConnections() error { - return connection.RemoveConnections(v.Name, fmt.Sprintf("%s-root", v.Name)) -} - -func downloadDistro(v *MachineVM, opts define.InitOptions) error { - var ( - dd machine.DistributionDownload - err error - ) - - // The default FCOS stream names are reserved indicators for the standard Fedora fetch - if machine.IsValidFCOSStreamString(opts.ImagePath) { - v.ImageStream = opts.ImagePath - dd, err = NewFedoraDownloader(vmtype, v.Name, opts.ImagePath) - } else { - v.ImageStream = "custom" - dd, err = machine.NewGenericDownloader(vmtype, v.Name, opts.ImagePath) - } - if err != nil { - return err - } - - v.ImagePath = dd.Get().LocalUncompressedFile - return machine.DownloadImage(dd) -} - -func (v *MachineVM) writeConfig() error { - return machine.WriteConfig(v.ConfigPath, v) -} - -func constructSSHUris(v *MachineVM) ([]url.URL, []string) { - uri := connection.SSHRemoteConnection.MakeSSHURL(connection.LocalhostIP, rootlessSock, strconv.Itoa(v.Port), v.RemoteUsername) - uriRoot := connection.SSHRemoteConnection.MakeSSHURL(connection.LocalhostIP, rootfulSock, strconv.Itoa(v.Port), "root") - - uris := []url.URL{uri, uriRoot} - names := []string{v.Name, v.Name + "-root"} - - return uris, names -} - -func setupConnections(v *MachineVM, opts define.InitOptions) error { - uris, names := constructSSHUris(v) - - // The first connection defined when connections is empty will become the default - // regardless of IsDefault, so order according to rootful - if opts.Rootful { - uris[0], names[0], uris[1], names[1] = uris[1], names[1], uris[0], names[0] - } - - // We need to prevent racing connection updates to containers.conf globally - // across all backends to prevent connection overwrites - flock, err := obtainGlobalConfigLock() - if err != nil { - return fmt.Errorf("could not obtain global lock: %w", err) - } - defer flock.unlock() - - for i := 0; i < 2; i++ { - if err := connection.AddConnection(&uris[i], names[i], v.IdentityPath, opts.IsDefault && i == 0); err != nil { - return err - } - } - - return nil -} - +// TODO there are some differences here that I dont fully groak but I think +// we should push this stuff be more common (dir names, etc) and also use +// typed things where possible like vmfiles func provisionWSLDist(name string, imagePath string, prompt string) (string, error) { vmDataDir, err := machine.GetDataDir(vmtype) if err != nil { @@ -574,32 +87,37 @@ func provisionWSLDist(name string, imagePath string, prompt string) (string, err return "", fmt.Errorf("could not create wsldist directory: %w", err) } - dist := toDist(name) fmt.Println(prompt) - if err = runCmdPassThrough("wsl", "--import", dist, distTarget, imagePath, "--version", "2"); err != nil { + if err = runCmdPassThrough("wsl", "--import", name, distTarget, imagePath, "--version", "2"); err != nil { return "", fmt.Errorf("the WSL import of guest OS failed: %w", err) } // Fixes newuidmap - if err = wslInvoke(dist, "rpm", "--restore", "shadow-utils"); err != nil { + if err = wslInvoke(name, "rpm", "--restore", "shadow-utils"); err != nil { return "", fmt.Errorf("package permissions restore of shadow-utils on guest OS failed: %w", err) } - return dist, nil + return name, nil } -func createKeys(v *MachineVM, dist string) error { - user := v.RemoteUsername +func createKeys(mc *vmconfigs.MachineConfig, dist string) error { + user := mc.SSH.RemoteUsername if err := terminateDist(dist); err != nil { return fmt.Errorf("could not cycle WSL dist: %w", err) } - key, err := wslCreateKeys(v.IdentityPath, dist) + identityPath := mc.SSH.IdentityPath + ".pub" + + // TODO We could audit vmfile reads and see if a 'ReadToString' + // method makes sense. + pubKey, err := os.ReadFile(identityPath) if err != nil { return fmt.Errorf("could not create ssh keys: %w", err) } + key := string(pubKey) + if err := wslPipe(key+"\n", dist, "sh", "-c", "mkdir -p /root/.ssh;"+ "cat >> /root/.ssh/authorized_keys; chmod 600 /root/.ssh/authorized_keys"); err != nil { return fmt.Errorf("could not create root authorized keys on guest OS: %w", err) @@ -609,37 +127,37 @@ func createKeys(v *MachineVM, dist string) error { "cat >> /home/[USER]/.ssh/authorized_keys; chown -R [USER]:[USER] /home/[USER]/.ssh;"+ "chmod 600 /home/[USER]/.ssh/authorized_keys", user) if err := wslPipe(key+"\n", dist, "sh", "-c", userAuthCmd); err != nil { - return fmt.Errorf("could not create '%s' authorized keys on guest OS: %w", v.RemoteUsername, err) + return fmt.Errorf("could not create '%s' authorized keys on guest OS: %w", user, err) } return nil } -func configureSystem(v *MachineVM, dist string) error { - user := v.RemoteUsername - if err := wslInvoke(dist, "sh", "-c", fmt.Sprintf(appendPort, v.Port, v.Port)); err != nil { +func configureSystem(mc *vmconfigs.MachineConfig, dist string) error { + user := mc.SSH.RemoteUsername + if err := wslInvoke(dist, "sh", "-c", fmt.Sprintf(appendPort, mc.SSH.Port, mc.SSH.Port)); err != nil { return fmt.Errorf("could not configure SSH port for guest OS: %w", err) } - if err := wslPipe(withUser(configServices, user), dist, "sh"); err != nil { + if err := wslPipe(withUser(configServices, user), mc.Name, "sh"); err != nil { return fmt.Errorf("could not configure systemd settings for guest OS: %w", err) } - if err := wslPipe(sudoers, dist, "sh", "-c", "cat >> /etc/sudoers"); err != nil { + if err := wslPipe(sudoers, mc.Name, "sh", "-c", "cat >> /etc/sudoers"); err != nil { return fmt.Errorf("could not add wheel to sudoers: %w", err) } - if err := wslPipe(overrideSysusers, dist, "sh", "-c", + if err := wslPipe(overrideSysusers, mc.Name, "sh", "-c", "cat > /etc/systemd/system/systemd-sysusers.service.d/override.conf"); err != nil { return fmt.Errorf("could not generate systemd-sysusers override for guest OS: %w", err) } lingerCmd := withUser("cat > /home/[USER]/.config/systemd/[USER]/linger-example.service", user) - if err := wslPipe(lingerService, dist, "sh", "-c", lingerCmd); err != nil { + if err := wslPipe(lingerService, mc.Name, "sh", "-c", lingerCmd); err != nil { return fmt.Errorf("could not generate linger service for guest OS: %w", err) } - if err := enableUserLinger(v, dist); err != nil { + if err := enableUserLinger(mc, dist); err != nil { return err } @@ -651,11 +169,11 @@ func configureSystem(v *MachineVM, dist string) error { return fmt.Errorf("could not create containers.conf for guest OS: %w", err) } - if err := configureRegistries(v, dist); err != nil { + if err := configureRegistries(dist); err != nil { return err } - if err := v.setupPodmanDockerSock(dist, v.Rootful); err != nil { + if err := setupPodmanDockerSock(dist, mc.HostUser.Rootful); err != nil { return err } @@ -667,7 +185,7 @@ func configureSystem(v *MachineVM, dist string) error { return err } - return changeDistUserModeNetworking(dist, user, v.ImagePath, v.UserModeNetworking) + return changeDistUserModeNetworking(dist, user, mc.ImagePath.GetPath(), mc.WSLHypervisor.UserModeNetworking) } func configureBindMounts(dist string, user string) error { @@ -712,7 +230,7 @@ func getBindMountFsTab(dist string) string { return fmt.Sprintf(bindMountFsTab, dist) } -func (v *MachineVM) setupPodmanDockerSock(dist string, rootful bool) error { +func setupPodmanDockerSock(dist string, rootful bool) error { content := ignition.GetPodmanDockerTmpConfig(1000, rootful, true) if err := wslPipe(content, dist, "sh", "-c", "cat > "+ignition.PodmanDockerTmpConfPath); err != nil { @@ -757,8 +275,8 @@ func configureProxy(dist string, useProxy bool, quiet bool) error { return nil } -func enableUserLinger(v *MachineVM, dist string) error { - lingerCmd := "mkdir -p /var/lib/systemd/linger; touch /var/lib/systemd/linger/" + v.RemoteUsername +func enableUserLinger(mc *vmconfigs.MachineConfig, dist string) error { + lingerCmd := "mkdir -p /var/lib/systemd/linger; touch /var/lib/systemd/linger/" + mc.SSH.RemoteUsername if err := wslInvoke(dist, "sh", "-c", lingerCmd); err != nil { return fmt.Errorf("could not enable linger for remote user on guest OS: %w", err) } @@ -766,7 +284,7 @@ func enableUserLinger(v *MachineVM, dist string) error { return nil } -func configureRegistries(v *MachineVM, dist string) error { +func configureRegistries(dist string) error { cmd := "cat > /etc/containers/registries.conf.d/999-podman-machine.conf" if err := wslPipe(registriesConf, dist, "sh", "-c", cmd); err != nil { return fmt.Errorf("could not configure registries on guest OS: %w", err) @@ -811,7 +329,7 @@ func writeWslConf(dist string, user string) error { return nil } -func checkAndInstallWSL(opts define.InitOptions) (bool, error) { +func checkAndInstallWSL(reExec bool) (bool, error) { if wutil.IsWSLInstalled() { return true, nil } @@ -819,11 +337,11 @@ func checkAndInstallWSL(opts define.InitOptions) (bool, error) { admin := hasAdminRights() if !IsWSLFeatureEnabled() { - return false, attemptFeatureInstall(opts, admin) + return false, attemptFeatureInstall(reExec, admin) } skip := false - if !opts.ReExec && !admin { + if reExec && !admin { fmt.Println("Launching WSL Kernel Install...") if err := launchElevate(wslInstallKernel); err != nil { return false, err @@ -838,7 +356,7 @@ func checkAndInstallWSL(opts define.InitOptions) (bool, error) { return false, err } - if opts.ReExec { + if reExec { return false, nil } } @@ -846,7 +364,7 @@ func checkAndInstallWSL(opts define.InitOptions) (bool, error) { return true, nil } -func attemptFeatureInstall(opts define.InitOptions, admin bool) error { +func attemptFeatureInstall(reExec, admin bool) error { if !winVersionAtLeast(10, 0, 18362) { return errors.New("your version of Windows does not support WSL. Update to Windows 10 Build 19041 or later") } else if !winVersionAtLeast(10, 0, 19041) { @@ -864,11 +382,11 @@ func attemptFeatureInstall(opts define.InitOptions, admin bool) error { message += "NOTE: A system reboot will be required as part of this process. " + "If you prefer, you may abort now, and perform a manual installation using the \"wsl --install\" command." - if !opts.ReExec && MessageBox(message, "Podman Machine", false) != 1 { + if reExec && MessageBox(message, "Podman Machine", false) != 1 { return errors.New("the WSL installation aborted") } - if !opts.ReExec && !admin { + if reExec && !admin { return launchElevate("install the Windows WSL Features") } @@ -1028,12 +546,6 @@ func isMsiError(err error) bool { return true } -func toDist(name string) string { - if !strings.HasPrefix(name, "podman") { - name = "podman-" + name - } - return name -} func withUser(s string, user string) string { return strings.ReplaceAll(s, "[USER]", user) @@ -1105,126 +617,6 @@ func setupWslProxyEnv() (hasProxy bool) { return } -func (v *MachineVM) Set(_ string, opts define.SetOptions) ([]error, error) { - // If one setting fails to be applied, the others settings will not fail and still be applied. - // The setting(s) that failed to be applied will have its errors returned in setErrors - var setErrors []error - - v.lock.Lock() - defer v.lock.Unlock() - - if opts.Rootful != nil && v.Rootful != *opts.Rootful { - err := v.setRootful(*opts.Rootful) - if err != nil { - setErrors = append(setErrors, fmt.Errorf("setting rootful option: %w", err)) - } else { - if v.isRunning() { - logrus.Warn("restart is necessary for rootful change to go into effect") - } - v.Rootful = *opts.Rootful - } - } - - if opts.CPUs != nil { - setErrors = append(setErrors, errors.New("changing CPUs not supported for WSL machines")) - } - - if opts.Memory != nil { - setErrors = append(setErrors, errors.New("changing memory not supported for WSL machines")) - } - - if opts.USBs != nil { - setErrors = append(setErrors, errors.New("changing USBs not supported for WSL machines")) - } - - if opts.DiskSize != nil { - setErrors = append(setErrors, errors.New("changing disk size not supported for WSL machines")) - } - - if opts.UserModeNetworking != nil && *opts.UserModeNetworking != v.UserModeNetworking { - update := true - if v.isRunning() { - update = false - setErrors = append(setErrors, fmt.Errorf("user-mode networking can only be changed when the machine is not running")) - } else { - dist := toDist(v.Name) - if err := changeDistUserModeNetworking(dist, v.RemoteUsername, v.ImagePath, *opts.UserModeNetworking); err != nil { - update = false - setErrors = append(setErrors, err) - } - } - - if update { - v.UserModeNetworking = *opts.UserModeNetworking - } - } - err := v.writeConfig() - if err != nil { - setErrors = append(setErrors, err) - } - - if len(setErrors) > 0 { - return setErrors, setErrors[0] - } - return setErrors, nil -} - -func (v *MachineVM) Start(name string, opts machine.StartOptions) error { - v.lock.Lock() - defer v.lock.Unlock() - - if v.isRunning() { - return define.ErrVMAlreadyRunning - } - - dist := toDist(name) - useProxy := setupWslProxyEnv() - if err := configureProxy(dist, useProxy, opts.Quiet); err != nil { - return err - } - - if !machine.IsLocalPortAvailable(v.Port) { - logrus.Warnf("SSH port conflict detected, reassigning a new port") - if err := v.reassignSshPort(); err != nil { - return err - } - } - - // Startup user-mode networking if enabled - if err := v.startUserModeNetworking(); err != nil { - return err - } - - err := wslInvoke(dist, "/root/bootstrap") - if err != nil { - return fmt.Errorf("the WSL bootstrap script failed: %w", err) - } - - if !v.Rootful && !opts.NoInfo { - 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) - } - winProxyOpts := machine.WinProxyOpts{ - Name: v.Name, - IdentityPath: v.IdentityPath, - Port: v.Port, - RemoteUsername: v.RemoteUsername, - Rootful: v.Rootful, - VMType: vmtype, - } - machine.LaunchWinProxy(winProxyOpts, opts.NoInfo) - - _, _, err = v.updateTimeStamps(true) - return err -} - func obtainGlobalConfigLock() (*fileLock, error) { lockDir, err := machine.GetGlobalDataDir() if err != nil { @@ -1236,63 +628,6 @@ func obtainGlobalConfigLock() (*fileLock, error) { return lockFile(filepath.Join(lockDir, "podman-config.lck")) } -func (v *MachineVM) reassignSshPort() error { - dist := toDist(v.Name) - newPort, err := machine.AllocateMachinePort() - if err != nil { - return err - } - - success := false - defer func() { - if !success { - if err := machine.ReleaseMachinePort(newPort); err != nil { - logrus.Warnf("could not release port allocation as part of failure rollback (%d): %w", newPort, err) - } - } - }() - - // We need to prevent racing connection updates to containers.conf globally - // across all backends to prevent connection overwrites - flock, err := obtainGlobalConfigLock() - if err != nil { - return fmt.Errorf("could not obtain global lock: %w", err) - } - defer flock.unlock() - - // Write a transient invalid port, to force a retry on failure - oldPort := v.Port - v.Port = 0 - if err := v.writeConfig(); err != nil { - return err - } - - if err := machine.ReleaseMachinePort(oldPort); err != nil { - logrus.Warnf("could not release current ssh port allocation (%d): %w", oldPort, err) - } - - if err := wslInvoke(dist, "sh", "-c", fmt.Sprintf(changePort, newPort)); err != nil { - return fmt.Errorf("could not change SSH port for guest OS: %w", err) - } - - v.Port = newPort - uris, names := constructSSHUris(v) - for i := 0; i < 2; i++ { - if err := connection.ChangeConnectionURI(names[i], &uris[i]); err != nil { - return err - } - } - - // Write updated port back - if err := v.writeConfig(); err != nil { - return err - } - - success = true - - return nil -} - func IsWSLFeatureEnabled() bool { return wutil.SilentExec("wsl", "--set-default-version", "2") == nil } @@ -1368,58 +703,6 @@ func isSystemdRunning(dist string) (bool, error) { return result, nil } -func (v *MachineVM) Stop(name string, _ machine.StopOptions) error { - v.lock.Lock() - defer v.lock.Unlock() - - dist := toDist(v.Name) - - wsl, err := isWSLRunning(dist) - if err != nil { - return err - } - - sysd := false - if wsl { - sysd, err = isSystemdRunning(dist) - if err != nil { - return err - } - } - - if !wsl || !sysd { - return nil - } - - // Stop user-mode networking if enabled - if err := v.stopUserModeNetworking(dist); err != nil { - fmt.Fprintf(os.Stderr, "Could not cleanly stop user-mode networking: %s\n", err.Error()) - } - - _, _, _ = v.updateTimeStamps(true) - - if err := machine.StopWinProxy(v.Name, vmtype); err != nil { - fmt.Fprintf(os.Stderr, "Could not stop API forwarding service (win-sshproxy.exe): %s\n", err.Error()) - } - - cmd := exec.Command("wsl", "-u", "root", "-d", dist, "sh") - cmd.Stdin = strings.NewReader(waitTerm) - if err = cmd.Start(); err != nil { - return fmt.Errorf("executing wait command: %w", err) - } - - exitCmd := exec.Command("wsl", "-u", "root", "-d", dist, "/usr/local/bin/enterns", "systemctl", "exit", "0") - if err = exitCmd.Run(); err != nil { - return fmt.Errorf("stopping sysd: %w", err) - } - - if err = cmd.Wait(); err != nil { - return err - } - - return terminateDist(dist) -} - func terminateDist(dist string) error { cmd := exec.Command("wsl", "--terminate", dist) return cmd.Run() @@ -1430,145 +713,31 @@ func unregisterDist(dist string) error { return cmd.Run() } -func (v *MachineVM) State(bypass bool) (define.Status, error) { - if v.isRunning() { - return define.Running, nil - } - - return define.Stopped, nil -} - -//nolint:cyclop -func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) { - var files []string - - if v.isRunning() { - if !opts.Force { - return "", nil, &define.ErrVMRunningCannotDestroyed{Name: v.Name} - } - if err := v.Stop(v.Name, machine.StopOptions{}); err != nil { - return "", nil, err - } - } - - v.lock.Lock() - defer v.lock.Unlock() - - // Collect all the files that need to be destroyed - if !opts.SaveImage { - files = append(files, v.ImagePath) - } - - vmConfigDir, err := machine.GetConfDir(vmtype) +func isRunning(name string) (bool, error) { + wsl, err := isWSLRunning(name) if err != nil { - return "", nil, err - } - files = append(files, filepath.Join(vmConfigDir, v.Name+".json")) - - vmDataDir, err := machine.GetDataDir(vmtype) - if err != nil { - return "", nil, err - } - files = append(files, filepath.Join(vmDataDir, "wsldist", v.Name)) - - confirmationMessage := "\nThe following files will be deleted:\n\n" - for _, msg := range files { - confirmationMessage += msg + "\n" - } - - confirmationMessage += "\n" - return confirmationMessage, func() error { - if err := connection.RemoveConnections(v.Name, v.Name+"-root"); err != nil { - logrus.Error(err) - } - if err := runCmdPassThrough("wsl", "--unregister", toDist(v.Name)); err != nil { - logrus.Error(err) - } - for _, f := range files { - if err := utils.GuardedRemoveAll(f); err != nil { - logrus.Error(err) - } - } - if err := machine.ReleaseMachinePort(v.Port); err != nil { - logrus.Warnf("could not release port allocation as part of removal (%d): %w", v.Port, err) - } - - return nil - }, nil -} - -func (v *MachineVM) isRunning() bool { - dist := toDist(v.Name) - - wsl, err := isWSLRunning(dist) - if err != nil { - return false + return false, err } sysd := false if wsl { - sysd, err = isSystemdRunning(dist) + sysd, err = isSystemdRunning(name) if err != nil { - return false + return false, err } } - return sysd + return sysd, err } -// SSH opens an interactive SSH session to the vm specified. -// Added ssh function to VM interface: pkg/machine/config/go : line 58 -func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error { - if !v.isRunning() { - return fmt.Errorf("vm %q is not running.", v.Name) - } - - username := opts.Username - if username == "" { - username = v.RemoteUsername - } - - sshDestination := username + "@localhost" - port := strconv.Itoa(v.Port) - - args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, - "-o", "IdentitiesOnly yes", - "-o", "UserKnownHostsFile /dev/null", - "-o", "StrictHostKeyChecking no"} - if len(opts.Args) > 0 { - args = append(args, opts.Args...) - } else { - fmt.Printf("Connecting to vm %s. To close connection, use `~.` or `exit`\n", v.Name) - } - - cmd := exec.Command("ssh", args...) - logrus.Debugf("Executing: ssh %v\n", args) - - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Stdin = os.Stdin - - return cmd.Run() -} - -func (v *MachineVM) updateTimeStamps(updateLast bool) (time.Time, time.Time, error) { - var err error - if updateLast { - v.LastUp = time.Now() - err = v.writeConfig() - } - - return v.Created, v.LastUp, err -} - -func getDiskSize(vm *MachineVM) uint64 { +func getDiskSize(name string) uint64 { vmDataDir, err := machine.GetDataDir(vmtype) if err != nil { return 0 } distDir := filepath.Join(vmDataDir, "wsldist") - disk := filepath.Join(distDir, vm.Name, "ext4.vhdx") + disk := filepath.Join(distDir, name, "ext4.vhdx") info, err := os.Stat(disk) if err != nil { return 0 @@ -1576,12 +745,11 @@ func getDiskSize(vm *MachineVM) uint64 { return uint64(info.Size()) } -func getCPUs(vm *MachineVM) (uint64, error) { - dist := toDist(vm.Name) - if run, _ := isWSLRunning(dist); !run { +func getCPUs(name string) (uint64, error) { + if run, _ := isWSLRunning(name); !run { return 0, nil } - cmd := exec.Command("wsl", "-u", "root", "-d", dist, "nproc") + cmd := exec.Command("wsl", "-u", "root", "-d", name, "nproc") out, err := cmd.StdoutPipe() if err != nil { return 0, err @@ -1600,12 +768,11 @@ func getCPUs(vm *MachineVM) (uint64, error) { return uint64(ret), err } -func getMem(vm *MachineVM) (uint64, error) { - dist := toDist(vm.Name) - if run, _ := isWSLRunning(dist); !run { +func getMem(name string) (uint64, error) { + if run, _ := isWSLRunning(name); !run { return 0, nil } - cmd := exec.Command("wsl", "-u", "root", "-d", dist, "cat", "/proc/meminfo") + cmd := exec.Command("wsl", "-u", "root", "-d", name, "cat", "/proc/meminfo") out, err := cmd.StdoutPipe() if err != nil { return 0, err @@ -1636,48 +803,9 @@ func getMem(vm *MachineVM) (uint64, error) { return total - available, err } -func (v *MachineVM) setRootful(rootful bool) error { - if err := machine.SetRootful(rootful, v.Name, v.Name+"-root"); err != nil { - return err - } - - dist := toDist(v.Name) - return v.setupPodmanDockerSock(dist, rootful) -} - -// Inspect returns verbose detail about the machine -func (v *MachineVM) Inspect() (*machine.InspectInfo, error) { - state, err := v.State(false) - if err != nil { - return nil, err - } - - connInfo := new(machine.ConnectionConfig) - machinePipe := toDist(v.Name) - connInfo.PodmanPipe = &define.VMFile{Path: `\\.\pipe\` + machinePipe} - - created, lastUp, _ := v.updateTimeStamps(state == define.Running) - return &machine.InspectInfo{ - ConfigPath: define.VMFile{Path: v.ConfigPath}, - ConnectionInfo: *connInfo, - Created: created, - Image: machine.ImageConfig{ - ImagePath: define.VMFile{Path: v.ImagePath}, - ImageStream: v.ImageStream, - }, - LastUp: lastUp, - Name: v.Name, - Resources: v.getResources(), - SSHConfig: v.SSHConfig, - State: state, - UserModeNetworking: v.UserModeNetworking, - Rootful: v.Rootful, - }, nil -} - -func (v *MachineVM) getResources() (resources vmconfigs.ResourceConfig) { - resources.CPUs, _ = getCPUs(v) - resources.Memory, _ = getMem(v) - resources.DiskSize = getDiskSize(v) +func getResources(mc *vmconfigs.MachineConfig) (resources vmconfigs.ResourceConfig) { + resources.CPUs, _ = getCPUs(mc.Name) + resources.Memory, _ = getMem(mc.Name) + resources.DiskSize = getDiskSize(mc.Name) return } diff --git a/pkg/machine/wsl/stubber.go b/pkg/machine/wsl/stubber.go new file mode 100644 index 0000000000..25347678cf --- /dev/null +++ b/pkg/machine/wsl/stubber.go @@ -0,0 +1,362 @@ +//go:build windows + +package wsl + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/containers/podman/v5/pkg/machine/ocipull" + "github.com/containers/podman/v5/pkg/machine/stdpull" + + gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types" + "github.com/containers/podman/v5/pkg/machine" + "github.com/containers/podman/v5/pkg/machine/define" + "github.com/containers/podman/v5/pkg/machine/ignition" + "github.com/containers/podman/v5/pkg/machine/vmconfigs" + "github.com/sirupsen/logrus" +) + +type WSLStubber struct { + vmconfigs.WSLConfig +} + +func (w WSLStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.MachineConfig, _ *ignition.IgnitionBuilder) error { + var ( + err error + ) + // cleanup half-baked files if init fails at any point + callbackFuncs := machine.InitCleanup() + defer callbackFuncs.CleanIfErr(&err) + go callbackFuncs.CleanOnSignal() + mc.WSLHypervisor = new(vmconfigs.WSLConfig) + // TODO + // USB opts are unsupported in WSL. Need to account for that here + // or up the stack + // if len(opts.USBs) > 0 { + // return nil, fmt.Errorf("USB host passthrough is not supported for WSL machines") + // } + + if cont, err := checkAndInstallWSL(opts.ReExec); !cont { + appendOutputIfError(opts.ReExec, err) + return err + } + + _ = setupWslProxyEnv() + + if opts.UserModeNetworking { + if err = verifyWSLUserModeCompat(); err != nil { + return err + } + mc.WSLHypervisor.UserModeNetworking = true + } + + const prompt = "Importing operating system into WSL (this may take a few minutes on a new WSL install)..." + dist, err := provisionWSLDist(mc.Name, mc.ImagePath.GetPath(), prompt) + if err != nil { + return err + } + + unprovisionCallbackFunc := func() error { + return unprovisionWSL(mc) + } + callbackFuncs.Add(unprovisionCallbackFunc) + + if mc.WSLHypervisor.UserModeNetworking { + if err = installUserModeDist(dist, mc.ImagePath.GetPath()); err != nil { + _ = unregisterDist(dist) + return err + } + } + + fmt.Println("Configuring system...") + if err = configureSystem(mc, dist); err != nil { + return err + } + + if err = installScripts(dist); err != nil { + return err + } + + if err = createKeys(mc, dist); err != nil { + return err + } + + // recycle vm + return terminateDist(dist) +} + +func (w WSLStubber) PrepareIgnition(_ *vmconfigs.MachineConfig, _ *ignition.IgnitionBuilder) (*ignition.ReadyUnitOpts, error) { + return nil, nil +} + +func (w WSLStubber) GetHyperVisorVMs() ([]string, error) { + vms, err := getAllWSLDistros(false) + if err != nil { + return nil, err + } + wslVMs := make([]string, 0) + for name := range vms { + wslVMs = append(wslVMs, name) + } + return wslVMs, nil +} + +func (w WSLStubber) MountType() vmconfigs.VolumeMountType { + //TODO implement me + panic("implement me") +} + +func (w WSLStubber) MountVolumesToVM(mc *vmconfigs.MachineConfig, quiet bool) error { + return nil +} + +func (w WSLStubber) Remove(mc *vmconfigs.MachineConfig) ([]string, func() error, error) { + // Note: we could consider swapping the two conditionals + // below if we wanted to hard error on the wsl unregister + // of the vm + wslRemoveFunc := func() error { + if err := runCmdPassThrough("wsl", "--unregister", mc.Name); err != nil { + logrus.Error(err) + } + return machine.ReleaseMachinePort(mc.SSH.Port) + } + + return []string{}, wslRemoveFunc, nil +} + +func (w WSLStubber) RemoveAndCleanMachines(_ *define.MachineDirs) error { + return nil +} + + +func (w WSLStubber) SetProviderAttrs(mc *vmconfigs.MachineConfig, opts define.SetOptions) error { + mc.Lock() + defer mc.Unlock() + + // TODO the check for running when setting rootful is something I have not + // seen in the other distributions. I wonder if this is true everywhere or just + // with WSL? + // TODO maybe the "rule" for set is that it must be done when the machine is + // stopped? + // if opts.Rootful != nil && v.Rootful != *opts.Rootful { + // err := v.setRootful(*opts.Rootful) + // if err != nil { + // setErrors = append(setErrors, fmt.Errorf("setting rootful option: %w", err)) + // } else { + // if v.isRunning() { + // logrus.Warn("restart is necessary for rootful change to go into effect") + // } + // v.Rootful = *opts.Rootful + // } + // } + + if opts.Rootful != nil && mc.HostUser.Rootful != *opts.Rootful { + if err := mc.SetRootful(*opts.Rootful); err != nil { + return err + } + } + + if opts.CPUs != nil { + return errors.New("changing CPUs not supported for WSL machines") + } + + if opts.Memory != nil { + return errors.New("changing memory not supported for WSL machines") + } + + // TODO USB still needs to be plumbed for all providers + // if USBs != nil { + // setErrors = append(setErrors, errors.New("changing USBs not supported for WSL machines")) + // } + + if opts.DiskSize != nil { + return errors.New("changing disk size not supported for WSL machines") + } + + // TODO This needs to be plumbed in for set as well + //if opts.UserModeNetworking != nil && *opts.UserModeNetworking != v.UserModeNetworking { + // update := true + // + // if v.isRunning() { + // update = false + // setErrors = append(setErrors, fmt.Errorf("user-mode networking can only be changed when the machine is not running")) + // } else { + // dist := toDist(v.Name) + // if err := changeDistUserModeNetworking(dist, v.RemoteUsername, v.ImagePath, *opts.UserModeNetworking); err != nil { + // update = false + // setErrors = append(setErrors, err) + // } + // } + // + // if update { + // v.UserModeNetworking = *opts.UserModeNetworking + // } + return nil +} + +func (w WSLStubber) StartNetworking(mc *vmconfigs.MachineConfig, cmd *gvproxy.GvproxyCommand) error { + // Startup user-mode networking if enabled + if mc.WSLHypervisor.UserModeNetworking { + return startUserModeNetworking(mc) + } + return nil +} + +func (w WSLStubber) UserModeNetworkEnabled(mc *vmconfigs.MachineConfig) bool { + return mc.WSLHypervisor.UserModeNetworking +} + +func (w WSLStubber) PostStartNetworking(mc *vmconfigs.MachineConfig) error { + if mc.WSLHypervisor.UserModeNetworking { + winProxyOpts := machine.WinProxyOpts{ + Name: mc.Name, + IdentityPath: mc.SSH.IdentityPath, + Port: mc.SSH.Port, + RemoteUsername: mc.SSH.RemoteUsername, + Rootful: mc.HostUser.Rootful, + VMType: w.VMType(), + } + machine.LaunchWinProxy(winProxyOpts, false) + } + return nil +} + +func (w WSLStubber) StartVM(mc *vmconfigs.MachineConfig) (func() error, func() error, error) { + useProxy := setupWslProxyEnv() + + // TODO Quiet is hard set to false: follow up + if err := configureProxy(mc.Name, useProxy, false); err != nil { + return nil, nil, err + } + + // TODO The original code checked to see if the SSH port was actually open and re-assigned if it was + // we could consider this but it should be higher up the stack + // if !machine.IsLocalPortAvailable(v.Port) { + // logrus.Warnf("SSH port conflict detected, reassigning a new port") + // if err := v.reassignSshPort(); err != nil { + // return err + // } + // } + + err := wslInvoke(mc.Name, "/root/bootstrap") + if err != nil { + err = fmt.Errorf("the WSL bootstrap script failed: %w", err) + } + + // TODO we dont show this for any other provider. perhaps we should ? and if + // so, we need to move it up the stack + //if !v.Rootful && !opts.NoInfo { + // 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) + //} + + readyFunc := func() error { + return nil + } + + return nil, readyFunc, err +} + +func (w WSLStubber) State(mc *vmconfigs.MachineConfig, bypass bool) (define.Status, error) { + running, err := isRunning(mc.Name) + if err != nil { + return "", err + } + if running { + return define.Running, nil + } + return define.Stopped, nil +} + +func (w WSLStubber) StopVM(mc *vmconfigs.MachineConfig, hardStop bool) error { + var ( + err error + ) + // by this time, state has been verified to be running and a request + // to stop is fair game + mc.Lock() + defer mc.Unlock() + + // Stop user-mode networking if enabled + if err := stopUserModeNetworking(mc); err != nil { + fmt.Fprintf(os.Stderr, "Could not cleanly stop user-mode networking: %s\n", err.Error()) + } + + if err := machine.StopWinProxy(mc.Name, vmtype); err != nil { + fmt.Fprintf(os.Stderr, "Could not stop API forwarding service (win-sshproxy.exe): %s\n", err.Error()) + } + + cmd := exec.Command("wsl", "-u", "root", "-d", mc.Name, "sh") + cmd.Stdin = strings.NewReader(waitTerm) + if err = cmd.Start(); err != nil { + return fmt.Errorf("executing wait command: %w", err) + } + + exitCmd := exec.Command("wsl", "-u", "root", "-d", mc.Name, "/usr/local/bin/enterns", "systemctl", "exit", "0") + if err = exitCmd.Run(); err != nil { + return fmt.Errorf("stopping sysd: %w", err) + } + + if err = cmd.Wait(); err != nil { + return err + } + + return terminateDist(mc.Name) +} + +func (w WSLStubber) StopHostNetworking(mc *vmconfigs.MachineConfig, vmType define.VMType) error { + return stopUserModeNetworking(mc) +} + +func (w WSLStubber) VMType() define.VMType { + return define.WSLVirt +} + +func (w WSLStubber) GetDisk(_ string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error { + var ( + myDisk ocipull.Disker + ) + + // check github for the latest version of the WSL dist + downloadURL, downloadVersion, _, _, err := GetFedoraDownloadForWSL() + if err != nil { + return err + } + + // we now save the "cached" rootfs in the form of "v-rootfs.tar.xz" + // i.e.v39.0.31-rootfs.tar.xz + versionedBase := fmt.Sprintf("%s-%s", downloadVersion, filepath.Base(downloadURL.Path)) + + // TODO we need a mechanism for "flushing" old cache files + cachedFile, err := dirs.DataDir.AppendToNewVMFile(versionedBase, nil) + if err != nil { + return err + } + + // if we find the same file cached (determined by filename only), then dont pull + if _, err = os.Stat(cachedFile.GetPath()); err == nil { + logrus.Debugf("%q already exists locally", cachedFile.GetPath()) + myDisk, err = stdpull.NewStdDiskPull(cachedFile.GetPath(), mc.ImagePath) + } else { + // no cached file + myDisk, err = stdpull.NewDiskFromURL(downloadURL.String(), mc.ImagePath, dirs.DataDir, &versionedBase) + } + if err != nil { + return err + } + // up until now, nothing has really happened + // pull if needed and decompress to image location + return myDisk.Get() +} diff --git a/pkg/machine/wsl/usermodenet.go b/pkg/machine/wsl/usermodenet.go index bb2f6528ad..320bdda418 100644 --- a/pkg/machine/wsl/usermodenet.go +++ b/pkg/machine/wsl/usermodenet.go @@ -5,6 +5,7 @@ package wsl import ( "errors" "fmt" + "github.com/containers/podman/v5/pkg/machine/vmconfigs" "os" "os/exec" "path/filepath" @@ -69,8 +70,8 @@ func verifyWSLUserModeCompat() error { prefix) } -func (v *MachineVM) startUserModeNetworking() error { - if !v.UserModeNetworking { +func startUserModeNetworking(mc *vmconfigs.MachineConfig) error { + if !mc.WSLHypervisor.UserModeNetworking { return nil } @@ -79,7 +80,7 @@ func (v *MachineVM) startUserModeNetworking() error { return fmt.Errorf("could not locate %s, which is necessary for user-mode networking, please reinstall", gvProxy) } - flock, err := v.obtainUserModeNetLock() + flock, err := obtainUserModeNetLock() if err != nil { return err } @@ -93,17 +94,17 @@ func (v *MachineVM) startUserModeNetworking() error { // Start or reuse if !running { - if err := v.launchUserModeNetDist(exe); err != nil { + if err := launchUserModeNetDist(exe); err != nil { return err } } - if err := createUserModeResolvConf(toDist(v.Name)); err != nil { + if err := createUserModeResolvConf(mc.Name); err != nil { return err } // Register in-use - err = v.addUserModeNetEntry() + err = addUserModeNetEntry(mc) if err != nil { return err } @@ -111,23 +112,23 @@ func (v *MachineVM) startUserModeNetworking() error { return nil } -func (v *MachineVM) stopUserModeNetworking(dist string) error { - if !v.UserModeNetworking { +func stopUserModeNetworking(mc *vmconfigs.MachineConfig) error { + if !mc.WSLHypervisor.UserModeNetworking { return nil } - flock, err := v.obtainUserModeNetLock() + flock, err := obtainUserModeNetLock() if err != nil { return err } defer flock.unlock() - err = v.removeUserModeNetEntry() + err = removeUserModeNetEntry(mc.Name) if err != nil { return err } - count, err := v.cleanupAndCountNetEntries() + count, err := cleanupAndCountNetEntries() if err != nil { return err } @@ -159,7 +160,7 @@ func isGvProxyVMRunning() bool { return wslInvoke(userModeDist, "bash", "-c", "ps -eo args | grep -q -m1 ^/usr/local/bin/vm || exit 42") == nil } -func (v *MachineVM) launchUserModeNetDist(exeFile string) error { +func launchUserModeNetDist(exeFile string) error { fmt.Println("Starting user-mode networking...") exe, err := specgen.ConvertWinMountPath(exeFile) @@ -220,7 +221,7 @@ func createUserModeResolvConf(dist string) error { return err } -func (v *MachineVM) getUserModeNetDir() (string, error) { +func getUserModeNetDir() (string, error) { vmDataDir, err := machine.GetDataDir(vmtype) if err != nil { return "", err @@ -234,8 +235,8 @@ func (v *MachineVM) getUserModeNetDir() (string, error) { return dir, nil } -func (v *MachineVM) getUserModeNetEntriesDir() (string, error) { - netDir, err := v.getUserModeNetDir() +func getUserModeNetEntriesDir() (string, error) { + netDir, err := getUserModeNetDir() if err != nil { return "", err } @@ -248,13 +249,13 @@ func (v *MachineVM) getUserModeNetEntriesDir() (string, error) { return dir, nil } -func (v *MachineVM) addUserModeNetEntry() error { - entriesDir, err := v.getUserModeNetEntriesDir() +func addUserModeNetEntry(mc *vmconfigs.MachineConfig) error { + entriesDir, err := getUserModeNetEntriesDir() if err != nil { return err } - path := filepath.Join(entriesDir, toDist(v.Name)) + path := filepath.Join(entriesDir, mc.Name) file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) if err != nil { return fmt.Errorf("could not add user-mode networking registration: %w", err) @@ -263,18 +264,18 @@ func (v *MachineVM) addUserModeNetEntry() error { return nil } -func (v *MachineVM) removeUserModeNetEntry() error { - entriesDir, err := v.getUserModeNetEntriesDir() +func removeUserModeNetEntry(name string) error { + entriesDir, err := getUserModeNetEntriesDir() if err != nil { return err } - path := filepath.Join(entriesDir, toDist(v.Name)) + path := filepath.Join(entriesDir, name) return os.Remove(path) } -func (v *MachineVM) cleanupAndCountNetEntries() (uint, error) { - entriesDir, err := v.getUserModeNetEntriesDir() +func cleanupAndCountNetEntries() (uint, error) { + entriesDir, err := getUserModeNetEntriesDir() if err != nil { return 0, err } @@ -302,8 +303,8 @@ func (v *MachineVM) cleanupAndCountNetEntries() (uint, error) { return count, nil } -func (v *MachineVM) obtainUserModeNetLock() (*fileLock, error) { - dir, err := v.getUserModeNetDir() +func obtainUserModeNetLock() (*fileLock, error) { + dir, err := getUserModeNetDir() if err != nil { return nil, err