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