Initial WIP logic to handle loading configuration from the disk using viper
This commit is contained in:
@@ -6,7 +6,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -14,6 +13,8 @@ import (
|
||||
"github.com/cobaugh/osrelease"
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/gbrlsnchs/jwt/v3"
|
||||
"github.com/pterodactyl/wings/system"
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
@@ -223,98 +224,64 @@ func (c *Configuration) GetPath() string {
|
||||
return c.path
|
||||
}
|
||||
|
||||
// Ensures that the Pterodactyl core user exists on the system. This user will be the
|
||||
// owner of all data in the root data directory and is used as the user within containers.
|
||||
// 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
|
||||
// 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.
|
||||
func (c *Configuration) EnsurePterodactylUser() (*user.User, error) {
|
||||
// If files are not owned by this user there will be issues with permissions on
|
||||
// Docker mount points.
|
||||
func EnsurePterodactylUser() error {
|
||||
sysName, err := getSystemName()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
// Our way of detecting if wings is running inside of Docker.
|
||||
if sysName == "busybox" {
|
||||
uid := os.Getenv("WINGS_UID")
|
||||
if uid == "" {
|
||||
uid = "988"
|
||||
}
|
||||
|
||||
gid := os.Getenv("WINGS_GID")
|
||||
if gid == "" {
|
||||
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)
|
||||
viper.Set("system.username", system.FirstNotEmpty(os.Getenv("WINGS_USERNAME"), "pterodactyl"))
|
||||
viper.Set("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")))
|
||||
return nil
|
||||
}
|
||||
|
||||
u, err := user.Lookup(c.System.Username)
|
||||
|
||||
username := viper.GetString("system.username")
|
||||
u, err := user.Lookup(username)
|
||||
// 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.
|
||||
if err == nil {
|
||||
return u, c.setSystemUser(u)
|
||||
} else if _, ok := err.(user.UnknownUserError); !ok {
|
||||
return nil, err
|
||||
if err != nil {
|
||||
if _, ok := err.(user.UnknownUserError); !ok {
|
||||
return 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)
|
||||
|
||||
// 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 := 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
|
||||
// command, so in those cases we just modify the command a bit to work as expected.
|
||||
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
|
||||
// to the user creation process.
|
||||
if _, err := exec.Command("addgroup", "-S", c.System.Username).Output(); err != nil {
|
||||
return nil, err
|
||||
if _, err := exec.Command("addgroup", "-S", username).Output(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
split := strings.Split(command, " ")
|
||||
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
|
||||
}
|
||||
|
||||
gid, err := strconv.Atoi(u.Gid)
|
||||
u, err = user.Lookup(username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Lock()
|
||||
c.System.Username = u.Username
|
||||
c.System.User.Uid = uid
|
||||
c.System.User.Gid = gid
|
||||
c.Unlock()
|
||||
|
||||
return c.WriteToDisk()
|
||||
viper.Set("system.user.uid", system.MustInt(u.Uid))
|
||||
viper.Set("system.user.gid", system.MustInt(u.Gid))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Writes the configuration to the disk as a blocking operation by obtaining an exclusive
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"emperror.dev/errors"
|
||||
"github.com/apex/log"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Defines basic system configuration settings.
|
||||
@@ -116,11 +117,13 @@ type Transfers struct {
|
||||
DownloadLimit int `default:"0" yaml:"download_limit"`
|
||||
}
|
||||
|
||||
// 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 (sc *SystemConfiguration) ConfigureDirectories() error {
|
||||
log.WithField("path", sc.RootDirectory).Debug("ensuring root data directory exists")
|
||||
if err := os.MkdirAll(sc.RootDirectory, 0700); 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.
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
// symlink, and if so resolve to its final real path, and then update the configuration to use
|
||||
// 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) {
|
||||
return err
|
||||
}
|
||||
} else if d != sc.Data {
|
||||
sc.Data = d
|
||||
} else if d != data {
|
||||
data = d
|
||||
viper.Set("system.data", d)
|
||||
}
|
||||
|
||||
log.WithField("path", sc.Data).Debug("ensuring server data directory exists")
|
||||
if err := os.MkdirAll(sc.Data, 0700); err != nil {
|
||||
log.WithField("path", data).Debug("ensuring server data directory exists")
|
||||
if err := os.MkdirAll(data, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.WithField("path", sc.ArchiveDirectory).Debug("ensuring archive data directory exists")
|
||||
if err := os.MkdirAll(sc.ArchiveDirectory, 0700); err != nil {
|
||||
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", sc.BackupDirectory).Debug("ensuring backup data directory exists")
|
||||
if err := os.MkdirAll(sc.BackupDirectory, 0700); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// 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 (sc *SystemConfiguration) EnableLogRotation() error {
|
||||
// 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 sc.EnableLogRotate == false {
|
||||
if !viper.GetBool("system.enable_log_rotate") {
|
||||
log.Info("skipping log rotate configuration, disabled in wings config file")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -174,14 +179,11 @@ func (sc *SystemConfiguration) EnableLogRotation() error {
|
||||
} else if (err != nil && os.IsNotExist(err)) || !st.IsDir() {
|
||||
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
|
||||
} 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
|
||||
// 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.
|
||||
@@ -191,8 +193,14 @@ func (sc *SystemConfiguration) EnableLogRotation() error {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
type logrotateConfig struct {
|
||||
Directory string
|
||||
UserID int
|
||||
GroupID int
|
||||
}
|
||||
|
||||
t, err := template.New("logrotate").Parse(`
|
||||
{{.LogDirectory}}/wings.log {
|
||||
{{.Directory}}/wings.log {
|
||||
size 10M
|
||||
compress
|
||||
delaycompress
|
||||
@@ -200,17 +208,21 @@ func (sc *SystemConfiguration) EnableLogRotation() error {
|
||||
maxage 7
|
||||
missingok
|
||||
notifempty
|
||||
create 0640 {{.User.Uid}} {{.User.Gid}}
|
||||
create 0640 {{.UserID}} {{.GroupID}}
|
||||
postrotate
|
||||
killall -SIGHUP wings
|
||||
endscript
|
||||
}`)
|
||||
|
||||
if err != nil {
|
||||
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.
|
||||
@@ -223,25 +235,28 @@ func (sc *SystemConfiguration) GetInstallLogPath() string {
|
||||
return path.Join(sc.LogDirectory, "install/")
|
||||
}
|
||||
|
||||
// Configures the timezone data for the configuration if it is currently missing. If
|
||||
// a value has been set, this functionality will only run to validate that the timezone
|
||||
// being used is valid.
|
||||
func (sc *SystemConfiguration) ConfigureTimezone() error {
|
||||
if sc.Timezone == "" {
|
||||
if b, err := ioutil.ReadFile("/etc/timezone"); err != nil {
|
||||
// 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, "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
|
||||
// command fails, exit, but if it returns a value use that. If no value is returned we will
|
||||
// fall through to UTC to get Wings booted at least.
|
||||
out, err := exec.CommandContext(ctx, "timedatectl").Output()
|
||||
if err != nil {
|
||||
log.WithField("error", err).Warn("failed to execute \"timedatectl\" to determine system timezone, falling back to UTC")
|
||||
|
||||
sc.Timezone = "UTC"
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -249,20 +264,16 @@ func (sc *SystemConfiguration) ConfigureTimezone() error {
|
||||
matches := r.FindSubmatch(out)
|
||||
if len(matches) != 2 || string(matches[1]) == "" {
|
||||
log.Warn("failed to parse timezone from \"timedatectl\" output, falling back to UTC")
|
||||
|
||||
sc.Timezone = "UTC"
|
||||
return nil
|
||||
}
|
||||
|
||||
sc.Timezone = string(matches[1])
|
||||
tz = string(matches[1])
|
||||
} 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", sc.Timezone))
|
||||
return errors.WithMessage(err, fmt.Sprintf("the supplied timezone %s is invalid", tz))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user