Repository: lima-vm/lima

Issue:
Print current boot session log in cloud-init progress monitoring.
### Description

`/var/log/cloud-init-output.log` contains the previous boot session's log.
So, it needs to start printing the log from the beginning of the current boot session.

Border of the session in `/var/log/cloud-init-output.log`::
```log
LIMA 2025-08-25T15:04:03+09:00| Exiting with code 0
Cloud-init v. 25.1.4-0ubuntu0~24.04.1 finished at Mon, 25 Aug 2025 06:04:03 +0000. Datasource DataSourceNoCloud [seed=/dev/vdb].  Up 5.37 seconds
Cloud-init v. 25.1.4-0ubuntu0~24.04.1 running 'init-local' at Mon, 25 Aug 2025 06:07:19 +0000. Up 1.00 seconds.
Cloud-init v. 25.1.4-0ubuntu0~24.04.1 running 'init' at Mon, 25 Aug 2025 06:07:21 +0000. Up 2.59 seconds.
```

Hints from issue comments:
@olamilekan000: Can the content of the boot session always be overwritten on every reboot?
If that's not possible, can we have a new log file on every reboot? We can always tail the the content of the latest log file.@norio-nomura: On some conditions (#2905), VM's clock does not sync to the host at the beginning of the boot session.
On such conditions, time syncing happens while the boot session, comparing the timestamp in the log with host time may not work for detecting the beginning of the current boot session.
@norio-nomura: Following code, depending on `awk`, works as expected:
```golang
	args = append(args,
		"-p", strconv.Itoa(a.sshLocalPort),
		"127.0.0.1",
		"sh", "-c",
		`"sudo tail -n +$(sudo awk '
			BEGIN{b=1; e=1}
			/^Cloud-init.* finished/{e=NR}
			/.*/{if(NR>e){b=e+1}}
			END{print b}
		' /var/log/cloud-init-output.log) -f /var/log/cloud-init-output.log"`,
	)
```

edited code for `awk`@norio-nomura: > Following code, depending on `awk`, works as expected:
> 
> 	args = append(args,
> 		"-p", strconv.Itoa(a.sshLocalPort),
> 		"127.0.0.1",
> 		"sh", "-c",
> 		`"sudo tail -n +$(sudo awk '
> 			BEGIN{b=1; e=1}
> 			/^Cloud-init.* finished/{e=NR}
> 			/.*/{if(NR>e){b=e+1}}
> 			END{print b}
> 		' /var/log/cloud-init-output.log) -f /var/log/cloud-init-output.log"`,
> 	)
> edited code for `awk`

This does not work with `template://alpine`, because printing host key happens after `/^Cloud-init.* finished/`.
Is the cloud-init on Alpine’s behavior different from the original?@afbjorklund: > Is the cloud-init on Alpine’s behavior different from the original?

We don't run (the real) cloud-init on Alpine, or with the WSL2 driver

So those scripts would have to be modified, with "expected" output

Or reported by the agents, if we want something more controllable@norio-nomura: > This does not work with `template://alpine`, because printing host key happens after `/^Cloud-init.* finished/`.
> Is the cloud-init on Alpine’s behavior different from the original?

opened #3916 without applying a workaround to Alpine

