diff --git a/http.go b/http.go index 2c95880..842bac3 100644 --- a/http.go +++ b/http.go @@ -399,9 +399,19 @@ func (rt *Router) routeCreateServer(w http.ResponseWriter, r *http.Request, ps h if err != nil { zap.S().Warnw("failed to validate the received data", zap.Error(err)) + + http.Error(w, "failed to validate data", http.StatusUnprocessableEntity) + return } - inst.Execute() + // Plop that server instance onto the request so that it can be referenced in + // requests from here-on out. + rt.Servers = append(rt.Servers, inst.Server()) + + // Begin the installation process in the background to not block the request + // cycle. If there are any errors they will be logged and communicated back + // to the Panel where a reinstall may take place. + go inst.Execute() w.WriteHeader(http.StatusAccepted) } diff --git a/installer/installer.go b/installer/installer.go index 7d9f8fd..7d10a32 100644 --- a/installer/installer.go +++ b/installer/installer.go @@ -5,8 +5,11 @@ import ( "errors" "github.com/asaskevich/govalidator" "github.com/buger/jsonparser" + "github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/server" "go.uber.org/zap" + "gopkg.in/yaml.v2" + "os" ) type Installer struct { @@ -26,17 +29,17 @@ func New(data []byte) (error, *Installer) { } s := &server.Server{ - Uuid: getString(data, "uuid"), - Suspended: false, - State: server.ProcessOfflineState, - Invocation: "", - EnvVars: make(map[string]string), - Build: &server.BuildSettings{ + Uuid: getString(data, "uuid"), + Suspended: false, + State: server.ProcessOfflineState, + Invocation: "", + EnvVars: make(map[string]string), + Build: &server.BuildSettings{ MemoryLimit: getInt(data, "build", "memory"), - Swap: getInt(data, "build", "swap"), - IoWeight: uint16(getInt(data, "build", "io")), - CpuLimit: getInt(data, "build", "cpu"), - DiskSpace: getInt(data, "build", "disk"), + Swap: getInt(data, "build", "swap"), + IoWeight: uint16(getInt(data, "build", "io")), + CpuLimit: getInt(data, "build", "cpu"), + DiskSpace: getInt(data, "build", "disk"), }, Allocations: &server.Allocations{ Mappings: make(map[string][]int), @@ -65,8 +68,20 @@ func New(data []byte) (error, *Installer) { s.Container.Image = getString(data, "container", "image") + b, err := WriteConfigurationToDisk(s) + if err != nil { + return err, nil + } + + // Destroy the temporary server instance. + s = nil + + // Create a new server instance using the configuration we wrote to the disk + // so that everything gets instantiated correctly on the struct. + s2, err := server.FromConfiguration(b, config.Get().System) + return nil, &Installer{ - server: s, + server: s2, } } @@ -75,13 +90,44 @@ func (i *Installer) Uuid() string { return i.server.Uuid } +// Return the server instance. +func (i *Installer) Server() *server.Server { + return i.server +} + // Executes the installer process, creating the server and running through the // associated installation process based on the parameters passed through for // the server instance. -func (i *Installer) Execute() error { +func (i *Installer) Execute() { zap.S().Debugw("beginning installation process for server", zap.String("server", i.server.Uuid)) - return nil + zap.S().Debugw("creating required environment for server instance", zap.String("server", i.server.Uuid)) + if err := i.server.Environment.Create(); err != nil { + zap.S().Errorw("failed to create environment for server", zap.String("server", i.server.Uuid), zap.Error(err)) + return + } +} + +// Writes the server configuration to the disk and return the byte representation +// of the configuration object. This allows us to pass it directly into the +// servers.FromConfiguration() function. +func WriteConfigurationToDisk(s *server.Server) ([]byte, error) { + f, err := os.Create("data/servers/" + s.Uuid + ".yml") + if err != nil { + return nil, err + } + defer f.Close() + + b, err := yaml.Marshal(&s) + if err != nil { + return nil, err + } + + if _, err := f.Write(b); err != nil { + return nil, err + } + + return b, nil } // Returns a string value from the JSON data provided. diff --git a/server/environment_docker.go b/server/environment_docker.go index ae99f7f..f4f96f0 100644 --- a/server/environment_docker.go +++ b/server/environment_docker.go @@ -3,6 +3,7 @@ package server import ( "bufio" "bytes" + "context" "encoding/json" "fmt" "github.com/docker/docker/api/types" @@ -14,7 +15,6 @@ import ( "github.com/pkg/errors" "github.com/pterodactyl/wings/api" "go.uber.org/zap" - "golang.org/x/net/context" "io" "os" "strconv" @@ -385,6 +385,8 @@ func (d *DockerEnvironment) DisableResourcePolling() error { // Creates a new container for the server using all of the data that is currently // available for it. If the container already exists it will be returned. +// +// @todo pull the image being requested if it doesn't exist currently. func (d *DockerEnvironment) Create() error { ctx := context.Background() cli, err := client.NewClientWithOpts(client.FromEnv) @@ -394,6 +396,11 @@ func (d *DockerEnvironment) Create() error { var oomDisabled = true + // Ensure the data directory exists before getting too far through this process. + if err := d.Server.Filesystem.EnsureDataDirectory(); err != nil { + return err + } + // If the container already exists don't hit the user with an error, just return // the current information about it which is what we would do when creating the // container anyways. diff --git a/server/filesystem.go b/server/filesystem.go index de51d50..df87a39 100644 --- a/server/filesystem.go +++ b/server/filesystem.go @@ -553,3 +553,18 @@ func (fs *Filesystem) ListDirectory(p string) ([]*Stat, error) { return out, nil } + +// Ensures that the data directory for the server instance exists. +func (fs *Filesystem) EnsureDataDirectory() error { + if _, err := os.Stat(fs.Path()); err != nil && !os.IsNotExist(err) { + return err + } else if err != nil { + // Create the server data directory because it does not currently exist + // on the system. + if err := os.MkdirAll(fs.Path(), 0600); err != nil { + return err + } + } + + return nil +} \ No newline at end of file