Initial WIP logic to handle loading configuration from the disk using viper

This commit is contained in:
Dane Everitt 2021-01-12 21:14:57 -08:00
parent d45a159456
commit 9480ccdbba
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
9 changed files with 213 additions and 287 deletions

1
.gitignore vendored
View File

@ -22,6 +22,7 @@
# ignore configuration file # ignore configuration file
/config.yml /config.yml
/config*.yml
# Ignore Vagrant stuff # Ignore Vagrant stuff
/.vagrant /.vagrant

View File

@ -1,61 +0,0 @@
package cmd
import (
"os"
"path/filepath"
"github.com/pterodactyl/wings/config"
)
// We've gone through a couple of iterations of where the configuration is stored. This
// helpful little function will look through the three areas it might have ended up, and
// return it.
//
// We only run this if the configuration flag for the instance is not actually passed in
// via the command line. Once found, the configuration is moved into the expected default
// location. Only errors are returned from this function, you can safely assume that after
// running this the configuration can be found in the correct default location.
func RelocateConfiguration() error {
var match string
check := []string{
config.DefaultLocation,
"/var/lib/pterodactyl/config.yml",
"/etc/wings/config.yml",
}
// Loop over all of the configuration paths, and return which one we found, if
// any.
for _, p := range check {
if s, err := os.Stat(p); err != nil {
if !os.IsNotExist(err) {
return err
}
} else if !s.IsDir() {
match = p
break
}
}
// Just return a generic not exist error at this point if we didn't have a match, this
// will allow the caller to handle displaying a more friendly error to the user. If we
// did match in the default location, go ahead and return successfully.
if match == "" {
return os.ErrNotExist
} else if match == config.DefaultLocation {
return nil
}
// The rest of this function simply creates the new default location and moves the
// old configuration file over to the new location, then sets the permissions on the
// file correctly so that only the user running this process can read it.
p, _ := filepath.Split(config.DefaultLocation)
if err := os.MkdirAll(p, 0755); err != nil {
return err
}
if err := os.Rename(match, config.DefaultLocation); err != nil {
return err
}
return os.Chmod(config.DefaultLocation, 0600)
}

View File

