Repository: lima-vm/lima
Pull request #3207 (2025-02-08 12:45:14)

Make it possible to configure user shell
The default shell in FreeBSD is `/bin/tcsh`.

You need to install `/usr/local/bin/bash`.

Maybe others want a different login shell?

For instance `fish` or `zsh`, or whatever...


Patch:
diff --git a/pkg/cidata/cidata.TEMPLATE.d/lima.env b/pkg/cidata/cidata.TEMPLATE.d/lima.env
index b4d00673..b1720f0d 100644
--- a/pkg/cidata/cidata.TEMPLATE.d/lima.env
+++ b/pkg/cidata/cidata.TEMPLATE.d/lima.env
@@ -4,6 +4,7 @@ LIMA_CIDATA_USER={{ .User }}
 LIMA_CIDATA_UID={{ .UID }}
 LIMA_CIDATA_COMMENT={{ .Comment }}
 LIMA_CIDATA_HOME={{ .Home}}
+LIMA_CIDATA_SHELL={{ .Shell }}
 LIMA_CIDATA_HOSTHOME_MOUNTPOINT={{ .HostHomeMountPoint }}
 LIMA_CIDATA_MOUNTS={{ len .Mounts }}
 {{- range $i, $val := .Mounts}}
diff --git a/pkg/cidata/cidata.TEMPLATE.d/user-data b/pkg/cidata/cidata.TEMPLATE.d/user-data
index 13a98c78..3842f8d4 100644
--- a/pkg/cidata/cidata.TEMPLATE.d/user-data
+++ b/pkg/cidata/cidata.TEMPLATE.d/user-data
@@ -34,7 +34,7 @@ users:
     gecos: {{ printf "%q" .Comment }}
 {{- end }}
     homedir: "{{.Home}}"
-    shell: /bin/bash
+    shell: {{.Shell}}
     sudo: ALL=(ALL) NOPASSWD:ALL
     lock_passwd: true
     ssh-authorized-keys:
