diff --git a/api/_testdata/config.yml b/api/_testdata/config.yml deleted file mode 100644 index 2d04127..0000000 --- a/api/_testdata/config.yml +++ /dev/null @@ -1 +0,0 @@ -authKey: 'existingkey' diff --git a/api/_testdata/servers/existingserver.json b/api/_testdata/servers/existingserver.json deleted file mode 100644 index 10175c4..0000000 --- a/api/_testdata/servers/existingserver.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "uuid": "existingserver", - "keys": { - "existinggkey": [ - "g:*" - ], - "existingspecificskey": [ - "s:test" - ], - "existingglobalskey": [ - "s:*" - ] - } -} diff --git a/api/api.go b/api/api.go deleted file mode 100644 index 7fde00a..0000000 --- a/api/api.go +++ /dev/null @@ -1,95 +0,0 @@ -package api - -import ( - "fmt" - - "github.com/gin-gonic/gin" - "github.com/pterodactyl/wings/config" - "github.com/sirupsen/logrus" - "github.com/spf13/viper" -) - -type InternalAPI struct { - router *gin.Engine -} - -// Configure the API and begin listening on the configured IP and Port. -func (api *InternalAPI) Listen() { - if !viper.GetBool(config.Debug) { - gin.SetMode(gin.ReleaseMode) - } - - api.router = gin.Default() - api.router.RedirectTrailingSlash = false - - // Setup Access-Control origin headers. Down the road once this is closer to - // release we should setup this header properly and lock it down to the domain - // used to run the Panel. - api.router.Use(func(c *gin.Context) { - c.Header("Access-Control-Allow-Origin", "*") - }) - - api.router.OPTIONS("/", func(c *gin.Context) { - c.Header("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") - c.Header("Access-Control-Allow-Headers", "Authorization") - }) - - // Register all of the API route bindings. - api.register() - - l := fmt.Sprintf("%s:%d", viper.GetString(config.APIHost), viper.GetInt(config.APIPort)) - api.router.Run(l) - - logrus.Info("API Server is now listening on %s", l) -} - -// Register routes for v1 of the API. This API should be fully backwards compatable with -// the existing Nodejs Daemon API. -// -// Routes that are not yet completed are commented out. Routes are grouped where possible -// to keep this function organized. -func (api *InternalAPI) register() { - v1 := api.router.Group("/api/v1") - { - v1.GET("", AuthHandler(""), GetIndex) - //v1.PATCH("/config", AuthHandler("c:config"), PatchConfiguration) - - v1.GET("/servers", AuthHandler("c:list"), handleGetServers) - v1.POST("/servers", AuthHandler("c:create"), handlePostServers) - - v1ServerRoutes := v1.Group("/servers/:server") - { - v1ServerRoutes.GET("", AuthHandler("s:get"), handleGetServer) - v1ServerRoutes.PATCH("", AuthHandler("s:config"), handlePatchServer) - v1ServerRoutes.DELETE("", AuthHandler("g:server:delete"), handleDeleteServer) - v1ServerRoutes.POST("/reinstall", AuthHandler("s:install-server"), handlePostServerReinstall) - v1ServerRoutes.POST("/rebuild", AuthHandler("g:server:rebuild"), handlePostServerRebuild) - v1ServerRoutes.POST("/password", AuthHandler(""), handlePostServerPassword) - v1ServerRoutes.POST("/power", AuthHandler("s:power"), handlePostServerPower) - v1ServerRoutes.POST("/command", AuthHandler("s:command"), handlePostServerCommand) - v1ServerRoutes.GET("/log", AuthHandler("s:console"), handleGetConsole) - v1ServerRoutes.POST("/suspend", AuthHandler(""), handlePostServerSuspend) - v1ServerRoutes.POST("/unsuspend", AuthHandler(""), handlePostServerUnsuspend) - } - - //v1ServerFileRoutes := v1.Group("/servers/:server/files") - //{ - // v1ServerFileRoutes.GET("/file/:file", AuthHandler("s:files:read"), handleGetFile) - // v1ServerFileRoutes.GET("/stat/:file", AuthHandler("s:files:"), handleGetFileStat) - // v1ServerFileRoutes.GET("/dir/:directory", AuthHandler("s:files:get"), handleGetDirectory) - // - // v1ServerFileRoutes.POST("/dir/:directory", AuthHandler("s:files:create"), handlePostFilesFolder) - // v1ServerFileRoutes.POST("/file/:file", AuthHandler("s:files:post"), handlePostFile) - // - // v1ServerFileRoutes.POST("/copy/:file", AuthHandler("s:files:copy"), handlePostFileCopy) - // v1ServerFileRoutes.POST("/move/:file", AuthHandler("s:files:move"), handlePostFileMove) - // v1ServerFileRoutes.POST("/rename/:file", AuthHandler("s:files:move"), handlePostFileMove) - // v1ServerFileRoutes.POST("/compress/:file", AuthHandler("s:files:compress"), handlePostFileCompress) - // v1ServerFileRoutes.POST("/decompress/:file", AuthHandler("s:files:decompress"), handlePostFileDecompress) - // - // v1ServerFileRoutes.DELETE("/file/:file", AuthHandler("s:files:delete"), handleDeleteFile) - // - // v1ServerFileRoutes.GET("/download/:token", handleGetDownloadFile) - //} - } -} diff --git a/api/auth.go b/api/auth.go deleted file mode 100644 index f19efee..0000000 --- a/api/auth.go +++ /dev/null @@ -1,123 +0,0 @@ -package api - -import ( - "net/http" - "strings" - - "strconv" - - "github.com/gin-gonic/gin" - "github.com/google/jsonapi" - "github.com/pterodactyl/wings/config" - "github.com/pterodactyl/wings/control" - log "github.com/sirupsen/logrus" -) - -const ( - accessTokenHeader = "Authorization" - - contextVarServer = "server" - contextVarAuth = "auth" -) - -type responseError struct { - Error string `json:"error"` -} - -// AuthorizationManager handles permission checks -type AuthorizationManager interface { - HasPermission(string) bool -} - -type authorizationManager struct { - token string - server control.Server -} - -var _ AuthorizationManager = &authorizationManager{} - -func newAuthorizationManager(token string, server control.Server) *authorizationManager { - return &authorizationManager{ - token: token, - server: server, - } -} - -func (a *authorizationManager) HasPermission(permission string) bool { - if permission == "" { - return true - } - prefix := permission[:1] - if prefix == "c" { - return config.ContainsAuthKey(a.token) - } - if a.server == nil { - log.WithField("permission", permission).Error("Auth: Server required but none found.") - return false - } - if prefix == "g" { - return config.ContainsAuthKey(a.token) - } - if prefix == "s" { - return a.server.HasPermission(a.token, permission) || config.ContainsAuthKey(a.token) - } - return false -} - -// AuthHandler returns a HandlerFunc that checks request authentication -// permission is a permission string describing the required permission to access the route -// -// The AuthHandler looks for an access token header (defined in accessTokenHeader) -// or a `token` request parameter -func AuthHandler(permission string) gin.HandlerFunc { - return func(c *gin.Context) { - requestToken := c.Request.Header.Get(accessTokenHeader) - if requestToken != "" && strings.HasPrefix(requestToken, "Baerer ") { - requestToken = requestToken[7:] - } else { - requestToken = c.Query("token") - } - requestServer := c.Param("server") - var server control.Server - - if requestToken == "" && permission != "" { - sendErrors(c, http.StatusUnauthorized, &jsonapi.ErrorObject{ - Title: "Missing required " + accessTokenHeader + " header or token param.", - Status: strconv.Itoa(http.StatusUnauthorized), - }) - c.Abort() - return - } - if requestServer != "" { - server = control.GetServer(requestServer) - //fmt.Println(server) - if server == nil { - log.WithField("serverUUID", requestServer).Error("Auth: Requested server not found.") - } - } - - auth := newAuthorizationManager(requestToken, server) - - if auth.HasPermission(permission) { - c.Set(contextVarServer, server) - c.Set(contextVarAuth, auth) - return - } - - sendForbidden(c) - c.Abort() - } -} - -// GetContextAuthManager returns a AuthorizationManager contained in -// a gin.Context or nil -func GetContextAuthManager(c *gin.Context) AuthorizationManager { - auth, exists := c.Get(contextVarAuth) - if !exists { - return nil - } - if auth, ok := auth.(AuthorizationManager); ok { - return auth - } - return nil -} diff --git a/api/auth_test.go b/api/auth_test.go deleted file mode 100644 index 692e64a..0000000 --- a/api/auth_test.go +++ /dev/null @@ -1,143 +0,0 @@ -package api - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" - - "github.com/pterodactyl/wings/config" - "github.com/pterodactyl/wings/control" -) - -const configFile = "_testdata/config.yml" - -func TestAuthHandler(t *testing.T) { - gin.SetMode(gin.ReleaseMode) - - t.Run("rejects missing token", func(t *testing.T) { - loadConfiguration(t, false) - - responded, rec := requestMiddlewareWith("c:somepermission", "", "") - - assert.False(t, responded) - assert.Equal(t, http.StatusBadRequest, rec.Code) - }) - - t.Run("rejects c:* with invalid key", func(t *testing.T) { - loadConfiguration(t, false) - - responded, rec := requestMiddlewareWith("c:somepermission", "invalidkey", "") - - assert.False(t, responded) - assert.Equal(t, http.StatusForbidden, rec.Code) - }) - - t.Run("accepts existing c: key", func(t *testing.T) { - loadConfiguration(t, false) - - responded, rec := requestMiddlewareWith("c:somepermission", "existingkey", "") // TODO: working token - - assert.True(t, responded) - assert.Equal(t, http.StatusOK, rec.Code) - }) - - t.Run("rejects not existing server", func(t *testing.T) { - loadConfiguration(t, true) - - responded, rec := requestMiddlewareWith("g:testnotexisting", "existingkey", "notexistingserver") - - assert.False(t, responded) - assert.Equal(t, http.StatusForbidden, rec.Code) - }) - - t.Run("accepts server with existing g: key", func(t *testing.T) { - loadConfiguration(t, true) - - responded, rec := requestMiddlewareWith("g:test", "existingkey", "existingserver") - - assert.True(t, responded) - assert.Equal(t, http.StatusOK, rec.Code) - }) - - t.Run("rejects server with not existing g: key", func(t *testing.T) { - loadConfiguration(t, true) - - responded, rec := requestMiddlewareWith("g:test", "notexistingkey", "existingserver") - - assert.False(t, responded) - assert.Equal(t, http.StatusForbidden, rec.Code) - }) - - t.Run("rejects server with not existing s: key", func(t *testing.T) { - loadConfiguration(t, true) - - responded, rec := requestMiddlewareWith("s:test", "notexistingskey", "existingserver") - - assert.False(t, responded) - assert.Equal(t, http.StatusForbidden, rec.Code) - }) - - t.Run("accepts server with existing s: key with specific permission", func(t *testing.T) { - loadConfiguration(t, true) - - responded, rec := requestMiddlewareWith("s:test", "existingspecificskey", "existingserver") - - assert.True(t, responded) - assert.Equal(t, http.StatusOK, rec.Code) - }) - - t.Run("accepts server with existing s: key with gloabl permission", func(t *testing.T) { - loadConfiguration(t, true) - - responded, rec := requestMiddlewareWith("s:test", "existingglobalskey", "existingserver") - - assert.True(t, responded) - assert.Equal(t, http.StatusOK, rec.Code) - }) - - t.Run("rejects server with existing s: key without permission", func(t *testing.T) { - loadConfiguration(t, true) - - responded, rec := requestMiddlewareWith("s:without", "existingspecificskey", "existingserver") - - assert.False(t, responded) - assert.Equal(t, http.StatusForbidden, rec.Code) - }) -} - -func requestMiddlewareWith(neededPermission string, token string, serverUUID string) (responded bool, recorder *httptest.ResponseRecorder) { - router := gin.New() - responded = false - recorder = httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/"+serverUUID, nil) - - endpoint := "/" - if serverUUID != "" { - endpoint += ":server" - } - - router.GET(endpoint, AuthHandler(neededPermission), func(c *gin.Context) { - c.String(http.StatusOK, "Access granted.") - responded = true - }) - - req.Header.Set(accessTokenHeader, token) - router.ServeHTTP(recorder, req) - return -} - -func loadConfiguration(t *testing.T, serverConfig bool) { - if err := config.LoadConfiguration(configFile); err != nil { - t.Error(err) - return - } - - if serverConfig { - if err := control.LoadServerConfigurations("_testdata/servers/"); err != nil { - t.Error(err) - } - } -} diff --git a/api/handlers.go b/api/handlers.go deleted file mode 100644 index 61bacd4..0000000 --- a/api/handlers.go +++ /dev/null @@ -1,144 +0,0 @@ -package api - -import ( - "fmt" - "net/http" - //"runtime" - - "github.com/gin-gonic/gin" - "github.com/shirou/gopsutil/cpu" - //"github.com/shirou/gopsutil/host" - //"github.com/shirou/gopsutil/mem" - log "github.com/sirupsen/logrus" -) - -func GetIndex(c *gin.Context) { - //auth := GetContextAuthManager(c) - //if auth == nil { - // c.Header("Content-Type", "text/html") - // c.String(http.StatusOK, constants.IndexPage) - //} - - s, err := cpu.Counts(true) - if err != nil { - log.WithError(err).Error("Failed to retrieve host information.") - } - - fmt.Println(s) - i := struct { - Name string - Cpu struct { - Cores int - } - }{ - Name: "Wings", - } - - i.Cpu.Cores = s - - c.JSON(http.StatusOK, i) - return - - //if auth != nil && auth.HasPermission("c:info") { - // hostInfo, err := host.Info() - // if err != nil { - // log.WithError(err).Error("Failed to retrieve host information.") - // } - // cpuInfo, err := cpu.Info() - // if err != nil { - // log.WithError(err).Error("Failed to retrieve CPU information.") - // } - // memInfo, err := mem.VirtualMemory() - // if err != nil { - // log.WithError(err).Error("Failed to retrieve memory information.") - // } - // - // info := struct { - // Name string `json:"name"` - // Version string `json:"version"` - // System struct { - // SystemType string `json:"type"` - // Platform string `json:"platform"` - // Arch string `json:"arch"` - // Release string `json:"release"` - // Cpus int32 `json:"cpus"` - // Freemem uint64 `json:"freemem"` - // } `json:"system"` - // }{ - // Name: "Pterodactyl wings", - // Version: constants.Version, - // } - // info.System.SystemType = hostInfo.OS - // info.System.Platform = hostInfo.Platform - // info.System.Arch = runtime.GOARCH - // info.System.Release = hostInfo.KernelVersion - // info.System.Cpus = cpuInfo[0].Cores - // info.System.Freemem = memInfo.Free - // - // c.JSON(http.StatusOK, info) - // return - //} -} - -type incomingConfiguration struct { - Debug bool `mapstructure:"debug"` - Web struct { - ListenHost string `mapstructure:"host"` - ListenPort int16 `mapstructure:"port"` - SSL struct { - Enabled bool `mapstructure:"enabled"` - Certificate string `mapstructure:"certificate"` - Key string `mapstructure:"key"` - } `mapstructure:"ssl"` - - Uploads struct { - MaximumSize int64 `mapstructure:"maximumSize"` - } `mapstructure:"uploads"` - } `mapstructure:"web"` - - Docker struct { - Socket string `mapstructure:"socket"` - AutoupdateImages bool `mapstructure:"autoupdateImages"` - NetworkInterface string `mapstructure:"networkInterface"` - TimezonePath string `mapstructure:"timezonePath"` - } `mapstructure:"docker"` - - Sftp struct { - Path string `mapstructure:"path"` - Port int16 `mapstructure:"port"` - } `mapstructure:"sftp"` - - Query struct { - KillOnFail bool `mapstructure:"killOnFail"` - FailLimit bool `mapstructure:"failLimit"` - } `mapstructure:"query"` - - Remote string `mapstructure:"remote"` - - Log struct { - Path string `mapstructure:"path"` - Level string `mapstructure:"level"` - DeleteAfterDays int `mapstructure:"deleteAfterDays"` - } `mapstructure:"log"` - - AuthKeys []string `mapstructure:"authKeys"` -} - -// handlePatchConfig handles PATCH /config -func PatchConfiguration(c *gin.Context) { - // reqBody, err := ioutil.ReadAll(c.Request.Body) - // if err != nil { - // log.WithError(err).Error("Failed to read input.") - // return - // } - // reqJSON := new(incomingConfiguration) - // err = json.Unmarshal(reqBody, reqJSON) - // if err != nil { - // log.WithError(err).Error("Failed to decode JSON.") - // return - // } - var json incomingConfiguration - if err := c.BindJSON(&json); err != nil { - log.WithError(err).Error("Failed to bind Json.") - } -} diff --git a/api/handlers_server.go b/api/handlers_server.go deleted file mode 100644 index 3107f12..0000000 --- a/api/handlers_server.go +++ /dev/null @@ -1,190 +0,0 @@ -package api - -import ( - "net/http" - - "strconv" - - "github.com/gin-gonic/gin" - "github.com/google/jsonapi" - "github.com/pterodactyl/wings/control" - log "github.com/sirupsen/logrus" -) - -// GET /servers -func handleGetServers(c *gin.Context) { - servers := control.GetServers() - sendData(c, servers) -} - -// POST /servers -func handlePostServers(c *gin.Context) { - server := control.ServerStruct{} - if err := c.BindJSON(&server); err != nil { - log.WithField("server", server).WithError(err).Error("Failed to parse server request.") - sendErrors(c, http.StatusBadRequest, &jsonapi.ErrorObject{ - Status: strconv.Itoa(http.StatusBadRequest), - Title: "The passed server object is invalid.", - }) - return - } - var srv control.Server - var err error - if srv, err = control.CreateServer(&server); err != nil { - if _, ok := err.(control.ErrServerExists); ok { - log.WithError(err).Error("Cannot create server, it already exists.") - c.Status(http.StatusBadRequest) - sendErrors(c, http.StatusConflict, &jsonapi.ErrorObject{ - Status: strconv.Itoa(http.StatusConflict), - Title: "A server with this ID already exists.", - }) - return - } - log.WithField("server", server).WithError(err).Error("Failed to create server.") - sendInternalError(c, "Failed to create the server", "") - return - } - go func() { - env, err := srv.Environment() - if err != nil { - log.WithField("server", srv).WithError(err).Error("Failed to get server environment.") - } - env.Create() - }() - sendDataStatus(c, http.StatusCreated, srv) -} - -// GET /servers/:server -func handleGetServer(c *gin.Context) { - id := c.Param("server") - server := control.GetServer(id) - if server == nil { - sendErrors(c, http.StatusNotFound, &jsonapi.ErrorObject{ - Code: strconv.Itoa(http.StatusNotFound), - Title: "Server not found.", - Detail: "The requested Server with the id " + id + " couldn't be found.", - }) - return - } - sendData(c, server) -} - -// PATCH /servers/:server -func handlePatchServer(c *gin.Context) { - -} - -// DELETE /servers/:server -func handleDeleteServer(c *gin.Context) { - id := c.Param("server") - server := control.GetServer(id) - if server == nil { - c.Status(http.StatusNotFound) - return - } - - env, err := server.Environment() - if err != nil { - sendInternalError(c, "The server could not be deleted.", "") - return - } - if err := env.Destroy(); err != nil { - log.WithError(err).Error("Failed to delete server, the environment couldn't be destroyed.") - sendInternalError(c, "The server could not be deleted.", "The server environment couldn't be destroyed.") - return - } - - if err := control.DeleteServer(id); err != nil { - log.WithError(err).Error("Failed to delete server.") - sendInternalError(c, "The server could not be deleted.", "") - return - } - c.Status(http.StatusNoContent) -} - -func handlePostServerReinstall(c *gin.Context) { - -} - -func handlePostServerPassword(c *gin.Context) { - -} - -func handlePostServerRebuild(c *gin.Context) { - -} - -// POST /servers/:server/power -func handlePostServerPower(c *gin.Context) { - server := getServerFromContext(c) - if server == nil { - c.Status(http.StatusNotFound) - return - } - - auth := GetContextAuthManager(c) - if auth == nil { - sendInternalError(c, "An internal error occured.", "") - return - } - - switch c.Query("action") { - case "start": - { - if !auth.HasPermission("s:power:start") { - sendForbidden(c) - return - } - server.Start() - } - case "stop": - { - if !auth.HasPermission("s:power:stop") { - sendForbidden(c) - return - } - server.Stop() - } - case "restart": - { - if !auth.HasPermission("s:power:restart") { - sendForbidden(c) - return - } - server.Restart() - } - case "kill": - { - if !auth.HasPermission("s:power:kill") { - sendForbidden(c) - return - } - server.Kill() - } - default: - { - c.Status(http.StatusBadRequest) - } - } -} - -// POST /servers/:server/command -func handlePostServerCommand(c *gin.Context) { - server := getServerFromContext(c) - cmd := c.Query("command") - server.Exec(cmd) - c.Status(204) -} - -func handleGetConsole(c *gin.Context) { - server := getServerFromContext(c) - server.Websockets().Upgrade(c.Writer, c.Request) -} - -func handlePostServerSuspend(c *gin.Context) { - -} - -func handlePostServerUnsuspend(c *gin.Context) { - -} diff --git a/api/handlers_server_test.go b/api/handlers_server_test.go deleted file mode 100644 index 34d3911..0000000 --- a/api/handlers_server_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package api - -import ( - "bytes" - "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/gin-gonic/gin" - "github.com/pterodactyl/wings/control" - "github.com/stretchr/testify/assert" -) - -func TestHandleGetServers(t *testing.T) { - router := gin.New() - req, _ := http.NewRequest("GET", "/servers", nil) - - router.GET("/servers", handleGetServers) - - t.Run("returns an empty json array when no servers are configured", func(t *testing.T) { - rec := httptest.NewRecorder() - router.ServeHTTP(rec, req) - body, err := ioutil.ReadAll(rec.Body) - assert.Equal(t, http.StatusOK, rec.Code) - assert.Nil(t, err) - assert.Equal(t, "[]\n", string(body)) - }) - - t.Run("returns an array of servers", func(t *testing.T) { - - }) -} - -var testServer = control.ServerStruct{ - ID: "id1", -} - -func TestHandlePostServer(t *testing.T) { - // We need to decide how deep we want to go with testing here - // Should we just verify it works in general or test validation and - // minimal required config options as well? - // This will also vary depending on the Environment the server runs in -} - -func TestHandleGetServer(t *testing.T) { - router := gin.New() - router.GET("/servers/:server", handleGetServer) - - control.CreateServer(&testServer) - - t.Run("returns not found when the server doesn't exist", func(t *testing.T) { - req, _ := http.NewRequest("GET", "/servers/id0", nil) - rec := httptest.NewRecorder() - router.ServeHTTP(rec, req) - assert.Equal(t, http.StatusNotFound, rec.Code) - }) - - t.Run("returns the server as json", func(t *testing.T) { - req, _ := http.NewRequest("GET", "/servers/id1", nil) - rec := httptest.NewRecorder() - router.ServeHTTP(rec, req) - assert.Equal(t, http.StatusOK, rec.Code) - }) -} - -func TestHandlePatchServer(t *testing.T) { - -} - -func TestHandleDeleteServer(t *testing.T) { - router := gin.New() - router.DELETE("/servers/:server", handleDeleteServer) - - control.CreateServer(&testServer) - - t.Run("returns not found when the server doesn't exist", func(t *testing.T) { - req, _ := http.NewRequest("DELETE", "/servers/id0", nil) - rec := httptest.NewRecorder() - router.ServeHTTP(rec, req) - assert.Equal(t, http.StatusNotFound, rec.Code) - }) - - t.Run("deletes the server", func(t *testing.T) { - req, _ := http.NewRequest("DELETE", "/servers/id1", nil) - rec := httptest.NewRecorder() - router.ServeHTTP(rec, req) - assert.Equal(t, http.StatusOK, rec.Code) - }) -} - -func bodyToJSON(body *bytes.Buffer, v interface{}) error { - reqBody, err := ioutil.ReadAll(body) - if err != nil { - return err - } - err = json.Unmarshal(reqBody, v) - if err != nil { - return err - } - return nil -} diff --git a/api/handlers_test.go b/api/handlers_test.go deleted file mode 100644 index 9ddcc2c..0000000 --- a/api/handlers_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package api - -import ( - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" -) - -func TestHandleGetIndex(t *testing.T) { - router := gin.New() - recorder := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/", nil) - - router.GET("/", handleGetIndex) - router.ServeHTTP(recorder, req) - - assert.Equal(t, http.StatusOK, recorder.Code) -} - -func TestHandlePatchConfig(t *testing.T) { - router := gin.New() - recorder := httptest.NewRecorder() - - req, _ := http.NewRequest("PATCH", "/", strings.NewReader("{}")) - - router.PATCH("/", handlePatchConfig) - router.ServeHTTP(recorder, req) - - assert.Equal(t, http.StatusOK, recorder.Code) -} diff --git a/api/utils.go b/api/utils.go deleted file mode 100644 index 9f00adb..0000000 --- a/api/utils.go +++ /dev/null @@ -1,45 +0,0 @@ -package api - -import ( - "net/http" - "strconv" - - "github.com/gin-gonic/gin" - "github.com/google/jsonapi" - "github.com/pterodactyl/wings/control" -) - -func getServerFromContext(context *gin.Context) control.Server { - return control.GetServer(context.Param("server")) -} - -func sendErrors(c *gin.Context, s int, err ...*jsonapi.ErrorObject) { - c.Status(s) - c.Header("Content-Type", "application/json") - jsonapi.MarshalErrors(c.Writer, err) -} - -func sendInternalError(c *gin.Context, title string, detail string) { - sendErrors(c, http.StatusInternalServerError, &jsonapi.ErrorObject{ - Status: strconv.Itoa(http.StatusInternalServerError), - Title: title, - Detail: detail, - }) -} - -func sendForbidden(c *gin.Context) { - sendErrors(c, http.StatusForbidden, &jsonapi.ErrorObject{ - Title: "The provided token has insufficient permissions to perform this action.", - Status: strconv.Itoa(http.StatusForbidden), - }) -} - -func sendData(c *gin.Context, payload interface{}) { - sendDataStatus(c, http.StatusOK, payload) -} - -func sendDataStatus(c *gin.Context, status int, payload interface{}) { - c.Status(status) - c.Header("Content-Type", "application/json") - jsonapi.MarshalPayload(c.Writer, payload) -} diff --git a/api/websockets/collection.go b/api/websockets/collection.go deleted file mode 100644 index dc5425b..0000000 --- a/api/websockets/collection.go +++ /dev/null @@ -1,120 +0,0 @@ -package websockets - -import ( - "encoding/json" - "net/http" - "time" - - "github.com/gorilla/websocket" - log "github.com/sirupsen/logrus" -) - -const ( - writeWait = 10 * time.Second - pongWait = 60 * time.Second - - pingPeriod = pongWait * 9 / 10 - - maxMessageSize = 512 -) - -var wsupgrader = websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, - CheckOrigin: func(r *http.Request) bool { - return true - }, -} - -type websocketMap map[*Socket]bool - -type Collection struct { - sockets websocketMap - - Broadcast chan Message - - register chan *Socket - unregister chan *Socket - close chan bool -} - -//var _ io.Writer = &Collection{} - -func NewCollection() *Collection { - return &Collection{ - Broadcast: make(chan Message), - register: make(chan *Socket), - unregister: make(chan *Socket), - close: make(chan bool), - sockets: make(websocketMap), - } -} - -func (c *Collection) Upgrade(w http.ResponseWriter, r *http.Request) { - socket, err := wsupgrader.Upgrade(w, r, nil) - if err != nil { - log.WithError(err).Error("Failed to upgrade to websocket") - return - } - s := &Socket{ - collection: c, - conn: socket, - send: make(chan []byte, 256), - } - c.register <- s - - go s.readPump() - go s.writePump() -} - -func (c *Collection) Add(s *Socket) { - c.register <- s -} - -func (c *Collection) Remove(s *Socket) { - c.unregister <- s -} - -func (c *Collection) Run() { - defer func() { - for s := range c.sockets { - close(s.send) - delete(c.sockets, s) - } - close(c.register) - close(c.unregister) - close(c.Broadcast) - close(c.close) - }() - for { - select { - case s := <-c.register: - c.sockets[s] = true - case s := <-c.unregister: - if _, ok := c.sockets[s]; ok { - delete(c.sockets, s) - close(s.send) - } - case m := <-c.Broadcast: - b, err := json.Marshal(m) - if err != nil { - log.WithError(err).Error("Failed to encode websocket message.") - continue - } - for s := range c.sockets { - select { - case s.send <- b: - default: - close(s.send) - delete(c.sockets, s) - } - } - case <-c.close: - return - } - } -} - -func (c *Collection) Close() { - c.close <- true -} diff --git a/api/websockets/message.go b/api/websockets/message.go deleted file mode 100644 index 8ce2230..0000000 --- a/api/websockets/message.go +++ /dev/null @@ -1,59 +0,0 @@ -package websockets - -type MessageType string - -const ( - MessageTypeProc MessageType = "proc" - MessageTypeConsole MessageType = "console" - MessageTypeStatus MessageType = "status" -) - -// Message is a message that can be sent using a websocket in JSON format -type Message struct { - // Type is the type of a websocket message - Type MessageType `json:"type"` - // Payload is the payload of the message - // The payload needs to support encoding in JSON - Payload interface{} `json:"payload"` -} - -type ProcPayload struct { - Memory int `json:"memory"` - CPUCores []int `json:"cpu_cores"` - CPUTotal int `json:"cpu_total"` - Disk int `json:"disk"` -} - -type ConsoleSource string -type ConsoleLevel string - -const ( - ConsoleSourceWings ConsoleSource = "wings" - ConsoleSourceServer ConsoleSource = "server" - - ConsoleLevelPlain ConsoleLevel = "plain" - ConsoleLevelInfo ConsoleLevel = "info" - ConsoleLevelWarn ConsoleLevel = "warn" - ConsoleLevelError ConsoleLevel = "error" -) - -type ConsolePayload struct { - // Source is the source of the console line, either ConsoleSourceWings or ConsoleSourceServer - Source ConsoleSource `json:"source"` - // Level is the level of the message. - // Use one of plain, info, warn or error. If omitted the default is plain. - Level ConsoleLevel `json:"level,omitempty"` - // Line is the actual line to print to the console. - Line string `json:"line"` -} - -func (c *Collection) Log(l ConsoleLevel, m string) { - c.Broadcast <- Message{ - Type: MessageTypeConsole, - Payload: ConsolePayload{ - Source: ConsoleSourceWings, - Level: l, - Line: m, - }, - } -} diff --git a/api/websockets/socket.go b/api/websockets/socket.go deleted file mode 100644 index fe977cd..0000000 --- a/api/websockets/socket.go +++ /dev/null @@ -1,81 +0,0 @@ -package websockets - -import "github.com/gorilla/websocket" -import ( - "time" - - log "github.com/sirupsen/logrus" -) - -type Socket struct { - collection *Collection - - conn *websocket.Conn - - send chan []byte -} - -func (s *Socket) readPump() { - defer func() { - s.collection.unregister <- s - s.conn.Close() - }() - s.conn.SetReadLimit(maxMessageSize) - s.conn.SetReadDeadline(time.Now().Add(pongWait)) - s.conn.SetPongHandler(func(string) error { - s.conn.SetReadDeadline(time.Now().Add(pongWait)) - return nil - }) - for { - t, m, err := s.conn.ReadMessage() - if err != nil { - if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) { - log.WithError(err).Debug("Websocket closed unexpectedly.") - } - return - } - // Handle websocket responses somehow - if t == websocket.TextMessage { - log.Debug("Received websocket message: " + string(m)) - } - } -} - -func (s *Socket) writePump() { - ticker := time.NewTicker(pingPeriod) - defer func() { - ticker.Stop() - s.conn.Close() - }() - for { - select { - case m, ok := <-s.send: - s.conn.SetWriteDeadline(time.Now().Add(writeWait)) - if !ok { - // The collection closed the channel - s.conn.WriteMessage(websocket.CloseMessage, []byte{}) - return - } - w, err := s.conn.NextWriter(websocket.TextMessage) - if err != nil { - return - } - w.Write([]byte{'['}) - w.Write(m) - n := len(s.send) - 1 - for i := 0; i < n; i++ { - w.Write([]byte{','}) - w.Write(<-s.send) - } - w.Write([]byte{']'}) - if err := w.Close(); err != nil { - return - } - case <-ticker.C: - s.conn.SetWriteDeadline(time.Now().Add(writeWait)) - if err := s.conn.WriteMessage(websocket.PingMessage, nil); err != nil { - return - } - } - } -} diff --git a/control/console_handler.go b/control/console_handler.go deleted file mode 100644 index cc88ea3..0000000 --- a/control/console_handler.go +++ /dev/null @@ -1,33 +0,0 @@ -package control - -import ( - "io" - - "github.com/pterodactyl/wings/api/websockets" -) - -type ConsoleHandler struct { - Websockets *websockets.Collection - HandlerFunc *func(string) -} - -var _ io.Writer = ConsoleHandler{} - -func (c ConsoleHandler) Write(b []byte) (n int, e error) { - l := make([]byte, len(b)) - copy(l, b) - line := string(l) - m := websockets.Message{ - Type: websockets.MessageTypeConsole, - Payload: websockets.ConsolePayload{ - Line: line, - Level: websockets.ConsoleLevelPlain, - Source: websockets.ConsoleSourceServer, - }, - } - c.Websockets.Broadcast <- m - if c.HandlerFunc != nil { - (*c.HandlerFunc)(line) - } - return len(b), nil -} diff --git a/control/environment.go b/control/environment.go deleted file mode 100644 index 061bdeb..0000000 --- a/control/environment.go +++ /dev/null @@ -1,72 +0,0 @@ -package control - -// Environment provides abstraction of different environments -type Environment interface { - // Create creates the environment - Create() error - - // Destroy destroys the environment - Destroy() error - - // Start starts the service in the environment - Start() error - - // Stop stops the service in the environment - Stop() error - - // Kill kills the service in the environment - Kill() error - - // Execute a command in the environment - // This sends the command to the standard input of the environment - Exec(command string) error - - // Exists checks wether the Environment exists or not - Exists() bool - - // ReCreate recreates the environment by first Destroying and then Creating - ReCreate() error -} - -type baseEnvironment struct { -} - -// Ensure BaseEnvironment implements Environment -var _ Environment = &baseEnvironment{} - -func (env *baseEnvironment) Create() error { - return nil -} - -func (env *baseEnvironment) Destroy() error { - return nil -} - -func (env *baseEnvironment) Start() error { - return nil -} - -func (env *baseEnvironment) Stop() error { - return nil -} - -func (env *baseEnvironment) Kill() error { - return nil -} - -func (env *baseEnvironment) Exists() bool { - return false -} - -func (env *baseEnvironment) ReCreate() error { - if env.Exists() { - if err := env.Destroy(); err != nil { - return err - } - } - return env.Create() -} - -func (env *baseEnvironment) Exec(command string) error { - return nil -} diff --git a/control/environment_docker.go b/control/environment_docker.go deleted file mode 100644 index 1d7daec..0000000 --- a/control/environment_docker.go +++ /dev/null @@ -1,245 +0,0 @@ -package control - -import ( - "context" - "io" - "os" - "strings" - "time" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/client" - "github.com/pterodactyl/wings/constants" - log "github.com/sirupsen/logrus" -) - -type dockerEnvironment struct { - baseEnvironment - - client *client.Client - hires types.HijackedResponse - attached bool - - server *ServerStruct -} - -// Ensure DockerEnvironment implements Environment -var _ Environment = &dockerEnvironment{} - -// NewDockerEnvironment creates a new docker enviornment -// instance and connects to the docker client on the host system -// If the container is already running it will try to reattach -// to the running container -func NewDockerEnvironment(server *ServerStruct) (Environment, error) { - env := dockerEnvironment{} - - env.server = server - env.attached = false - - cli, err := client.NewEnvClient() - env.client = cli - ctx := context.TODO() - cli.NegotiateAPIVersion(ctx) - - if err != nil { - log.WithError(err).Fatal("Failed to connect to docker.") - return nil, err - } - - if env.server.DockerContainer.ID != "" { - if err := env.inspectContainer(ctx); err != nil { - log.WithError(err).Error("Failed to find the container with stored id, removing id.") - env.server.DockerContainer.ID = "" - env.server.Save() - } - } - - return &env, nil -} - -func (env *dockerEnvironment) inspectContainer(ctx context.Context) error { - _, err := env.client.ContainerInspect(ctx, env.server.DockerContainer.ID) - return err -} - -func (env *dockerEnvironment) attach() error { - if env.attached { - return nil - } - - cw := ConsoleHandler{ - Websockets: env.server.websockets, - } - - var err error - env.hires, err = env.client.ContainerAttach(context.TODO(), env.server.DockerContainer.ID, - types.ContainerAttachOptions{ - Stdin: true, - Stdout: true, - Stderr: true, - Stream: true, - }) - - if err != nil { - log.WithField("server", env.server.ID).WithError(err).Error("Failed to attach to docker container.") - return err - } - env.attached = true - - go func() { - defer env.hires.Close() - defer func() { - env.attached = false - }() - io.Copy(cw, env.hires.Reader) - }() - - return nil -} - -// Create creates the docker container for the environment and applies all -// settings to it -func (env *dockerEnvironment) Create() error { - log.WithField("server", env.server.ID).Debug("Creating docker environment") - - ctx := context.TODO() - - if err := env.pullImage(ctx); err != nil { - log.WithError(err).WithField("image", env.server.GetService().DockerImage).WithField("server", env.server.ID).Error("Failed to pull docker image.") - return err - } - - if err := os.MkdirAll(env.server.dataPath(), constants.DefaultFolderPerms); err != nil { - return err - } - - // Create docker container - // TODO: apply cpu, io, disk limits. - - containerConfig := &container.Config{ - Image: env.server.GetService().DockerImage, - Cmd: strings.Split(env.server.StartupCommand, " "), - AttachStdin: true, - OpenStdin: true, - AttachStdout: true, - AttachStderr: true, - Tty: true, - Hostname: constants.DockerContainerPrefix + env.server.UUIDShort(), - } - - containerHostConfig := &container.HostConfig{ - Resources: container.Resources{ - Memory: env.server.Settings.Memory, - MemorySwap: env.server.Settings.Swap, - }, - // TODO: Allow custom binds via some kind of settings in the service - Binds: []string{env.server.dataPath() + ":/home/container"}, - // TODO: Add port bindings - } - - containerHostConfig.Memory = 0 - - container, err := env.client.ContainerCreate(ctx, containerConfig, containerHostConfig, nil, constants.DockerContainerPrefix+env.server.UUIDShort()) - if err != nil { - log.WithError(err).WithField("server", env.server.ID).Error("Failed to create docker container") - return err - } - - env.server.DockerContainer.ID = container.ID - env.server.Save() - - log.WithField("server", env.server.ID).Debug("Docker environment created") - return nil -} - -// Destroy removes the environment's docker container -func (env *dockerEnvironment) Destroy() error { - log.WithField("server", env.server.ID).Debug("Destroying docker environment") - - ctx := context.TODO() - - if err := env.inspectContainer(ctx); err != nil { - log.WithError(err).Debug("Container not found error") - log.WithField("server", env.server.ID).Debug("Container not found, docker environment destroyed already.") - return nil - } - - if err := env.client.ContainerRemove(ctx, env.server.DockerContainer.ID, types.ContainerRemoveOptions{}); err != nil { - log.WithError(err).WithField("server", env.server.ID).Error("Failed to destroy docker environment") - return err - } - - log.WithField("server", env.server.ID).Debug("Docker environment destroyed") - return nil -} - -func (env *dockerEnvironment) Exists() bool { - if err := env.inspectContainer(context.TODO()); err != nil { - return false - } - return true -} - -// Start starts the environment's docker container -func (env *dockerEnvironment) Start() error { - log.WithField("server", env.server.ID).Debug("Starting service in docker environment") - if err := env.attach(); err != nil { - log.WithError(err).Error("Failed to attach to docker container") - } - - if err := env.client.ContainerStart(context.TODO(), env.server.DockerContainer.ID, types.ContainerStartOptions{}); err != nil { - log.WithError(err).Error("Failed to start docker container") - return err - } - return nil -} - -// Stop stops the environment's docker container -func (env *dockerEnvironment) Stop() error { - log.WithField("server", env.server.ID).Debug("Stopping service in docker environment") - - // TODO: Decide after what timeout to kill the container, currently 30 seconds - timeout := 30 * time.Second - if err := env.client.ContainerStop(context.TODO(), env.server.DockerContainer.ID, &timeout); err != nil { - log.WithError(err).Error("Failed to stop docker container") - return err - } - return nil -} - -func (env *dockerEnvironment) Kill() error { - log.WithField("server", env.server.ID).Debug("Killing service in docker environment") - - if err := env.client.ContainerKill(context.TODO(), env.server.DockerContainer.ID, "KILL"); err != nil { - log.WithError(err).Error("Failed to kill docker container") - return err - } - return nil -} - -// Exec sends commands to the standard input of the docker container -func (env *dockerEnvironment) Exec(command string) error { - log.Debug("Command: " + command) - _, err := env.hires.Conn.Write([]byte(command + "\n")) - return err -} - -func (env *dockerEnvironment) pullImage(ctx context.Context) error { - // Split image repository and tag - //imageParts := strings.Split(env.server.GetService().DockerImage, ":") - //imageRepoParts := strings.Split(imageParts[0], "/") - //if len(imageRepoParts) >= 3 { - // TODO: Handle possibly required authentication - //} - - // Pull docker image - log.WithField("image", env.server.GetService().DockerImage).Debug("Pulling docker image") - - rc, err := env.client.ImagePull(ctx, env.server.GetService().DockerImage, types.ImagePullOptions{}) - if err != nil { - return err - } - defer rc.Close() - return nil -} diff --git a/control/environment_docker_test.go b/control/environment_docker_test.go deleted file mode 100644 index f5ce62e..0000000 --- a/control/environment_docker_test.go +++ /dev/null @@ -1,133 +0,0 @@ -package control - -import ( - "context" - "fmt" - "testing" - - "github.com/pterodactyl/wings/api/websockets" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/client" - "github.com/pterodactyl/wings/config" - "github.com/spf13/viper" - "github.com/stretchr/testify/assert" -) - -func testServer() *ServerStruct { - viper.SetDefault(config.DataPath, "./test_data") - return &ServerStruct{ - ID: "testuuid-something-something", - Service: &Service{ - DockerImage: "alpine:latest", - }, - StartupCommand: "/bin/ash echo hello && sleep 100", - websockets: websockets.NewCollection(), - } -} - -func TestNewDockerEnvironment(t *testing.T) { - env, err := createTestDockerEnv(nil) - - assert.Nil(t, err) - assert.NotNil(t, env) - assert.NotNil(t, env.client) -} - -func TestNewDockerEnvironmentExisting(t *testing.T) { - eenv, _ := createTestDockerEnv(nil) - eenv.Create() - - env, err := createTestDockerEnv(eenv.server) - - assert.Nil(t, err) - assert.NotNil(t, env) - assert.NotNil(t, env.server.DockerContainer) - - eenv.Destroy() -} - -func TestCreateDockerEnvironment(t *testing.T) { - env, _ := createTestDockerEnv(nil) - - err := env.Create() - - a := assert.New(t) - a.Nil(err) - a.NotNil(env.server.DockerContainer.ID) - - if err := env.client.ContainerRemove(context.TODO(), env.server.DockerContainer.ID, types.ContainerRemoveOptions{}); err != nil { - fmt.Println(err) - } -} - -func TestDestroyDockerEnvironment(t *testing.T) { - env, _ := createTestDockerEnv(nil) - env.Create() - - err := env.Destroy() - - _, ierr := env.client.ContainerInspect(context.TODO(), env.server.DockerContainer.ID) - - assert.Nil(t, err) - assert.True(t, client.IsErrNotFound(ierr)) -} - -func TestStartDockerEnvironment(t *testing.T) { - env, _ := createTestDockerEnv(nil) - env.Create() - err := env.Start() - - i, ierr := env.client.ContainerInspect(context.TODO(), env.server.DockerContainer.ID) - - assert.Nil(t, err) - assert.Nil(t, ierr) - assert.True(t, i.State.Running) - - env.client.ContainerKill(context.TODO(), env.server.DockerContainer.ID, "KILL") - env.Destroy() -} - -func TestStopDockerEnvironment(t *testing.T) { - env, _ := createTestDockerEnv(nil) - env.Create() - env.Start() - err := env.Stop() - - i, ierr := env.client.ContainerInspect(context.TODO(), env.server.DockerContainer.ID) - - assert.Nil(t, err) - assert.Nil(t, ierr) - assert.False(t, i.State.Running) - - env.client.ContainerKill(context.TODO(), env.server.DockerContainer.ID, "KILL") - env.Destroy() -} - -func TestKillDockerEnvironment(t *testing.T) { - env, _ := createTestDockerEnv(nil) - env.Create() - env.Start() - err := env.Kill() - - i, ierr := env.client.ContainerInspect(context.TODO(), env.server.DockerContainer.ID) - - assert.Nil(t, err) - assert.Nil(t, ierr) - assert.False(t, i.State.Running) - - env.client.ContainerKill(context.TODO(), env.server.DockerContainer.ID, "KILL") - env.Destroy() -} - -func TestExecDockerEnvironment(t *testing.T) { - -} - -func createTestDockerEnv(s *ServerStruct) (*dockerEnvironment, error) { - if s == nil { - s = testServer() - } - env, err := NewDockerEnvironment(s) - return env.(*dockerEnvironment), err -} diff --git a/control/server.go b/control/server.go deleted file mode 100644 index c21fcd3..0000000 --- a/control/server.go +++ /dev/null @@ -1,235 +0,0 @@ -package control - -import ( - "errors" - - "github.com/pterodactyl/wings/api/websockets" - log "github.com/sirupsen/logrus" -) - -type Status string - -const ( - StatusStopped Status = "stopped" - StatusStarting Status = "starting" - StatusRunning Status = "running" - StatusStopping Status = "stopping" -) - -// 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) - Websockets() *websockets.Collection - - 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" jsonapi:"primary,server"` - - // 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 `json:"-" jsonapi:"relation,service"` - environment Environment - - // StartupCommand is the command executed in the environment to start the server - StartupCommand string `json:"startupCommand" jsonapi:"attr,startup_command"` - - // DockerContainer holds information regarding the docker container when the server - // is running in a docker environment - DockerContainer dockerContainer `json:"dockerContainer" jsonapi:"attr,docker_container"` - - // EnvironmentVariables are set in the Environment the server is running in - EnvironmentVariables map[string]string `json:"environmentVariables" jsonapi:"attr,environment_variables"` - - // Allocations contains the ports and ip addresses assigned to the server - Allocations allocations `json:"allocation" jsonapi:"attr,allocations"` - - // Settings are the environment settings and limitations for the server - Settings settings `json:"settings" jsonapi:"attr,settings"` - - // Keys are some auth keys we will hopefully replace by something better. - // TODO remove - Keys map[string][]string `json:"keys"` - - websockets *websockets.Collection - status Status -} - -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) - -// 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 { - delete(servers, server.ID) - return nil, err - } - if err := server.init(); err != nil { - DeleteServer(server.ID) - 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) init() error { - // TODO: Properly use the correct service, mock for now. - s.Service = &Service{ - DockerImage: "quay.io/pterodactyl/core:java", - EnvironmentName: "docker", - } - s.status = StatusStopped - - s.websockets = websockets.NewCollection() - go s.websockets.Run() - - var err error - if s.environment == nil { - switch s.GetService().EnvironmentName { - case "docker": - s.environment, err = NewDockerEnvironment(s) - default: - log.WithField("service", s.ServiceName).Error("Invalid environment name") - return errors.New("Invalid environment name") - } - } - return err -} - -func (s *ServerStruct) Start() error { - s.SetStatus(StatusStarting) - env, err := s.Environment() - if err != nil { - s.SetStatus(StatusStopped) - return err - } - if !env.Exists() { - if err := env.Create(); err != nil { - s.SetStatus(StatusStopped) - return err - } - } - return env.Start() -} - -func (s *ServerStruct) Stop() error { - s.SetStatus(StatusStopping) - env, err := s.Environment() - if err != nil { - s.SetStatus(StatusRunning) - 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() -} diff --git a/control/server_persistence.go b/control/server_persistence.go deleted file mode 100644 index 14c2520..0000000 --- a/control/server_persistence.go +++ /dev/null @@ -1,108 +0,0 @@ -package control - -import ( - "encoding/json" - "io/ioutil" - "os" - "path/filepath" - - "github.com/pterodactyl/wings/config" - "github.com/pterodactyl/wings/constants" - log "github.com/sirupsen/logrus" - "github.com/spf13/viper" -) - -// 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 - } - if err := server.init(); 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) -} - -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 { - p, err := filepath.Abs(viper.GetString(config.DataPath)) - if err != nil { - log.WithError(err).WithField("server", s.ID).Error("Failed to get absolute data path for server.") - p = viper.GetString(config.DataPath) - } - return filepath.Join(p, 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) -} diff --git a/control/server_util.go b/control/server_util.go deleted file mode 100644 index d5d6ada..0000000 --- a/control/server_util.go +++ /dev/null @@ -1,49 +0,0 @@ -package control - -import ( - "strings" - - "github.com/pterodactyl/wings/api/websockets" -) - -func (s *ServerStruct) SetStatus(st Status) { - s.status = st - s.websockets.Broadcast <- websockets.Message{ - Type: websockets.MessageTypeStatus, - Payload: s.status, - } -} - -// Service returns the server's service configuration -func (s *ServerStruct) GetService() *Service { - 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) { - return s.environment, nil -} - -func (s *ServerStruct) Websockets() *websockets.Collection { - return s.websockets -} - -// 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 -} diff --git a/control/service.go b/control/service.go deleted file mode 100644 index 8b7f06c..0000000 --- a/control/service.go +++ /dev/null @@ -1,10 +0,0 @@ -package control - -type Service struct { - server *Server - - // EnvironmentName is the name of the environment used by the service - EnvironmentName string `json:"environmentName" jsonapi:"primary,service"` - - DockerImage string `json:"dockerImage" jsonapi:"attr,docker_image"` -} diff --git a/go.sum b/go.sum index 5da01a0..2df81ad 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,9 @@ github.com/Microsoft/go-winio v0.4.7/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docker/distribution v0.0.0-20180327202408-83389a148052 h1:bYklS+YB8BZreSEY+/WqaH+S8upfuYf0Hq/EmNOQMIA= github.com/docker/distribution v0.0.0-20180327202408-83389a148052/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo= github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF+n1M6o= github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.3.2 h1:Kjm80apys7gTtfVmCvVY8gwu10uofaFSrmAKOVrtueE= github.com/docker/go-units v0.3.2/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= @@ -24,14 +20,11 @@ github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPp github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce h1:aP+C+YbHZfOQlutA4p4soHi7rVUqHQdWEVMSkHfDTqY= github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo= github.com/rifflock/lfshook v0.0.0-20180227222202-bf539943797a/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM= github.com/shirou/gopsutil v0.0.0-20180227225847-5776ff9c7c5d/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= @@ -44,19 +37,14 @@ github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/ugorji/go v0.0.0-20180112141927-9831f2c3ac10/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= -go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180330210355-12892e8c234f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/net v0.0.0-20180330215511-b68f30494add h1:oGr9qHpQTQvl/BmeWw95ZrQKahW4qdIPUiGfQkJYDsA= golang.org/x/net v0.0.0-20180330215511-b68f30494add/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/sys v0.0.0-20180329131831-378d26f46672/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=