@ -26,12 +26,13 @@ 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 = config.DefaultLocation configPath = ""
debug = false debug = false
) )
@ -64,7 +65,9 @@ func Execute() {
} }
func init() { func init() {
rootCommand.PersistentFlags().StringVar(&configPath, "config", config.DefaultLocation, "set the location for the configuration file") cobra.OnInitialize(initConfig, initLogging)
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.
@ -119,17 +122,6 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
defer profile.Start(profile.BlockProfile).Stop() defer profile.Start(profile.BlockProfile).Stop()
} }
// Only attempt configuration file relocation if a custom location has not
// been specified in the command startup.
if configPath == config.DefaultLocation {
if err := RelocateConfiguration(); err != nil {
if errors.Is(err, os.ErrNotExist) {
exitWithConfigurationNotice()
}
panic(err)
}
}
c, err := readConfiguration() c, err := readConfiguration()
if err != nil { if err != nil {
panic(err) panic(err)
@ -140,14 +132,8 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
} }
printLogo() printLogo()
if err := configureLogging(c.System.LogDirectory, c.Debug); err != nil { log.WithField("path", viper.ConfigFileUsed()).Info("loading configuration from file")
panic(err)
}
log.WithField("path", c.GetPath()).Info("loading configuration from path")
if c.Debug {
log.Debug("running in debug mode") log.Debug("running in debug mode")
}
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")
@ -158,47 +144,42 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
config.Set(c) config.Set(c)
config.SetDebugViaFlag(debug) config.SetDebugViaFlag(debug)
if err := config.ConfigureTimezone(); err != nil {
if err := c.System.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")
return
} }
log.WithField("timezone", c.System.Timezone).Info("configured wings with system timezone") log.WithField("timezone", c.System.Timezone).Info("configured wings with system timezone")
if err := c.System.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 := c.System.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", c.System.Username).Info("checking for pterodactyl system user")
if su, err := c.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")
return
} else {
log.WithFields(log.Fields{
"username": su.Username,
"uid": su.Uid,
"gid": su.Gid,
}).Info("configured system user successfully")
} }
log.WithFields(log.Fields{
"username": viper.GetString("system.username"),
"uid": viper.GetInt("system.user.uid"),
"gid": viper.GetInt("system.user.gid"),
}).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 return
} }
if err := environment.ConfigureDocker(&c.Docker); 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 return
} }
if err := c.WriteToDisk(); err != nil { if err := viper.WriteConfig(); err != nil {
log.WithField("error", err).Error("failed to save configuration to disk") log.WithField("error", err).Error("failed to save configuration to disk")
} }
@ -379,28 +360,44 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
} }
} }
func initConfig() {
if configPath != "" {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath("/etc/pterodactyl")
viper.AddConfigPath("$HOME/.pterodactyl")
viper.AddConfigPath(".")
} else {
viper.SetConfigFile(configPath)
}
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(*viper.ConfigFileNotFoundError); ok {
exitWithConfigurationNotice()
}
log2.Fatalf("cmd/root: failed to read configuration: %s", err)
}
}
// 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 configureLogging(logDir string, debug bool) error { func initLogging() {
if err := os.MkdirAll(path.Join(logDir, "/install"), 0700); err != nil { dir := viper.GetString("system.log_directory")
return err if err := os.MkdirAll(path.Join(dir, "/install"), 0700); err != nil {
log2.Fatalf("cmd/root: failed to create install directory path: %s", err)
} }
p := filepath.Join(dir, "/wings.log")
p := filepath.Join(logDir, "/wings.log")
w, err := logrotate.NewFile(p) w, err := logrotate.NewFile(p)
if err != nil { if err != nil {
return err log2.Fatalf("cmd/root: failed to create wings log: %s", err)
} }
log.SetLevel(log.InfoLevel) log.SetLevel(log.InfoLevel)
if debug { if viper.GetBool("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")
return nil
} }
// Prints the wings logo, nothing special here! // Prints the wings logo, nothing special here!
@ -429,11 +426,8 @@ func exitWithConfigurationNotice() {
[_red_][white][bold]Error: Configuration File Not Found[reset] [_red_][white][bold]Error: Configuration File Not Found[reset]
Wings was not able to locate your configuration file, and therefore is not Wings was not able to locate your configuration file, and therefore is not
able to complete its boot process. able to complete its boot process. Please ensure you have copied your instance
configuration file into the default location below.
Please ensure you have copied your instance configuration file into
the default location, or have provided the --config flag to use a
custom location.
Default Location: /etc/pterodactyl/config.yml Default Location: /etc/pterodactyl/config.yml

View File

@ -6,7 +6,6 @@ import (
"os" "os"
"os/exec" "os/exec"
"os/user" "os/user"
"strconv"
"strings" "strings"
"sync" "sync"
@ -14,6 +13,8 @@ import (
"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/spf13/viper"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@ -223,98 +224,64 @@ func (c *Configuration) GetPath() string {
return c.path return c.path
} }
// Ensures that the Pterodactyl core user exists on the system. This user will be the // EnsurePterodactylUser ensures that the Pterodactyl core user exists on the
// owner of all data in the root data directory and is used as the user within containers. // system. This user will be the owner of all data in the root data directory
// and is used as the user within containers.
// //
// If files are not owned by this user there will be issues with permissions on Docker // If files are not owned by this user there will be issues with permissions on
// mount points. // Docker mount points.
func (c *Configuration) EnsurePterodactylUser() (*user.User, error) { func EnsurePterodactylUser() error {
sysName, err := getSystemName() sysName, err := getSystemName()
if err != nil { if err != nil {
return nil, err return err
} }
// 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" {
uid := os.Getenv("WINGS_UID") viper.Set("system.username", system.FirstNotEmpty(os.Getenv("WINGS_USERNAME"), "pterodactyl"))
if uid == "" { viper.Set("system.user.uid", system.MustInt(system.FirstNotEmpty(os.Getenv("WINGS_UID"), "988")))
uid = "988" viper.Set("system.user.gid", system.MustInt(system.FirstNotEmpty(os.Getenv("WINGS_GID"), "988")))
return nil
} }
gid := os.Getenv("WINGS_GID") username := viper.GetString("system.username")
if gid == "" { u, err := user.Lookup(username)
gid = "988"
}
username := os.Getenv("WINGS_USERNAME")
if username == "" {
username = "pterodactyl"
}
u := &user.User{
Uid: uid,
Gid: gid,
Username: username,
}
return u, c.setSystemUser(u)
}
u, err := user.Lookup(c.System.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 {
return u, c.setSystemUser(u) if _, ok := err.(user.UnknownUserError); !ok {
} else if _, ok := err.(user.UnknownUserError); !ok { return err
return nil, err }
} else {
viper.Set("system.user.uid", system.MustInt(u.Uid))
viper.Set("system.user.gid", system.MustInt(u.Gid))
return nil
} }
command := fmt.Sprintf("useradd --system --no-create-home --shell /usr/sbin/nologin %s", c.System.Username) command := fmt.Sprintf("useradd --system --no-create-home --shell /usr/sbin/nologin %s", 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 // command, so in those cases we just modify the command a bit to work as expected.
// 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", c.System.Username) command = fmt.Sprintf("adduser -S -D -H -G %[1]s -s /sbin/nologin %[1]s", 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", c.System.Username).Output(); err != nil { if _, err := exec.Command("addgroup", "-S", username).Output(); err != nil {
return nil, err return err
} }
} }
split := strings.Split(command, " ") split := strings.Split(command, " ")
if _, err := exec.Command(split[0], split[1:]...).Output(); err != nil { if _, err := exec.Command(split[0], split[1:]...).Output(); err != nil {
return nil, err
}
if u, err := user.Lookup(c.System.Username); err != nil {
return nil, err
} else {
return u, c.setSystemUser(u)
}
}
// Set the system user into the configuration and then write it to the disk so that
// it is persisted on boot.
func (c *Configuration) setSystemUser(u *user.User) error {
uid, err := strconv.Atoi(u.Uid)
if err != nil {
return err return err
} }
gid, err := strconv.Atoi(u.Gid) u, err = user.Lookup(username)
if err != nil { if err != nil {
return err return err
} }
viper.Set("system.user.uid", system.MustInt(u.Uid))
c.Lock() viper.Set("system.user.gid", system.MustInt(u.Gid))
c.System.Username = u.Username return nil
c.System.User.Uid = uid
c.System.User.Gid = gid
c.Unlock()
return c.WriteToDisk()
} }
// Writes the configuration to the disk as a blocking operation by obtaining an exclusive // Writes the configuration to the disk as a blocking operation by obtaining an exclusive

