diff --git a/api/process_configuration.go b/api/process_configuration.go new file mode 100644 index 0000000..a36eaa8 --- /dev/null +++ b/api/process_configuration.go @@ -0,0 +1,66 @@ +package api + +import ( + "encoding/json" + "github.com/apex/log" + "github.com/pterodactyl/wings/parser" + "regexp" + "strings" +) + +type OutputLineMatcher struct { + // The raw string to match against. This may or may not be prefixed with + // regex: which indicates we want to match against the regex expression. + raw string + reg *regexp.Regexp +} + +// Determine if a given string "s" matches the given line. +func (olm *OutputLineMatcher) Matches(s string) bool { + if olm.reg == nil { + return strings.Contains(s, olm.raw) + } + + return olm.reg.MatchString(s) +} + +// Return the matcher's raw comparison string. +func (olm *OutputLineMatcher) String() string { + return olm.raw +} + +// Unmarshal the startup lines into individual structs for easier matching abilities. +func (olm *OutputLineMatcher) UnmarshalJSON(data []byte) error { + if err := json.Unmarshal(data, &olm.raw); err != nil { + return err + } + + if strings.HasPrefix(olm.raw, "regex:") && len(olm.raw) > 6 { + r, err := regexp.Compile(strings.TrimPrefix(olm.raw, "regex:")) + if err != nil { + log.WithField("error", err).WithField("raw", olm.raw).Warn("failed to compile output line marked as being regex") + } + + olm.reg = r + } + + return nil +} + +// Defines the process configuration for a given server instance. This sets what the +// daemon is looking for to mark a server as done starting, what to do when stopping, +// and what changes to make to the configuration file for a server. +type ProcessConfiguration struct { + Startup struct { + Done []*OutputLineMatcher `json:"done"` + UserInteraction []string `json:"user_interaction"` + StripAnsi bool `json:"strip_ansi"` + } `json:"startup"` + + Stop struct { + Type string `json:"type"` + Value string `json:"value"` + } `json:"stop"` + + ConfigurationFiles []parser.ConfigurationFile `json:"configs"` +} diff --git a/api/server_endpoints.go b/api/server_endpoints.go index c0bceda..e974f5c 100644 --- a/api/server_endpoints.go +++ b/api/server_endpoints.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "github.com/pkg/errors" - "github.com/pterodactyl/wings/parser" ) const ( @@ -26,24 +25,6 @@ type ServerConfigurationResponse struct { ProcessConfiguration *ProcessConfiguration `json:"process_configuration"` } -// Defines the process configuration for a given server instance. This sets what the -// daemon is looking for to mark a server as done starting, what to do when stopping, -// and what changes to make to the configuration file for a server. -type ProcessConfiguration struct { - Startup struct { - Done []string `json:"done"` - UserInteraction []string `json:"user_interaction"` - StripAnsi bool `json:"strip_ansi"` - } `json:"startup"` - - Stop struct { - Type string `json:"type"` - Value string `json:"value"` - } `json:"stop"` - - ConfigurationFiles []parser.ConfigurationFile `json:"configs"` -} - // Defines installation script information for a server process. This is used when // a server is installed for the first time, and when a server is marked for re-installation. type InstallationScript struct { diff --git a/server/listeners.go b/server/listeners.go index 30c977a..4263f77 100644 --- a/server/listeners.go +++ b/server/listeners.go @@ -4,8 +4,6 @@ import ( "github.com/apex/log" "github.com/pterodactyl/wings/api" "regexp" - "strings" - "sync" ) // Adds all of the internal event listeners we want to use for a server. @@ -23,12 +21,7 @@ func (s *Server) AddEventListeners() { }() } -var ( - stripAnsiRegex = regexp.MustCompile("[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))") - - regexpCacheMx sync.RWMutex - regexpCache map[string]*regexp.Regexp -) +var stripAnsiRegex = regexp.MustCompile("[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))") // Custom listener for console output events that will check if the given line // of output matches one that should mark the server as started or not. @@ -38,10 +31,6 @@ func (s *Server) onConsoleOutput(data string) { // Check if the server is currently starting. if s.GetState() == ProcessStartingState { - // If the specific line of output is one that would mark the server as started, - // set the server to that state. Only do this if the server is not currently stopped - // or stopping. - // Check if we should strip ansi color codes. if processConfiguration.Startup.StripAnsi { // Strip ansi color codes from the data string. @@ -49,40 +38,19 @@ func (s *Server) onConsoleOutput(data string) { } // Iterate over all the done lines. - for _, match := range processConfiguration.Startup.Done { - if strings.HasPrefix(match, "regex:") && len(match) > 6 { - match = match[6:] - - regexpCacheMx.RLock() - rxp, ok := regexpCache[match] - regexpCacheMx.RUnlock() - - if !ok { - var err error - - rxp, err = regexp.Compile(match) - if err != nil { - log.WithError(err).Warn("failed to compile regexp") - break - } - - regexpCacheMx.Lock() - regexpCache[match] = rxp - regexpCacheMx.Unlock() - } - - if !rxp.MatchString(data) { - continue - } - } else if !strings.Contains(data, match) { + for _, l := range processConfiguration.Startup.Done { + if !l.Matches(data) { continue } s.Log().WithFields(log.Fields{ - "match": match, + "match": l.String(), "against": data, }).Debug("detected server in running state based on console line output") + // If the specific line of output is one that would mark the server as started, + // set the server to that state. Only do this if the server is not currently stopped + // or stopping. _ = s.SetState(ProcessRunningState) break }