Repository: lima-vm/lima

Issue:
`limactl restart` should accept `--progress` flags
### Description

#3846 is very useful.
It could be more useful if it can do instead of `limactl stop default; limactl start default --progress`.

Hints from issue comments:

Solution patch:
diff --git a/cmd/limactl/restart.go b/cmd/limactl/restart.go
index a46bd09e..7416b84e 100644
--- a/cmd/limactl/restart.go
+++ b/cmd/limactl/restart.go
@@ -21,6 +21,7 @@ func newRestartCommand() *cobra.Command {
 	}
 
 	restartCmd.Flags().BoolP("force", "f", false, "Force stop and restart the instance")
+	restartCmd.Flags().Bool("progress", false, "Show provision script progress by tailing cloud-init logs")
 	return restartCmd
 }
 
@@ -40,12 +41,16 @@ func restartAction(cmd *cobra.Command, args []string) error {
 	if err != nil {
 		return err
 	}
+	progress, err := cmd.Flags().GetBool("progress")
+	if err != nil {
+		return err
+	}
 
 	if force {
-		return instance.RestartForcibly(ctx, inst)
+		return instance.RestartForcibly(ctx, inst, progress)
 	}
 
-	return instance.Restart(ctx, inst)
+	return instance.Restart(ctx, inst, progress)
 }
 
 func restartBashComplete(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
diff --git a/cmd/limactl/shell.go b/cmd/limactl/shell.go
index f3aa208d..7fe79405 100644
--- a/cmd/limactl/shell.go
+++ b/cmd/limactl/shell.go
@@ -36,6 +36,11 @@ lima command is provided as an alias for limactl shell $LIMA_INSTANCE. $LIMA_INS
 By default, the first 'ssh' executable found in the host's PATH is used to connect to the Lima instance.
 A custom ssh alias can be used instead by setting the $` + sshutil.EnvShellSSH + ` environment variable.
 
+Environment Variables:
+  --preserve-env: Propagates host environment variables to the guest instance.
+                  Use LIMA_SHELLENV_ALLOW to specify which variables to allow.
+                  Use LIMA_SHELLENV_BLOCK to specify which variables to block (extends default blocklist with +).
+
 Hint: try --debug to show the detailed logs, if it seems hanging (mostly due to some SSH issue).
 `
 
diff --git a/cmd/nerdctl.lima b/cmd/nerdctl.lima
index b398e075..9a5205a6 100755
--- a/cmd/nerdctl.lima
+++ b/cmd/nerdctl.lima
@@ -1,3 +1,4 @@
 #!/bin/sh
 set -eu
+# Use --preserve-env to pass through environment variables from host machine into guest instance
 exec limactl shell --preserve-env default nerdctl "$@"
diff --git a/hack/test-preserve-env.sh b/hack/test-preserve-env.sh
new file mode 100755
index 00000000..20959107
--- /dev/null
+++ b/hack/test-preserve-env.sh
@@ -0,0 +1,94 @@
+#!/usr/bin/env bash
+
+# SPDX-FileCopyrightText: Copyright The Lima Authors
+# SPDX-License-Identifier: Apache-2.0
+
+set -eu -o pipefail
+
+scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+# shellcheck source=common.inc.sh
+source "${scriptdir}/common.inc.sh"
+
+if [ "$#" -ne 1 ]; then
+	ERROR "Usage: $0 NAME"
+	exit 1
+fi
+
+NAME="$1"
+
+INFO "Testing --preserve-env flag"
+
+# Environment variable propagation with --preserve-env
+INFO "=== Environment variable propagation with --preserve-env ==="
+test_var_name="LIMA_TEST_PRESERVE_ENV_VAR"
+test_var_value="test-value-${RANDOM}"
+
+got="$(LIMA_SHELLENV_ALLOW="LIMA_TEST_*" LIMA_TEST_PRESERVE_ENV_VAR="$test_var_value" limactl shell --preserve-env "$NAME" printenv "$test_var_name" 2>/dev/null || echo "NOT_FOUND")"
+INFO "$test_var_name: expected=${test_var_value}, got=${got}"
+if [ "$got" != "$test_var_value" ]; then
+	ERROR "Environment variable was not propagated with --preserve-env"
+	exit 1
+fi
+
+# Clean up before next test
+unset LIMA_TEST_PRESERVE_ENV_VAR
+unset LIMA_SHELLENV_ALLOW
+
+# Environment variable NOT propagated without --preserve-env
+INFO "=== Environment variable NOT propagated without --preserve-env ==="
+got_without_flag="$(LIMA_TEST_PRESERVE_ENV_VAR="$test_var_value" limactl shell "$NAME" printenv "$test_var_name" 2>/dev/null || echo "NOT_FOUND")"
+INFO "$test_var_name without --preserve-env: got=${got_without_flag}"
+if [ "$got_without_flag" != "NOT_FOUND" ]; then
+	ERROR "Environment variable was unexpectedly propagated without --preserve-env"
+	exit 1
+fi
+
+# Blocked environment variables should not be propagated even with --preserve-env
+INFO "=== Blocked environment variables are not propagated with --preserve-env ==="
+blocked_var_name="HOME"
+fake_home="/tmp/fake-home-${RANDOM}"
+got_blocked="$(HOME="$fake_home" limactl shell --preserve-env "$NAME" printenv "$blocked_var_name" 2>/dev/null || echo "NOT_FOUND")"
+INFO "$blocked_var_name: host=${fake_home}, guest=${got_blocked}"
+if [ "$got_blocked" = "$fake_home" ]; then
+	ERROR "Blocked environment variable $blocked_var_name was propagated"
+	exit 1
+fi
+
+# LIMA_SHELLENV_BLOCK functionality
+INFO "=== LIMA_SHELLENV_BLOCK with custom pattern ==="
+custom_test_var="LIMA_TEST_CUSTOM_BLOCK"
+custom_test_value="should-be-blocked"
+got_custom_blocked="$(LIMA_SHELLENV_BLOCK="+LIMA_TEST_CUSTOM_*" LIMA_TEST_CUSTOM_BLOCK="$custom_test_value" limactl shell --preserve-env "$NAME" printenv "$custom_test_var" 2>/dev/null || echo "NOT_FOUND")"
+INFO "$custom_test_var with LIMA_SHELLENV_BLOCK: got=${got_custom_blocked}"
+if [ "$got_custom_blocked" != "NOT_FOUND" ]; then
+	ERROR "Custom blocked environment variable was propagated"
+	exit 1
+fi
+
+# Clean up before next test
+unset LIMA_TEST_CUSTOM_BLOCK 2>/dev/null || true
+unset LIMA_SHELLENV_BLOCK 2>/dev/null || true
+
+# LIMA_SHELLENV_ALLOW functionality
+INFO "=== LIMA_SHELLENV_ALLOW with custom pattern ==="
+allow_test_var="LIMA_TEST_ALLOW_VAR"
+allow_test_value="should-be-allowed"
+got_allowed="$(LIMA_SHELLENV_ALLOW="LIMA_TEST_ALLOW_*" LIMA_TEST_ALLOW_VAR="$allow_test_value" limactl shell --preserve-env "$NAME" printenv "$allow_test_var" 2>/dev/null || echo "NOT_FOUND")"
+INFO "$allow_test_var with LIMA_SHELLENV_ALLOW: got=${got_allowed}"
+if [ "$got_allowed" != "$allow_test_value" ]; then
+	ERROR "Allowed environment variable was not propagated"
+	exit 1
+fi
+
+# Non-allowed variables are blocked when LIMA_SHELLENV_ALLOW is set
+INFO "=== Non-allowed variables are blocked when LIMA_SHELLENV_ALLOW is set ==="
+other_test_var="LIMA_TEST_OTHER_VAR"
+other_test_value="should-be-blocked"
+got_other="$(LIMA_SHELLENV_ALLOW="LIMA_TEST_ALLOW_*" LIMA_TEST_OTHER_VAR="$other_test_value" limactl shell --preserve-env "$NAME" printenv "$other_test_var" 2>/dev/null || echo "NOT_FOUND")"
+INFO "$other_test_var with LIMA_SHELLENV_ALLOW (should be blocked): got=${got_other}"
+if [ "$got_other" != "NOT_FOUND" ]; then
+	ERROR "Non-allowed environment variable was propagated when LIMA_SHELLENV_ALLOW was set"
+	exit 1
+fi
+
+INFO "All --preserve-env tests passed"
diff --git a/hack/test-templates.sh b/hack/test-templates.sh
index 58158a41..f01d249e 100755
--- a/hack/test-templates.sh
+++ b/hack/test-templates.sh
@@ -58,6 +58,7 @@ declare -A CHECKS=(
 	["provision-data"]=""
 	["param-env-variables"]=""
 	["set-user"]=""
+	["preserve-env"]="1"
 )
 
 case "$NAME" in
@@ -276,6 +277,10 @@ if [[ -n ${CHECKS["mount-home"]} ]]; then
 	"${scriptdir}"/test-mount-home.sh "$NAME"
 fi
 
+if [[ -n ${CHECKS["preserve-env"]} ]]; then
+	"${scriptdir}"/test-preserve-env.sh "$NAME"
+fi
+
 # Use GHCR to avoid hitting Docker Hub rate limit
 nginx_image="ghcr.io/stargz-containers/nginx:1.19-alpine-org"
 alpine_image="ghcr.io/containerd/alpine:3.14.0"
diff --git a/pkg/cidata/cidata.TEMPLATE.d/boot/05-rosetta-volume.sh b/pkg/cidata/cidata.TEMPLATE.d/boot/05-rosetta-volume.sh
index 4a43ff3f..9fa517ae 100755
--- a/pkg/cidata/cidata.TEMPLATE.d/boot/05-rosetta-volume.sh
+++ b/pkg/cidata/cidata.TEMPLATE.d/boot/05-rosetta-volume.sh
@@ -27,7 +27,7 @@ if [ "$LIMA_CIDATA_ROSETTA_BINFMT" = "true" ]; then
 	[ ! -d "$(dirname "$binfmtd_conf")" ] || [ -f "$binfmtd_conf" ] || echo "$rosetta_binfmt" >"$binfmtd_conf"
 else
 	# unregister rosetta from binfmt_misc if it exists
-	[ ! -f "$binfmt_entry" ] || echo -1 | "$binfmt_entry"
+	[ ! -f "$binfmt_entry" ] || echo -1 >"$binfmt_entry"
 	# remove binfmt.d(5) configuration if it exists
 	[ ! -f "$binfmtd_conf" ] || rm "$binfmtd_conf"
 fi
diff --git a/pkg/driver/vz/boot/05-rosetta-volume.sh b/pkg/driver/vz/boot/05-rosetta-volume.sh
index 4a43ff3f..9fa517ae 100755
--- a/pkg/driver/vz/boot/05-rosetta-volume.sh
+++ b/pkg/driver/vz/boot/05-rosetta-volume.sh
@@ -27,7 +27,7 @@ if [ "$LIMA_CIDATA_ROSETTA_BINFMT" = "true" ]; then
 	[ ! -d "$(dirname "$binfmtd_conf")" ] || [ -f "$binfmtd_conf" ] || echo "$rosetta_binfmt" >"$binfmtd_conf"
 else
 	# unregister rosetta from binfmt_misc if it exists
-	[ ! -f "$binfmt_entry" ] || echo -1 | "$binfmt_entry"
+	[ ! -f "$binfmt_entry" ] || echo -1 >"$binfmt_entry"
 	# remove binfmt.d(5) configuration if it exists
 	[ ! -f "$binfmtd_conf" ] || rm "$binfmtd_conf"
 fi
diff --git a/pkg/instance/restart.go b/pkg/instance/restart.go
index 310bfd80..52db7a4d 100644
--- a/pkg/instance/restart.go
+++ b/pkg/instance/restart.go
@@ -14,10 +14,9 @@ import (
 
 const (
 	launchHostAgentForeground = false
-	showProgress              = false
 )
 
-func Restart(ctx context.Context, inst *limatype.Instance) error {
+func Restart(ctx context.Context, inst *limatype.Instance, showProgress bool) error {
 	if err := StopGracefully(ctx, inst, true); err != nil {
 		return err
 	}
@@ -33,7 +32,7 @@ func Restart(ctx context.Context, inst *limatype.Instance) error {
 	return nil
 }
 
-func RestartForcibly(ctx context.Context, inst *limatype.Instance) error {
+func RestartForcibly(ctx context.Context, inst *limatype.Instance, showProgress bool) error {
 	logrus.Info("Restarting the instance forcibly")
 	StopForcibly(inst)
 
diff --git a/website/content/en/docs/config/environment-variables.md b/website/content/en/docs/config/environment-variables.md
index dcaa128c..064917e9 100644
--- a/website/content/en/docs/config/environment-variables.md
+++ b/website/content/en/docs/config/environment-variables.md
@@ -57,6 +57,45 @@ This page documents the environment variables used in Lima.
   lima
   ```
 
+### `LIMA_SHELLENV_ALLOW`
+
+- **Description**: Specifies a comma-separated list of environment variable patterns to allow when propagating environment variables to the Lima instance with `--preserve-env`. When set, **only** variables matching these patterns will be passed through, completely overriding the default block list behavior. This feature only applies to Lima v2.0.0 or later.
+- **Default**: unset (when using `--preserve-env`, all variables are propagated except those matching the block list patterns)
+- **Usage**:
+  ```sh
+  export LIMA_SHELLENV_ALLOW="FPATH,XAUTHORITY,CUSTOM_*"
+  limactl shell --preserve-env default
+  ```
+- **Behavior**:
+  - **Without `--preserve-env`**: No environment variables are propagated (regardless of this setting)
+  - **With `--preserve-env` and `LIMA_SHELLENV_ALLOW` unset**: All variables are propagated except those in the block list
+  - **With `--preserve-env` and `LIMA_SHELLENV_ALLOW` set**: Only variables matching the allow patterns are propagated (block list is ignored)
+- **Note**: Patterns support wildcards using `*` at the end (e.g., `CUSTOM_*` matches `CUSTOM_VAR`, `CUSTOM_PATH`, etc.).
+
+### `LIMA_SHELLENV_BLOCK`
+
+- **Description**: Specifies a comma-separated list of environment variable patterns to block when propagating environment variables to the Lima instance with `--preserve-env`. Can either replace the default block list or extend it by prefixing with `+`. This feature only applies to Lima v2.0.0 or later.
+- **Default**: A predefined list of system and shell-specific variables that should not be propagated:
+  - Shell variables: `BASH*`, `SHELL`, `SHLVL`, `ZSH*`, `ZDOTDIR`, `FPATH`
+  - System paths: `PATH`, `PWD`, `OLDPWD`, `TMPDIR`
+  - User/system info: `HOME`, `USER`, `LOGNAME`, `UID`, `GID`, `EUID`, `GROUP`, `HOSTNAME`
+  - Display/terminal: `DISPLAY`, `TERM`, `TERMINFO`, `XAUTHORITY`, `XDG_*`
+  - SSH/security: `SSH_*`
+  - Dynamic linker: `DYLD_*`, `LD_*`
+  - Internal variables: `_*` (variables starting with underscore)
+  
+  See [`GetDefaultBlockList()`](https://github.com/lima-vm/lima/blob/master/pkg/envutil/envutil.go#L133) for the complete list.
+- **Usage**:
+  ```sh
+  # Replace default block list entirely (not recommended)
+  export LIMA_SHELLENV_BLOCK="SECRET_*,PRIVATE_*"
+  
+  # Extend default block list (recommended)
+  export LIMA_SHELLENV_BLOCK="+SECRET_*,PRIVATE_*"
+  limactl shell --preserve-env default
+  ```
+- **Note**: Patterns support wildcards using `*` at the end (e.g., `SSH_*` matches `SSH_AUTH_SOCK`, `SSH_AGENT_PID`). Use the `+` prefix to add to the default block list rather than replacing it entirely. This variable only affects the `--preserve-env` flag behavior.
+
 ### `LIMACTL`
 
 - **Description**: Specifies the path to the `limactl` binary.



Test patch:
diff --git a/pkg/envutil/envutil_test.go b/pkg/envutil/envutil_test.go
index b6fb37ea..3530288f 100644
--- a/pkg/envutil/envutil_test.go
+++ b/pkg/envutil/envutil_test.go
@@ -207,7 +207,7 @@ func TestGetDefaultBlockList(t *testing.T) {
 
 	assert.DeepEqual(t, blocklist, defaultBlockList)
 
-	expectedItems := []string{"PATH", "HOME", "SSH_*"}
+	expectedItems := []string{"PATH", "HOME", "SSH_*", "USER"}
 	for _, item := range expectedItems {
 		found := slices.Contains(blocklist, item)
 		assert.Assert(t, found, "Expected builtin blocklist to contain %q", item)