View File

@ -14,6 +14,7 @@ import (
"emperror.dev/errors" "emperror.dev/errors"
"github.com/apex/log" "github.com/apex/log"
"github.com/spf13/viper"
) )
// Defines basic system configuration settings. // Defines basic system configuration settings.
@ -116,11 +117,13 @@ type Transfers struct {
DownloadLimit int `default:"0" yaml:"download_limit"` DownloadLimit int `default:"0" yaml:"download_limit"`
} }
// Ensures that all of the system directories exist on the system. These directories are // ConfigureDirectories ensures that all of the system directories exist on the
// created so that only the owner can read the data, and no other users. // system. These directories are created so that only the owner can read the data,
func (sc *SystemConfiguration) ConfigureDirectories() error { // and no other users.
log.WithField("path", sc.RootDirectory).Debug("ensuring root data directory exists") func ConfigureDirectories() error {
if err := os.MkdirAll(sc.RootDirectory, 0700); err != nil { 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 return err
} }
@ -132,40 +135,42 @@ func (sc *SystemConfiguration) ConfigureDirectories() error {
// For the sake of automating away as much of this as possible, see if the data directory is a // 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 // symlink, and if so resolve to its final real path, and then update the configuration to use
// that. // that.
if d, err := filepath.EvalSymlinks(sc.Data); err != nil { data := viper.GetString("system.data")
if d, err := filepath.EvalSymlinks(data); err != nil {
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
return err return err
} }
} else if d != sc.Data { } else if d != data {
sc.Data = d data = d
viper.Set("system.data", d)
} }
log.WithField("path", sc.Data).Debug("ensuring server data directory exists") log.WithField("path", data).Debug("ensuring server data directory exists")
if err := os.MkdirAll(sc.Data, 0700); err != nil { if err := os.MkdirAll(data, 0700); err != nil {
return err return err
} }
log.WithField("path", sc.ArchiveDirectory).Debug("ensuring archive data directory exists") log.WithField("path", viper.GetString("system.archive_directory")).Debug("ensuring archive data directory exists")
if err := os.MkdirAll(sc.ArchiveDirectory, 0700); err != nil { if err := os.MkdirAll(viper.GetString("system.archive_directory"), 0700); err != nil {
return err return err
} }
log.WithField("path", sc.BackupDirectory).Debug("ensuring backup data directory exists") log.WithField("path", viper.GetString("system.backup_directory")).Debug("ensuring backup data directory exists")
if err := os.MkdirAll(sc.BackupDirectory, 0700); err != nil { if err := os.MkdirAll(viper.GetString("system.backup_directory"), 0700); err != nil {
return err return err
} }
return nil return nil
} }
// Writes a logrotate file for wings to the system logrotate configuration directory if one // EnableLogRotation writes a logrotate file for wings to the system logrotate
// exists and a logrotate file is not found. This allows us to basically automate away the log // configuration directory if one exists and a logrotate file is not found. This
// rotation for most installs, but also enable users to make modifications on their own. // allows us to basically automate away the log rotation for most installs, but
func (sc *SystemConfiguration) EnableLogRotation() error { // also enable users to make modifications on their own.
func EnableLogRotation() error {
// Do nothing if not enabled. // Do nothing if not enabled.
if sc.EnableLogRotate == false { if !viper.GetBool("system.enable_log_rotate") {
log.Info("skipping log rotate configuration, disabled in wings config file") log.Info("skipping log rotate configuration, disabled in wings config file")
return nil return nil
} }
@ -174,14 +179,11 @@ func (sc *SystemConfiguration) EnableLogRotation() error {
} else if (err != nil && os.IsNotExist(err)) || !st.IsDir() { } else if (err != nil && os.IsNotExist(err)) || !st.IsDir() {
return nil return nil
} }
if _, err := os.Stat("/etc/logrotate.d/wings"); err == nil || !os.IsNotExist(err) {
if _, err := os.Stat("/etc/logrotate.d/wings"); err != nil && !os.IsNotExist(err) {
return err return err
} else if err == nil {
return nil
} }
log.Info("no log rotation configuration found, system is configured to support it, adding file now") 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 // 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 // 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. // it so files can be rotated easily.
@ -191,8 +193,14 @@ func (sc *SystemConfiguration) EnableLogRotation() error {
} }
defer f.Close() defer f.Close()
type logrotateConfig struct {
Directory string
UserID int
GroupID int
}
t, err := template.New("logrotate").Parse(` t, err := template.New("logrotate").Parse(`
{{.LogDirectory}}/wings.log { {{.Directory}}/wings.log {
size 10M size 10M
compress compress
delaycompress delaycompress
@ -200,17 +208,21 @@ func (sc *SystemConfiguration) EnableLogRotation() error {
maxage 7 maxage 7
missingok missingok
notifempty notifempty
create 0640 {{.User.Uid}} {{.User.Gid}} create 0640 {{.UserID}} {{.GroupID}}
postrotate postrotate
killall -SIGHUP wings killall -SIGHUP wings
endscript endscript
}`) }`)
if err != nil { if err != nil {
return err return err
} }
return errors.WithMessage(t.Execute(f, sc), "failed to write logrotate file to disk") 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. // Returns the location of the JSON file that tracks server states.
@ -223,25 +235,28 @@ func (sc *SystemConfiguration) GetInstallLogPath() string {
return path.Join(sc.LogDirectory, "install/") return path.Join(sc.LogDirectory, "install/")
} }
// Configures the timezone data for the configuration if it is currently missing. If // ConfigureTimezone sets the timezone data for the configuration if it is
// a value has been set, this functionality will only run to validate that the timezone // currently missing. If a value has been set, this functionality will only run
// being used is valid. // to validate that the timezone being used is valid.
func (sc *SystemConfiguration) ConfigureTimezone() error { func ConfigureTimezone() error {
if sc.Timezone == "" { tz := viper.GetString("system.timezone")
if b, err := ioutil.ReadFile("/etc/timezone"); err != nil { defer viper.Set("system.timezone", tz)
if tz == "" {
b, err := ioutil.ReadFile("/etc/timezone")
if err != nil {
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
return errors.WithMessage(err, "failed to open /etc/timezone for automatic server timezone calibration") return errors.WithMessage(err, "config: failed to open timezone file")
} }
ctx, _ := context.WithTimeout(context.Background(), time.Second*5) 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 // 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 // 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. // fall through to UTC to get Wings booted at least.
out, err := exec.CommandContext(ctx, "timedatectl").Output() out, err := exec.CommandContext(ctx, "timedatectl").Output()
if err != nil { if err != nil {
log.WithField("error", err).Warn("failed to execute \"timedatectl\" to determine system timezone, falling back to UTC") log.WithField("error", err).Warn("failed to execute \"timedatectl\" to determine system timezone, falling back to UTC")
sc.Timezone = "UTC"
return nil return nil
} }
@ -249,20 +264,16 @@ func (sc *SystemConfiguration) ConfigureTimezone() error {
matches := r.FindSubmatch(out) matches := r.FindSubmatch(out)
if len(matches) != 2 || string(matches[1]) == "" { if len(matches) != 2 || string(matches[1]) == "" {
log.Warn("failed to parse timezone from \"timedatectl\" output, falling back to UTC") log.Warn("failed to parse timezone from \"timedatectl\" output, falling back to UTC")
sc.Timezone = "UTC"
return nil return nil
} }
tz = string(matches[1])
sc.Timezone = string(matches[1])
} else { } else {
sc.Timezone = string(b) tz = string(b)
} }
} }
sc.Timezone = regexp.MustCompile(`(?i)[^a-z_/]+`).ReplaceAllString(sc.Timezone, "") tz = regexp.MustCompile(`(?i)[^a-z_/]+`).ReplaceAllString(tz, "")
_, err := time.LoadLocation(tz)
_, err := time.LoadLocation(sc.Timezone) return errors.WithMessage(err, fmt.Sprintf("the supplied timezone %s is invalid", tz))
return errors.WithMessage(err, fmt.Sprintf("the supplied timezone %s is invalid", sc.Timezone))
} }