diff --git a/pkg/cidata/cidata.go b/pkg/cidata/cidata.go
index 2030a9a8..abd2ccf4 100644
--- a/pkg/cidata/cidata.go
+++ b/pkg/cidata/cidata.go
@@ -125,6 +125,7 @@ func templateArgs(bootScripts bool, instDir, name string, instConfig *limayaml.L
 		User:               *instConfig.User.Name,
 		Comment:            *instConfig.User.Comment,
 		Home:               *instConfig.User.Home,
+		Shell:              *instConfig.User.Shell,
 		UID:                *instConfig.User.UID,
 		GuestInstallPrefix: *instConfig.GuestInstallPrefix,
 		UpgradePackages:    *instConfig.UpgradePackages,
diff --git a/pkg/cidata/template.go b/pkg/cidata/template.go
index 92460df1..b2287f6e 100644
--- a/pkg/cidata/template.go
+++ b/pkg/cidata/template.go
@@ -62,6 +62,7 @@ type TemplateArgs struct {
 	User                            string // user name
 	Comment                         string // user information
 	Home                            string // home directory
+	Shell                           string // login shell
 	UID                             uint32
 	SSHPubKeys                      []string
 	Mounts                          []Mount
@@ -109,6 +110,9 @@ func ValidateTemplateArgs(args *TemplateArgs) error {
 	if args.Home == "" {
 		return errors.New("field Home must be set")
 	}
+	if args.Shell == "" {
+		return errors.New("field Shell must be set")
+	}
 	if len(args.SSHPubKeys) == 0 {
 		return errors.New("field SSHPubKeys must be set")
 	}
diff --git a/pkg/limayaml/defaults.go b/pkg/limayaml/defaults.go
index dfa88885..20a157a0 100644
--- a/pkg/limayaml/defaults.go
+++ b/pkg/limayaml/defaults.go
@@ -209,6 +209,9 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) {
 	if y.User.Home == nil {
 		y.User.Home = d.User.Home
 	}
+	if y.User.Shell == nil {
+		y.User.Shell = d.User.Shell
+	}
 	if y.User.UID == nil {
 		y.User.UID = d.User.UID
 	}
@@ -221,6 +224,9 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) {
 	if o.User.Home != nil {
 		y.User.Home = o.User.Home
 	}
+	if o.User.Shell != nil {
+		y.User.Shell = o.User.Shell
+	}
 	if o.User.UID != nil {
 		y.User.UID = o.User.UID
 	}
@@ -236,6 +242,9 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) {
 		y.User.Home = ptr.Of(osutil.LimaUser(existingLimaVersion, warn).HomeDir)
 		warn = false
 	}
+	if y.User.Shell == nil {
+		y.User.Shell = ptr.Of("/bin/bash")
+	}
 	if y.User.UID == nil {
 		uidString := osutil.LimaUser(existingLimaVersion, warn).Uid
 		if uid, err := strconv.ParseUint(uidString, 10, 32); err == nil {
diff --git a/pkg/limayaml/limayaml.go b/pkg/limayaml/limayaml.go
index 6927fab2..3f4e8520 100644
--- a/pkg/limayaml/limayaml.go
+++ b/pkg/limayaml/limayaml.go
@@ -88,6 +88,7 @@ type User struct {
 	Name    *string `yaml:"name,omitempty" json:"name,omitempty" jsonschema:"nullable"`
 	Comment *string `yaml:"comment,omitempty" json:"comment,omitempty" jsonschema:"nullable"`
 	Home    *string `yaml:"home,omitempty" json:"home,omitempty" jsonschema:"nullable"`
+	Shell   *string `yaml:"shell,omitempty" json:"shell,omitempty" jsonschema:"nullable"`
 	UID     *uint32 `yaml:"uid,omitempty" json:"uid,omitempty" jsonschema:"nullable"`
 }
 
diff --git a/templates/default.yaml b/templates/default.yaml
index 9825d9ee..e8c84cfe 100644
--- a/templates/default.yaml
+++ b/templates/default.yaml
@@ -294,6 +294,9 @@ user:
   # It can use the following template variables: {{.Name}}, {{.Hostname}}, {{.UID}}, {{.User}}, and {{.Param.Key}}.
   # 🟢 Builtin default: "/home/{{.User}}.linux"
   home: null
+  # Shell. Needs to be an absolute path.
+  # 🟢 Builtin default: "/bin/bash"
+  shell: null
 
 vmOpts:
   qemu:
diff --git a/website/content/en/docs/dev/internals.md b/website/content/en/docs/dev/internals.md
index bce899a7..48aa0b08 100644
--- a/website/content/en/docs/dev/internals.md
+++ b/website/content/en/docs/dev/internals.md
@@ -181,6 +181,7 @@ The volume label is "cidata", as defined by [cloud-init NoCloud](https://docs.cl
 - `LIMA_CIDATA_UID`: the numeric UID
 - `LIMA_CIDATA_COMMENT`: the full name or comment string
 - `LIMA_CIDATA_HOME`: the guest home directory
+- `LIMA_CIDATA_SHELL`: the guest login shell
 - `LIMA_CIDATA_HOSTHOME_MOUNTPOINT`: the mount point of the host home directory, or empty if not mounted
 - `LIMA_CIDATA_MOUNTS`: the number of the Lima mounts
 - `LIMA_CIDATA_MOUNTS_%d_MOUNTPOINT`: the N-th mount point of Lima mounts (N=0, 1, ...)


Test patch:
diff --git a/pkg/cidata/template_test.go b/pkg/cidata/template_test.go
index e05abd3e..79be8a95 100644
--- a/pkg/cidata/template_test.go
+++ b/pkg/cidata/template_test.go
@@ -17,6 +17,7 @@ func TestConfig(t *testing.T) {
 		UID:     501,
 		Comment: "Foo",
 		Home:    "/home/foo.linux",
+		Shell:   "/bin/bash",
 		SSHPubKeys: []string{
 			"ssh-rsa dummy foo@example.com",
 		},
@@ -35,6 +36,7 @@ func TestConfigCACerts(t *testing.T) {
 		UID:     501,
 		Comment: "Foo",
 		Home:    "/home/foo.linux",
+		Shell:   "/bin/bash",
 		SSHPubKeys: []string{
 			"ssh-rsa dummy foo@example.com",
 		},
@@ -51,10 +53,11 @@ func TestConfigCACerts(t *testing.T) {
 
 func TestTemplate(t *testing.T) {
 	args := &TemplateArgs{
-		Name: "default",
-		User: "foo",
-		UID:  501,
-		Home: "/home/foo.linux",
+		Name:  "default",
+		User:  "foo",
+		UID:   501,
+		Home:  "/home/foo.linux",
+		Shell: "/bin/bash",
 		SSHPubKeys: []string{
 			"ssh-rsa dummy foo@example.com",
 		},
@@ -86,10 +89,11 @@ func TestTemplate(t *testing.T) {
 
 func TestTemplate9p(t *testing.T) {
 	args := &TemplateArgs{
-		Name: "default",
-		User: "foo",
-		UID:  501,
-		Home: "/home/foo.linux",
+		Name:  "default",
+		User:  "foo",
+		UID:   501,
+		Home:  "/home/foo.linux",
+		Shell: "/bin/bash",
 		SSHPubKeys: []string{
 			"ssh-rsa dummy foo@example.com",
 		},
diff --git a/pkg/limayaml/defaults_test.go b/pkg/limayaml/defaults_test.go
index 03b7ff80..1a2f91b8 100644
--- a/pkg/limayaml/defaults_test.go
+++ b/pkg/limayaml/defaults_test.go
@@ -117,6 +117,7 @@ func TestFillDefault(t *testing.T) {
 			Name:    ptr.Of(user.Username),
 			Comment: ptr.Of(user.Name),
 			Home:    ptr.Of(user.HomeDir),
+			Shell:   ptr.Of("/bin/bash"),
 			UID:     ptr.Of(uint32(uid)),
 		},
 	}
@@ -441,6 +442,7 @@ func TestFillDefault(t *testing.T) {
 			Name:    ptr.Of("xxx"),
 			Comment: ptr.Of("Foo Bar"),
 			Home:    ptr.Of("/tmp"),
+			Shell:   ptr.Of("/bin/tcsh"),
 			UID:     ptr.Of(uint32(8080)),
 		},
 	}
@@ -664,6 +666,7 @@ func TestFillDefault(t *testing.T) {
 			Name:    ptr.Of("foo"),
 			Comment: ptr.Of("foo bar baz"),
 			Home:    ptr.Of("/override"),
+			Shell:   ptr.Of("/bin/sh"),
 			UID:     ptr.Of(uint32(1122)),
 		},
 	}