github.com/containers/podman/v4@v4.9.4/pkg/machine/wsl/machine.go (about) 1 //go:build windows 2 // +build windows 3 4 package wsl 5 6 import ( 7 "bufio" 8 "encoding/json" 9 "errors" 10 "fmt" 11 "io" 12 "net/url" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "strconv" 17 "strings" 18 "time" 19 20 "github.com/containers/common/pkg/config" 21 "github.com/containers/podman/v4/pkg/machine" 22 "github.com/containers/podman/v4/pkg/machine/define" 23 "github.com/containers/podman/v4/pkg/machine/wsl/wutil" 24 "github.com/containers/podman/v4/pkg/util" 25 "github.com/containers/podman/v4/utils" 26 "github.com/containers/storage/pkg/homedir" 27 "github.com/containers/storage/pkg/lockfile" 28 "github.com/sirupsen/logrus" 29 "golang.org/x/text/encoding/unicode" 30 "golang.org/x/text/transform" 31 ) 32 33 var ( 34 // vmtype refers to qemu (vs libvirt, krun, etc) 35 vmtype = machine.WSLVirt 36 ) 37 38 const ( 39 ErrorSuccessRebootInitiated = 1641 40 ErrorSuccessRebootRequired = 3010 41 currentMachineVersion = 3 42 ) 43 44 const containersConf = `[containers] 45 46 [engine] 47 cgroup_manager = "cgroupfs" 48 ` 49 50 const registriesConf = `unqualified-search-registries=["docker.io"] 51 ` 52 53 const appendPort = `grep -q Port\ %d /etc/ssh/sshd_config || echo Port %d >> /etc/ssh/sshd_config` 54 55 const changePort = `sed -E -i 's/^Port[[:space:]]+[0-9]+/Port %d/' /etc/ssh/sshd_config` 56 57 const configServices = `ln -fs /usr/lib/systemd/system/sshd.service /etc/systemd/system/multi-user.target.wants/sshd.service 58 ln -fs /usr/lib/systemd/system/podman.socket /etc/systemd/system/sockets.target.wants/podman.socket 59 rm -f /etc/systemd/system/getty.target.wants/console-getty.service 60 rm -f /etc/systemd/system/getty.target.wants/getty@tty1.service 61 rm -f /etc/systemd/system/multi-user.target.wants/systemd-resolved.service 62 rm -f /etc/systemd/system/sysinit.target.wants//systemd-resolved.service 63 rm -f /etc/systemd/system/dbus-org.freedesktop.resolve1.service 64 ln -fs /dev/null /etc/systemd/system/console-getty.service 65 ln -fs /dev/null /etc/systemd/system/systemd-oomd.socket 66 mkdir -p /etc/systemd/system/systemd-sysusers.service.d/ 67 echo CREATE_MAIL_SPOOL=no >> /etc/default/useradd 68 adduser -m [USER] -G wheel 69 mkdir -p /home/[USER]/.config/systemd/[USER]/ 70 chown [USER]:[USER] /home/[USER]/.config 71 ` 72 73 const sudoers = `%wheel ALL=(ALL) NOPASSWD: ALL 74 ` 75 76 const bootstrap = `#!/bin/bash 77 ps -ef | grep -v grep | grep -q systemd && exit 0 78 nohup unshare --kill-child --fork --pid --mount --mount-proc --propagation shared /lib/systemd/systemd >/dev/null 2>&1 & 79 sleep 0.1 80 ` 81 82 const wslmotd = ` 83 You will be automatically entered into a nested process namespace where 84 systemd is running. If you need to access the parent namespace, hit ctrl-d 85 or type exit. This also means to log out you need to exit twice. 86 87 ` 88 89 const sysdpid = "SYSDPID=`ps -eo cmd,pid | grep -m 1 ^/lib/systemd/systemd | awk '{print $2}'`" 90 91 const profile = sysdpid + ` 92 if [ ! -z "$SYSDPID" ] && [ "$SYSDPID" != "1" ]; then 93 cat /etc/wslmotd 94 /usr/local/bin/enterns 95 fi 96 ` 97 98 const enterns = "#!/bin/bash\n" + sysdpid + ` 99 if [ ! -z "$SYSDPID" ] && [ "$SYSDPID" != "1" ]; then 100 NSENTER=("nsenter" "-m" "-p" "-t" "$SYSDPID" "--wd=$PWD") 101 102 if [ "$UID" != "0" ]; then 103 NSENTER=("sudo" "${NSENTER[@]}") 104 if [ "$#" != "0" ]; then 105 NSENTER+=("sudo" "-u" "$USER") 106 else 107 NSENTER+=("su" "-l" "$USER") 108 fi 109 fi 110 "${NSENTER[@]}" "$@" 111 fi` 112 113 const waitTerm = sysdpid + ` 114 if [ ! -z "$SYSDPID" ]; then 115 timeout 60 tail -f /dev/null --pid $SYSDPID 116 fi 117 ` 118 119 const wslConf = `[user] 120 default=[USER] 121 ` 122 123 const wslConfUserNet = ` 124 [network] 125 generateResolvConf = false 126 ` 127 128 const resolvConfUserNet = ` 129 nameserver 192.168.127.1 130 ` 131 132 // WSL kernel does not have sg and crypto_user modules 133 const overrideSysusers = `[Service] 134 LoadCredential= 135 ` 136 137 const lingerService = `[Unit] 138 Description=A systemd user unit demo 139 After=network-online.target 140 Wants=network-online.target podman.socket 141 [Service] 142 ExecStart=/usr/bin/sleep infinity 143 ` 144 145 const lingerSetup = `mkdir -p /home/[USER]/.config/systemd/user/default.target.wants 146 ln -fs /home/[USER]/.config/systemd/user/linger-example.service \ 147 /home/[USER]/.config/systemd/user/default.target.wants/linger-example.service 148 ` 149 150 const bindMountSystemService = ` 151 [Unit] 152 Description=Bind mount for system podman sockets 153 After=podman.socket 154 155 [Service] 156 RemainAfterExit=true 157 Type=oneshot 158 # Ensure user services can register sockets as well 159 ExecStartPre=mkdir -p -m 777 /mnt/wsl/podman-sockets 160 ExecStartPre=mkdir -p -m 777 /mnt/wsl/podman-sockets/%[1]s 161 ExecStartPre=touch /mnt/wsl/podman-sockets/%[1]s/podman-root.sock 162 ExecStart=mount --bind %%t/podman/podman.sock /mnt/wsl/podman-sockets/%[1]s/podman-root.sock 163 ExecStop=umount /mnt/wsl/podman-sockets/%[1]s/podman-root.sock 164 ` 165 166 const bindMountUserService = ` 167 [Unit] 168 Description=Bind mount for user podman sockets 169 After=podman.socket 170 171 [Service] 172 RemainAfterExit=true 173 Type=oneshot 174 # Consistency with system service (supports racing) 175 ExecStartPre=mkdir -p -m 777 /mnt/wsl/podman-sockets 176 ExecStartPre=mkdir -p -m 777 /mnt/wsl/podman-sockets/%[1]s 177 ExecStartPre=touch /mnt/wsl/podman-sockets/%[1]s/podman-user.sock 178 # Relies on /etc/fstab entry for user mounting 179 ExecStart=mount /mnt/wsl/podman-sockets/%[1]s/podman-user.sock 180 ExecStop=umount /mnt/wsl/podman-sockets/%[1]s/podman-user.sock 181 ` 182 183 const bindMountFsTab = `/run/user/1000/podman/podman.sock /mnt/wsl/podman-sockets/%s/podman-user.sock none noauto,user,bind,defaults 0 0 184 ` 185 const ( 186 defaultTargetWants = "default.target.wants" 187 userSystemdPath = "/home/%[1]s/.config/systemd/user" 188 sysSystemdPath = "/etc/systemd/system" 189 userSystemdWants = userSystemdPath + "/" + defaultTargetWants 190 sysSystemdWants = sysSystemdPath + "/" + defaultTargetWants 191 bindUnitFileName = "podman-mnt-bindings.service" 192 bindUserUnitPath = userSystemdPath + "/" + bindUnitFileName 193 bindUserUnitWant = userSystemdWants + "/" + bindUnitFileName 194 bindSysUnitPath = sysSystemdPath + "/" + bindUnitFileName 195 bindSysUnitWant = sysSystemdWants + "/" + bindUnitFileName 196 podmanSocketDropin = "podman.socket.d" 197 podmanSocketDropinPath = sysSystemdPath + "/" + podmanSocketDropin 198 ) 199 200 const configBindServices = "mkdir -p " + userSystemdWants + " " + sysSystemdWants + " " + podmanSocketDropinPath + "\n" + 201 "ln -fs " + bindUserUnitPath + " " + bindUserUnitWant + "\n" + 202 "ln -fs " + bindSysUnitPath + " " + bindSysUnitWant + "\n" 203 204 const overrideSocketGroup = ` 205 [Socket] 206 SocketMode=0660 207 SocketGroup=wheel 208 ` 209 210 const proxyConfigSetup = `#!/bin/bash 211 212 SYSTEMD_CONF=/etc/systemd/system.conf.d/default-env.conf 213 ENVD_CONF=/etc/environment.d/default-env.conf 214 PROFILE_CONF=/etc/profile.d/default-env.sh 215 216 IFS="|" 217 read proxies 218 219 mkdir -p /etc/profile.d /etc/environment.d /etc/systemd/system.conf.d/ 220 rm -f $SYSTEMD_CONF 221 for proxy in $proxies; do 222 output+="$proxy " 223 done 224 echo "[Manager]" >> $SYSTEMD_CONF 225 echo -ne "DefaultEnvironment=" >> $SYSTEMD_CONF 226 227 echo $output >> $SYSTEMD_CONF 228 rm -f $ENVD_CONF 229 for proxy in $proxies; do 230 echo "$proxy" >> $ENVD_CONF 231 done 232 rm -f $PROFILE_CONF 233 for proxy in $proxies; do 234 echo "export $proxy" >> $PROFILE_CONF 235 done 236 ` 237 238 const proxyConfigAttempt = `if [ -f /usr/local/bin/proxyinit ]; \ 239 then /usr/local/bin/proxyinit; \ 240 else exit 42; \ 241 fi` 242 243 const clearProxySettings = `rm -f /etc/systemd/system.conf.d/default-env.conf \ 244 /etc/environment.d/default-env.conf \ 245 /etc/profile.d/default-env.sh` 246 247 const wslInstallError = `Could not %s. See previous output for any potential failure details. 248 If you can not resolve the issue, and rerunning fails, try the "wsl --install" process 249 outlined in the following article: 250 251 http://docs.microsoft.com/en-us/windows/wsl/install 252 253 ` 254 255 const wslKernelError = `Could not %s. See previous output for any potential failure details. 256 If you can not resolve the issue, try rerunning the "podman machine init command". If that fails 257 try the "wsl --update" command and then rerun "podman machine init". Finally, if all else fails, 258 try following the steps outlined in the following article: 259 260 http://docs.microsoft.com/en-us/windows/wsl/install 261 262 ` 263 264 const wslInstallKernel = "install the WSL Kernel" 265 266 const wslOldVersion = `Automatic installation of WSL can not be performed on this version of Windows 267 Either update to Build 19041 (or later), or perform the manual installation steps 268 outlined in the following article: 269 270 http://docs.microsoft.com/en-us/windows/wsl/install\ 271 272 ` 273 274 const ( 275 gvProxy = "gvproxy.exe" 276 winSShProxy = "win-sshproxy.exe" 277 pipePrefix = "npipe:////./pipe/" 278 globalPipe = "docker_engine" 279 userModeDist = "podman-net-usermode" 280 rootfulSock = "/run/podman/podman.sock" 281 rootlessSock = "/run/user/1000/podman/podman.sock" 282 ) 283 284 type MachineVM struct { 285 // ConfigPath is the path to the configuration file 286 ConfigPath string 287 // Created contains the original created time instead of querying the file mod time 288 Created time.Time 289 // ImageStream is the version of fcos being used 290 ImageStream string 291 // ImagePath is the fq path to 292 ImagePath string 293 // LastUp contains the last recorded uptime 294 LastUp time.Time 295 // Name of the vm 296 Name string 297 // Whether this machine should run in a rootful or rootless manner 298 Rootful bool 299 // SSH identity, username, etc 300 machine.SSHConfig 301 // machine version 302 Version int 303 // Whether to use user-mode networking 304 UserModeNetworking bool 305 // Used at runtime for serializing write operations 306 lock *lockfile.LockFile 307 } 308 309 type ExitCodeError struct { 310 code uint 311 } 312 313 func (e *ExitCodeError) Error() string { 314 return fmt.Sprintf("Process failed with exit code: %d", e.code) 315 } 316 317 func getConfigPath(name string) (string, error) { 318 return getConfigPathExt(name, "json") 319 } 320 321 func getConfigPathExt(name string, extension string) (string, error) { 322 vmConfigDir, err := machine.GetConfDir(vmtype) 323 if err != nil { 324 return "", err 325 } 326 327 return filepath.Join(vmConfigDir, fmt.Sprintf("%s.%s", name, extension)), nil 328 } 329 330 // readAndMigrate returns the content of the VM's 331 // configuration file in json 332 func readAndMigrate(configPath string, name string) (*MachineVM, error) { 333 vm := new(MachineVM) 334 b, err := os.ReadFile(configPath) 335 if err != nil { 336 if errors.Is(err, os.ErrNotExist) { 337 return nil, fmt.Errorf("%v: %w", name, machine.ErrNoSuchVM) 338 } 339 return vm, err 340 } 341 err = json.Unmarshal(b, vm) 342 if err == nil && vm.Version < currentMachineVersion { 343 err = vm.migrateMachine(configPath) 344 } 345 346 return vm, err 347 } 348 349 func (v *MachineVM) migrateMachine(configPath string) error { 350 if v.Created.IsZero() { 351 if err := v.migrate40(configPath); err != nil { 352 return err 353 } 354 } 355 356 // Update older machines to use lingering 357 if err := enableUserLinger(v, toDist(v.Name)); err != nil { 358 return err 359 } 360 361 // Update older machines missing unqualified search config 362 if err := configureRegistries(v, toDist(v.Name)); err != nil { 363 return err 364 } 365 366 v.Version = currentMachineVersion 367 return v.writeConfig() 368 } 369 370 func (v *MachineVM) migrate40(configPath string) error { 371 v.ConfigPath = configPath 372 fi, err := os.Stat(configPath) 373 if err != nil { 374 return err 375 } 376 v.Created = fi.ModTime() 377 v.LastUp = getLegacyLastStart(v) 378 return nil 379 } 380 381 func getLegacyLastStart(vm *MachineVM) time.Time { 382 vmDataDir, err := machine.GetDataDir(vmtype) 383 if err != nil { 384 return vm.Created 385 } 386 distDir := filepath.Join(vmDataDir, "wsldist") 387 start := filepath.Join(distDir, vm.Name, "laststart") 388 info, err := os.Stat(start) 389 if err != nil { 390 return vm.Created 391 } 392 return info.ModTime() 393 } 394 395 // Init writes the json configuration file to the filesystem for 396 // other verbs (start, stop) 397 func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { 398 var ( 399 err error 400 ) 401 // cleanup half-baked files if init fails at any point 402 callbackFuncs := machine.InitCleanup() 403 defer callbackFuncs.CleanIfErr(&err) 404 go callbackFuncs.CleanOnSignal() 405 406 if cont, err := checkAndInstallWSL(opts); !cont { 407 appendOutputIfError(opts.ReExec, err) 408 return cont, err 409 } 410 411 _ = setupWslProxyEnv() 412 v.IdentityPath = util.GetIdentityPath(v.Name) 413 v.Rootful = opts.Rootful 414 v.Version = currentMachineVersion 415 416 if v.UserModeNetworking { 417 if err = verifyWSLUserModeCompat(); err != nil { 418 return false, err 419 } 420 } 421 422 if err = downloadDistro(v, opts); err != nil { 423 return false, err 424 } 425 callbackFuncs.Add(v.removeMachineImage) 426 427 const prompt = "Importing operating system into WSL (this may take a few minutes on a new WSL install)..." 428 dist, err := provisionWSLDist(v.Name, v.ImagePath, prompt) 429 if err != nil { 430 return false, err 431 } 432 callbackFuncs.Add(v.unprovisionWSL) 433 434 if v.UserModeNetworking { 435 if err = installUserModeDist(dist, v.ImagePath); err != nil { 436 _ = unregisterDist(dist) 437 return false, err 438 } 439 } 440 441 fmt.Println("Configuring system...") 442 if err = configureSystem(v, dist); err != nil { 443 return false, err 444 } 445 446 if err = installScripts(dist); err != nil { 447 return false, err 448 } 449 450 if err = createKeys(v, dist); err != nil { 451 return false, err 452 } 453 callbackFuncs.Add(v.removeSSHKeys) 454 455 // Cycle so that user change goes into effect 456 _ = terminateDist(dist) 457 458 if err = v.writeConfig(); err != nil { 459 return false, err 460 } 461 callbackFuncs.Add(v.removeMachineConfig) 462 463 if err = setupConnections(v, opts); err != nil { 464 return false, err 465 } 466 callbackFuncs.Add(v.removeSystemConnections) 467 return true, nil 468 } 469 470 func (v *MachineVM) unprovisionWSL() error { 471 if err := terminateDist(toDist(v.Name)); err != nil { 472 logrus.Error(err) 473 } 474 if err := unregisterDist(toDist(v.Name)); err != nil { 475 logrus.Error(err) 476 } 477 478 vmDataDir, err := machine.GetDataDir(vmtype) 479 if err != nil { 480 return err 481 } 482 distDir := filepath.Join(vmDataDir, "wsldist") 483 distTarget := filepath.Join(distDir, v.Name) 484 return utils.GuardedRemoveAll(distTarget) 485 } 486 487 func (v *MachineVM) removeMachineConfig() error { 488 return utils.GuardedRemoveAll(v.ConfigPath) 489 } 490 491 func (v *MachineVM) removeMachineImage() error { 492 return utils.GuardedRemoveAll(v.ImagePath) 493 } 494 495 func (v *MachineVM) removeSSHKeys() error { 496 if err := utils.GuardedRemoveAll(fmt.Sprintf("%s.pub", v.IdentityPath)); err != nil { 497 logrus.Error(err) 498 } 499 return utils.GuardedRemoveAll(v.IdentityPath) 500 } 501 502 func (v *MachineVM) removeSystemConnections() error { 503 return machine.RemoveConnections(v.Name, fmt.Sprintf("%s-root", v.Name)) 504 } 505 506 func downloadDistro(v *MachineVM, opts machine.InitOptions) error { 507 var ( 508 dd machine.DistributionDownload 509 err error 510 ) 511 512 // The default FCOS stream names are reserved indicators for the standard Fedora fetch 513 if machine.IsValidFCOSStreamString(opts.ImagePath) { 514 v.ImageStream = opts.ImagePath 515 dd, err = NewFedoraDownloader(vmtype, v.Name, opts.ImagePath) 516 } else { 517 v.ImageStream = "custom" 518 dd, err = machine.NewGenericDownloader(vmtype, v.Name, opts.ImagePath) 519 } 520 if err != nil { 521 return err 522 } 523 524 v.ImagePath = dd.Get().LocalUncompressedFile 525 return machine.DownloadImage(dd) 526 } 527 528 func (v *MachineVM) writeConfig() error { 529 return machine.WriteConfig(v.ConfigPath, v) 530 } 531 532 func constructSSHUris(v *MachineVM) ([]url.URL, []string) { 533 uri := machine.SSHRemoteConnection.MakeSSHURL(machine.LocalhostIP, rootlessSock, strconv.Itoa(v.Port), v.RemoteUsername) 534 uriRoot := machine.SSHRemoteConnection.MakeSSHURL(machine.LocalhostIP, rootfulSock, strconv.Itoa(v.Port), "root") 535 536 uris := []url.URL{uri, uriRoot} 537 names := []string{v.Name, v.Name + "-root"} 538 539 return uris, names 540 } 541 542 func setupConnections(v *MachineVM, opts machine.InitOptions) error { 543 uris, names := constructSSHUris(v) 544 545 // The first connection defined when connections is empty will become the default 546 // regardless of IsDefault, so order according to rootful 547 if opts.Rootful { 548 uris[0], names[0], uris[1], names[1] = uris[1], names[1], uris[0], names[0] 549 } 550 551 // We need to prevent racing connection updates to containers.conf globally 552 // across all backends to prevent connection overwrites 553 flock, err := obtainGlobalConfigLock() 554 if err != nil { 555 return fmt.Errorf("could not obtain global lock: %w", err) 556 } 557 defer flock.unlock() 558 559 for i := 0; i < 2; i++ { 560 if err := machine.AddConnection(&uris[i], names[i], v.IdentityPath, opts.IsDefault && i == 0); err != nil { 561 return err 562 } 563 } 564 565 return nil 566 } 567 568 func provisionWSLDist(name string, imagePath string, prompt string) (string, error) { 569 vmDataDir, err := machine.GetDataDir(vmtype) 570 if err != nil { 571 return "", err 572 } 573 574 distDir := filepath.Join(vmDataDir, "wsldist") 575 distTarget := filepath.Join(distDir, name) 576 if err := os.MkdirAll(distDir, 0755); err != nil { 577 return "", fmt.Errorf("could not create wsldist directory: %w", err) 578 } 579 580 dist := toDist(name) 581 fmt.Println(prompt) 582 if err = runCmdPassThrough("wsl", "--import", dist, distTarget, imagePath, "--version", "2"); err != nil { 583 return "", fmt.Errorf("the WSL import of guest OS failed: %w", err) 584 } 585 586 // Fixes newuidmap 587 if err = wslInvoke(dist, "rpm", "--restore", "shadow-utils"); err != nil { 588 return "", fmt.Errorf("package permissions restore of shadow-utils on guest OS failed: %w", err) 589 } 590 591 return dist, nil 592 } 593 594 func createKeys(v *MachineVM, dist string) error { 595 user := v.RemoteUsername 596 597 if err := terminateDist(dist); err != nil { 598 return fmt.Errorf("could not cycle WSL dist: %w", err) 599 } 600 601 key, err := wslCreateKeys(v.IdentityPath, dist) 602 if err != nil { 603 return fmt.Errorf("could not create ssh keys: %w", err) 604 } 605 606 if err := wslPipe(key+"\n", dist, "sh", "-c", "mkdir -p /root/.ssh;"+ 607 "cat >> /root/.ssh/authorized_keys; chmod 600 /root/.ssh/authorized_keys"); err != nil { 608 return fmt.Errorf("could not create root authorized keys on guest OS: %w", err) 609 } 610 611 userAuthCmd := withUser("mkdir -p /home/[USER]/.ssh;"+ 612 "cat >> /home/[USER]/.ssh/authorized_keys; chown -R [USER]:[USER] /home/[USER]/.ssh;"+ 613 "chmod 600 /home/[USER]/.ssh/authorized_keys", user) 614 if err := wslPipe(key+"\n", dist, "sh", "-c", userAuthCmd); err != nil { 615 return fmt.Errorf("could not create '%s' authorized keys on guest OS: %w", v.RemoteUsername, err) 616 } 617 618 return nil 619 } 620 621 func configureSystem(v *MachineVM, dist string) error { 622 user := v.RemoteUsername 623 if err := wslInvoke(dist, "sh", "-c", fmt.Sprintf(appendPort, v.Port, v.Port)); err != nil { 624 return fmt.Errorf("could not configure SSH port for guest OS: %w", err) 625 } 626 627 if err := wslPipe(withUser(configServices, user), dist, "sh"); err != nil { 628 return fmt.Errorf("could not configure systemd settings for guest OS: %w", err) 629 } 630 631 if err := wslPipe(sudoers, dist, "sh", "-c", "cat >> /etc/sudoers"); err != nil { 632 return fmt.Errorf("could not add wheel to sudoers: %w", err) 633 } 634 635 if err := wslPipe(overrideSysusers, dist, "sh", "-c", 636 "cat > /etc/systemd/system/systemd-sysusers.service.d/override.conf"); err != nil { 637 return fmt.Errorf("could not generate systemd-sysusers override for guest OS: %w", err) 638 } 639 640 lingerCmd := withUser("cat > /home/[USER]/.config/systemd/[USER]/linger-example.service", user) 641 if err := wslPipe(lingerService, dist, "sh", "-c", lingerCmd); err != nil { 642 return fmt.Errorf("could not generate linger service for guest OS: %w", err) 643 } 644 645 if err := enableUserLinger(v, dist); err != nil { 646 return err 647 } 648 649 if err := wslPipe(withUser(lingerSetup, user), dist, "sh"); err != nil { 650 return fmt.Errorf("could not configure systemd settings for guest OS: %w", err) 651 } 652 653 if err := wslPipe(containersConf, dist, "sh", "-c", "cat > /etc/containers/containers.conf"); err != nil { 654 return fmt.Errorf("could not create containers.conf for guest OS: %w", err) 655 } 656 657 if err := configureRegistries(v, dist); err != nil { 658 return err 659 } 660 661 if err := v.setupPodmanDockerSock(dist, v.Rootful); err != nil { 662 return err 663 } 664 665 if err := wslInvoke(dist, "sh", "-c", "echo wsl > /etc/containers/podman-machine"); err != nil { 666 return fmt.Errorf("could not create podman-machine file for guest OS: %w", err) 667 } 668 669 if err := configureBindMounts(dist, user); err != nil { 670 return err 671 } 672 673 return changeDistUserModeNetworking(dist, user, v.ImagePath, v.UserModeNetworking) 674 } 675 676 func configureBindMounts(dist string, user string) error { 677 if err := wslPipe(fmt.Sprintf(bindMountSystemService, dist), dist, "sh", "-c", "cat > /etc/systemd/system/podman-mnt-bindings.service"); err != nil { 678 return fmt.Errorf("could not create podman binding service file for guest OS: %w", err) 679 } 680 681 catUserService := "cat > " + getUserUnitPath(user) 682 if err := wslPipe(getBindMountUserService(dist), dist, "sh", "-c", catUserService); err != nil { 683 return fmt.Errorf("could not create podman binding user service file for guest OS: %w", err) 684 } 685 686 if err := wslPipe(getBindMountFsTab(dist), dist, "sh", "-c", "cat >> /etc/fstab"); err != nil { 687 return fmt.Errorf("could not create podman binding fstab entry for guest OS: %w", err) 688 } 689 690 if err := wslPipe(getConfigBindServicesScript(user), dist, "sh"); err != nil { 691 return fmt.Errorf("could not configure podman binding services for guest OS: %w", err) 692 } 693 694 catGroupDropin := fmt.Sprintf("cat > %s/%s", podmanSocketDropinPath, "10-group.conf") 695 if err := wslPipe(overrideSocketGroup, dist, "sh", "-c", catGroupDropin); err != nil { 696 return fmt.Errorf("could not configure podman socket group override: %w", err) 697 } 698 699 return nil 700 } 701 702 func getConfigBindServicesScript(user string) string { 703 return fmt.Sprintf(configBindServices, user) 704 } 705 706 func getBindMountUserService(dist string) string { 707 return fmt.Sprintf(bindMountUserService, dist) 708 } 709 710 func getUserUnitPath(user string) string { 711 return fmt.Sprintf(bindUserUnitPath, user) 712 } 713 714 func getBindMountFsTab(dist string) string { 715 return fmt.Sprintf(bindMountFsTab, dist) 716 } 717 718 func (v *MachineVM) setupPodmanDockerSock(dist string, rootful bool) error { 719 content := machine.GetPodmanDockerTmpConfig(1000, rootful, true) 720 721 if err := wslPipe(content, dist, "sh", "-c", "cat > "+machine.PodmanDockerTmpConfPath); err != nil { 722 return fmt.Errorf("could not create internal docker sock conf: %w", err) 723 } 724 725 return nil 726 } 727 728 func configureProxy(dist string, useProxy bool, quiet bool) error { 729 if !useProxy { 730 _ = wslInvoke(dist, "sh", "-c", clearProxySettings) 731 return nil 732 } 733 var content string 734 for i, key := range config.ProxyEnv { 735 if value, _ := os.LookupEnv(key); len(value) > 0 { 736 var suffix string 737 if i < (len(config.ProxyEnv) - 1) { 738 suffix = "|" 739 } 740 content = fmt.Sprintf("%s%s=\"%s\"%s", content, key, value, suffix) 741 } 742 } 743 744 if err := wslPipe(content, dist, "sh", "-c", proxyConfigAttempt); err != nil { 745 const failMessage = "Failure creating proxy configuration" 746 if exitErr, isExit := err.(*exec.ExitError); isExit && exitErr.ExitCode() != 42 { 747 return fmt.Errorf("%v: %w", failMessage, err) 748 } 749 if !quiet { 750 fmt.Println("Installing proxy support") 751 } 752 _ = wslPipe(proxyConfigSetup, dist, "sh", "-c", 753 "cat > /usr/local/bin/proxyinit; chmod 755 /usr/local/bin/proxyinit") 754 755 if err = wslPipe(content, dist, "/usr/local/bin/proxyinit"); err != nil { 756 return fmt.Errorf("%v: %w", failMessage, err) 757 } 758 } 759 760 return nil 761 } 762 763 func enableUserLinger(v *MachineVM, dist string) error { 764 lingerCmd := "mkdir -p /var/lib/systemd/linger; touch /var/lib/systemd/linger/" + v.RemoteUsername 765 if err := wslInvoke(dist, "sh", "-c", lingerCmd); err != nil { 766 return fmt.Errorf("could not enable linger for remote user on guest OS: %w", err) 767 } 768 769 return nil 770 } 771 772 func configureRegistries(v *MachineVM, dist string) error { 773 cmd := "cat > /etc/containers/registries.conf.d/999-podman-machine.conf" 774 if err := wslPipe(registriesConf, dist, "sh", "-c", cmd); err != nil { 775 return fmt.Errorf("could not configure registries on guest OS: %w", err) 776 } 777 778 return nil 779 } 780 781 func installScripts(dist string) error { 782 if err := wslPipe(enterns, dist, "sh", "-c", 783 "cat > /usr/local/bin/enterns; chmod 755 /usr/local/bin/enterns"); err != nil { 784 return fmt.Errorf("could not create enterns script for guest OS: %w", err) 785 } 786 787 if err := wslPipe(profile, dist, "sh", "-c", 788 "cat > /etc/profile.d/enterns.sh"); err != nil { 789 return fmt.Errorf("could not create motd profile script for guest OS: %w", err) 790 } 791 792 if err := wslPipe(wslmotd, dist, "sh", "-c", "cat > /etc/wslmotd"); err != nil { 793 return fmt.Errorf("could not create a WSL MOTD for guest OS: %w", err) 794 } 795 796 if err := wslPipe(bootstrap, dist, "sh", "-c", 797 "cat > /root/bootstrap; chmod 755 /root/bootstrap"); err != nil { 798 return fmt.Errorf("could not create bootstrap script for guest OS: %w", err) 799 } 800 801 if err := wslPipe(proxyConfigSetup, dist, "sh", "-c", 802 "cat > /usr/local/bin/proxyinit; chmod 755 /usr/local/bin/proxyinit"); err != nil { 803 return fmt.Errorf("could not create proxyinit script for guest OS: %w", err) 804 } 805 806 return nil 807 } 808 809 func writeWslConf(dist string, user string) error { 810 if err := wslPipe(withUser(wslConf, user), dist, "sh", "-c", "cat > /etc/wsl.conf"); err != nil { 811 return fmt.Errorf("could not configure wsl config for guest OS: %w", err) 812 } 813 814 return nil 815 } 816 817 func checkAndInstallWSL(opts machine.InitOptions) (bool, error) { 818 if wutil.IsWSLInstalled() { 819 return true, nil 820 } 821 822 admin := hasAdminRights() 823 824 if !IsWSLFeatureEnabled() { 825 return false, attemptFeatureInstall(opts, admin) 826 } 827 828 skip := false 829 if !opts.ReExec && !admin { 830 fmt.Println("Launching WSL Kernel Install...") 831 if err := launchElevate(wslInstallKernel); err != nil { 832 return false, err 833 } 834 835 skip = true 836 } 837 838 if !skip { 839 if err := installWslKernel(); err != nil { 840 fmt.Fprintf(os.Stderr, wslKernelError, wslInstallKernel) 841 return false, err 842 } 843 844 if opts.ReExec { 845 return false, nil 846 } 847 } 848 849 return true, nil 850 } 851 852 func attemptFeatureInstall(opts machine.InitOptions, admin bool) error { 853 if !winVersionAtLeast(10, 0, 18362) { 854 return errors.New("your version of Windows does not support WSL. Update to Windows 10 Build 19041 or later") 855 } else if !winVersionAtLeast(10, 0, 19041) { 856 fmt.Fprint(os.Stderr, wslOldVersion) 857 return errors.New("the WSL can not be automatically installed") 858 } 859 860 message := "WSL is not installed on this system, installing it.\n\n" 861 862 if !admin { 863 message += "Since you are not running as admin, a new window will open and " + 864 "require you to approve administrator privileges.\n\n" 865 } 866 867 message += "NOTE: A system reboot will be required as part of this process. " + 868 "If you prefer, you may abort now, and perform a manual installation using the \"wsl --install\" command." 869 870 if !opts.ReExec && MessageBox(message, "Podman Machine", false) != 1 { 871 return errors.New("the WSL installation aborted") 872 } 873 874 if !opts.ReExec && !admin { 875 return launchElevate("install the Windows WSL Features") 876 } 877 878 return installWsl() 879 } 880 881 func launchElevate(operation string) error { 882 truncateElevatedOutputFile() 883 err := relaunchElevatedWait() 884 if err != nil { 885 if eerr, ok := err.(*ExitCodeError); ok { 886 if eerr.code == ErrorSuccessRebootRequired { 887 fmt.Println("Reboot is required to continue installation, please reboot at your convenience") 888 return nil 889 } 890 } 891 892 fmt.Fprintf(os.Stderr, "Elevated process failed with error: %v\n\n", err) 893 dumpOutputFile() 894 fmt.Fprintf(os.Stderr, wslInstallError, operation) 895 } 896 return err 897 } 898 899 func installWsl() error { 900 log, err := getElevatedOutputFileWrite() 901 if err != nil { 902 return err 903 } 904 defer log.Close() 905 if err := runCmdPassThroughTee(log, "dism", "/online", "/enable-feature", 906 "/featurename:Microsoft-Windows-Subsystem-Linux", "/all", "/norestart"); isMsiError(err) { 907 return fmt.Errorf("could not enable WSL Feature: %w", err) 908 } 909 910 if err = runCmdPassThroughTee(log, "dism", "/online", "/enable-feature", 911 "/featurename:VirtualMachinePlatform", "/all", "/norestart"); isMsiError(err) { 912 return fmt.Errorf("could not enable Virtual Machine Feature: %w", err) 913 } 914 log.Close() 915 916 return reboot() 917 } 918 919 func installWslKernel() error { 920 log, err := getElevatedOutputFileWrite() 921 if err != nil { 922 return err 923 } 924 defer log.Close() 925 926 message := "Installing WSL Kernel Update" 927 fmt.Println(message) 928 fmt.Fprintln(log, message) 929 930 backoff := 500 * time.Millisecond 931 for i := 0; i < 5; i++ { 932 err = runCmdPassThroughTee(log, "wsl", "--update") 933 if err == nil { 934 break 935 } 936 // In case of unusual circumstances (e.g. race with installer actions) 937 // retry a few times 938 message = "An error occurred attempting the WSL Kernel update, retrying..." 939 fmt.Println(message) 940 fmt.Fprintln(log, message) 941 time.Sleep(backoff) 942 backoff *= 2 943 } 944 945 if err != nil { 946 return fmt.Errorf("could not install WSL Kernel: %w", err) 947 } 948 949 return nil 950 } 951 952 func getElevatedOutputFileName() (string, error) { 953 dir, err := homedir.GetDataHome() 954 if err != nil { 955 return "", err 956 } 957 return filepath.Join(dir, "podman-elevated-output.log"), nil 958 } 959 960 func dumpOutputFile() { 961 file, err := getElevatedOutputFileRead() 962 if err != nil { 963 logrus.Debug("could not find elevated child output file") 964 return 965 } 966 defer file.Close() 967 _, _ = io.Copy(os.Stdout, file) 968 } 969 970 func getElevatedOutputFileRead() (*os.File, error) { 971 return getElevatedOutputFile(os.O_RDONLY) 972 } 973 974 func getElevatedOutputFileWrite() (*os.File, error) { 975 return getElevatedOutputFile(os.O_WRONLY | os.O_CREATE | os.O_APPEND) 976 } 977 978 func appendOutputIfError(write bool, err error) { 979 if write && err == nil { 980 return 981 } 982 983 if file, check := getElevatedOutputFileWrite(); check == nil { 984 defer file.Close() 985 fmt.Fprintf(file, "Error: %v\n", err) 986 } 987 } 988 989 func truncateElevatedOutputFile() error { 990 name, err := getElevatedOutputFileName() 991 if err != nil { 992 return err 993 } 994 995 return os.Truncate(name, 0) 996 } 997 998 func getElevatedOutputFile(mode int) (*os.File, error) { 999 name, err := getElevatedOutputFileName() 1000 if err != nil { 1001 return nil, err 1002 } 1003 1004 dir, err := homedir.GetDataHome() 1005 if err != nil { 1006 return nil, err 1007 } 1008 1009 if err = os.MkdirAll(dir, 0755); err != nil { 1010 return nil, err 1011 } 1012 1013 return os.OpenFile(name, mode, 0644) 1014 } 1015 1016 func isMsiError(err error) bool { 1017 if err == nil { 1018 return false 1019 } 1020 1021 if eerr, ok := err.(*exec.ExitError); ok { 1022 switch eerr.ExitCode() { 1023 case 0: 1024 fallthrough 1025 case ErrorSuccessRebootInitiated: 1026 fallthrough 1027 case ErrorSuccessRebootRequired: 1028 return false 1029 } 1030 } 1031 1032 return true 1033 } 1034 func toDist(name string) string { 1035 if !strings.HasPrefix(name, "podman") { 1036 name = "podman-" + name 1037 } 1038 return name 1039 } 1040 1041 func withUser(s string, user string) string { 1042 return strings.ReplaceAll(s, "[USER]", user) 1043 } 1044 1045 func wslInvoke(dist string, arg ...string) error { 1046 newArgs := []string{"-u", "root", "-d", dist} 1047 newArgs = append(newArgs, arg...) 1048 return runCmdPassThrough("wsl", newArgs...) 1049 } 1050 1051 func wslPipe(input string, dist string, arg ...string) error { 1052 newArgs := []string{"-u", "root", "-d", dist} 1053 newArgs = append(newArgs, arg...) 1054 return pipeCmdPassThrough("wsl", input, newArgs...) 1055 } 1056 1057 func wslCreateKeys(identityPath string, dist string) (string, error) { 1058 return machine.CreateSSHKeysPrefix(identityPath, true, false, "wsl", "-u", "root", "-d", dist) 1059 } 1060 1061 func runCmdPassThrough(name string, arg ...string) error { 1062 logrus.Debugf("Running command: %s %v", name, arg) 1063 cmd := exec.Command(name, arg...) 1064 cmd.Stdin = os.Stdin 1065 cmd.Stdout = os.Stdout 1066 cmd.Stderr = os.Stderr 1067 return cmd.Run() 1068 } 1069 1070 func runCmdPassThroughTee(out io.Writer, name string, arg ...string) error { 1071 logrus.Debugf("Running command: %s %v", name, arg) 1072 1073 // TODO - Perhaps improve this with a conpty pseudo console so that 1074 // dism installer text bars mirror console behavior (redraw) 1075 cmd := exec.Command(name, arg...) 1076 cmd.Stdin = os.Stdin 1077 cmd.Stdout = io.MultiWriter(os.Stdout, out) 1078 cmd.Stderr = io.MultiWriter(os.Stderr, out) 1079 return cmd.Run() 1080 } 1081 1082 func pipeCmdPassThrough(name string, input string, arg ...string) error { 1083 logrus.Debugf("Running command: %s %v", name, arg) 1084 cmd := exec.Command(name, arg...) 1085 cmd.Stdin = strings.NewReader(input) 1086 cmd.Stdout = os.Stdout 1087 cmd.Stderr = os.Stderr 1088 return cmd.Run() 1089 } 1090 1091 func setupWslProxyEnv() (hasProxy bool) { 1092 current, _ := os.LookupEnv("WSLENV") 1093 for _, key := range config.ProxyEnv { 1094 if value, _ := os.LookupEnv(key); len(value) < 1 { 1095 continue 1096 } 1097 1098 hasProxy = true 1099 delim := "" 1100 if len(current) > 0 { 1101 delim = ":" 1102 } 1103 current = fmt.Sprintf("%s%s%s/u", current, delim, key) 1104 } 1105 if hasProxy { 1106 os.Setenv("WSLENV", current) 1107 } 1108 return 1109 } 1110 1111 func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) { 1112 // If one setting fails to be applied, the others settings will not fail and still be applied. 1113 // The setting(s) that failed to be applied will have its errors returned in setErrors 1114 var setErrors []error 1115 1116 v.lock.Lock() 1117 defer v.lock.Unlock() 1118 1119 if opts.Rootful != nil && v.Rootful != *opts.Rootful { 1120 err := v.setRootful(*opts.Rootful) 1121 if err != nil { 1122 setErrors = append(setErrors, fmt.Errorf("setting rootful option: %w", err)) 1123 } else { 1124 if v.isRunning() { 1125 logrus.Warn("restart is necessary for rootful change to go into effect") 1126 } 1127 v.Rootful = *opts.Rootful 1128 } 1129 } 1130 1131 if opts.CPUs != nil { 1132 setErrors = append(setErrors, errors.New("changing CPUs not supported for WSL machines")) 1133 } 1134 1135 if opts.Memory != nil { 1136 setErrors = append(setErrors, errors.New("changing memory not supported for WSL machines")) 1137 } 1138 1139 if opts.USBs != nil { 1140 setErrors = append(setErrors, errors.New("changing USBs not supported for WSL machines")) 1141 } 1142 1143 if opts.DiskSize != nil { 1144 setErrors = append(setErrors, errors.New("changing disk size not supported for WSL machines")) 1145 } 1146 1147 if opts.UserModeNetworking != nil && *opts.UserModeNetworking != v.UserModeNetworking { 1148 update := true 1149 if v.isRunning() { 1150 update = false 1151 setErrors = append(setErrors, fmt.Errorf("user-mode networking can only be changed when the machine is not running")) 1152 } else { 1153 dist := toDist(v.Name) 1154 if err := changeDistUserModeNetworking(dist, v.RemoteUsername, v.ImagePath, *opts.UserModeNetworking); err != nil { 1155 update = false 1156 setErrors = append(setErrors, err) 1157 } 1158 } 1159 1160 if update { 1161 v.UserModeNetworking = *opts.UserModeNetworking 1162 } 1163 } 1164 err := v.writeConfig() 1165 if err != nil { 1166 setErrors = append(setErrors, err) 1167 } 1168 1169 if len(setErrors) > 0 { 1170 return setErrors, setErrors[0] 1171 } 1172 return setErrors, nil 1173 } 1174 1175 func (v *MachineVM) Start(name string, opts machine.StartOptions) error { 1176 v.lock.Lock() 1177 defer v.lock.Unlock() 1178 1179 if v.isRunning() { 1180 return machine.ErrVMAlreadyRunning 1181 } 1182 1183 dist := toDist(name) 1184 useProxy := setupWslProxyEnv() 1185 if err := configureProxy(dist, useProxy, opts.Quiet); err != nil { 1186 return err 1187 } 1188 1189 if !machine.IsLocalPortAvailable(v.Port) { 1190 logrus.Warnf("SSH port conflict detected, reassigning a new port") 1191 if err := v.reassignSshPort(); err != nil { 1192 return err 1193 } 1194 } 1195 1196 // Startup user-mode networking if enabled 1197 if err := v.startUserModeNetworking(); err != nil { 1198 return err 1199 } 1200 1201 err := wslInvoke(dist, "/root/bootstrap") 1202 if err != nil { 1203 return fmt.Errorf("the WSL bootstrap script failed: %w", err) 1204 } 1205 1206 if !v.Rootful && !opts.NoInfo { 1207 fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n") 1208 fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n") 1209 fmt.Printf("issues with non-podman clients, you can switch using the following command: \n") 1210 1211 suffix := "" 1212 if name != machine.DefaultMachineName { 1213 suffix = " " + name 1214 } 1215 fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix) 1216 } 1217 winProxyOpts := machine.WinProxyOpts{ 1218 Name: v.Name, 1219 IdentityPath: v.IdentityPath, 1220 Port: v.Port, 1221 RemoteUsername: v.RemoteUsername, 1222 Rootful: v.Rootful, 1223 VMType: vmtype, 1224 } 1225 machine.LaunchWinProxy(winProxyOpts, opts.NoInfo) 1226 1227 _, _, err = v.updateTimeStamps(true) 1228 return err 1229 } 1230 1231 func obtainGlobalConfigLock() (*fileLock, error) { 1232 lockDir, err := machine.GetGlobalDataDir() 1233 if err != nil { 1234 return nil, err 1235 } 1236 1237 // Lock file needs to be above all backends 1238 // TODO: This should be changed to a common.Config lock mechanism when available 1239 return lockFile(filepath.Join(lockDir, "podman-config.lck")) 1240 } 1241 1242 func (v *MachineVM) reassignSshPort() error { 1243 dist := toDist(v.Name) 1244 newPort, err := machine.AllocateMachinePort() 1245 if err != nil { 1246 return err 1247 } 1248 1249 success := false 1250 defer func() { 1251 if !success { 1252 if err := machine.ReleaseMachinePort(newPort); err != nil { 1253 logrus.Warnf("could not release port allocation as part of failure rollback (%d): %w", newPort, err) 1254 } 1255 } 1256 }() 1257 1258 // We need to prevent racing connection updates to containers.conf globally 1259 // across all backends to prevent connection overwrites 1260 flock, err := obtainGlobalConfigLock() 1261 if err != nil { 1262 return fmt.Errorf("could not obtain global lock: %w", err) 1263 } 1264 defer flock.unlock() 1265 1266 // Write a transient invalid port, to force a retry on failure 1267 oldPort := v.Port 1268 v.Port = 0 1269 if err := v.writeConfig(); err != nil { 1270 return err 1271 } 1272 1273 if err := machine.ReleaseMachinePort(oldPort); err != nil { 1274 logrus.Warnf("could not release current ssh port allocation (%d): %w", oldPort, err) 1275 } 1276 1277 if err := wslInvoke(dist, "sh", "-c", fmt.Sprintf(changePort, newPort)); err != nil { 1278 return fmt.Errorf("could not change SSH port for guest OS: %w", err) 1279 } 1280 1281 v.Port = newPort 1282 uris, names := constructSSHUris(v) 1283 for i := 0; i < 2; i++ { 1284 if err := machine.ChangeConnectionURI(names[i], &uris[i]); err != nil { 1285 return err 1286 } 1287 } 1288 1289 // Write updated port back 1290 if err := v.writeConfig(); err != nil { 1291 return err 1292 } 1293 1294 success = true 1295 1296 return nil 1297 } 1298 1299 func IsWSLFeatureEnabled() bool { 1300 return wutil.SilentExec("wsl", "--set-default-version", "2") == nil 1301 } 1302 1303 func isWSLRunning(dist string) (bool, error) { 1304 return wslCheckExists(dist, true) 1305 } 1306 1307 func isWSLExist(dist string) (bool, error) { 1308 return wslCheckExists(dist, false) 1309 } 1310 1311 func wslCheckExists(dist string, running bool) (bool, error) { 1312 all, err := getAllWSLDistros(running) 1313 if err != nil { 1314 return false, err 1315 } 1316 1317 _, exists := all[dist] 1318 return exists, nil 1319 } 1320 1321 func getAllWSLDistros(running bool) (map[string]struct{}, error) { 1322 args := []string{"-l", "--quiet"} 1323 if running { 1324 args = append(args, "--running") 1325 } 1326 cmd := exec.Command("wsl", args...) 1327 out, err := cmd.StdoutPipe() 1328 if err != nil { 1329 return nil, err 1330 } 1331 if err = cmd.Start(); err != nil { 1332 return nil, err 1333 } 1334 1335 all := make(map[string]struct{}) 1336 scanner := bufio.NewScanner(transform.NewReader(out, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder())) 1337 for scanner.Scan() { 1338 fields := strings.Fields(scanner.Text()) 1339 if len(fields) > 0 { 1340 all[fields[0]] = struct{}{} 1341 } 1342 } 1343 1344 _ = cmd.Wait() 1345 1346 return all, nil 1347 } 1348 1349 func isSystemdRunning(dist string) (bool, error) { 1350 cmd := exec.Command("wsl", "-u", "root", "-d", dist, "sh") 1351 cmd.Stdin = strings.NewReader(sysdpid + "\necho $SYSDPID\n") 1352 out, err := cmd.StdoutPipe() 1353 if err != nil { 1354 return false, err 1355 } 1356 if err = cmd.Start(); err != nil { 1357 return false, err 1358 } 1359 scanner := bufio.NewScanner(out) 1360 result := false 1361 if scanner.Scan() { 1362 text := scanner.Text() 1363 i, err := strconv.Atoi(text) 1364 if err == nil && i > 0 { 1365 result = true 1366 } 1367 } 1368 1369 _ = cmd.Wait() 1370 1371 return result, nil 1372 } 1373 1374 func (v *MachineVM) Stop(name string, _ machine.StopOptions) error { 1375 v.lock.Lock() 1376 defer v.lock.Unlock() 1377 1378 dist := toDist(v.Name) 1379 1380 wsl, err := isWSLRunning(dist) 1381 if err != nil { 1382 return err 1383 } 1384 1385 sysd := false 1386 if wsl { 1387 sysd, err = isSystemdRunning(dist) 1388 if err != nil { 1389 return err 1390 } 1391 } 1392 1393 if !wsl || !sysd { 1394 return nil 1395 } 1396 1397 // Stop user-mode networking if enabled 1398 if err := v.stopUserModeNetworking(dist); err != nil { 1399 fmt.Fprintf(os.Stderr, "Could not cleanly stop user-mode networking: %s\n", err.Error()) 1400 } 1401 1402 _, _, _ = v.updateTimeStamps(true) 1403 1404 if err := machine.StopWinProxy(v.Name, vmtype); err != nil { 1405 fmt.Fprintf(os.Stderr, "Could not stop API forwarding service (win-sshproxy.exe): %s\n", err.Error()) 1406 } 1407 1408 cmd := exec.Command("wsl", "-u", "root", "-d", dist, "sh") 1409 cmd.Stdin = strings.NewReader(waitTerm) 1410 if err = cmd.Start(); err != nil { 1411 return fmt.Errorf("executing wait command: %w", err) 1412 } 1413 1414 exitCmd := exec.Command("wsl", "-u", "root", "-d", dist, "/usr/local/bin/enterns", "systemctl", "exit", "0") 1415 if err = exitCmd.Run(); err != nil { 1416 return fmt.Errorf("stopping sysd: %w", err) 1417 } 1418 1419 if err = cmd.Wait(); err != nil { 1420 return err 1421 } 1422 1423 return terminateDist(dist) 1424 } 1425 1426 func terminateDist(dist string) error { 1427 cmd := exec.Command("wsl", "--terminate", dist) 1428 return cmd.Run() 1429 } 1430 1431 func unregisterDist(dist string) error { 1432 cmd := exec.Command("wsl", "--unregister", dist) 1433 return cmd.Run() 1434 } 1435 1436 func (v *MachineVM) State(bypass bool) (machine.Status, error) { 1437 if v.isRunning() { 1438 return machine.Running, nil 1439 } 1440 1441 return machine.Stopped, nil 1442 } 1443 1444 //nolint:cyclop 1445 func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) { 1446 var files []string 1447 1448 if v.isRunning() { 1449 if !opts.Force { 1450 return "", nil, &machine.ErrVMRunningCannotDestroyed{Name: v.Name} 1451 } 1452 if err := v.Stop(v.Name, machine.StopOptions{}); err != nil { 1453 return "", nil, err 1454 } 1455 } 1456 1457 v.lock.Lock() 1458 defer v.lock.Unlock() 1459 1460 // Collect all the files that need to be destroyed 1461 if !opts.SaveKeys { 1462 files = append(files, v.IdentityPath, v.IdentityPath+".pub") 1463 } 1464 if !opts.SaveImage { 1465 files = append(files, v.ImagePath) 1466 } 1467 1468 vmConfigDir, err := machine.GetConfDir(vmtype) 1469 if err != nil { 1470 return "", nil, err 1471 } 1472 files = append(files, filepath.Join(vmConfigDir, v.Name+".json")) 1473 1474 vmDataDir, err := machine.GetDataDir(vmtype) 1475 if err != nil { 1476 return "", nil, err 1477 } 1478 files = append(files, filepath.Join(vmDataDir, "wsldist", v.Name)) 1479 1480 confirmationMessage := "\nThe following files will be deleted:\n\n" 1481 for _, msg := range files { 1482 confirmationMessage += msg + "\n" 1483 } 1484 1485 confirmationMessage += "\n" 1486 return confirmationMessage, func() error { 1487 if err := machine.RemoveConnections(v.Name, v.Name+"-root"); err != nil { 1488 logrus.Error(err) 1489 } 1490 if err := runCmdPassThrough("wsl", "--unregister", toDist(v.Name)); err != nil { 1491 logrus.Error(err) 1492 } 1493 for _, f := range files { 1494 if err := utils.GuardedRemoveAll(f); err != nil { 1495 logrus.Error(err) 1496 } 1497 } 1498 if err := machine.ReleaseMachinePort(v.Port); err != nil { 1499 logrus.Warnf("could not release port allocation as part of removal (%d): %w", v.Port, err) 1500 } 1501 1502 return nil 1503 }, nil 1504 } 1505 1506 func (v *MachineVM) isRunning() bool { 1507 dist := toDist(v.Name) 1508 1509 wsl, err := isWSLRunning(dist) 1510 if err != nil { 1511 return false 1512 } 1513 1514 sysd := false 1515 if wsl { 1516 sysd, err = isSystemdRunning(dist) 1517 1518 if err != nil { 1519 return false 1520 } 1521 } 1522 1523 return sysd 1524 } 1525 1526 // SSH opens an interactive SSH session to the vm specified. 1527 // Added ssh function to VM interface: pkg/machine/config/go : line 58 1528 func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error { 1529 if !v.isRunning() { 1530 return fmt.Errorf("vm %q is not running.", v.Name) 1531 } 1532 1533 username := opts.Username 1534 if username == "" { 1535 username = v.RemoteUsername 1536 } 1537 1538 sshDestination := username + "@localhost" 1539 port := strconv.Itoa(v.Port) 1540 1541 args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, 1542 "-o", "IdentitiesOnly yes", 1543 "-o", "UserKnownHostsFile /dev/null", 1544 "-o", "StrictHostKeyChecking no"} 1545 if len(opts.Args) > 0 { 1546 args = append(args, opts.Args...) 1547 } else { 1548 fmt.Printf("Connecting to vm %s. To close connection, use `~.` or `exit`\n", v.Name) 1549 } 1550 1551 cmd := exec.Command("ssh", args...) 1552 logrus.Debugf("Executing: ssh %v\n", args) 1553 1554 cmd.Stdout = os.Stdout 1555 cmd.Stderr = os.Stderr 1556 cmd.Stdin = os.Stdin 1557 1558 return cmd.Run() 1559 } 1560 1561 func (v *MachineVM) updateTimeStamps(updateLast bool) (time.Time, time.Time, error) { 1562 var err error 1563 if updateLast { 1564 v.LastUp = time.Now() 1565 err = v.writeConfig() 1566 } 1567 1568 return v.Created, v.LastUp, err 1569 } 1570 1571 func getDiskSize(vm *MachineVM) uint64 { 1572 vmDataDir, err := machine.GetDataDir(vmtype) 1573 if err != nil { 1574 return 0 1575 } 1576 distDir := filepath.Join(vmDataDir, "wsldist") 1577 disk := filepath.Join(distDir, vm.Name, "ext4.vhdx") 1578 info, err := os.Stat(disk) 1579 if err != nil { 1580 return 0 1581 } 1582 return uint64(info.Size()) 1583 } 1584 1585 func getCPUs(vm *MachineVM) (uint64, error) { 1586 dist := toDist(vm.Name) 1587 if run, _ := isWSLRunning(dist); !run { 1588 return 0, nil 1589 } 1590 cmd := exec.Command("wsl", "-u", "root", "-d", dist, "nproc") 1591 out, err := cmd.StdoutPipe() 1592 if err != nil { 1593 return 0, err 1594 } 1595 if err = cmd.Start(); err != nil { 1596 return 0, err 1597 } 1598 scanner := bufio.NewScanner(out) 1599 var result string 1600 for scanner.Scan() { 1601 result = scanner.Text() 1602 } 1603 _ = cmd.Wait() 1604 1605 ret, err := strconv.Atoi(result) 1606 return uint64(ret), err 1607 } 1608 1609 func getMem(vm *MachineVM) (uint64, error) { 1610 dist := toDist(vm.Name) 1611 if run, _ := isWSLRunning(dist); !run { 1612 return 0, nil 1613 } 1614 cmd := exec.Command("wsl", "-u", "root", "-d", dist, "cat", "/proc/meminfo") 1615 out, err := cmd.StdoutPipe() 1616 if err != nil { 1617 return 0, err 1618 } 1619 if err = cmd.Start(); err != nil { 1620 return 0, err 1621 } 1622 scanner := bufio.NewScanner(out) 1623 var ( 1624 total, available uint64 1625 t, a int 1626 ) 1627 for scanner.Scan() { 1628 fields := strings.Fields(scanner.Text()) 1629 if strings.HasPrefix(fields[0], "MemTotal") && len(fields) >= 2 { 1630 t, err = strconv.Atoi(fields[1]) 1631 total = uint64(t) * 1024 1632 } else if strings.HasPrefix(fields[0], "MemAvailable") && len(fields) >= 2 { 1633 a, err = strconv.Atoi(fields[1]) 1634 available = uint64(a) * 1024 1635 } 1636 if err != nil { 1637 break 1638 } 1639 } 1640 _ = cmd.Wait() 1641 1642 return total - available, err 1643 } 1644 1645 func (v *MachineVM) setRootful(rootful bool) error { 1646 if err := machine.SetRootful(rootful, v.Name, v.Name+"-root"); err != nil { 1647 return err 1648 } 1649 1650 dist := toDist(v.Name) 1651 return v.setupPodmanDockerSock(dist, rootful) 1652 } 1653 1654 // Inspect returns verbose detail about the machine 1655 func (v *MachineVM) Inspect() (*machine.InspectInfo, error) { 1656 state, err := v.State(false) 1657 if err != nil { 1658 return nil, err 1659 } 1660 1661 connInfo := new(machine.ConnectionConfig) 1662 machinePipe := toDist(v.Name) 1663 connInfo.PodmanPipe = &define.VMFile{Path: `\\.\pipe\` + machinePipe} 1664 1665 created, lastUp, _ := v.updateTimeStamps(state == machine.Running) 1666 return &machine.InspectInfo{ 1667 ConfigPath: define.VMFile{Path: v.ConfigPath}, 1668 ConnectionInfo: *connInfo, 1669 Created: created, 1670 Image: machine.ImageConfig{ 1671 ImagePath: define.VMFile{Path: v.ImagePath}, 1672 ImageStream: v.ImageStream, 1673 }, 1674 LastUp: lastUp, 1675 Name: v.Name, 1676 Resources: v.getResources(), 1677 SSHConfig: v.SSHConfig, 1678 State: state, 1679 UserModeNetworking: v.UserModeNetworking, 1680 Rootful: v.Rootful, 1681 }, nil 1682 } 1683 1684 func (v *MachineVM) getResources() (resources machine.ResourceConfig) { 1685 resources.CPUs, _ = getCPUs(v) 1686 resources.Memory, _ = getMem(v) 1687 resources.DiskSize = getDiskSize(v) 1688 return 1689 }