Repository: lima-vm/lima

Issue:
[RFC] [Windows] Consider allow extra path set by limactl to improve Windows UX
### Description

`limactl` on Windows requires additional Unix tools to operate (right now only tested against Git bash). The most straightforward they to use `limactl` is from Git bash, but this is not general purpose shell with no extension points (user is only getting the tools provided with the distribution). For daily work one might better prefer full msys2 with a package manager or Powershell as a more Windows flavored alternative, but then git tools under `/usr/bin` have to be added to PATH and take precedence of the tools provided by the shell. It works well for `limactl`, there are number of inherited problems with this approach:

* with msys2 multiple tools from Git will collide with msys2 and running msys2 userspace applications from different roots is bug prone. The big offender here is `bash`, when I tried it, to preserve env I needed to call it through cmd - `cmd /C "bash -c '<your commands>'"` if I needed to preserve env of my working shell. 
* with Powershell bash again is a problem, because when working in PS one might expect bash to be a command to start shell into default WSL instance. 

To support using these shells and `limactl` with more convenience it is possible to allow `limactl` to read dedicated environment variable and then use `os.Setenv` to update path early in the process startup (before any of the tools are needed). `os.Setenv` is not universally supported across platforms, but works in Windows at least, so, the scope might be narrowed for Windows case.

I created a prototype https://github.com/arixmkii/lima/commit/4cefee402e16b24ce0183f11118749451a93a793 but I want to have a brief discussion if this sounds feasible before making into full fledged PR.

