Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
77 changes: 54 additions & 23 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@
"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 @@
// 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 @@
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 defaultc
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 @@
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 Down Expand Up @@ -648,9 +646,42 @@
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 @@
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 @@
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
13 changes: 12 additions & 1 deletion server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"net/http"
"os"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -166,7 +167,6 @@
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,6 +312,17 @@
return err
}

if config.Get().System.MachineID.Enable {
// 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())
machineID := append([]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 s.Environment.Create()
}

Expand Down
Loading