Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
log.WithField("error", err).Fatal("failed to create pelican system user")
return
}
if err := config.ConfigurePasswd(); err != nil {
log.WithField("error", err).Fatal("failed to create passwd files for pelican")
}
log.WithFields(log.Fields{
"username": config.Get().System.Username,
"uid": config.Get().System.User.Uid,
Expand Down
82 changes: 59 additions & 23 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@ import (
"text/template"
"time"

"github.com/gbrlsnchs/jwt/v3"

"emperror.dev/errors"
"github.com/acobaugh/osrelease"
"github.com/apex/log"
"github.com/creasty/defaults"
"github.com/gbrlsnchs/jwt/v3"
"golang.org/x/sys/unix"
"gopkg.in/yaml.v2"

Expand Down Expand Up @@ -129,9 +128,9 @@ type RemoteQueryConfiguration struct {
// be less likely to cause performance issues on the Panel.
BootServersPerPage int `default:"50" yaml:"boot_servers_per_page"`

//When using services like Cloudflare Access to manage access to
//a specific system via an external authentication system,
//it is possible to add special headers to bypass authentication.
//When using services like Cloudflare Access to manage access to
//a specific system via an external authentication system,
//it is possible to add special headers to bypass authentication.
//The mentioned headers can be appended to queries sent from Wings to the panel.
CustomHeaders map[string]string `yaml:"custom_headers"`
}
Expand Down Expand Up @@ -187,11 +186,23 @@ type SystemConfiguration struct {
Uid int `yaml:"uid"`
Gid int `yaml:"gid"`

// Passwd controls weather a passwd file is mounted in the container
// at /etc/passwd to resolve missing user issues
Passwd bool `json:"mount_passwd" yaml:"mount_passwd" default:"true"`
PasswdFile string `json:"passwd_file" yaml:"passwd_file" default:"/etc/pelican/passwd"`
} `yaml:"user"`
// Passwd controls weather a passwd and group file is mounted in the container
// at /etc/passwd to resolve missing user/group issues inside the container
Passwd struct {
Enable bool `json:"enable" yaml:"enable" default:"true"`
Directory string `json:"directory" yaml:"directory" default:"/etc/pelican"`
} `json:"passwd" yaml:"passwd"`
} `json:"user" yaml:"user"`

// MachineID manages the mounting of a 'machine-id' file for containers as required for
// some game servers. I.E. Hytale
MachineID struct {
// Enable controls if the machine-id file is generated and mounted into the server container
// This is enabled by default
Enable bool `json:"enable" yaml:"enable" default:"true"`
// FilePath is the full path to the machine-id file that will be generated and mounted
Directory string `json:"directory" yaml:"directory" default:"/etc/pelican/machine-id"`
} `json:"machine_id" yaml:"machine_id"`

// The amount of time in seconds that can elapse before a server's disk space calculation is
// considered stale and a re-check should occur. DANGER: setting this value too low can seriously
Expand Down Expand Up @@ -604,19 +615,6 @@ func ConfigureDirectories() error {
return err
}

log.WithField("filepath", _config.System.User.PasswdFile).Debug("ensuring passwd file exists")
if passwd, err := os.Create(_config.System.User.PasswdFile); err != nil {
return err
} else {
// the WriteFile method returns an error if unsuccessful
err := os.WriteFile(passwd.Name(), []byte(fmt.Sprintf("container:x:%d:%d::/home/container:/usr/sbin/nologin", _config.System.User.Uid, _config.System.User.Gid)), 0644)
// handle this error
if err != nil {
// print it out
fmt.Println(err)
}
}

// There are a non-trivial number of users out there whose data directories are actually a
// symlink to another location on the disk. If we do not resolve that final destination at this
// point things will appear to work, but endless errors will be encountered when we try to
Expand All @@ -638,6 +636,11 @@ func ConfigureDirectories() error {
return err
}

log.WithField("path", _config.System.TmpDirectory).Debug("ensuring temporary data directory exists")
if err := os.MkdirAll(_config.System.TmpDirectory, 0o700); err != nil {
return err
}

log.WithField("path", _config.System.ArchiveDirectory).Debug("ensuring archive data directory exists")
if err := os.MkdirAll(_config.System.ArchiveDirectory, 0o700); err != nil {
return err
Expand All @@ -648,9 +651,42 @@ func ConfigureDirectories() error {
return err
}

log.WithField("path", _config.System.User.Passwd.Directory).Debug("ensuring passwd directory exists")
if err := os.MkdirAll(_config.System.User.Passwd.Directory, 0o700); err != nil {
return err
}

log.WithField("path", _config.System.MachineID.Directory).Debug("ensuring machine-id directory exists")
if err := os.MkdirAll(_config.System.MachineID.Directory, 0o700); err != nil {
return err
}
return nil
}

// ConfigurePasswd generates the passwd and group files to be used by
// this looks cleaner than the previous way and is similar to pterodactyl
func ConfigurePasswd() (err error) {
if !_config.System.User.Passwd.Enable {
return
}
log.WithField("filepath", filepath.Join(_config.System.User.Passwd.Directory, "passwd")).
Debug("ensuring passwd file exists")
if err = os.WriteFile(filepath.Join(_config.System.User.Passwd.Directory, "passwd"),
[]byte(fmt.Sprintf("container:x:%d:%d::/home/container:/usr/sbin/nologin",
_config.System.User.Uid, _config.System.User.Gid)), 0644); err != nil {
return fmt.Errorf("could not write passwd file: %w", err)
}

log.WithField("filepath", filepath.Join(_config.System.User.Passwd.Directory, "group")).
Debug("ensuring group file exists")
if err = os.WriteFile(filepath.Join(_config.System.User.Passwd.Directory, "group"),
[]byte(fmt.Sprintf("container:x:%d:container",
_config.System.User.Gid)), 0644); err != nil {
return fmt.Errorf("could not write group file: %w", err)
}
return
}

// 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
Expand Down
12 changes: 10 additions & 2 deletions router/router_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ func getServerInstallLogs(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": output})
}


// Handles a request to control the power state of a server. If the action being passed
// through is invalid a 404 is returned. Otherwise, a HTTP/202 Accepted response is returned
// and the actual power action is run asynchronously so that we don't have to block the
Expand Down Expand Up @@ -281,7 +280,16 @@ func deleteServer(c *gin.Context) {
p := fs.Path()
_ = fs.UnixFS().Close()
if err := os.RemoveAll(p); err != nil {
log.WithFields(log.Fields{"path": p, "error": err}).Warn("failed to remove server files during deletion process")
log.WithFields(log.Fields{"path": p, "error": err}).
Warn("failed to remove server files during deletion process")
}
}(s)

// remove hanging machine-id file for the server when removing
go func(s *server.Server) {
if err := os.Remove(filepath.Join(config.Get().System.MachineID.Directory, s.ID())); err != nil {
log.WithFields(log.Fields{"server_id": s.ID(), "error": err}).
Warn("failed to remove machine-id file for server")
}
}(s)

Expand Down
23 changes: 20 additions & 3 deletions server/mounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,32 @@ func (s *Server) Mounts() []environment.Mount {
},
}

if config.Get().System.User.Passwd {
if config.Get().System.User.Passwd.Enable {
passwdMount := environment.Mount{
Default: true,
Target: "/etc/passwd",
Source: config.Get().System.User.PasswdFile,
Source: filepath.Join(config.Get().System.User.Passwd.Directory, "passwd"),
ReadOnly: true,
}

m = append(m, passwdMount)

groupMount := environment.Mount{
Target: "/etc/group",
Source: filepath.Join(config.Get().System.User.Passwd.Directory, "group"),
ReadOnly: true,
}

m = append(m, groupMount)
}

if config.Get().System.MachineID.Enable {
machineIDMount := environment.Mount{
Target: "/etc/machine-id",
Source: filepath.Join(config.Get().System.MachineID.Directory, s.ID()),
ReadOnly: true,
}

m = append(m, machineIDMount)
}
// Also include any of this server's custom mounts when returning them.
return append(m, s.customMounts()...)
Expand Down
7 changes: 7 additions & 0 deletions server/power.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,13 @@ func (s *Server) onBeforeStart() error {
}
}

// create the machine-id file on start in case it's missing
if config.Get().System.MachineID.Enable {
if err := s.CreateMachineID(); err != nil {
return err
}
}

s.Log().Info("completed server preflight, starting boot process...")
return nil
}
25 changes: 24 additions & 1 deletion server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"
"os"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -166,7 +167,6 @@ func DetermineServerTimezone(envvars map[string]interface{}, defaultTimezone str
return defaultTimezone
}


