diff --git a/api/server_endpoints.go b/api/server_endpoints.go index 0290799..f7f2d16 100644 --- a/api/server_endpoints.go +++ b/api/server_endpoints.go @@ -123,6 +123,60 @@ func (r *PanelRequest) SendInstallationStatus(uuid string, successful bool) (*Re return nil, nil } +type archiveRequest struct { + Successful bool `json:"successful"` +} + +func (r *PanelRequest) SendArchiveStatus(uuid string, successful bool) (*RequestError, error) { + b, err := json.Marshal(archiveRequest{Successful: successful}) + if err != nil { + return nil, errors.WithStack(err) + } + + resp, err := r.Post(fmt.Sprintf("/servers/%s/archive", uuid), b) + if err != nil { + return nil, errors.WithStack(err) + } + defer resp.Body.Close() + + r.Response = resp + if r.HasError() { + return r.Error(), nil + } + + return nil, nil +} + +func (r *PanelRequest) SendTransferFailure(uuid string) (*RequestError, error) { + resp, err := r.Get(fmt.Sprintf("/servers/%s/transfer/failure", uuid)) + if err != nil { + return nil, errors.WithStack(err) + } + defer resp.Body.Close() + + r.Response = resp + if r.HasError() { + return r.Error(), nil + } + + return nil, nil +} + +func (r *PanelRequest) SendTransferSuccess(uuid string) (*RequestError, error) { + resp, err := r.Get(fmt.Sprintf("/servers/%s/transfer/success", uuid)) + if err != nil { + return nil, errors.WithStack(err) + } + defer resp.Body.Close() + + r.Response = resp + if r.HasError() { + return r.Error(), nil + } + + return nil, nil +} + type BackupRequest struct { Successful bool `json:"successful"` Sha256Hash string `json:"sha256_hash"` @@ -147,4 +201,4 @@ func (r *PanelRequest) SendBackupStatus(uuid string, backup string, data BackupR } return nil, nil -} \ No newline at end of file +} diff --git a/archive_auth.go b/archive_auth.go new file mode 100644 index 0000000..c486cb6 --- /dev/null +++ b/archive_auth.go @@ -0,0 +1,32 @@ +package main + +import ( + "github.com/gbrlsnchs/jwt/v3" + "github.com/pterodactyl/wings/config" + "time" +) + +// ArchiveTokenPayload represents an Archive Token Payload. +type ArchiveTokenPayload struct { + jwt.Payload +} + +func ParseArchiveJWT(token []byte) (*ArchiveTokenPayload, error) { + var payload ArchiveTokenPayload + if alg == nil { + alg = jwt.NewHS256([]byte(config.Get().AuthenticationToken)) + } + + now := time.Now() + verifyOptions := jwt.ValidatePayload( + &payload.Payload, + jwt.ExpirationTimeValidator(now), + ) + + _, err := jwt.Verify(token, alg, &payload, verifyOptions) + if err != nil { + return nil, err + } + + return &payload, nil +} diff --git a/config/config.go b/config/config.go index b397a88..0326f8e 100644 --- a/config/config.go +++ b/config/config.go @@ -57,7 +57,7 @@ type Configuration struct { PanelLocation string `yaml:"remote"` // The token used when performing operations. Requests to this instance must - // validate aganist it. + // validate against it. AuthenticationToken string `yaml:"token"` } @@ -66,6 +66,9 @@ type SystemConfiguration struct { // Directory where the server data is stored at. Data string `default:"/srv/daemon-data" yaml:"data"` + // Directory where server archives for transferring will be stored. + ArchiveDirectory string `default:"/srv/daemon-data/.archives" yaml:"archive_directory"` + // Directory where local backups will be stored on the machine. BackupDirectory string `default:"/srv/daemon-data/.backups" yaml:"backup_directory"` diff --git a/go.mod b/go.mod index 8322bbc..e53810d 100644 --- a/go.mod +++ b/go.mod @@ -35,9 +35,9 @@ require ( github.com/imdario/mergo v0.3.8 github.com/julienschmidt/httprouter v1.2.0 github.com/magiconair/properties v1.8.1 + github.com/mattn/go-shellwords v1.0.10 // indirect github.com/mholt/archiver/v3 v3.3.0 github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db - github.com/olebedev/emitter v0.0.0-20190110104742-e8d1457e6aee github.com/onsi/ginkgo v1.8.0 // indirect github.com/onsi/gomega v1.5.0 // indirect github.com/opencontainers/go-digest v1.0.0-rc1 // indirect @@ -48,18 +48,18 @@ require ( github.com/pterodactyl/sftp-server v1.1.1 github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce github.com/sirupsen/logrus v1.0.5 // indirect + github.com/smartystreets/goconvey v1.6.4 // indirect go.uber.org/atomic v1.5.1 // indirect go.uber.org/multierr v1.4.0 // indirect go.uber.org/zap v1.13.0 - golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 + golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 // indirect golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect - golang.org/x/net v0.0.0-20190923162816-aa69164e4478 + golang.org/x/net v0.0.0-20190923162816-aa69164e4478 // indirect golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab // indirect golang.org/x/text v0.3.2 // indirect golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect golang.org/x/tools v0.0.0-20191206204035-259af5ff87bd // indirect - golang.org/x/tools/gopls v0.1.7 // indirect gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect gopkg.in/ini.v1 v1.51.0 diff --git a/go.sum b/go.sum index 2c974f5..028166f 100644 --- a/go.sum +++ b/go.sum @@ -14,9 +14,6 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= -github.com/buger/jsonparser v0.0.0-20181023193515-52c6e1462ebd/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 h1:D21IyuvjDCshj1/qq+pCNd3VZOAEI9jy6Bi131YlXgI= -github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v0.0.0-20191204142016-1a29609e0929 h1:MW/JDk68Rny52yI0M0N+P8lySNgB+NhpI/uAmhgOhUM= github.com/buger/jsonparser v0.0.0-20191204142016-1a29609e0929/go.mod h1:tgcrVJ81GPSF0mz+0nu1Xaz0fazGPrmmJfJtxjbHhUQ= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448 h1:PUD50EuOMkXVcpBIA/R95d56duJR9VxhwncsFbNnxW4= @@ -24,6 +21,7 @@ github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv github.com/creasty/defaults v1.3.0 h1:uG+RAxYbJgOPCOdKEcec9ZJXeva7Y6mj/8egdzwmLtw= github.com/creasty/defaults v1.3.0/go.mod h1:CIEEvs7oIVZm30R8VxtFJs+4k201gReYyuYHJxZc68I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= @@ -52,13 +50,14 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= @@ -69,6 +68,8 @@ github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFE github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -90,14 +91,14 @@ github.com/magefile/mage v1.9.0 h1:t3AU2wNwehMCW97vuqQLtw6puppWXHO+O2MHo5a50XE= github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw= +github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mholt/archiver/v3 v3.3.0 h1:vWjhY8SQp5yzM9P6OJ/eZEkmi3UAbRrxCq48MxjAzig= github.com/mholt/archiver/v3 v3.3.0/go.mod h1:YnQtqsp+94Rwd0D/rk5cnLrxusUBUXg+08Ebtr1Mqao= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs= github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/olebedev/emitter v0.0.0-20190110104742-e8d1457e6aee h1:IquUs3fIykn10zWDIyddanhpTqBvAHMaPnFhQuyYw5U= -github.com/olebedev/emitter v0.0.0-20190110104742-e8d1457e6aee/go.mod h1:eT2/Pcsim3XBjbvldGiJBvvgiqZkAFyiOJJsDKXs/ts= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -119,11 +120,8 @@ github.com/pkg/sftp v1.8.3 h1:9jSe2SxTM8/3bXZjtqnkgTBW+lA8db0knZJyns7gpBA= github.com/pkg/sftp v1.8.3/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk= github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pterodactyl/sftp-server v1.0.4 h1:hPUaUQvA6U/R8/bybQFDMBDcZaqqj+kufGBQZ3esP5M= -github.com/pterodactyl/sftp-server v1.0.4/go.mod h1:0LKDl+f1qY2TH9+B5jxdROktW0+10UM1qJ472iWbyvQ= -github.com/pterodactyl/sftp-server v1.1.0 h1:NcYh+UqEH8pfvFsee6yt7eb08RLLidw6q+cNOCdh/V0= -github.com/pterodactyl/sftp-server v1.1.0/go.mod h1:b1VVWYv0RF9rxSZQqaD/rYXriiRMNPsbV//CKMXR4ag= github.com/pterodactyl/sftp-server v1.1.1 h1:IjuOy21BNZxfejKnXG1RgLxXAYylDqBVpbKZ6+fG5FQ= github.com/pterodactyl/sftp-server v1.1.1/go.mod h1:b1VVWYv0RF9rxSZQqaD/rYXriiRMNPsbV//CKMXR4ag= github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce h1:aP+C+YbHZfOQlutA4p4soHi7rVUqHQdWEVMSkHfDTqY= @@ -131,8 +129,13 @@ github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce/go.mod h1:3j2 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/sirupsen/logrus v1.0.5 h1:8c8b5uO0zS4X6RPl/sd1ENwSkIc0/H2PaHxE3udaE8I= github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/uber-go/zap v1.9.1/go.mod h1:GY+83l3yxBcBw2kmHu/sAWwItnTn+ynxHCRo+WiIQOY= github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= @@ -157,17 +160,12 @@ go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480 h1:O5YqonU5IWby+w98jVUG9h7zlCWCcH4RHyPVReBmhzk= -golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= @@ -183,30 +181,26 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab h1:FvshnhkKW+LO3HWHodML8kuVX8rnJTxKm9dFPuI68UM= golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190710153321-831012c29e42/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= -golang.org/x/tools v0.0.0-20190918214516-5a1a30219888/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190925020647-22afafe3322a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -214,7 +208,6 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191206204035-259af5ff87bd h1:Zc7EU2PqpsNeIfOoVA7hvQX4cS3YDJEs5KlfatT3hLo= golang.org/x/tools v0.0.0-20191206204035-259af5ff87bd/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools/gopls v0.1.3/go.mod h1:vrCQzOKxvuiZLjCKSmbbov04oeBQQOb4VQqwYK2PWIY= -golang.org/x/tools/gopls v0.1.7/go.mod h1:PE3vTwT0ejw3a2L2fFgSJkxlEbA8Slbk+Lsy9hTmbG8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo= diff --git a/http.go b/http.go index 6a5af59..6a6cddb 100644 --- a/http.go +++ b/http.go @@ -3,21 +3,28 @@ package main import ( "bufio" "bytes" + "crypto/sha256" + "encoding/hex" "encoding/json" "fmt" "github.com/buger/jsonparser" "github.com/gorilla/websocket" "github.com/julienschmidt/httprouter" + "github.com/mholt/archiver/v3" "github.com/pkg/errors" + "github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/installer" "github.com/pterodactyl/wings/server" "go.uber.org/zap" "io" + "io/ioutil" "net/http" "os" + "path/filepath" "strconv" "strings" + "time" ) // Retrieves a server out of the collection by UUID. @@ -31,7 +38,7 @@ type Router struct { upgrader websocket.Upgrader // The authentication token defined in the config.yml file that allows - // a request to perform any action aganist the daemon. + // a request to perform any action against the daemon. token string } @@ -60,7 +67,7 @@ func (rt *Router) AttachAccessControlHeaders(w http.ResponseWriter, r *http.Requ return w, r, ps } -// Authenticates the request token aganist the given permission string, ensuring that +// Authenticates the request token against the given permission string, ensuring that // 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. func (rt *Router) AuthenticateToken(h httprouter.Handle) httprouter.Handle { @@ -77,7 +84,7 @@ func (rt *Router) AuthenticateToken(h httprouter.Handle) httprouter.Handle { return } - // Try to match the request aganist the global token for the Daemon, regardless + // Try to match the request against 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. if auth[1] == rt.token { @@ -154,7 +161,7 @@ func (rt *Router) routeServerPower(w http.ResponseWriter, r *http.Request, ps ht return } - // Because we route all of the actual bootup process to a seperate thread we need to + // 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. // @@ -207,7 +214,7 @@ func (rt *Router) routeServerPower(w http.ResponseWriter, r *http.Request, ps ht zap.String("action", "restart"), ) } - break; + break case "kill": if err := s.Environment.Terminate(os.Kill); err != nil { zap.S().Errorw( @@ -265,7 +272,7 @@ func (rt *Router) routeServerFileRead(w http.ResponseWriter, r *http.Request, ps return } - f, err := os.OpenFile(cleaned, os.O_RDONLY, 0) + f, err := os.Open(cleaned) 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)) @@ -462,10 +469,11 @@ func (rt *Router) routeServerUpdate(w http.ResponseWriter, r *http.Request, ps h return } + zap.S().Debugw("updated server's data structure", zap.String("server", s.Uuid)) w.WriteHeader(http.StatusNoContent) } -func (rt *Router) routeCreateServer(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { +func (rt *Router) routeCreateServer(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { defer r.Body.Close() inst, err := installer.New(rt.ReaderToBytes(r.Body)) @@ -557,6 +565,12 @@ func (rt *Router) routeServerDelete(w http.ResponseWriter, r *http.Request, ps h // to start it while this process is running. s.Suspended = true + // Delete the server's archive if it exists. + if err := s.Archiver.DeleteIfExists(); err != nil { + zap.S().Errorw("failed to delete server's archive", zap.String("server", s.Uuid), zap.Error(err)) + // We intentionally don't return here, if the archive fails to delete, the server can still be removed. + } + zap.S().Infow("processing server deletion request", zap.String("server", s.Uuid)) // 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 @@ -569,7 +583,7 @@ func (rt *Router) routeServerDelete(w http.ResponseWriter, r *http.Request, ps h } // 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 + // done in a separate 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 @@ -597,6 +611,270 @@ func (rt *Router) routeServerDelete(w http.ResponseWriter, r *http.Request, ps h w.WriteHeader(http.StatusAccepted) } +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) { + auth := strings.SplitN(r.Header.Get("Authorization"), " ", 2) + + if len(auth) != 2 || auth[0] != "Bearer" { + w.Header().Set("WWW-Authenticate", "Bearer") + http.Error(w, "authorization failed", http.StatusUnauthorized) + return + } + + token, err := ParseArchiveJWT([]byte(auth[1])) + if err != nil { + http.Error(w, "authorization failed", http.StatusUnauthorized) + return + } + + if token.Subject != ps.ByName("server") { + http.Error(w, "forbidden", http.StatusForbidden) + return + } + + 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) +} + +func (rt *Router) routeIncomingTransfer(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + zap.S().Debug("incoming transfer from panel!") + defer r.Body.Close() + + go func(data []byte) { + serverID, _ := jsonparser.GetString(data, "server_id") + url, _ := jsonparser.GetString(data, "url") + token, _ := jsonparser.GetString(data, "token") + + // Create an http client with no timeout. + client := &http.Client{Timeout: 0} + + hasError := true + defer func() { + if !hasError { + return + } + + zap.S().Errorw("server transfer has failed", zap.String("server", serverID)) + rerr, err := api.NewRequester().SendTransferFailure(serverID) + if rerr != nil || err != nil { + if err != nil { + zap.S().Errorw("failed to notify panel with transfer failure", zap.String("server", serverID), zap.Error(err)) + return + } + + zap.S().Errorw("panel returned an error when notifying of a transfer failure", zap.String("server", serverID), zap.Error(errors.New(rerr.String()))) + return + } + + zap.S().Debugw("successfully notified panel about transfer failure", zap.String("server", serverID)) + }() + + // Make a new GET request to the URL the panel gave us. + req, err := http.NewRequest("GET", url, nil) + if err != nil { + zap.S().Errorw("failed to create http request", zap.Error(err)) + return + } + + // Add the authorization header. + req.Header.Set("Authorization", token) + + // Execute the http request. + res, err := client.Do(req) + if err != nil { + zap.S().Errorw("failed to send http request", zap.Error(err)) + return + } + defer res.Body.Close() + + // Handle non-200 status codes. + if res.StatusCode != 200 { + body, err := ioutil.ReadAll(res.Body) + if err != nil { + zap.S().Errorw("failed to read response body", zap.Int("status", res.StatusCode), zap.Error(err)) + return + } + + zap.S().Errorw("failed to request server archive", zap.Int("status", res.StatusCode), zap.String("body", string(body))) + return + } + + // Get the path to the archive. + archivePath := filepath.Join(config.Get().System.ArchiveDirectory, serverID + ".tar.gz") + + // Check if the archive already exists and delete it if it does. + _, err = os.Stat(archivePath) + if err != nil { + if !os.IsNotExist(err) { + zap.S().Errorw("failed to stat file", zap.Error(err)) + return + } + } else { + if err := os.Remove(archivePath); err != nil { + zap.S().Errorw("failed to delete old file", zap.Error(err)) + return + } + } + + // Create the file. + file, err := os.Create(archivePath) + if err != nil { + zap.S().Errorw("failed to open file on disk", zap.Error(err)) + return + } + + // Copy the file. + _, err = io.Copy(file, res.Body) + if err != nil { + zap.S().Errorw("failed to copy file to disk", zap.Error(err)) + return + } + + // Close the file so it can be opened to verify the checksum. + if err := file.Close(); err != nil { + zap.S().Errorw("failed to close archive file", zap.Error(err)) + return + } + zap.S().Debug("server archive has been downloaded, computing checksum..", zap.String("server", serverID)) + + // Open the archive file for computing a checksum. + file, err = os.Open(archivePath) + if err != nil { + zap.S().Errorw("failed to open file on disk", zap.Error(err)) + return + } + + // Compute the sha256 checksum of the file. + hash := sha256.New() + if _, err := io.Copy(hash, file); err != nil { + zap.S().Errorw("failed to copy file for checksum verification", zap.Error(err)) + return + } + + // Verify the two checksums. + if hex.EncodeToString(hash.Sum(nil)) != res.Header.Get("X-Checksum") { + zap.S().Errorw("checksum failed verification") + return + } + + // Close the file. + if err := file.Close(); err != nil { + zap.S().Errorw("failed to close archive file", zap.Error(err)) + return + } + + zap.S().Infow("server archive transfer was successful", zap.String("server", serverID)) + + // Get the server data from the request. + serverData, t, _, _ := jsonparser.Get(data, "server") + if t != jsonparser.Object { + zap.S().Errorw("invalid server data passed in request") + return + } + + zap.S().Debug(string(serverData)) + + // Create a new server installer (note this does not execute the install script) + i, err := installer.New(serverData) + if err != nil { + zap.S().Warnw("failed to validate the received server data", zap.Error(err)) + return + } + + // Add the server to the collection. + server.GetServers().Add(i.Server()) + + // Create the server's environment (note this does not execute the install script) + i.Execute() + + // Un-archive the archive. That sounds weird.. + archiver.NewTarGz().Unarchive(archivePath, i.Server().Filesystem.Path()) + + rerr, err := api.NewRequester().SendTransferSuccess(serverID) + if rerr != nil || err != nil { + if err != nil { + zap.S().Errorw("failed to notify panel with transfer success", zap.String("server", serverID), zap.Error(err)) + return + } + + zap.S().Errorw("panel returned an error when notifying of a transfer success", zap.String("server", serverID), zap.Error(errors.New(rerr.String()))) + return + } + + zap.S().Debugw("successfully notified panel about transfer success", zap.String("server", serverID)) + hasError = false + }(rt.ReaderToBytes(r.Body)) + + w.WriteHeader(202) +} + func (rt *Router) ReaderToBytes(r io.Reader) []byte { buf := bytes.Buffer{} buf.ReadFrom(r) @@ -635,5 +913,10 @@ func (rt *Router) ConfigureRouter() *httprouter.Router { router.PATCH("/api/servers/:server", rt.AuthenticateRequest(rt.routeServerUpdate)) router.DELETE("/api/servers/:server", rt.AuthenticateRequest(rt.routeServerDelete)) + router.POST("/api/servers/:server/archive", rt.AuthenticateRequest(rt.routeRequestServerArchive)) + router.GET("/api/servers/:server/archive", rt.AuthenticateServer(rt.routeGetServerArchive)) + + router.POST("/api/transfer", rt.AuthenticateToken(rt.routeIncomingTransfer)) + return router } diff --git a/server/archiver.go b/server/archiver.go new file mode 100644 index 0000000..5c12976 --- /dev/null +++ b/server/archiver.go @@ -0,0 +1,104 @@ +package server + +import ( + "crypto/sha256" + "encoding/hex" + "github.com/mholt/archiver/v3" + "github.com/pterodactyl/wings/config" + "io" + "io/ioutil" + "os" + "path/filepath" +) + +// Archiver represents a Server Archiver. +type Archiver struct { + Server *Server +} + +// ArchivePath returns the path to the server's archive. +func (a *Archiver) ArchivePath() string { + return filepath.Join(config.Get().System.ArchiveDirectory, a.ArchiveName()) +} + +// ArchiveName returns the name of the server's archive. +func (a *Archiver) ArchiveName() string { + return a.Server.Uuid + ".tar.gz" +} + +// Exists returns a boolean based off if the archive exists. +func (a *Archiver) Exists() bool { + if _, err := os.Stat(a.ArchivePath()); os.IsNotExist(err) { + return false + } + + return true +} + +// Stat stats the archive file. +func (a *Archiver) Stat() (*Stat, error) { + return a.Server.Filesystem.unsafeStat(a.ArchivePath()) +} + +// Archive creates an archive of the server and deletes the previous one. +func (a *Archiver) Archive() error { + path := a.Server.Filesystem.Path() + + // Get the list of root files and directories to archive. + var files []string + fileInfo, err := ioutil.ReadDir(path) + if err != nil { + return err + } + + for _, file := range fileInfo { + files = append(files, filepath.Join(path, file.Name())) + } + + stat, err := a.Stat() + if err != nil && !os.IsNotExist(err) { + return err + } + + // Check if the file exists. + if stat != nil { + if err := os.Remove(a.ArchivePath()); err != nil { + return err + } + } + + return archiver.NewTarGz().Archive(files, a.ArchivePath()) +} + +// DeleteIfExists deletes the archive if it exists. +func (a *Archiver) DeleteIfExists() error { + stat, err := a.Stat() + if err != nil && !os.IsNotExist(err) { + return err + } + + // Check if the file exists. + if stat != nil { + if err := os.Remove(a.ArchivePath()); err != nil { + return err + } + } + + return nil +} + +// Checksum computes a SHA256 checksum of the server's archive. +func (a *Archiver) Checksum() (string, error) { + file, err := os.Open(a.ArchivePath()) + if err != nil { + return "", err + } + defer file.Close() + + hash := sha256.New() + if _, err := io.Copy(hash, file); err != nil { + return "", err + } + + return hex.EncodeToString(hash.Sum(nil)), nil +} diff --git a/server/filesystem.go b/server/filesystem.go index e38fb4e..02b6e2b 100644 --- a/server/filesystem.go +++ b/server/filesystem.go @@ -299,14 +299,18 @@ func (fs *Filesystem) Stat(p string) (*Stat, error) { return nil, err } - s, err := os.Stat(cleaned) + return fs.unsafeStat(cleaned) +} + +func (fs *Filesystem) unsafeStat(p string) (*Stat, error) { + s, err := os.Stat(p) if err != nil { return nil, err } var m = "inode/directory" if !s.IsDir() { - m, _, err = mimetype.DetectFile(cleaned) + m, _, err = mimetype.DetectFile(p) if err != nil { return nil, err } diff --git a/server/server.go b/server/server.go index 7b9f37e..d095d34 100644 --- a/server/server.go +++ b/server/server.go @@ -27,11 +27,11 @@ func GetServers() *Collection { // High level definition for a server instance being controlled by Wings. type Server struct { // The unique identifier for the server that should be used when referencing - // it aganist the Panel API (and internally). This will be used when naming + // it against the Panel API (and internally). This will be used when naming // docker containers as well as in log output. Uuid string `json:"uuid"` - // Wether or not the server is in a suspended state. Suspended servers cannot + // Whether or not the server is in a suspended state. Suspended servers cannot // be started or modified except in certain scenarios by an admin user. Suspended bool `json:"suspended"` @@ -45,6 +45,7 @@ type Server struct { // server process. EnvVars map[string]string `json:"environment" yaml:"environment"` + Archiver Archiver `json:"-" yaml:"-"` CrashDetection CrashDetection `json:"crash_detection" yaml:"crash_detection"` Build BuildSettings `json:"build"` Allocations Allocations `json:"allocations"` @@ -205,7 +206,7 @@ func (s *Server) Init() { s.mutex = &sync.Mutex{} } -// Initalizes a server using a data byte array. This will be marshaled into the +// Initializes a server using a data byte array. This will be marshaled into the // given struct using a YAML marshaler. This will also configure the given environment // for a server. func FromConfiguration(data []byte, cfg *config.SystemConfiguration) (*Server, error) { @@ -231,6 +232,9 @@ func FromConfiguration(data []byte, cfg *config.SystemConfiguration) (*Server, e } s.Cache = cache.New(time.Minute*10, time.Minute*15) + s.Archiver = Archiver{ + Server: s, + } s.Filesystem = Filesystem{ Configuration: cfg, Server: s, @@ -367,7 +371,7 @@ func (s *Server) SetState(state string) error { // // In the event that we have passed the thresholds, don't do anything, otherwise // automatically attempt to start the process back up for the user. This is done in a - // seperate thread as to not block any actions currently taking place in the flow + // separate thread as to not block any actions currently taking place in the flow // that called this function. if (prevState == ProcessStartingState || prevState == ProcessRunningState) && s.State == ProcessOfflineState { zap.S().Infow("detected server as entering a potentially crashed state; running handler", zap.String("server", s.Uuid)) diff --git a/server/update.go b/server/update.go index 27e3be5..9e74676 100644 --- a/server/update.go +++ b/server/update.go @@ -6,7 +6,6 @@ import ( "github.com/imdario/mergo" "github.com/pkg/errors" "go.uber.org/zap" - "os" ) // Merges data passed through in JSON form into the existing server object. @@ -53,6 +52,11 @@ func (s *Server) UpdateDataStructure(data []byte, background bool) error { } } else { s.Suspended = v + if s.Suspended { + zap.S().Debugw("server has been suspended", zap.String("server", s.Uuid)) + } else { + zap.S().Debugw("server has been unsuspended", zap.String("server", s.Uuid)) + } } // Environment and Mappings should be treated as a full update at all times, never a @@ -101,12 +105,20 @@ func (s *Server) runBackgroundActions() { if server.Suspended && server.State != ProcessOfflineState { zap.S().Infow("server suspended with running process state, terminating now", zap.String("server", server.Uuid)) - if err := server.Environment.Terminate(os.Kill); err != nil { + /*if err := server.Environment.Terminate(os.Kill); err != nil { zap.S().Warnw( "failed to terminate server environment after seeing suspension", zap.String("server", server.Uuid), zap.Error(err), ) + }*/ + + if err := server.Environment.WaitForStop(10, true); err != nil { + zap.S().Warnw( + "failed to stop server environment after seeing suspension", + zap.String("server", server.Uuid), + zap.Error(err), + ) } } }(s) diff --git a/wings.go b/wings.go index 59ee8f6..adda06e 100644 --- a/wings.go +++ b/wings.go @@ -54,7 +54,7 @@ func main() { config.Set(c) config.SetDebugViaFlag(debug) - zap.S().Infof("checking for pterodactyl system user \"%s\"", c.System.User) + zap.S().Infof("checking for pterodactyl system user \"%s\"", c.System.Username) if su, err := c.EnsurePterodactylUser(); err != nil { zap.S().Panicw("failed to create pterodactyl system user", zap.Error(err)) return @@ -144,6 +144,11 @@ func main() { sftp.Initialize(c) } + // Ensure the archive directory exists. + if err := os.MkdirAll(c.System.ArchiveDirectory, 0755); err != nil { + zap.S().Errorw("failed to create archive directory", zap.Error(err)) + } + r := &Router{ token: c.AuthenticationToken, upgrader: websocket.Upgrader{