2019-03-25 00:27:14 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
2019-04-21 19:02:28 +00:00
|
|
|
"fmt"
|
2019-12-22 05:02:02 +00:00
|
|
|
"github.com/creasty/defaults"
|
2019-04-06 23:53:22 +00:00
|
|
|
"github.com/patrickmn/go-cache"
|
2019-11-17 01:01:38 +00:00
|
|
|
"github.com/pkg/errors"
|
2019-09-23 03:47:38 +00:00
|
|
|
"github.com/pterodactyl/wings/api"
|
2019-04-06 20:33:54 +00:00
|
|
|
"github.com/pterodactyl/wings/config"
|
2019-03-25 01:00:21 +00:00
|
|
|
"github.com/remeh/sizedwaitgroup"
|
|
|
|
"go.uber.org/zap"
|
2020-05-09 03:57:00 +00:00
|
|
|
"math"
|
2019-03-25 01:00:21 +00:00
|
|
|
"os"
|
|
|
|
"strings"
|
2019-11-24 23:08:38 +00:00
|
|
|
"sync"
|
2019-04-06 23:53:22 +00:00
|
|
|
"time"
|
2019-03-25 00:27:14 +00:00
|
|
|
)
|
|
|
|
|
2019-12-08 00:43:00 +00:00
|
|
|
var servers *Collection
|
|
|
|
|
|
|
|
func GetServers() *Collection {
|
|
|
|
return servers
|
|
|
|
}
|
|
|
|
|
2019-03-25 00:27:14 +00:00
|
|
|
// High level definition for a server instance being controlled by Wings.
|
|
|
|
type Server struct {
|
2019-03-25 01:00:21 +00:00
|
|
|
// The unique identifier for the server that should be used when referencing
|
2020-04-04 05:17:26 +00:00
|
|
|
// it against the Panel API (and internally). This will be used when naming
|
2019-03-25 01:00:21 +00:00
|
|
|
// docker containers as well as in log output.
|
2019-04-06 05:20:26 +00:00
|
|
|
Uuid string `json:"uuid"`
|
2019-03-25 00:27:14 +00:00
|
|
|
|
2020-04-04 05:17:26 +00:00
|
|
|
// Whether or not the server is in a suspended state. Suspended servers cannot
|
2019-03-25 01:00:21 +00:00
|
|
|
// be started or modified except in certain scenarios by an admin user.
|
2019-04-06 05:20:26 +00:00
|
|
|
Suspended bool `json:"suspended"`
|
2019-03-25 00:27:14 +00:00
|
|
|
|
2019-03-25 01:00:21 +00:00
|
|
|
// The power state of the server.
|
2019-11-25 05:31:31 +00:00
|
|
|
State string `default:"offline" json:"state"`
|
2019-03-25 00:27:14 +00:00
|
|
|
|
2019-03-25 01:00:21 +00:00
|
|
|
// The command that should be used when booting up the server instance.
|
2019-04-06 05:20:26 +00:00
|
|
|
Invocation string `json:"invocation"`
|
2019-03-25 00:39:13 +00:00
|
|
|
|
2019-03-25 01:00:21 +00:00
|
|
|
// An array of environment variables that should be passed along to the running
|
|
|
|
// server process.
|
2019-11-16 21:34:05 +00:00
|
|
|
EnvVars map[string]string `json:"environment" yaml:"environment"`
|
2019-03-25 00:27:14 +00:00
|
|
|
|
2020-04-04 05:17:26 +00:00
|
|
|
Archiver Archiver `json:"-" yaml:"-"`
|
2019-12-01 00:43:18 +00:00
|
|
|
CrashDetection CrashDetection `json:"crash_detection" yaml:"crash_detection"`
|
2019-12-01 00:37:11 +00:00
|
|
|
Build BuildSettings `json:"build"`
|
|
|
|
Allocations Allocations `json:"allocations"`
|
|
|
|
Environment Environment `json:"-" yaml:"-"`
|
|
|
|
Filesystem Filesystem `json:"-" yaml:"-"`
|
|
|
|
Resources ResourceUsage `json:"resources" yaml:"-"`
|
2019-03-25 00:27:14 +00:00
|
|
|
|
2019-04-04 05:01:11 +00:00
|
|
|
Container struct {
|
|
|
|
// Defines the Docker image that will be used for this server
|
2019-04-06 05:20:26 +00:00
|
|
|
Image string `json:"image,omitempty"`
|
2019-11-24 23:08:38 +00:00
|
|
|
// If set to true, OOM killer will be disabled on the server's Docker container.
|
|
|
|
// If not present (nil) we will default to disabling it.
|
2019-11-25 04:02:53 +00:00
|
|
|
OomDisabled bool `default:"true" json:"oom_disabled" yaml:"oom_disabled"`
|
2019-04-06 05:20:26 +00:00
|
|
|
} `json:"container,omitempty"`
|
2019-04-04 05:01:11 +00:00
|
|
|
|
2019-04-06 23:53:22 +00:00
|
|
|
// Server cache used to store frequently requested information in memory and make
|
|
|
|
// certain long operations return faster. For example, FS disk space usage.
|
2019-11-16 21:34:05 +00:00
|
|
|
Cache *cache.Cache `json:"-" yaml:"-"`
|
2019-04-20 21:57:37 +00:00
|
|
|
|
2020-01-18 22:04:26 +00:00
|
|
|
// Events emitted by the server instance.
|
|
|
|
emitter *EventBus
|
2019-09-23 03:47:38 +00:00
|
|
|
|
|
|
|
// 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.
|
2019-12-22 21:21:21 +00:00
|
|
|
processConfiguration *api.ProcessConfiguration
|
2019-11-24 23:08:38 +00:00
|
|
|
|
|
|
|
// Internal mutex used to block actions that need to occur sequentially, such as
|
|
|
|
// writing the configuration to the disk.
|
2020-04-11 01:03:35 +00:00
|
|
|
sync.RWMutex
|
2019-03-25 00:27:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// The build settings for a given server that impact docker container creation and
|
|
|
|
// resource limits for a server instance.
|
|
|
|
type BuildSettings struct {
|
2019-03-25 01:00:21 +00:00
|
|
|
// The total amount of memory in megabytes that this server is allowed to
|
|
|
|
// use on the host system.
|
2019-04-06 05:20:26 +00:00
|
|
|
MemoryLimit int64 `json:"memory_limit" yaml:"memory"`
|
2019-03-25 00:27:14 +00:00
|
|
|
|
2019-03-25 01:00:21 +00:00
|
|
|
// The amount of additional swap space to be provided to a container instance.
|
2019-04-06 05:20:26 +00:00
|
|
|
Swap int64 `json:"swap"`
|
2019-03-25 00:27:14 +00:00
|
|
|
|
2019-03-25 01:00:21 +00:00
|
|
|
// The relative weight for IO operations in a container. This is relative to other
|
|
|
|
// containers on the system and should be a value between 10 and 1000.
|
2019-04-06 05:20:26 +00:00
|
|
|
IoWeight uint16 `json:"io_weight" yaml:"io"`
|
2019-03-25 00:27:14 +00:00
|
|
|
|
2019-03-25 01:00:21 +00:00
|
|
|
// The percentage of CPU that this instance is allowed to consume relative to
|
|
|
|
// the host. A value of 200% represents complete utilization of two cores. This
|
|
|
|
// should be a value between 1 and THREAD_COUNT * 100.
|
2019-04-06 05:20:26 +00:00
|
|
|
CpuLimit int64 `json:"cpu_limit" yaml:"cpu"`
|
2019-03-25 00:27:14 +00:00
|
|
|
|
2019-03-25 01:00:21 +00:00
|
|
|
// The amount of disk space in megabytes that a server is allowed to use.
|
2019-04-06 05:20:26 +00:00
|
|
|
DiskSpace int64 `json:"disk_space" yaml:"disk"`
|
2020-03-29 19:31:17 +00:00
|
|
|
|
|
|
|
// Sets which CPU threads can be used by the docker instance.
|
|
|
|
Threads string `json:"threads" yaml:"threads"`
|
2019-03-25 00:27:14 +00:00
|
|
|
}
|
|
|
|
|
2019-04-04 05:49:15 +00:00
|
|
|
// Converts the CPU limit for a server build into a number that can be better understood
|
|
|
|
// by the Docker environment. If there is no limit set, return -1 which will indicate to
|
|
|
|
// Docker that it has unlimited CPU quota.
|
|
|
|
func (b *BuildSettings) ConvertedCpuLimit() int64 {
|
|
|
|
if b.CpuLimit == 0 {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
|
|
|
return b.CpuLimit * 1000
|
|
|
|
}
|
|
|
|
|
2020-05-09 03:57:00 +00:00
|
|
|
// Set the hard limit for memory usage to be 5% more than the amount of memory assigned to
|
|
|
|
// the server. If the memory limit for the server is < 4G, use 10%, if less than 2G use
|
|
|
|
// 15%. This avoids unexpected crashes from processes like Java which run over the limit.
|
|
|
|
func (b *BuildSettings) MemoryOverheadMultiplier() float64 {
|
|
|
|
if b.MemoryLimit <= 2048 {
|
|
|
|
return 1.15
|
|
|
|
} else if b.MemoryLimit <= 4096 {
|
|
|
|
return 1.10
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1.05
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *BuildSettings) BoundedMemoryLimit() int64 {
|
|
|
|
return int64(math.Round(float64(b.MemoryLimit) * b.MemoryOverheadMultiplier() * 1_000_000))
|
|
|
|
}
|
|
|
|
|
2019-04-04 05:49:15 +00:00
|
|
|
// Returns the amount of swap available as a total in bytes. This is returned as the amount
|
|
|
|
// of memory available to the server initially, PLUS the amount of additional swap to include
|
|
|
|
// which is the format used by Docker.
|
|
|
|
func (b *BuildSettings) ConvertedSwap() int64 {
|
|
|
|
if b.Swap < 0 {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
2020-05-09 03:57:00 +00:00
|
|
|
return (b.Swap * 1_000_000) + b.BoundedMemoryLimit()
|
2019-04-04 05:49:15 +00:00
|
|
|
}
|
|
|
|
|
2019-03-25 00:27:14 +00:00
|
|
|
// Defines the allocations available for a given server. When using the Docker environment
|
|
|
|
// driver these correspond to mappings for the container that allow external connections.
|
|
|
|
type Allocations struct {
|
2019-03-25 01:00:21 +00:00
|
|
|
// Defines the default allocation that should be used for this server. This is
|
|
|
|
// what will be used for {SERVER_IP} and {SERVER_PORT} when modifying configuration
|
|
|
|
// files or the startup arguments for a server.
|
|
|
|
DefaultMapping struct {
|
2019-04-06 05:20:26 +00:00
|
|
|
Ip string `json:"ip"`
|
|
|
|
Port int `json:"port"`
|
|
|
|
} `json:"default" yaml:"default"`
|
2019-03-25 01:00:21 +00:00
|
|
|
|
|
|
|
// Mappings contains all of the ports that should be assigned to a given server
|
|
|
|
// attached to the IP they correspond to.
|
2019-04-06 05:20:26 +00:00
|
|
|
Mappings map[string][]int `json:"mappings"`
|
2019-03-25 01:00:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Iterates over a given directory and loads all of the servers listed before returning
|
|
|
|
// them to the calling function.
|
2020-04-11 01:03:35 +00:00
|
|
|
func LoadDirectory() error {
|
2019-03-25 01:39:01 +00:00
|
|
|
// We could theoretically use a standard wait group here, however doing
|
|
|
|
// that introduces the potential to crash the program due to too many
|
|
|
|
// open files. This wouldn't happen on a small setup, but once the daemon is
|
|
|
|
// handling many servers you run that risk.
|
|
|
|
//
|
|
|
|
// For now just process 10 files at a time, that should be plenty fast to
|
|
|
|
// read and parse the YAML. We should probably make this configurable down
|
|
|
|
// the road to help big instances scale better.
|
|
|
|
wg := sizedwaitgroup.New(10)
|
2019-03-25 01:00:21 +00:00
|
|
|
|
2020-04-10 21:39:07 +00:00
|
|
|
configs, rerr, err := api.NewRequester().GetAllServerConfigurations()
|
|
|
|
if err != nil || rerr != nil {
|
|
|
|
if err != nil {
|
|
|
|
return errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors.New(rerr.String())
|
|
|
|
}
|
|
|
|
|
2020-04-11 01:07:57 +00:00
|
|
|
states, err := getServerStates()
|
2020-04-10 22:33:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.WithStack(err)
|
|
|
|
}
|
2019-03-25 01:39:01 +00:00
|
|
|
|
2020-04-10 22:33:30 +00:00
|
|
|
servers = NewCollection(nil)
|
2019-03-25 01:00:21 +00:00
|
|
|
|
2020-04-10 22:33:30 +00:00
|
|
|
for uuid, data := range configs {
|
2019-03-25 01:00:21 +00:00
|
|
|
wg.Add()
|
2020-04-10 21:39:07 +00:00
|
|
|
|
2020-04-10 22:33:30 +00:00
|
|
|
go func(uuid string, data *api.ServerConfigurationResponse) {
|
2019-03-25 01:39:01 +00:00
|
|
|
defer wg.Done()
|
|
|
|
|
2020-04-11 01:03:35 +00:00
|
|
|
s, err := FromConfiguration(data)
|
2019-03-25 01:39:01 +00:00
|
|
|
if err != nil {
|
2020-04-10 22:33:30 +00:00
|
|
|
zap.S().Errorw("failed to load server, skipping...", zap.String("server", uuid), zap.Error(err))
|
2019-03-25 01:39:01 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-04-10 22:33:30 +00:00
|
|
|
if state, exists := states[s.Uuid]; exists {
|
2020-04-11 01:22:18 +00:00
|
|
|
s.SetState(state)
|
|
|
|
zap.S().Debugw("loaded server state from cache", zap.String("server", s.Uuid), zap.String("state", s.GetState()))
|
2019-03-25 01:39:01 +00:00
|
|
|
}
|
|
|
|
|
2019-12-08 00:43:00 +00:00
|
|
|
servers.Add(s)
|
2020-04-10 22:33:30 +00:00
|
|
|
}(uuid, data)
|
2019-03-25 01:00:21 +00:00
|
|
|
}
|
|
|
|
|
2019-03-25 01:39:01 +00:00
|
|
|
// Wait until we've processed all of the configuration files in the directory
|
|
|
|
// before continuing.
|
2019-03-25 01:00:21 +00:00
|
|
|
wg.Wait()
|
|
|
|
|
2019-12-08 00:43:00 +00:00
|
|
|
return nil
|
2019-03-25 00:27:14 +00:00
|
|
|
}
|
|
|
|
|
2020-04-04 05:17:26 +00:00
|
|
|
// Initializes a server using a data byte array. This will be marshaled into the
|
2019-03-25 00:27:14 +00:00
|
|
|
// given struct using a YAML marshaler. This will also configure the given environment
|
|
|
|
// for a server.
|
2020-04-11 01:03:35 +00:00
|
|
|
func FromConfiguration(data *api.ServerConfigurationResponse) (*Server, error) {
|
2019-12-01 00:37:11 +00:00
|
|
|
s := new(Server)
|
2019-12-17 05:23:57 +00:00
|
|
|
|
2019-12-22 05:02:02 +00:00
|
|
|
if err := defaults.Set(s); err != nil {
|
|
|
|
return nil, err
|
2019-12-17 05:23:57 +00:00
|
|
|
}
|
|
|
|
|
2020-04-10 23:55:36 +00:00
|
|
|
if err := s.UpdateDataStructure(data.Settings, false); err != nil {
|
2019-03-25 01:00:21 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-09-23 03:47:38 +00:00
|
|
|
s.AddEventListeners()
|
|
|
|
|
2019-03-25 01:00:21 +00:00
|
|
|
// Right now we only support a Docker based environment, so I'm going to hard code
|
|
|
|
// this logic in. When we're ready to support other environment we'll need to make
|
|
|
|
// some modifications here obviously.
|
2019-12-25 00:40:51 +00:00
|
|
|
if err := NewDockerEnvironment(s); err != nil {
|
2019-04-06 04:59:27 +00:00
|
|
|
return nil, err
|
2019-03-25 01:00:21 +00:00
|
|
|
}
|
|
|
|
|
2019-04-21 19:02:28 +00:00
|
|
|
s.Cache = cache.New(time.Minute*10, time.Minute*15)
|
2020-04-04 05:17:26 +00:00
|
|
|
s.Archiver = Archiver{
|
|
|
|
Server: s,
|
|
|
|
}
|
2019-12-01 00:37:11 +00:00
|
|
|
s.Filesystem = Filesystem{
|
2020-04-11 01:03:35 +00:00
|
|
|
Configuration: &config.Get().System,
|
2019-05-28 00:12:51 +00:00
|
|
|
Server: s,
|
2019-04-04 05:49:15 +00:00
|
|
|
}
|
2019-12-01 00:37:11 +00:00
|
|
|
s.Resources = ResourceUsage{}
|
2019-04-04 05:49:15 +00:00
|
|
|
|
2020-04-10 18:15:46 +00:00
|
|
|
// Forces the configuration to be synced with the panel.
|
2020-04-10 22:33:30 +00:00
|
|
|
if err := s.SyncWithConfiguration(data); err != nil {
|
|
|
|
return nil, err
|
2019-12-22 21:21:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
2019-12-28 22:57:19 +00:00
|
|
|
// 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.Invocation),
|
|
|
|
fmt.Sprintf("SERVER_MEMORY=%d", s.Build.MemoryLimit),
|
|
|
|
fmt.Sprintf("SERVER_IP=%s", s.Allocations.DefaultMapping.Ip),
|
|
|
|
fmt.Sprintf("SERVER_PORT=%d", s.Allocations.DefaultMapping.Port),
|
|
|
|
}
|
|
|
|
|
|
|
|
eloop:
|
|
|
|
for k, v := range s.EnvVars {
|
|
|
|
for _, e := range out {
|
|
|
|
if strings.HasPrefix(e, strings.ToUpper(k)) {
|
|
|
|
continue eloop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out = append(out, fmt.Sprintf("%s=%s", strings.ToUpper(k), v))
|
|
|
|
}
|
|
|
|
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
2019-12-22 21:21:21 +00:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2019-12-17 04:47:35 +00:00
|
|
|
if rerr.Status == "404" {
|
2019-12-22 21:21:21 +00:00
|
|
|
return &serverDoesNotExist{}
|
2019-12-17 04:47:35 +00:00
|
|
|
}
|
|
|
|
|
2019-12-22 21:21:21 +00:00
|
|
|
return errors.New(rerr.String())
|
2019-09-23 04:22:16 +00:00
|
|
|
}
|
2019-09-23 03:47:38 +00:00
|
|
|
|
2020-04-10 21:39:07 +00:00
|
|
|
return s.SyncWithConfiguration(cfg)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) SyncWithConfiguration(cfg *api.ServerConfigurationResponse) error {
|
2019-12-22 21:21:21 +00:00
|
|
|
// Update the data structure and persist it to the disk.
|
2020-03-29 19:31:17 +00:00
|
|
|
if err := s.UpdateDataStructure(cfg.Settings, false); err != nil {
|
2019-12-22 21:21:21 +00:00
|
|
|
return errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
s.processConfiguration = cfg.ProcessConfiguration
|
|
|
|
return nil
|
2019-03-25 00:27:14 +00:00
|
|
|
}
|
2019-04-04 05:01:11 +00:00
|
|
|
|
2019-04-06 19:27:44 +00:00
|
|
|
// Reads the log file for a server up to a specified number of bytes.
|
|
|
|
func (s *Server) ReadLogfile(len int64) ([]string, error) {
|
2019-04-20 23:26:55 +00:00
|
|
|
return s.Environment.Readlog(len)
|
2019-04-06 23:53:22 +00:00
|
|
|
}
|
|
|
|
|
2019-04-04 05:01:11 +00:00
|
|
|
// 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 {
|
2019-04-20 23:26:55 +00:00
|
|
|
exists, _ := s.Environment.Exists()
|
2019-04-20 23:20:08 +00:00
|
|
|
|
|
|
|
return exists
|
2019-04-04 05:01:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2019-04-20 23:26:55 +00:00
|
|
|
return s.Environment.Create()
|
2019-04-21 19:02:28 +00:00
|
|
|
}
|
|
|
|
|
2019-09-23 03:47:38 +00:00
|
|
|
// Gets the process configuration data for the server.
|
2019-12-22 21:21:21 +00:00
|
|
|
func (s *Server) GetProcessConfiguration() (*api.ServerConfigurationResponse, *api.RequestError, error) {
|
2019-09-23 03:47:38 +00:00
|
|
|
return api.NewRequester().GetServerConfiguration(s.Uuid)
|
2019-11-24 23:08:38 +00:00
|
|
|
}
|
2020-04-06 01:00:33 +00:00
|
|
|
|
|
|
|
// Helper function that can receieve a power action and then process the
|
|
|
|
// actions that need to occur for it.
|
|
|
|
func (s *Server) HandlePowerAction(action PowerAction) error {
|
|
|
|
switch action.Action {
|
|
|
|
case "start":
|
|
|
|
return s.Environment.Start()
|
|
|
|
case "restart":
|
|
|
|
if err := s.Environment.WaitForStop(60, false); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return s.Environment.Start()
|
|
|
|
case "stop":
|
|
|
|
return s.Environment.Stop()
|
|
|
|
case "kill":
|
|
|
|
return s.Environment.Terminate(os.Kill)
|
|
|
|
default:
|
|
|
|
return errors.New("an invalid power action was provided")
|
|
|
|
}
|
2020-04-10 18:00:04 +00:00
|
|
|
}
|