Solution patch:
diff --git a/cmd/lima-guestagent/main_linux.go b/cmd/lima-guestagent/main_linux.go
index 875880a6..1e203c4c 100644
--- a/cmd/lima-guestagent/main_linux.go
+++ b/cmd/lima-guestagent/main_linux.go
@@ -9,11 +9,13 @@ import (
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 
+	"github.com/lima-vm/lima/v2/cmd/yq"
 	"github.com/lima-vm/lima/v2/pkg/debugutil"
 	"github.com/lima-vm/lima/v2/pkg/version"
 )
 
 func main() {
+	yq.MaybeRunYQ()
 	if err := newApp().Execute(); err != nil {
 		logrus.Fatal(err)
 	}
diff --git a/cmd/limactl/main.go b/cmd/limactl/main.go
index d5a51ea4..2b58312c 100644
--- a/cmd/limactl/main.go
+++ b/cmd/limactl/main.go
@@ -17,6 +17,7 @@ import (
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 
+	"github.com/lima-vm/lima/v2/cmd/yq"
 	"github.com/lima-vm/lima/v2/pkg/debugutil"
 	"github.com/lima-vm/lima/v2/pkg/driver/external/server"
 	"github.com/lima-vm/lima/v2/pkg/fsutil"
@@ -32,6 +33,7 @@ const (
 )
 
 func main() {
+	yq.MaybeRunYQ()
 	if runtime.GOOS == "windows" {
 		extras, hasExtra := os.LookupEnv("_LIMA_WINDOWS_EXTRA_PATH")
 		if hasExtra && strings.TrimSpace(extras) != "" {
diff --git a/cmd/yq/yq.go b/cmd/yq/yq.go
new file mode 100644
index 00000000..0203f16d
--- /dev/null
+++ b/cmd/yq/yq.go
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: Copyright The Lima Authors
+// SPDX-License-Identifier: Apache-2.0
+
+// SPDX-FileCopyrightText: Copyright (c) 2017 Mike Farah
+
+// This file has been adapted from https://github.com/mikefarah/yq/blob/v4.47.1/yq.go
+
+package yq
+
+import (
+	"os"
+	"path/filepath"
+	"strings"
+
+	command "github.com/mikefarah/yq/v4/cmd"
+)
+
+func main() {
+	cmd := command.New()
+	args := os.Args[1:]
+	_, _, err := cmd.Find(args)
+	if err != nil && args[0] != "__complete" {
+		// default command when nothing matches...
+		newArgs := []string{"eval"}
+		cmd.SetArgs(append(newArgs, os.Args[1:]...))
+	}
+	code := 0
+	if err := cmd.Execute(); err != nil {
+		code = 1
+	}
+	os.Exit(code)
+}
+
+// MaybeRunYQ runs as `yq` if the program name or first argument is `yq`.
+// Only returns to caller if os.Args doesn't contain a `yq` command.
+func MaybeRunYQ() {
+	progName := filepath.Base(os.Args[0])
+	// remove all extensions, so we match "yq.lima.exe"
+	progName, _, _ = strings.Cut(progName, ".")
+	if progName == "yq" {
+		main()
+	}
+	if len(os.Args) > 1 && os.Args[1] == "yq" {
+		os.Args = os.Args[1:]
+		main()
+	}
+}
diff --git a/pkg/cidata/cidata.go b/pkg/cidata/cidata.go
index 7e633018..a44608f3 100644
--- a/pkg/cidata/cidata.go
+++ b/pkg/cidata/cidata.go
@@ -150,7 +150,7 @@ func templateArgs(ctx context.Context, bootScripts bool, instDir, name string, i
 		args.RosettaEnabled = *instConfig.VMOpts.VZ.Rosetta.Enabled
 	}
 	if instConfig.VMOpts.VZ.Rosetta.BinFmt != nil {
-		args.RosettaEnabled = *instConfig.VMOpts.VZ.Rosetta.BinFmt
+		args.RosettaBinFmt = *instConfig.VMOpts.VZ.Rosetta.BinFmt
 	}
 
 	firstUsernetIndex := limayaml.FirstUsernetIndex(instConfig)
diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go
index 2ecaef08..09248aab 100644
--- a/pkg/driver/driver.go
+++ b/pkg/driver/driver.go
@@ -92,6 +92,7 @@ type ConfiguredDriver struct {
 }
 
 type Info struct {
+	Name        string         `json:"name"`
 	VsockPort   int            `json:"vsockPort"`
 	VirtioPort  string         `json:"virtioPort"`
 	InstanceDir string         `json:"instanceDir,omitempty"`
@@ -99,8 +100,7 @@ type Info struct {
 }
 
 type DriverFeatures struct {
-	DriverName           string `json:"driverName"`
-	CanRunGUI            bool   `json:"canRunGui,omitempty"`
-	DynamicSSHAddress    bool   `json:"dynamicSSHAddress"`
-	SkipSocketForwarding bool   `json:"skipSocketForwarding"`
+	CanRunGUI            bool `json:"canRunGui,omitempty"`
+	DynamicSSHAddress    bool `json:"dynamicSSHAddress"`
+	SkipSocketForwarding bool `json:"skipSocketForwarding"`
 }
diff --git a/pkg/driver/external/server/server.go b/pkg/driver/external/server/server.go
index 2f5c37c9..a6068be6 100644
--- a/pkg/driver/external/server/server.go
+++ b/pkg/driver/external/server/server.go
@@ -68,7 +68,7 @@ func Serve(ctx context.Context, driver driver.Driver) {
 	logger := logrus.New()
 	logger.SetLevel(logrus.DebugLevel)
 
-	socketPath := filepath.Join(os.TempDir(), fmt.Sprintf("lima-driver-%s-%d.sock", driver.Info().Features.DriverName, os.Getpid()))
+	socketPath := filepath.Join(os.TempDir(), fmt.Sprintf("lima-driver-%s-%d.sock", driver.Info().Name, os.Getpid()))
 
 	defer func() {
 		if err := os.Remove(socketPath); err != nil && !os.IsNotExist(err) {
@@ -146,7 +146,7 @@ func Serve(ctx context.Context, driver driver.Driver) {
 	}()
 
 	go func() {
-		logger.Infof("Starting external driver server for %s", driver.Info().Features.DriverName)
+		logger.Infof("Starting external driver server for %s", driver.Info().Name)
 		logger.Infof("Server starting on Unix socket: %s", socketPath)
 		if err := server.Serve(tListener); err != nil {
 			if errors.Is(err, grpc.ErrServerStopped) {
diff --git a/pkg/driver/qemu/qemu_driver.go b/pkg/driver/qemu/qemu_driver.go
index 24ae8f2b..303c95c7 100644
--- a/pkg/driver/qemu/qemu_driver.go
+++ b/pkg/driver/qemu/qemu_driver.go
@@ -674,6 +674,7 @@ func (a *qArgTemplateApplier) applyTemplate(qArg string) (string, error) {
 
 func (l *LimaQemuDriver) Info() driver.Info {
 	var info driver.Info
+	info.Name = "qemu"
 	if l.Instance != nil && l.Instance.Dir != "" {
 		info.InstanceDir = l.Instance.Dir
 	}
@@ -683,7 +684,6 @@ func (l *LimaQemuDriver) Info() driver.Info {
 	info.Features = driver.DriverFeatures{
 		DynamicSSHAddress:    false,
 		SkipSocketForwarding: false,
-		DriverName:           "qemu",
 		CanRunGUI:            false,
 	}
 	return info
diff --git a/pkg/driver/vz/vz_driver_darwin.go b/pkg/driver/vz/vz_driver_darwin.go
index decfc10f..85b7a55d 100644
--- a/pkg/driver/vz/vz_driver_darwin.go
+++ b/pkg/driver/vz/vz_driver_darwin.go
@@ -372,7 +372,7 @@ func (l *LimaVzDriver) GuestAgentConn(_ context.Context) (net.Conn, string, erro
 func (l *LimaVzDriver) Info() driver.Info {
 	var info driver.Info
 
-	info.Features.DriverName = "vz"
+	info.Name = "vz"
 	info.VsockPort = l.vSockPort
 	info.VirtioPort = l.virtioPort
 	if l.Instance != nil {
@@ -386,7 +386,6 @@ func (l *LimaVzDriver) Info() driver.Info {
 	info.Features = driver.DriverFeatures{
 		DynamicSSHAddress:    false,
 		SkipSocketForwarding: false,
-		DriverName:           "vz",
 		CanRunGUI:            guiFlag,
 	}
 	return info
diff --git a/pkg/driver/wsl2/wsl_driver_windows.go b/pkg/driver/wsl2/wsl_driver_windows.go
index ef49dd73..01b21e4e 100644
--- a/pkg/driver/wsl2/wsl_driver_windows.go
+++ b/pkg/driver/wsl2/wsl_driver_windows.go
@@ -306,6 +306,7 @@ func (l *LimaWslDriver) GuestAgentConn(ctx context.Context) (net.Conn, string, e
 
 func (l *LimaWslDriver) Info() driver.Info {
 	var info driver.Info
+	info.Name = "wsl2"
 	if l.Instance != nil {
 		info.InstanceDir = l.Instance.Dir
 	}
@@ -315,7 +316,6 @@ func (l *LimaWslDriver) Info() driver.Info {
 	info.Features = driver.DriverFeatures{
 		DynamicSSHAddress:    true,
 		SkipSocketForwarding: true,
-		DriverName:           "wsl2",
 		CanRunGUI:            l.canRunGUI(),
 	}
 	return info
diff --git a/pkg/driverutil/instance.go b/pkg/driverutil/instance.go
index e861b1cf..dfb1422c 100644
--- a/pkg/driverutil/instance.go
+++ b/pkg/driverutil/instance.go
@@ -39,6 +39,6 @@ func CreateConfiguredDriver(inst *limatype.Instance, sshLocalPort int) (*driver.
 		return extDriver.Client.Configure(inst), nil
 	}
 
-	logrus.Debugf("Using internal driver %q", intDriver.Info().Features.DriverName)
+	logrus.Debugf("Using internal driver %q", intDriver.Info().Name)
 	return intDriver.Configure(inst), nil
 }
diff --git a/pkg/hostagent/hostagent.go b/pkg/hostagent/hostagent.go
index 6fd52c8a..b5ca940e 100644
--- a/pkg/hostagent/hostagent.go
+++ b/pkg/hostagent/hostagent.go
@@ -892,7 +892,13 @@ func (a *HostAgent) watchCloudInitProgress(ctx context.Context) {
 	args = append(args,
 		"-p", strconv.Itoa(a.sshLocalPort),
 		"127.0.0.1",
-		"sudo", "tail", "-n", "+1", "-f", "/var/log/cloud-init-output.log",
+		"sh", "-c",
+		`"sudo tail -n +$(sudo awk '
+			BEGIN{b=1; e=1}
+			/^Cloud-init.* finished/{e=NR}
+			/.*/{if(NR>e){b=e+1}}
+			END{print b}
+		' /var/log/cloud-init-output.log) -f /var/log/cloud-init-output.log"`,
 	)
 
 	cmd = exec.CommandContext(ctx, a.sshConfig.Binary(), args...)
diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go
index d69146c5..e9369203 100644
--- a/pkg/registry/registry.go
+++ b/pkg/registry/registry.go
@@ -195,7 +195,7 @@ func isExecutable(mode os.FileMode) bool {
 }
 
 func Register(driver driver.Driver) {
-	name := driver.Info().Features.DriverName
+	name := driver.Info().Name
 	if _, exists := internalDrivers[name]; exists {
 		return
 	}



Test patch:
diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go
index d75565af..eccc5712 100644
--- a/pkg/registry/registry_test.go
+++ b/pkg/registry/registry_test.go
@@ -45,7 +45,7 @@ func (m *mockDriver) Unregister(_ context.Context) error
 func (m *mockDriver) ForwardGuestAgent() bool                                    { return false }
 func (m *mockDriver) GuestAgentConn(_ context.Context) (net.Conn, string, error) { return nil, "", nil }
 func (m *mockDriver) Info() driver.Info {
-	return driver.Info{Features: driver.DriverFeatures{DriverName: m.Name}}
+	return driver.Info{Name: m.Name}
 }
 func (m *mockDriver) Configure(_ *limatype.Instance) *driver.ConfiguredDriver      { return nil }
 func (m *mockDriver) AcceptConfig(_ *limatype.LimaYAML, _ string) error            { return nil }
@@ -80,7 +80,7 @@ func TestRegister(t *testing.T) {
 	assert.Equal(t, exists, true)
 	assert.Assert(t, extDriver == nil)
 	assert.Assert(t, intDriver != nil)
-	assert.Equal(t, intDriver.Info().Features.DriverName, "test-driver")
+	assert.Equal(t, intDriver.Info().Name, "test-driver")
 
 	vmTypes := List()
 	assert.Equal(t, vmTypes["test-driver-2"], Internal)