diff --git a/examples/default.yaml b/examples/default.yaml index 8df843bb125..7484366b41e 100644 --- a/examples/default.yaml +++ b/examples/default.yaml @@ -259,6 +259,11 @@ rosetta: # 🟢 Builtin default: false binfmt: null +# Specify the timezone name (as used by the zoneinfo database). Specify the empty string +# to not set a timezone in the instance. +# 🟢 Builtin default: use name from /etc/timezone or deduce from symlink target of /etc/localtime +timezone: null + firmware: # Use legacy BIOS instead of UEFI. Ignored for aarch64. # 🟢 Builtin default: false diff --git a/pkg/cidata/cidata.TEMPLATE.d/user-data b/pkg/cidata/cidata.TEMPLATE.d/user-data index cb1acba8fa4..59e6b74e736 100644 --- a/pkg/cidata/cidata.TEMPLATE.d/user-data +++ b/pkg/cidata/cidata.TEMPLATE.d/user-data @@ -14,6 +14,10 @@ mounts: {{- end }} {{- end }} +{{- if .TimeZone }} +timezone: {{.TimeZone}} +{{- end }} + users: - name: "{{.User}}" uid: "{{.UID}}" diff --git a/pkg/cidata/cidata.go b/pkg/cidata/cidata.go index 22a54091168..a6ad80b8d27 100644 --- a/pkg/cidata/cidata.go +++ b/pkg/cidata/cidata.go @@ -136,6 +136,7 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort VMType: *y.VMType, VSockPort: vsockPort, Plain: *y.Plain, + TimeZone: *y.TimeZone, } firstUsernetIndex := limayaml.FirstUsernetIndex(y) diff --git a/pkg/cidata/template.go b/pkg/cidata/template.go index 089a085f76b..c856fb672c0 100644 --- a/pkg/cidata/template.go +++ b/pkg/cidata/template.go @@ -82,6 +82,7 @@ type TemplateArgs struct { VMType string VSockPort int Plain bool + TimeZone string } func ValidateTemplateArgs(args TemplateArgs) error { diff --git a/pkg/limayaml/defaults.go b/pkg/limayaml/defaults.go index 8e076a38609..46da7c8d736 100644 --- a/pkg/limayaml/defaults.go +++ b/pkg/limayaml/defaults.go @@ -9,20 +9,20 @@ import ( "path/filepath" "runtime" "strconv" + "strings" "text/template" "github.com/docker/go-units" - "github.com/lima-vm/lima/pkg/networks" - "github.com/lima-vm/lima/pkg/ptr" "github.com/pbnjay/memory" + "github.com/sirupsen/logrus" + "golang.org/x/sys/cpu" "github.com/lima-vm/lima/pkg/guestagent/api" + "github.com/lima-vm/lima/pkg/networks" "github.com/lima-vm/lima/pkg/osutil" + "github.com/lima-vm/lima/pkg/ptr" "github.com/lima-vm/lima/pkg/store/dirnames" "github.com/lima-vm/lima/pkg/store/filenames" - "github.com/sirupsen/logrus" - - "golang.org/x/sys/cpu" ) const ( @@ -83,6 +83,26 @@ func MACAddress(uniqueID string) string { return hw.String() } +func hostTimeZone() string { + // WSL2 will automatically set the timezone + if runtime.GOOS != "windows" { + tz, err := os.ReadFile("/etc/timezone") + if err == nil { + return strings.TrimSpace(string(tz)) + } + zoneinfoFile, err := filepath.EvalSymlinks("/etc/localtime") + if err == nil { + for baseDir := filepath.Dir(zoneinfoFile); baseDir != "/"; baseDir = filepath.Dir(baseDir) { + if _, err = os.Stat(filepath.Join(baseDir, "Etc/UTC")); err == nil { + return strings.TrimPrefix(zoneinfoFile, baseDir+"/") + } + } + logrus.Warnf("could not locate zoneinfo directory from %q", zoneinfoFile) + } + } + return "" +} + func defaultCPUs() int { const x = 4 if hostCPUs := runtime.NumCPU(); hostCPUs < x { @@ -325,6 +345,16 @@ func FillDefault(y, d, o *LimaYAML, filePath string) { } } + if y.TimeZone == nil { + y.TimeZone = d.TimeZone + } + if o.TimeZone != nil { + y.TimeZone = o.TimeZone + } + if y.TimeZone == nil { + y.TimeZone = ptr.Of(hostTimeZone()) + } + if y.SSH.LocalPort == nil { y.SSH.LocalPort = d.SSH.LocalPort } @@ -731,6 +761,7 @@ func fixUpForPlainMode(y *LimaYAML) { y.Containerd.User = ptr.Of(false) y.Rosetta.BinFmt = ptr.Of(false) y.Rosetta.Enabled = ptr.Of(false) + y.TimeZone = ptr.Of("") } func executeGuestTemplate(format string) (bytes.Buffer, error) { diff --git a/pkg/limayaml/defaults_test.go b/pkg/limayaml/defaults_test.go index 9c5a26db13c..bd415699b7c 100644 --- a/pkg/limayaml/defaults_test.go +++ b/pkg/limayaml/defaults_test.go @@ -85,6 +85,7 @@ func TestFillDefault(t *testing.T) { ForwardX11: ptr.Of(false), ForwardX11Trusted: ptr.Of(false), }, + TimeZone: ptr.Of(hostTimeZone()), Firmware: Firmware{ LegacyBIOS: ptr.Of(false), }, @@ -181,6 +182,7 @@ func TestFillDefault(t *testing.T) { "-----BEGIN CERTIFICATE-----\nYOUR-ORGS-TRUSTED-CA-CERT\n-----END CERTIFICATE-----\n", }, }, + TimeZone: ptr.Of("Antarctica"), Firmware: Firmware{ LegacyBIOS: ptr.Of(false), Images: []FileWithVMType{ @@ -273,6 +275,7 @@ func TestFillDefault(t *testing.T) { }, } + expect.TimeZone = y.TimeZone expect.Firmware = y.Firmware expect.Rosetta = Rosetta{ @@ -320,6 +323,7 @@ func TestFillDefault(t *testing.T) { ForwardX11: ptr.Of(false), ForwardX11Trusted: ptr.Of(false), }, + TimeZone: ptr.Of("Zulu"), Firmware: Firmware{ LegacyBIOS: ptr.Of(true), Images: []FileWithVMType{ @@ -512,6 +516,7 @@ func TestFillDefault(t *testing.T) { ForwardX11: ptr.Of(false), ForwardX11Trusted: ptr.Of(false), }, + TimeZone: ptr.Of("Universal"), Firmware: Firmware{ LegacyBIOS: ptr.Of(true), }, diff --git a/pkg/limayaml/limayaml.go b/pkg/limayaml/limayaml.go index ad927834fc1..eb1e3ea22f3 100644 --- a/pkg/limayaml/limayaml.go +++ b/pkg/limayaml/limayaml.go @@ -39,6 +39,7 @@ type LimaYAML struct { CACertificates CACertificates `yaml:"caCerts,omitempty" json:"caCerts,omitempty"` Rosetta Rosetta `yaml:"rosetta,omitempty" json:"rosetta,omitempty"` Plain *bool `yaml:"plain,omitempty" json:"plain,omitempty"` + TimeZone *string `yaml:"timezone,omitempty" json:"timezone,omitempty"` } type (