328 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			328 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package control
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"io/ioutil"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/pterodactyl/wings/api/websockets"
 | |
| 	log "github.com/sirupsen/logrus"
 | |
| 	"github.com/spf13/viper"
 | |
| )
 | |
| 
 | |
| // ErrServerExists is returned when a server already exists on creation.
 | |
| type ErrServerExists struct {
 | |
| 	id string
 | |
| }
 | |
| 
 | |
| func (e ErrServerExists) Error() string {
 | |
| 	return "server " + e.id + " already exists"
 | |
| }
 | |
| 
 | |
| // Server is a Server
 | |
| type Server interface {
 | |
| 	Start() error
 | |
| 	Stop() error
 | |
| 	Restart() error
 | |
| 	Kill() error
 | |
| 	Exec(command string) error
 | |
| 	Rebuild() error
 | |
| 
 | |
| 	Save() error
 | |
| 
 | |
| 	Environment() (Environment, error)
 | |
| 
 | |
| 	HasPermission(string, string) bool
 | |
| }
 | |
| 
 | |
| // ServerStruct is a single instance of a Service managed by the panel
 | |
| type ServerStruct struct {
 | |
| 	// ID is the unique identifier of the server
 | |
| 	ID string `json:"uuid"`
 | |
| 
 | |
| 	// ServiceName is the name of the service. It is mainly used to allow storing the service
 | |
| 	// in the config
 | |
| 	ServiceName string `json:"serviceName"`
 | |
| 	service     *Service
 | |
| 	environment Environment
 | |
| 
 | |
| 	// StartupCommand is the command executed in the environment to start the server
 | |
| 	StartupCommand string `json:"startupCommand"`
 | |
| 
 | |
| 	// DockerContainer holds information regarding the docker container when the server
 | |
| 	// is running in a docker environment
 | |
| 	DockerContainer dockerContainer `json:"dockerContainer"`
 | |
| 
 | |
| 	// EnvironmentVariables are set in the Environment the server is running in
 | |
| 	EnvironmentVariables map[string]string `json:"env"`
 | |
| 
 | |
| 	// Allocations contains the ports and ip addresses assigned to the server
 | |
| 	Allocations allocations `json:"allocation"`
 | |
| 
 | |
| 	// Settings are the environment settings and limitations for the server
 | |
| 	Settings settings `json:"settings"`
 | |
| 
 | |
| 	// Keys are some auth keys we will hopefully replace by something better.
 | |
| 	Keys map[string][]string `json:"keys"`
 | |
| }
 | |
| 
 | |
| type allocations struct {
 | |
| 	Ports    []int16 `json:"ports"`
 | |
| 	MainIP   string  `json:"ip"`
 | |
| 	MainPort int16   `json:"port"`
 | |
| }
 | |
| 
 | |
| type settings struct {
 | |
| 	Memory int64  `json:"memory"`
 | |
| 	Swap   int64  `json:"swap"`
 | |
| 	IO     int64  `json:"io"`
 | |
| 	CPU    int16  `json:"cpu"`
 | |
| 	Disk   int64  `json:"disk"`
 | |
| 	Image  string `json:"image"`
 | |
| 	User   string `json:"user"`
 | |
| 	UserID int16  `json:"userID"`
 | |
| }
 | |
| 
 | |
| type dockerContainer struct {
 | |
| 	ID    string `json:"id"`
 | |
| 	Image string `json:"image"`
 | |
| }
 | |
| 
 | |
| // ensure server implements Server
 | |
| var _ Server = &ServerStruct{}
 | |
| 
 | |
| type serversMap map[string]*ServerStruct
 | |
| 
 | |
| var servers = make(serversMap)
 | |
| 
 | |
| // LoadServerConfigurations loads the configured servers from a specified path
 | |