Hints from issue comments:
@jandubois: > I created a prototype [arixmkii@4cefee4](https://github.com/arixmkii/lima/commit/4cefee402e16b24ce0183f11118749451a93a793) but I want to have a brief discussion if this sounds feasible before making into full fledged PR.

I'm not sure if this is going to be the right long-term solution (I suspect we may need something to configure external drivers to control these kind of settings), but I would support adding this temporarily for the benefit of experimentation. I would probably prefix the variable with `_LIMA` instead of `LIMA` to make it obvious that this is an experimental thing.

Solution patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 992816e4..0e45bb2a 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -39,7 +39,7 @@ jobs:
     - name: Verify generated files
       run: make install-tools generate check-generated
     - name: Run golangci-lint
-      uses: golangci/golangci-lint-action@4696ba8babb6127d732c3c6dde519db15edab9ea  # v6.5.1
+      uses: golangci/golangci-lint-action@55c2c1448f86e01eaae002a5a3a9624417608d84  # v6.5.2
       with:
         version: v1.64.2
         args: --verbose --timeout=10m
diff --git a/Kconfig b/Kconfig
index 3c8853fe..31850bce 100644
--- a/Kconfig
+++ b/Kconfig
@@ -31,6 +31,12 @@ config GUESTAGENT_ARCH_RISCV64
 		Build lima-guestagent for "riscv64" Arch
 	default y
 
+config GUESTAGENT_ARCH_S390X
+	bool "guestagent Arch: s390x"
+	help
+		Build lima-guestagent for "s390x" Arch
+	default y
+
 config GUESTAGENT_COMPRESS
 	bool "guestagent compress"
 	help
diff --git a/Makefile b/Makefile
index ef472651..c11b04d3 100644
--- a/Makefile
+++ b/Makefile
@@ -272,7 +272,7 @@ LINUX_GUESTAGENT_PATH_COMMON = _output/share/lima/lima-guestagent.Linux-
 # How to add architecture specific guestagent:
 # 1. Add the architecture to GUESTAGENT_ARCHS
 # 2. Add ENVS_$(*_GUESTAGENT_PATH_COMMON)<arch> to set GOOS, GOARCH, and other necessary environment variables
-LINUX_GUESTAGENT_ARCHS = aarch64 armv7l riscv64 x86_64
+LINUX_GUESTAGENT_ARCHS = aarch64 armv7l riscv64 s390x x86_64
 
 ifeq ($(CONFIG_GUESTAGENT_OS_LINUX),y)
 ALL_GUESTAGENTS_NOT_COMPRESSED += $(addprefix $(LINUX_GUESTAGENT_PATH_COMMON),$(LINUX_GUESTAGENT_ARCHS))
@@ -321,6 +321,7 @@ additional-guestagents: $(ADDITIONAL_GUESTAGENTS)
 ENVS_$(LINUX_GUESTAGENT_PATH_COMMON)aarch64 = CGO_ENABLED=0 GOOS=linux GOARCH=arm64
 ENVS_$(LINUX_GUESTAGENT_PATH_COMMON)armv7l = CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7
 ENVS_$(LINUX_GUESTAGENT_PATH_COMMON)riscv64 = CGO_ENABLED=0 GOOS=linux GOARCH=riscv64
+ENVS_$(LINUX_GUESTAGENT_PATH_COMMON)s390x = CGO_ENABLED=0 GOOS=linux GOARCH=s390x
 ENVS_$(LINUX_GUESTAGENT_PATH_COMMON)x86_64 = CGO_ENABLED=0 GOOS=linux GOARCH=amd64
 $(ALL_GUESTAGENTS_NOT_COMPRESSED): $(call dependencies_for_cmd,lima-guestagent) $$(call force_build_with_gunzip,$$@) | _output/share/lima
 	$(ENVS_$@) $(GO_BUILD) -o $@ ./cmd/lima-guestagent
diff --git a/cmd/limactl/editflags/editflags.go b/cmd/limactl/editflags/editflags.go
index e5b9a584..da0f266c 100644
--- a/cmd/limactl/editflags/editflags.go
+++ b/cmd/limactl/editflags/editflags.go
@@ -73,9 +73,9 @@ func RegisterCreate(cmd *cobra.Command, commentPrefix string) {
 	registerEdit(cmd, commentPrefix)
 	flags := cmd.Flags()
 
-	flags.String("arch", "", commentPrefix+"machine architecture (x86_64, aarch64, riscv64)") // colima-compatible
+	flags.String("arch", "", commentPrefix+"machine architecture (x86_64, aarch64, riscv64, armv7l, s390x)") // colima-compatible
 	_ = cmd.RegisterFlagCompletionFunc("arch", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
-		return []string{"x86_64", "aarch64", "riscv64"}, cobra.ShellCompDirectiveNoFileComp
+		return []string{"x86_64", "aarch64", "riscv64", "armv7l", "s390x"}, cobra.ShellCompDirectiveNoFileComp
 	})
 
 	flags.String("containerd", "", commentPrefix+"containerd mode (user, system, user+system, none)")
diff --git a/cmd/limactl/main.go b/cmd/limactl/main.go
index 268fad0d..d475f902 100644
--- a/cmd/limactl/main.go
+++ b/cmd/limactl/main.go
@@ -28,6 +28,16 @@ const (
 )
 
 func main() {
+	if runtime.GOOS == "windows" {
+		extras, hasExtra := os.LookupEnv("_LIMA_WINDOWS_EXTRA_PATH")
+		if hasExtra && strings.TrimSpace(extras) != "" {
+			p := os.Getenv("PATH")
+			err := os.Setenv("PATH", strings.TrimSpace(extras)+string(filepath.ListSeparator)+p)
+			if err != nil {
+				logrus.Warning("Can't add extras to PATH, relying entirely on system PATH")
+			}
+		}
+	}
 	if err := newApp().Execute(); err != nil {
 		handleExitCoder(err)
 		logrus.Fatal(err)
diff --git a/config.mk b/config.mk
index 7b3eb49e..6b7ff313 100644
--- a/config.mk
+++ b/config.mk
@@ -3,4 +3,5 @@ CONFIG_GUESTAGENT_ARCH_X8664=y
 CONFIG_GUESTAGENT_ARCH_AARCH64=y
 CONFIG_GUESTAGENT_ARCH_ARMV7L=y
 CONFIG_GUESTAGENT_ARCH_RISCV64=y
+CONFIG_GUESTAGENT_ARCH_S390X=y
 CONFIG_GUESTAGENT_COMPRESS=n
diff --git a/pkg/guestagent/guestagent_linux.go b/pkg/guestagent/guestagent_linux.go
index 91d1c4d5..f64a672e 100644
--- a/pkg/guestagent/guestagent_linux.go
+++ b/pkg/guestagent/guestagent_linux.go
@@ -20,7 +20,6 @@ import (
 	"github.com/lima-vm/lima/pkg/guestagent/procnettcp"
 	"github.com/lima-vm/lima/pkg/guestagent/timesync"
 	"github.com/sirupsen/logrus"
-	"golang.org/x/sys/cpu"
 	"google.golang.org/protobuf/types/known/timestamppb"
 )
 
@@ -220,9 +219,6 @@ func (a *agent) Events(ctx context.Context, ch chan *api.Event) {
 }
 
 func (a *agent) LocalPorts(_ context.Context) ([]*api.IPPort, error) {
-	if cpu.IsBigEndian {
-		return nil, errors.New("big endian architecture is unsupported, because I don't know how /proc/net/tcp looks like on big endian hosts")
-	}
 	var res []*api.IPPort
 	tcpParsed, err := procnettcp.ParseFiles()
 	if err != nil {
diff --git a/pkg/guestagent/procnettcp/procnettcp.go b/pkg/guestagent/procnettcp/procnettcp.go
index a1145705..2865b7be 100644
--- a/pkg/guestagent/procnettcp/procnettcp.go
+++ b/pkg/guestagent/procnettcp/procnettcp.go
@@ -12,6 +12,8 @@ import (
 	"net"
 	"strconv"
 	"strings"
+
+	"golang.org/x/sys/cpu"
 )
 
 type Kind = string
@@ -40,6 +42,10 @@ type Entry struct {
 }
 
 func Parse(r io.Reader, kind Kind) ([]Entry, error) {
+	return ParseWithEndian(r, kind, cpu.IsBigEndian)
+}
+
+func ParseWithEndian(r io.Reader, kind Kind, isBE bool) ([]Entry, error) {
 	switch kind {
 	case TCP, TCP6, UDP, UDP6:
 	default:
@@ -72,7 +78,7 @@ func Parse(r io.Reader, kind Kind) ([]Entry, error) {
 		default:
 			// localAddress is like "0100007F:053A"
 			localAddress := fields[fieldNames["local_address"]]
-			ip, port, err := ParseAddress(localAddress)
+			ip, port, err := ParseAddressWithEndian(localAddress, isBE)
 			if err != nil {
 				return entries, err
 			}
@@ -99,17 +105,24 @@ func Parse(r io.Reader, kind Kind) ([]Entry, error) {
 	return entries, nil
 }
 
-// ParseAddress parses a string, e.g.,
+// ParseAddress parses a string.
+//
+// Little endian hosts:
 // "0100007F:0050"                         (127.0.0.1:80)
 // "000080FE00000000FF57A6705DC771FE:0050" ([fe80::70a6:57ff:fe71:c75d]:80)
 // "00000000000000000000000000000000:0050" (0.0.0.0:80)
 //
-// See https://serverfault.com/questions/592574/why-does-proc-net-tcp6-represents-1-as-1000
+// Big endian hosts:
+// "7F000001:0050"                         (127.0.0.1:80)
+// "FE8000000000000070A657FFFE71C75D:0050" ([fe80::70a6:57ff:fe71:c75d]:80)
+// "00000000000000000000000000000000:0050" (0.0.0.0:80)
 //
-// ParseAddress is expected to be used for /proc/net/{tcp,tcp6} entries on
-// little endian machines.
-// Not sure how those entries look like on big endian machines.
+// See https://serverfault.com/questions/592574/why-does-proc-net-tcp6-represents-1-as-1000
 func ParseAddress(s string) (net.IP, uint16, error) {
+	return ParseAddressWithEndian(s, cpu.IsBigEndian)
+}
+
+func ParseAddressWithEndian(s string, isBE bool) (net.IP, uint16, error) {
 	split := strings.SplitN(s, ":", 2)
 	if len(split) != 2 {
 		return nil, 0, fmt.Errorf("unparsable address %q", s)
@@ -124,12 +137,18 @@ func ParseAddress(s string) (net.IP, uint16, error) {
 	ipBytes := make([]byte, len(split[0])/2) // 4 bytes (8 chars) or 16 bytes (32 chars)
 	for i := 0; i < len(split[0])/8; i++ {
 		quartet := split[0][8*i : 8*(i+1)]
-		quartetLE, err := hex.DecodeString(quartet) // surprisingly little endian, per 4 bytes
+		quartetB, err := hex.DecodeString(quartet) // surprisingly little endian, per 4 bytes, on little endian hosts
 		if err != nil {
 			return nil, 0, fmt.Errorf("unparsable address %q: unparsable quartet %q: %w", s, quartet, err)
 		}
-		for j := 0; j < len(quartetLE); j++ {
-			ipBytes[4*i+len(quartetLE)-1-j] = quartetLE[j]
+		if isBE {
+			for j := 0; j < len(quartetB); j++ {
+				ipBytes[4*i+j] = quartetB[j]
+			}
+		} else {
+			for j := 0; j < len(quartetB); j++ {
+				ipBytes[4*i+len(quartetB)-1-j] = quartetB[j]
+			}
 		}
 	}
 	ip := net.IP(ipBytes)
diff --git a/pkg/limatmpl/locator.go b/pkg/limatmpl/locator.go
index 5afb7cb6..b270332f 100644
--- a/pkg/limatmpl/locator.go
+++ b/pkg/limatmpl/locator.go
@@ -140,6 +140,7 @@ var archKeywords = map[string]limayaml.Arch{
 	"armhf":   limayaml.ARMV7L,
 	"armv7l":  limayaml.ARMV7L,
 	"riscv64": limayaml.RISCV64,
+	"s390x":   limayaml.S390X,
 	"x86_64":  limayaml.X8664,
 }
 
diff --git a/pkg/limayaml/defaults.go b/pkg/limayaml/defaults.go
index 177f80a7..8ca2b419 100644
--- a/pkg/limayaml/defaults.go
+++ b/pkg/limayaml/defaults.go
@@ -65,6 +65,7 @@ func defaultCPUType() CPUType {
 		// Since https://github.com/lima-vm/lima/pull/494, we use qemu64 cpu for better emulation of x86_64.
 		X8664:   "qemu64",
 		RISCV64: "rv64", // FIXME: what is the right choice for riscv64?
+		S390X:   "qemu", // FIXME: what is the right choice for s390x?
 	}
 	for arch := range cpuType {
 		if IsNativeArch(arch) && IsAccelOS() {
@@ -1090,6 +1091,8 @@ func NewArch(arch string) Arch {
 		return arch
 	case "riscv64":
 		return RISCV64
+	case "s390x":
+		return S390X
 	default:
 		logrus.Warnf("Unknown arch: %s", arch)
 		return arch
@@ -1222,7 +1225,7 @@ func ResolveArch(s *string) Arch {
 
 func IsAccelOS() bool {
 	switch runtime.GOOS {
-	case "darwin", "linux", "netbsd", "windows":
+	case "darwin", "linux", "netbsd", "windows", "dragonfly":
 		// Accelerator
 		return true
 	}
@@ -1280,7 +1283,8 @@ func IsNativeArch(arch Arch) bool {
 	nativeAARCH64 := arch == AARCH64 && runtime.GOARCH == "arm64"
 	nativeARMV7L := arch == ARMV7L && runtime.GOARCH == "arm" && goarm() == 7
 	nativeRISCV64 := arch == RISCV64 && runtime.GOARCH == "riscv64"
-	return nativeX8664 || nativeAARCH64 || nativeARMV7L || nativeRISCV64
+	nativeS390X := arch == S390X && runtime.GOARCH == "s390x"
+	return nativeX8664 || nativeAARCH64 || nativeARMV7L || nativeRISCV64 || nativeS390X
 }
 
 func unique(s []string) []string {
diff --git a/pkg/limayaml/limayaml.go b/pkg/limayaml/limayaml.go
index 39a7c557..b954063c 100644
--- a/pkg/limayaml/limayaml.go
+++ b/pkg/limayaml/limayaml.go
@@ -77,6 +77,7 @@ const (
 	AARCH64 Arch = "aarch64"
 	ARMV7L  Arch = "armv7l"
 	RISCV64 Arch = "riscv64"
+	S390X   Arch = "s390x"
 
 	REVSSHFS MountType = "reverse-sshfs"
 	NINEP    MountType = "9p"
@@ -90,7 +91,7 @@ const (
 
 var (
 	OSTypes    = []OS{LINUX}
-	ArchTypes  = []Arch{X8664, AARCH64, ARMV7L, RISCV64}
+	ArchTypes  = []Arch{X8664, AARCH64, ARMV7L, RISCV64, S390X}
 	MountTypes = []MountType{REVSSHFS, NINEP, VIRTIOFS, WSLMount}
 	VMTypes    = []VMType{QEMU, VZ, WSL2}
 )
diff --git a/pkg/limayaml/validate.go b/pkg/limayaml/validate.go
index fb93a08b..1dce335c 100644
--- a/pkg/limayaml/validate.go
+++ b/pkg/limayaml/validate.go
@@ -35,9 +35,9 @@ func validateFileObject(f File, fieldName string) error {
 		// f.Location does NOT need to be accessible, so we do NOT check os.Stat(f.Location)
 	}
 	switch f.Arch {
-	case X8664, AARCH64, ARMV7L, RISCV64:
+	case X8664, AARCH64, ARMV7L, RISCV64, S390X:
 	default:
-		return fmt.Errorf("field `arch` must be %q, %q, %q, or %q; got %q", X8664, AARCH64, ARMV7L, RISCV64, f.Arch)
+		return fmt.Errorf("field `arch` must be %q, %q, %q, %q, or %q; got %q", X8664, AARCH64, ARMV7L, RISCV64, S390X, f.Arch)
 	}
 	if f.Digest != "" {
 		if !f.Digest.Algorithm().Available() {
@@ -77,9 +77,9 @@ func Validate(y *LimaYAML, warn bool) error {
 		return fmt.Errorf("field `os` must be %q; got %q", LINUX, *y.OS)
 	}
 	switch *y.Arch {
-	case X8664, AARCH64, ARMV7L, RISCV64:
+	case X8664, AARCH64, ARMV7L, RISCV64, S390X:
 	default:
-		return fmt.Errorf("field `arch` must be %q, %q, %q or %q; got %q", X8664, AARCH64, ARMV7L, RISCV64, *y.Arch)
+		return fmt.Errorf("field `arch` must be %q, %q, %q, %q or %q; got %q", X8664, AARCH64, ARMV7L, RISCV64, S390X, *y.Arch)
 	}
 
 	switch *y.VMType {
@@ -125,7 +125,7 @@ func Validate(y *LimaYAML, warn bool) error {
 
 	for arch := range y.CPUType {
 		switch arch {
-		case AARCH64, X8664, ARMV7L, RISCV64:
+		case AARCH64, X8664, ARMV7L, RISCV64, S390X:
 			// these are the only supported architectures
 		default:
 			return fmt.Errorf("field `cpuType` uses unsupported arch %q", arch)
@@ -578,8 +578,9 @@ func warnExperimental(y *LimaYAML) {
 	if *y.MountType == VIRTIOFS && runtime.GOOS == "linux" {
 		logrus.Warn("`mountType: virtiofs` on Linux is experimental")
 	}
-	if *y.Arch == RISCV64 {
-		logrus.Warn("`arch: riscv64` is experimental")
+	switch *y.Arch {
+	case RISCV64, ARMV7L, S390X:
+		logrus.Warnf("`arch: %s ` is experimental", *y.Arch)
 	}
 	if y.Video.Display != nil && strings.Contains(*y.Video.Display, "vnc") {
 		logrus.Warn("`video.display: vnc` is experimental")
diff --git a/pkg/qemu/qemu.go b/pkg/qemu/qemu.go
index adc75222..f0a92c37 100644
--- a/pkg/qemu/qemu.go
+++ b/pkg/qemu/qemu.go
@@ -587,6 +587,9 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er
 	case limayaml.ARMV7L:
 		machine := "virt,accel=" + accel
 		args = appendArgsIfNoConflict(args, "-machine", machine)
+	case limayaml.S390X:
+		machine := "s390-ccw-virtio,accel=" + accel
+		args = appendArgsIfNoConflict(args, "-machine", machine)
 	}
 
 	// SMP
@@ -599,13 +602,14 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er
 		logrus.Warnf("field `firmware.legacyBIOS` is not supported for architecture %q, ignoring", *y.Arch)
 		legacyBIOS = false
 	}
-	if !legacyBIOS {
+	noFirmware := *y.Arch == limayaml.S390X || legacyBIOS
+	if !noFirmware {
 		var firmware string
 		firmwareInBios := runtime.GOOS == "windows"
-		if envVar := os.Getenv("LIMA_QEMU_UEFI_IN_BIOS"); envVar != "" {
-			b, err := strconv.ParseBool(os.Getenv("LIMA_QEMU_UEFI_IN_BIOS"))
+		if envVar := os.Getenv("_LIMA_QEMU_UEFI_IN_BIOS"); envVar != "" {
+			b, err := strconv.ParseBool(os.Getenv("_LIMA_QEMU_UEFI_IN_BIOS"))
 			if err != nil {
-				logrus.WithError(err).Warnf("invalid LIMA_QEMU_UEFI_IN_BIOS value %q", envVar)
+				logrus.WithError(err).Warnf("invalid _LIMA_QEMU_UEFI_IN_BIOS value %q", envVar)
 			} else {
 				firmwareInBios = b
 			}
@@ -782,7 +786,13 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er
 		}
 		args = append(args, "-netdev", fmt.Sprintf("socket,id=net0,fd={{ fd_connect %q }}", qemuSock))
 	}
-	args = append(args, "-device", "virtio-net-pci,netdev=net0,mac="+limayaml.MACAddress(cfg.InstanceDir))
+	virtioNet := "virtio-net-pci"
+	if *y.Arch == limayaml.S390X {
+		// virtio-net-pci does not work on EL, while it works on Ubuntu
+		// https://github.com/lima-vm/lima/pull/3319/files#r1986388345
+		virtioNet = "virtio-net-ccw"
+	}
+	args = append(args, "-device", virtioNet+",netdev=net0,mac="+limayaml.MACAddress(cfg.InstanceDir))
 
 	for i, nw := range y.Networks {
 		if nw.Lima != "" {
@@ -805,7 +815,7 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er
 					return "", nil, err
 				}
 				args = append(args, "-netdev", fmt.Sprintf("socket,id=net%d,fd={{ fd_connect %q }}", i+1, qemuSock))
-				args = append(args, "-device", fmt.Sprintf("virtio-net-pci,netdev=net%d,mac=%s", i+1, nw.MACAddress))
+				args = append(args, "-device", fmt.Sprintf("%s,netdev=net%d,mac=%s", virtioNet, i+1, nw.MACAddress))
 			} else {
 				if runtime.GOOS != "darwin" {
 					return "", nil, fmt.Errorf("networks.yaml '%s' configuration is only supported on macOS right now", nw.Lima)
@@ -824,7 +834,7 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er
 		} else {
 			return "", nil, fmt.Errorf("invalid network spec %+v", nw)
 		}
-		args = append(args, "-device", fmt.Sprintf("virtio-net-pci,netdev=net%d,mac=%s", i+1, nw.MACAddress))
+		args = append(args, "-device", fmt.Sprintf("%s,netdev=net%d,mac=%s", virtioNet, i+1, nw.MACAddress))
 	}
 
 	// virtio-rng-pci accelerates starting up the OS, according to https://wiki.gentoo.org/wiki/QEMU/Options
@@ -866,7 +876,7 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er
 		args = append(args, "-device", "virtio-keyboard-pci")
 		args = append(args, "-device", "virtio-"+input+"-pci")
 		args = append(args, "-device", "qemu-xhci,id=usb-bus")
-	case limayaml.AARCH64, limayaml.ARMV7L:
+	case limayaml.AARCH64, limayaml.ARMV7L, limayaml.S390X:
 		if features.VersionGEQ7 {
 			args = append(args, "-device", "virtio-gpu")
 			args = append(args, "-device", "virtio-keyboard-pci")
@@ -926,7 +936,11 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er
 	const serialvChardev = "char-serial-virtio"
 	args = append(args, "-chardev", fmt.Sprintf("socket,id=%s,path=%s,server=on,wait=off,logfile=%s", serialvChardev, serialvSock, serialvLog))
 	// max_ports=1 is required for https://github.com/lima-vm/lima/issues/1689 https://github.com/lima-vm/lima/issues/1691
-	args = append(args, "-device", "virtio-serial-pci,id=virtio-serial0,max_ports=1")
+	serialvMaxPorts := 1
+	if *y.Arch == limayaml.S390X {
+		serialvMaxPorts++ // needed to avoid `virtio-serial-bus: Out-of-range port id specified, max. allowed: 0`
+	}
+	args = append(args, "-device", fmt.Sprintf("virtio-serial-pci,id=virtio-serial0,max_ports=%d", serialvMaxPorts))
 	args = append(args, "-device", fmt.Sprintf("virtconsole,chardev=%s,id=console0", serialvChardev))
 
 	// We also want to enable vsock here, but QEMU does not support vsock for macOS hosts
@@ -1120,6 +1134,8 @@ func Accel(arch limayaml.Arch) string {
 			return "kvm"
 		case "netbsd":
 			return "nvmm"
+		case "dragonfly":
+			return "nvmm"
 		case "windows":
 			return "whpx"
 		}
diff --git a/templates/almalinux-8.yaml b/templates/almalinux-8.yaml
index e5f1f270..f42327e8 100644
--- a/templates/almalinux-8.yaml
+++ b/templates/almalinux-8.yaml
@@ -10,12 +10,17 @@ images:
 - location: "https://repo.almalinux.org/almalinux/8.10/cloud/aarch64/images/AlmaLinux-8-GenericCloud-8.10-20240819.aarch64.qcow2"
   arch: "aarch64"
   digest: "sha256:cec6736cbc562d06895e218b6f022621343c553bfa79192ca491381b4636c7b8"
+- location: "https://repo.almalinux.org/almalinux/8.10/cloud/s390x/images/AlmaLinux-8-GenericCloud-8.10-20240819.s390x.qcow2"
+  arch: "s390x"
+  digest: "sha256:7f8866a4247ad57c81f5d2c5a0fa64940691f9df1e858a1510d34a0de008eb16"
 # Fallback to the latest release image.
 # Hint: run `limactl prune` to invalidate the cache
 - location: "https://repo.almalinux.org/almalinux/8/cloud/x86_64/images/AlmaLinux-8-GenericCloud-latest.x86_64.qcow2"
   arch: "x86_64"
 - location: "https://repo.almalinux.org/almalinux/8/cloud/aarch64/images/AlmaLinux-8-GenericCloud-latest.aarch64.qcow2"
   arch: "aarch64"
+- location: "https://repo.almalinux.org/almalinux/8/cloud/s390x/images/AlmaLinux-8-GenericCloud-latest.s390x.qcow2"
+  arch: "s390x"
 mountTypesUnsupported: ["9p"]
 mounts:
 - location: "~"
diff --git a/templates/almalinux-9.yaml b/templates/almalinux-9.yaml
index ef7f4a09..2e727243 100644
--- a/templates/almalinux-9.yaml
+++ b/templates/almalinux-9.yaml
@@ -7,12 +7,17 @@ images:
 - location: "https://repo.almalinux.org/almalinux/9.5/cloud/aarch64/images/AlmaLinux-9-GenericCloud-9.5-20241120.aarch64.qcow2"
   arch: "aarch64"
   digest: "sha256:5ede4affaad0a997a2b642f1628b6268bd8eba775f281346b75be3ed20ec369e"
+- location: "https://repo.almalinux.org/almalinux/9.5/cloud/s390x/images/AlmaLinux-9-GenericCloud-9.5-20241120.s390x.qcow2"
+  arch: "s390x"
+  digest: "sha256:9948ea1c5ee1c6497bde5cec18c71c5f9e50861af4c0f1400c1af504eee2b995"
 # Fallback to the latest release image.
 # Hint: run `limactl prune` to invalidate the cache
 - location: "https://repo.almalinux.org/almalinux/9/cloud/x86_64/images/AlmaLinux-9-GenericCloud-latest.x86_64.qcow2"
   arch: "x86_64"
 - location: "https://repo.almalinux.org/almalinux/9/cloud/aarch64/images/AlmaLinux-9-GenericCloud-latest.aarch64.qcow2"
   arch: "aarch64"
+- location: "https://repo.almalinux.org/almalinux/9/cloud/s390x/images/AlmaLinux-9-GenericCloud-latest.s390x.qcow2"
+  arch: "s390x"
 mountTypesUnsupported: ["9p"]
 mounts:
 - location: "~"
diff --git a/templates/centos-stream-10.yaml b/templates/centos-stream-10.yaml
index 1d985ee4..cbbe7729 100644
--- a/templates/centos-stream-10.yaml
+++ b/templates/centos-stream-10.yaml
@@ -8,12 +8,17 @@ images:
 - location: "https://cloud.centos.org/centos/10-stream/aarch64/images/CentOS-Stream-GenericCloud-10-20250210.0.aarch64.qcow2"
   arch: "aarch64"
   digest: "sha256:996cdea7be4e13b04a71a45e89215e85c2909d5e08d6f1a8faaa66e7e2f93cbb"
+- location: "https://cloud.centos.org/centos/10-stream/s390x/images/CentOS-Stream-GenericCloud-10-20250210.0.s390x.qcow2"
+  arch: "s390x"
+  digest: "sha256:ed39940423de3b9b84bdef9fd155fdb09384d099dca4cb036d283fa27668f6f3"
 # Fallback to the latest release image.
 # Hint: run `limactl prune` to invalidate the cache
 - location: "https://cloud.centos.org/centos/10-stream/x86_64/images/CentOS-Stream-GenericCloud-10-latest.x86_64.qcow2"
   arch: "x86_64"
 - location: "https://cloud.centos.org/centos/10-stream/aarch64/images/CentOS-Stream-GenericCloud-10-latest.aarch64.qcow2"
   arch: "aarch64"
+- location: "https://cloud.centos.org/centos/10-stream/s390x/images/CentOS-Stream-GenericCloud-10-latest.s390x.qcow2"
+  arch: "s390x"
 mountTypesUnsupported: ["9p"]
 mounts:
 - location: "~"
diff --git a/templates/centos-stream-9.yaml b/templates/centos-stream-9.yaml
index 277a3052..ee0a977d 100644
--- a/templates/centos-stream-9.yaml
+++ b/templates/centos-stream-9.yaml
@@ -8,12 +8,17 @@ images:
 - location: "https://cloud.centos.org/centos/9-stream/aarch64/images/CentOS-Stream-GenericCloud-9-20250210.0.aarch64.qcow2"
   arch: "aarch64"
   digest: "sha256:507f3e49c8a62eed63e73887b7cdb97a4a1382f6a20ded1eb5eed69c18d9197f"
+- location: "https://cloud.centos.org/centos/9-stream/s390x/images/CentOS-Stream-GenericCloud-9-20250210.0.s390x.qcow2"
+  arch: "s390x"
+  digest: "sha256:f347daf1008a3efefcb9da4ee7cf06e38226d2f81f606770b07dbebe5b172552"
 # Fallback to the latest release image.
 # Hint: run `limactl prune` to invalidate the cache
 - location: "https://cloud.centos.org/centos/9-stream/x86_64/images/CentOS-Stream-GenericCloud-9-latest.x86_64.qcow2"
   arch: "x86_64"
 - location: "https://cloud.centos.org/centos/9-stream/aarch64/images/CentOS-Stream-GenericCloud-9-latest.aarch64.qcow2"
   arch: "aarch64"
+- location: "https://cloud.centos.org/centos/9-stream/s390x/images/CentOS-Stream-GenericCloud-9-latest.s390x.qcow2"
+  arch: "s390x"
 mountTypesUnsupported: ["9p"]
 mounts:
 - location: "~"
diff --git a/templates/default.yaml b/templates/default.yaml
index 181b66e7..c3f29c51 100644
--- a/templates/default.yaml
+++ b/templates/default.yaml
@@ -32,6 +32,9 @@ images:
 - location: "https://cloud-images.ubuntu.com/releases/24.10/release-20250129/ubuntu-24.10-server-cloudimg-armhf.img"
   arch: "armv7l"
   digest: "sha256:6a1ca78f06227ba060d11a88009cc9b65bdd42e17b84d444d204cb5c80f81552"
+- location: "https://cloud-images.ubuntu.com/releases/24.10/release-20250129/ubuntu-24.10-server-cloudimg-s390x.img"
+  arch: "s390x"
+  digest: "sha256:940d835ebfb4de02ba93d46bce75ab5602f36f8a66c0865df579205aa02d28db"
 # Fallback to the latest release image.
 # Hint: run `limactl prune` to invalidate the cache
 - location: "https://cloud-images.ubuntu.com/releases/24.10/release/ubuntu-24.10-server-cloudimg-amd64.img"
@@ -42,6 +45,8 @@ images:
   arch: "riscv64"
 - location: "https://cloud-images.ubuntu.com/releases/24.10/release/ubuntu-24.10-server-cloudimg-armhf.img"
   arch: "armv7l"
+- location: "https://cloud-images.ubuntu.com/releases/24.10/release/ubuntu-24.10-server-cloudimg-s390x.img"
+  arch: "s390x"
 # CPUs
 # 🟢 Builtin default: min(4, host CPU cores)
 cpus: null
diff --git a/templates/ubuntu-20.04.yaml b/templates/ubuntu-20.04.yaml
index cd0041f2..4c554992 100644
--- a/templates/ubuntu-20.04.yaml
+++ b/templates/ubuntu-20.04.yaml
@@ -10,6 +10,9 @@ images:
 - location: "https://cloud-images.ubuntu.com/releases/focal/release-20250127/ubuntu-20.04-server-cloudimg-armhf.img"
   arch: "armv7l"
   digest: "sha256:027579b19db39e989a95129440fbbc495d9d7426fedde7548851dac1693f8312"
+- location: "https://cloud-images.ubuntu.com/releases/focal/release-20250127/ubuntu-20.04-server-cloudimg-s390x.img"
+  arch: "s390x"
+  digest: "sha256:20306930392db13ef3bf169d236b4d67cd1c84e606df05bc4539b0f55d00ad74"
 # Fallback to the latest release image.
 # Hint: run `limactl prune` to invalidate the cache
 - location: "https://cloud-images.ubuntu.com/releases/focal/release/ubuntu-20.04-server-cloudimg-amd64.img"
@@ -18,6 +21,8 @@ images:
   arch: "aarch64"
 - location: "https://cloud-images.ubuntu.com/releases/focal/release/ubuntu-20.04-server-cloudimg-armhf.img"
   arch: "armv7l"
+- location: "https://cloud-images.ubuntu.com/releases/focal/release/ubuntu-20.04-server-cloudimg-s390x.img"
+  arch: "s390x"
 
 mounts:
 - location: "~"
diff --git a/templates/ubuntu-22.04.yaml b/templates/ubuntu-22.04.yaml
index c25956ed..012290aa 100644
--- a/templates/ubuntu-22.04.yaml
+++ b/templates/ubuntu-22.04.yaml
@@ -13,6 +13,9 @@ images:
 - location: "https://cloud-images.ubuntu.com/releases/22.04/release-20250128/ubuntu-22.04-server-cloudimg-armhf.img"
   arch: "armv7l"
   digest: "sha256:2a3fce7f82426204b1aa44312c3cfb2a107de2204eea6a0bf1221c2541ee7f3f"
+- location: "https://cloud-images.ubuntu.com/releases/22.04/release-20250128/ubuntu-22.04-server-cloudimg-s390x.img"
+  arch: "s390x"
+  digest: "sha256:fa287d4ab8ff236bfe0dc1e6253159ecfa0734b6d682d0006977a46a53984d67"
 # Fallback to the latest release image.
 # Hint: run `limactl prune` to invalidate the cache
 - location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img"
@@ -23,6 +26,8 @@ images:
   arch: "riscv64"
 - location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-armhf.img"
   arch: "armv7l"
+- location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-s390x.img"
+  arch: "s390x"
 
 mounts:
 - location: "~"
diff --git a/templates/ubuntu-24.04.yaml b/templates/ubuntu-24.04.yaml
index ffe256c7..3dc64453 100644
--- a/templates/ubuntu-24.04.yaml
+++ b/templates/ubuntu-24.04.yaml
@@ -13,6 +13,9 @@ images:
 - location: "https://cloud-images.ubuntu.com/releases/24.04/release-20250115/ubuntu-24.04-server-cloudimg-armhf.img"
   arch: "armv7l"
   digest: "sha256:c9ad061c976443d8d4831da1a28aa9ac192d392d392ac287e5ae873d9b00cbcd"
+- location: "https://cloud-images.ubuntu.com/releases/24.04/release-20250115/ubuntu-24.04-server-cloudimg-s390x.img"
+  arch: "s390x"
+  digest: "sha256:367eef35e239ebd123bd00a1fb66cd048604b70bca362eccad1f95b0021d7458"
 # Fallback to the latest release image.
 # Hint: run `limactl prune` to invalidate the cache
 - location: "https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img"
@@ -23,6 +26,8 @@ images:
   arch: "riscv64"
 - location: "https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-armhf.img"
   arch: "armv7l"
+- location: "https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-s390x.img"
+  arch: "s390x"
 
 mounts:
 - location: "~"
diff --git a/templates/ubuntu-24.10.yaml b/templates/ubuntu-24.10.yaml
index 9be2249f..fac0ce00 100644
--- a/templates/ubuntu-24.10.yaml
+++ b/templates/ubuntu-24.10.yaml
@@ -13,6 +13,9 @@ images:
 - location: "https://cloud-images.ubuntu.com/releases/24.10/release-20250129/ubuntu-24.10-server-cloudimg-armhf.img"
   arch: "armv7l"
   digest: "sha256:6a1ca78f06227ba060d11a88009cc9b65bdd42e17b84d444d204cb5c80f81552"
+- location: "https://cloud-images.ubuntu.com/releases/24.10/release-20250129/ubuntu-24.10-server-cloudimg-s390x.img"
+  arch: "s390x"
+  digest: "sha256:940d835ebfb4de02ba93d46bce75ab5602f36f8a66c0865df579205aa02d28db"
 # Fallback to the latest release image.
 # Hint: run `limactl prune` to invalidate the cache
 - location: "https://cloud-images.ubuntu.com/releases/24.10/release/ubuntu-24.10-server-cloudimg-amd64.img"
@@ -23,6 +26,8 @@ images:
   arch: "riscv64"
 - location: "https://cloud-images.ubuntu.com/releases/24.10/release/ubuntu-24.10-server-cloudimg-armhf.img"
   arch: "armv7l"
+- location: "https://cloud-images.ubuntu.com/releases/24.10/release/ubuntu-24.10-server-cloudimg-s390x.img"
+  arch: "s390x"
 mounts:
 - location: "~"
 - location: "/tmp/lima"
diff --git a/website/content/en/docs/config/environment-variables.md b/website/content/en/docs/config/environment-variables.md
index b769e95a..90e01c80 100644
--- a/website/content/en/docs/config/environment-variables.md
+++ b/website/content/en/docs/config/environment-variables.md
@@ -67,13 +67,27 @@ This page documents the environment variables used in Lima.
   export LIMA_USERNET_RESOLVE_IP_ADDRESS_TIMEOUT=5
   ```
 
-### `LIMA_QEMU_UEFI_IN_BIOS`
+### `_LIMA_QEMU_UEFI_IN_BIOS`
 
 - **Description**: Commands QEMU to load x86_64 UEFI images using `-bios` instead of `pflash` drives.
 - **Default**: `false` on Unix like hosts and `true` on Windows hosts
 - **Usage**: 
   ```sh
-  export LIMA_QEMU_UEFI_IN_BIOS=true
+  export _LIMA_QEMU_UEFI_IN_BIOS=true
   ```
 - **Note**: It is expected that this variable will be set to `false` by default in future
   when QEMU supports `pflash` UEFI for accelerated guests on Windows.
+
+### `_LIMA_WINDOWS_EXTRA_PATH`
+
+- **Description**: Additional directories which will be added to PATH by `limactl.exe` process to search for tools.
+  It is useful, when there is a need to prevent collisions between binaries available in active shell and ones
+  used by `limactl.exe` - injecting them only for the running process w/o altering PATH observed by user shell.
+  Is is Windows specific and does nothing for other platforms.
+- **Default**: unset
+- **Usage**:
+  ```bat
+  set _LIMA_WINDOWS_EXTRA_PATH=C:\Program Files\Git\usr\bin
+  ```
+- **Note**: It is an experimental setting and has no guarantees being ever promoted to stable. It may be removed
+  or changed at any stage of project development.
diff --git a/website/content/en/docs/releases/experimental.md b/website/content/en/docs/releases/experimental.md
index d443fce8..da007dc5 100644
--- a/website/content/en/docs/releases/experimental.md
+++ b/website/content/en/docs/releases/experimental.md
@@ -8,10 +8,9 @@ The following features are experimental and subject to change:
 
 - `mountType: virtiofs` on Linux
 - `vmType: wsl2` and relevant configurations (`mountType: wsl2`)
-- `arch: riscv64`
+- `arch: riscv64`, `arch: armv7l`, and `arch: s390x`
 - `video.display: vnc` and relevant configuration (`video.vnc.display`)
 - `audio.device`
-- `arch: armv7l`
 - `mountInotify: true`
 - `base:` and variations like `base[]:` and `base[].url`
 - `provision[].file:` and alternative `provision[].file.url:`  



Test patch:
diff --git a/pkg/guestagent/procnettcp/procnettcp_test.go b/pkg/guestagent/procnettcp/procnettcp_test.go
index f61467ee..b92a9c97 100644
--- a/pkg/guestagent/procnettcp/procnettcp_test.go
+++ b/pkg/guestagent/procnettcp/procnettcp_test.go
@@ -12,6 +12,7 @@ import (
 )
 
 func TestParseTCP(t *testing.T) {
+	const isBE = false
 	procNetTCP := `  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode                                                     
    0: 0100007F:8AEF 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 28152 1 0000000000000000 100 0 0 10 0                     
    1: 0103000A:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 31474 1 0000000000000000 100 0 0 10 5                     
@@ -20,7 +21,7 @@ func TestParseTCP(t *testing.T) {
    4: 0100007F:053A 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 31430 1 0000000000000000 100 0 0 10 0                     
    5: 0B3CA8C0:0016 690AA8C0:F705 01 00000000:00000000 02:00028D8B 00000000     0        0 32989 4 0000000000000000 20 4 31 10 19
 `
-	entries, err := Parse(strings.NewReader(procNetTCP), TCP)
+	entries, err := ParseWithEndian(strings.NewReader(procNetTCP), TCP, isBE)
 	assert.NilError(t, err)
 	t.Log(entries)
 
@@ -34,9 +35,10 @@ func TestParseTCP(t *testing.T) {
 }
 
 func TestParseTCP6(t *testing.T) {
+	const isBE = false
 	procNetTCP := `  sl  local_address                         remote_address                        st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
 	   0: 000080FE00000000FF57A6705DC771FE:0050 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 850222 1 0000000000000000 100 0 0 10 0`
-	entries, err := Parse(strings.NewReader(procNetTCP), TCP6)
+	entries, err := ParseWithEndian(strings.NewReader(procNetTCP), TCP6, isBE)
 	assert.NilError(t, err)
 	t.Log(entries)
 
@@ -46,12 +48,13 @@ func TestParseTCP6(t *testing.T) {
 }
 
 func TestParseTCP6Zero(t *testing.T) {
+	const isBE = false
 	procNetTCP := `  sl  local_address                         remote_address                        st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
    0: 00000000000000000000000000000000:0016 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 33825 1 0000000000000000 100 0 0 10 0
    1: 00000000000000000000000000000000:006F 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 26772 1 0000000000000000 100 0 0 10 0
    2: 00000000000000000000000000000000:0050 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 1210901 1 0000000000000000 100 0 0 10 0
 `
-	entries, err := Parse(strings.NewReader(procNetTCP), TCP6)
+	entries, err := ParseWithEndian(strings.NewReader(procNetTCP), TCP6, isBE)
 	assert.NilError(t, err)
 	t.Log(entries)
 
@@ -61,13 +64,14 @@ func TestParseTCP6Zero(t *testing.T) {
 }
 
 func TestParseUDP(t *testing.T) {
+	const isBE = false
 	procNetTCP := `   sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode ref pointer drops            
   716: 3600007F:0035 00000000:0000 07 00000000:00000000 00:00000000 00000000   991        0 2964 2 0000000000000000 0          
   716: 3500007F:0035 00000000:0000 07 00000000:00000000 00:00000000 00000000   991        0 2962 2 0000000000000000 0          
   731: 0369A8C0:0044 00000000:0000 07 00000000:00000000 00:00000000 00000000   998        0 29132 2 0000000000000000 0         
   731: 0F05A8C0:0044 00000000:0000 07 00000000:00000000 00:00000000 00000000   998        0 4049 2 0000000000000000 0          
  1768: 00000000:1451 00000000:0000 07 00000000:00000000 00:00000000 00000000   502        0 28364 2 0000000000000000 0  `
-	entries, err := Parse(strings.NewReader(procNetTCP), UDP)
+	entries, err := ParseWithEndian(strings.NewReader(procNetTCP), UDP, isBE)
 	assert.NilError(t, err)
 	t.Log(entries)
 
@@ -79,6 +83,7 @@ func TestParseUDP(t *testing.T) {
 func TestParseAddress(t *testing.T) {
 	tests := []struct {
 		input             string
+		bigEndian         bool
 		expectedIP        net.IP
 		expectedPort      uint16
 		expectedErrSubstr string
@@ -88,13 +93,31 @@ func TestParseAddress(t *testing.T) {
 			expectedIP:   net.IPv4(127, 0, 0, 1),
 			expectedPort: 80,
 		},
+		{
+			input:        "7F000001:0050",
+			bigEndian:    true,
+			expectedIP:   net.IPv4(127, 0, 0, 1),
+			expectedPort: 80,
+		},
 		{
 			input:        "000080FE00000000FF57A6705DC771FE:0050",
 			expectedIP:   net.ParseIP("fe80::70a6:57ff:fe71:c75d"),
 			expectedPort: 80,
 		},
+		{
+			input:        "FE8000000000000070A657FFFE71C75D:0050",
+			bigEndian:    true,
+			expectedIP:   net.ParseIP("fe80::70a6:57ff:fe71:c75d"),
+			expectedPort: 80,
+		},
+		{
+			input:        "00000000000000000000000000000000:0050",
+			expectedIP:   net.IPv6zero,
+			expectedPort: 80,
+		},
 		{
 			input:        "00000000000000000000000000000000:0050",
+			bigEndian:    true,
 			expectedIP:   net.IPv6zero,
 			expectedPort: 80,
 		},
@@ -126,7 +149,7 @@ func TestParseAddress(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.input, func(t *testing.T) {
-			ip, port, err := ParseAddress(test.input)
+			ip, port, err := ParseAddressWithEndian(test.input, test.bigEndian)
 			if test.expectedErrSubstr != "" {
 				assert.ErrorContains(t, err, test.expectedErrSubstr)
 			} else {
diff --git a/pkg/limayaml/defaults_test.go b/pkg/limayaml/defaults_test.go
index 71f7e981..b666880f 100644
--- a/pkg/limayaml/defaults_test.go
+++ b/pkg/limayaml/defaults_test.go
@@ -50,6 +50,8 @@ func TestFillDefault(t *testing.T) {
 		arch = ARMV7L
 	case "riscv64":
 		arch = RISCV64
+	case "s390x":
+		arch = S390X
 	default:
 		t.Skipf("unknown GOARCH: %s", runtime.GOARCH)
 	}
@@ -335,6 +337,7 @@ func TestFillDefault(t *testing.T) {
 			ARMV7L:  "armhf",
 			X8664:   "amd64",
 			RISCV64: "riscv64",
+			S390X:   "s390x",
 		},
 		CPUs:   ptr.Of(7),
 		Memory: ptr.Of("5GiB"),
@@ -547,6 +550,7 @@ func TestFillDefault(t *testing.T) {
 			ARMV7L:  "armv8",
 			X8664:   "pentium",
 			RISCV64: "sifive-u54",
+			S390X:   "z14",
 		},
 		CPUs:   ptr.Of(12),
 		Memory: ptr.Of("7GiB"),