Merge develop into feature/file-uploads

This commit is contained in:
Matthew Penner
2020-07-31 16:31:06 -06:00
38 changed files with 1288 additions and 625 deletions

View File

@@ -33,7 +33,7 @@ func TrackedError(err error) *RequestError {
// generated this server for the purposes of logging.
func TrackedServerError(err error, s *server.Server) *RequestError {
return &RequestError{
Err: err,
Err: errors.WithStack(err),
Uuid: uuid.Must(uuid.NewRandom()).String(),
Message: "",
server: s,

View File

@@ -48,7 +48,7 @@ func AuthorizationMiddleware(c *gin.Context) {
// Helper function to fetch a server out of the servers collection stored in memory.
func GetServer(uuid string) *server.Server {
return server.GetServers().Find(func(s *server.Server) bool {
return uuid == s.Uuid
return uuid == s.Id()
})
}

View File

@@ -85,6 +85,7 @@ func Configure() *gin.Engine {
files.POST("/create-directory", postServerCreateDirectory)
files.POST("/delete", postServerDeleteFiles)
files.POST("/compress", postServerCompressFiles)
files.POST("/decompress", postServerDecompressFiles)
}
backup := server.Group("/backup")

View File

@@ -13,7 +13,9 @@ import (
// Returns a single server from the collection of servers.
func getServer(c *gin.Context) {
c.JSON(http.StatusOK, GetServer(c.Param("server")))
s := GetServer(c.Param("server"))
c.JSON(http.StatusOK, s.Proc())
}
// Returns the logs for a given server instance.
@@ -64,7 +66,7 @@ func postServerPower(c *gin.Context) {
//
// We don't really care about any of the other actions at this point, they'll all result
// in the process being stopped, which should have happened anyways if the server is suspended.
if (data.Action == "start" || data.Action == "restart") && s.Suspended {
if (data.Action == "start" || data.Action == "restart") && s.IsSuspended() {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"error": "Cannot start or restart a server that is suspended.",
})
@@ -162,7 +164,7 @@ func deleteServer(c *gin.Context) {
// Immediately suspend the server to prevent a user from attempting
// to start it while this process is running.
s.Suspended = true
s.Config().SetSuspended(true)
// If the server is currently installing, abort it.
if s.IsInstalling() {
@@ -200,9 +202,9 @@ func deleteServer(c *gin.Context) {
}
}(s.Filesystem.Path())
var uuid = s.Uuid
var uuid = s.Id()
server.GetServers().Remove(func(s2 *server.Server) bool {
return s2.Uuid == uuid
return s2.Id() == uuid
})
// Deallocate the reference to this server.

View File

@@ -3,7 +3,6 @@ package router
import (
"bufio"
"context"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/router/tokens"
@@ -83,6 +82,13 @@ func getServerListDirectory(c *gin.Context) {
stats, err := s.Filesystem.ListDirectory(d)
if err != nil {
if err.Error() == "readdirent: not a directory" {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
"error": "The requested directory does not exist.",
})
return
}
TrackedServerError(err, s).AbortWithServerError(c)
return
}
@@ -175,7 +181,7 @@ func postServerDeleteFiles(c *gin.Context) {
if len(data.Files) == 0 {
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
"error": "No files were specified for deletion.",
"error": "No files were specififed for deletion.",
})
return
}
@@ -283,6 +289,39 @@ func postServerCompressFiles(c *gin.Context) {
})
}
func postServerDecompressFiles(c *gin.Context) {
s := GetServer(c.Param("server"))
var data struct {
RootPath string `json:"root"`
File string `json:"file"`
}
if err := c.BindJSON(&data); err != nil {
return
}
hasSpace, err := s.Filesystem.SpaceAvailableForDecompression(data.RootPath, data.File)
if err != nil {
TrackedServerError(err, s).AbortWithServerError(c)
return
}
if !hasSpace {
c.AbortWithStatusJSON(http.StatusConflict, gin.H{
"error": "This server does not have enough available disk space to decompress this archive.",
})
return
}
if err := s.Filesystem.DecompressFile(data.RootPath, data.File); err != nil {
TrackedServerError(err, s).AbortWithServerError(c)
return
}
c.Status(http.StatusNoContent)
}
func postServerUploadFiles(c *gin.Context) {
token := tokens.UploadPayload{}
if err := tokens.ParseToken([]byte(c.Query("token")), &token); err != nil {
@@ -313,10 +352,6 @@ func postServerUploadFiles(c *gin.Context) {
return
}
for i := range form.File {
log.Debug(i)
}
headers, ok := form.File["files"]
if !ok {
c.AbortWithStatusJSON(http.StatusNotModified, gin.H{
@@ -335,7 +370,6 @@ func postServerUploadFiles(c *gin.Context) {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
log.Debug(p)
// We run this in a different method so I can use defer without any of
// the consequences caused by calling it in a loop.

View File

@@ -51,8 +51,10 @@ func getServerWebsocket(c *gin.Context) {
continue
}
if err := handler.HandleInbound(j); err != nil {
handler.SendErrorJson(j, err)
}
go func(msg websocket.Message) {
if err := handler.HandleInbound(msg); err != nil {
handler.SendErrorJson(msg, err)
}
}(j)
}
}

View File

@@ -98,33 +98,33 @@ func postServerArchive(c *gin.Context) {
start := time.Now()
if err := server.Archiver.Archive(); err != nil {
zap.S().Errorw("failed to get archive for server", zap.String("server", s.Uuid), zap.Error(err))
zap.S().Errorw("failed to get archive for server", zap.String("server", server.Id()), zap.Error(err))
return
}
zap.S().Debugw(
"successfully created archive for server",
zap.String("server", server.Uuid),
zap.String("server", server.Id()),
zap.Duration("time", time.Now().Sub(start).Round(time.Microsecond)),
)
r := api.NewRequester()
rerr, err := r.SendArchiveStatus(server.Uuid, true)
rerr, err := r.SendArchiveStatus(server.Id(), true)
if rerr != nil || err != nil {
if err != nil {
zap.S().Errorw("failed to notify panel with archive status", zap.String("server", server.Uuid), zap.Error(err))
zap.S().Errorw("failed to notify panel with archive status", zap.String("server", server.Id()), zap.Error(err))
return
}
zap.S().Errorw(
"panel returned an error when sending the archive status",
zap.String("server", server.Uuid),
zap.String("server", server.Id()),
zap.Error(errors.New(rerr.String())),
)
return
}
zap.S().Debugw("successfully notified panel about archive status", zap.String("server", server.Uuid))
zap.S().Debugw("successfully notified panel about archive status", zap.String("server", server.Id()))
}(s)
c.Status(http.StatusAccepted)

View File

@@ -4,10 +4,13 @@ import (
"encoding/json"
"github.com/gbrlsnchs/jwt/v3"
"strings"
"sync"
)
type WebsocketPayload struct {
jwt.Payload
sync.RWMutex
UserID json.Number `json:"user_id"`
ServerUUID string `json:"server_uuid"`
Permissions []string `json:"permissions"`
@@ -15,11 +18,24 @@ type WebsocketPayload struct {
// Returns the JWT payload.
func (p *WebsocketPayload) GetPayload() *jwt.Payload {
p.RLock()
defer p.RUnlock()
return &p.Payload
}
func (p *WebsocketPayload) GetServerUuid() string {
p.RLock()
defer p.RUnlock()
return p.ServerUUID
}
// Checks if the given token payload has a permission string.
func (p *WebsocketPayload) HasPermission(permission string) bool {
p.RLock()
defer p.RUnlock()
for _, k := range p.Permissions {
if k == permission || (!strings.HasPrefix(permission, "admin") && k == "*") {
return true

View File

@@ -43,6 +43,8 @@ func (h *Handler) ListenForServerEvents(ctx context.Context) {
server.StatusEvent,
server.ConsoleOutputEvent,
server.InstallOutputEvent,
server.InstallStartedEvent,
server.InstallCompletedEvent,
server.DaemonMessageEvent,
server.BackupCompletedEvent,
}
@@ -52,16 +54,15 @@ func (h *Handler) ListenForServerEvents(ctx context.Context) {
h.server.Events().Subscribe(event, eventChannel)
}
select {
case <-ctx.Done():
for _, event := range events {
h.server.Events().Unsubscribe(event, eventChannel)
}
for d := range eventChannel {
select {
case <-ctx.Done():
for _, event := range events {
h.server.Events().Unsubscribe(event, eventChannel)
}
close(eventChannel)
default:
// Listen for different events emitted by the server and respond to them appropriately.
for d := range eventChannel {
close(eventChannel)
default:
h.SendJson(&Message{
Event: d.Topic,
Args: []string{d.Data},

View File

@@ -127,7 +127,7 @@ func (h *Handler) TokenValid() error {
return errors.New("jwt does not have connect permission")
}
if h.server.Uuid != j.ServerUUID {
if h.server.Id() != j.GetServerUuid() {
return errors.New("jwt server uuid mismatch")
}
@@ -247,16 +247,7 @@ func (h *Handler) HandleInbound(m Message) error {
if state == server.ProcessOfflineState {
_ = h.server.Filesystem.HasSpaceAvailable()
resources := server.ResourceUsage{
Memory: 0,
MemoryLimit: 0,
CpuAbsolute: 0.0,
Disk: h.server.Resources.Disk,
}
resources.Network.RxBytes = 0
resources.Network.TxBytes = 0
b, _ := json.Marshal(resources)
b, _ := json.Marshal(h.server.Proc())
h.SendJson(&Message{
Event: server.StatsEvent,
Args: []string{string(b)},
@@ -280,11 +271,14 @@ func (h *Handler) HandleInbound(m Message) error {
break
case "restart":
if h.GetJwt().HasPermission(PermissionSendPowerRestart) {
if err := h.server.Environment.WaitForStop(60, false); err != nil {
return err
// If the server is alreay restarting don't do anything. Perhaps we send back an event
// in the future for this? For now no reason to knowingly trigger an error by trying to
// restart a process already restarting.
if h.server.Environment.IsRestarting() {
return nil
}
return h.server.Environment.Start()
return h.server.Environment.Restart()
}
break
case "kill":