Initial WIP logic to handle loading configuration from the disk using viper
This commit is contained in:
parent
d45a159456
commit
9480ccdbba
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
# ignore configuration file
|
# ignore configuration file
|
||||||
/config.yml
|
/config.yml
|
||||||
|
/config*.yml
|
||||||
|
|
||||||
# Ignore Vagrant stuff
|
# Ignore Vagrant stuff
|
||||||
/.vagrant
|
/.vagrant
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
94
cmd/root.go
94
cmd/root.go
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
1
go.mod
|
@ -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
9
go.sum
|
@ -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=
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user