Skip to content

Commit f5c2776

Browse files
committed
Make guest username, uid, home directory configurable
Also disallows the "admin" username by default (because it is a builtin user in Ubuntu), but can be overridden by setting it explicitly in lima.yaml. Signed-off-by: Jan Dubois <[email protected]>
1 parent 361fe23 commit f5c2776

File tree

19 files changed

+289
-175
lines changed

19 files changed

+289
-175
lines changed

.golangci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ linters-settings:
121121
- name: context-keys-type
122122
- name: deep-exit
123123
- name: dot-imports
124+
arguments:
125+
- allowedPackages:
126+
- github.com/lima-vm/lima/pkg/must
124127
- name: empty-block
125128
- name: error-naming
126129
- name: error-return

cmd/limactl/copy.go

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"strings"
99

1010
"github.com/coreos/go-semver/semver"
11-
"github.com/lima-vm/lima/pkg/osutil"
1211
"github.com/lima-vm/lima/pkg/sshutil"
1312
"github.com/lima-vm/lima/pkg/store"
1413
"github.com/sirupsen/logrus"
@@ -48,11 +47,7 @@ func copyAction(cmd *cobra.Command, args []string) error {
4847
if err != nil {
4948
return err
5049
}
51-
u, err := osutil.LimaUser(false)
52-
if err != nil {
53-
return err
54-
}
55-
instDirs := make(map[string]string)
50+
instances := make(map[string]*store.Instance)
5651
scpFlags := []string{}
5752
scpArgs := []string{}
5853
debug, err := cmd.Flags().GetBool("debug")
@@ -85,28 +80,28 @@ func copyAction(cmd *cobra.Command, args []string) error {
8580
}
8681
if legacySSH {
8782
scpFlags = append(scpFlags, "-P", fmt.Sprintf("%d", inst.SSHLocalPort))
88-
scpArgs = append(scpArgs, fmt.Sprintf("%[email protected]:%s", u.Username, path[1]))
83+
scpArgs = append(scpArgs, fmt.Sprintf("%[email protected]:%s", *inst.Config.User.Username, path[1]))
8984
} else {
90-
scpArgs = append(scpArgs, fmt.Sprintf("scp://%[email protected]:%d/%s", u.Username, inst.SSHLocalPort, path[1]))
85+
scpArgs = append(scpArgs, fmt.Sprintf("scp://%[email protected]:%d/%s", *inst.Config.User.Username, inst.SSHLocalPort, path[1]))
9186
}
92-
instDirs[instName] = inst.Dir
87+
instances[instName] = inst
9388
default:
9489
return fmt.Errorf("path %q contains multiple colons", arg)
9590
}
9691
}
97-
if legacySSH && len(instDirs) > 1 {
92+
if legacySSH && len(instances) > 1 {
9893
return fmt.Errorf("more than one (instance) host is involved in this command, this is only supported for openSSH v8.0 or higher")
9994
}
10095
scpFlags = append(scpFlags, "-3", "--")
10196
scpArgs = append(scpFlags, scpArgs...)
10297

10398
var sshOpts []string
104-
if len(instDirs) == 1 {
99+
if len(instances) == 1 {
105100
// Only one (instance) host is involved; we can use the instance-specific
106101
// arguments such as ControlPath. This is preferred as we can multiplex
107102
// sessions without re-authenticating (MaxSessions permitting).
108-
for _, instDir := range instDirs {
109-
sshOpts, err = sshutil.SSHOpts(instDir, false, false, false, false)
103+
for _, inst := range instances {
104+
sshOpts, err = sshutil.SSHOpts(inst.Dir, *inst.Config.User.Username, false, false, false, false)
110105
if err != nil {
111106
return err
112107
}

cmd/limactl/shell.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ func shellAction(cmd *cobra.Command, args []string) error {
167167

168168
sshOpts, err := sshutil.SSHOpts(
169169
inst.Dir,
170+
*inst.Config.User.Username,
170171
*inst.Config.SSH.LoadDotSSHPubKeys,
171172
*inst.Config.SSH.ForwardAgent,
172173
*inst.Config.SSH.ForwardX11,

cmd/limactl/show-ssh.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ func showSSHAction(cmd *cobra.Command, args []string) error {
9090
filepath.Join(inst.Dir, filenames.SSHConfig), inst.Hostname)
9191
opts, err := sshutil.SSHOpts(
9292
inst.Dir,
93+
*inst.Config.User.Username,
9394
*inst.Config.SSH.LoadDotSSHPubKeys,
9495
*inst.Config.SSH.ForwardAgent,
9596
*inst.Config.SSH.ForwardX11,

cmd/limactl/tunnel.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ func tunnelAction(cmd *cobra.Command, args []string) error {
107107

108108
sshOpts, err := sshutil.SSHOpts(
109109
inst.Dir,
110+
*inst.Config.User.Username,
110111
*inst.Config.SSH.LoadDotSSHPubKeys,
111112
*inst.Config.SSH.ForwardAgent,
112113
*inst.Config.SSH.ForwardX11,

pkg/cidata/cidata.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,7 @@ func templateArgs(bootScripts bool, instDir, name string, instConfig *limayaml.L
117117
if err := limayaml.Validate(instConfig, false); err != nil {
118118
return nil, err
119119
}
120-
u, err := osutil.LimaUser(true)
121-
if err != nil {
122-
return nil, err
123-
}
124-
uid, err := strconv.Atoi(u.Uid)
120+
uid, err := strconv.Atoi(*instConfig.User.UID)
125121
if err != nil {
126122
return nil, err
127123
}
@@ -130,9 +126,9 @@ func templateArgs(bootScripts bool, instDir, name string, instConfig *limayaml.L
130126
BootScripts: bootScripts,
131127
Name: name,
132128
Hostname: identifierutil.HostnameFromInstName(name), // TODO: support customization
133-
User: u.Username,
134129
UID: uid,
135-
Home: fmt.Sprintf("/home/%s.linux", u.Username),
130+
User: *instConfig.User.Username,
131+
Home: *instConfig.User.HomeDir,
136132
GuestInstallPrefix: *instConfig.GuestInstallPrefix,
137133
UpgradePackages: *instConfig.UpgradePackages,
138134
Containerd: Containerd{System: *instConfig.Containerd.System, User: *instConfig.Containerd.User},

pkg/cidata/template.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"github.com/lima-vm/lima/pkg/iso9660util"
1212

1313
"github.com/containerd/containerd/identifiers"
14-
"github.com/lima-vm/lima/pkg/osutil"
1514
"github.com/lima-vm/lima/pkg/textutil"
1615
)
1716

@@ -96,9 +95,8 @@ func ValidateTemplateArgs(args *TemplateArgs) error {
9695
if err := identifiers.Validate(args.Name); err != nil {
9796
return err
9897
}
99-
if !osutil.ValidateUsername(args.User) {
100-
return errors.New("field User must be valid linux username")
101-
}
98+
// args.User is intentionally not validated here; the user can override with any name they want
99+
// limayaml.FillDefault will validate the default (local) username, but not an explicit setting
102100
if args.User == "root" {
103101
return errors.New("field User must not be \"root\"")
104102
}

pkg/hostagent/hostagent.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ func New(instName string, stdout io.Writer, signalCh chan os.Signal, opts ...Opt
144144

145145
sshOpts, err := sshutil.SSHOpts(
146146
inst.Dir,
147+
*inst.Config.User.Username,
147148
*inst.Config.SSH.LoadDotSSHPubKeys,
148149
*inst.Config.SSH.ForwardAgent,
149150
*inst.Config.SSH.ForwardX11,
@@ -182,13 +183,13 @@ func New(instName string, stdout io.Writer, signalCh chan os.Signal, opts ...Opt
182183
// Block ports 22 and sshLocalPort on all IPs
183184
for _, port := range []int{sshGuestPort, sshLocalPort} {
184185
rule := limayaml.PortForward{GuestIP: net.IPv4zero, GuestPort: port, Ignore: true}
185-
limayaml.FillPortForwardDefaults(&rule, inst.Dir, inst.Param)
186+
limayaml.FillPortForwardDefaults(&rule, inst.Dir, inst.Config.User, inst.Param)
186187
rules = append(rules, rule)
187188
}
188189
rules = append(rules, inst.Config.PortForwards...)
189190
// Default forwards for all non-privileged ports from "127.0.0.1" and "::1"
190191
rule := limayaml.PortForward{}
191-
limayaml.FillPortForwardDefaults(&rule, inst.Dir, inst.Param)
192+
limayaml.FillPortForwardDefaults(&rule, inst.Dir, inst.Config.User, inst.Param)
192193
rules = append(rules, rule)
193194

194195
limaDriver := driverutil.CreateTargetDriverInstance(&driver.BaseDriver{

pkg/instance/create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func Create(ctx context.Context, instName string, instConfig []byte, saveBrokenY
4141
}
4242
// limayaml.Load() needs to pass the store file path to limayaml.FillDefault() to calculate default MAC addresses
4343
filePath := filepath.Join(instDir, filenames.LimaYAML)
44-
loadedInstConfig, err := limayaml.Load(instConfig, filePath)
44+
loadedInstConfig, err := limayaml.LoadWithWarnings(instConfig, filePath)
4545
if err != nil {
4646
return nil, err
4747
}

pkg/limayaml/defaults.go

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"net"
1010
"os"
11+
"os/user"
1112
"path/filepath"
1213
"runtime"
1314
"slices"
@@ -18,11 +19,13 @@ import (
1819
"github.com/coreos/go-semver/semver"
1920
"github.com/docker/go-units"
2021
"github.com/goccy/go-yaml"
22+
"github.com/lima-vm/lima/pkg/version"
2123
"github.com/pbnjay/memory"
2224
"github.com/sirupsen/logrus"
2325
"golang.org/x/sys/cpu"
2426

2527
"github.com/lima-vm/lima/pkg/identifierutil"
28+
. "github.com/lima-vm/lima/pkg/must"
2629
"github.com/lima-vm/lima/pkg/networks"
2730
"github.com/lima-vm/lima/pkg/osutil"
2831
"github.com/lima-vm/lima/pkg/ptr"
@@ -43,7 +46,12 @@ const (
4346
DefaultVirtiofsQueueSize int = 1024
4447
)
4548

46-
var IPv4loopback1 = net.IPv4(127, 0, 0, 1)
49+
var (
50+
IPv4loopback1 = net.IPv4(127, 0, 0, 1)
51+
52+
userHomeDir = Must(os.UserHomeDir())
53+
currentUser = Must(user.Current())
54+
)
4755

4856
func defaultCPUType() CPUType {
4957
cpuType := map[Arch]string{
@@ -171,17 +179,58 @@ func defaultGuestInstallPrefix() string {
171179
// - Networks are appended in d, y, o order
172180
// - DNS are picked from the highest priority where DNS is not empty.
173181
// - CACertificates Files and Certs are uniquely appended in d, y, o order
174-
func FillDefault(y, d, o *LimaYAML, filePath string) {
182+
func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) {
175183
instDir := filepath.Dir(filePath)
176184

177185
// existingLimaVersion can be empty if the instance was created with Lima prior to v0.20,
178186
// or, when editing a template file without an instance (`limactl edit foo.yaml`)
179187
var existingLimaVersion string
180-
limaVersionFile := filepath.Join(instDir, filenames.LimaVersion)
181-
if b, err := os.ReadFile(limaVersionFile); err == nil {
182-
existingLimaVersion = strings.TrimSpace(string(b))
183-
} else if !errors.Is(err, os.ErrNotExist) {
184-
logrus.WithError(err).Warnf("Failed to read %q", limaVersionFile)
188+
if warn {
189+
// If warnings are enabled, then a new instance is being created using the current Lima version
190+
existingLimaVersion = version.Version
191+
} else {
192+
limaVersionFile := filepath.Join(instDir, filenames.LimaVersion)
193+
if b, err := os.ReadFile(limaVersionFile); err == nil {
194+
existingLimaVersion = strings.TrimSpace(string(b))
195+
} else if !errors.Is(err, os.ErrNotExist) {
196+
logrus.WithError(err).Warnf("Failed to read %q", limaVersionFile)
197+
}
198+
}
199+
200+
if y.User.Username == nil {
201+
y.User.Username = d.User.Username
202+
}
203+
if y.User.HomeDir == nil {
204+
y.User.HomeDir = d.User.HomeDir
205+
}
206+
if y.User.UID == nil {
207+
y.User.UID = d.User.UID
208+
}
209+
if o.User.Username != nil {
210+
y.User.Username = o.User.Username
211+
}
212+
if o.User.HomeDir != nil {
213+
y.User.HomeDir = o.User.HomeDir
214+
}
215+
if o.User.UID != nil {
216+
y.User.UID = o.User.UID
217+
}
218+
if y.User.Username == nil {
219+
y.User.Username = ptr.Of(osutil.LimaUser(existingLimaVersion, warn).Username)
220+
warn = false
221+
}
222+
if y.User.HomeDir == nil {
223+
y.User.HomeDir = ptr.Of(osutil.LimaUser(existingLimaVersion, warn).HomeDir)
224+
warn = false
225+
}
226+
if y.User.UID == nil {
227+
y.User.UID = ptr.Of(osutil.LimaUser(existingLimaVersion, warn).Uid)
228+
// warn = false
229+
}
230+
if out, err := executeGuestTemplate(*y.User.HomeDir, instDir, y.User, y.Param); err == nil {
231+
y.User.HomeDir = ptr.Of(out.String())
232+
} else {
233+
logrus.WithError(err).Warnf("Couldn't process `user.homeDir` value %q as a template", *y.User.HomeDir)
185234
}
186235

187236
if y.VMType == nil {
@@ -406,7 +455,7 @@ func FillDefault(y, d, o *LimaYAML, filePath string) {
406455
if provision.Mode == ProvisionModeDependency && provision.SkipDefaultDependencyResolution == nil {
407456
provision.SkipDefaultDependencyResolution = ptr.Of(false)
408457
}
409-
if out, err := executeGuestTemplate(provision.Script, instDir, y.Param); err == nil {
458+
if out, err := executeGuestTemplate(provision.Script, instDir, y.User, y.Param); err == nil {
410459
provision.Script = out.String()
411460
} else {
412461
logrus.WithError(err).Warnf("Couldn't process provisioning script %q as a template", provision.Script)
@@ -477,7 +526,7 @@ func FillDefault(y, d, o *LimaYAML, filePath string) {
477526
if probe.Description == "" {
478527
probe.Description = fmt.Sprintf("user probe %d/%d", i+1, len(y.Probes))
479528
}
480-
if out, err := executeGuestTemplate(probe.Script, instDir, y.Param); err == nil {
529+
if out, err := executeGuestTemplate(probe.Script, instDir, y.User, y.Param); err == nil {
481530
probe.Script = out.String()
482531
} else {
483532
logrus.WithError(err).Warnf("Couldn't process probing script %q as a template", probe.Script)
@@ -486,13 +535,13 @@ func FillDefault(y, d, o *LimaYAML, filePath string) {
486535

487536
y.PortForwards = append(append(o.PortForwards, y.PortForwards...), d.PortForwards...)
488537
for i := range y.PortForwards {
489-
FillPortForwardDefaults(&y.PortForwards[i], instDir, y.Param)
538+
FillPortForwardDefaults(&y.PortForwards[i], instDir, y.User, y.Param)
490539
// After defaults processing the singular HostPort and GuestPort values should not be used again.
491540
}
492541

493542
y.CopyToHost = append(append(o.CopyToHost, y.CopyToHost...), d.CopyToHost...)
494543
for i := range y.CopyToHost {
495-
FillCopyToHostDefaults(&y.CopyToHost[i], instDir, y.Param)
544+
FillCopyToHostDefaults(&y.CopyToHost[i], instDir, y.User, y.Param)
496545
}
497546

498547
if y.HostResolver.Enabled == nil {
@@ -621,7 +670,7 @@ func FillDefault(y, d, o *LimaYAML, filePath string) {
621670
logrus.WithError(err).Warnf("Couldn't process mount location %q as a template", mount.Location)
622671
}
623672
if mount.MountPoint != nil {
624-
if out, err := executeGuestTemplate(*mount.MountPoint, instDir, y.Param); err == nil {
673+
if out, err := executeGuestTemplate(*mount.MountPoint, instDir, y.User, y.Param); err == nil {
625674
mount.MountPoint = ptr.Of(out.String())
626675
} else {
627676
logrus.WithError(err).Warnf("Couldn't process mount point %q as a template", *mount.MountPoint)
@@ -811,17 +860,16 @@ func fixUpForPlainMode(y *LimaYAML) {
811860
y.TimeZone = ptr.Of("")
812861
}
813862

814-
func executeGuestTemplate(format, instDir string, param map[string]string) (bytes.Buffer, error) {
863+
func executeGuestTemplate(format, instDir string, user User, param map[string]string) (bytes.Buffer, error) {
815864
tmpl, err := template.New("").Parse(format)
816865
if err == nil {
817-
user, _ := osutil.LimaUser(false)
818866
name := filepath.Base(instDir)
819867
data := map[string]interface{}{
820-
"Home": fmt.Sprintf("/home/%s.linux", user.Username),
821868
"Name": name,
822869
"Hostname": identifierutil.HostnameFromInstName(name), // TODO: support customization
823-
"UID": user.Uid,
824-
"User": user.Username,
870+
"UID": *user.UID,
871+
"User": *user.Username,
872+
"Home": *user.HomeDir,
825873
"Param": param,
826874
}
827875
var out bytes.Buffer
@@ -835,16 +883,14 @@ func executeGuestTemplate(format, instDir string, param map[string]string) (byte
835883
func executeHostTemplate(format, instDir string, param map[string]string) (bytes.Buffer, error) {
836884
tmpl, err := template.New("").Parse(format)
837885
if err == nil {
838-
user, _ := osutil.LimaUser(false)
839-
home, _ := os.UserHomeDir()
840886
limaHome, _ := dirnames.LimaDir()
841887
data := map[string]interface{}{
842888
"Dir": instDir,
843-
"Home": home,
844889
"Name": filepath.Base(instDir),
845890
// TODO: add hostname fields for the host and the guest
846-
"UID": user.Uid,
847-
"User": user.Username,
891+
"UID": currentUser.Uid,
892+
"User": currentUser.Username,
893+
"Home": userHomeDir,
848894
"Param": param,
849895

850896
"Instance": filepath.Base(instDir), // DEPRECATED, use `{{.Name}}`
@@ -858,7 +904,7 @@ func executeHostTemplate(format, instDir string, param map[string]string) (bytes
858904
return bytes.Buffer{}, err
859905
}
860906

861-
func FillPortForwardDefaults(rule *PortForward, instDir string, param map[string]string) {
907+
func FillPortForwardDefaults(rule *PortForward, instDir string, user User, param map[string]string) {
862908
if rule.Proto == "" {
863909
rule.Proto = ProtoTCP
864910
}
@@ -890,7 +936,7 @@ func FillPortForwardDefaults(rule *PortForward, instDir string, param map[string
890936
}
891937
}
892938
if rule.GuestSocket != "" {
893-
if out, err := executeGuestTemplate(rule.GuestSocket, instDir, param); err == nil {
939+
if out, err := executeGuestTemplate(rule.GuestSocket, instDir, user, param); err == nil {
894940
rule.GuestSocket = out.String()
895941
} else {
896942
logrus.WithError(err).Warnf("Couldn't process guestSocket %q as a template", rule.GuestSocket)
@@ -908,9 +954,9 @@ func FillPortForwardDefaults(rule *PortForward, instDir string, param map[string
908954
}
909955
}
910956

911-
func FillCopyToHostDefaults(rule *CopyToHost, instDir string, param map[string]string) {
957+
func FillCopyToHostDefaults(rule *CopyToHost, instDir string, user User, param map[string]string) {
912958
if rule.GuestFile != "" {
913-
if out, err := executeGuestTemplate(rule.GuestFile, instDir, param); err == nil {
959+
if out, err := executeGuestTemplate(rule.GuestFile, instDir, user, param); err == nil {
914960
rule.GuestFile = out.String()
915961
} else {
916962
logrus.WithError(err).Warnf("Couldn't process guest %q as a template", rule.GuestFile)

0 commit comments

Comments
 (0)