wings/server/server.go
Dane Everitt cc52954a2a
Refactor environment handling logic to separate a server from the environment handler itself
This change makes the environment handling logic execute independent of the server itself and should make it much easier for people to contribute changes and additional environment handlers down the road without polluting the server object even more.

There is still a lot of work to do on this front to make things easier to work with, and there are some questionable design decisions at play I'm sure.

Welcome to additional modifications and cleanup to make this code easier to reason about and work with.
2020-08-10 21:38:42 -07:00

188 lines
5.8 KiB
Go

package server
import (
"context"
"fmt"
"github.com/apex/log"
"github.com/patrickmn/go-cache"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/environment/docker"
"github.com/pterodactyl/wings/events"
"golang.org/x/sync/semaphore"
"strings"
"sync"
"time"
)
// High level definition for a server instance being controlled by Wings.
type Server struct {
// Internal mutex used to block actions that need to occur sequentially, such as
// writing the configuration to the disk.
sync.RWMutex
emitterLock sync.Mutex
powerLock *semaphore.Weighted
throttleLock sync.RWMutex
// Maintains the configuration for the server. This is the data that gets returned by the Panel
// such as build settings and container images.
cfg Configuration
// The crash handler for this server instance.
crasher CrashHandler
resources ResourceUsage
Archiver Archiver `json:"-"`
Environment environment.ProcessEnvironment `json:"-"`
Filesystem Filesystem `json:"-"`
// Server cache used to store frequently requested information in memory and make
// certain long operations return faster. For example, FS disk space usage.
cache *cache.Cache
// Events emitted by the server instance.
emitter *events.EventBus
// Defines the process configuration for the server instance. This is dynamically
// fetched from the Pterodactyl Server instance each time the server process is
// started, and then cached here.
procConfig *api.ProcessConfiguration
// Tracks the installation process for this server and prevents a server from running
// two installer processes at the same time. This also allows us to cancel a running
// installation process, for example when a server is deleted from the panel while the
// installer process is still running.
installer InstallerDetails
// The console throttler instance used to control outputs.
throttler *ConsoleThrottler
}
type InstallerDetails struct {
// The cancel function for the installer. This will be a non-nil value while there
// is an installer running for the server.
cancel *context.CancelFunc
// Installer lock. You should obtain an exclusive lock on this context while running
// the installation process and release it when finished.
sem *semaphore.Weighted
}
// Returns the UUID for the server instance.
func (s *Server) Id() string {
return s.Config().GetUuid()
}
// Returns all of the environment variables that should be assigned to a running
// server instance.
func (s *Server) GetEnvironmentVariables() []string {
zone, _ := time.Now().In(time.Local).Zone()
var out = []string{
fmt.Sprintf("TZ=%s", zone),
fmt.Sprintf("STARTUP=%s", s.Config().Invocation),
fmt.Sprintf("SERVER_MEMORY=%d", s.Build().MemoryLimit),
fmt.Sprintf("SERVER_IP=%s", s.Config().Allocations.DefaultMapping.Ip),
fmt.Sprintf("SERVER_PORT=%d", s.Config().Allocations.DefaultMapping.Port),
}
eloop:
for k := range s.Config().EnvVars {
for _, e := range out {
if strings.HasPrefix(e, strings.ToUpper(k)) {
continue eloop
}
}
out = append(out, fmt.Sprintf("%s=%s", strings.ToUpper(k), s.Config().EnvVars.Get(k)))
}
return out
}
func (s *Server) Log() *log.Entry {
return log.WithField("server", s.Id())
}
// Syncs the state of the server on the Panel with Wings. This ensures that we're always
// using the state of the server from the Panel and allows us to not require successful
// API calls to Wings to do things.
//
// This also means mass actions can be performed against servers on the Panel and they
// will automatically sync with Wings when the server is started.
func (s *Server) Sync() error {
cfg, rerr, err := s.GetProcessConfiguration()
if err != nil || rerr != nil {
if err != nil {
return errors.WithStack(err)
}
if rerr.Status == "404" {
return &serverDoesNotExist{}
}
return errors.New(rerr.String())
}
return s.SyncWithConfiguration(cfg)
}
func (s *Server) SyncWithConfiguration(cfg *api.ServerConfigurationResponse) error {
// Update the data structure and persist it to the disk.
if err := s.UpdateDataStructure(cfg.Settings, false); err != nil {
return errors.WithStack(err)
}
s.Lock()
s.procConfig = cfg.ProcessConfiguration
s.Unlock()
// If this is a Docker environment we need to sync the stop configuration with it so that
// the process isn't just terminated when a user requests it be stopped.
if e, ok := s.Environment.(*docker.Environment); ok {
s.Log().Debug("syncing stop configuration with configured docker environment")
e.SetStopConfiguration(&cfg.ProcessConfiguration.Stop)
}
return nil
}
// Reads the log file for a server up to a specified number of bytes.
func (s *Server) ReadLogfile(len int64) ([]string, error) {
return s.Environment.Readlog(len)
}
// Determine if the server is bootable in it's current state or not. This will not
// indicate why a server is not bootable, only if it is.
func (s *Server) IsBootable() bool {
exists, _ := s.Environment.Exists()
return exists
}
// Initalizes a server instance. This will run through and ensure that the environment
// for the server is setup, and that all of the necessary files are created.
func (s *Server) CreateEnvironment() error {
// Ensure the data directory exists before getting too far through this process.
if err := s.Filesystem.EnsureDataDirectory(); err != nil {
return errors.WithStack(err)
}
return s.Environment.Create()
}
// Gets the process configuration data for the server.
func (s *Server) GetProcessConfiguration() (*api.ServerConfigurationResponse, *api.RequestError, error) {
return api.NewRequester().GetServerConfiguration(s.Id())
}
// Checks if the server is marked as being suspended or not on the system.
func (s *Server) IsSuspended() bool {
return s.Config().Suspended
}
func (s *Server) Build() *environment.Limits {
return &s.Config().Build
}