2020-04-06 01:00:33 +00:00
|
|
|
package router
|
|
|
|
|
|
|
|
import (
|
2020-08-01 22:20:39 +00:00
|
|
|
"context"
|
2021-01-10 01:22:39 +00:00
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
|
2020-12-16 05:56:53 +00:00
|
|
|
"emperror.dev/errors"
|
2020-06-13 17:26:35 +00:00
|
|
|
"github.com/apex/log"
|
2020-04-06 01:00:33 +00:00
|
|
|
"github.com/gin-gonic/gin"
|
2022-10-06 15:58:42 +00:00
|
|
|
|
2020-12-20 20:53:40 +00:00
|
|
|
"github.com/pterodactyl/wings/router/downloader"
|
2021-01-26 04:28:24 +00:00
|
|
|
"github.com/pterodactyl/wings/router/middleware"
|
2020-11-04 05:01:50 +00:00
|
|
|
"github.com/pterodactyl/wings/router/tokens"
|
2020-04-06 01:00:33 +00:00
|
|
|
"github.com/pterodactyl/wings/server"
|
2022-11-15 01:25:01 +00:00
|
|
|
"github.com/pterodactyl/wings/server/transfer"
|
2020-04-06 01:00:33 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Returns a single server from the collection of servers.
|
|
|
|
func getServer(c *gin.Context) {
|
2021-04-04 17:20:27 +00:00
|
|
|
c.JSON(http.StatusOK, ExtractServer(c).ToAPIResponse())
|
2020-04-06 01:00:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Returns the logs for a given server instance.
|
|
|
|
func getServerLogs(c *gin.Context) {
|
2021-01-08 23:14:56 +00:00
|
|
|
s := ExtractServer(c)
|
2020-04-06 01:00:33 +00:00
|
|
|
|
2020-09-07 20:04:56 +00:00
|
|
|
l, _ := strconv.Atoi(c.DefaultQuery("size", "100"))
|
2020-04-06 01:00:33 +00:00
|
|
|
if l <= 0 {
|
2020-09-07 20:04:56 +00:00
|
|
|
l = 100
|
|
|
|
} else if l > 100 {
|
|
|
|
l = 100
|
2020-04-06 01:00:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
out, err := s.ReadLogfile(l)
|
|
|
|
if err != nil {
|
2020-12-16 05:08:00 +00:00
|
|
|
NewServerError(err, s).Abort(c)
|
2020-04-06 01:00:33 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{"data": out})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handles a request to control the power state of a server. If the action being passed
|
|
|
|
// through is invalid a 404 is returned. Otherwise, a HTTP/202 Accepted response is returned
|
|
|
|
// and the actual power action is run asynchronously so that we don't have to block the
|
|
|
|
// request until a potentially slow operation completes.
|
|
|
|
//
|
|
|
|
// This is done because for the most part the Panel is using websockets to determine when
|
|
|
|
// things are happening, so theres no reason to sit and wait for a request to finish. We'll
|
|
|
|
// just see over the socket if something isn't working correctly.
|
|
|
|
func postServerPower(c *gin.Context) {
|
2021-01-08 23:14:56 +00:00
|
|
|
s := ExtractServer(c)
|
2020-04-06 01:00:33 +00:00
|
|
|
|
2020-08-30 16:54:33 +00:00
|
|
|
var data struct {
|
2022-01-23 17:49:35 +00:00
|
|
|
Action server.PowerAction `json:"action"`
|
|
|
|
WaitSeconds int `json:"wait_seconds"`
|
2020-08-01 22:20:39 +00:00
|
|
|
}
|
|
|
|
|
2020-05-29 15:44:49 +00:00
|
|
|
if err := c.BindJSON(&data); err != nil {
|
|
|
|
return
|
|
|
|
}
|
2020-04-06 01:00:33 +00:00
|
|
|
|
2020-08-01 22:20:39 +00:00
|
|
|
if !data.Action.IsValid() {
|
2020-04-06 01:00:33 +00:00
|
|
|
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
|
|
|
|
"error": "The power action provided was not valid, should be one of \"stop\", \"start\", \"restart\", \"kill\"",
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Because we route all of the actual bootup process to a separate thread we need to
|
|
|
|
// check the suspension status here, otherwise the user will hit the endpoint and then
|
|
|
|
// just sit there wondering why it returns a success but nothing actually happens.
|
|
|
|
//
|
|
|
|
// 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.
|
2020-08-01 22:20:39 +00:00
|
|
|
if (data.Action == server.PowerActionStart || data.Action == server.PowerActionRestart) && s.IsSuspended() {
|
2020-04-06 01:00:33 +00:00
|
|
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
|
|
|
|
"error": "Cannot start or restart a server that is suspended.",
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-09-05 19:08:40 +00:00
|
|
|
// Pass the actual heavy processing off to a separate thread to handle so that
|
2020-04-06 01:00:33 +00:00
|
|
|
// we can immediately return a response from the server. Some of these actions
|
|
|
|
// can take quite some time, especially stopping or restarting.
|
2020-08-01 22:20:39 +00:00
|
|
|
go func(s *server.Server) {
|
2022-01-23 17:49:35 +00:00
|
|
|
if data.WaitSeconds < 0 || data.WaitSeconds > 300 {
|
|
|
|
data.WaitSeconds = 30
|
|
|
|
}
|
|
|
|
if err := s.HandlePowerAction(data.Action, data.WaitSeconds); err != nil {
|
2020-08-01 22:20:39 +00:00
|
|
|
if errors.Is(err, context.DeadlineExceeded) {
|
2022-01-23 17:49:35 +00:00
|
|
|
s.Log().WithField("action", data.Action).WithField("error", err).Warn("could not process server power action")
|
|
|
|
} else if errors.Is(err, server.ErrIsRunning) {
|
|
|
|
// Do nothing, this isn't something we care about for logging,
|
2020-08-01 22:20:39 +00:00
|
|
|
} else {
|
2022-01-23 17:49:35 +00:00
|
|
|
s.Log().WithFields(log.Fields{"action": data.Action, "wait_seconds": data.WaitSeconds, "error": err}).
|
2020-08-01 22:20:39 +00:00
|
|
|
Error("encountered error processing a server power action in the background")
|
|
|
|
}
|
2020-04-06 01:00:33 +00:00
|
|
|
}
|
2020-06-13 17:26:35 +00:00
|
|
|
}(s)
|
2020-04-06 01:00:33 +00:00
|
|
|
|
|
|
|
c.Status(http.StatusAccepted)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sends an array of commands to a running server instance.
|
|
|
|
func postServerCommands(c *gin.Context) {
|
2021-01-08 23:14:56 +00:00
|
|
|
s := ExtractServer(c)
|
2020-04-06 01:00:33 +00:00
|
|
|
|
2021-09-11 21:13:19 +00:00
|
|
|
if running, err := s.Environment.IsRunning(c.Request.Context()); err != nil {
|
2020-12-16 05:08:00 +00:00
|
|
|
NewServerError(err, s).Abort(c)
|
2020-04-06 01:00:33 +00:00
|
|
|
return
|
|
|
|
} else if !running {
|
|
|
|
c.AbortWithStatusJSON(http.StatusBadGateway, gin.H{
|
|
|
|
"error": "Cannot send commands to a stopped server instance.",
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-05-29 15:44:49 +00:00
|
|
|
var data struct {
|
|
|
|
Commands []string `json:"commands"`
|
|
|
|
}
|
|
|
|
// BindJSON sends 400 if the request fails, all we need to do is return
|
|
|
|
if err := c.BindJSON(&data); err != nil {
|
|
|
|
return
|
|
|
|
}
|
2020-04-06 01:00:33 +00:00
|
|
|
|
|
|
|
for _, command := range data.Commands {
|
|
|
|
if err := s.Environment.SendCommand(command); err != nil {
|
2020-06-13 17:26:35 +00:00
|
|
|
s.Log().WithFields(log.Fields{"command": command, "error": err}).Warn("failed to send command to server instance")
|
2020-04-06 01:00:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Status(http.StatusNoContent)
|
|
|
|
}
|
|
|
|
|
2021-08-29 20:37:18 +00:00
|
|
|
// postServerSync will accept a POST request and trigger a re-sync of the given
|
|
|
|
// server against the Panel. This can be manually triggered when needed by an
|
|
|
|
// external system, or triggered by the Panel itself when modifications are made
|
|
|
|
// to the build of a server internally.
|
|
|
|
func postServerSync(c *gin.Context) {
|
2021-01-08 23:14:56 +00:00
|
|
|
s := ExtractServer(c)
|
2020-04-06 01:00:33 +00:00
|
|
|
|
2021-08-29 20:37:18 +00:00
|
|
|
if err := s.Sync(); err != nil {
|
|
|
|
WithError(c, err)
|
|
|
|
} else {
|
|
|
|
c.Status(http.StatusNoContent)
|
2020-04-06 01:00:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-06 19:49:49 +00:00
|
|
|
// Performs a server installation in a background thread.
|
2020-04-06 01:00:33 +00:00
|
|
|
func postServerInstall(c *gin.Context) {
|
2021-01-08 23:14:56 +00:00
|
|
|
s := ExtractServer(c)
|
2020-04-06 01:00:33 +00:00
|
|
|
|
2022-11-21 21:57:44 +00:00
|
|
|
go func(s *server.Server) {
|
|
|
|
s.Log().Info("syncing server state with remote source before executing installation process")
|
|
|
|
if err := s.Sync(); err != nil {
|
|
|
|
s.Log().WithField("error", err).Error("failed to sync server state with Panel")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.Install(); err != nil {
|
|
|
|
s.Log().WithField("error", err).Error("failed to execute server installation process")
|
2020-04-06 01:00:33 +00:00
|
|
|
}
|
|
|
|
}(s)
|
|
|
|
|
|
|
|
c.Status(http.StatusAccepted)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reinstalls a server.
|
|
|
|
func postServerReinstall(c *gin.Context) {
|
2021-01-08 23:14:56 +00:00
|
|
|
s := ExtractServer(c)
|
2020-04-06 01:00:33 +00:00
|
|
|
|
2020-09-26 03:03:04 +00:00
|
|
|
if s.ExecutingPowerAction() {
|
|
|
|
c.AbortWithStatusJSON(http.StatusConflict, gin.H{
|
|
|
|
"error": "Cannot execute server reinstall event while another power action is running.",
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
go func(s *server.Server) {
|
|
|
|
if err := s.Reinstall(); err != nil {
|
|
|
|
s.Log().WithField("error", err).Error("failed to complete server re-install process")
|
2020-04-06 01:00:33 +00:00
|
|
|
}
|
|
|
|
}(s)
|
|
|
|
|
|
|
|
c.Status(http.StatusAccepted)
|
|
|
|
}
|
|
|
|
|
2022-09-25 19:34:28 +00:00
|
|
|
// Deletes a server from the wings daemon and dissociate its objects.
|
2020-04-06 01:00:33 +00:00
|
|
|
func deleteServer(c *gin.Context) {
|
2021-01-26 04:28:24 +00:00
|
|
|
s := middleware.ExtractServer(c)
|
2020-04-06 01:00:33 +00:00
|
|
|
|
|
|
|
// Immediately suspend the server to prevent a user from attempting
|
|
|
|
// to start it while this process is running.
|
2020-07-19 23:27:55 +00:00
|
|
|
s.Config().SetSuspended(true)
|
2022-11-15 01:25:01 +00:00
|
|
|
|
|
|
|
// Notify all websocket clients that the server is being deleted.
|
|
|
|
// This is useful for two reasons, one to tell clients not to bother
|
|
|
|
// retrying to connect to the websocket. And two, for transfers when
|
|
|
|
// the server has been successfully transferred to another node, and
|
|
|
|
// the client needs to switch to the new node.
|
|
|
|
if s.IsTransferring() {
|
|
|
|
s.Events().Publish(server.TransferStatusEvent, transfer.StatusCompleted)
|
|
|
|
}
|
|
|
|
s.Events().Publish(server.DeletedEvent, nil)
|
|
|
|
|
2022-01-23 17:49:35 +00:00
|
|
|
s.CleanupForDestroy()
|
2020-06-23 04:38:16 +00:00
|
|
|
|
2020-12-20 20:53:40 +00:00
|
|
|
// Remove any pending remote file downloads for the server.
|
2021-08-02 21:07:00 +00:00
|
|
|
for _, dl := range downloader.ByServer(s.ID()) {
|
2020-12-20 20:53:40 +00:00
|
|
|
dl.Cancel()
|
|
|
|
}
|
|
|
|
|
2020-04-06 01:00:33 +00:00
|
|
|
// Destroy the environment; in Docker this will handle a running container and
|
|
|
|
// forcibly terminate it before removing the container, so we do not need to handle
|
|
|
|
// that here.
|
|
|
|
if err := s.Environment.Destroy(); err != nil {
|
2022-11-15 01:25:01 +00:00
|
|
|
_ = WithError(c, err)
|
2020-12-25 19:21:09 +00:00
|
|
|
return
|
2020-04-06 01:00:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Once the environment is terminated, remove the server files from the system. This is
|
|
|
|
// done in a separate process since failure is not the end of the world and can be
|
|
|
|
// manually cleaned up after the fact.
|
|
|
|
//
|
2022-11-15 01:25:01 +00:00
|
|
|
// In addition, servers with large amounts of files can take some time to finish deleting,
|
2020-04-06 01:00:33 +00:00
|
|
|
// so we don't want to block the HTTP call while waiting on this.
|
|
|
|
go func(p string) {
|
|
|
|
if err := os.RemoveAll(p); err != nil {
|
2020-12-26 18:42:44 +00:00
|
|
|
log.WithFields(log.Fields{"path": p, "error": err}).Warn("failed to remove server files during deletion process")
|
2020-04-06 01:00:33 +00:00
|
|
|
}
|
2020-09-27 19:24:08 +00:00
|
|
|
}(s.Filesystem().Path())
|
2020-04-06 01:00:33 +00:00
|
|
|
|
2021-01-26 04:28:24 +00:00
|
|
|
middleware.ExtractManager(c).Remove(func(server *server.Server) bool {
|
2021-08-02 21:07:00 +00:00
|
|
|
return server.ID() == s.ID()
|
2021-01-26 04:28:24 +00:00
|
|
|
})
|
2020-04-06 01:00:33 +00:00
|
|
|
|
|
|
|
// Deallocate the reference to this server.
|
|
|
|
s = nil
|
|
|
|
|
|
|
|
c.Status(http.StatusNoContent)
|
2020-04-06 19:49:49 +00:00
|
|
|
}
|
2020-11-04 05:01:50 +00:00
|
|
|
|
|
|
|
// Adds any of the JTIs passed through in the body to the deny list for the websocket
|
|
|
|
// preventing any JWT generated before the current time from being used to connect to
|
|
|
|
// the socket or send along commands.
|
|
|
|
func postServerDenyWSTokens(c *gin.Context) {
|
2020-11-28 23:57:10 +00:00
|
|
|
var data struct {
|
|
|
|
JTIs []string `json:"jtis"`
|
|
|
|
}
|
2020-11-04 05:01:50 +00:00
|
|
|
|
|
|
|
if err := c.BindJSON(&data); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, jti := range data.JTIs {
|
|
|
|
tokens.DenyJTI(jti)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Status(http.StatusNoContent)
|
|
|
|
}
|