// parseInvocation parses the start command in the same way we already do in the entrypoint
// We can use this to set the container command with all variables replaced.
func parseInvocation(invocation string, envvars map[string]interface{}, memory int64, port int, ip string) (parsed string) {
Expand Down Expand Up @@ -312,9 +312,32 @@ func (s *Server) CreateEnvironment() error {
return err
}

// create the machine-id file on install
if config.Get().System.MachineID.Enable {
if err := s.CreateMachineID(); err != nil {
return err
}
}

return s.Environment.Create()
}

// CreateMachineID generates the machine-id file for the server
func (s *Server) CreateMachineID() error {
// Hytale wants a machine-id in order to encrypt tokens for the server. So
// write a machine-id file for the server that contains the server's UUID
// without any dashes.
p := filepath.Join(config.Get().System.MachineID.Directory, s.ID())
s.Log().WithFields(log.Fields{
"path": p}).Debug("creating machine-id file")
machineID := []byte(strings.ReplaceAll(s.ID(), "-", ""))
if err := os.WriteFile(p, machineID, 0o644); err != nil {
return fmt.Errorf("failed to write machine-id (at '%s') for server '%s': %w", p, s.ID(), err)
}

return nil
}

// Checks if the server is marked as being suspended or not on the system.
func (s *Server) IsSuspended() bool {
return s.Config().Suspended
Expand Down
Loading