2019-04-06 05:20:26 +00:00
package main
import (
2019-04-07 00:32:35 +00:00
"bufio"
2019-05-04 23:04:41 +00:00
"bytes"
2019-04-06 05:20:26 +00:00
"encoding/json"
"fmt"
2019-05-04 23:04:41 +00:00
"github.com/buger/jsonparser"
2019-04-20 06:29:52 +00:00
"github.com/gorilla/websocket"
2019-04-06 05:20:26 +00:00
"github.com/julienschmidt/httprouter"
2019-12-10 05:05:55 +00:00
"github.com/pkg/errors"
2020-04-04 05:17:26 +00:00
"github.com/pterodactyl/wings/api"
2019-12-17 05:43:07 +00:00
"github.com/pterodactyl/wings/config"
2019-11-16 23:10:53 +00:00
"github.com/pterodactyl/wings/installer"
2019-04-06 05:20:26 +00:00
"github.com/pterodactyl/wings/server"
2019-04-06 05:55:48 +00:00
"go.uber.org/zap"
"io"
2019-04-06 05:20:26 +00:00
"net/http"
2019-04-06 05:55:48 +00:00
"os"
2019-04-06 19:27:44 +00:00
"strconv"
2019-04-06 17:49:31 +00:00
"strings"
2020-04-04 05:17:26 +00:00
"time"
2019-04-06 05:20:26 +00:00
)
2019-04-06 05:34:53 +00:00
// Retrieves a server out of the collection by UUID.
2019-12-08 00:43:00 +00:00
func ( rt * Router ) GetServer ( uuid string ) * server . Server {
return server . GetServers ( ) . Find ( func ( i * server . Server ) bool {
return i . Uuid == uuid
} )
2019-04-06 05:34:53 +00:00
}
2019-04-06 05:20:26 +00:00
type Router struct {
2019-04-20 06:29:52 +00:00
upgrader websocket . Upgrader
2019-04-07 23:28:01 +00:00
2019-04-06 17:49:31 +00:00
// The authentication token defined in the config.yml file that allows
// a request to perform any action aganist the daemon.
token string
2019-04-06 05:20:26 +00:00
}
2019-09-06 04:08:03 +00:00
func ( rt * Router ) AuthenticateRequest ( h httprouter . Handle ) httprouter . Handle {
return rt . AuthenticateToken ( rt . AuthenticateServer ( h ) )
}
2019-04-06 05:34:53 +00:00
// Middleware to protect server specific routes. This will ensure that the server exists and
// is in a state that allows it to be exposed to the API.
func ( rt * Router ) AuthenticateServer ( h httprouter . Handle ) httprouter . Handle {
return func ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) {
2019-12-08 00:43:00 +00:00
if rt . GetServer ( ps . ByName ( "server" ) ) != nil {
2019-04-06 05:34:53 +00:00
h ( w , r , ps )
return
}
http . NotFound ( w , r )
}
}
2019-12-17 05:43:07 +00:00
// Attaches required access control headers to all of the requests.
func ( rt * Router ) AttachAccessControlHeaders ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) ( http . ResponseWriter , * http . Request , httprouter . Params ) {
w . Header ( ) . Set ( "Access-Control-Allow-Origin" , config . Get ( ) . PanelLocation )
w . Header ( ) . Set ( "Access-Control-Allow-Headers" , "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization" )
return w , r , ps
}
2020-04-04 05:17:26 +00:00
// Authenticates the request token against the given permission string, ensuring that
2019-04-06 17:49:31 +00:00
// if it is a server permission, the token has control over that server. If it is a global
// token, this will ensure that the request is using a properly signed global token.
2019-09-06 04:08:03 +00:00
func ( rt * Router ) AuthenticateToken ( h httprouter . Handle ) httprouter . Handle {
2019-04-06 17:49:31 +00:00
return func ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) {
2019-04-20 06:29:52 +00:00
// Adds support for using this middleware on the websocket routes for servers. Those
// routes don't support Authorization headers, per the spec, so we abuse the socket
// protocol header and use that to pass the authorization token along to Wings without
// exposing the token in the URL directly. Neat. 📸
2019-04-06 17:49:31 +00:00
auth := strings . SplitN ( r . Header . Get ( "Authorization" ) , " " , 2 )
2019-04-20 06:29:52 +00:00
2019-04-06 17:49:31 +00:00
if len ( auth ) != 2 || auth [ 0 ] != "Bearer" {
w . Header ( ) . Set ( "WWW-Authenticate" , "Bearer" )
http . Error ( w , "authorization failed" , http . StatusUnauthorized )
return
}
// Try to match the request aganist the global token for the Daemon, regardless
// of the permission type. If nothing is matched we will fall through to the Panel
// API to try and validate permissions for a server.
2019-09-06 04:08:03 +00:00
if auth [ 1 ] == rt . token {
2019-12-17 05:43:07 +00:00
h ( rt . AttachAccessControlHeaders ( w , r , ps ) )
2019-09-06 04:08:03 +00:00
return
2019-04-06 17:49:31 +00:00
}
2019-04-20 06:29:52 +00:00
// Happens because we don't have any of the server handling code here.
2019-04-06 17:49:31 +00:00
http . Error ( w , "not implemented" , http . StatusNotImplemented )
return
}
}
2019-04-06 05:34:53 +00:00
// Returns the basic Wings index page without anything else.
func ( rt * Router ) routeIndex ( w http . ResponseWriter , _ * http . Request , _ httprouter . Params ) {
2019-04-06 05:20:26 +00:00
fmt . Fprint ( w , "Welcome!\n" )
}
2019-04-06 05:34:53 +00:00
// Returns all of the servers that exist on the Daemon. This route is only accessible to
// requests that include an administrative control key, otherwise a 404 is returned. This
// authentication is handled by a middleware.
func ( rt * Router ) routeAllServers ( w http . ResponseWriter , _ * http . Request , _ httprouter . Params ) {
2019-12-08 00:43:00 +00:00
json . NewEncoder ( w ) . Encode ( server . GetServers ( ) . All ( ) )
2019-04-06 05:20:26 +00:00
}
2019-04-06 05:34:53 +00:00
// Returns basic information about a single server found on the Daemon.
func ( rt * Router ) routeServer ( w http . ResponseWriter , _ * http . Request , ps httprouter . Params ) {
2019-12-08 00:43:00 +00:00
s := rt . GetServer ( ps . ByName ( "server" ) )
2019-04-06 05:20:26 +00:00
2019-04-06 05:34:53 +00:00
json . NewEncoder ( w ) . Encode ( s )
}
2019-04-06 05:55:48 +00:00
type PowerActionRequest struct {
Action string ` json:"action" `
}
2019-05-02 05:09:01 +00:00
type CreateDirectoryRequest struct {
Name string ` json:"name" `
Path string ` json:"path" `
}
2019-04-06 05:55:48 +00:00
func ( pr * PowerActionRequest ) IsValid ( ) bool {
return pr . Action == "start" || pr . Action == "stop" || pr . Action == "kill" || pr . Action == "restart"
}
// 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 ( rt * Router ) routeServerPower ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) {
2019-12-08 00:43:00 +00:00
s := rt . GetServer ( ps . ByName ( "server" ) )
2019-04-06 05:55:48 +00:00
defer r . Body . Close ( )
dec := json . NewDecoder ( r . Body )
var action PowerActionRequest
if err := dec . Decode ( & action ) ; err != nil {
// Don't flood the logs with error messages if someone sends through bad
// JSON data. We don't really care.
2019-04-06 05:57:39 +00:00
if err != io . EOF && err != io . ErrUnexpectedEOF {
2019-04-06 05:55:48 +00:00
zap . S ( ) . Errorw ( "failed to decode power action" , zap . Error ( err ) )
}
http . Error ( w , "could not parse power action from request" , http . StatusInternalServerError )
return
}
if ! action . IsValid ( ) {
http . NotFound ( w , r )
return
}
2019-11-30 23:19:08 +00:00
// Because we route all of the actual bootup process to a seperate 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.
if action . Action == "start" && s . Suspended {
http . Error ( w , "server is suspended" , http . StatusBadRequest )
return
}
2019-04-06 05:55:48 +00:00
// Pass the actual heavy processing off to a seperate thread to handle so that
// we can immediately return a response from the server.
go func ( a string , s * server . Server ) {
switch a {
case "start" :
2019-04-20 23:26:55 +00:00
if err := s . Environment . Start ( ) ; err != nil {
2019-11-17 01:01:38 +00:00
zap . S ( ) . Errorw (
"encountered unexpected error starting server process" ,
zap . Error ( err ) ,
zap . String ( "server" , s . Uuid ) ,
zap . String ( "action" , "start" ) ,
)
2019-04-06 05:55:48 +00:00
}
break
case "stop" :
2019-04-20 23:26:55 +00:00
if err := s . Environment . Stop ( ) ; err != nil {
2019-11-17 01:01:38 +00:00
zap . S ( ) . Errorw (
"encountered unexpected error stopping server process" ,
zap . Error ( err ) ,
zap . String ( "server" , s . Uuid ) ,
zap . String ( "action" , "stop" ) ,
)
2019-04-06 05:55:48 +00:00
}
break
case "restart" :
2020-04-03 21:52:24 +00:00
if err := s . Environment . WaitForStop ( 60 , false ) ; err != nil {
zap . S ( ) . Errorw (
"encountered unexpected error waiting for server process to stop" ,
zap . Error ( err ) ,
zap . String ( "server" , s . Uuid ) ,
zap . String ( "action" , "restart" ) ,
)
}
if err := s . Environment . Start ( ) ; err != nil {
zap . S ( ) . Errorw (
"encountered unexpected error starting server process" ,
zap . Error ( err ) ,
zap . String ( "server" , s . Uuid ) ,
zap . String ( "action" , "restart" ) ,
)
}
2020-04-04 05:17:26 +00:00
break
2019-04-06 05:55:48 +00:00
case "kill" :
2019-04-20 23:26:55 +00:00
if err := s . Environment . Terminate ( os . Kill ) ; err != nil {
2019-11-17 01:01:38 +00:00
zap . S ( ) . Errorw (
"encountered unexpected error killing server process" ,
zap . Error ( err ) ,
zap . String ( "server" , s . Uuid ) ,
zap . String ( "action" , "kill" ) ,
)
2019-04-06 05:55:48 +00:00
}
}
} ( action . Action , s )
w . WriteHeader ( http . StatusAccepted )
}
2019-04-06 19:27:44 +00:00
// Return the last 1Kb of the server log file.
func ( rt * Router ) routeServerLogs ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) {
2019-12-08 00:43:00 +00:00
s := rt . GetServer ( ps . ByName ( "server" ) )
2019-04-06 19:27:44 +00:00
l , _ := strconv . ParseInt ( r . URL . Query ( ) . Get ( "size" ) , 10 , 64 )
if l <= 0 {
l = 2048
}
out , err := s . ReadLogfile ( l )
if err != nil {
zap . S ( ) . Errorw ( "failed to read server log file" , zap . Error ( err ) )
http . Error ( w , "failed to read log" , http . StatusInternalServerError )
return
}
2019-05-04 23:04:41 +00:00
json . NewEncoder ( w ) . Encode ( struct { Data [ ] string ` json:"data" ` } { Data : out } )
2019-04-06 19:27:44 +00:00
}
2019-04-07 00:32:35 +00:00
// Handle a request to get the contents of a file on the server.
func ( rt * Router ) routeServerFileRead ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) {
2019-12-08 00:43:00 +00:00
s := rt . GetServer ( ps . ByName ( "server" ) )
2019-04-07 00:32:35 +00:00
2019-05-25 22:40:02 +00:00
cleaned , err := s . Filesystem . SafePath ( r . URL . Query ( ) . Get ( "file" ) )
2019-04-07 00:32:35 +00:00
if err != nil {
2019-05-25 22:40:02 +00:00
http . NotFound ( w , r )
2019-04-07 00:32:35 +00:00
return
}
2019-05-25 22:58:55 +00:00
st , err := s . Filesystem . Stat ( cleaned )
2019-04-07 00:32:35 +00:00
if err != nil {
if ! os . IsNotExist ( err ) {
zap . S ( ) . Errorw ( "failed to stat file for reading" , zap . String ( "path" , ps . ByName ( "path" ) ) , zap . String ( "server" , s . Uuid ) , zap . Error ( err ) )
2019-05-25 22:58:55 +00:00
http . Error ( w , "failed to stat file" , http . StatusInternalServerError )
return
2019-04-07 00:32:35 +00:00
}
2019-05-25 22:58:55 +00:00
http . NotFound ( w , r )
2019-04-07 00:32:35 +00:00
return
}
2020-04-04 05:17:26 +00:00
f , err := os . Open ( cleaned )
2019-04-07 00:32:35 +00:00
if err != nil {
if ! os . IsNotExist ( err ) {
zap . S ( ) . Errorw ( "failed to open file for reading" , zap . String ( "path" , ps . ByName ( "path" ) ) , zap . String ( "server" , s . Uuid ) , zap . Error ( err ) )
}
http . Error ( w , "failed to open file" , http . StatusInternalServerError )
return
}
defer f . Close ( )
2019-05-25 22:58:55 +00:00
w . Header ( ) . Set ( "X-Mime-Type" , st . Mimetype )
w . Header ( ) . Set ( "Content-Length" , strconv . Itoa ( int ( st . Info . Size ( ) ) ) )
2019-04-07 00:32:35 +00:00
// If a download parameter is included in the URL go ahead and attach the necessary headers
// so that the file can be downloaded.
if r . URL . Query ( ) . Get ( "download" ) != "" {
2019-05-25 22:58:55 +00:00
w . Header ( ) . Set ( "Content-Disposition" , "attachment; filename=" + st . Info . Name ( ) )
2019-04-07 00:32:35 +00:00
w . Header ( ) . Set ( "Content-Type" , "application/octet-stream" )
}
bufio . NewReader ( f ) . WriteTo ( w )
}
2019-04-07 21:45:23 +00:00
// Lists the contents of a directory.
2019-05-25 22:40:02 +00:00
func ( rt * Router ) routeServerListDirectory ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) {
2019-12-08 00:43:00 +00:00
s := rt . GetServer ( ps . ByName ( "server" ) )
2019-04-07 21:45:23 +00:00
2019-05-25 22:40:02 +00:00
stats , err := s . Filesystem . ListDirectory ( r . URL . Query ( ) . Get ( "directory" ) )
2019-04-07 21:45:23 +00:00
if os . IsNotExist ( err ) {
http . NotFound ( w , r )
return
} else if err != nil {
zap . S ( ) . Errorw ( "failed to list contents of directory" , zap . String ( "server" , s . Uuid ) , zap . String ( "path" , ps . ByName ( "path" ) ) , zap . Error ( err ) )
http . Error ( w , "failed to list directory" , http . StatusInternalServerError )
return
}
json . NewEncoder ( w ) . Encode ( stats )
}
2019-05-25 22:40:15 +00:00
// Writes a file to the system for the server.
func ( rt * Router ) routeServerWriteFile ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) {
2019-12-08 00:43:00 +00:00
s := rt . GetServer ( ps . ByName ( "server" ) )
2019-05-25 22:40:15 +00:00
p := r . URL . Query ( ) . Get ( "file" )
defer r . Body . Close ( )
err := s . Filesystem . Writefile ( p , r . Body )
if err != nil {
zap . S ( ) . Errorw ( "failed to write file to directory" , zap . String ( "server" , s . Uuid ) , zap . String ( "path" , p ) , zap . Error ( err ) )
http . Error ( w , "failed to write file to directory" , http . StatusInternalServerError )
return
}
w . WriteHeader ( http . StatusNoContent )
}
2019-05-02 05:09:01 +00:00
// Creates a new directory for the server.
func ( rt * Router ) routeServerCreateDirectory ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) {
2019-12-08 00:43:00 +00:00
s := rt . GetServer ( ps . ByName ( "server" ) )
2019-05-02 05:09:01 +00:00
defer r . Body . Close ( )
dec := json . NewDecoder ( r . Body )
var data CreateDirectoryRequest
if err := dec . Decode ( & data ) ; err != nil {
// Don't flood the logs with error messages if someone sends through bad
// JSON data. We don't really care.
if err != io . EOF && err != io . ErrUnexpectedEOF {
zap . S ( ) . Errorw ( "failed to decode directory creation data" , zap . Error ( err ) )
}
http . Error ( w , "could not parse data in request" , http . StatusUnprocessableEntity )
return
}
if err := s . Filesystem . CreateDirectory ( data . Name , data . Path ) ; err != nil {
zap . S ( ) . Errorw ( "failed to create directory for server" , zap . String ( "server" , s . Uuid ) , zap . Error ( err ) )
http . Error ( w , "an error was encountered while creating the directory" , http . StatusInternalServerError )
return
}
w . WriteHeader ( http . StatusNoContent )
}
2019-05-04 23:04:41 +00:00
func ( rt * Router ) routeServerRenameFile ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) {
2019-12-08 00:43:00 +00:00
s := rt . GetServer ( ps . ByName ( "server" ) )
2019-05-04 23:04:41 +00:00
defer r . Body . Close ( )
data := rt . ReaderToBytes ( r . Body )
oldPath , _ := jsonparser . GetString ( data , "rename_from" )
newPath , _ := jsonparser . GetString ( data , "rename_to" )
if oldPath == "" || newPath == "" {
http . Error ( w , "invalid paths provided; did you forget to provide an old path and new path?" , http . StatusUnprocessableEntity )
return
}
if err := s . Filesystem . Rename ( oldPath , newPath ) ; err != nil {
zap . S ( ) . Errorw ( "failed to rename file on server" , zap . String ( "server" , s . Uuid ) , zap . Error ( err ) )
http . Error ( w , "an error occurred while renaming the file" , http . StatusInternalServerError )
return
}
w . WriteHeader ( http . StatusNoContent )
}
2019-05-05 00:02:01 +00:00
func ( rt * Router ) routeServerCopyFile ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) {
2019-12-08 00:43:00 +00:00
s := rt . GetServer ( ps . ByName ( "server" ) )
2019-05-05 00:02:01 +00:00
defer r . Body . Close ( )
data := rt . ReaderToBytes ( r . Body )
2019-05-05 00:09:35 +00:00
loc , _ := jsonparser . GetString ( data , "location" )
2019-05-05 00:02:01 +00:00
2019-05-05 00:09:35 +00:00
if err := s . Filesystem . Copy ( loc ) ; err != nil {
2019-05-05 00:02:01 +00:00
zap . S ( ) . Errorw ( "error copying file for server" , zap . String ( "server" , s . Uuid ) , zap . Error ( err ) )
http . Error ( w , "an error occurred while copying the file" , http . StatusInternalServerError )
return
}
w . WriteHeader ( http . StatusNoContent )
}
2019-05-05 00:09:35 +00:00
func ( rt * Router ) routeServerDeleteFile ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) {
2019-12-08 00:43:00 +00:00
s := rt . GetServer ( ps . ByName ( "server" ) )
2019-05-05 00:09:35 +00:00
defer r . Body . Close ( )
data := rt . ReaderToBytes ( r . Body )
loc , _ := jsonparser . GetString ( data , "location" )
if err := s . Filesystem . Delete ( loc ) ; err != nil {
zap . S ( ) . Errorw ( "failed to delete a file or directory for server" , zap . String ( "server" , s . Uuid ) , zap . Error ( err ) )
http . Error ( w , "an error occurred while trying to delete a file or directory" , http . StatusInternalServerError )
return
}
w . WriteHeader ( http . StatusNoContent )
}
2019-09-06 04:08:03 +00:00
func ( rt * Router ) routeServerSendCommand ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) {
2019-12-08 00:43:00 +00:00
s := rt . GetServer ( ps . ByName ( "server" ) )
2019-09-06 04:08:03 +00:00
defer r . Body . Close ( )
if running , err := s . Environment . IsRunning ( ) ; ! running || err != nil {
http . Error ( w , "cannot send commands to a stopped instance" , http . StatusBadGateway )
return
}
data := rt . ReaderToBytes ( r . Body )
commands , dt , _ , _ := jsonparser . Get ( data , "commands" )
if dt != jsonparser . Array {
http . Error ( w , "commands must be an array of strings" , http . StatusUnprocessableEntity )
return
}
for _ , command := range commands {
if err := s . Environment . SendCommand ( string ( command ) ) ; err != nil {
zap . S ( ) . Warnw ( "failed to send command to server" , zap . Any ( "command" , command ) , zap . Error ( err ) )
return
}
}
w . WriteHeader ( http . StatusNoContent )
}
2019-12-28 22:57:19 +00:00
func ( rt * Router ) routeServerInstall ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) {
s := rt . GetServer ( ps . ByName ( "server" ) )
defer r . Body . Close ( )
2020-04-03 20:43:13 +00:00
go func ( serv * server . Server ) {
2020-01-19 21:30:54 +00:00
if err := serv . Install ( ) ; err != nil {
zap . S ( ) . Errorw ( "failed to execute server installation process" , zap . String ( "server" , s . Uuid ) , zap . Error ( err ) )
}
} ( s )
2019-12-28 22:57:19 +00:00
2019-12-28 23:12:12 +00:00
w . WriteHeader ( http . StatusAccepted )
2019-12-28 22:57:19 +00:00
}
2019-11-24 23:08:38 +00:00
func ( rt * Router ) routeServerUpdate ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) {
2019-12-08 00:43:00 +00:00
s := rt . GetServer ( ps . ByName ( "server" ) )
2019-11-24 23:08:38 +00:00
defer r . Body . Close ( )
data := rt . ReaderToBytes ( r . Body )
2019-12-22 21:21:21 +00:00
if err := s . UpdateDataStructure ( data , true ) ; err != nil {
2019-11-24 23:08:38 +00:00
zap . S ( ) . Errorw ( "failed to update a server's data structure" , zap . String ( "server" , s . Uuid ) , zap . Error ( err ) )
http . Error ( w , "failed to update data structure" , http . StatusInternalServerError )
return
}
w . WriteHeader ( http . StatusNoContent )
}
2020-04-04 05:17:26 +00:00
func ( rt * Router ) routeCreateServer ( w http . ResponseWriter , r * http . Request , _ httprouter . Params ) {
2019-11-16 23:10:53 +00:00
defer r . Body . Close ( )
2019-11-25 04:40:13 +00:00
inst , err := installer . New ( rt . ReaderToBytes ( r . Body ) )
2019-11-16 23:10:53 +00:00
if err != nil {
zap . S ( ) . Warnw ( "failed to validate the received data" , zap . Error ( err ) )
2019-11-16 23:48:50 +00:00
http . Error ( w , "failed to validate data" , http . StatusUnprocessableEntity )
return
2019-11-16 23:10:53 +00:00
}
2019-11-16 23:48:50 +00:00
// Plop that server instance onto the request so that it can be referenced in
// requests from here-on out.
2019-12-08 00:43:00 +00:00
server . GetServers ( ) . Add ( inst . Server ( ) )
2019-11-16 23:48:50 +00:00
2020-01-19 21:05:49 +00:00
zap . S ( ) . Infow ( "beginning installation process for server" , zap . String ( "server" , inst . Uuid ( ) ) )
2019-11-16 23:48:50 +00:00
// Begin the installation process in the background to not block the request
// cycle. If there are any errors they will be logged and communicated back
// to the Panel where a reinstall may take place.
2020-01-19 21:05:49 +00:00
go func ( i * installer . Installer ) {
i . Execute ( )
if err := i . Server ( ) . Install ( ) ; err != nil {
zap . S ( ) . Errorw ( "failed to run install process for server" , zap . String ( "server" , i . Uuid ( ) ) , zap . Error ( err ) )
}
} ( inst )
2019-11-16 23:10:53 +00:00
w . WriteHeader ( http . StatusAccepted )
}
2020-04-03 20:43:13 +00:00
func ( rt * Router ) routeServerReinstall ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) {
s := rt . GetServer ( ps . ByName ( "server" ) )
defer r . Body . Close ( )
zap . S ( ) . Infow ( "beginning reinstall process for server" , zap . String ( "server" , s . Uuid ) )
go func ( server * server . Server ) {
if err := server . Reinstall ( ) ; err != nil {
zap . S ( ) . Errorw ( "failed to complete server reinstall process" , zap . String ( "server" , server . Uuid ) , zap . Error ( err ) )
}
} ( s )
w . WriteHeader ( http . StatusAccepted )
}
2020-04-04 05:17:26 +00:00
func ( rt * Router ) routeSystemInformation ( w http . ResponseWriter , r * http . Request , _ httprouter . Params ) {
2019-12-10 05:05:55 +00:00
defer r . Body . Close ( )
s , err := GetSystemInformation ( )
if err != nil {
zap . S ( ) . Errorw ( "failed to retrieve system information" , zap . Error ( errors . WithStack ( err ) ) )
http . Error ( w , "failed to retrieve information" , http . StatusInternalServerError )
return
}
json . NewEncoder ( w ) . Encode ( s )
}
2019-12-22 07:23:56 +00:00
func ( rt * Router ) routeServerDelete ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) {
s := rt . GetServer ( ps . ByName ( "server" ) )
defer r . Body . Close ( )
// Immediately suspend the server to prevent a user from attempting
// to start it while this process is running.
s . Suspended = true
2020-01-19 22:00:59 +00:00
zap . S ( ) . Infow ( "processing server deletion request" , zap . String ( "server" , s . Uuid ) )
2019-12-22 07:23:56 +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 {
zap . S ( ) . Errorw ( "failed to destroy server environment" , zap . Error ( errors . WithStack ( err ) ) )
http . Error ( w , "failed to destroy server environment" , http . StatusInternalServerError )
return
}
// Once the environment is terminated, remove the server files from the system. This is
// done in a seperate process since failure is not the end of the world and can be
// manually cleaned up after the fact.
//
// In addition, servers with large amounts of files can take some time to finish deleting
// 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 {
zap . S ( ) . Warnw ( "failed to remove server files on deletion" , zap . String ( "path" , p ) , zap . Error ( errors . WithStack ( err ) ) )
}
} ( s . Filesystem . Path ( ) )
var uuid = s . Uuid
server . GetServers ( ) . Remove ( func ( s2 * server . Server ) bool {
return s2 . Uuid == uuid
} )
s = nil
// Remove the configuration file stored on the Daemon for this server.
go func ( u string ) {
if err := os . Remove ( "data/servers/" + u + ".yml" ) ; err != nil {
zap . S ( ) . Warnw ( "failed to delete server configuration file on deletion" , zap . String ( "server" , u ) , zap . Error ( errors . WithStack ( err ) ) )
}
} ( uuid )
w . WriteHeader ( http . StatusAccepted )
}
2020-04-04 05:17:26 +00:00
func ( rt * Router ) routeRequestServerArchive ( w http . ResponseWriter , _ * http . Request , ps httprouter . Params ) {
s := rt . GetServer ( ps . ByName ( "server" ) )
go func ( ) {
start := time . Now ( )
if err := s . Archiver . Archive ( ) ; err != nil {
zap . S ( ) . Errorw ( "failed to get archive for server" , zap . String ( "server" , s . Uuid ) , zap . Error ( err ) )
return
}
zap . S ( ) . Debugw ( "successfully created archive for server" , zap . String ( "server" , s . Uuid ) , zap . Duration ( "time" , time . Now ( ) . Sub ( start ) . Round ( time . Microsecond ) ) )
r := api . NewRequester ( )
rerr , err := r . SendArchiveStatus ( s . Uuid , true )
if rerr != nil || err != nil {
if err != nil {
zap . S ( ) . Errorw ( "failed to notify panel with archive status" , zap . String ( "server" , s . Uuid ) , zap . Error ( err ) )
return
}
zap . S ( ) . Errorw ( "panel returned an error when sending the archive status" , zap . String ( "server" , s . Uuid ) , zap . Error ( errors . New ( rerr . String ( ) ) ) )
return
}
zap . S ( ) . Debugw ( "successfully notified panel about archive status" , zap . String ( "server" , s . Uuid ) )
} ( )
w . WriteHeader ( http . StatusAccepted )
}
func ( rt * Router ) routeGetServerArchive ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) {
s := rt . GetServer ( ps . ByName ( "server" ) )
st , err := s . Archiver . Stat ( )
if err != nil {
if ! os . IsNotExist ( err ) {
zap . S ( ) . Errorw ( "failed to stat archive for reading" , zap . String ( "server" , s . Uuid ) , zap . Error ( err ) )
http . Error ( w , "failed to stat archive" , http . StatusInternalServerError )
return
}
http . NotFound ( w , r )
return
}
checksum , err := s . Archiver . Checksum ( )
if err != nil {
zap . S ( ) . Errorw ( "failed to calculate checksum" , zap . String ( "server" , s . Uuid ) , zap . Error ( err ) )
http . Error ( w , "failed to calculate checksum" , http . StatusInternalServerError )
return
}
file , err := os . Open ( s . Archiver . ArchivePath ( ) )
if err != nil {
if ! os . IsNotExist ( err ) {
zap . S ( ) . Errorw ( "failed to open archive for reading" , zap . String ( "server" , s . Uuid ) , zap . Error ( err ) )
}
http . Error ( w , "failed to open archive" , http . StatusInternalServerError )
return
}
defer file . Close ( )
w . Header ( ) . Set ( "X-Checksum" , checksum )
w . Header ( ) . Set ( "X-Mime-Type" , st . Mimetype )
w . Header ( ) . Set ( "Content-Length" , strconv . Itoa ( int ( st . Info . Size ( ) ) ) )
w . Header ( ) . Set ( "Content-Disposition" , "attachment; filename=" + s . Archiver . ArchiveName ( ) )
w . Header ( ) . Set ( "Content-Type" , "application/octet-stream" )
bufio . NewReader ( file ) . WriteTo ( w )
}
2019-05-04 23:04:41 +00:00
func ( rt * Router ) ReaderToBytes ( r io . Reader ) [ ] byte {
buf := bytes . Buffer { }
buf . ReadFrom ( r )
return buf . Bytes ( )
}
2019-04-06 17:49:31 +00:00
// Configures the router and all of the associated routes.
2019-04-06 05:34:53 +00:00
func ( rt * Router ) ConfigureRouter ( ) * httprouter . Router {
router := httprouter . New ( )
2019-04-06 05:20:26 +00:00
2019-12-17 05:43:07 +00:00
router . OPTIONS ( "/api/system" , func ( w http . ResponseWriter , r * http . Request , ps httprouter . Params ) {
rt . AttachAccessControlHeaders ( w , r , ps )
} )
2019-04-06 05:34:53 +00:00
router . GET ( "/" , rt . routeIndex )
2019-12-10 05:05:55 +00:00
router . GET ( "/api/system" , rt . AuthenticateToken ( rt . routeSystemInformation ) )
2019-09-06 04:08:03 +00:00
router . GET ( "/api/servers" , rt . AuthenticateToken ( rt . routeAllServers ) )
router . GET ( "/api/servers/:server" , rt . AuthenticateRequest ( rt . routeServer ) )
2019-09-25 04:21:59 +00:00
router . GET ( "/api/servers/:server/ws" , rt . AuthenticateServer ( rt . routeWebsocket ) )
2019-09-06 04:08:03 +00:00
router . GET ( "/api/servers/:server/logs" , rt . AuthenticateRequest ( rt . routeServerLogs ) )
router . GET ( "/api/servers/:server/files/contents" , rt . AuthenticateRequest ( rt . routeServerFileRead ) )
router . GET ( "/api/servers/:server/files/list-directory" , rt . AuthenticateRequest ( rt . routeServerListDirectory ) )
router . PUT ( "/api/servers/:server/files/rename" , rt . AuthenticateRequest ( rt . routeServerRenameFile ) )
2019-12-28 22:57:19 +00:00
router . POST ( "/api/servers" , rt . AuthenticateToken ( rt . routeCreateServer ) )
router . POST ( "/api/servers/:server/install" , rt . AuthenticateRequest ( rt . routeServerInstall ) )
2019-09-06 04:08:03 +00:00
router . POST ( "/api/servers/:server/files/copy" , rt . AuthenticateRequest ( rt . routeServerCopyFile ) )
router . POST ( "/api/servers/:server/files/write" , rt . AuthenticateRequest ( rt . routeServerWriteFile ) )
router . POST ( "/api/servers/:server/files/create-directory" , rt . AuthenticateRequest ( rt . routeServerCreateDirectory ) )
router . POST ( "/api/servers/:server/files/delete" , rt . AuthenticateRequest ( rt . routeServerDeleteFile ) )
router . POST ( "/api/servers/:server/power" , rt . AuthenticateRequest ( rt . routeServerPower ) )
router . POST ( "/api/servers/:server/commands" , rt . AuthenticateRequest ( rt . routeServerSendCommand ) )
2020-04-03 20:43:13 +00:00
router . POST ( "/api/servers/:server/reinstall" , rt . AuthenticateRequest ( rt . routeServerReinstall ) )
2019-11-24 23:08:38 +00:00
router . PATCH ( "/api/servers/:server" , rt . AuthenticateRequest ( rt . routeServerUpdate ) )
2019-12-22 07:23:56 +00:00
router . DELETE ( "/api/servers/:server" , rt . AuthenticateRequest ( rt . routeServerDelete ) )
2019-09-06 04:08:03 +00:00
2020-04-04 05:17:26 +00:00
router . POST ( "/api/servers/:server/archive" , rt . AuthenticateRequest ( rt . routeRequestServerArchive ) )
router . GET ( "/api/servers/:server/archive" , rt . AuthenticateRequest ( rt . routeGetServerArchive ) )
2019-04-06 05:20:26 +00:00
return router
2019-04-06 05:55:48 +00:00
}