View File

@ -6,112 +6,95 @@ import (
"sync" "sync"
"github.com/apex/log" "github.com/apex/log"
"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/pterodactyl/wings/config" "github.com/spf13/viper"
) )
var _cmu sync.Mutex var _conce sync.Once
var _client *client.Client var _client *client.Client
// Return a Docker client to be used throughout the codebase. Once a client has been created it // DockerClient returns a Docker client to be used throughout the codebase. Once
// will be returned for all subsequent calls to this function. // a client has been created it will be returned for all subsequent calls to this
// function.
func DockerClient() (*client.Client, error) { func DockerClient() (*client.Client, error) {
_cmu.Lock() var err error
defer _cmu.Unlock() _conce.Do(func() {
_client, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if _client != nil { })
return _client, nil
}
_client, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
return _client, err return _client, err
} }
// Configures the required network for the docker environment. // ConfigureDocker configures the required network for the docker environment.
func ConfigureDocker(c *config.DockerConfiguration) error { func ConfigureDocker(ctx context.Context) error {
// Ensure the required docker network exists on the system. // Ensure the required docker network exists on the system.
cli, err := DockerClient() cli, err := DockerClient()
if err != nil { if err != nil {
return err return err
} }
resource, err := cli.NetworkInspect(context.Background(), c.Network.Name, types.NetworkInspectOptions{}) nw := viper.Sub("docker.network")
if err != nil && client.IsErrNotFound(err) { resource, err := cli.NetworkInspect(ctx, nw.GetString("name"), types.NetworkInspectOptions{})
if err != nil {
if client.IsErrNotFound(err) {
log.Info("creating missing pterodactyl0 interface, this could take a few seconds...") log.Info("creating missing pterodactyl0 interface, this could take a few seconds...")
return createDockerNetwork(cli, c) if err := createDockerNetwork(ctx, cli); err != nil {
} else if err != nil { return err
log.WithField("error", err).Fatal("failed to create required docker network for containers") }
}
return err
} else {
nw.Set("driver", resource.Driver)
} }
switch resource.Driver { switch nw.GetString("driver") {
case "host": case "host":
c.Network.Interface = "127.0.0.1" nw.Set("interface", "127.0.0.1")
c.Network.ISPN = false nw.Set("ispn", false)
return nil
case "overlay": case "overlay":
fallthrough
case "weavemesh": case "weavemesh":
c.Network.Interface = "" nw.Set("interface", "")
c.Network.ISPN = true nw.Set("ispn", true)
return nil
default: default:
c.Network.ISPN = false nw.Set("ispn", false)
} }
return nil return nil
} }
// Creates a new network on the machine if one does not exist already. // Creates a new network on the machine if one does not exist already.
func createDockerNetwork(cli *client.Client, c *config.DockerConfiguration) error { func createDockerNetwork(ctx context.Context, cli *client.Client) error {
_, err := cli.NetworkCreate(context.Background(), c.Network.Name, types.NetworkCreate{ nw := viper.Sub("docker.network")
Driver: c.Network.Driver, _, err := cli.NetworkCreate(ctx, nw.GetString("name"), types.NetworkCreate{
Driver: nw.GetString("driver"),
EnableIPv6: true, EnableIPv6: true,
Internal: c.Network.IsInternal, Internal: nw.GetBool("is_internal"),
IPAM: &network.IPAM{ IPAM: &network.IPAM{
Config: []network.IPAMConfig{ Config: []network.IPAMConfig{
{ {
Subnet: c.Network.Interfaces.V4.Subnet, Subnet: nw.GetString("interfaces.v4.subnet"),
Gateway: c.Network.Interfaces.V4.Gateway, Gateway: nw.GetString("interfaces.v4.gateway"),
}, },
{ {
Subnet: c.Network.Interfaces.V6.Subnet, Subnet: nw.GetString("interfaces.v6.subnet"),
Gateway: c.Network.Interfaces.V6.Gateway, Gateway: nw.GetString("interfaces.v6.gateway"),
}, },
}, },
}, },
Options: map[string]string{ Options: map[string]string{
"encryption": "false", "encryption": "false",
"com.docker.network.bridge.default_bridge": "false", "com.docker.network.bridge.default_bridge": "false",
"com.docker.network.bridge.enable_icc": strconv.FormatBool(c.Network.EnableICC), "com.docker.network.bridge.enable_icc": strconv.FormatBool(nw.GetBool("enable_icc")),
"com.docker.network.bridge.enable_ip_masquerade": "true", "com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0", "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "pterodactyl0", "com.docker.network.bridge.name": "pterodactyl0",
"com.docker.network.driver.mtu": "1500", "com.docker.network.driver.mtu": "1500",
}, },
}) })
driver := nw.GetString("driver")
if err != nil { if driver != "host" && driver != "overlay" && driver != "weavemesh" {
nw.Set("interface", nw.GetString("interfaces.v4.gateway"))
}
return err return err
}
switch c.Network.Driver {
case "host":
c.Network.Interface = "127.0.0.1"
c.Network.ISPN = false
break
case "overlay":
case "weavemesh":
c.Network.Interface = ""
c.Network.ISPN = true
break
default:
c.Network.Interface = c.Network.Interfaces.V4.Gateway
c.Network.ISPN = false
break
}
return nil
} }

