diff --git a/pkg/distros/opensuse_tumbleweed.go b/pkg/distros/opensuse_tumbleweed.go index a69b1e2..715de06 100644 --- a/pkg/distros/opensuse_tumbleweed.go +++ b/pkg/distros/opensuse_tumbleweed.go @@ -1,15 +1,8 @@ package distros import ( - "compress/gzip" "errors" "fmt" - "io" - "net/http" - "os" - "path/filepath" - "regexp" - "strings" "github.com/rs/zerolog/log" "libguestfs.org/guestfs" @@ -46,154 +39,27 @@ func (mgr *OpensuseTumbleweedTemplateManager) DownloadTemplate(version, codename ) } -const ossPackagesBaseURL = "https://download.opensuse.org/tumbleweed/repo/oss" - -func (mgr *OpensuseTumbleweedTemplateManager) getSystemdNetworkPackageURL() (string, error) { - const indexURL = ossPackagesBaseURL + "/INDEX.gz" - mgr.logger.Debug().Msg("Downloading " + indexURL) - resp, err := http.Get(indexURL) +func (mgr *OpensuseTumbleweedTemplateManager) InstallNetworkManager(handle *guestfs.Guestfs) error { + args := []string{"zypper", "--non-interactive", "install", "NetworkManager"} + _, err := mgr.logAndRunCommand(handle, args) if err != nil { - return "", err + return fmt.Errorf("Could not install NetworkManager: %w", err) } - defer resp.Body.Close() - gzipReader, err := gzip.NewReader(resp.Body) + // Make sure dhcp-client is installed because we use it as the + // DHCP backend + dhclientIsPresent, err := handle.Is_file("/sbin/dhclient", nil) if err != nil { - return "", err + return fmt.Errorf("Could not determine if /sbin/dhclient is a file: %w", err) } - defer gzipReader.Close() - body, err := io.ReadAll(gzipReader) - if err != nil { - return "", err + if !dhclientIsPresent { + return errors.New("dhclient is not present") } - pattern := regexp.MustCompile(`(?m)^\./x86_64/systemd-network-(?:[\d.-]+)\.x86_64\.rpm$`) - match := pattern.Find(body) - if match == nil { - return "", errors.New("Could not find systemd-network package in OpenSUSE Tumbleweed index") - } - packageURL := ossPackagesBaseURL + "/" + string(match[2:]) - return packageURL, nil -} - -func (mgr *OpensuseTumbleweedTemplateManager) downloadSystemdNetworkPackage() (string, error) { - packageURL, err := mgr.getSystemdNetworkPackageURL() - if err != nil { - return "", err - } - mgr.logger.Debug().Msg("Downloading " + packageURL) - resp, err := http.Get(packageURL) - if err != nil { - return "", err - } - defer resp.Body.Close() - // TODO: download to somewhere in /tmp - const path = "./systemd-network.rpm" - out, err := os.Create(path) - if err != nil { - return "", err - } - defer out.Close() - _, err = io.Copy(out, resp.Body) - if err != nil { - return "", err - } - return path, nil -} - -func (mgr *OpensuseTumbleweedTemplateManager) logAndRunCommand(handle *guestfs.Guestfs, args []string) error { - mgr.logger.Debug().Msg("Running command `" + strings.Join(args, " ") + "`") - _, err := handle.Command(args) - return err -} - -func (mgr *OpensuseTumbleweedTemplateManager) addSystemUserAndGroup(handle *guestfs.Guestfs, name, uid, gid string) error { - matches, err := handle.Grep("^"+name, "/etc/group", nil) - if err != nil { - return fmt.Errorf("Could not grep /etc/group: %w", err) - } - if len(matches) == 0 { - err = mgr.logAndRunCommand(handle, []string{ - "groupadd", - "--system", - "--gid", gid, - name, - }) - if err != nil { - return err - } - } - - matches, err = handle.Grep("^"+name, "/etc/passwd", nil) - if err != nil { - return fmt.Errorf("Could not grep /etc/passwd: %w", err) - } - if len(matches) == 0 { - err = mgr.logAndRunCommand(handle, []string{ - "useradd", - "--system", - "--no-user-group", - "--uid", uid, - "--gid", gid, - "--home-dir", "/run/systemd", - "--shell", "/usr/sbin/nologin", - name, - }) - if err != nil { - return err - } - } - return nil -} - -// Workaround for bug in VM image: systemd-network user and group are not -// present, and are not created when the systemd-network package is installed -// Also see: https://github.com/lima-vm/lima/issues/1496 -// -// https://bugzilla.opensuse.org/show_bug.cgi?id=1203393 -func (mgr *OpensuseTumbleweedTemplateManager) addSystemdNetworkUsersAndGroups(handle *guestfs.Guestfs) error { - // UIDs and GIDs were determined by installing systemd-network in - // a VM booted from a live ISO image - if err := mgr.addSystemUserAndGroup(handle, "systemd-network", "470", "470"); err != nil { - return err - } - if err := mgr.addSystemUserAndGroup(handle, "systemd-resolve", "469", "469"); err != nil { - return err - } - return nil -} - -func (mgr *OpensuseTumbleweedTemplateManager) InstallSystemdNetworkd(handle *guestfs.Guestfs) error { - localPath, err := mgr.downloadSystemdNetworkPackage() - if err != nil { - return err - } - mgr.logger.Debug().Msg("Copying " + localPath + " to /tmp in VM") - err = handle.Copy_in(localPath, "/tmp") - if err != nil { - return err - } - mgr.logger.Debug().Msg("Deleting " + localPath) - err = os.Remove(localPath) - if err != nil { - return err - } - vmPath := "/tmp/" + filepath.Base(localPath) - args := []string{"zypper", "--no-remote", "--non-interactive", "install", vmPath} - err = mgr.logAndRunCommand(handle, args) - if err != nil { - return err - } - mgr.logger.Debug().Msg("Deleting " + vmPath + " in VM") - err = handle.Rm(vmPath) - if err != nil { - return err - } - return mgr.addSystemdNetworkUsersAndGroups(handle) -} - -func (mgr *OpensuseTumbleweedTemplateManager) NeedsDynamicResolvConf() bool { - // cloud-init won't work if we overwrite /etc/resolv.conf. - // I'm not sure why it works on other distros but not on this one. - return true + // Workaround for bug in cloud-init which prevents the NetworkManager + // backend from being selected if NetworkManager.conf does not exist + // https://github.com/canonical/cloud-init/blob/23.1.x/cloudinit/net/network_manager.py#L381 + const cfgFilePath = "/etc/NetworkManager/NetworkManager.conf" + mgr.logger.Debug().Msg("Creating " + cfgFilePath) + return handle.Touch(cfgFilePath) } func (mgr *OpensuseTumbleweedTemplateManager) removeWelcomeMessage(handle *guestfs.Guestfs) error { @@ -203,17 +69,16 @@ func (mgr *OpensuseTumbleweedTemplateManager) removeWelcomeMessage(handle *guest } func (mgr *OpensuseTumbleweedTemplateManager) CommandToUpdatePackageCache() []string { - return []string{"sudo", "zypper", "refresh"} + return []string{"sudo", "zypper", "refresh", "--force"} } -func (mgr *OpensuseTumbleweedTemplateManager) PerformDistroSpecificModifications(handle *guestfs.Guestfs) (err error) { - unitPath, err := getSystemdUnitPath(handle, "wickedd.service") +func (mgr *OpensuseTumbleweedTemplateManager) PerformDistroSpecificModifications(handle *guestfs.Guestfs) error { + // Newer Tumbleweed images shouldn't be using wickedd anymore + wickeddExists, err := handle.Is_file("/lib/systemd/system/wickedd.service", nil) if err != nil { - return + return err } - if unitPath != "" { - // OpenSUSE Tumbleweed images should be using NetworkManager - // or systemd-networkd (after we install it) + if wickeddExists { return errors.New("wickedd.service was unexpectedly found") } return mgr.removeWelcomeMessage(handle) diff --git a/pkg/distros/resources/97_csclub_resolv_conf.sh b/pkg/distros/resources/97_csclub_resolv_conf.sh deleted file mode 100644 index 4813108..0000000 --- a/pkg/distros/resources/97_csclub_resolv_conf.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -set -ex - -RESOLV_CONF=/run/netconfig/resolv.conf - -sed -i '/^search .*/d' $RESOLV_CONF -sed -i '/^options .*/d' $RESOLV_CONF -cat <> $RESOLV_CONF -search csclub.uwaterloo.ca uwaterloo.ca -options ndots:2 -EOF diff --git a/pkg/distros/template_manager.go b/pkg/distros/template_manager.go index dd04fe9..42a9f44 100644 --- a/pkg/distros/template_manager.go +++ b/pkg/distros/template_manager.go @@ -52,10 +52,6 @@ type ITemplateManager interface { // An IDistroSpecificTemplateManager performs the distro-specific tasks // when modifying a VM template. It is used by the generic TemplateManager. type IDistroSpecificTemplateManager interface { - // On some distros, like OpenSUSE Tumbleweed, we cannot overwrite - // /etc/resolv.conf or else cloud-init will fail. Such distros should - // override this method to return true. - NeedsDynamicResolvConf() bool // GetLatestVersion returns the version number and codename of the // latest version of particular OS (e.g. version="22.04", codename="jammy") GetLatestVersion() (version string, codename string, err error) @@ -70,21 +66,14 @@ type IDistroSpecificTemplateManager interface { // (but does not upgrade) the packages for this distro, e.g. // "sudo apt update". CommandToUpdatePackageCache() []string - // InstallSystemdNetworkd installs the OS-specific package for - // systemd-networkd. Since the VM does not have network access, this - // will require downloading the package and copying it into the VM. - InstallSystemdNetworkd(handle *guestfs.Guestfs) error + // InstallNetworkManager installs the OS-specific package for + // NetworkManager. + InstallNetworkManager(handle *guestfs.Guestfs) error } -func (mgr *TemplateManager) NeedsDynamicResolvConf() bool { - // Assume false by default. Individual distros should override this - // method if necessary. - return false -} - -func (mgr *TemplateManager) InstallSystemdNetworkd(handle *guestfs.Guestfs) error { +func (mgr *TemplateManager) InstallNetworkManager(handle *guestfs.Guestfs) error { // Individual distros should override this method if necessary - return errors.New("InstallSystemdNetworkd: not implemented for this distro") + return errors.New("InstallNetworkManager: not implemented for this distro") } func (mgr *TemplateManager) DownloadTemplateGeneric(filename, url string) (path string, err error) { @@ -175,6 +164,12 @@ func (mgr *TemplateManager) getGuestfsMountedHandle(filename string) (handle *gu if err != nil { return } + // Needed to install packages in VM + log.Debug().Msg("Enabling network access") + err = handle.Set_network(true) + if err != nil { + return + } log.Debug().Msg("Adding drive " + filename) if err = handle.Add_drive(filename, nil); err != nil { return @@ -279,141 +274,37 @@ func isSameFile(handle *guestfs.Guestfs, path1, path2 string) (bool, error) { var systemdDirPrefixes = []string{"/etc", "/lib"} -func getSystemdUnitPath(handle *guestfs.Guestfs, unit string) (string, error) { - for _, dirPrefix := range systemdDirPrefixes { - possibleUnitPath := dirPrefix + "/systemd/system/" + unit - exists, err := handle.Is_file(possibleUnitPath, nil) - if err != nil { - return "", fmt.Errorf("could not determine if %s is a file: %w", possibleUnitPath, err) - } - if exists { - return possibleUnitPath, nil - } - } - return "", nil -} - -func getSystemdServiceWantedBy(handle *guestfs.Guestfs, unitPath string) (systemdUnitInstallInfo, error) { - installInfo := systemdUnitInstallInfo{} - content, err := handle.Cat(unitPath) - if err != nil { - return installInfo, fmt.Errorf("could not read %s: %w", unitPath, err) - } - lines := strings.Split(content, "\n") - for _, line := range lines { - if strings.HasPrefix(line, "WantedBy=") { - if installInfo.WantedBy != "" { - return installInfo, fmt.Errorf("multiple WantedBy directives found in %s", unitPath) - } - installInfo.WantedBy = line[len("WantedBy="):] - } else if strings.HasPrefix(line, "Also=") { - installInfo.Also = append(installInfo.Also, line[len("Also="):]) - } - } - return installInfo, nil -} - -type systemdUnitInstallInfo struct { - WantedBy string - Also []string -} - -type systemdUnitInfo struct { - UnitPath string - Enabled bool - systemdUnitInstallInfo -} - -// Returns (nil, nil) if the unit does not exist -func getSystemdUnitInfo(handle *guestfs.Guestfs, unit string) (*systemdUnitInfo, error) { - unitPath, err := getSystemdUnitPath(handle, unit) - if err != nil || unitPath == "" { - return nil, err - } - installInfo, err := getSystemdServiceWantedBy(handle, unitPath) - if err != nil { - return nil, err - } - if installInfo.WantedBy == "" { - return nil, fmt.Errorf("could not find WantedBy property for %s", unitPath) - } - linkName := "" - for _, dirPrefix := range systemdDirPrefixes { - possibleLinkName := dirPrefix + "/systemd/system/" + installInfo.WantedBy + ".wants/" + unit - isSymlink, err := handle.Is_symlink(possibleLinkName) - if err != nil { - return nil, fmt.Errorf("could not determine if %s is a symlink: %w", possibleLinkName, err) - } - if isSymlink { - linkName = possibleLinkName - break - } - } - unitInfo := &systemdUnitInfo{ - UnitPath: unitPath, - systemdUnitInstallInfo: installInfo, - } - if linkName != "" { - target, err := handle.Readlink(linkName) - if err != nil { - return nil, fmt.Errorf("could not read link at %s: %w", linkName, err) - } - // The target might be in /usr/lib, which will likely be a symlink - // to /lib. So we can't compare the paths directly. - targetIsUnitFile, err := isSameFile(handle, target, unitPath) - if err != nil { - return nil, err - } - if !targetIsUnitFile { - return nil, fmt.Errorf("%s is not the same as %s", target, unitPath) - } - unitInfo.Enabled = true - } - return unitInfo, nil +func (mgr *TemplateManager) logAndRunCommand(handle *guestfs.Guestfs, args []string) (string, error) { + mgr.logger.Debug().Msg("Running command `" + strings.Join(args, " ") + "`") + return handle.Command(args) } func (mgr *TemplateManager) enableSystemdUnit(handle *guestfs.Guestfs, unit string) error { - unitInfo, err := getSystemdUnitInfo(handle, unit) - if err != nil { - return err - } - if unitInfo == nil { - return fmt.Errorf("Unit %s does not exist", unit) - } - if unitInfo.Enabled { - return nil - } - if unitInfo.WantedBy == "" { - return fmt.Errorf("%s does not have WantedBy directive", unit) - } - wantsDir := "/etc/systemd/system/" + unitInfo.WantedBy + ".wants" - mgr.logger.Debug().Msg("mkdir -p " + wantsDir) - err = handle.Mkdir_p(wantsDir) - if err != nil { - return err - } - linkPath := wantsDir + "/" + unit - mgr.logger.Debug().Msg("ln -sf " + unitInfo.UnitPath + " " + linkPath) - err = handle.Ln_sf(unitInfo.UnitPath, linkPath) - if err != nil { - return err - } - unitInfo.Enabled = true - for _, also := range unitInfo.Also { - err = mgr.enableSystemdUnit(handle, also) - if err != nil { - return err - } - } - return nil + _, err := mgr.logAndRunCommand(handle, []string{ + "systemctl", "enable", unit, + }) + return err } -func systemdServiceIsEnabled(handle *guestfs.Guestfs, unit string) (bool, error) { - unitInfo, err := getSystemdUnitInfo(handle, unit) - if err != nil || unitInfo == nil { +func (mgr *TemplateManager) systemdServiceIsEnabled(handle *guestfs.Guestfs, unit string) (bool, error) { + // Use '|| true' because the exit code will be non-zero if the unit + // is not enabled or does not exist + command := "systemctl is-enabled " + unit + " || true" + mgr.logger.Debug().Msg(`Running sh -c "` + command + `"`) + output, err := handle.Sh(command) + if err != nil { return false, err } - return unitInfo.Enabled, nil + // Strip newline, if there is one + if len(output) > 0 && output[len(output)-1] == '\n' { + output = output[:len(output)-1] + } + if output == "enabled" { + return true, nil + } else if output == "disabled" || output == "not-found" { + return false, nil + } + return false, errors.New("Unexpected output '" + output + "'") } // setChronyOptions sets custom NTP server URLs in a chrony config file. @@ -515,28 +406,28 @@ func (mgr *TemplateManager) setTimesyncdConf(handle *guestfs.Guestfs) error { return handle.Write("/etc/systemd/timesyncd.conf.d/csclub.conf", getResource("timesyncd.conf")) } -func usesChrony(handle *guestfs.Guestfs) (bool, error) { +func (mgr *TemplateManager) usesChrony(handle *guestfs.Guestfs) (bool, error) { // The systemd service for chrony is called on chrony.service on e.g. Debian, // but is called chronyd.service on e.g. Fedora - chronyIsEnabled, err := systemdServiceIsEnabled(handle, "chrony.service") + chronyIsEnabled, err := mgr.systemdServiceIsEnabled(handle, "chrony.service") if err != nil { return false, err } if chronyIsEnabled { return true, nil } - return systemdServiceIsEnabled(handle, "chronyd.service") + return mgr.systemdServiceIsEnabled(handle, "chronyd.service") } func (mgr *TemplateManager) setupCommonNTPConfigs(handle *guestfs.Guestfs) error { - usesChrony, err := usesChrony(handle) + usesChrony, err := mgr.usesChrony(handle) if err != nil { return err } if usesChrony { return mgr.setChronyOptions(handle) } - usesSystemdTimesyncd, err := systemdServiceIsEnabled(handle, "systemd-timesyncd.service") + usesSystemdTimesyncd, err := mgr.systemdServiceIsEnabled(handle, "systemd-timesyncd.service") if err != nil { return err } @@ -555,11 +446,11 @@ func (mgr *TemplateManager) setNetworkManagerOptions(handle *guestfs.Guestfs) (e return handle.Write(path, getResource("network-manager-snippet")) } -func usesNetworkManager(handle *guestfs.Guestfs) (bool, error) { - return systemdServiceIsEnabled(handle, "NetworkManager.service") +func (mgr *TemplateManager) usesNetworkManager(handle *guestfs.Guestfs) (bool, error) { + return mgr.systemdServiceIsEnabled(handle, "NetworkManager.service") } -func usesEtcNetworkInterfaces(handle *guestfs.Guestfs) (bool, error) { +func (mgr *TemplateManager) usesEtcNetworkInterfaces(handle *guestfs.Guestfs) (bool, error) { configPath := "/etc/network/interfaces" configExists, err := handle.Is_file(configPath, nil) if err != nil { @@ -568,16 +459,7 @@ func usesEtcNetworkInterfaces(handle *guestfs.Guestfs) (bool, error) { if !configExists { return false, nil } - return systemdServiceIsEnabled(handle, "networking.service") -} - -func usesSysconfigNetwork(handle *guestfs.Guestfs) (bool, error) { - configPath := "/etc/sysconfig/network/config" - configExists, err := handle.Is_file(configPath, nil) - if err != nil { - err = fmt.Errorf("Could not determine if "+configPath+" is a file: %w", err) - } - return configExists, err + return mgr.systemdServiceIsEnabled(handle, "networking.service") } func (mgr *TemplateManager) addSystemdNetworkdCloudInitSnippet(handle *guestfs.Guestfs) error { @@ -587,62 +469,54 @@ func (mgr *TemplateManager) addSystemdNetworkdCloudInitSnippet(handle *guestfs.G return handle.Write(path, getResource("systemd-networkd-cloud-init")) } -func (mgr *TemplateManager) installAndEnableSystemdNetworkd(handle *guestfs.Guestfs) error { - isInstalled, err := handle.Is_file("/lib/systemd/system/systemd-networkd.service", nil) +func (mgr *TemplateManager) installAndEnableNetworkManager(handle *guestfs.Guestfs) error { + isInstalled, err := handle.Is_file("/lib/systemd/system/NetworkManager.service", nil) if err != nil { return err } if !isInstalled { - err = mgr.impl.InstallSystemdNetworkd(handle) + err = mgr.impl.InstallNetworkManager(handle) if err != nil { return err } } - return mgr.enableSystemdUnit(handle, "systemd-networkd.service") + return mgr.enableSystemdUnit(handle, "NetworkManager.service") } func (mgr *TemplateManager) setupCommonNetworkConfigs(handle *guestfs.Guestfs) error { - usesNetworkManager, err := usesNetworkManager(handle) + usesNetworkManager, err := mgr.usesNetworkManager(handle) if err != nil { return err } if usesNetworkManager { return mgr.setNetworkManagerOptions(handle) } - usesSystemdNetworkd, err := systemdServiceIsEnabled(handle, "systemd-networkd.service") + usesSystemdNetworkd, err := mgr.systemdServiceIsEnabled(handle, "systemd-networkd.service") if err != nil { return err } if usesSystemdNetworkd { return mgr.addSystemdNetworkdCloudInitSnippet(handle) } - usesEtcNetworkInterfaces, err := usesEtcNetworkInterfaces(handle) + usesEtcNetworkInterfaces, err := mgr.usesEtcNetworkInterfaces(handle) if err != nil { return err } if usesEtcNetworkInterfaces { return mgr.setDhclientOptions(handle) } - usesSysconfigNetwork, err := usesSysconfigNetwork(handle) + mgr.logger.Info().Msg("Could not determine network backend, resorting to installing NetworkManager") + err = mgr.installAndEnableNetworkManager(handle) if err != nil { return err } - if usesSysconfigNetwork { - // Handled separately in opensuse_tumbleweed.go - return nil - } - mgr.logger.Info().Msg("Could not determine network backend, resorting to installing systemd-networkd") - err = mgr.installAndEnableSystemdNetworkd(handle) - if err != nil { - return err - } - return mgr.addSystemdNetworkdCloudInitSnippet(handle) + return mgr.setNetworkManagerOptions(handle) } func (mgr *TemplateManager) setupIpv6Scripts(handle *guestfs.Guestfs) (err error) { log := mgr.logger scripts := []string{"99_csclub_ipv6_addr.sh"} - usesNetworkManager, err := usesNetworkManager(handle) + usesNetworkManager, err := mgr.usesNetworkManager(handle) if err != nil { return } @@ -671,25 +545,11 @@ func (mgr *TemplateManager) setupIpv6Scripts(handle *guestfs.Guestfs) (err error } func (mgr *TemplateManager) setResolvConf(handle *guestfs.Guestfs) (err error) { - if mgr.impl.NeedsDynamicResolvConf() { - scriptDir := "/var/lib/cloud/scripts/per-boot" - if err = handle.Mkdir_p(scriptDir); err != nil { - return - } - filename := "97_csclub_resolv_conf.sh" - path := scriptDir + "/" + filename - mgr.logger.Debug().Msg("Writing to " + path) - if err = handle.Write(path, getResource(filename)); err != nil { - return - } - return handle.Chmod(0755, path) - } else { - mgr.logger.Debug().Msg("Writing to /etc/resolv.conf") - if err = handle.Rm_f("/etc/resolv.conf"); err != nil { - return - } - return handle.Write("/etc/resolv.conf", getResource("resolv.conf")) + mgr.logger.Debug().Msg("Writing to /etc/resolv.conf") + if err = handle.Rm_f("/etc/resolv.conf"); err != nil { + return } + return handle.Write("/etc/resolv.conf", getResource("resolv.conf")) } func (mgr *TemplateManager) createAugeasHandle(handle *guestfs.Guestfs) (err error) { @@ -906,16 +766,14 @@ func (mgr *TemplateManager) dnfRemoveUnnecessaryPackages(handle *guestfs.Guestfs // auditd spams the system log and uses lots of disk IO. // bluez is unnecessary on servers. args := []string{"dnf", "-C", "remove", "-y", "sssd-common", "audit", "bluez"} - mgr.logger.Debug().Msg("Running '" + strings.Join(args, " ") + "'") - _, err = handle.Command(args) + _, err = mgr.logAndRunCommand(handle, args) if err != nil { return } // Now that we removed SSSD, we also have to make sure that it's not being used in PAM. // The way to do this on Fedora (and likely other RHEL-based distros) is with authselect. args = []string{"authselect", "select", "minimal"} - mgr.logger.Debug().Msg("Running '" + strings.Join(args, " ") + "'") - _, err = handle.Command(args) + _, err = mgr.logAndRunCommand(handle, args) return err } diff --git a/scripts/guestfish.sh b/scripts/guestfish.sh index e443242..ce134ce 100755 --- a/scripts/guestfish.sh +++ b/scripts/guestfish.sh @@ -9,4 +9,4 @@ if ! [ -e /usr/bin/qemu-img ]; then export PATH="$DEPS_DIR/usr/bin:$PATH" fi set -x -exec $GUESTFISH +exec $GUESTFISH --network