From 80387bc29481bfb981ec2060543ee5d74716203c Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 17 Oct 2020 11:35:20 -0700 Subject: [PATCH] Use more easily configurable timezone, remove /etc/timezone mounts from containers; closes pterodactyl/panel#2513 If this does not completely solve the issue in containers then we need to evaluate the image being used to determine what changes need to happen to the image itself to support the timezone. ref pterodactyl/panel#2239 ref pterodactyl/panel#2329 ref pterodactyl/panel#2389 --- cmd/root.go | 7 ++++++ config/config_docker.go | 4 --- config/config_system.go | 55 +++++++++++++++++++++++++++++++++++++++++ server/mounts.go | 46 ++++++++-------------------------- server/server.go | 7 +++--- 5 files changed, 75 insertions(+), 44 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 702cd4e..03a0a7e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -132,6 +132,13 @@ func rootCmdRun(*cobra.Command, []string) { config.Set(c) config.SetDebugViaFlag(debug) + if err := c.System.ConfigureTimezone(); err != nil { + log.WithField("error", err).Fatal("failed to detect system timezone or use supplied configuration value") + return + } + + log.WithField("timezone", c.System.Timezone).Info("configured wings with system timezone") + if err := c.System.ConfigureDirectories(); err != nil { log.WithField("error", err).Fatal("failed to configure system directories for pterodactyl") return diff --git a/config/config_docker.go b/config/config_docker.go index fa54e32..9db3190 100644 --- a/config/config_docker.go +++ b/config/config_docker.go @@ -57,10 +57,6 @@ type DockerConfiguration struct { // The location of the Docker socket. Socket string `default:"/var/run/docker.sock" json:"socket" yaml:"socket"` - // Defines the location of the timezone file on the host system that should - // be mounted into the created containers so that they all use the same time. - TimezonePath string `default:"/etc/timezone" json:"timezone_path" yaml:"timezone_path"` - // Registries . Registries map[string]RegistryConfiguration `json:"registries" yaml:"registries"` diff --git a/config/config_system.go b/config/config_system.go index ff19863..2877197 100644 --- a/config/config_system.go +++ b/config/config_system.go @@ -1,12 +1,18 @@ package config import ( + "context" + "fmt" "github.com/apex/log" "github.com/pkg/errors" "html/template" + "io/ioutil" "os" + "os/exec" "path" "path/filepath" + "regexp" + "time" ) // Defines basic system configuration settings. @@ -29,6 +35,13 @@ type SystemConfiguration struct { // The user that should own all of the server files, and be used for containers. Username string `default:"pterodactyl" yaml:"username"` + // The timezone for this Wings instance. This is detected by Wings automatically if possible, + // and falls back to UTC if not able to be detected. If you need to set this manually, that + // can also be done. + // + // This timezone value is passed into all containers created by Wings. + Timezone string `yaml:"timezone"` + // Definitions for the user that gets created to ensure that we can quickly access // this information without constantly having to do a system lookup. User struct { @@ -166,3 +179,45 @@ func (sc *SystemConfiguration) GetStatesPath() string { func (sc *SystemConfiguration) GetInstallLogPath() string { return path.Join(sc.LogDirectory, "install/") } + +// Configures the timezone data for the configuration if it is currently missing. If +// a value has been set, this functionality will only run to validate that the timezone +// being used is valid. +func (sc *SystemConfiguration) ConfigureTimezone() error { + if sc.Timezone == "" { + if b, err := ioutil.ReadFile("/etc/timezone"); err != nil { + if !os.IsNotExist(err) { + return errors.Wrap(err, "failed to open /etc/timezone for automatic server timezone calibration") + } + + ctx, _ := context.WithTimeout(context.Background(), time.Second * 5) + // Okay, file isn't found on this OS, we will try using timedatectl to handle this. If this + // command fails, exit, but if it returns a value use that. If no value is returned we will + // fall through to UTC to get Wings booted at least. + out, err := exec.CommandContext(ctx, "timedatectl").Output() + if err != nil { + log.WithField("error", err).Warn("failed to execute \"timedatectl\" to determine system timezone, falling back to UTC") + + sc.Timezone = "UTC" + return nil + } + + r := regexp.MustCompile(`Time zone: ([\w/]+)`) + matches := r.FindSubmatch(out) + if len(matches) != 2 || string(matches[1]) == "" { + log.Warn("failed to parse timezone from \"timedatectl\" output, falling back to UTC") + + sc.Timezone = "UTC" + return nil + } + + sc.Timezone = regexp.MustCompile(`\s+$`).ReplaceAllString(string(matches[1]), "") + } else { + sc.Timezone = string(b) + } + } + + _, err := time.LoadLocation(sc.Timezone) + + return errors.Wrap(err, fmt.Sprintf("the supplied timezone %s is invalid", sc.Timezone)) +} \ No newline at end of file diff --git a/server/mounts.go b/server/mounts.go index bb1b699..c77ecc9 100644 --- a/server/mounts.go +++ b/server/mounts.go @@ -2,10 +2,8 @@ package server import ( "github.com/apex/log" - "github.com/pkg/errors" "github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/environment" - "os" "path/filepath" "strings" ) @@ -16,41 +14,17 @@ import ( type Mount environment.Mount // Returns the default container mounts for the server instance. This includes the data directory -// for the server as well as any timezone related files if they exist on the host system so that -// servers running within the container will use the correct time. +// for the server. Previously this would also mount in host timezone files, however we've moved from +// that approach to just setting `TZ=Timezone` environment values in containers which should work +// in most scenarios. func (s *Server) Mounts() []environment.Mount { - var m []environment.Mount - - m = append(m, environment.Mount{ - Default: true, - Target: "/home/container", - Source: s.Filesystem().Path(), - ReadOnly: false, - }) - - // Try to mount in /etc/localtime and /etc/timezone if they exist on the host system. - if _, err := os.Stat("/etc/localtime"); err != nil { - if !os.IsNotExist(err) { - log.WithField("error", errors.WithStack(err)).Warn("failed to stat /etc/localtime due to an error") - } - } else { - m = append(m, environment.Mount{ - Target: "/etc/localtime", - Source: "/etc/localtime", - ReadOnly: true, - }) - } - - if _, err := os.Stat("/etc/timezone"); err != nil { - if !os.IsNotExist(err) { - log.WithField("error", errors.WithStack(err)).Warn("failed to stat /etc/timezone due to an error") - } - } else { - m = append(m, environment.Mount{ - Target: "/etc/timezone", - Source: "/etc/timezone", - ReadOnly: true, - }) + m := []environment.Mount{ + { + Default: true, + Target: "/home/container", + Source: s.Filesystem().Path(), + ReadOnly: false, + }, } // Also include any of this server's custom mounts when returning them. diff --git a/server/server.go b/server/server.go index 3c968c3..fc14d55 100644 --- a/server/server.go +++ b/server/server.go @@ -6,6 +6,7 @@ import ( "github.com/apex/log" "github.com/pkg/errors" "github.com/pterodactyl/wings/api" + "github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment/docker" "github.com/pterodactyl/wings/events" @@ -13,7 +14,6 @@ import ( "golang.org/x/sync/semaphore" "strings" "sync" - "time" ) // High level definition for a server instance being controlled by Wings. @@ -78,10 +78,8 @@ func (s *Server) Id() string { // Returns all of the environment variables that should be assigned to a running // server instance. func (s *Server) GetEnvironmentVariables() []string { - zone, _ := time.Now().In(time.Local).Zone() - var out = []string{ - fmt.Sprintf("TZ=%s", zone), + fmt.Sprintf("TZ=%s", config.Get().System.Timezone), fmt.Sprintf("STARTUP=%s", s.Config().Invocation), fmt.Sprintf("SERVER_MEMORY=%d", s.MemoryLimit()), fmt.Sprintf("SERVER_IP=%s", s.Config().Allocations.DefaultMapping.Ip), @@ -90,6 +88,7 @@ func (s *Server) GetEnvironmentVariables() []string { eloop: for k := range s.Config().EnvVars { + // Don't allow any environment variables that we have already set above. for _, e := range out { if strings.HasPrefix(e, strings.ToUpper(k)) { continue eloop