1
go.mod
View File

@ -64,6 +64,7 @@ require (
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/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

9
go.sum
View File

@ -285,6 +285,7 @@ github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
@ -401,6 +402,7 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
@ -454,6 +456,7 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
@ -550,11 +553,14 @@ github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4S
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
@ -562,6 +568,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
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/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=
@ -575,6 +583,7 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tj/assert v0.0.0-20171129193455-018094318fb0 h1:Rw8kxzWo1mr6FSaYXjQELRe88y2KdfynXdnK72rdjtA= github.com/tj/assert v0.0.0-20171129193455-018094318fb0 h1:Rw8kxzWo1mr6FSaYXjQELRe88y2KdfynXdnK72rdjtA=
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=

View File

@ -7,14 +7,35 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"emperror.dev/errors"
) )
var cr = []byte(" \r") var cr = []byte(" \r")
var crr = []byte("\r\n") var crr = []byte("\r\n")
// FirstNotEmpty returns the first string passed in that is not an empty value.
func FirstNotEmpty(v ...string) string {
for _, val := range v {
if val != "" {
return val
}
}
return ""
}
func MustInt(v string) int {
i, err := strconv.Atoi(v)
if err != nil {
panic(errors.Wrap(err, "system/utils: could not parse int"))
}
return i
}
func ScanReader(r io.Reader, callback func(line string)) error { func ScanReader(r io.Reader, callback func(line string)) error {
br := bufio.NewReader(r) br := bufio.NewReader(r)
// Avoid constantly re-allocating memory when we're flooding lines through this // Avoid constantly re-allocating memory when we're flooding lines through this