yoink viper back out of code, simplify some config logic
This commit is contained in:
		
							parent
							
								
									9480ccdbba
								
							
						
					
					
						commit
						80faea3286
					
				| 
						 | 
					@ -147,7 +147,7 @@ func configureCmdRun(cmd *cobra.Command, args []string) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	b, err := ioutil.ReadAll(res.Body)
 | 
						b, err := ioutil.ReadAll(res.Body)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cfg, err := config.NewFromPath(configPath)
 | 
						cfg, err := config.NewAtPath(configPath)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		panic(err)
 | 
							panic(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -156,7 +156,7 @@ func configureCmdRun(cmd *cobra.Command, args []string) {
 | 
				
			||||||
		panic(err)
 | 
							panic(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err = cfg.WriteToDisk(); err != nil {
 | 
						if err = config.WriteToDisk(cfg); err != nil {
 | 
				
			||||||
		panic(err)
 | 
							panic(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -102,7 +102,7 @@ func diagnosticsCmdRun(cmd *cobra.Command, args []string) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	printHeader(output, "Wings Configuration")
 | 
						printHeader(output, "Wings Configuration")
 | 
				
			||||||
	cfg, err := config.ReadConfiguration(config.DefaultLocation)
 | 
						cfg, err := config.FromFile(config.DefaultLocation)
 | 
				
			||||||
	if cfg != nil {
 | 
						if cfg != nil {
 | 
				
			||||||
		fmt.Fprintln(output, "    Panel Location:", redact(cfg.PanelLocation))
 | 
							fmt.Fprintln(output, "    Panel Location:", redact(cfg.PanelLocation))
 | 
				
			||||||
		fmt.Fprintln(output, "")
 | 
							fmt.Fprintln(output, "")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										165
									
								
								cmd/root.go
									
									
									
									
									
								
							
							
						
						
									
										165
									
								
								cmd/root.go
									
									
									
									
									
								
							| 
						 | 
					@ -8,6 +8,7 @@ import (
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"emperror.dev/errors"
 | 
						"emperror.dev/errors"
 | 
				
			||||||
| 
						 | 
					@ -26,13 +27,12 @@ import (
 | 
				
			||||||
	"github.com/pterodactyl/wings/sftp"
 | 
						"github.com/pterodactyl/wings/sftp"
 | 
				
			||||||
	"github.com/pterodactyl/wings/system"
 | 
						"github.com/pterodactyl/wings/system"
 | 
				
			||||||
	"github.com/spf13/cobra"
 | 
						"github.com/spf13/cobra"
 | 
				
			||||||
	"github.com/spf13/viper"
 | 
					 | 
				
			||||||
	"golang.org/x/crypto/acme"
 | 
						"golang.org/x/crypto/acme"
 | 
				
			||||||
	"golang.org/x/crypto/acme/autocert"
 | 
						"golang.org/x/crypto/acme/autocert"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	configPath = ""
 | 
						configPath = config.DefaultLocation
 | 
				
			||||||
	debug      = false
 | 
						debug      = false
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,6 +40,8 @@ var rootCommand = &cobra.Command{
 | 
				
			||||||
	Use:   "wings",
 | 
						Use:   "wings",
 | 
				
			||||||
	Short: "Runs the API server allowing programatic control of game servers for Pterodactyl Panel.",
 | 
						Short: "Runs the API server allowing programatic control of game servers for Pterodactyl Panel.",
 | 
				
			||||||
	PreRun: func(cmd *cobra.Command, args []string) {
 | 
						PreRun: func(cmd *cobra.Command, args []string) {
 | 
				
			||||||
 | 
							initConfig()
 | 
				
			||||||
 | 
							initLogging()
 | 
				
			||||||
		if tls, _ := cmd.Flags().GetBool("auto-tls"); tls {
 | 
							if tls, _ := cmd.Flags().GetBool("auto-tls"); tls {
 | 
				
			||||||
			if host, _ := cmd.Flags().GetString("tls-hostname"); host == "" {
 | 
								if host, _ := cmd.Flags().GetString("tls-hostname"); host == "" {
 | 
				
			||||||
				fmt.Println("A TLS hostname must be provided when running wings with automatic TLS, e.g.:\n\n    ./wings --auto-tls --tls-hostname my.example.com")
 | 
									fmt.Println("A TLS hostname must be provided when running wings with automatic TLS, e.g.:\n\n    ./wings --auto-tls --tls-hostname my.example.com")
 | 
				
			||||||
| 
						 | 
					@ -65,9 +67,7 @@ func Execute() {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
	cobra.OnInitialize(initConfig, initLogging)
 | 
						rootCommand.PersistentFlags().StringVar(&configPath, "config", config.DefaultLocation, "set the location for the configuration file")
 | 
				
			||||||
 | 
					 | 
				
			||||||
	rootCommand.PersistentFlags().StringVar(&configPath, "config", "", "set the location for the configuration file")
 | 
					 | 
				
			||||||
	rootCommand.PersistentFlags().BoolVar(&debug, "debug", false, "pass in order to run wings in debug mode")
 | 
						rootCommand.PersistentFlags().BoolVar(&debug, "debug", false, "pass in order to run wings in debug mode")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Flags specifically used when running the API.
 | 
						// Flags specifically used when running the API.
 | 
				
			||||||
| 
						 | 
					@ -81,27 +81,6 @@ func init() {
 | 
				
			||||||
	rootCommand.AddCommand(diagnosticsCmd)
 | 
						rootCommand.AddCommand(diagnosticsCmd)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Get the configuration path based on the arguments provided.
 | 
					 | 
				
			||||||
func readConfiguration() (*config.Configuration, error) {
 | 
					 | 
				
			||||||
	p := configPath
 | 
					 | 
				
			||||||
	if !strings.HasPrefix(p, "/") {
 | 
					 | 
				
			||||||
		d, err := os.Getwd()
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		p = path.Clean(path.Join(d, configPath))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if s, err := os.Stat(p); err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	} else if s.IsDir() {
 | 
					 | 
				
			||||||
		return nil, errors.New("cannot use directory as configuration file path")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return config.ReadConfiguration(p)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func rootCmdRun(cmd *cobra.Command, _ []string) {
 | 
					func rootCmdRun(cmd *cobra.Command, _ []string) {
 | 
				
			||||||
	switch cmd.Flag("profiler").Value.String() {
 | 
						switch cmd.Flag("profiler").Value.String() {
 | 
				
			||||||
	case "cpu":
 | 
						case "cpu":
 | 
				
			||||||
| 
						 | 
					@ -122,18 +101,9 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
 | 
				
			||||||
		defer profile.Start(profile.BlockProfile).Stop()
 | 
							defer profile.Start(profile.BlockProfile).Stop()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c, err := readConfiguration()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		panic(err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if debug {
 | 
					 | 
				
			||||||
		c.Debug = true
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	printLogo()
 | 
						printLogo()
 | 
				
			||||||
	log.WithField("path", viper.ConfigFileUsed()).Info("loading configuration from file")
 | 
					 | 
				
			||||||
	log.Debug("running in debug mode")
 | 
						log.Debug("running in debug mode")
 | 
				
			||||||
 | 
						log.WithField("config_file", configPath).Info("loading configuration from file")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if ok, _ := cmd.Flags().GetBool("ignore-certificate-errors"); ok {
 | 
						if ok, _ := cmd.Flags().GetBool("ignore-certificate-errors"); ok {
 | 
				
			||||||
		log.Warn("running with --ignore-certificate-errors: TLS certificate host chains and name will not be verified")
 | 
							log.Warn("running with --ignore-certificate-errors: TLS certificate host chains and name will not be verified")
 | 
				
			||||||
| 
						 | 
					@ -142,45 +112,39 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	config.Set(c)
 | 
					 | 
				
			||||||
	config.SetDebugViaFlag(debug)
 | 
					 | 
				
			||||||
	if err := config.ConfigureTimezone(); err != nil {
 | 
						if err := config.ConfigureTimezone(); err != nil {
 | 
				
			||||||
		log.WithField("error", err).Fatal("failed to detect system timezone or use supplied configuration value")
 | 
							log.WithField("error", err).Fatal("failed to detect system timezone or use supplied configuration value")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	log.WithField("timezone", c.System.Timezone).Info("configured wings with system timezone")
 | 
						log.WithField("timezone", config.Get().System.Timezone).Info("configured wings with system timezone")
 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err := config.ConfigureDirectories(); err != nil {
 | 
						if err := config.ConfigureDirectories(); err != nil {
 | 
				
			||||||
		log.WithField("error", err).Fatal("failed to configure system directories for pterodactyl")
 | 
							log.WithField("error", err).Fatal("failed to configure system directories for pterodactyl")
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err := config.EnableLogRotation(); err != nil {
 | 
						if err := config.EnableLogRotation(); err != nil {
 | 
				
			||||||
		log.WithField("error", err).Fatal("failed to configure log rotation on the system")
 | 
							log.WithField("error", err).Fatal("failed to configure log rotation on the system")
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.WithField("username", c.System.Username).Info("checking for pterodactyl system user")
 | 
						log.WithField("username", config.Get().System.User).Info("checking for pterodactyl system user")
 | 
				
			||||||
	if err := config.EnsurePterodactylUser(); err != nil {
 | 
						if err := config.EnsurePterodactylUser(); err != nil {
 | 
				
			||||||
		log.WithField("error", err).Fatal("failed to create pterodactyl system user")
 | 
							log.WithField("error", err).Fatal("failed to create pterodactyl system user")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	log.WithFields(log.Fields{
 | 
						log.WithFields(log.Fields{
 | 
				
			||||||
		"username": viper.GetString("system.username"),
 | 
							"username": config.Get().System.Username,
 | 
				
			||||||
		"uid":      viper.GetInt("system.user.uid"),
 | 
							"uid":      config.Get().System.User.Uid,
 | 
				
			||||||
		"gid":      viper.GetInt("system.user.gid"),
 | 
							"gid":      config.Get().System.User.Gid,
 | 
				
			||||||
	}).Info("configured system user successfully")
 | 
						}).Info("configured system user successfully")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := server.LoadDirectory(); err != nil {
 | 
						if err := server.LoadDirectory(); err != nil {
 | 
				
			||||||
		log.WithField("error", err).Fatal("failed to load server configurations")
 | 
							log.WithField("error", err).Fatal("failed to load server configurations")
 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := environment.ConfigureDocker(cmd.Context()); err != nil {
 | 
						if err := environment.ConfigureDocker(cmd.Context()); err != nil {
 | 
				
			||||||
		log.WithField("error", err).Fatal("failed to configure docker environment")
 | 
							log.WithField("error", err).Fatal("failed to configure docker environment")
 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := viper.WriteConfig(); err != nil {
 | 
						if err := config.WriteToDisk(config.Get()); err != nil {
 | 
				
			||||||
		log.WithField("error", err).Error("failed to save configuration to disk")
 | 
							log.WithField("error", err).Fatal("failed to write configuration to disk")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Just for some nice log output.
 | 
						// Just for some nice log output.
 | 
				
			||||||
| 
						 | 
					@ -197,7 +161,6 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
 | 
				
			||||||
	// on Wings. This allows us to ensure the environment exists, write configurations,
 | 
						// on Wings. This allows us to ensure the environment exists, write configurations,
 | 
				
			||||||
	// and reboot processes without causing a slow-down due to sequential booting.
 | 
						// and reboot processes without causing a slow-down due to sequential booting.
 | 
				
			||||||
	pool := workerpool.New(4)
 | 
						pool := workerpool.New(4)
 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, serv := range server.GetServers().All() {
 | 
						for _, serv := range server.GetServers().All() {
 | 
				
			||||||
		s := serv
 | 
							s := serv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -252,6 +215,13 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Wait until all of the servers are ready to go before we fire up the SFTP and HTTP servers.
 | 
						// Wait until all of the servers are ready to go before we fire up the SFTP and HTTP servers.
 | 
				
			||||||
	pool.StopWait()
 | 
						pool.StopWait()
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							// Cancel the context on all of the running servers at this point, even though the
 | 
				
			||||||
 | 
							// program is just shutting down.
 | 
				
			||||||
 | 
							for _, s := range server.GetServers().All() {
 | 
				
			||||||
 | 
								s.CtxCancel()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	go func() {
 | 
						go func() {
 | 
				
			||||||
		// Run the SFTP server.
 | 
							// Run the SFTP server.
 | 
				
			||||||
| 
						 | 
					@ -261,13 +231,14 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sys := config.Get().System
 | 
				
			||||||
	// Ensure the archive directory exists.
 | 
						// Ensure the archive directory exists.
 | 
				
			||||||
	if err := os.MkdirAll(c.System.ArchiveDirectory, 0755); err != nil {
 | 
						if err := os.MkdirAll(sys.ArchiveDirectory, 0755); err != nil {
 | 
				
			||||||
		log.WithField("error", err).Error("failed to create archive directory")
 | 
							log.WithField("error", err).Error("failed to create archive directory")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Ensure the backup directory exists.
 | 
						// Ensure the backup directory exists.
 | 
				
			||||||
	if err := os.MkdirAll(c.System.BackupDirectory, 0755); err != nil {
 | 
						if err := os.MkdirAll(sys.BackupDirectory, 0755); err != nil {
 | 
				
			||||||
		log.WithField("error", err).Error("failed to create backup directory")
 | 
							log.WithField("error", err).Error("failed to create backup directory")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -277,47 +248,31 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
 | 
				
			||||||
		autotls = false
 | 
							autotls = false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						api := config.Get().Api
 | 
				
			||||||
	log.WithFields(log.Fields{
 | 
						log.WithFields(log.Fields{
 | 
				
			||||||
		"use_ssl":      c.Api.Ssl.Enabled,
 | 
							"use_ssl":      api.Ssl.Enabled,
 | 
				
			||||||
		"use_auto_tls": autotls,
 | 
							"use_auto_tls": autotls,
 | 
				
			||||||
		"host_address": c.Api.Host,
 | 
							"host_address": api.Host,
 | 
				
			||||||
		"host_port":    c.Api.Port,
 | 
							"host_port":    api.Port,
 | 
				
			||||||
	}).Info("configuring internal webserver")
 | 
						}).Info("configuring internal webserver")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Configure the router.
 | 
						// Create a new HTTP server instance to handle inbound requests from the Panel
 | 
				
			||||||
	r := router.Configure()
 | 
						// and external clients.
 | 
				
			||||||
 | 
					 | 
				
			||||||
	s := &http.Server{
 | 
						s := &http.Server{
 | 
				
			||||||
		Addr:    fmt.Sprintf("%s:%d", c.Api.Host, c.Api.Port),
 | 
							Addr:      api.Host + ":" + strconv.Itoa(api.Port),
 | 
				
			||||||
		Handler: r,
 | 
							Handler:   router.Configure(),
 | 
				
			||||||
		TLSConfig: &tls.Config{
 | 
							TLSConfig: config.DefaultTLSConfig,
 | 
				
			||||||
			NextProtos: []string{"h2", "http/1.1"},
 | 
					 | 
				
			||||||
			// @see https://blog.cloudflare.com/exposing-go-on-the-internet
 | 
					 | 
				
			||||||
			CipherSuites: []uint16{
 | 
					 | 
				
			||||||
				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
 | 
					 | 
				
			||||||
				tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
 | 
					 | 
				
			||||||
				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
 | 
					 | 
				
			||||||
				tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
 | 
					 | 
				
			||||||
				tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
 | 
					 | 
				
			||||||
				tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			PreferServerCipherSuites: true,
 | 
					 | 
				
			||||||
			MinVersion:               tls.VersionTLS12,
 | 
					 | 
				
			||||||
			MaxVersion:               tls.VersionTLS13,
 | 
					 | 
				
			||||||
			CurvePreferences:         []tls.CurveID{tls.X25519, tls.CurveP256},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Check if the server should run with TLS but using autocert.
 | 
						// Check if the server should run with TLS but using autocert.
 | 
				
			||||||
	if autotls {
 | 
						if autotls {
 | 
				
			||||||
		m := autocert.Manager{
 | 
							m := autocert.Manager{
 | 
				
			||||||
			Prompt:     autocert.AcceptTOS,
 | 
								Prompt:     autocert.AcceptTOS,
 | 
				
			||||||
			Cache:      autocert.DirCache(path.Join(c.System.RootDirectory, "/.tls-cache")),
 | 
								Cache:      autocert.DirCache(path.Join(sys.RootDirectory, "/.tls-cache")),
 | 
				
			||||||
			HostPolicy: autocert.HostWhitelist(tlshostname),
 | 
								HostPolicy: autocert.HostWhitelist(tlshostname),
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		log.WithField("hostname", tlshostname).
 | 
							log.WithField("hostname", tlshostname).Info("webserver is now listening with auto-TLS enabled; certificates will be automatically generated by Let's Encrypt")
 | 
				
			||||||
			Info("webserver is now listening with auto-TLS enabled; certificates will be automatically generated by Let's Encrypt")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Hook autocert into the main http server.
 | 
							// Hook autocert into the main http server.
 | 
				
			||||||
		s.TLSConfig.GetCertificate = m.GetCertificate
 | 
							s.TLSConfig.GetCertificate = m.GetCertificate
 | 
				
			||||||
| 
						 | 
					@ -329,59 +284,53 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
 | 
				
			||||||
				log.WithError(err).Error("failed to serve autocert http server")
 | 
									log.WithError(err).Error("failed to serve autocert http server")
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}()
 | 
							}()
 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Start the main http server with TLS using autocert.
 | 
							// Start the main http server with TLS using autocert.
 | 
				
			||||||
		if err := s.ListenAndServeTLS("", ""); err != nil {
 | 
							if err := s.ListenAndServeTLS("", ""); err != nil {
 | 
				
			||||||
			log.WithFields(log.Fields{"auto_tls": true, "tls_hostname": tlshostname, "error": err}).
 | 
								log.WithFields(log.Fields{"auto_tls": true, "tls_hostname": tlshostname, "error": err}).Fatal("failed to configure HTTP server using auto-tls")
 | 
				
			||||||
				Fatal("failed to configure HTTP server using auto-tls")
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Check if main http server should run with TLS.
 | 
						// Check if main http server should run with TLS. Otherwise reset the TLS
 | 
				
			||||||
	if c.Api.Ssl.Enabled {
 | 
						// config on the server and then serve it over normal HTTP.
 | 
				
			||||||
		if err := s.ListenAndServeTLS(strings.ToLower(c.Api.Ssl.CertificateFile), strings.ToLower(c.Api.Ssl.KeyFile)); err != nil {
 | 
						if api.Ssl.Enabled {
 | 
				
			||||||
 | 
							if err := s.ListenAndServeTLS(strings.ToLower(api.Ssl.CertificateFile), strings.ToLower(api.Ssl.KeyFile)); err != nil {
 | 
				
			||||||
			log.WithFields(log.Fields{"auto_tls": false, "error": err}).Fatal("failed to configure HTTPS server")
 | 
								log.WithFields(log.Fields{"auto_tls": false, "error": err}).Fatal("failed to configure HTTPS server")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Run the main http server without TLS.
 | 
					 | 
				
			||||||
	s.TLSConfig = nil
 | 
						s.TLSConfig = nil
 | 
				
			||||||
	if err := s.ListenAndServe(); err != nil {
 | 
						if err := s.ListenAndServe(); err != nil {
 | 
				
			||||||
		log.WithField("error", err).Fatal("failed to configure HTTP server")
 | 
							log.WithField("error", err).Fatal("failed to configure HTTP server")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Cancel the context on all of the running servers at this point, even though the
 | 
					 | 
				
			||||||
	// program is just shutting down.
 | 
					 | 
				
			||||||
	for _, s := range server.GetServers().All() {
 | 
					 | 
				
			||||||
		s.CtxCancel()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Reads the configuration from the disk and then sets up the global singleton
 | 
				
			||||||
 | 
					// with all of the configuration values.
 | 
				
			||||||
func initConfig() {
 | 
					func initConfig() {
 | 
				
			||||||
	if configPath != "" {
 | 
						if !strings.HasPrefix(configPath, "/") {
 | 
				
			||||||
		viper.SetConfigName("config")
 | 
							d, err := os.Getwd()
 | 
				
			||||||
		viper.SetConfigType("yaml")
 | 
							if err != nil {
 | 
				
			||||||
		viper.AddConfigPath("/etc/pterodactyl")
 | 
								log2.Fatalf("cmd/root: could not determine directory: %s", err)
 | 
				
			||||||
		viper.AddConfigPath("$HOME/.pterodactyl")
 | 
					 | 
				
			||||||
		viper.AddConfigPath(".")
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		viper.SetConfigFile(configPath)
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	if err := viper.ReadInConfig(); err != nil {
 | 
							configPath = path.Clean(path.Join(d, configPath))
 | 
				
			||||||
		if _, ok := err.(*viper.ConfigFileNotFoundError); ok {
 | 
						}
 | 
				
			||||||
 | 
						err := config.FromFile(configPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if errors.Is(err, os.ErrNotExist) {
 | 
				
			||||||
			exitWithConfigurationNotice()
 | 
								exitWithConfigurationNotice()
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		log2.Fatalf("cmd/root: failed to read configuration: %s", err)
 | 
							log2.Fatalf("cmd/root: error while reading configuration file: %s", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if debug && !config.Get().Debug {
 | 
				
			||||||
 | 
							config.SetDebugViaFlag(debug)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configures the global logger for Zap so that we can call it from any location
 | 
					// Configures the global logger for Zap so that we can call it from any location
 | 
				
			||||||
// in the code without having to pass around a logger instance.
 | 
					// in the code without having to pass around a logger instance.
 | 
				
			||||||
func initLogging() {
 | 
					func initLogging() {
 | 
				
			||||||
	dir := viper.GetString("system.log_directory")
 | 
						dir := config.Get().System.LogDirectory
 | 
				
			||||||
	if err := os.MkdirAll(path.Join(dir, "/install"), 0700); err != nil {
 | 
						if err := os.MkdirAll(path.Join(dir, "/install"), 0700); err != nil {
 | 
				
			||||||
		log2.Fatalf("cmd/root: failed to create install directory path: %s", err)
 | 
							log2.Fatalf("cmd/root: failed to create install directory path: %s", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -390,12 +339,10 @@ func initLogging() {
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log2.Fatalf("cmd/root: failed to create wings log: %s", err)
 | 
							log2.Fatalf("cmd/root: failed to create wings log: %s", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	log.SetLevel(log.InfoLevel)
 | 
						log.SetLevel(log.InfoLevel)
 | 
				
			||||||
	if viper.GetBool("debug") {
 | 
						if config.Get().Debug {
 | 
				
			||||||
		log.SetLevel(log.DebugLevel)
 | 
							log.SetLevel(log.DebugLevel)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	log.SetHandler(multi.New(cli.Default, cli.New(w.File, false)))
 | 
						log.SetHandler(multi.New(cli.Default, cli.New(w.File, false)))
 | 
				
			||||||
	log.WithField("path", p).Info("writing log files to disk")
 | 
						log.WithField("path", p).Info("writing log files to disk")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										618
									
								
								config/config.go
									
									
									
									
									
								
							
							
						
						
									
										618
									
								
								config/config.go
									
									
									
									
									
								
							| 
						 | 
					@ -1,34 +1,247 @@
 | 
				
			||||||
package config
 | 
					package config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"crypto/tls"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"os/exec"
 | 
						"os/exec"
 | 
				
			||||||
	"os/user"
 | 
						"os/user"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
 | 
						"text/template"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"emperror.dev/errors"
 | 
						"emperror.dev/errors"
 | 
				
			||||||
 | 
						"github.com/apex/log"
 | 
				
			||||||
	"github.com/cobaugh/osrelease"
 | 
						"github.com/cobaugh/osrelease"
 | 
				
			||||||
	"github.com/creasty/defaults"
 | 
						"github.com/creasty/defaults"
 | 
				
			||||||
	"github.com/gbrlsnchs/jwt/v3"
 | 
						"github.com/gbrlsnchs/jwt/v3"
 | 
				
			||||||
	"github.com/pterodactyl/wings/system"
 | 
						"github.com/pterodactyl/wings/system"
 | 
				
			||||||
	"github.com/spf13/viper"
 | 
					 | 
				
			||||||
	"gopkg.in/yaml.v2"
 | 
						"gopkg.in/yaml.v2"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DefaultLocation = "/etc/pterodactyl/config.yml"
 | 
					const DefaultLocation = "/etc/pterodactyl/config.yml"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Configuration struct {
 | 
					// DefaultTLSConfig sets sane defaults to use when configuring the internal
 | 
				
			||||||
	sync.RWMutex `json:"-" yaml:"-"`
 | 
					// webserver to listen for public connections.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// @see https://blog.cloudflare.com/exposing-go-on-the-internet
 | 
				
			||||||
 | 
					var DefaultTLSConfig = &tls.Config{
 | 
				
			||||||
 | 
						NextProtos: []string{"h2", "http/1.1"},
 | 
				
			||||||
 | 
						CipherSuites: []uint16{
 | 
				
			||||||
 | 
							tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
 | 
				
			||||||
 | 
							tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
 | 
				
			||||||
 | 
							tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
 | 
				
			||||||
 | 
							tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
 | 
				
			||||||
 | 
							tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
 | 
				
			||||||
 | 
							tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						PreferServerCipherSuites: true,
 | 
				
			||||||
 | 
						MinVersion:               tls.VersionTLS12,
 | 
				
			||||||
 | 
						MaxVersion:               tls.VersionTLS13,
 | 
				
			||||||
 | 
						CurvePreferences:         []tls.CurveID{tls.X25519, tls.CurveP256},
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// The location from which this configuration instance was instantiated.
 | 
					var mu sync.RWMutex
 | 
				
			||||||
	path string
 | 
					var _config *Configuration
 | 
				
			||||||
 | 
					var _jwtAlgo *jwt.HMACSHA
 | 
				
			||||||
 | 
					var _debugViaFlag bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Locker specific to writing the configuration to the disk, this happens
 | 
					// Locker specific to writing the configuration to the disk, this happens
 | 
				
			||||||
// in areas that might already be locked so we don't want to crash the process.
 | 
					// in areas that might already be locked so we don't want to crash the process.
 | 
				
			||||||
	writeLock sync.Mutex
 | 
					var _writeLock sync.Mutex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SftpConfiguration defines the configuration of the internal SFTP server.
 | 
				
			||||||
 | 
					type SftpConfiguration struct {
 | 
				
			||||||
 | 
						// The bind address of the SFTP server.
 | 
				
			||||||
 | 
						Address string `default:"0.0.0.0" json:"bind_address" yaml:"bind_address"`
 | 
				
			||||||
 | 
						// The bind port of the SFTP server.
 | 
				
			||||||
 | 
						Port int `default:"2022" json:"bind_port" yaml:"bind_port"`
 | 
				
			||||||
 | 
						// If set to true, no write actions will be allowed on the SFTP server.
 | 
				
			||||||
 | 
						ReadOnly bool `default:"false" yaml:"read_only"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ApiConfiguration defines the configuration for the internal API that is
 | 
				
			||||||
 | 
					// exposed by the Wings webserver.
 | 
				
			||||||
 | 
					type ApiConfiguration struct {
 | 
				
			||||||
 | 
						// The interface that the internal webserver should bind to.
 | 
				
			||||||
 | 
						Host string `default:"0.0.0.0" yaml:"host"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The port that the internal webserver should bind to.
 | 
				
			||||||
 | 
						Port int `default:"8080" yaml:"port"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// SSL configuration for the daemon.
 | 
				
			||||||
 | 
						Ssl struct {
 | 
				
			||||||
 | 
							Enabled         bool   `json:"enabled" yaml:"enabled"`
 | 
				
			||||||
 | 
							CertificateFile string `json:"cert" yaml:"cert"`
 | 
				
			||||||
 | 
							KeyFile         string `json:"key" yaml:"key"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Determines if functionality for allowing remote download of files into server directories
 | 
				
			||||||
 | 
						// is enabled on this instance. If set to "true" remote downloads will not be possible for
 | 
				
			||||||
 | 
						// servers.
 | 
				
			||||||
 | 
						DisableRemoteDownload bool `json:"disable_remote_download" yaml:"disable_remote_download"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The maximum size for files uploaded through the Panel in bytes.
 | 
				
			||||||
 | 
						UploadLimit int `default:"100" json:"upload_limit" yaml:"upload_limit"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RemoteQueryConfiguration defines the configuration settings for remote requests
 | 
				
			||||||
 | 
					// from Wings to the Panel.
 | 
				
			||||||
 | 
					type RemoteQueryConfiguration struct {
 | 
				
			||||||
 | 
						// The amount of time in seconds that Wings should allow for a request to the Panel API
 | 
				
			||||||
 | 
						// to complete. If this time passes the request will be marked as failed. If your requests
 | 
				
			||||||
 | 
						// are taking longer than 30 seconds to complete it is likely a performance issue that
 | 
				
			||||||
 | 
						// should be resolved on the Panel, and not something that should be resolved by upping this
 | 
				
			||||||
 | 
						// number.
 | 
				
			||||||
 | 
						Timeout uint `default:"30" yaml:"timeout"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The number of servers to load in a single request to the Panel API when booting the
 | 
				
			||||||
 | 
						// Wings instance. A single request is initially made to the Panel to get this number
 | 
				
			||||||
 | 
						// of servers, and then the pagination status is checked and additional requests are
 | 
				
			||||||
 | 
						// fired off in parallel to request the remaining pages.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// It is not recommended to change this from the default as you will likely encounter
 | 
				
			||||||
 | 
						// memory limits on your Panel instance. In the grand scheme of things 4 requests for
 | 
				
			||||||
 | 
						// 50 servers is likely just as quick as two for 100 or one for 400, and will certainly
 | 
				
			||||||
 | 
						// be less likely to cause performance issues on the Panel.
 | 
				
			||||||
 | 
						BootServersPerPage uint `default:"50" yaml:"boot_servers_per_page"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SystemConfiguration defines basic system configuration settings.
 | 
				
			||||||
 | 
					type SystemConfiguration struct {
 | 
				
			||||||
 | 
						// The root directory where all of the pterodactyl data is stored at.
 | 
				
			||||||
 | 
						RootDirectory string `default:"/var/lib/pterodactyl" yaml:"root_directory"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Directory where logs for server installations and other wings events are logged.
 | 
				
			||||||
 | 
						LogDirectory string `default:"/var/log/pterodactyl" yaml:"log_directory"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Directory where the server data is stored at.
 | 
				
			||||||
 | 
						Data string `default:"/var/lib/pterodactyl/volumes" yaml:"data"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Directory where server archives for transferring will be stored.
 | 
				
			||||||
 | 
						ArchiveDirectory string `default:"/var/lib/pterodactyl/archives" yaml:"archive_directory"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Directory where local backups will be stored on the machine.
 | 
				
			||||||
 | 
						BackupDirectory string `default:"/var/lib/pterodactyl/backups" yaml:"backup_directory"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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 {
 | 
				
			||||||
 | 
							Uid int
 | 
				
			||||||
 | 
							Gid int
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The amount of time in seconds that can elapse before a server's disk space calculation is
 | 
				
			||||||
 | 
						// considered stale and a re-check should occur. DANGER: setting this value too low can seriously
 | 
				
			||||||
 | 
						// impact system performance and cause massive I/O bottlenecks and high CPU usage for the Wings
 | 
				
			||||||
 | 
						// process.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Set to 0 to disable disk checking entirely. This will always return 0 for the disk space used
 | 
				
			||||||
 | 
						// by a server and should only be set in extreme scenarios where performance is critical and
 | 
				
			||||||
 | 
						// disk usage is not a concern.
 | 
				
			||||||
 | 
						DiskCheckInterval int64 `default:"150" yaml:"disk_check_interval"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If set to true, file permissions for a server will be checked when the process is
 | 
				
			||||||
 | 
						// booted. This can cause boot delays if the server has a large amount of files. In most
 | 
				
			||||||
 | 
						// cases disabling this should not have any major impact unless external processes are
 | 
				
			||||||
 | 
						// frequently modifying a servers' files.
 | 
				
			||||||
 | 
						CheckPermissionsOnBoot bool `default:"true" yaml:"check_permissions_on_boot"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If set to false Wings will not attempt to write a log rotate configuration to the disk
 | 
				
			||||||
 | 
						// when it boots and one is not detected.
 | 
				
			||||||
 | 
						EnableLogRotate bool `default:"true" yaml:"enable_log_rotate"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The number of lines to send when a server connects to the websocket.
 | 
				
			||||||
 | 
						WebsocketLogCount int `default:"150" yaml:"websocket_log_count"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Sftp SftpConfiguration `yaml:"sftp"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CrashDetection CrashDetection `yaml:"crash_detection"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Backups Backups `yaml:"backups"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Transfers Transfers `yaml:"transfers"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type CrashDetection struct {
 | 
				
			||||||
 | 
						// Determines if Wings should detect a server that stops with a normal exit code of
 | 
				
			||||||
 | 
						// "0" as being crashed if the process stopped without any Wings interaction. E.g.
 | 
				
			||||||
 | 
						// the user did not press the stop button, but the process stopped cleanly.
 | 
				
			||||||
 | 
						DetectCleanExitAsCrash bool `default:"true" yaml:"detect_clean_exit_as_crash"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Timeout specifies the timeout between crashes that will not cause the server
 | 
				
			||||||
 | 
						// to be automatically restarted, this value is used to prevent servers from
 | 
				
			||||||
 | 
						// becoming stuck in a boot-loop after multiple consecutive crashes.
 | 
				
			||||||
 | 
						Timeout int `default:"60" json:"timeout"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Backups struct {
 | 
				
			||||||
 | 
						// WriteLimit imposes a Disk I/O write limit on backups to the disk, this affects all
 | 
				
			||||||
 | 
						// backup drivers as the archiver must first write the file to the disk in order to
 | 
				
			||||||
 | 
						// upload it to any external storage provider.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// If the value is less than 1, the write speed is unlimited,
 | 
				
			||||||
 | 
						// if the value is greater than 0, the write speed is the value in MiB/s.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Defaults to 0 (unlimited)
 | 
				
			||||||
 | 
						WriteLimit int `default:"0" yaml:"write_limit"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Transfers struct {
 | 
				
			||||||
 | 
						// DownloadLimit imposes a Network I/O read limit when downloading a transfer archive.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// If the value is less than 1, the write speed is unlimited,
 | 
				
			||||||
 | 
						// if the value is greater than 0, the write speed is the value in MiB/s.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Defaults to 0 (unlimited)
 | 
				
			||||||
 | 
						DownloadLimit int `default:"0" yaml:"download_limit"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ConsoleThrottles struct {
 | 
				
			||||||
 | 
						// Whether or not the throttler is enabled for this instance.
 | 
				
			||||||
 | 
						Enabled bool `json:"enabled" yaml:"enabled" default:"true"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The total number of lines that can be output in a given LineResetInterval period before
 | 
				
			||||||
 | 
						// a warning is triggered and counted against the server.
 | 
				
			||||||
 | 
						Lines uint64 `json:"lines" yaml:"lines" default:"2000"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The total number of throttle activations that can accumulate before a server is considered
 | 
				
			||||||
 | 
						// to be breaching and will be stopped. This value is decremented by one every DecayInterval.
 | 
				
			||||||
 | 
						MaximumTriggerCount uint64 `json:"maximum_trigger_count" yaml:"maximum_trigger_count" default:"5"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The amount of time after which the number of lines processed is reset to 0. This runs in
 | 
				
			||||||
 | 
						// a constant loop and is not affected by the current console output volumes. By default, this
 | 
				
			||||||
 | 
						// will reset the processed line count back to 0 every 100ms.
 | 
				
			||||||
 | 
						LineResetInterval uint64 `json:"line_reset_interval" yaml:"line_reset_interval" default:"100"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The amount of time in milliseconds that must pass without an output warning being triggered
 | 
				
			||||||
 | 
						// before a throttle activation is decremented.
 | 
				
			||||||
 | 
						DecayInterval uint64 `json:"decay_interval" yaml:"decay_interval" default:"10000"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The amount of time that a server is allowed to be stopping for before it is terminated
 | 
				
			||||||
 | 
						// forcefully if it triggers output throttles.
 | 
				
			||||||
 | 
						StopGracePeriod uint `json:"stop_grace_period" yaml:"stop_grace_period" default:"15"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Configuration struct {
 | 
				
			||||||
 | 
						// The location from which this configuration instance was instantiated.
 | 
				
			||||||
 | 
						path string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Determines if wings should be running in debug mode. This value is ignored
 | 
						// Determines if wings should be running in debug mode. This value is ignored
 | 
				
			||||||
	// if the debug flag is passed through the command line arguments.
 | 
						// if the debug flag is passed through the command line arguments.
 | 
				
			||||||
| 
						 | 
					@ -68,168 +281,110 @@ type Configuration struct {
 | 
				
			||||||
	AllowedOrigins []string `json:"allowed_origins" yaml:"allowed_origins"`
 | 
						AllowedOrigins []string `json:"allowed_origins" yaml:"allowed_origins"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Defines the configuration of the internal SFTP server.
 | 
					// NewAtPath creates a new struct and set the path where it should be stored.
 | 
				
			||||||
type SftpConfiguration struct {
 | 
					// This function does not modify the currently stored global configuration.
 | 
				
			||||||
	// The bind address of the SFTP server.
 | 
					func NewAtPath(path string) (*Configuration, error) {
 | 
				
			||||||
	Address string `default:"0.0.0.0" json:"bind_address" yaml:"bind_address"`
 | 
						var c Configuration
 | 
				
			||||||
	// The bind port of the SFTP server.
 | 
					 | 
				
			||||||
	Port int `default:"2022" json:"bind_port" yaml:"bind_port"`
 | 
					 | 
				
			||||||
	// If set to true, no write actions will be allowed on the SFTP server.
 | 
					 | 
				
			||||||
	ReadOnly bool `default:"false" yaml:"read_only"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Defines the configuration for the internal API that is exposed by the
 | 
					 | 
				
			||||||
// daemon webserver.
 | 
					 | 
				
			||||||
type ApiConfiguration struct {
 | 
					 | 
				
			||||||
	// The interface that the internal webserver should bind to.
 | 
					 | 
				
			||||||
	Host string `default:"0.0.0.0" yaml:"host"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// The port that the internal webserver should bind to.
 | 
					 | 
				
			||||||
	Port int `default:"8080" yaml:"port"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// SSL configuration for the daemon.
 | 
					 | 
				
			||||||
	Ssl struct {
 | 
					 | 
				
			||||||
		Enabled         bool   `json:"enabled" yaml:"enabled"`
 | 
					 | 
				
			||||||
		CertificateFile string `json:"cert" yaml:"cert"`
 | 
					 | 
				
			||||||
		KeyFile         string `json:"key" yaml:"key"`
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Determines if functionality for allowing remote download of files into server directories
 | 
					 | 
				
			||||||
	// is enabled on this instance. If set to "true" remote downloads will not be possible for
 | 
					 | 
				
			||||||
	// servers.
 | 
					 | 
				
			||||||
	DisableRemoteDownload bool `json:"disable_remote_download" yaml:"disable_remote_download"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// The maximum size for files uploaded through the Panel in bytes.
 | 
					 | 
				
			||||||
	UploadLimit int `default:"100" json:"upload_limit" yaml:"upload_limit"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Defines the configuration settings for remote requests from Wings to the Panel.
 | 
					 | 
				
			||||||
type RemoteQueryConfiguration struct {
 | 
					 | 
				
			||||||
	// The amount of time in seconds that Wings should allow for a request to the Panel API
 | 
					 | 
				
			||||||
	// to complete. If this time passes the request will be marked as failed. If your requests
 | 
					 | 
				
			||||||
	// are taking longer than 30 seconds to complete it is likely a performance issue that
 | 
					 | 
				
			||||||
	// should be resolved on the Panel, and not something that should be resolved by upping this
 | 
					 | 
				
			||||||
	// number.
 | 
					 | 
				
			||||||
	Timeout uint `default:"30" yaml:"timeout"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// The number of servers to load in a single request to the Panel API when booting the
 | 
					 | 
				
			||||||
	// Wings instance. A single request is initially made to the Panel to get this number
 | 
					 | 
				
			||||||
	// of servers, and then the pagination status is checked and additional requests are
 | 
					 | 
				
			||||||
	// fired off in parallel to request the remaining pages.
 | 
					 | 
				
			||||||
	//
 | 
					 | 
				
			||||||
	// It is not recommended to change this from the default as you will likely encounter
 | 
					 | 
				
			||||||
	// memory limits on your Panel instance. In the grand scheme of things 4 requests for
 | 
					 | 
				
			||||||
	// 50 servers is likely just as quick as two for 100 or one for 400, and will certainly
 | 
					 | 
				
			||||||
	// be less likely to cause performance issues on the Panel.
 | 
					 | 
				
			||||||
	BootServersPerPage uint `default:"50" yaml:"boot_servers_per_page"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Reads the configuration from the provided file and returns the configuration
 | 
					 | 
				
			||||||
// object that can then be used.
 | 
					 | 
				
			||||||
func ReadConfiguration(path string) (*Configuration, error) {
 | 
					 | 
				
			||||||
	b, err := ioutil.ReadFile(path)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	c := new(Configuration)
 | 
					 | 
				
			||||||
	// Configures the default values for many of the configuration options present
 | 
						// Configures the default values for many of the configuration options present
 | 
				
			||||||
	// in the structs. Values set in the configuration file take priority over the
 | 
						// in the structs. Values set in the configuration file take priority over the
 | 
				
			||||||
	// default values.
 | 
						// default values.
 | 
				
			||||||
	if err := defaults.Set(c); err != nil {
 | 
						if err := defaults.Set(&c); err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Track the location where we created this configuration.
 | 
						// Track the location where we created this configuration.
 | 
				
			||||||
	c.unsafeSetPath(path)
 | 
						c.path = path
 | 
				
			||||||
 | 
						return &c, nil
 | 
				
			||||||
	// Replace environment variables within the configuration file with their
 | 
					 | 
				
			||||||
	// values from the host system.
 | 
					 | 
				
			||||||
	b = []byte(os.ExpandEnv(string(b)))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err := yaml.Unmarshal(b, c); err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return c, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var mu sync.RWMutex
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var _config *Configuration
 | 
					 | 
				
			||||||
var _jwtAlgo *jwt.HMACSHA
 | 
					 | 
				
			||||||
var _debugViaFlag bool
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Set the global configuration instance. This is a blocking operation such that
 | 
					// Set the global configuration instance. This is a blocking operation such that
 | 
				
			||||||
// anything trying to set a different configuration value, or read the configuration
 | 
					// anything trying to set a different configuration value, or read the configuration
 | 
				
			||||||
// will be paused until it is complete.
 | 
					// will be paused until it is complete.
 | 
				
			||||||
func Set(c *Configuration) {
 | 
					func Set(c *Configuration) {
 | 
				
			||||||
	mu.Lock()
 | 
						mu.Lock()
 | 
				
			||||||
 | 
					 | 
				
			||||||
	if _config == nil || _config.AuthenticationToken != c.AuthenticationToken {
 | 
						if _config == nil || _config.AuthenticationToken != c.AuthenticationToken {
 | 
				
			||||||
		_jwtAlgo = jwt.NewHS256([]byte(c.AuthenticationToken))
 | 
							_jwtAlgo = jwt.NewHS256([]byte(c.AuthenticationToken))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	_config = c
 | 
						_config = c
 | 
				
			||||||
	mu.Unlock()
 | 
						mu.Unlock()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetDebugViaFlag tracks if the application is running in debug mode because of
 | 
				
			||||||
 | 
					// a command line flag argument. If so we do not want to store that configuration
 | 
				
			||||||
 | 
					// change to the disk.
 | 
				
			||||||
func SetDebugViaFlag(d bool) {
 | 
					func SetDebugViaFlag(d bool) {
 | 
				
			||||||
 | 
						mu.Lock()
 | 
				
			||||||
 | 
						_config.Debug = d
 | 
				
			||||||
	_debugViaFlag = d
 | 
						_debugViaFlag = d
 | 
				
			||||||
 | 
						mu.Unlock()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Get the global configuration instance. This is a read-safe operation that will block
 | 
					// Get returns the global configuration instance. This is a thread-safe operation
 | 
				
			||||||
// if the configuration is presently being modified.
 | 
					// that will block if the configuration is presently being modified.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Be aware that you CANNOT make modifications to the currently stored configuration
 | 
				
			||||||
 | 
					// by modifying the struct returned by this function. The only way to make
 | 
				
			||||||
 | 
					// modifications is by using the Update() function and passing data through in
 | 
				
			||||||
 | 
					// the callback.
 | 
				
			||||||
func Get() *Configuration {
 | 
					func Get() *Configuration {
 | 
				
			||||||
	mu.RLock()
 | 
						mu.RLock()
 | 
				
			||||||
	defer mu.RUnlock()
 | 
						// Create a copy of the struct so that all modifications made beyond this
 | 
				
			||||||
 | 
						// point are immutable.
 | 
				
			||||||
	return _config
 | 
						//goland:noinspection GoVetCopyLock
 | 
				
			||||||
 | 
						c := *_config
 | 
				
			||||||
 | 
						mu.RUnlock()
 | 
				
			||||||
 | 
						return &c
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Returns the in-memory JWT algorithm.
 | 
					// Update performs an in-situ update of the global configuration object using
 | 
				
			||||||
 | 
					// a thread-safe mutex lock. This is the correct way to make modifications to
 | 
				
			||||||
 | 
					// the global configuration.
 | 
				
			||||||
 | 
					func Update(callback func(c *Configuration)) {
 | 
				
			||||||
 | 
						mu.Lock()
 | 
				
			||||||
 | 
						defer mu.Unlock()
 | 
				
			||||||
 | 
						callback(_config)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetJwtAlgorithm returns the in-memory JWT algorithm.
 | 
				
			||||||
func GetJwtAlgorithm() *jwt.HMACSHA {
 | 
					func GetJwtAlgorithm() *jwt.HMACSHA {
 | 
				
			||||||
	mu.RLock()
 | 
						mu.RLock()
 | 
				
			||||||
	defer mu.RUnlock()
 | 
						defer mu.RUnlock()
 | 
				
			||||||
 | 
					 | 
				
			||||||
	return _jwtAlgo
 | 
						return _jwtAlgo
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Create a new struct and set the path where it should be stored.
 | 
					// WriteToDisk writes the configuration to the disk. This is a thread safe operation
 | 
				
			||||||
func NewFromPath(path string) (*Configuration, error) {
 | 
					// and will only allow one write at a time. Additional calls while writing are
 | 
				
			||||||
	c := new(Configuration)
 | 
					// queued up.
 | 
				
			||||||
	if err := defaults.Set(c); err != nil {
 | 
					func WriteToDisk(c *Configuration) error {
 | 
				
			||||||
		return c, err
 | 
						_writeLock.Lock()
 | 
				
			||||||
 | 
						defer _writeLock.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//goland:noinspection GoVetCopyLock
 | 
				
			||||||
 | 
						ccopy := *c
 | 
				
			||||||
 | 
						// If debugging is set with the flag, don't save that to the configuration file,
 | 
				
			||||||
 | 
						// otherwise you'll always end up in debug mode.
 | 
				
			||||||
 | 
						if _debugViaFlag {
 | 
				
			||||||
 | 
							ccopy.Debug = false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if c.path == "" {
 | 
				
			||||||
	c.unsafeSetPath(path)
 | 
							return errors.New("cannot write configuration, no path defined in struct")
 | 
				
			||||||
 | 
					 | 
				
			||||||
	return c, nil
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						b, err := yaml.Marshal(&ccopy)
 | 
				
			||||||
// Sets the path where the configuration file is located on the server. This function should
 | 
						if err != nil {
 | 
				
			||||||
// not be called except by processes that are generating the configuration such as the configuration
 | 
							return err
 | 
				
			||||||
// command shipped with this software.
 | 
					 | 
				
			||||||
func (c *Configuration) unsafeSetPath(path string) {
 | 
					 | 
				
			||||||
	c.Lock()
 | 
					 | 
				
			||||||
	c.path = path
 | 
					 | 
				
			||||||
	c.Unlock()
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if err := ioutil.WriteFile(c.path, b, 0600); err != nil {
 | 
				
			||||||
// Returns the path for this configuration file.
 | 
							return err
 | 
				
			||||||
func (c *Configuration) GetPath() string {
 | 
						}
 | 
				
			||||||
	c.RLock()
 | 
						return nil
 | 
				
			||||||
	defer c.RUnlock()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return c.path
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// EnsurePterodactylUser ensures that the Pterodactyl core user exists on the
 | 
					// EnsurePterodactylUser ensures that the Pterodactyl core user exists on the
 | 
				
			||||||
// system. This user will be the owner of all data in the root data directory
 | 
					// system. This user will be the owner of all data in the root data directory
 | 
				
			||||||
// and is used as the user within containers.
 | 
					// and is used as the user within containers. If files are not owned by this
 | 
				
			||||||
 | 
					// user there will be issues with permissions on Docker mount points.
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// If files are not owned by this user there will be issues with permissions on
 | 
					// This function IS NOT thread safe and should only be called in the main thread
 | 
				
			||||||
// Docker mount points.
 | 
					// when the application is booting.
 | 
				
			||||||
func EnsurePterodactylUser() error {
 | 
					func EnsurePterodactylUser() error {
 | 
				
			||||||
	sysName, err := getSystemName()
 | 
						sysName, err := getSystemName()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
| 
						 | 
					@ -238,14 +393,13 @@ func EnsurePterodactylUser() error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Our way of detecting if wings is running inside of Docker.
 | 
						// Our way of detecting if wings is running inside of Docker.
 | 
				
			||||||
	if sysName == "busybox" {
 | 
						if sysName == "busybox" {
 | 
				
			||||||
		viper.Set("system.username", system.FirstNotEmpty(os.Getenv("WINGS_USERNAME"), "pterodactyl"))
 | 
							_config.System.Username = system.FirstNotEmpty(os.Getenv("WINGS_USERNAME"), "pterodactyl")
 | 
				
			||||||
		viper.Set("system.user.uid", system.MustInt(system.FirstNotEmpty(os.Getenv("WINGS_UID"), "988")))
 | 
							_config.System.User.Uid = system.MustInt(system.FirstNotEmpty(os.Getenv("WINGS_UID"), "988"))
 | 
				
			||||||
		viper.Set("system.user.gid", system.MustInt(system.FirstNotEmpty(os.Getenv("WINGS_GID"), "988")))
 | 
							_config.System.User.Gid = system.MustInt(system.FirstNotEmpty(os.Getenv("WINGS_UID"), "988"))
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	username := viper.GetString("system.username")
 | 
						u, err := user.Lookup(_config.System.Username)
 | 
				
			||||||
	u, err := user.Lookup(username)
 | 
					 | 
				
			||||||
	// If an error is returned but it isn't the unknown user error just abort
 | 
						// If an error is returned but it isn't the unknown user error just abort
 | 
				
			||||||
	// the process entirely. If we did find a user, return it immediately.
 | 
						// the process entirely. If we did find a user, return it immediately.
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
| 
						 | 
					@ -253,19 +407,19 @@ func EnsurePterodactylUser() error {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		viper.Set("system.user.uid", system.MustInt(u.Uid))
 | 
							_config.System.User.Uid = system.MustInt(u.Uid)
 | 
				
			||||||
		viper.Set("system.user.gid", system.MustInt(u.Gid))
 | 
							_config.System.User.Gid = system.MustInt(u.Gid)
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	command := fmt.Sprintf("useradd --system --no-create-home --shell /usr/sbin/nologin %s", username)
 | 
						command := fmt.Sprintf("useradd --system --no-create-home --shell /usr/sbin/nologin %s", _config.System.Username)
 | 
				
			||||||
	// Alpine Linux is the only OS we currently support that doesn't work with the useradd
 | 
						// Alpine Linux is the only OS we currently support that doesn't work with the useradd
 | 
				
			||||||
	// command, so in those cases we just modify the command a bit to work as expected.
 | 
						// command, so in those cases we just modify the command a bit to work as expected.
 | 
				
			||||||
	if strings.HasPrefix(sysName, "alpine") {
 | 
						if strings.HasPrefix(sysName, "alpine") {
 | 
				
			||||||
		command = fmt.Sprintf("adduser -S -D -H -G %[1]s -s /sbin/nologin %[1]s", username)
 | 
							command = fmt.Sprintf("adduser -S -D -H -G %[1]s -s /sbin/nologin %[1]s", _config.System.Username)
 | 
				
			||||||
		// We have to create the group first on Alpine, so do that here before continuing on
 | 
							// We have to create the group first on Alpine, so do that here before continuing on
 | 
				
			||||||
		// to the user creation process.
 | 
							// to the user creation process.
 | 
				
			||||||
		if _, err := exec.Command("addgroup", "-S", username).Output(); err != nil {
 | 
							if _, err := exec.Command("addgroup", "-S", _config.System.Username).Output(); err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -274,53 +428,189 @@ func EnsurePterodactylUser() error {
 | 
				
			||||||
	if _, err := exec.Command(split[0], split[1:]...).Output(); err != nil {
 | 
						if _, err := exec.Command(split[0], split[1:]...).Output(); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						u, err = user.Lookup(_config.System.Username)
 | 
				
			||||||
	u, err = user.Lookup(username)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	viper.Set("system.user.uid", system.MustInt(u.Uid))
 | 
						_config.System.User.Uid = system.MustInt(u.Uid)
 | 
				
			||||||
	viper.Set("system.user.gid", system.MustInt(u.Gid))
 | 
						_config.System.User.Gid = system.MustInt(u.Gid)
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Writes the configuration to the disk as a blocking operation by obtaining an exclusive
 | 
					// FromFile reads the configuration from the provided file and stores it in the
 | 
				
			||||||
// lock on the file. This prevents something else from writing at the exact same time and
 | 
					// global singleton for this instance.
 | 
				
			||||||
// leading to bad data conditions.
 | 
					func FromFile(path string) error {
 | 
				
			||||||
func (c *Configuration) WriteToDisk() error {
 | 
						b, err := ioutil.ReadFile(path)
 | 
				
			||||||
	// Obtain an exclusive write against the configuration file.
 | 
					 | 
				
			||||||
	c.writeLock.Lock()
 | 
					 | 
				
			||||||
	defer c.writeLock.Unlock()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	ccopy := *c
 | 
					 | 
				
			||||||
	// If debugging is set with the flag, don't save that to the configuration file, otherwise
 | 
					 | 
				
			||||||
	// you'll always end up in debug mode.
 | 
					 | 
				
			||||||
	if _debugViaFlag {
 | 
					 | 
				
			||||||
		ccopy.Debug = false
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if c.path == "" {
 | 
					 | 
				
			||||||
		return errors.New("cannot write configuration, no path defined in struct")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	b, err := yaml.Marshal(&ccopy)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						c, err := NewAtPath(path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Replace environment variables within the configuration file with their
 | 
				
			||||||
 | 
						// values from the host system.
 | 
				
			||||||
 | 
						b = []byte(os.ExpandEnv(string(b)))
 | 
				
			||||||
 | 
						if err := yaml.Unmarshal(b, c); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Store this configuration in the global state.
 | 
				
			||||||
 | 
						Set(c)
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := ioutil.WriteFile(c.GetPath(), b, 0644); err != nil {
 | 
					// ConfigureDirectories ensures that all of the system directories exist on the
 | 
				
			||||||
 | 
					// system. These directories are created so that only the owner can read the data,
 | 
				
			||||||
 | 
					// and no other users.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// This function IS NOT thread-safe.
 | 
				
			||||||
 | 
					func ConfigureDirectories() error {
 | 
				
			||||||
 | 
						root := _config.System.RootDirectory
 | 
				
			||||||
 | 
						log.WithField("path", root).Debug("ensuring root data directory exists")
 | 
				
			||||||
 | 
						if err := os.MkdirAll(root, 0700); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// There are a non-trivial number of users out there whose data directories are actually a
 | 
				
			||||||
 | 
						// symlink to another location on the disk. If we do not resolve that final destination at this
 | 
				
			||||||
 | 
						// point things will appear to work, but endless errors will be encountered when we try to
 | 
				
			||||||
 | 
						// verify accessed paths since they will all end up resolving outside the expected data directory.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// For the sake of automating away as much of this as possible, see if the data directory is a
 | 
				
			||||||
 | 
						// symlink, and if so resolve to its final real path, and then update the configuration to use
 | 
				
			||||||
 | 
						// that.
 | 
				
			||||||
 | 
						if d, err := filepath.EvalSymlinks(_config.System.Data); err != nil {
 | 
				
			||||||
 | 
							if !os.IsNotExist(err) {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else if d != _config.System.Data {
 | 
				
			||||||
 | 
							_config.System.Data = d
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.WithField("path", _config.System.Data).Debug("ensuring server data directory exists")
 | 
				
			||||||
 | 
						if err := os.MkdirAll(_config.System.Data, 0700); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.WithField("path", _config.System.ArchiveDirectory).Debug("ensuring archive data directory exists")
 | 
				
			||||||
 | 
						if err := os.MkdirAll(_config.System.ArchiveDirectory, 0700); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.WithField("path", _config.System.BackupDirectory).Debug("ensuring backup data directory exists")
 | 
				
			||||||
 | 
						if err := os.MkdirAll(_config.System.BackupDirectory, 0700); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// EnableLogRotation writes a logrotate file for wings to the system logrotate
 | 
				
			||||||
 | 
					// configuration directory if one exists and a logrotate file is not found. This
 | 
				
			||||||
 | 
					// allows us to basically automate away the log rotation for most installs, but
 | 
				
			||||||
 | 
					// also enable users to make modifications on their own.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// This function IS NOT thread-safe.
 | 
				
			||||||
 | 
					func EnableLogRotation() error {
 | 
				
			||||||
 | 
						if !_config.System.EnableLogRotate {
 | 
				
			||||||
 | 
							log.Info("skipping log rotate configuration, disabled in wings config file")
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if st, err := os.Stat("/etc/logrotate.d"); err != nil && !os.IsNotExist(err) {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						} else if (err != nil && os.IsNotExist(err)) || !st.IsDir() {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if _, err := os.Stat("/etc/logrotate.d/wings"); err == nil || !os.IsNotExist(err) {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Info("no log rotation configuration found: adding file now")
 | 
				
			||||||
 | 
						// If we've gotten to this point it means the logrotate directory exists on the system
 | 
				
			||||||
 | 
						// but there is not a file for wings already. In that case, let us write a new file to
 | 
				
			||||||
 | 
						// it so files can be rotated easily.
 | 
				
			||||||
 | 
						f, err := os.Create("/etc/logrotate.d/wings")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer f.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t, err := template.New("logrotate").Parse(`
 | 
				
			||||||
 | 
					{{.LogDirectory}}/wings.log {
 | 
				
			||||||
 | 
					    size 10M
 | 
				
			||||||
 | 
					    compress
 | 
				
			||||||
 | 
					    delaycompress
 | 
				
			||||||
 | 
					    dateext
 | 
				
			||||||
 | 
					    maxage 7
 | 
				
			||||||
 | 
					    missingok
 | 
				
			||||||
 | 
					    notifempty
 | 
				
			||||||
 | 
					    create 0640 {{.User.Uid}} {{.User.Gid}}
 | 
				
			||||||
 | 
					    postrotate
 | 
				
			||||||
 | 
					        killall -SIGHUP wings
 | 
				
			||||||
 | 
					    endscript
 | 
				
			||||||
 | 
					}`)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return errors.Wrap(t.Execute(f, _config.System), "config: failed to write logrotate to disk")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetStatesPath returns the location of the JSON file that tracks server states.
 | 
				
			||||||
 | 
					func (sc *SystemConfiguration) GetStatesPath() string {
 | 
				
			||||||
 | 
						return path.Join(sc.RootDirectory, "/states.json")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ConfigureTimezone sets 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.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// This function IS NOT thread-safe.
 | 
				
			||||||
 | 
					func ConfigureTimezone() error {
 | 
				
			||||||
 | 
						if _config.System.Timezone == "" {
 | 
				
			||||||
 | 
							b, err := ioutil.ReadFile("/etc/timezone")
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								if !os.IsNotExist(err) {
 | 
				
			||||||
 | 
									return errors.WithMessage(err, "config: failed to open timezone file")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								_config.System.Timezone = "UTC"
 | 
				
			||||||
 | 
								ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
 | 
				
			||||||
 | 
								defer cancel()
 | 
				
			||||||
 | 
								// 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")
 | 
				
			||||||
 | 
									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")
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								_config.System.Timezone = string(matches[1])
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								_config.System.Timezone = string(b)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_config.System.Timezone = regexp.MustCompile(`(?i)[^a-z_/]+`).ReplaceAllString(_config.System.Timezone, "")
 | 
				
			||||||
 | 
						_, err := time.LoadLocation(_config.System.Timezone)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return errors.WithMessage(err, fmt.Sprintf("the supplied timezone %s is invalid", _config.System.Timezone))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Gets the system release name.
 | 
					// Gets the system release name.
 | 
				
			||||||
func getSystemName() (string, error) {
 | 
					func getSystemName() (string, error) {
 | 
				
			||||||
	// use osrelease to get release version and ID
 | 
						// use osrelease to get release version and ID
 | 
				
			||||||
	if release, err := osrelease.Read(); err != nil {
 | 
						release, err := osrelease.Read()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
		return "", err
 | 
							return "", err
 | 
				
			||||||
	} else {
 | 
						}
 | 
				
			||||||
	return release["ID"], nil
 | 
						return release["ID"], nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,6 @@ type dockerNetworkInterfaces struct {
 | 
				
			||||||
		Subnet  string `default:"172.18.0.0/16"`
 | 
							Subnet  string `default:"172.18.0.0/16"`
 | 
				
			||||||
		Gateway string `default:"172.18.0.1"`
 | 
							Gateway string `default:"172.18.0.1"`
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	V6 struct {
 | 
						V6 struct {
 | 
				
			||||||
		Subnet  string `default:"fdba:17c8:6c94::/64"`
 | 
							Subnet  string `default:"fdba:17c8:6c94::/64"`
 | 
				
			||||||
		Gateway string `default:"fdba:17c8:6c94::1011"`
 | 
							Gateway string `default:"fdba:17c8:6c94::1011"`
 | 
				
			||||||
| 
						 | 
					@ -39,8 +38,8 @@ type DockerNetworkConfiguration struct {
 | 
				
			||||||
	Interfaces dockerNetworkInterfaces `yaml:"interfaces"`
 | 
						Interfaces dockerNetworkInterfaces `yaml:"interfaces"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Defines the docker configuration used by the daemon when interacting with
 | 
					// DockerConfiguration defines the docker configuration used by the daemon when
 | 
				
			||||||
// containers and networks on the system.
 | 
					// interacting with containers and networks on the system.
 | 
				
			||||||
type DockerConfiguration struct {
 | 
					type DockerConfiguration struct {
 | 
				
			||||||
	// Network configuration that should be used when creating a new network
 | 
						// Network configuration that should be used when creating a new network
 | 
				
			||||||
	// for containers run through the daemon.
 | 
						// for containers run through the daemon.
 | 
				
			||||||
| 
						 | 
					@ -58,23 +57,22 @@ type DockerConfiguration struct {
 | 
				
			||||||
	TmpfsSize uint `default:"100" json:"tmpfs_size" yaml:"tmpfs_size"`
 | 
						TmpfsSize uint `default:"100" json:"tmpfs_size" yaml:"tmpfs_size"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RegistryConfiguration .
 | 
					// RegistryConfiguration defines the authentication credentials for a given
 | 
				
			||||||
 | 
					// Docker registry.
 | 
				
			||||||
type RegistryConfiguration struct {
 | 
					type RegistryConfiguration struct {
 | 
				
			||||||
	Username string `yaml:"username"`
 | 
						Username string `yaml:"username"`
 | 
				
			||||||
	Password string `yaml:"password"`
 | 
						Password string `yaml:"password"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Base64 .
 | 
					// Base64 returns the authentication for a given registry as a base64 encoded
 | 
				
			||||||
 | 
					// string value.
 | 
				
			||||||
func (c RegistryConfiguration) Base64() (string, error) {
 | 
					func (c RegistryConfiguration) Base64() (string, error) {
 | 
				
			||||||
	authConfig := types.AuthConfig{
 | 
						b, err := json.Marshal(types.AuthConfig{
 | 
				
			||||||
		Username: c.Username,
 | 
							Username: c.Username,
 | 
				
			||||||
		Password: c.Password,
 | 
							Password: c.Password,
 | 
				
			||||||
	}
 | 
						})
 | 
				
			||||||
 | 
					 | 
				
			||||||
	b, err := json.Marshal(authConfig)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return "", err
 | 
							return "", err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	return base64.URLEncoding.EncodeToString(b), nil
 | 
						return base64.URLEncoding.EncodeToString(b), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,279 +0,0 @@
 | 
				
			||||||
package config
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"context"
 | 
					 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"html/template"
 | 
					 | 
				
			||||||
	"io/ioutil"
 | 
					 | 
				
			||||||
	"os"
 | 
					 | 
				
			||||||
	"os/exec"
 | 
					 | 
				
			||||||
	"path"
 | 
					 | 
				
			||||||
	"path/filepath"
 | 
					 | 
				
			||||||
	"regexp"
 | 
					 | 
				
			||||||
	"time"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"emperror.dev/errors"
 | 
					 | 
				
			||||||
	"github.com/apex/log"
 | 
					 | 
				
			||||||
	"github.com/spf13/viper"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Defines basic system configuration settings.
 | 
					 | 
				
			||||||
type SystemConfiguration struct {
 | 
					 | 
				
			||||||
	// The root directory where all of the pterodactyl data is stored at.
 | 
					 | 
				
			||||||
	RootDirectory string `default:"/var/lib/pterodactyl" yaml:"root_directory"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Directory where logs for server installations and other wings events are logged.
 | 
					 | 
				
			||||||
	LogDirectory string `default:"/var/log/pterodactyl" yaml:"log_directory"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Directory where the server data is stored at.
 | 
					 | 
				
			||||||
	Data string `default:"/var/lib/pterodactyl/volumes" yaml:"data"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Directory where server archives for transferring will be stored.
 | 
					 | 
				
			||||||
	ArchiveDirectory string `default:"/var/lib/pterodactyl/archives" yaml:"archive_directory"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Directory where local backups will be stored on the machine.
 | 
					 | 
				
			||||||
	BackupDirectory string `default:"/var/lib/pterodactyl/backups" yaml:"backup_directory"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 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 {
 | 
					 | 
				
			||||||
		Uid int
 | 
					 | 
				
			||||||
		Gid int
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// The amount of time in seconds that can elapse before a server's disk space calculation is
 | 
					 | 
				
			||||||
	// considered stale and a re-check should occur. DANGER: setting this value too low can seriously
 | 
					 | 
				
			||||||
	// impact system performance and cause massive I/O bottlenecks and high CPU usage for the Wings
 | 
					 | 
				
			||||||
	// process.
 | 
					 | 
				
			||||||
	//
 | 
					 | 
				
			||||||
	// Set to 0 to disable disk checking entirely. This will always return 0 for the disk space used
 | 
					 | 
				
			||||||
	// by a server and should only be set in extreme scenarios where performance is critical and
 | 
					 | 
				
			||||||
	// disk usage is not a concern.
 | 
					 | 
				
			||||||
	DiskCheckInterval int64 `default:"150" yaml:"disk_check_interval"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// If set to true, file permissions for a server will be checked when the process is
 | 
					 | 
				
			||||||
	// booted. This can cause boot delays if the server has a large amount of files. In most
 | 
					 | 
				
			||||||
	// cases disabling this should not have any major impact unless external processes are
 | 
					 | 
				
			||||||
	// frequently modifying a servers' files.
 | 
					 | 
				
			||||||
	CheckPermissionsOnBoot bool `default:"true" yaml:"check_permissions_on_boot"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// If set to false Wings will not attempt to write a log rotate configuration to the disk
 | 
					 | 
				
			||||||
	// when it boots and one is not detected.
 | 
					 | 
				
			||||||
	EnableLogRotate bool `default:"true" yaml:"enable_log_rotate"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// The number of lines to send when a server connects to the websocket.
 | 
					 | 
				
			||||||
	WebsocketLogCount int `default:"150" yaml:"websocket_log_count"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	Sftp SftpConfiguration `yaml:"sftp"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	CrashDetection CrashDetection `yaml:"crash_detection"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	Backups Backups `yaml:"backups"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	Transfers Transfers `yaml:"transfers"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type CrashDetection struct {
 | 
					 | 
				
			||||||
	// Determines if Wings should detect a server that stops with a normal exit code of
 | 
					 | 
				
			||||||
	// "0" as being crashed if the process stopped without any Wings interaction. E.g.
 | 
					 | 
				
			||||||
	// the user did not press the stop button, but the process stopped cleanly.
 | 
					 | 
				
			||||||
	DetectCleanExitAsCrash bool `default:"true" yaml:"detect_clean_exit_as_crash"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Timeout specifies the timeout between crashes that will not cause the server
 | 
					 | 
				
			||||||
	// to be automatically restarted, this value is used to prevent servers from
 | 
					 | 
				
			||||||
	// becoming stuck in a boot-loop after multiple consecutive crashes.
 | 
					 | 
				
			||||||
	Timeout int `default:"60" json:"timeout"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Backups struct {
 | 
					 | 
				
			||||||
	// WriteLimit imposes a Disk I/O write limit on backups to the disk, this affects all
 | 
					 | 
				
			||||||
	// backup drivers as the archiver must first write the file to the disk in order to
 | 
					 | 
				
			||||||
	// upload it to any external storage provider.
 | 
					 | 
				
			||||||
	//
 | 
					 | 
				
			||||||
	// If the value is less than 1, the write speed is unlimited,
 | 
					 | 
				
			||||||
	// if the value is greater than 0, the write speed is the value in MiB/s.
 | 
					 | 
				
			||||||
	//
 | 
					 | 
				
			||||||
	// Defaults to 0 (unlimited)
 | 
					 | 
				
			||||||
	WriteLimit int `default:"0" yaml:"write_limit"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Transfers struct {
 | 
					 | 
				
			||||||
	// DownloadLimit imposes a Network I/O read limit when downloading a transfer archive.
 | 
					 | 
				
			||||||
	//
 | 
					 | 
				
			||||||
	// If the value is less than 1, the write speed is unlimited,
 | 
					 | 
				
			||||||
	// if the value is greater than 0, the write speed is the value in MiB/s.
 | 
					 | 
				
			||||||
	//
 | 
					 | 
				
			||||||
	// Defaults to 0 (unlimited)
 | 
					 | 
				
			||||||
	DownloadLimit int `default:"0" yaml:"download_limit"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ConfigureDirectories ensures that all of the system directories exist on the
 | 
					 | 
				
			||||||
// system. These directories are created so that only the owner can read the data,
 | 
					 | 
				
			||||||
// and no other users.
 | 
					 | 
				
			||||||
func ConfigureDirectories() error {
 | 
					 | 
				
			||||||
	root := viper.GetString("system.root_directory")
 | 
					 | 
				
			||||||
	log.WithField("path", root).Debug("ensuring root data directory exists")
 | 
					 | 
				
			||||||
	if err := os.MkdirAll(root, 0700); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// There are a non-trivial number of users out there whose data directories are actually a
 | 
					 | 
				
			||||||
	// symlink to another location on the disk. If we do not resolve that final destination at this
 | 
					 | 
				
			||||||
	// point things will appear to work, but endless errors will be encountered when we try to
 | 
					 | 
				
			||||||
	// verify accessed paths since they will all end up resolving outside the expected data directory.
 | 
					 | 
				
			||||||
	//
 | 
					 | 
				
			||||||
	// For the sake of automating away as much of this as possible, see if the data directory is a
 | 
					 | 
				
			||||||
	// symlink, and if so resolve to its final real path, and then update the configuration to use
 | 
					 | 
				
			||||||
	// that.
 | 
					 | 
				
			||||||
	data := viper.GetString("system.data")
 | 
					 | 
				
			||||||
	if d, err := filepath.EvalSymlinks(data); err != nil {
 | 
					 | 
				
			||||||
		if !os.IsNotExist(err) {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else if d != data {
 | 
					 | 
				
			||||||
		data = d
 | 
					 | 
				
			||||||
		viper.Set("system.data", d)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	log.WithField("path", data).Debug("ensuring server data directory exists")
 | 
					 | 
				
			||||||
	if err := os.MkdirAll(data, 0700); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	log.WithField("path", viper.GetString("system.archive_directory")).Debug("ensuring archive data directory exists")
 | 
					 | 
				
			||||||
	if err := os.MkdirAll(viper.GetString("system.archive_directory"), 0700); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	log.WithField("path", viper.GetString("system.backup_directory")).Debug("ensuring backup data directory exists")
 | 
					 | 
				
			||||||
	if err := os.MkdirAll(viper.GetString("system.backup_directory"), 0700); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// EnableLogRotation writes a logrotate file for wings to the system logrotate
 | 
					 | 
				
			||||||
// configuration directory if one exists and a logrotate file is not found. This
 | 
					 | 
				
			||||||
// allows us to basically automate away the log rotation for most installs, but
 | 
					 | 
				
			||||||
// also enable users to make modifications on their own.
 | 
					 | 
				
			||||||
func EnableLogRotation() error {
 | 
					 | 
				
			||||||
	// Do nothing if not enabled.
 | 
					 | 
				
			||||||
	if !viper.GetBool("system.enable_log_rotate") {
 | 
					 | 
				
			||||||
		log.Info("skipping log rotate configuration, disabled in wings config file")
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if st, err := os.Stat("/etc/logrotate.d"); err != nil && !os.IsNotExist(err) {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	} else if (err != nil && os.IsNotExist(err)) || !st.IsDir() {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if _, err := os.Stat("/etc/logrotate.d/wings"); err == nil || !os.IsNotExist(err) {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	log.Info("no log rotation configuration found: adding file now")
 | 
					 | 
				
			||||||
	// If we've gotten to this point it means the logrotate directory exists on the system
 | 
					 | 
				
			||||||
	// but there is not a file for wings already. In that case, let us write a new file to
 | 
					 | 
				
			||||||
	// it so files can be rotated easily.
 | 
					 | 
				
			||||||
	f, err := os.Create("/etc/logrotate.d/wings")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer f.Close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	type logrotateConfig struct {
 | 
					 | 
				
			||||||
		Directory string
 | 
					 | 
				
			||||||
		UserID    int
 | 
					 | 
				
			||||||
		GroupID   int
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	t, err := template.New("logrotate").Parse(`
 | 
					 | 
				
			||||||
{{.Directory}}/wings.log {
 | 
					 | 
				
			||||||
    size 10M
 | 
					 | 
				
			||||||
    compress
 | 
					 | 
				
			||||||
    delaycompress
 | 
					 | 
				
			||||||
    dateext
 | 
					 | 
				
			||||||
    maxage 7
 | 
					 | 
				
			||||||
    missingok
 | 
					 | 
				
			||||||
    notifempty
 | 
					 | 
				
			||||||
    create 0640 {{.UserID}} {{.GroupID}}
 | 
					 | 
				
			||||||
    postrotate
 | 
					 | 
				
			||||||
        killall -SIGHUP wings
 | 
					 | 
				
			||||||
    endscript
 | 
					 | 
				
			||||||
}`)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	err = t.Execute(f, logrotateConfig{
 | 
					 | 
				
			||||||
		Directory: viper.GetString("system.log_directory"),
 | 
					 | 
				
			||||||
		UserID: viper.GetInt("system.user.uid"),
 | 
					 | 
				
			||||||
		GroupID: viper.GetInt("system.user.gid"),
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	return errors.Wrap(err, "config: failed to write logrotate to disk")
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Returns the location of the JSON file that tracks server states.
 | 
					 | 
				
			||||||
func (sc *SystemConfiguration) GetStatesPath() string {
 | 
					 | 
				
			||||||
	return path.Join(sc.RootDirectory, "states.json")
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Returns the location of the JSON file that tracks server states.
 | 
					 | 
				
			||||||
func (sc *SystemConfiguration) GetInstallLogPath() string {
 | 
					 | 
				
			||||||
	return path.Join(sc.LogDirectory, "install/")
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ConfigureTimezone sets 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 ConfigureTimezone() error {
 | 
					 | 
				
			||||||
	tz := viper.GetString("system.timezone")
 | 
					 | 
				
			||||||
	defer viper.Set("system.timezone", tz)
 | 
					 | 
				
			||||||
	if tz == "" {
 | 
					 | 
				
			||||||
		b, err := ioutil.ReadFile("/etc/timezone")
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			if !os.IsNotExist(err) {
 | 
					 | 
				
			||||||
				return errors.WithMessage(err, "config: failed to open timezone file")
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			tz = "UTC"
 | 
					 | 
				
			||||||
			ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
 | 
					 | 
				
			||||||
			defer cancel()
 | 
					 | 
				
			||||||
			// 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")
 | 
					 | 
				
			||||||
				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")
 | 
					 | 
				
			||||||
				return nil
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			tz = string(matches[1])
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			tz = string(b)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	tz = regexp.MustCompile(`(?i)[^a-z_/]+`).ReplaceAllString(tz, "")
 | 
					 | 
				
			||||||
	_, err := time.LoadLocation(tz)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return errors.WithMessage(err, fmt.Sprintf("the supplied timezone %s is invalid", tz))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,27 +0,0 @@
 | 
				
			||||||
package config
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type ConsoleThrottles struct {
 | 
					 | 
				
			||||||
	// Whether or not the throttler is enabled for this instance.
 | 
					 | 
				
			||||||
	Enabled bool `json:"enabled" yaml:"enabled" default:"true"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// The total number of lines that can be output in a given LineResetInterval period before
 | 
					 | 
				
			||||||
	// a warning is triggered and counted against the server.
 | 
					 | 
				
			||||||
	Lines uint64 `json:"lines" yaml:"lines" default:"2000"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// The total number of throttle activations that can accumulate before a server is considered
 | 
					 | 
				
			||||||
	// to be breaching and will be stopped. This value is decremented by one every DecayInterval.
 | 
					 | 
				
			||||||
	MaximumTriggerCount uint64 `json:"maximum_trigger_count" yaml:"maximum_trigger_count" default:"5"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// The amount of time after which the number of lines processed is reset to 0. This runs in
 | 
					 | 
				
			||||||
	// a constant loop and is not affected by the current console output volumes. By default, this
 | 
					 | 
				
			||||||
	// will reset the processed line count back to 0 every 100ms.
 | 
					 | 
				
			||||||
	LineResetInterval uint64 `json:"line_reset_interval" yaml:"line_reset_interval" default:"100"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// The amount of time in milliseconds that must pass without an output warning being triggered
 | 
					 | 
				
			||||||
	// before a throttle activation is decremented.
 | 
					 | 
				
			||||||
	DecayInterval uint64 `json:"decay_interval" yaml:"decay_interval" default:"10000"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// The amount of time that a server is allowed to be stopping for before it is terminated
 | 
					 | 
				
			||||||
	// forcefully if it triggers output throttles.
 | 
					 | 
				
			||||||
	StopGracePeriod uint `json:"stop_grace_period" yaml:"stop_grace_period" default:"15"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,6 @@ import (
 | 
				
			||||||
	"github.com/docker/docker/api/types"
 | 
						"github.com/docker/docker/api/types"
 | 
				
			||||||
	"github.com/docker/docker/api/types/network"
 | 
						"github.com/docker/docker/api/types/network"
 | 
				
			||||||
	"github.com/docker/docker/client"
 | 
						"github.com/docker/docker/client"
 | 
				
			||||||
	"github.com/spf13/viper"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var _conce sync.Once
 | 
					var _conce sync.Once
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							| 
						 | 
					@ -63,8 +63,6 @@ require (
 | 
				
			||||||
	github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f
 | 
						github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f
 | 
				
			||||||
	github.com/sirupsen/logrus v1.7.0 // indirect
 | 
						github.com/sirupsen/logrus v1.7.0 // indirect
 | 
				
			||||||
	github.com/spf13/cobra v1.1.1
 | 
						github.com/spf13/cobra v1.1.1
 | 
				
			||||||
	github.com/spf13/pflag v1.0.5
 | 
					 | 
				
			||||||
	github.com/spf13/viper v1.7.1
 | 
					 | 
				
			||||||
	github.com/ugorji/go v1.2.2 // indirect
 | 
						github.com/ugorji/go v1.2.2 // indirect
 | 
				
			||||||
	github.com/ulikunitz/xz v0.5.9 // indirect
 | 
						github.com/ulikunitz/xz v0.5.9 // indirect
 | 
				
			||||||
	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
 | 
						golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										3
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								go.sum
									
									
									
									
									
								
							| 
						 | 
					@ -567,9 +567,8 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
 | 
				
			||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 | 
					github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 | 
				
			||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 | 
					github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 | 
				
			||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 | 
					github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 | 
				
			||||||
 | 
					github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
 | 
				
			||||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
 | 
					github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
 | 
				
			||||||
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
 | 
					 | 
				
			||||||
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
 | 
					 | 
				
			||||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 | 
					github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 | 
				
			||||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 | 
					github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 | 
				
			||||||
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 | 
					github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -326,7 +326,7 @@ func (ip *InstallationProcess) BeforeExecute() error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Returns the log path for the installation process.
 | 
					// Returns the log path for the installation process.
 | 
				
			||||||
func (ip *InstallationProcess) GetLogPath() string {
 | 
					func (ip *InstallationProcess) GetLogPath() string {
 | 
				
			||||||
	return filepath.Join(config.Get().System.GetInstallLogPath(), ip.Server.Id()+".log")
 | 
						return filepath.Join(config.Get().System.LogDirectory, "/install", ip.Server.Id()+".log")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Cleans up after the execution of the installation process. This grabs the logs from the
 | 
					// Cleans up after the execution of the installation process. This grabs the logs from the
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user