| func LoadServerConfigurations(path string) error {
 | |
| 	serverFiles, err := ioutil.ReadDir(path)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	servers = make(serversMap)
 | |
| 
 | |
| 	for _, file := range serverFiles {
 | |
| 		if file.IsDir() {
 | |
| 			server, err := loadServerConfiguration(filepath.Join(path, file.Name(), constants.ServerConfigFile))
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			servers[server.ID] = server
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func loadServerConfiguration(path string) (*ServerStruct, error) {
 | |
| 	file, err := ioutil.ReadFile(path)
 | |
| 
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	server := &ServerStruct{}
 | |
| 	if err := json.Unmarshal(file, server); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return server, nil
 | |
| }
 | |
| 
 | |
| func storeServerConfiguration(server *ServerStruct) error {
 | |
| 	serverJSON, err := json.MarshalIndent(server, "", constants.JSONIndent)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err := os.MkdirAll(server.path(), constants.DefaultFolderPerms); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err := ioutil.WriteFile(server.configFilePath(), serverJSON, constants.DefaultFilePerms); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func storeServerConfigurations() error {
 | |
| 	for _, s := range servers {
 | |
| 		if err := storeServerConfiguration(s); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func deleteServerFolder(id string) error {
 | |
| 	path := filepath.Join(viper.GetString(config.DataPath), constants.ServersPath, id)
 | |
| 	folder, err := os.Stat(path)
 | |
| 	if os.IsNotExist(err) || !folder.IsDir() {
 | |
| 		return err
 | |
| 	}
 | |
| 	return os.RemoveAll(path)
 | |
| }
 | |
| 
 | |
| // GetServers returns an array of all servers the daemon manages
 | |
| func GetServers() []Server {
 | |
| 	serverArray := make([]Server, len(servers))
 | |
| 	i := 0
 | |
| 	for _, s := range servers {
 | |
| 		serverArray[i] = s
 | |
| 		i++
 | |
| 	}
 | |
| 	return serverArray
 | |
| }
 | |
| 
 | |
| // GetServer returns the server identified by the provided uuid
 | |
| func GetServer(id string) Server {
 | |
| 	server := servers[id]
 | |
| 	if server == nil {
 | |
| 		return nil // https://golang.org/doc/faq#nil_error
 | |
| 	}
 | |
| 	return server
 | |
| }
 | |
| 
 | |
| // CreateServer creates a new server
 | |
| func CreateServer(server *ServerStruct) (Server, error) {
 | |
| 	if servers[server.ID] != nil {
 | |
| 		return nil, ErrServerExists{server.ID}
 | |
| 	}
 | |
| 	servers[server.ID] = server
 | |
| 	if err := server.Save(); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return server, nil
 | |
| }
 | |
| 
 | |
| // DeleteServer deletes a server and all related files
 | |
| // NOTE: This is not reversible.
 | |
| func DeleteServer(id string) error {
 | |
| 	if err := deleteServerFolder(id); err != nil {
 | |
| 		log.WithField("server", id).WithError(err).Error("Failed to delete server.")
 | |
| 	}
 | |
| 	delete(servers, id)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *ServerStruct) Start() error {
 | |
| 	env, err := s.Environment()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if !env.Exists() {
 | |
| 		if err := env.Create(); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return env.Start()
 | |
| }
 | |
| 
 | |
| func (s *ServerStruct) Stop() error {
 | |
| 	env, err := s.Environment()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return env.Stop()
 | |
| }
 | |
| 
 | |
| func (s *ServerStruct) Restart() error {
 | |
| 	if err := s.Stop(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return s.Start()
 | |
| }
 | |
| 
 | |
| func (s *ServerStruct) Kill() error {
 | |
| 	env, err := s.Environment()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return env.Kill()
 | |
| }
 | |
| 
 | |
| func (s *ServerStruct) Exec(command string) error {
 | |
| 	env, err := s.Environment()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return env.Exec(command)
 | |
| }
 | |
| 
 | |
| func (s *ServerStruct) Rebuild() error {
 | |
| 	env, err := s.Environment()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return env.ReCreate()
 | |
| }
 | |
| 
 | |
| // Service returns the server's service configuration
 | |
| func (s *ServerStruct) Service() *Service {
 | |
| 	if s.service == nil {
 | |
| 		// TODO: Properly use the correct service, mock for now.
 | |
| 		s.service = &Service{
 | |
| 			DockerImage:     "quay.io/pterodactyl/core:java",
 | |
| 			EnvironmentName: "docker",
 | |
| 		}
 | |
| 	}
 | |
| 	return s.service
 | |
| }
 | |
| 
 | |
| // UUIDShort returns the first block of the UUID
 | |
| func (s *ServerStruct) UUIDShort() string {
 | |
| 	return s.ID[0:strings.Index(s.ID, "-")]
 | |
| }
 | |
| 
 | |
| // Environment returns the servers environment
 | |
| func (s *ServerStruct) Environment() (Environment, error) {
 | |
| 	var err error
 | |
| 	if s.environment == nil {
 | |
| 		switch s.Service().EnvironmentName {
 | |
| 		case "docker":
 | |
| 			s.environment, err = NewDockerEnvironment(s)
 | |
| 		default:
 | |
| 			log.WithField("service", s.ServiceName).Error("Invalid environment name")
 | |
| 			return nil, errors.New("Invalid environment name")
 | |
| 		}
 | |
| 	}
 | |
| 	return s.environment, err
 | |
| }
 | |
| 
 | |
| // HasPermission checks wether a provided token has a specific permission
 | |
| func (s *ServerStruct) HasPermission(token string, permission string) bool {
 | |
| 	for key, perms := range s.Keys {
 | |
| 		if key == token {
 | |
| 			for _, perm := range perms {
 | |
| 				if perm == permission || perm == "s:*" {
 | |
| 					return true
 | |
| 				}
 | |
| 			}
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func (s *ServerStruct) Save() error {
 | |
| 	if err := storeServerConfiguration(s); err != nil {
 | |
| 		log.WithField("server", s.ID).WithError(err).Error("Failed to store server configuration.")
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *ServerStruct) path() string {
 | |
| 	return filepath.Join(viper.GetString(config.DataPath), constants.ServersPath, s.ID)
 | |
| }
 | |
| 
 | |
| func (s *ServerStruct) dataPath() string {
 | |
| 	return filepath.Join(s.path(), constants.ServerDataPath)
 | |
| }
 | |
| 
 | |
| func (s *ServerStruct) configFilePath() string {
 | |
| 	return filepath.Join(s.path(), constants.ServerConfigFile)
 | |
| }
 |