Merge develop into feature/file-uploads
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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":
|
||||
|
||||
Reference in New Issue
Block a user