From 3120b89cd69a61903daaae6cab631145cf5e4619 Mon Sep 17 00:00:00 2001 From: "Michael (Parker) Parker" Date: Wed, 14 Jan 2026 00:26:01 -0500 Subject: [PATCH 1/3] Implement pterodactyl 292 changes Add the same change as https://github.com/pterodactyl/wings/pull/292 This adds configuration for the `machone-id` file that is required by hytale Creates and manages machine-id files on a per-server basis Adds code to remove machine-id files when a server is deleted as well. It also adds a group file for use along with the passwd file Updated config for passwd Updated mounts to not set default except for the the correct default. --- cmd/root.go | 3 ++ config/config.go | 77 +++++++++++++++++++++++++++++------------ router/router_server.go | 12 +++++-- server/mounts.go | 23 ++++++++++-- server/server.go | 13 ++++++- 5 files changed, 99 insertions(+), 29 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 8acc73bd..c615b0b4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -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, diff --git a/config/config.go b/config/config.go index 04e8c04e..674757e7 100644 --- a/config/config.go +++ b/config/config.go @@ -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" @@ -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"` } @@ -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 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 @@ -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 @@ -648,9 +646,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 diff --git a/router/router_server.go b/router/router_server.go index 47c4eb46..e49bbda2 100644 --- a/router/router_server.go +++ b/router/router_server.go @@ -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 @@ -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) diff --git a/server/mounts.go b/server/mounts.go index d9f72e21..a0a53cc7 100644 --- a/server/mounts.go +++ b/server/mounts.go @@ -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()...) diff --git a/server/server.go b/server/server.go index 8e0b9518..9f4aa7ecf 100644 --- a/server/server.go +++ b/server/server.go @@ -6,6 +6,7 @@ import ( "net/http" "os" "path" + "path/filepath" "regexp" "strconv" "strings" @@ -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) { @@ -312,6 +312,17 @@ func (s *Server) CreateEnvironment() error { 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() } From f15747058be85a9b6839ba4f4020b1ea1dc95109 Mon Sep 17 00:00:00 2001 From: "Michael (Parker) Parker" Date: Wed, 14 Jan 2026 10:16:31 -0500 Subject: [PATCH 2/3] Update machine-id generation Moved machine-id generation code outside of server create only called during initial server creation Create machine-id file for servers that already exists if the file is missing. Makes sure tempdir is created on wings start --- config/config.go | 7 ++++++- server/power.go | 7 +++++++ server/server.go | 26 +++++++++++++++++++------- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/config/config.go b/config/config.go index 674757e7..cf0cd38d 100644 --- a/config/config.go +++ b/config/config.go @@ -198,7 +198,7 @@ type SystemConfiguration struct { // 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 + // 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"` @@ -636,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 diff --git a/server/power.go b/server/power.go index c8a15069..622fd06c 100644 --- a/server/power.go +++ b/server/power.go @@ -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 } diff --git a/server/server.go b/server/server.go index 9f4aa7ecf..3066fee2 100644 --- a/server/server.go +++ b/server/server.go @@ -312,20 +312,32 @@ func (s *Server) CreateEnvironment() error { return err } + // create the machine-id file on install 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) + 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 := 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 nil +} + // Checks if the server is marked as being suspended or not on the system. func (s *Server) IsSuspended() bool { return s.Config().Suspended From 93d3c34f8b7ac22a21a878c7e01095a32065425b Mon Sep 17 00:00:00 2001 From: "Michael (Parker) Parker" Date: Wed, 14 Jan 2026 10:37:39 -0500 Subject: [PATCH 3/3] remove append removes the append where not needed --- server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index 3066fee2..88b323f4 100644 --- a/server/server.go +++ b/server/server.go @@ -330,7 +330,7 @@ func (s *Server) CreateMachineID() error { p := filepath.Join(config.Get().System.MachineID.Directory, s.ID()) s.Log().WithFields(log.Fields{ "path": p}).Debug("creating machine-id file") - machineID := append([]byte(strings.ReplaceAll(s.ID(), "-", ""))) + 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) }