Compare commits

..

58 Commits

Author SHA1 Message Date
Dane Everitt
04d714f19c Update CHANGELOG.md 2020-12-06 15:49:46 -08:00
Dane Everitt
a18f60bd05 Merge branch 'develop' of https://github.com/pterodactyl/wings into develop 2020-12-06 15:35:04 -08:00
Dane Everitt
deea5babbc Publish message when error occurs during crash handling 2020-12-06 15:34:58 -08:00
Matthew Penner
e41b3dc09a Merge branch 'matthewpi/backups-patch-1' into develop
# Conflicts:
#	loggers/cli/cli.go
2020-12-06 16:18:15 -07:00
Matthew Penner
6366794838 Actually initialize the cache 2020-12-06 16:15:54 -07:00
Matthew Penner
c01a39d881 Add caching to build-test workflow 2020-12-06 16:12:41 -07:00
Dane Everitt
3f2ce59766 Merge branch 'develop' of https://github.com/pterodactyl/wings into develop 2020-12-06 15:10:14 -08:00
Dane Everitt
bcf0c72e47 Correctly report errors to admin users over the websocket; closes pterodactyl/panel#2709 2020-12-06 15:10:08 -08:00
Matthew Penner
bd5892b70c yeet useless boi 2020-12-06 16:05:57 -07:00
Dane Everitt
70ea61f22f test first I guess 2020-12-06 15:04:00 -08:00
Dane Everitt
1b0c2e1764 * 2020-12-06 15:02:50 -08:00
Dane Everitt
3d532f6e0b Simplify logic 2020-12-06 14:58:35 -08:00
Matthew Penner
81fd1a3758 Make logged stacktraces more useful 2020-12-06 15:55:35 -07:00
Matthew Penner
b52c3fb61e Cleanup backup/backup_s3.go 2020-12-06 15:25:11 -07:00
Matthew Penner
bc3d92f9e6 Change backup upload id cache to use the cache package 2020-12-06 15:23:44 -07:00
Matthew Penner
ee08829a28 s3 backups: handle CompleteMultipartUpload and AbortMultipartUpload on the panel 2020-12-06 13:56:17 -07:00
Dane Everitt
83f0d2c953 Merge branch 'develop' of https://github.com/pterodactyl/wings into develop 2020-12-06 10:53:57 -08:00
Dane Everitt
605be3ebad Update README.md 2020-12-06 10:53:55 -08:00
Matthew Penner
ca6dc2c964 Remove .travis.yml 2020-12-03 18:32:59 -07:00
Dane Everitt
dc41126e25 Merge pull request #72 from TortleWortle/patch-1
Allow multiple publishing on multiple interfaces on same port.
2020-12-02 08:18:38 -08:00
TortleWortle
da4c542724 Update allocations.go 2020-12-02 14:27:37 +01:00
Tortle
e7d93a5248 Update allocations.go 2020-12-01 23:03:05 +01:00
Tortle
51aa4c73cd Update allocations.go 2020-12-01 21:20:01 +01:00
Tortle
4a7510d36f Allow multiple publishing on multiple interfaces on same port.
Fixes the issue where you cannot have multiple ip addresses on the same port for a server.
2020-12-01 20:43:40 +01:00
Matthew Penner
ba0a1a651e Only use the permission bits for ModeBits 2020-11-29 16:13:07 -07:00
Matthew Penner
068f41393d Return mode_bits when listing files in a directory 2020-11-29 14:20:24 -07:00
Matthew Penner
169e8b8f8b Fix chmod endpoint mode value 2020-11-29 13:48:15 -07:00
Matthew Penner
c6e2889075 Fix chmod endpoint 2020-11-29 13:44:28 -07:00
Matthew Penner
f62f714863 Add chmod endpoint to server files 2020-11-29 13:07:45 -07:00
Dane Everitt
da9ace5d9d Create FUNDING.yml 2020-11-29 11:58:20 -08:00
Dane Everitt
97345123ce Merge branch 'develop' of https://github.com/pterodactyl/wings into develop 2020-11-29 11:48:03 -08:00
Dane Everitt
8aa9105ed3 Make paths lowercase for cert serving; closes pterodactyl/panel#2745 2020-11-29 11:47:52 -08:00
Matthew Penner
e8088f85d0 Lets not try to log the server ID twice 2020-11-29 12:35:02 -07:00
Matthew Penner
0c8476c79b Cleanup router/router_transfer.go 2020-11-29 12:33:44 -07:00
Matthew Penner
a7a66e8bc0 Notify panel of failed archive generation when transferring a server 2020-11-29 12:31:54 -07:00
Matthew Penner
65a861a9b6 Remove temporary transfer archives; closes https://github.com/pterodactyl/panel/issues/2742 2020-11-28 17:00:52 -07:00
Matthew Penner
de51fd1c51 Error handling improvements (#71)
* Remove `emperror.dev/errors`, remove all `errors#Wrap` and `errors#WithStack` calls
* Improve logging in `server/backup.go`
2020-11-28 16:57:10 -07:00
Matthew Penner
40c70673cd Use registry authentication when pulling install image, fixes https://github.com/pterodactyl/panel/issues/2589 2020-11-20 14:36:29 -07:00
Matthew Penner
73b221d022 Switch old Server#GetState() calls over to Server#Environment#State() 2020-11-20 14:35:29 -07:00
Matthew Penner
287b286940 Oh, that's why it was done like that 2020-11-18 18:44:25 -07:00
Matthew Penner
1d0e85cf55 Attempt to fix 'archive/tar: write too long' error when creating a backup 2020-11-18 16:30:34 -07:00
Matthew Penner
30ec6dc78d fix panic when getting error stack 2020-11-15 13:34:19 -07:00
Matthew Penner
121a4d1146 Attempt to fix codeql-analysis.yml 2020-11-15 13:04:03 -07:00
Matthew Penner
b9be373671 Attempt to force GitHub Actions to use go1.15.5 2020-11-15 13:00:33 -07:00
Dane Everitt
aedd0e406c appease docker compose [skip docker] 2020-11-14 20:50:09 -08:00
Dane Everitt
82b23ef638 Add support for automated docker build 2020-11-14 20:42:12 -08:00
Dane Everitt
d970ec35b7 Update dockerfile to build quicker 2020-11-14 19:22:29 -08:00
Dane Everitt
e2872e786e Tiny optimization for handling string output from server console 2020-11-12 21:41:35 -08:00
Dane Everitt
f81e35d960 Merge branch 'develop' of https://github.com/pterodactyl/wings into develop 2020-11-12 20:50:18 -08:00
Dane Everitt
672fb860ea Don't add a stack for deadline exceeded when terminating 2020-11-12 20:50:15 -08:00
Dane Everitt
8081c83de4 Allow disabling disk checking with 0 value for check interval 2020-11-12 20:50:03 -08:00
Dane Everitt
f379d0e54a Merge pull request #68 from gzzchh/develop
Turn off the CGO to make it static linked
2020-11-11 10:58:49 -08:00
Dane Everitt
ffb6bd72ef Don't accidentally reset a server's state when starting it after a system reboot; closes pterodactyl/panel#2695 2020-11-10 21:21:20 -08:00
Dane Everitt
488ef9de54 Merge branch 'develop' of https://github.com/pterodactyl/wings into develop 2020-11-10 20:36:42 -08:00
Dane Everitt
34349d4b48 Don't report a context cancelation as an error 2020-11-10 20:36:40 -08:00
Matthew Penner
2197d82957 Clean paths from AllowedMounts to ensure consistency 2020-11-09 16:58:01 -07:00
Dane Everitt
20ece60a72 Fix files not being editable correctly in the panel; closes pterodactyl/panel#2679 2020-11-08 17:06:40 -08:00
gzzchh
33e584b447 Turn off the CGO to make it static linked 2020-11-08 14:42:55 +08:00
70 changed files with 802 additions and 642 deletions

2
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
github: [DaneEveritt]
custom: ["https://paypal.me/PterodactylSoftware"]

View File

@@ -1,12 +1,10 @@
name: "Build & Test"
name: Run Tests
on:
push:
branches-ignore:
- 'master'
- 'release/**'
pull_request:
jobs:
build:
strategy:
@@ -15,7 +13,7 @@ jobs:
matrix:
os: [ ubuntu-20.04 ]
go: [ 1.15 ]
go: [ 1.15.6 ]
goos: [ linux ]
goarch: [ amd64, arm, arm64 ]
@@ -28,12 +26,40 @@ jobs:
with:
go-version: ${{ matrix.go }}
- name: Print Environment
id: env
run: |
printf "Go Executable Path: $(which go)\n"
printf "Go Version: $(go version)\n"
printf "\n\nGo Environment:\n\n"
go env
printf "\n\nSystem Environment:\n\n"
env
echo "::set-output name=version_tag::${GITHUB_REF/refs\/tags\//}"
echo "::set-output name=short_sha::$(git rev-parse --short HEAD)"
echo "::set-output name=go_cache::$(go env GOCACHE)"
- name: Build Cache
uses: actions/cache@v2
with:
path: ${{ steps.env.outputs.go_cache }}
key: ${{ runner.os }}-${{ matrix.go }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-${{ matrix.go }}-go
- name: Get Dependencies
run: |
go get -v -t -d ./...
- name: Build
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: 0
SRC_PATH: github.com/pterodactyl/wings
run: |
go build -v -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=dev-${GIT_COMMIT:0:7}" -o build/wings_${{ matrix.goos }}_${{ matrix.goarch }} wings.go
go build -v -trimpath -ldflags="-s -w -X ${SRC_PATH}/system.Version=dev-${GIT_COMMIT:0:7}" -o build/wings_${{ matrix.goos }}_${{ matrix.goarch }} wings.go
- name: Test
run: go test ./...

View File

@@ -1,16 +1,12 @@
name: "Code scanning - action"
name: CodeQL Scanning
on:
push:
branches:
- 'develop'
pull_request:
schedule:
- cron: '0 21 * * 6'
jobs:
CodeQL-Build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
@@ -18,7 +14,6 @@ jobs:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2

47
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
name: Publish Docker Image
on:
push:
branches:
- 'develop'
tags:
- 'v*'
jobs:
push_to_registry:
name: Push Image to GitHub Packages
runs-on: ubuntu-latest
# Always run against a tag, even if the commit into the tag has [docker skip]
# within the commit message.
if: "!contains(github.ref, 'develop') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))"
steps:
- uses: actions/checkout@v2
- uses: crazy-max/ghaction-docker-meta@v1
id: docker_meta
with:
images: ghcr.io/pterodactyl/wings
- uses: docker/setup-qemu-action@v1
- uses: docker/setup-buildx-action@v1
- uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Release Production Build
uses: docker/build-push-action@v2
if: "!contains(github.ref, 'develop')"
env:
REF: ${{ github.ref }}
with:
push: true
build-args: |
VERSION=${REF:11}
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}
- name: Release Development Build
uses: docker/build-push-action@v2
if: "contains(github.ref, 'develop')"
with:
push: ${{ github.event_name != 'pull_request' }}
build-args: |
VERSION=dev-${GIT_COMMIT:0:7}
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}

View File

@@ -1,10 +1,8 @@
name: "Release"
name: Create Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-20.04
@@ -20,9 +18,9 @@ jobs:
env:
REF: ${{ github.ref }}
run: |
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${REF:11}" -o build/wings_linux_amd64 -v wings.go
GOOS=linux GOARCH=arm64 go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${REF:11}" -o build/wings_linux_arm64 -v wings.go
GOOS=linux GOARCH=arm go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${REF:11}" -o build/wings_linux_arm -v wings.go
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${REF:11}" -o build/wings_linux_amd64 -v wings.go
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${REF:11}" -o build/wings_linux_arm64 -v wings.go
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${REF:11}" -o build/wings_linux_arm -v wings.go
- name: Test
run: go test ./...

View File

@@ -1,47 +0,0 @@
os: linux
dist: xenial
language: go
go:
- 1.13.x
go_import_path: "github.com/pterodactyl/wings"
services:
- docker
install:
- mkdir -p $GOPATH/bin
# Install used tools
- go get github.com/mitchellh/gox
- go get github.com/haya14busa/goverage
- go get github.com/schrej/godacov
- go mod download
script:
- make cross-build
- goverage -v -coverprofile=coverage.out ./...
- godacov -t $CODACY_TOKEN -r ./coverage.out -c $TRAVIS_COMMIT
deploy:
provider: releases
api_key:
secure: HQ8AvnSsOW2aDUKv25sU83SswK9rReGeFi68SotLGPdWyFBWJbp/JEHhw9swSqvhLPykx5QqLnRPG4nomOp2i5dVTXgM/7C3wQ2ULymkJDZqDJEAxjm1IuNsjXgcFqp0zcNXL3g0moaorHS2XZpzbgaewlCyYoEb+3SZUGzOCPIjSFvoIBaAYx6kRn+pyWo1I0mQChno2i7SGvAoZwh/hZIO6L5FZe5PcpBs/SxkZ+/shsGMk7CIyNMhG6CQTE1tlr+ZenluXjtliZfc4XwkHG/9MICNl8ihUrnN6YfdvJZXLQvolZQ0QJ5Eyb04jQd1yzKR1hcLx2S42IAWxaWTy5QxSN8QyG5wBRNg567ib5FEqY4M1nyQnWQbUbaiYloYBp14aR1L9DQw8+xmXnlgnTUPq1w+cOpQLeY/RENCalgHe7NoI3lClC2b7/c1j+O7RA68yYUFUod0y7ZXcCwsJkbRk7xgyDEAGs+rq8wLknj6f8y8cfNm179lRARwblnmo9uA43Tlee8DBSziSvJy/mYMzdIQeb+PHuznXjr4fze7x+zvronkiD/JH8MjJl3SWaE7DGtc5jz4+aRxU3rMbHwToEOY6u5pIsvz5PRFYWBvKX2+VoxmdR+m1qhAxsg0wtbA0CTnqgHNGMIFDWVTDQSy8LvJt+usUn1RtrYyyiI=
file_glob: true
file: build/*
on:
tags: true
branch: master
notifications:
email: false
webhooks:
urls:
- https://misc.schrej.net/travistodiscord/pterodev.php
on_success: change
on_failure: always
on_error: always
on_cancel: always
on_start: never

View File

@@ -1,5 +1,39 @@
# Changelog
## v1.1.3
### Fixed
* Fixes `archive/tar: write too long` error when creating a server backup.
* Fixes server installation docker images not using authentication properly during the pull.
* Fixes temporary transfer files not being removed after the transfer is completed.
* Fixes TLS certificate checking to be all lowercase to avoid any lookup issues when an all-caps domain is provided.
* Fixes multiple interfaces with the same port not being publishable for a server.
* Fixes errors encountered during websocket processes being incorrectly passed back to the Panel as a JWT error rather than a generic Wings error for admin users.
### Added
* Added logic to notify the Panel when archive generation fails.
* Added endpoint to run `chmod` commands against server files and updated API response to include the mode bits when requesting files.
### Changed
* Updated internals to call `Server.Environment.State()` rather than deprecated `Server.GetState()` functions.
* Improved error handling logic and massively simplified error passing around the codebase.
## v1.1.2
### Fixed
* Fixes binaries built as part of the release process not being usable in MUSL based environments (such as our Docker images).
* Fixes server states being incorrectly set back to offline when a server is started after a system restart.
### Changed
* Improved logic for cleaning `allowed_mount` paths for consistency.
* Certain context cancelation deadline errors are no longer wrong reported at an error level (since they're expected).
* Very minor micro-optimizations for some string handling with server console output.
### Added
* Added a hidden option to disable all disk checking for servers by setting the `disk_check_interval` to `0` in the config file.
## v1.1.1
### Fixed
* Fixes certain files returning invalid data in the request due to a bad header set after sending data down the line.
## v1.1.0
This release **requires** `Panel@1.1.0` or later to run due to API changes.

View File

@@ -1,14 +1,11 @@
# ----------------------------------
# Pterodactyl Panel Dockerfile
# ----------------------------------
FROM golang:1.15-alpine
ARG VERSION="develop"
COPY . /go/wings/
WORKDIR /go/wings/
RUN apk add --no-cache upx \
&& go build -ldflags="-s -w" \
&& upx --brute wings
&& CGO_ENABLED=0 go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${VERSION}" \
&& upx wings
FROM alpine:latest
COPY --from=0 /go/wings/wings /usr/bin/
CMD ["wings","--config", "/etc/pterodactyl/config.yml"]
CMD ["wings", "--config", "/etc/pterodactyl/config.yml"]

View File

@@ -23,7 +23,7 @@ I would like to extend my sincere thanks to the following sponsors for helping f
| [**MineStrator**](https://minestrator.com/) | Looking for a French highend hosting company for you minecraft server? More than 14,000 members on our discord, trust us. |
| [**DedicatedMC**](https://dedicatedmc.io/) | DedicatedMC provides Raw Power hosting at affordable pricing, making sure to never compromise on your performance and giving you the best performance money can buy. |
| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! |
| [**XCORE-SERVER.de**](https://xcore-server.de/) | XCORE-SERVER.de offers High-End Servers for hosting and gaming since 2012. Fast, excellent and well-known for eSports Gaming. |
| [**XCORE**](https://xcore-server.de/) | XCORE offers High-End Servers for hosting and gaming since 2012. Fast, excellent and well-known for eSports Gaming. |
| [**RoyaleHosting**](https://royalehosting.net/) | Build your dreams and deploy them with RoyaleHostings reliable servers and network. Easy to use, provisioned in a couple of minutes. |
| [**Spill Hosting**](https://spillhosting.no/) | Spill Hosting is a Norwegian hosting service, which aims to cheap services on quality servers. Premium i9-9900K processors will run your game like a dream. |
| [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. |

View File

@@ -2,10 +2,10 @@ package api
import (
"bytes"
"emperror.dev/errors"
"encoding/json"
"fmt"
"github.com/apex/log"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system"
"io"
@@ -69,7 +69,7 @@ func (r *Request) Endpoint(endpoint string) string {
func (r *Request) Make(method, url string, body io.Reader, opts ...func(r *http.Request)) (*Response, error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
req.Header.Set("User-Agent", fmt.Sprintf("Pterodactyl Wings/v%s (id:%s)", system.Version, config.Get().AuthenticationTokenId))
@@ -127,7 +127,7 @@ func (r *Request) Get(url string, data Q) (*Response, error) {
func (r *Request) Post(url string, data interface{}) (*Response, error) {
b, err := json.Marshal(data)
if err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
return r.Make(http.MethodPost, r.Endpoint(url), bytes.NewBuffer(b))
@@ -167,10 +167,10 @@ func (r *Response) Read() ([]byte, error) {
func (r *Response) Bind(v interface{}) error {
b, err := r.Read()
if err != nil {
return errors.WithStackIf(err)
return err
}
return errors.WithStackIf(json.Unmarshal(b, &v))
return json.Unmarshal(b, &v)
}
// Returns the error message from the API call as a string. The error message will be formatted

View File

@@ -1,22 +1,25 @@
package api
import (
"emperror.dev/errors"
"fmt"
"github.com/patrickmn/go-cache"
"strconv"
"time"
)
// backupUploadIDs stores a cache of active S3 backups.
var backupUploadIDs = cache.New(time.Hour*3, time.Minute*5)
type BackupRemoteUploadResponse struct {
CompleteMultipartUpload string `json:"complete_multipart_upload"`
AbortMultipartUpload string `json:"abort_multipart_upload"`
Parts []string `json:"parts"`
PartSize int64 `json:"part_size"`
UploadID string `json:"upload_id"`
Parts []string `json:"parts"`
PartSize int64 `json:"part_size"`
}
func (r *Request) GetBackupRemoteUploadURLs(backup string, size int64) (*BackupRemoteUploadResponse, error) {
resp, err := r.Get(fmt.Sprintf("/backups/%s", backup), Q{"size": strconv.FormatInt(size, 10)})
if err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
defer resp.Body.Close()
@@ -26,13 +29,19 @@ func (r *Request) GetBackupRemoteUploadURLs(backup string, size int64) (*BackupR
var res BackupRemoteUploadResponse
if err := resp.Bind(&res); err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
// Store the backup upload id for later use, this is a janky way to be able to use it later with SendBackupStatus.
// Yes, the timeout of 3 hours is intentional, if this value is removed before the backup completes,
// the backup will fail even if it uploaded properly.
backupUploadIDs.Set(backup, res.UploadID, 0)
return &res, nil
}
type BackupRequest struct {
UploadID string `json:"upload_id"`
Checksum string `json:"checksum"`
ChecksumType string `json:"checksum_type"`
Size int64 `json:"size"`
@@ -42,9 +51,14 @@ type BackupRequest struct {
// Notifies the panel that a specific backup has been completed and is now
// available for a user to view and download.
func (r *Request) SendBackupStatus(backup string, data BackupRequest) error {
// Set the UploadID on the data.
if v, ok := backupUploadIDs.Get(backup); ok {
data.UploadID = v.(string)
}
resp, err := r.Post(fmt.Sprintf("/backups/%s", backup), data)
if err != nil {
return errors.WithStackIf(err)
return err
}
defer resp.Body.Close()

View File

@@ -2,7 +2,6 @@ package api
import (
"context"
"emperror.dev/errors"
"encoding/json"
"fmt"
"github.com/apex/log"
@@ -57,7 +56,7 @@ type RawServerData struct {
func (r *Request) GetServers() ([]RawServerData, error) {
resp, err := r.Get("/servers", Q{"per_page": strconv.Itoa(int(config.Get().RemoteQuery.BootServersPerPage))})
if err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
defer resp.Body.Close()
@@ -67,7 +66,7 @@ func (r *Request) GetServers() ([]RawServerData, error) {
var res allServerResponse
if err := resp.Bind(&res); err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
var mu sync.Mutex
@@ -117,7 +116,7 @@ func (r *Request) GetServers() ([]RawServerData, error) {
}
if err := g.Wait(); err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
}
@@ -130,7 +129,7 @@ func (r *Request) GetServerConfiguration(uuid string) (ServerConfigurationRespon
resp, err := r.Get(fmt.Sprintf("/servers/%s", uuid), nil)
if err != nil {
return cfg, errors.WithStackIf(err)
return cfg, err
}
defer resp.Body.Close()
@@ -139,7 +138,7 @@ func (r *Request) GetServerConfiguration(uuid string) (ServerConfigurationRespon
}
if err := resp.Bind(&cfg); err != nil {
return cfg, errors.WithStackIf(err)
return cfg, err
}
return cfg, nil
@@ -150,7 +149,7 @@ func (r *Request) GetInstallationScript(uuid string) (InstallationScript, error)
var is InstallationScript
resp, err := r.Get(fmt.Sprintf("/servers/%s/install", uuid), nil)
if err != nil {
return is, errors.WithStackIf(err)
return is, err
}
defer resp.Body.Close()
@@ -159,7 +158,7 @@ func (r *Request) GetInstallationScript(uuid string) (InstallationScript, error)
}
if err := resp.Bind(&is); err != nil {
return is, errors.WithStackIf(err)
return is, err
}
return is, nil
@@ -169,7 +168,7 @@ func (r *Request) GetInstallationScript(uuid string) (InstallationScript, error)
func (r *Request) SendInstallationStatus(uuid string, successful bool) error {
resp, err := r.Post(fmt.Sprintf("/servers/%s/install", uuid), D{"successful": successful})
if err != nil {
return errors.WithStackIf(err)
return err
}
defer resp.Body.Close()
@@ -183,7 +182,7 @@ func (r *Request) SendInstallationStatus(uuid string, successful bool) error {
func (r *Request) SendArchiveStatus(uuid string, successful bool) error {
resp, err := r.Post(fmt.Sprintf("/servers/%s/archive", uuid), D{"successful": successful})
if err != nil {
return errors.WithStackIf(err)
return err
}
defer resp.Body.Close()
@@ -193,7 +192,7 @@ func (r *Request) SendArchiveStatus(uuid string, successful bool) error {
func (r *Request) SendTransferFailure(uuid string) error {
resp, err := r.Get(fmt.Sprintf("/servers/%s/transfer/failure", uuid), nil)
if err != nil {
return errors.WithStackIf(err)
return err
}
defer resp.Body.Close()
@@ -203,7 +202,7 @@ func (r *Request) SendTransferFailure(uuid string) error {
func (r *Request) SendTransferSuccess(uuid string) error {
resp, err := r.Get(fmt.Sprintf("/servers/%s/transfer/success", uuid), nil)
if err != nil {
return errors.WithStackIf(err)
return err
}
defer resp.Body.Close()

View File

@@ -1,8 +1,8 @@
package api
import (
"emperror.dev/errors"
"github.com/apex/log"
"github.com/pkg/errors"
"regexp"
)

View File

@@ -18,9 +18,9 @@ import (
"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/docker/cli/components/engine/pkg/parsers/operatingsystem"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/docker/pkg/parsers/operatingsystem"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system"
"github.com/spf13/cobra"

View File

@@ -19,7 +19,7 @@ import (
"github.com/pterodactyl/wings/loggers/cli"
"golang.org/x/crypto/acme/autocert"
"emperror.dev/errors"
"github.com/pkg/errors"
"github.com/pkg/profile"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
@@ -77,7 +77,7 @@ func readConfiguration() (*config.Configuration, error) {
}
if s, err := os.Stat(p); err != nil {
return nil, errors.WithStackIf(err)
return nil, err
} else if s.IsDir() {
return nil, errors.New("cannot use directory as configuration file path")
}
@@ -199,7 +199,7 @@ func rootCmdRun(*cobra.Command, []string) {
states, err := server.CachedServerStates()
if err != nil {
log.WithField("error", errors.WithStackIf(err)).Error("failed to retrieve locally cached server states from disk, assuming all servers in offline state")
log.WithField("error", err).Error("failed to retrieve locally cached server states from disk, assuming all servers in offline state")
}
// Create a new workerpool that limits us to 4 servers being bootstrapped at a time
@@ -235,7 +235,7 @@ func rootCmdRun(*cobra.Command, []string) {
// as a result will result in a slow boot.
if !r && (st == environment.ProcessRunningState || st == environment.ProcessStartingState) {
if err := s.HandlePowerAction(server.PowerActionStart); err != nil {
s.Log().WithField("error", errors.WithStackIf(err)).Warn("failed to return server to running state")
s.Log().WithField("error", err).Warn("failed to return server to running state")
}
} else if r || (!r && s.IsRunning()) {
// If the server is currently running on Docker, mark the process as being in that state.
@@ -248,15 +248,14 @@ func rootCmdRun(*cobra.Command, []string) {
s.Environment.SetState(environment.ProcessRunningState)
if err := s.Environment.Attach(); err != nil {
s.Log().WithField("error", errors.WithStackIf(err)).Warn("failed to attach to running server environment")
s.Log().WithField("error", err).Warn("failed to attach to running server environment")
}
return
} else {
// At this point we've determined that the server should indeed be in an offline state, so we'll
// make a call to set that state just to ensure we don't ever accidentally end up with some invalid
// state being tracked.
s.Environment.SetState(environment.ProcessOfflineState)
}
// Addresses potentially invalid data in the stored file that can cause Wings to lose
// track of what the actual server state is.
s.Environment.SetState(environment.ProcessOfflineState)
})
}
@@ -337,7 +336,6 @@ func rootCmdRun(*cobra.Command, []string) {
if err := s.ListenAndServeTLS("", ""); err != nil {
log.WithFields(log.Fields{"auto_tls": true, "tls_hostname": tlsHostname, "error": err}).
Fatal("failed to configure HTTP server using auto-tls")
os.Exit(1)
}
return
@@ -345,9 +343,8 @@ func rootCmdRun(*cobra.Command, []string) {
// Check if main http server should run with TLS.
if c.Api.Ssl.Enabled {
if err := s.ListenAndServeTLS(c.Api.Ssl.CertificateFile, c.Api.Ssl.KeyFile); err != nil {
if err := s.ListenAndServeTLS(strings.ToLower(c.Api.Ssl.CertificateFile), strings.ToLower(c.Api.Ssl.KeyFile)); err != nil {
log.WithFields(log.Fields{"auto_tls": false, "error": err}).Fatal("failed to configure HTTPS server")
os.Exit(1)
}
return
}
@@ -356,7 +353,6 @@ func rootCmdRun(*cobra.Command, []string) {
s.TLSConfig = nil
if err := s.ListenAndServe(); err != nil {
log.WithField("error", err).Fatal("failed to configure HTTP server")
os.Exit(1)
}
}
@@ -369,13 +365,13 @@ func Execute() error {
// in the code without having to pass around a logger instance.
func configureLogging(logDir string, debug bool) error {
if err := os.MkdirAll(path.Join(logDir, "/install"), 0700); err != nil {
return errors.WithStackIf(err)
return err
}
p := filepath.Join(logDir, "/wings.log")
w, err := logrotate.NewFile(p)
if err != nil {
panic(errors.WrapIf(err, "failed to open process log file"))
panic(errors.WithMessage(err, "failed to open process log file"))
}
if debug {

View File

@@ -1,11 +1,11 @@
package config
import (
"emperror.dev/errors"
"fmt"
"github.com/cobaugh/osrelease"
"github.com/creasty/defaults"
"github.com/gbrlsnchs/jwt/v3"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"io/ioutil"
"os"
@@ -47,12 +47,6 @@ type Configuration struct {
System SystemConfiguration `json:"system" yaml:"system"`
Docker DockerConfiguration `json:"docker" yaml:"docker"`
// The amount of time in seconds that should elapse between disk usage checks
// run by the daemon. Setting a higher number can result in better IO performance
// at an increased risk of a malicious user creating a process that goes over
// the assigned disk limits.
DiskCheckTimeout int `yaml:"disk_check_timeout"`
// Defines internal throttling configurations for server processes to prevent
// someone from running an endless loop that spams data to logs.
Throttles ConsoleThrottles
@@ -198,7 +192,7 @@ func GetJwtAlgorithm() *jwt.HMACSHA {
func NewFromPath(path string) (*Configuration, error) {
c := new(Configuration)
if err := defaults.Set(c); err != nil {
return c, errors.WithStackIf(err)
return c, err
}
c.unsafeSetPath(path)
@@ -236,12 +230,12 @@ func (c *Configuration) EnsurePterodactylUser() (*user.User, error) {
if err == nil {
return u, c.setSystemUser(u)
} else if _, ok := err.(user.UnknownUserError); !ok {
return nil, errors.WithStackIf(err)
return nil, err
}
sysName, err := getSystemName()
if err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
var command = fmt.Sprintf("useradd --system --no-create-home --shell /bin/false %s", c.System.Username)
@@ -254,17 +248,17 @@ func (c *Configuration) EnsurePterodactylUser() (*user.User, error) {
// We have to create the group first on Alpine, so do that here before continuing on
// to the user creation process.
if _, err := exec.Command("addgroup", "-S", c.System.Username).Output(); err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
}
split := strings.Split(command, " ")
if _, err := exec.Command(split[0], split[1:]...).Output(); err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
if u, err := user.Lookup(c.System.Username); err != nil {
return nil, errors.WithStackIf(err)
return nil, err
} else {
return u, c.setSystemUser(u)
}
@@ -306,11 +300,11 @@ func (c *Configuration) WriteToDisk() error {
b, err := yaml.Marshal(&ccopy)
if err != nil {
return errors.WithStackIf(err)
return err
}
if err := ioutil.WriteFile(c.GetPath(), b, 0644); err != nil {
return errors.WithStackIf(err)
return err
}
return nil
@@ -320,7 +314,7 @@ func (c *Configuration) WriteToDisk() error {
func getSystemName() (string, error) {
// use osrelease to get release version and ID
if release, err := osrelease.Read(); err != nil {
return "", errors.WithStackIf(err)
return "", err
} else {
return release["ID"], nil
}

View File

@@ -1,7 +1,6 @@
package config
import (
"emperror.dev/errors"
"encoding/base64"
"encoding/json"
"github.com/docker/docker/api/types"
@@ -73,7 +72,7 @@ func (c RegistryConfiguration) Base64() (string, error) {
b, err := json.Marshal(authConfig)
if err != nil {
return "", errors.WithStackIf(err)
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil

View File

@@ -2,9 +2,9 @@ package config
import (
"context"
"emperror.dev/errors"
"fmt"
"github.com/apex/log"
"github.com/pkg/errors"
"html/template"
"io/ioutil"
"os"
@@ -53,13 +53,12 @@ type SystemConfiguration struct {
// considered stale and a re-check should occur. DANGER: setting this value too low can seriously
// impact system performance and cause massive I/O bottlenecks and high CPU usage for the Wings
// process.
//
// Set to 0 to disable disk checking entirely. This will always return 0 for the disk space used
// by a server and should only be set in extreme scenarios where performance is critical and
// disk usage is not a concern.
DiskCheckInterval int64 `default:"150" yaml:"disk_check_interval"`
// Determines if Wings should detect a server that stops with a normal exit code of
// "0" as being crashed if the process stopped without any Wings interaction. E.g.
// the user did not press the stop button, but the process stopped cleanly.
DetectCleanExitAsCrash bool `default:"true" yaml:"detect_clean_exit_as_crash"`
// If set to true, file permissions for a server will be checked when the process is
// booted. This can cause boot delays if the server has a large amount of files. In most
// cases disabling this should not have any major impact unless external processes are
@@ -74,6 +73,20 @@ type SystemConfiguration struct {
WebsocketLogCount int `default:"150" yaml:"websocket_log_count"`
Sftp SftpConfiguration `yaml:"sftp"`
CrashDetection CrashDetection `yaml:"crash_detection"`
}
type CrashDetection struct {
// Determines if Wings should detect a server that stops with a normal exit code of
// "0" as being crashed if the process stopped without any Wings interaction. E.g.
// the user did not press the stop button, but the process stopped cleanly.
DetectCleanExitAsCrash bool `default:"true" yaml:"detect_clean_exit_as_crash"`
// Timeout specifies the timeout between crashes that will not cause the server
// to be automatically restarted, this value is used to prevent servers from
// becoming stuck in a boot-loop after multiple consecutive crashes.
Timeout int `default:"60" json:"timeout"`
}
// Ensures that all of the system directories exist on the system. These directories are
@@ -94,7 +107,7 @@ func (sc *SystemConfiguration) ConfigureDirectories() error {
// that.
if d, err := filepath.EvalSymlinks(sc.Data); err != nil {
if !os.IsNotExist(err) {
return errors.WithStackIf(err)
return err
}
} else if d != sc.Data {
sc.Data = d
@@ -130,13 +143,13 @@ func (sc *SystemConfiguration) EnableLogRotation() error {
}
if st, err := os.Stat("/etc/logrotate.d"); err != nil && !os.IsNotExist(err) {
return errors.WithStackIf(err)
return err
} else if (err != nil && os.IsNotExist(err)) || !st.IsDir() {
return nil
}
if _, err := os.Stat("/etc/logrotate.d/wings"); err != nil && !os.IsNotExist(err) {
return errors.WithStackIf(err)
return err
} else if err == nil {
return nil
}
@@ -147,7 +160,7 @@ func (sc *SystemConfiguration) EnableLogRotation() error {
// it so files can be rotated easily.
f, err := os.Create("/etc/logrotate.d/wings")
if err != nil {
return errors.WithStackIf(err)
return err
}
defer f.Close()
@@ -167,10 +180,10 @@ func (sc *SystemConfiguration) EnableLogRotation() error {
}`)
if err != nil {
return errors.WithStackIf(err)
return err
}
return errors.WrapIf(t.Execute(f, sc), "failed to write logrotate file to disk")
return errors.WithMessage(t.Execute(f, sc), "failed to write logrotate file to disk")
}
// Returns the location of the JSON file that tracks server states.
@@ -190,7 +203,7 @@ func (sc *SystemConfiguration) ConfigureTimezone() error {
if sc.Timezone == "" {
if b, err := ioutil.ReadFile("/etc/timezone"); err != nil {
if !os.IsNotExist(err) {
return errors.WrapIf(err, "failed to open /etc/timezone for automatic server timezone calibration")
return errors.WithMessage(err, "failed to open /etc/timezone for automatic server timezone calibration")
}
ctx, _ := context.WithTimeout(context.Background(), time.Second*5)
@@ -224,5 +237,5 @@ func (sc *SystemConfiguration) ConfigureTimezone() error {
_, err := time.LoadLocation(sc.Timezone)
return errors.WrapIf(err, fmt.Sprintf("the supplied timezone %s is invalid", sc.Timezone))
return errors.WithMessage(err, fmt.Sprintf("the supplied timezone %s is invalid", sc.Timezone))
}

View File

@@ -1,17 +1,17 @@
version: '3.5'
version: '3.8'
services:
daemon:
build: .
wings:
image: ghcr.io/pterodactyl/wings:latest
restart: always
networks:
- daemon0
- wings0
ports:
- "8080:8080"
- "2022:2022"
tty: true
environment:
- "DEBUG=false"
- "TZ=UTC" # change to the three letter timezone of your choosing
TZ: "UTC"
DEBUG: "false"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- "/var/lib/docker/containers/:/var/lib/docker/containers/"
@@ -19,17 +19,16 @@ services:
- "/var/lib/pterodactyl/:/var/lib/pterodactyl/"
- "/var/log/pterodactyl/:/var/log/pterodactyl/"
- "/tmp/pterodactyl/:/tmp/pterodactyl/"
## you may need /srv/daemon-data if you are upgrading from an old daemon
## - "/srv/daemon-data/:/srv/daemon-data/"
## Required for ssl if you user let's encrypt. uncomment to use.
## - "/etc/letsencrypt/:/etc/letsencrypt/"
# you may need /srv/daemon-data if you are upgrading from an old daemon
#- "/srv/daemon-data/:/srv/daemon-data/"
# Required for ssl if you user let's encrypt. uncomment to use.
#- "/etc/letsencrypt/:/etc/letsencrypt/"
networks:
daemon0:
name: daemon0
wings0:
name: wings0
driver: bridge
ipam:
config:
- subnet: "172.21.0.0/16"
driver_opts:
com.docker.network.bridge.name: daemon0
com.docker.network.bridge.name: wings0

View File

@@ -38,15 +38,16 @@ func (a *Allocations) Bindings() nat.PortMap {
continue
}
binding := []nat.PortBinding{
{
HostIP: ip,
HostPort: strconv.Itoa(port),
},
binding := nat.PortBinding{
HostIP: ip,
HostPort: strconv.Itoa(port),
}
out[nat.Port(fmt.Sprintf("%d/tcp", port))] = binding
out[nat.Port(fmt.Sprintf("%d/udp", port))] = binding
tcp := nat.Port(fmt.Sprintf("%d/tcp", port))
udp := nat.Port(fmt.Sprintf("%d/udp", port))
out[tcp] = append(out[tcp], binding)
out[udp] = append(out[udp], binding)
}
}

View File

@@ -4,7 +4,6 @@ import (
"bufio"
"bytes"
"context"
"emperror.dev/errors"
"encoding/json"
"fmt"
"github.com/apex/log"
@@ -13,6 +12,7 @@ import (
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/client"
"github.com/docker/docker/daemon/logger/jsonfilelog"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"io"
@@ -36,7 +36,7 @@ func (e *Environment) Attach() error {
}
if err := e.followOutput(); err != nil {
return errors.WithStackIf(err)
return err
}
opts := types.ContainerAttachOptions{
@@ -48,7 +48,7 @@ func (e *Environment) Attach() error {
// Set the stream again with the container.
if st, err := e.client.ContainerAttach(context.Background(), e.Id, opts); err != nil {
return errors.WithStackIf(err)
return err
} else {
e.SetStream(&st)
}
@@ -70,14 +70,19 @@ func (e *Environment) Attach() error {
// indicates that the container is no longer running.
go func(ctx context.Context) {
if err := e.pollResources(ctx); err != nil {
log.WithField("environment_id", e.Id).WithField("error", errors.WithStackIf(err)).Error("error during environment resource polling")
l := log.WithField("environment_id", e.Id)
if !errors.Is(err, context.Canceled) {
l.WithField("error", err).Error("error during environment resource polling")
} else {
l.Warn("stopping server resource polling: context canceled")
}
}
}(ctx)
// Stream the reader output to the console which will then fire off events and handle console
// throttling and sending the output to the user.
if _, err := io.Copy(console, e.stream.Reader); err != nil {
log.WithField("environment_id", e.Id).WithField("error", errors.WithStackIf(err)).Error("error while copying environment output to console")
log.WithField("environment_id", e.Id).WithField("error", err).Error("error while copying environment output to console")
}
}(c)
@@ -115,7 +120,7 @@ func (e *Environment) InSituUpdate() error {
return nil
}
return errors.WithStackIf(err)
return err
}
u := container.UpdateConfig{
@@ -125,7 +130,7 @@ func (e *Environment) InSituUpdate() error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
if _, err := e.client.ContainerUpdate(ctx, e.Id, u); err != nil {
return errors.WithStackIf(err)
return err
}
return nil
@@ -140,12 +145,12 @@ func (e *Environment) Create() error {
if _, err := e.client.ContainerInspect(context.Background(), e.Id); err == nil {
return nil
} else if !client.IsErrNotFound(err) {
return errors.WithStackIf(err)
return err
}
// Try to pull the requested image before creating the container.
if err := e.ensureImageExists(e.meta.Image); err != nil {
return errors.WithStackIf(err)
return err
}
a := e.Configuration.Allocations()
@@ -220,7 +225,7 @@ func (e *Environment) Create() error {
}
if _, err := e.client.ContainerCreate(context.Background(), conf, hostConf, nil, e.Id); err != nil {
return errors.WithStackIf(err)
return err
}
return nil
@@ -272,7 +277,7 @@ func (e *Environment) Destroy() error {
func (e *Environment) followOutput() error {
if exists, err := e.Exists(); !exists {
if err != nil {
return errors.WithStackIf(err)
return err
}
return errors.New(fmt.Sprintf("no such container: %s", e.Id))
@@ -291,9 +296,19 @@ func (e *Environment) followOutput() error {
defer reader.Close()
r := bufio.NewReader(reader)
// Micro-optimization to create these replacements one time when this routine
// fires up, rather than on every line that is executed.
cr := []byte(" \r")
crr := []byte("\r\n")
// Avoid constantly re-allocating memory when we're flooding lines through this
// function by using the same buffer for the duration of the call and just truncating
// the value back to 0 every loop.
var str strings.Builder
ParentLoop:
for {
var b bytes.Buffer
str.Reset()
var line []byte
var isPrefix bool
@@ -305,7 +320,7 @@ func (e *Environment) followOutput() error {
// in line with that it thinks is the terminal size. Those returns break a lot of output handling,
// so we'll just replace them with proper new-lines and then split it later and send each line as
// its own event in the response.
b.Write(bytes.ReplaceAll(line, []byte(" \r"), []byte("\r\n")))
str.Write(bytes.Replace(line, cr, crr, -1))
// Finish this loop and begin outputting the line if there is no prefix (the line fit into
// the default buffer), or if we hit the end of the line.
@@ -322,7 +337,7 @@ func (e *Environment) followOutput() error {
// Publish the line for this loop. Break on new-line characters so every line is sent as a single
// output event, otherwise you get funky handling in the browser console.
for _, line := range strings.Split(b.String(), "\r\n") {
for _, line := range strings.Split(str.String(), "\r\n") {
e.Events().Publish(environment.ConsoleOutputEvent, line)
}
@@ -338,7 +353,7 @@ func (e *Environment) followOutput() error {
}
}(reader)
return errors.WithStackIf(err)
return err
}
// Pulls the image from Docker. If there is an error while pulling the image from the source

View File

@@ -2,7 +2,6 @@ package docker
import (
"context"
"emperror.dev/errors"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/pterodactyl/wings/api"
@@ -156,7 +155,7 @@ func (e *Environment) ExitState() (uint32, bool, error) {
return 1, false, nil
}
return 0, false, errors.WithStackIf(err)
return 0, false, err
}
return uint32(c.State.ExitCode), c.State.OOMKilled, nil

View File

@@ -2,11 +2,11 @@ package docker
import (
"context"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/environment"
"os"
@@ -26,7 +26,7 @@ func (e *Environment) OnBeforeStart() error {
// the Panel is usee.
if err := e.client.ContainerRemove(context.Background(), e.Id, types.ContainerRemoveOptions{RemoveVolumes: true}); err != nil {
if !client.IsErrNotFound(err) {
return errors.WrapIf(err, "failed to remove server docker container during pre-boot")
return errors.WithMessage(err, "failed to remove server docker container during pre-boot")
}
}
@@ -69,7 +69,7 @@ func (e *Environment) Start() error {
//
// @see https://github.com/pterodactyl/panel/issues/2000
if !client.IsErrNotFound(err) {
return errors.WithStackIf(err)
return err
}
} else {
// If the server is running update our internal state and continue on with the attach.
@@ -84,7 +84,7 @@ func (e *Environment) Start() error {
// to truncate them.
if _, err := os.Stat(c.LogPath); err == nil {
if err := os.Truncate(c.LogPath, 0); err != nil {
return errors.WithStackIf(err)
return err
}
}
}
@@ -99,14 +99,14 @@ func (e *Environment) Start() error {
// exists on the system, and rebuild the container if that is required for server booting to
// occur.
if err := e.OnBeforeStart(); err != nil {
return errors.WithStackIf(err)
return err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
if err := e.client.ContainerStart(ctx, e.Id, types.ContainerStartOptions{}); err != nil {
return errors.WithStackIf(err)
return err
}
// No errors, good to continue through.
@@ -169,7 +169,7 @@ func (e *Environment) Stop() error {
// will be terminated forcefully depending on the value of the second argument.
func (e *Environment) WaitForStop(seconds uint, terminate bool) error {
if err := e.Stop(); err != nil {
return errors.WithStackIf(err)
return err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(seconds)*time.Second)
@@ -183,22 +183,27 @@ func (e *Environment) WaitForStop(seconds uint, terminate bool) error {
case <-ctx.Done():
if ctxErr := ctx.Err(); ctxErr != nil {
if terminate {
log.WithField("container_id", e.Id).Debug("server did not stop in time, executing process termination")
log.WithField("container_id", e.Id).Info("server did not stop in time, executing process termination")
return errors.WithStackIf(e.Terminate(os.Kill))
return e.Terminate(os.Kill)
}
return errors.WithStackIf(ctxErr)
return ctxErr
}
case err := <-errChan:
if err != nil {
if terminate {
log.WithField("container_id", e.Id).WithField("error", errors.WithStackIf(err)).Warn("error while waiting for container stop, attempting process termination")
l := log.WithField("container_id", e.Id)
if errors.Is(err, context.DeadlineExceeded) {
l.Warn("deadline exceeded for container stop; terminating process")
} else {
l.WithField("error", err).Warn("error while waiting for container stop; terminating process")
}
return errors.WithStackIf(e.Terminate(os.Kill))
return e.Terminate(os.Kill)
}
return errors.WithStackIf(err)
return err
}
case <-ok:
}
@@ -210,7 +215,7 @@ func (e *Environment) WaitForStop(seconds uint, terminate bool) error {
func (e *Environment) Terminate(signal os.Signal) error {
c, err := e.client.ContainerInspect(context.Background(), e.Id)
if err != nil {
return errors.WithStackIf(err)
return err
}
if !c.State.Running {

View File

@@ -1,8 +1,8 @@
package docker
import (
"emperror.dev/errors"
"fmt"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/environment"
)

View File

@@ -2,10 +2,10 @@ package docker
import (
"context"
"emperror.dev/errors"
"encoding/json"
"github.com/apex/log"
"github.com/docker/docker/api/types"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/environment"
"io"
"math"
@@ -15,18 +15,17 @@ import (
// Attach to the instance and then automatically emit an event whenever the resource usage for the
// server process changes.
func (e *Environment) pollResources(ctx context.Context) error {
l := log.WithField("container_id", e.Id)
l.Debug("starting resource polling for container")
defer l.Debug("stopped resource polling for container")
if e.st.Load() == environment.ProcessOfflineState {
return errors.New("cannot enable resource polling on a stopped server")
}
l := log.WithField("container_id", e.Id)
l.Debug("starting resource polling for container")
defer l.Debug("stopped resource polling for container")
stats, err := e.client.ContainerStats(context.Background(), e.Id, true)
if err != nil {
return errors.WithStackIf(err)
return err
}
defer stats.Body.Close()
@@ -41,7 +40,7 @@ func (e *Environment) pollResources(ctx context.Context) error {
if err := dec.Decode(&v); err != nil {
if err != io.EOF {
l.WithField("error", errors.WithStackIf(err)).Warn("error while processing Docker stats output for container")
l.WithField("error", err).Warn("error while processing Docker stats output for container")
} else {
l.Debug("io.EOF encountered during stats decode, stopping polling...")
}
@@ -76,7 +75,7 @@ func (e *Environment) pollResources(ctx context.Context) error {
}
if b, err := json.Marshal(st); err != nil {
l.WithField("error", errors.WithStackIf(err)).Warn("error while marshaling stats object for environment")
l.WithField("error", err).Warn("error while marshaling stats object for environment")
} else {
e.Events().Publish(environment.ResourceEvent, string(b))
}

View File

@@ -4,9 +4,9 @@ import (
"bufio"
"bytes"
"context"
"emperror.dev/errors"
"encoding/json"
"github.com/docker/docker/api/types"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/environment"
"strconv"
)
@@ -15,7 +15,7 @@ type dockerLogLine struct {
Log string `json:"log"`
}
var ErrNotAttached = errors.Sentinel("not attached to instance")
var ErrNotAttached = errors.New("not attached to instance")
func (e *Environment) setStream(s *types.HijackedResponse) {
e.mu.Lock()
@@ -42,7 +42,7 @@ func (e *Environment) SendCommand(c string) error {
_, err := e.stream.Conn.Write([]byte(c + "\n"))
return errors.WithStackIf(err)
return err
}
// Reads the log file for the server. This does not care if the server is running or not, it will
@@ -54,7 +54,7 @@ func (e *Environment) Readlog(lines int) ([]string, error) {
Tail: strconv.Itoa(lines),
})
if err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
defer r.Close()

View File

@@ -1,7 +1,6 @@
package events
import (
"emperror.dev/errors"
"encoding/json"
"github.com/gammazero/workerpool"
"strings"
@@ -69,7 +68,7 @@ func (e *EventBus) Publish(topic string, data string) {
func (e *EventBus) PublishJson(topic string, data interface{}) error {
b, err := json.Marshal(data)
if err != nil {
return errors.WithStackIf(err)
return err
}
e.Publish(topic, string(b))

3
go.mod
View File

@@ -3,7 +3,6 @@ module github.com/pterodactyl/wings
go 1.13
require (
emperror.dev/errors v0.8.0
github.com/AlecAivazis/survey/v2 v2.1.0
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/Jeffail/gabs/v2 v2.5.1
@@ -18,7 +17,6 @@ require (
github.com/containerd/containerd v1.3.7 // indirect
github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b // indirect
github.com/creasty/defaults v1.5.0
github.com/docker/cli v17.12.1-ce-rc2+incompatible
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible
github.com/docker/go-connections v0.4.0
@@ -58,6 +56,7 @@ require (
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
github.com/pkg/errors v0.9.1
github.com/pkg/profile v1.5.0
github.com/pkg/sftp v1.11.0
github.com/prometheus/common v0.11.1 // indirect

8
go.sum
View File

@@ -1,7 +1,5 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
emperror.dev/errors v0.8.0 h1:4lycVEx0sdJkwDUfQ9pdu6SR0x7rgympt5f4+ok8jDk=
emperror.dev/errors v0.8.0/go.mod h1:YcRvLPh626Ubn2xqtoprejnA5nFha+TJ+2vew48kWuE=
github.com/AlecAivazis/survey/v2 v2.1.0 h1:AT4+23hOFopXYZaNGugbk7MWItkz0SfTmH/Hk92KeeE=
github.com/AlecAivazis/survey/v2 v2.1.0/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
@@ -95,8 +93,6 @@ 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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docker/cli v17.12.1-ce-rc2+incompatible h1:ESUycEAqvFuLglAHkUW66rCc2djYtd3i1x231svLq9o=
github.com/docker/cli v17.12.1-ce-rc2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
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=
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible h1:iWPIG7pWIsCwT6ZtHnTUpoVMnete7O/pzd9HFE3+tn8=
@@ -563,13 +559,9 @@ go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=

View File

@@ -1,10 +1,10 @@
package installer
import (
"emperror.dev/errors"
"encoding/json"
"github.com/asaskevich/govalidator"
"github.com/buger/jsonparser"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/server"
@@ -43,21 +43,21 @@ func New(data []byte) (*Installer, error) {
// Unmarshal the environment variables from the request into the server struct.
if b, _, _, err := jsonparser.Get(data, "environment"); err != nil {
return nil, errors.WithStackIf(err)
return nil, err
} else {
cfg.EnvVars = make(environment.Variables)
if err := json.Unmarshal(b, &cfg.EnvVars); err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
}
// Unmarshal the allocation mappings from the request into the server struct.
if b, _, _, err := jsonparser.Get(data, "allocations", "mappings"); err != nil {
return nil, errors.WithStackIf(err)
return nil, err
} else {
cfg.Allocations.Mappings = make(map[string][]int)
if err := json.Unmarshal(b, &cfg.Allocations.Mappings); err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
}
@@ -66,7 +66,7 @@ func New(data []byte) (*Installer, error) {
c, err := api.New().GetServerConfiguration(cfg.Uuid)
if err != nil {
if !api.IsRequestError(err) {
return nil, errors.WithStackIf(err)
return nil, err
}
return nil, errors.New(err.Error())

View File

@@ -6,16 +6,17 @@ import (
"github.com/apex/log/handlers/cli"
color2 "github.com/fatih/color"
"github.com/mattn/go-colorable"
"emperror.dev/errors"
"github.com/pkg/errors"
"io"
"math"
"os"
"sync"
"time"
)
var Default = New(os.Stderr, true)
var bold = color2.New(color2.Bold)
var boldred = color2.New(color2.Bold, color2.FgRed)
var Strings = [...]string{
log.DebugLevel: "DEBUG",
@@ -60,7 +61,6 @@ func (h *Handler) HandleLog(e *log.Entry) error {
if name == "source" {
continue
}
fmt.Fprintf(h.Writer, " %s=%v", color.Sprint(name), e.Fields.Get(name))
}
@@ -70,45 +70,16 @@ func (h *Handler) HandleLog(e *log.Entry) error {
if name != "error" {
continue
}
var br = color2.New(color2.Bold, color2.FgRed)
if err, ok := e.Fields.Get("error").(error); ok {
fmt.Fprintf(h.Writer, "\n%s%+v\n\n", br.Sprintf("Stacktrace:"), getErrorStack(err, false))
} else {
fmt.Fprintf(h.Writer, "\n%s%+v\n\n", br.Sprintf("Invalid Error:"), err)
if e, ok := errors.Cause(err).(tracer); ok {
st := e.StackTrace()
l := math.Min(float64(len(st)), 10)
fmt.Fprintf(h.Writer, "\n%s%+v\n\n", boldred.Sprintf("Stacktrace:"), st[0:int(l)])
} else {
fmt.Fprintf(h.Writer, "\n%s\n%+v\n\n", boldred.Sprintf("Stacktrace:"), err)
}
}
}
return nil
}
func getErrorStack(err error, i bool) errors.StackTrace {
e, ok := err.(tracer)
if !ok {
if i {
// Just abort out of this and return a stacktrace leading up to this point. It isn't perfect
// but it'll at least include what function lead to this being called which we can then handle.
return errors.WrapIf(err, "failed to generate stacktrace for caught error").(tracer).StackTrace()
}
return getErrorStack(errors.WrapIf(err, err.Error()), true)
}
st := e.StackTrace()
l := len(st)
// If this was an internal stack generation we're going to skip over the top four items in the stack
// trace since they'll point to the error that was generated by this function.
f := 0
if i {
f = 5
}
if i && l > 9 {
l = 9
} else if !i && l > 5 {
l = 5
}
return st[f:l]
}

View File

View File

@@ -2,11 +2,11 @@ package parser
import (
"bytes"
"emperror.dev/errors"
"github.com/Jeffail/gabs/v2"
"github.com/apex/log"
"github.com/buger/jsonparser"
"github.com/iancoleman/strcase"
"github.com/pkg/errors"
"io/ioutil"
"os"
"regexp"
@@ -76,13 +76,13 @@ func (cfr *ConfigurationFileReplacement) getKeyValue(value []byte) interface{} {
func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error) {
parsed, err := gabs.ParseJSON(data)
if err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
for _, v := range f.Replace {
value, err := f.LookupConfigurationValue(v)
if err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
// Check for a wildcard character, and if found split the key on that value to
@@ -101,7 +101,7 @@ func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error
continue
}
return nil, errors.WrapIf(err, "failed to set config value of array child")
return nil, errors.WithMessage(err, "failed to set config value of array child")
}
}
} else {
@@ -110,7 +110,7 @@ func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error
continue
}
return nil, errors.WrapIf(err, "unable to set config value at pathway: "+v.Match)
return nil, errors.WithMessage(err, "unable to set config value at pathway: "+v.Match)
}
}
}
@@ -138,7 +138,7 @@ func setValueAtPath(c *gabs.Container, path string, value interface{}) error {
_, err = c.SetP(value, path)
}
return errors.WithStackIf(err)
return err
}
i, _ := strconv.Atoi(matches[2])
@@ -147,7 +147,7 @@ func setValueAtPath(c *gabs.Container, path string, value interface{}) error {
ct, err := c.ArrayElementP(i, matches[1])
if err != nil {
if i != 0 || (!errors.Is(err, gabs.ErrNotArray) && !errors.Is(err, gabs.ErrNotFound)) {
return errors.WrapIf(err, "error while parsing array element at path")
return errors.WithMessage(err, "error while parsing array element at path")
}
var t = make([]interface{}, 1)
@@ -162,7 +162,7 @@ func setValueAtPath(c *gabs.Container, path string, value interface{}) error {
// an empty object if we have additional things to set on the array, or just an empty array type
// if there is not an object structure detected (no matches[3] available).
if _, err = c.SetP(t, matches[1]); err != nil {
return errors.WrapIf(err, "failed to create empty array for missing element")
return errors.WithMessage(err, "failed to create empty array for missing element")
}
// Set our cursor to be the array element we expect, which in this case is just the first element
@@ -170,7 +170,7 @@ func setValueAtPath(c *gabs.Container, path string, value interface{}) error {
// to match additional elements. In those cases the server will just have to be rebooted or something.
ct, err = c.ArrayElementP(0, matches[1])
if err != nil {
return errors.WrapIf(err, "failed to find array element at path")
return errors.WithMessage(err, "failed to find array element at path")
}
}
@@ -187,7 +187,7 @@ func setValueAtPath(c *gabs.Container, path string, value interface{}) error {
}
if err != nil {
return errors.WrapIf(err, "failed to set value at config path: "+path)
return errors.WithMessage(err, "failed to set value at config path: "+path)
}
return nil
@@ -253,7 +253,7 @@ func (f *ConfigurationFile) LookupConfigurationValue(cfr ConfigurationFileReplac
match, _, _, err := jsonparser.Get(f.configuration, path...)
if err != nil {
if err != jsonparser.KeyPathNotFoundError {
return string(match), errors.WithStackIf(err)
return string(match), err
}
log.WithFields(log.Fields{"path": path, "filename": f.FileName}).Debug("attempted to load a configuration value that does not exist")

View File

@@ -2,13 +2,13 @@ package parser
import (
"bufio"
"emperror.dev/errors"
"encoding/json"
"github.com/apex/log"
"github.com/beevik/etree"
"github.com/buger/jsonparser"
"github.com/icza/dyno"
"github.com/magiconair/properties"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/config"
"gopkg.in/ini.v1"
"gopkg.in/yaml.v2"
@@ -166,17 +166,17 @@ func (f *ConfigurationFile) Parse(path string, internal bool) error {
b := strings.TrimSuffix(path, filepath.Base(path))
if err := os.MkdirAll(b, 0755); err != nil {
return errors.WrapIf(err, "failed to create base directory for missing configuration file")
return errors.WithMessage(err, "failed to create base directory for missing configuration file")
} else {
if _, err := os.Create(path); err != nil {
return errors.WrapIf(err, "failed to create missing configuration file")
return errors.WithMessage(err, "failed to create missing configuration file")
}
}
return f.Parse(path, true)
}
return errors.WithStackIf(err)
return err
}
// Parses an xml file.
@@ -348,12 +348,12 @@ func (f *ConfigurationFile) parseJsonFile(path string) error {
func (f *ConfigurationFile) parseYamlFile(path string) error {
b, err := readFileBytes(path)
if err != nil {
return errors.WithStackIf(err)
return err
}
i := make(map[string]interface{})
if err := yaml.Unmarshal(b, &i); err != nil {
return errors.WithStackIf(err)
return err
}
// Unmarshal the yaml data into a JSON interface such that we can work with
@@ -361,20 +361,20 @@ func (f *ConfigurationFile) parseYamlFile(path string) error {
// makes working with unknown JSON significantly easier.
jsonBytes, err := json.Marshal(dyno.ConvertMapI2MapS(i))
if err != nil {
return errors.WithStackIf(err)
return err
}
// Now that the data is converted, treat it just like JSON and pass it to the
// iterator function to update values as necessary.
data, err := f.IterateOverJson(jsonBytes)
if err != nil {
return errors.WithStackIf(err)
return err
}
// Remarshal the JSON into YAML format before saving it back to the disk.
marshaled, err := yaml.Marshal(data.Data())
if err != nil {
return errors.WithStackIf(err)
return err
}
return ioutil.WriteFile(path, marshaled, 0644)
@@ -386,7 +386,7 @@ func (f *ConfigurationFile) parseYamlFile(path string) error {
func (f *ConfigurationFile) parseTextFile(path string) error {
input, err := ioutil.ReadFile(path)
if err != nil {
return errors.WithStackIf(err)
return err
}
lines := strings.Split(string(input), "\n")
@@ -403,7 +403,7 @@ func (f *ConfigurationFile) parseTextFile(path string) error {
}
if err := ioutil.WriteFile(path, []byte(strings.Join(lines, "\n")), 0644); err != nil {
return errors.WithStackIf(err)
return err
}
return nil
@@ -415,7 +415,7 @@ func (f *ConfigurationFile) parsePropertiesFile(path string) error {
// Open the file.
f2, err := os.Open(path)
if err != nil {
return errors.WithStackIf(err)
return err
}
var s strings.Builder
@@ -437,20 +437,20 @@ func (f *ConfigurationFile) parsePropertiesFile(path string) error {
// Handle any scanner errors.
if err := scanner.Err(); err != nil {
return errors.WithStackIf(err)
return err
}
// Decode the properties file.
p, err := properties.LoadFile(path, properties.UTF8)
if err != nil {
return errors.WithStackIf(err)
return err
}
// Replace any values that need to be replaced.
for _, replace := range f.Replace {
data, err := f.LookupConfigurationValue(replace)
if err != nil {
return errors.WithStackIf(err)
return err
}
v, ok := p.Get(replace.Match)
@@ -462,7 +462,7 @@ func (f *ConfigurationFile) parsePropertiesFile(path string) error {
}
if _, _, err := p.Set(replace.Match, data); err != nil {
return errors.WithStackIf(err)
return err
}
}
@@ -482,7 +482,7 @@ func (f *ConfigurationFile) parsePropertiesFile(path string) error {
// Open the file for writing.
w, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return errors.WithStackIf(err)
return err
}
defer w.Close()

View File

@@ -1,11 +1,11 @@
package router
import (
"emperror.dev/errors"
"fmt"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/server/filesystem"
"net/http"
@@ -75,7 +75,7 @@ func (e *RequestError) AbortWithStatus(status int, c *gin.Context) {
if status >= 500 {
e.logger().WithField("error", e.Err).Error("encountered HTTP/500 error while handling request")
c.Error(errors.WithStackIf(e))
c.Error(e)
} else {
e.logger().WithField("error", e.Err).Debug("encountered non-HTTP/500 error while handling request")
}

View File

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

View File

@@ -3,9 +3,9 @@ package router
import (
"bytes"
"context"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server"
"net/http"
@@ -227,7 +227,7 @@ func deleteServer(c *gin.Context) {
if err := os.RemoveAll(p); err != nil {
log.WithFields(log.Fields{
"path": p,
"error": errors.WithStackIf(err),
"error": err,
}).Warn("failed to remove server files during deletion process")
}
}(s.Filesystem().Path())
@@ -247,7 +247,9 @@ func deleteServer(c *gin.Context) {
// 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) {
var data struct{ JTIs []string `json:"jtis"` }
var data struct {
JTIs []string `json:"jtis"`
}
if err := c.BindJSON(&data); err != nil {
return

View File

@@ -2,8 +2,9 @@ package router
import (
"context"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/server/filesystem"
@@ -35,19 +36,28 @@ func getServerFileContents(c *gin.Context) {
return
}
c.Header("X-Mime-Type", st.Mimetype)
c.Header("Content-Length", strconv.Itoa(int(st.Info.Size())))
// If a download parameter is included in the URL go ahead and attach the necessary headers
// so that the file can be downloaded.
if c.Query("download") != "" {
c.Header("Content-Disposition", "attachment; filename="+st.Info.Name())
c.Header("Content-Type", "application/octet-stream")
}
// TODO(dane): should probably come up with a different approach here. If an error is encountered
// by this Readfile call you'll end up causing a (recovered) panic in the program because so many
// headers have already been set. We should probably add a RawReadfile that just returns the file
// to be read and then we can stream from that safely without error.
//
// Until that becomes a problem though I'm just going to leave this how it is. The panic is recovered
// and a normal 500 error is returned to the client to my knowledge. It is also very unlikely to
// happen since we're doing so much before this point that would normally throw an error if there
// was a problem with the file.
if err := s.Filesystem().Readfile(p, c.Writer); err != nil {
TrackedServerError(err, s).AbortFilesystemError(c)
return
} else {
c.Header("X-Mime-Type", st.Mimetype)
c.Header("Content-Length", strconv.Itoa(int(st.Info.Size())))
// If a download parameter is included in the URL go ahead and attach the necessary headers
// so that the file can be downloaded.
if c.Query("download") != "" {
c.Header("Content-Disposition", "attachment; filename="+st.Info.Name())
c.Header("Content-Type", "application/octet-stream")
}
}
}
@@ -356,6 +366,68 @@ func postServerDecompressFiles(c *gin.Context) {
c.Status(http.StatusNoContent)
}
type chmodFile struct {
File string `json:"file"`
Mode string `json:"mode"`
}
func postServerChmodFile(c *gin.Context) {
s := GetServer(c.Param("server"))
var data struct {
Root string `json:"root"`
Files []chmodFile `json:"files"`
}
if err := c.BindJSON(&data); err != nil {
log.Debug(err.Error())
return
}
if len(data.Files) == 0 {
c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
"error": "No files to chmod were provided.",
})
return
}
g, ctx := errgroup.WithContext(context.Background())
// Loop over the array of files passed in and perform the move or rename action against each.
for _, p := range data.Files {
g.Go(func() error {
select {
case <-ctx.Done():
return ctx.Err()
default:
mode, err := strconv.ParseUint(p.Mode, 8, 32)
if err != nil {
return err
}
if err := s.Filesystem().Chmod(path.Join(data.Root, p.File), os.FileMode(mode)); err != nil {
// Return nil if the error is an is not exists.
// NOTE: os.IsNotExist() does not work if the error is wrapped.
if errors.Is(err, os.ErrNotExist) {
return nil
}
return err
}
return nil
}
})
}
if err := g.Wait(); err != nil {
TrackedServerError(err, s).AbortFilesystemError(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 {
@@ -413,12 +485,12 @@ func postServerUploadFiles(c *gin.Context) {
func handleFileUpload(p string, s *server.Server, header *multipart.FileHeader) error {
file, err := header.Open()
if err != nil {
return errors.WithStackIf(err)
return err
}
defer file.Close()
if err := s.Filesystem().Writefile(p, file); err != nil {
return errors.WithStackIf(err)
return err
}
return nil

View File

@@ -89,8 +89,8 @@ func postUpdateConfiguration(c *gin.Context) {
//
// If you pass through manual locations in the API call this logic will be skipped.
if strings.HasPrefix(cfg.Api.Ssl.KeyFile, "/etc/letsencrypt/live/") {
cfg.Api.Ssl.KeyFile = ccopy.Api.Ssl.KeyFile
cfg.Api.Ssl.CertificateFile = ccopy.Api.Ssl.CertificateFile
cfg.Api.Ssl.KeyFile = strings.ToLower(ccopy.Api.Ssl.KeyFile)
cfg.Api.Ssl.CertificateFile = strings.ToLower(ccopy.Api.Ssl.CertificateFile)
}
config.Set(&cfg)

View File

@@ -4,12 +4,12 @@ import (
"bufio"
"bytes"
"crypto/sha256"
"emperror.dev/errors"
"encoding/hex"
"github.com/apex/log"
"github.com/buger/jsonparser"
"github.com/gin-gonic/gin"
"github.com/mholt/archiver/v3"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/installer"
@@ -94,27 +94,40 @@ func postServerArchive(c *gin.Context) {
s := GetServer(c.Param("server"))
go func(s *server.Server) {
r := api.New()
// Attempt to get an archive of the server. This **WILL NOT** modify the source files of a server,
// this process is 100% safe and will not corrupt a server's files if it fails.
if err := s.Archiver.Archive(); err != nil {
s.Log().WithField("error", err).Error("failed to get archive for server")
if err := r.SendArchiveStatus(s.Id(), false); err != nil {
if !api.IsRequestError(err) {
s.Log().WithField("error", err).Error("failed to notify panel of failed archive status")
return
}
s.Log().WithField("error", err.Error()).Error("panel returned an error when notifying it of a failed archive status")
return
}
s.Log().Info("successfully notified panel of failed archive status")
return
}
s.Log().Debug("successfully created server archive, notifying panel")
r := api.New()
err := r.SendArchiveStatus(s.Id(), true)
if err != nil {
if err := r.SendArchiveStatus(s.Id(), true); err != nil {
if !api.IsRequestError(err) {
s.Log().WithField("error", err).Error("failed to notify panel of archive status")
s.Log().WithField("error", err).Error("failed to notify panel of successful archive status")
return
}
s.Log().WithField("error", err.Error()).Error("panel returned an error when sending the archive status")
s.Log().WithField("error", err.Error()).Error("panel returned an error when notifying it of a successful archive status")
return
}
s.Log().Debug("successfully notified panel of archive status")
s.Log().Info("successfully notified panel of successful archive status")
}(s)
c.Status(http.StatusAccepted)
@@ -140,8 +153,7 @@ func postTransfer(c *gin.Context) {
}
l.Info("server transfer failed, notifying panel")
err := api.New().SendTransferFailure(serverID)
if err != nil {
if err := api.New().SendTransferFailure(serverID); err != nil {
if !api.IsRequestError(err) {
l.WithField("error", err).Error("failed to notify panel with transfer failure")
return
@@ -157,7 +169,7 @@ func postTransfer(c *gin.Context) {
// Make a new GET request to the URL the panel gave us.
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.WithField("error", errors.WithStackIf(err)).Error("failed to create http request for archive transfer")
log.WithField("error", err).Error("failed to create http request for archive transfer")
return
}
@@ -167,7 +179,7 @@ func postTransfer(c *gin.Context) {
// Execute the http request.
res, err := client.Do(req)
if err != nil {
l.WithField("error", errors.WithStackIf(err)).Error("failed to send archive http request")
l.WithField("error", err).Error("failed to send archive http request")
return
}
defer res.Body.Close()
@@ -176,13 +188,11 @@ func postTransfer(c *gin.Context) {
if res.StatusCode != 200 {
_, err := ioutil.ReadAll(res.Body)
if err != nil {
l.WithField("error", errors.WithStackIf(err)).WithField("status", res.StatusCode).Error("failed read transfer response body")
l.WithField("error", err).WithField("status", res.StatusCode).Error("failed read transfer response body")
return
}
l.WithField("error", errors.WithStackIf(err)).WithField("status", res.StatusCode).Error("failed to request server archive")
l.WithField("error", err).WithField("status", res.StatusCode).Error("failed to request server archive")
return
}
@@ -190,16 +200,14 @@ func postTransfer(c *gin.Context) {
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 _, err = os.Stat(archivePath); err != nil {
if !os.IsNotExist(err) {
l.WithField("error", errors.WithStackIf(err)).Error("failed to stat archive file")
l.WithField("error", err).Error("failed to stat archive file")
return
}
} else {
if err := os.Remove(archivePath); err != nil {
l.WithField("error", errors.WithStackIf(err)).Warn("failed to remove old archive file")
l.WithField("error", err).Warn("failed to remove old archive file")
return
}
}
@@ -207,8 +215,7 @@ func postTransfer(c *gin.Context) {
// Create the file.
file, err := os.Create(archivePath)
if err != nil {
l.WithField("error", errors.WithStackIf(err)).Error("failed to open archive on disk")
l.WithField("error", err).Error("failed to open archive on disk")
return
}
@@ -216,24 +223,35 @@ func postTransfer(c *gin.Context) {
buf := make([]byte, 1024*4)
_, err = io.CopyBuffer(file, res.Body, buf)
if err != nil {
l.WithField("error", errors.WithStackIf(err)).Error("failed to copy archive file to disk")
l.WithField("error", err).Error("failed to copy archive file to disk")
return
}
// Close the file so it can be opened to verify the checksum.
if err := file.Close(); err != nil {
l.WithField("error", errors.WithStackIf(err)).Error("failed to close archive file")
l.WithField("error", err).Error("failed to close archive file")
return
}
l.WithField("server", serverID).Debug("server archive downloaded, computing checksum...")
// Whenever the transfer fails or succeeds, delete the temporary transfer archive.
defer func() {
log.WithField("server", serverID).Debug("deleting temporary transfer archive..")
if err := os.Remove(archivePath); err != nil && !os.IsNotExist(err) {
l.WithFields(log.Fields{
"server": serverID,
"error": err,
}).Warn("failed to delete transfer archive")
} else {
l.Debug("deleted temporary transfer archive successfully")
}
}()
l.Debug("server archive downloaded, computing checksum...")
// Open the archive file for computing a checksum.
file, err = os.Open(archivePath)
if err != nil {
l.WithField("error", errors.WithStackIf(err)).Error("failed to open archive on disk")
l.WithField("error", err).Error("failed to open archive on disk")
return
}
@@ -241,7 +259,7 @@ func postTransfer(c *gin.Context) {
hash := sha256.New()
buf = make([]byte, 1024*4)
if _, err := io.CopyBuffer(hash, file, buf); err != nil {
l.WithField("error", errors.WithStackIf(err)).Error("failed to copy archive file for checksum verification")
l.WithField("error", err).Error("failed to copy archive file for checksum verification")
return
}
@@ -253,7 +271,7 @@ func postTransfer(c *gin.Context) {
// Close the file.
if err := file.Close(); err != nil {
l.WithField("error", errors.WithStackIf(err)).Error("failed to close archive file after calculating checksum")
l.WithField("error", err).Error("failed to close archive file after calculating checksum")
return
}
@@ -269,7 +287,7 @@ func postTransfer(c *gin.Context) {
// Create a new server installer (note this does not execute the install script)
i, err := installer.New(serverData)
if err != nil {
l.WithField("error", errors.WithStackIf(err)).Error("failed to validate received server data")
l.WithField("error", err).Error("failed to validate received server data")
return
}
@@ -282,9 +300,9 @@ func postTransfer(c *gin.Context) {
return
}
// Un-archive the archive. That sounds weird..
// Un-archive the archive, that sounds weird..
if err := archiver.NewTarGz().Unarchive(archivePath, i.Server().Filesystem().Path()); err != nil {
l.WithField("error", errors.WithStackIf(err)).Error("failed to extract server archive")
l.WithField("error", err).Error("failed to extract server archive")
return
}
@@ -299,12 +317,11 @@ func postTransfer(c *gin.Context) {
err = api.New().SendTransferSuccess(serverID)
if err != nil {
if !api.IsRequestError(err) {
l.WithField("error", errors.WithStackIf(err)).Error("failed to notify panel of transfer success")
l.WithField("error", err).Error("failed to notify panel of transfer success")
return
}
l.WithField("error", err.Error()).Error("panel responded with error after transfer success")
return
}

View File

@@ -2,13 +2,13 @@ package websocket
import (
"context"
"emperror.dev/errors"
"encoding/json"
"fmt"
"github.com/apex/log"
"github.com/gbrlsnchs/jwt/v3"
"github.com/google/uuid"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/environment/docker"
@@ -32,19 +32,18 @@ const (
)
type Handler struct {
sync.RWMutex
Connection *websocket.Conn
jwt *tokens.WebsocketPayload `json:"-"`
sync.RWMutex `json:"-"`
Connection *websocket.Conn `json:"-"`
jwt *tokens.WebsocketPayload
server *server.Server
uuid uuid.UUID
}
var (
ErrJwtNotPresent = errors.Sentinel("jwt: no jwt present")
ErrJwtNoConnectPerm = errors.Sentinel("jwt: missing connect permission")
ErrJwtUuidMismatch = errors.Sentinel("jwt: server uuid mismatch")
ErrJwtOnDenylist = errors.Sentinel("jwt: created too far in past (denylist)")
ErrJwtNotPresent = errors.New("jwt: no jwt present")
ErrJwtNoConnectPerm = errors.New("jwt: missing connect permission")
ErrJwtUuidMismatch = errors.New("jwt: server uuid mismatch")
ErrJwtOnDenylist = errors.New("jwt: created too far in past (denylist)")
)
func IsJwtError(err error) bool {
@@ -108,7 +107,7 @@ func GetHandler(s *server.Server, w http.ResponseWriter, r *http.Request) (*Hand
u, err := uuid.NewRandom()
if err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
return &Handler{
@@ -130,7 +129,6 @@ func (h *Handler) SendJson(v *Message) error {
Event: JwtErrorEvent,
Args: []string{err.Error()},
})
return nil
}
@@ -219,8 +217,11 @@ func (h *Handler) SendErrorJson(msg Message, err error, shouldLog ...bool) error
Event: ErrorEvent,
Args: []string{"an unexpected error was encountered while handling this request"},
}
if isJWTError || (j != nil && j.HasPermission(PermissionReceiveErrors)) {
wsm.Event = JwtErrorEvent
if isJWTError {
wsm.Event = JwtErrorEvent
}
wsm.Args = []string{err.Error()}
}
@@ -229,7 +230,7 @@ func (h *Handler) SendErrorJson(msg Message, err error, shouldLog ...bool) error
if !isJWTError && (len(shouldLog) == 0 || (len(shouldLog) == 1 && shouldLog[0] == true)) {
h.server.Log().WithFields(log.Fields{"event": msg.Event, "error_identifier": u.String(), "error": err}).
Error("failed to handle websocket process; an error was encountered processing an event")
Errorf("error processing websocket event \"%s\"", msg.Event)
}
return h.unsafeSendJson(wsm)
@@ -267,7 +268,6 @@ func (h *Handler) HandleInbound(m Message) error {
Event: JwtErrorEvent,
Args: []string{err.Error()},
})
return nil
}
}
@@ -311,7 +311,7 @@ func (h *Handler) HandleInbound(m Message) error {
// On every authentication event, send the current server status back
// to the client. :)
state := h.server.GetState()
state := h.server.Environment.State()
h.SendJson(&Message{
Event: server.StatusEvent,
Args: []string{state},
@@ -398,7 +398,7 @@ func (h *Handler) HandleInbound(m Message) error {
return nil
}
if h.server.GetState() == environment.ProcessOfflineState {
if h.server.Environment.State() == environment.ProcessOfflineState {
return nil
}
@@ -406,7 +406,7 @@ func (h *Handler) HandleInbound(m Message) error {
// so that we can better handle this and only set the environment to booted once we're attached.
//
// Or maybe just an IsBooted function?
if h.server.GetState() == environment.ProcessStartingState {
if h.server.Environment.State() == environment.ProcessStartingState {
if e, ok := h.server.Environment.(*docker.Environment); ok {
if !e.IsAttached() {
return nil

View File

@@ -2,9 +2,9 @@ package server
import (
"crypto/sha256"
"emperror.dev/errors"
"encoding/hex"
"github.com/mholt/archiver/v3"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/server/filesystem"
"io"
@@ -41,7 +41,7 @@ func (a *Archiver) Exists() bool {
func (a *Archiver) Stat() (*filesystem.Stat, error) {
s, err := os.Stat(a.Path())
if err != nil {
return nil, errors.WithStack(err)
return nil, err
}
return &filesystem.Stat{
@@ -58,7 +58,7 @@ func (a *Archiver) Archive() error {
var files []string
fileInfo, err := ioutil.ReadDir(path)
if err != nil {
return errors.WithStack(err)
return err
}
for _, file := range fileInfo {
@@ -94,17 +94,17 @@ func (a *Archiver) DeleteIfExists() error {
return nil
}
return errors.WithStackIf(err)
return err
}
return errors.WrapIf(os.Remove(a.Path()), "archiver: failed to delete archive from system")
return errors.WithMessage(os.Remove(a.Path()), "archiver: failed to delete archive from system")
}
// Checksum computes a SHA256 checksum of the server's archive.
func (a *Archiver) Checksum() (string, error) {
file, err := os.Open(a.Path())
if err != nil {
return "", errors.WithStack(err)
return "", err
}
defer file.Close()
@@ -112,7 +112,7 @@ func (a *Archiver) Checksum() (string, error) {
buf := make([]byte, 1024*4)
if _, err := io.CopyBuffer(hash, file, buf); err != nil {
return "", errors.WithStack(err)
return "", err
}
return hex.EncodeToString(hash.Sum(nil)), nil

View File

@@ -2,8 +2,8 @@ package server
import (
"bufio"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/server/backup"
"os"
@@ -13,15 +13,14 @@ import (
// Notifies the panel of a backup's state and returns an error if one is encountered
// while performing this action.
func (s *Server) notifyPanelOfBackup(uuid string, ad *backup.ArchiveDetails, successful bool) error {
err := api.New().SendBackupStatus(uuid, ad.ToRequest(successful))
if err != nil {
if err := api.New().SendBackupStatus(uuid, ad.ToRequest(successful)); err != nil {
if !api.IsRequestError(err) {
s.Log().WithFields(log.Fields{
"backup": uuid,
"error": err,
}).Error("failed to notify panel of backup status due to wings error")
return errors.WithStackIf(err)
return err
}
return errors.New(err.Error())
@@ -37,7 +36,7 @@ func (s *Server) getServerwideIgnoredFiles() ([]string, error) {
f, err := os.Open(path.Join(s.Filesystem().Path(), ".pteroignore"))
if err != nil {
if !os.IsNotExist(err) {
return nil, errors.WithStack(err)
return nil, err
}
} else {
scanner := bufio.NewScanner(f)
@@ -49,7 +48,7 @@ func (s *Server) getServerwideIgnoredFiles() ([]string, error) {
}
if err := scanner.Err(); err != nil {
return nil, errors.WithStack(err)
return nil, err
}
}
@@ -79,7 +78,7 @@ func (s *Server) Backup(b backup.BackupInterface) error {
// Get the included files based on the root path and the ignored files provided.
inc, err := s.GetIncludedBackupFiles(b.Ignored())
if err != nil {
return errors.WithStackIf(err)
return err
}
ad, err := b.Generate(inc, s.Filesystem().Path())
@@ -89,6 +88,11 @@ func (s *Server) Backup(b backup.BackupInterface) error {
"backup": b.Identifier(),
"error": notifyError,
}).Warn("failed to notify panel of failed backup state")
} else {
s.Log().WithFields(log.Fields{
"backup": b.Identifier(),
"error": err,
}).Info("notified panel of failed backup state")
}
s.Events().PublishJson(BackupCompletedEvent+":"+b.Identifier(), map[string]interface{}{
@@ -99,15 +103,17 @@ func (s *Server) Backup(b backup.BackupInterface) error {
"file_size": 0,
})
return errors.WrapIf(err, "backup: error while generating server backup")
return errors.WithMessage(err, "backup: error while generating server backup")
}
// Try to notify the panel about the status of this backup. If for some reason this request
// fails, delete the archive from the daemon and return that error up the chain to the caller.
if notifyError := s.notifyPanelOfBackup(b.Identifier(), ad, true); notifyError != nil {
b.Remove()
return errors.WithStackIf(err)
s.Log().WithField("error", notifyError).Info("failed to notify panel of successful backup state")
return err
} else {
s.Log().WithField("backup", b.Identifier()).Info("notified panel of successful backup state")
}
// Emit an event over the socket so we can update the backup in realtime on

View File

@@ -3,9 +3,9 @@ package backup
import (
"archive/tar"
"context"
"emperror.dev/errors"
"github.com/apex/log"
gzip "github.com/klauspost/pgzip"
"github.com/pkg/errors"
"github.com/remeh/sizedwaitgroup"
"golang.org/x/sync/errgroup"
"io"
@@ -26,7 +26,7 @@ type Archive struct {
func (a *Archive) Create(dst string, ctx context.Context) error {
f, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return errors.WithStackIf(err)
return err
}
defer f.Close()
@@ -58,7 +58,7 @@ func (a *Archive) Create(dst string, ctx context.Context) error {
select {
case <-ctx.Done():
return errors.WithStackIf(ctx.Err())
return ctx.Err()
default:
return a.addToArchive(p, tw)
}
@@ -75,7 +75,7 @@ func (a *Archive) Create(dst string, ctx context.Context) error {
log.WithField("location", dst).Warn("failed to delete corrupted backup archive")
}
return errors.WithStackIf(err)
return err
}
return nil
@@ -91,7 +91,7 @@ func (a *Archive) addToArchive(p string, w *tar.Writer) error {
return nil
}
return errors.WithStackIf(err)
return err
}
defer f.Close()
@@ -102,17 +102,15 @@ func (a *Archive) addToArchive(p string, w *tar.Writer) error {
return nil
}
return errors.WithStackIf(err)
return err
}
header := &tar.Header{
// Trim the long server path from the name of the file so that the resulting
// archive is exactly how the user would see it in the panel file manager.
Name: strings.TrimPrefix(p, a.TrimPrefix),
Size: s.Size(),
Mode: int64(s.Mode()),
ModTime: s.ModTime(),
name := strings.TrimPrefix(p, a.TrimPrefix)
header, err := tar.FileInfoHeader(s, name)
if err != nil {
return errors.WithMessage(err, "failed to get tar#FileInfoHeader for "+name)
}
header.Name = name
// These actions must occur sequentially, even if this function is called multiple
// in parallel. You'll get some nasty panic's otherwise.
@@ -120,12 +118,12 @@ func (a *Archive) addToArchive(p string, w *tar.Writer) error {
defer a.Unlock()
if err := w.WriteHeader(header); err != nil {
return errors.WithStackIf(err)
return err
}
buf := make([]byte, 4*1024)
if _, err := io.CopyBuffer(w, f, buf); err != nil {
return errors.WithStackIf(err)
if _, err := io.CopyBuffer(w, io.LimitReader(f, header.Size), buf); err != nil {
return errors.WithMessage(err, "failed to copy "+header.Name+" to archive")
}
return nil

View File

@@ -2,7 +2,6 @@ package backup
import (
"crypto/sha1"
"emperror.dev/errors"
"encoding/hex"
"github.com/apex/log"
"github.com/pterodactyl/wings/api"
@@ -87,7 +86,7 @@ func (b *Backup) Path() string {
func (b *Backup) Size() (int64, error) {
st, err := os.Stat(b.Path())
if err != nil {
return 0, errors.WithStackIf(err)
return 0, err
}
return st.Size(), nil
@@ -99,7 +98,7 @@ func (b *Backup) Checksum() ([]byte, error) {
f, err := os.Open(b.Path())
if err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
defer f.Close()
@@ -128,6 +127,7 @@ func (b *Backup) Details() *ArchiveDetails {
"backup": b.Identifier(),
"error": err,
}).Error("failed to calculate checksum for backup")
return
}
checksum = hex.EncodeToString(resp)

View File

@@ -2,7 +2,7 @@ package backup
import (
"context"
"emperror.dev/errors"
"errors"
"os"
)
@@ -24,7 +24,7 @@ func LocateLocal(uuid string) (*LocalBackup, os.FileInfo, error) {
st, err := os.Stat(b.Path())
if err != nil {
return nil, nil, errors.WithStackIf(err)
return nil, nil, err
}
if st.IsDir() {
@@ -48,7 +48,7 @@ func (b *LocalBackup) Generate(included *IncludedFiles, prefix string) (*Archive
}
if err := a.Create(b.Path(), context.Background()); err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
return b.Details(), nil

View File

@@ -1,7 +1,7 @@
package backup
import (
"emperror.dev/errors"
"errors"
"fmt"
)

View File

@@ -1,9 +1,7 @@
package backup
import (
"bytes"
"context"
"emperror.dev/errors"
"fmt"
"github.com/apex/log"
"github.com/pterodactyl/wings/api"
@@ -11,7 +9,6 @@ import (
"net/http"
"os"
"strconv"
"time"
)
type S3Backup struct {
@@ -31,20 +28,20 @@ func (s *S3Backup) Generate(included *IncludedFiles, prefix string) (*ArchiveDet
}
if err := a.Create(s.Path(), context.Background()); err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
rc, err := os.Open(s.Path())
if err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
defer rc.Close()
if err := s.generateRemoteRequest(rc); err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
return s.Details(), err
return s.Details(), nil
}
// Removes a backup from the system.
@@ -76,10 +73,12 @@ func (s *S3Backup) generateRemoteRequest(rc io.ReadCloser) error {
return err
}
log.WithFields(log.Fields{
l := log.WithFields(log.Fields{
"backup_id": s.Uuid,
"adapter": "s3",
}).Info("attempting to upload backup..")
})
l.Info("attempting to upload backup..")
handlePart := func(part string, size int64) (string, error) {
r, err := http.NewRequest(http.MethodPut, part, nil)
@@ -92,7 +91,7 @@ func (s *S3Backup) generateRemoteRequest(rc io.ReadCloser) error {
r.Header.Add("Content-Type", "application/x-gzip")
// Limit the reader to the size of the part.
r.Body = Reader{io.LimitReader(rc, size)}
r.Body = Reader{Reader: io.LimitReader(rc, size)}
// This http request can block forever due to it not having a timeout,
// but we are uploading up to 5GB of data, so there is not really
@@ -112,10 +111,6 @@ func (s *S3Backup) generateRemoteRequest(rc io.ReadCloser) error {
return res.Header.Get("ETag"), nil
}
// Start assembling the body that will be sent as apart of the CompleteMultipartUpload request.
var completeUploadBody bytes.Buffer
completeUploadBody.WriteString("<CompleteMultipartUpload>\n")
partCount := len(urls.Parts)
for i, part := range urls.Parts {
// Get the size for the current part.
@@ -129,67 +124,13 @@ func (s *S3Backup) generateRemoteRequest(rc io.ReadCloser) error {
}
// Attempt to upload the part.
etag, err := handlePart(part, partSize)
if err != nil {
log.WithError(err).Warn("failed to upload part")
// Send an AbortMultipartUpload request.
if err := s.finishUpload(urls.AbortMultipartUpload, nil); err != nil {
log.WithError(err).Warn("failed to abort multipart backup upload")
}
if _, err := handlePart(part, partSize); err != nil {
l.WithField("part_id", part).WithError(err).Warn("failed to upload part")
return err
}
// Add the part to the CompleteMultipartUpload body.
completeUploadBody.WriteString("\t<Part>\n")
completeUploadBody.WriteString("\t\t<ETag>\"" + etag + "\"</ETag>\n")
completeUploadBody.WriteString("\t\t<PartNumber>" + strconv.Itoa(i+1) + "</PartNumber>\n")
completeUploadBody.WriteString("\t</Part>\n")
}
completeUploadBody.WriteString("</CompleteMultipartUpload>")
// Send a CompleteMultipartUpload request.
if err := s.finishUpload(urls.CompleteMultipartUpload, &completeUploadBody); err != nil {
return err
}
log.WithFields(log.Fields{
"backup_id": s.Uuid,
"adapter": "s3",
}).Info("backup has been successfully uploaded")
return nil
}
// finishUpload sends a requests to the specified url to either complete or abort the upload.
func (s *S3Backup) finishUpload(url string, body io.Reader) error {
r, err := http.NewRequest(http.MethodPost, url, body)
if err != nil {
return err
}
// Create a new http client with a 10 second timeout.
c := &http.Client{
Timeout: 10 * time.Second,
}
res, err := c.Do(r)
if err != nil {
return err
}
defer res.Body.Close()
// Handle non-200 status codes.
if res.StatusCode != http.StatusOK {
// If no body was sent, we were aborting the upload.
if body == nil {
return fmt.Errorf("failed to abort S3 multipart upload, %d:%s", res.StatusCode, res.Status)
}
// If a body was sent we were completing the upload.
// TODO: Attempt to send abort request?
return fmt.Errorf("failed to complete S3 multipart upload, %d:%s", res.StatusCode, res.Status)
}
l.Info("backup has been successfully uploaded")
return nil
}

View File

@@ -2,9 +2,9 @@ package server
import (
"context"
"emperror.dev/errors"
"fmt"
"github.com/mitchellh/colorstring"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system"
"sync"
@@ -12,7 +12,7 @@ import (
"time"
)
var ErrTooMuchConsoleData = errors.Sentinel("console is outputting too much data")
var ErrTooMuchConsoleData = errors.New("console is outputting too much data")
type ConsoleThrottler struct {
mu sync.Mutex

View File

@@ -1,10 +1,10 @@
package server
import (
"emperror.dev/errors"
"fmt"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"strconv"
"sync"
"time"
)
@@ -45,11 +45,10 @@ func (s *Server) handleServerCrash() error {
// No point in doing anything here if the server isn't currently offline, there
// is no reason to do a crash detection event. If the server crash detection is
// disabled we want to skip anything after this as well.
if s.GetState() != environment.ProcessOfflineState || !s.Config().CrashDetectionEnabled {
if s.Environment.State() != environment.ProcessOfflineState || !s.Config().CrashDetectionEnabled {
if !s.Config().CrashDetectionEnabled {
s.Log().Debug("server triggered crash detection but handler is disabled for server process")
s.PublishConsoleOutputFromDaemon("Server detected as crashed; crash detection is disabled for this instance.")
s.PublishConsoleOutputFromDaemon("Aborting automatic restart, crash detection is disabled for this instance.")
}
return nil
@@ -57,14 +56,13 @@ func (s *Server) handleServerCrash() error {
exitCode, oomKilled, err := s.Environment.ExitState()
if err != nil {
return errors.WithStackIf(err)
return err
}
// If the system is not configured to detect a clean exit code as a crash, and the
// crash is not the result of the program running out of memory, do nothing.
if exitCode == 0 && !oomKilled && !config.Get().System.DetectCleanExitAsCrash {
if exitCode == 0 && !oomKilled && !config.Get().System.CrashDetection.DetectCleanExitAsCrash {
s.Log().Debug("server exited with successful exit code; system is configured to not detect this as a crash")
return nil
}
@@ -73,11 +71,14 @@ func (s *Server) handleServerCrash() error {
s.PublishConsoleOutputFromDaemon(fmt.Sprintf("Out of memory: %t", oomKilled))
c := s.crasher.LastCrashTime()
// If the last crash time was within the last 60 seconds we do not want to perform
// an automatic reboot of the process. Return an error that can be handled.
if !c.IsZero() && c.Add(time.Second*60).After(time.Now()) {
s.PublishConsoleOutputFromDaemon("Aborting automatic reboot: last crash occurred less than 60 seconds ago.")
timeout := config.Get().System.CrashDetection.Timeout
// If the last crash time was within the last `timeout` seconds we do not want to perform
// an automatic reboot of the process. Return an error that can be handled.
//
// If timeout is set to 0, always reboot the server (this is probably a terrible idea, but some people want it)
if timeout != 0 && !c.IsZero() && c.Add(time.Second*time.Duration(config.Get().System.CrashDetection.Timeout)).After(time.Now()) {
s.PublishConsoleOutputFromDaemon("Aborting automatic restart, last crash occurred less than " + strconv.Itoa(timeout) + " seconds ago.")
return &crashTooFrequent{}
}

View File

@@ -1,9 +1,11 @@
package server
import "emperror.dev/errors"
import (
"github.com/pkg/errors"
)
var ErrIsRunning = errors.Sentinel("server is running")
var ErrSuspended = errors.Sentinel("server is currently in a suspended state")
var ErrIsRunning = errors.New("server is running")
var ErrSuspended = errors.New("server is currently in a suspended state")
type crashTooFrequent struct {
}

View File

@@ -1,7 +1,6 @@
package server
import (
"emperror.dev/errors"
"github.com/pterodactyl/wings/server/filesystem"
"os"
)
@@ -13,12 +12,12 @@ func (s *Server) Filesystem() *filesystem.Filesystem {
// Ensures that the data directory for the server instance exists.
func (s *Server) EnsureDataDirectoryExists() error {
if _, err := os.Stat(s.fs.Path()); err != nil && !os.IsNotExist(err) {
return errors.WithStackIf(err)
return err
} else if err != nil {
// Create the server data directory because it does not currently exist
// on the system.
if err := os.MkdirAll(s.fs.Path(), 0700); err != nil {
return errors.WithStackIf(err)
return err
}
if err := s.fs.Chown("/"); err != nil {

View File

@@ -2,7 +2,6 @@ package filesystem
import (
"context"
"emperror.dev/errors"
"fmt"
"github.com/karrick/godirwalk"
"github.com/pterodactyl/wings/server/backup"
@@ -27,7 +26,7 @@ func (fs *Filesystem) GetIncludedFiles(dir string, ignored []string) (*backup.In
i, err := ignore.CompileIgnoreLines(ignored...)
if err != nil {
return nil, errors.WithStack(err)
return nil, err
}
// Walk through all of the files and directories on a server. This callback only returns
@@ -65,7 +64,7 @@ func (fs *Filesystem) GetIncludedFiles(dir string, ignored []string) (*backup.In
},
})
return inc, errors.WithStackIf(err)
return inc, err
}
// Compresses all of the files matching the given paths in the specified directory. This function
@@ -141,7 +140,7 @@ func (fs *Filesystem) CompressFiles(dir string, paths []string) (os.FileInfo, er
d := path.Join(cleanedRootDir, fmt.Sprintf("archive-%s.tar.gz", strings.ReplaceAll(time.Now().Format(time.RFC3339), ":", "")))
if err := a.Create(d, context.Background()); err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
f, err := os.Stat(d)

View File

@@ -4,9 +4,9 @@ import (
"archive/tar"
"archive/zip"
"compress/gzip"
"emperror.dev/errors"
"fmt"
"github.com/mholt/archiver/v3"
"github.com/pkg/errors"
"os"
"path/filepath"
"reflect"
@@ -47,10 +47,10 @@ func (fs *Filesystem) SpaceAvailableForDecompression(dir string, file string) (b
return false, ErrUnknownArchiveFormat
}
return false, errors.WithStackIf(err)
return false, err
}
return true, errors.WithStackIf(err)
return true, err
}
// Decompress a file in a given directory by using the archiver tool to infer the file
@@ -60,12 +60,12 @@ func (fs *Filesystem) SpaceAvailableForDecompression(dir string, file string) (b
func (fs *Filesystem) DecompressFile(dir string, file string) error {
source, err := fs.SafePath(filepath.Join(dir, file))
if err != nil {
return errors.WithStackIf(err)
return err
}
// Make sure the file exists basically.
if _, err := os.Stat(source); err != nil {
return errors.WithStackIf(err)
return err
}
// Walk over all of the files spinning up an additional go-routine for each file we've encountered
@@ -93,17 +93,17 @@ func (fs *Filesystem) DecompressFile(dir string, file string) error {
p, err := fs.SafePath(filepath.Join(dir, name))
if err != nil {
return errors.WrapIf(err, "failed to generate a safe path to server file")
return errors.WithMessage(err, "failed to generate a safe path to server file")
}
return errors.WrapIf(fs.Writefile(p, f), "could not extract file from archive")
return errors.WithMessage(fs.Writefile(p, f), "could not extract file from archive")
})
if err != nil {
if strings.HasPrefix(err.Error(), "format ") {
return errors.WithStackIf(ErrUnknownArchiveFormat)
return ErrUnknownArchiveFormat
}
return errors.WithStackIf(err)
return err
}
return nil

View File

@@ -1,7 +1,6 @@
package filesystem
import (
"emperror.dev/errors"
"github.com/apex/log"
"github.com/karrick/godirwalk"
"sync"
@@ -97,6 +96,11 @@ func (fs *Filesystem) CachedUsage() int64 {
// This is primarily to avoid a bunch of I/O operations from piling up on the server, especially on servers
// with a large amount of files.
func (fs *Filesystem) DiskUsage(allowStaleValue bool) (int64, error) {
// A disk check interval of 0 means this functionality is completely disabled.
if fs.diskCheckInterval == 0 {
return 0, nil
}
if !fs.lastLookupTime.Get().After(time.Now().Add(time.Second * fs.diskCheckInterval * -1)) {
// If we are now allowing a stale response go ahead and perform the lookup and return the fresh
// value. This is a blocking operation to the calling process.
@@ -153,7 +157,7 @@ func (fs *Filesystem) updateCachedDiskUsage() (int64, error) {
func (fs *Filesystem) DirectorySize(dir string) (int64, error) {
d, err := fs.SafePath(dir)
if err != nil {
return 0, errors.WithStackIf(err)
return 0, err
}
var size int64
@@ -184,7 +188,7 @@ func (fs *Filesystem) DirectorySize(dir string) (int64, error) {
},
})
return size, errors.WithStackIf(err)
return size, err
}
// Helper function to determine if a server has space available for a file of a given size.
@@ -197,7 +201,7 @@ func (fs *Filesystem) hasSpaceFor(size int64) error {
s, err := fs.DiskUsage(true)
if err != nil {
return errors.WithStackIf(err)
return err
}
if (s + size) > fs.MaxDisk() {

View File

@@ -1,16 +1,16 @@
package filesystem
import (
"emperror.dev/errors"
"fmt"
"github.com/apex/log"
"github.com/pkg/errors"
"os"
"path/filepath"
)
var ErrIsDirectory = errors.Sentinel("filesystem: is a directory")
var ErrNotEnoughDiskSpace = errors.Sentinel("filesystem: not enough disk space")
var ErrUnknownArchiveFormat = errors.Sentinel("filesystem: unknown archive format")
var ErrIsDirectory = errors.New("filesystem: is a directory")
var ErrNotEnoughDiskSpace = errors.New("filesystem: not enough disk space")
var ErrUnknownArchiveFormat = errors.New("filesystem: unknown archive format")
type BadPathResolutionError struct {
path string
@@ -23,6 +23,7 @@ func (b *BadPathResolutionError) Error() string {
if r == "" {
r = "<empty>"
}
return fmt.Sprintf("filesystem: server path [%s] resolves to a location outside the server root: %s", b.path, r)
}
@@ -57,7 +58,7 @@ func (fs *Filesystem) error(err error) *log.Entry {
// for the remainder of the directory. This is assuming an os.FileInfo struct was even returned.
func (fs *Filesystem) handleWalkerError(err error, f os.FileInfo) error {
if !IsBadPathResolutionError(err) {
return errors.WithStackIf(err)
return err
}
if f != nil && f.IsDir() {

View File

@@ -2,9 +2,9 @@ package filesystem
import (
"bufio"
"emperror.dev/errors"
"github.com/gabriel-vasile/mimetype"
"github.com/karrick/godirwalk"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system"
"io"
@@ -60,27 +60,27 @@ func (fs *Filesystem) Readfile(p string, w io.Writer) error {
}
if st, err := os.Stat(cleaned); err != nil {
return errors.WithStack(err)
return err
} else if st.IsDir() {
return errors.WithStack(ErrIsDirectory)
return ErrIsDirectory
}
f, err := os.Open(cleaned)
if err != nil {
return errors.WithStack(err)
return err
}
defer f.Close()
_, err = bufio.NewReader(f).WriteTo(w)
return errors.WithStack(err)
return err
}
// Writes a file to the system. If the file does not already exist one will be created.
func (fs *Filesystem) Writefile(p string, r io.Reader) error {
cleaned, err := fs.SafePath(p)
if err != nil {
return errors.WithStackIf(err)
return err
}
var currentSize int64
@@ -88,19 +88,19 @@ func (fs *Filesystem) Writefile(p string, r io.Reader) error {
// to it and an empty file. We'll then write to it later on after this completes.
if stat, err := os.Stat(cleaned); err != nil {
if !os.IsNotExist(err) {
return errors.WithStackIf(err)
return err
}
if err := os.MkdirAll(filepath.Dir(cleaned), 0755); err != nil {
return errors.WithStackIf(err)
return err
}
if err := fs.Chown(filepath.Dir(cleaned)); err != nil {
return errors.WithStackIf(err)
return err
}
} else {
if stat.IsDir() {
return errors.WithStack(ErrIsDirectory)
return ErrIsDirectory
}
currentSize = stat.Size()
@@ -119,7 +119,7 @@ func (fs *Filesystem) Writefile(p string, r io.Reader) error {
// truncate the existing file.
file, err := o.open(cleaned, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return errors.WithStackIf(err)
return err
}
defer file.Close()
@@ -138,7 +138,7 @@ func (fs *Filesystem) Writefile(p string, r io.Reader) error {
func (fs *Filesystem) CreateDirectory(name string, p string) error {
cleaned, err := fs.SafePath(path.Join(p, name))
if err != nil {
return errors.WithStackIf(err)
return err
}
return os.MkdirAll(cleaned, 0755)
@@ -148,12 +148,12 @@ func (fs *Filesystem) CreateDirectory(name string, p string) error {
func (fs *Filesystem) Rename(from string, to string) error {
cleanedFrom, err := fs.SafePath(from)
if err != nil {
return errors.WithStackIf(err)
return err
}
cleanedTo, err := fs.SafePath(to)
if err != nil {
return errors.WithStackIf(err)
return err
}
// If the target file or directory already exists the rename function will fail, so just
@@ -171,7 +171,7 @@ func (fs *Filesystem) Rename(from string, to string) error {
// we're not at the root directory level.
if d != fs.Path() {
if mkerr := os.MkdirAll(d, 0755); mkerr != nil {
return errors.WrapIf(mkerr, "failed to create directory structure for file rename")
return errors.WithMessage(mkerr, "failed to create directory structure for file rename")
}
}
@@ -185,7 +185,7 @@ func (fs *Filesystem) Rename(from string, to string) error {
func (fs *Filesystem) Chown(path string) error {
cleaned, err := fs.SafePath(path)
if err != nil {
return errors.WithStackIf(err)
return err
}
if fs.isTest {
@@ -197,7 +197,7 @@ func (fs *Filesystem) Chown(path string) error {
// Start by just chowning the initial path that we received.
if err := os.Chown(cleaned, uid, gid); err != nil {
return errors.WithStackIf(err)
return err
}
// If this is not a directory we can now return from the function, there is nothing
@@ -227,6 +227,23 @@ func (fs *Filesystem) Chown(path string) error {
})
}
func (fs *Filesystem) Chmod(path string, mode os.FileMode) error {
cleaned, err := fs.SafePath(path)
if err != nil {
return err
}
if fs.isTest {
return nil
}
if err := os.Chmod(cleaned, mode); err != nil {
return err
}
return nil
}
// Begin looping up to 50 times to try and create a unique copy file name. This will take
// an input of "file.txt" and generate "file copy.txt". If that name is already taken, it will
// then try to write "file copy 2.txt" and so on, until reaching 50 loops. At that point we
@@ -249,7 +266,7 @@ func (fs *Filesystem) findCopySuffix(dir string, name string, extension string)
// does exist, we'll just continue to the next loop and try again.
if _, err := fs.Stat(path.Join(dir, n)); err != nil {
if !errors.Is(err, os.ErrNotExist) {
return "", errors.WithStackIf(err)
return "", err
}
break
@@ -268,12 +285,12 @@ func (fs *Filesystem) findCopySuffix(dir string, name string, extension string)
func (fs *Filesystem) Copy(p string) error {
cleaned, err := fs.SafePath(p)
if err != nil {
return errors.WithStackIf(err)
return err
}
s, err := os.Stat(cleaned)
if err != nil {
return errors.WithStackIf(err)
return err
} else if s.IsDir() || !s.Mode().IsRegular() {
// If this is a directory or not a regular file, just throw a not-exist error
// since anything calling this function should understand what that means.
@@ -300,7 +317,7 @@ func (fs *Filesystem) Copy(p string) error {
source, err := os.Open(cleaned)
if err != nil {
return errors.WithStackIf(err)
return err
}
defer source.Close()

View File

@@ -2,7 +2,6 @@ package filesystem
import (
"context"
"emperror.dev/errors"
"golang.org/x/sync/errgroup"
"os"
"path/filepath"
@@ -26,7 +25,7 @@ func (fs *Filesystem) SafePath(p string) (string, error) {
// is truly pointing to.
ep, err := filepath.EvalSymlinks(r)
if err != nil && !os.IsNotExist(err) {
return "", errors.WithStackIf(err)
return "", err
} else if os.IsNotExist(err) {
// The requested directory doesn't exist, so at this point we need to iterate up the
// path chain until we hit a directory that _does_ exist and can be validated.
@@ -138,5 +137,5 @@ func (fs *Filesystem) ParallelSafePath(paths []string) ([]string, error) {
}
// Block until all of the routines finish and have returned a value.
return cleaned, errors.WithStackIf(g.Wait())
return cleaned, g.Wait()
}

View File

@@ -2,8 +2,8 @@ package filesystem
import (
"bytes"
"emperror.dev/errors"
. "github.com/franela/goblin"
"github.com/pkg/errors"
"os"
"path/filepath"
"testing"

View File

@@ -1,10 +1,10 @@
package filesystem
import (
"emperror.dev/errors"
"encoding/json"
"github.com/gabriel-vasile/mimetype"
"os"
"strconv"
"time"
)
@@ -19,16 +19,19 @@ func (s *Stat) MarshalJSON() ([]byte, error) {
Created string `json:"created"`
Modified string `json:"modified"`
Mode string `json:"mode"`
ModeBits string `json:"mode_bits"`
Size int64 `json:"size"`
Directory bool `json:"directory"`
File bool `json:"file"`
Symlink bool `json:"symlink"`
Mime string `json:"mime"`
}{
Name: s.Info.Name(),
Created: s.CTime().Format(time.RFC3339),
Modified: s.Info.ModTime().Format(time.RFC3339),
Mode: s.Info.Mode().String(),
Name: s.Info.Name(),
Created: s.CTime().Format(time.RFC3339),
Modified: s.Info.ModTime().Format(time.RFC3339),
Mode: s.Info.Mode().String(),
// Using `&os.ModePerm` on the file's mode will cause the mode to only have the permission values, and nothing else.
ModeBits: strconv.FormatUint(uint64(s.Info.Mode()&os.ModePerm), 8),
Size: s.Info.Size(),
Directory: s.Info.IsDir(),
File: !s.Info.IsDir(),
@@ -51,14 +54,14 @@ func (fs *Filesystem) Stat(p string) (*Stat, error) {
func (fs *Filesystem) unsafeStat(p string) (*Stat, error) {
s, err := os.Stat(p)
if err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
var m *mimetype.MIME
if !s.IsDir() {
m, err = mimetype.DetectFile(p)
if err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
}

View File

@@ -4,12 +4,12 @@ import (
"bufio"
"bytes"
"context"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
@@ -19,6 +19,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
@@ -75,7 +76,7 @@ func (s *Server) Install(sync bool) error {
// Reinstalls a server's software by utilizing the install script for the server egg. This
// does not touch any existing files for the server, other than what the script modifies.
func (s *Server) Reinstall() error {
if s.GetState() != environment.ProcessOfflineState {
if s.Environment.State() != environment.ProcessOfflineState {
s.Log().Debug("waiting for server instance to enter a stopped state")
if err := s.Environment.WaitForStop(10, true); err != nil {
return err
@@ -90,7 +91,7 @@ func (s *Server) internalInstall() error {
script, err := api.New().GetInstallationScript(s.Id())
if err != nil {
if !api.IsRequestError(err) {
return errors.WithStackIf(err)
return err
}
return errors.New(err.Error())
@@ -98,7 +99,7 @@ func (s *Server) internalInstall() error {
p, err := NewInstallationProcess(s, &script)
if err != nil {
return errors.WithStackIf(err)
return err
}
s.Log().Info("beginning installation process for server")
@@ -130,7 +131,7 @@ func NewInstallationProcess(s *Server, script *api.InstallationScript) (*Install
s.installer.cancel = &cancel
if c, err := environment.DockerClient(); err != nil {
return nil, errors.WithStackIf(err)
return nil, err
} else {
proc.client = c
proc.context = ctx
@@ -193,7 +194,7 @@ func (ip *InstallationProcess) RemoveContainer() {
})
if err != nil && !client.IsErrNotFound(err) {
ip.Server.Log().WithField("error", errors.WithStackIf(err)).Warn("failed to delete server install container")
ip.Server.Log().WithField("error", err).Warn("failed to delete server install container")
}
}
@@ -218,14 +219,14 @@ func (ip *InstallationProcess) Run() error {
}()
if err := ip.BeforeExecute(); err != nil {
return errors.WithStackIf(err)
return err
}
cid, err := ip.Execute()
if err != nil {
ip.RemoveContainer()
return errors.WithStackIf(err)
return err
}
// If this step fails, log a warning but don't exit out of the process. This is completely
@@ -248,12 +249,12 @@ func (ip *InstallationProcess) writeScriptToDisk() error {
// Make sure the temp directory root exists before trying to make a directory within it. The
// ioutil.TempDir call expects this base to exist, it won't create it for you.
if err := os.MkdirAll(ip.tempDir(), 0700); err != nil {
return errors.WrapIf(err, "could not create temporary directory for install process")
return errors.WithMessage(err, "could not create temporary directory for install process")
}
f, err := os.OpenFile(filepath.Join(ip.tempDir(), "install.sh"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return errors.WrapIf(err, "failed to write server installation script to disk before mount")
return errors.WithMessage(err, "failed to write server installation script to disk before mount")
}
defer f.Close()
@@ -265,7 +266,7 @@ func (ip *InstallationProcess) writeScriptToDisk() error {
}
if err := scanner.Err(); err != nil {
return errors.WithStackIf(err)
return err
}
w.Flush()
@@ -275,11 +276,62 @@ func (ip *InstallationProcess) writeScriptToDisk() error {
// Pulls the docker image to be used for the installation container.
func (ip *InstallationProcess) pullInstallationImage() error {
r, err := ip.client.ImagePull(ip.context, ip.Script.ContainerImage, types.ImagePullOptions{})
if err != nil {
return errors.WithStackIf(err)
// Get a registry auth configuration from the config.
var registryAuth *config.RegistryConfiguration
for registry, c := range config.Get().Docker.Registries {
if !strings.HasPrefix(ip.Script.ContainerImage, registry) {
continue
}
log.WithField("registry", registry).Debug("using authentication for registry")
registryAuth = &c
break
}
// Get the ImagePullOptions.
imagePullOptions := types.ImagePullOptions{All: false}
if registryAuth != nil {
b64, err := registryAuth.Base64()
if err != nil {
log.WithError(err).Error("failed to get registry auth credentials")
}
// b64 is a string so if there is an error it will just be empty, not nil.
imagePullOptions.RegistryAuth = b64
}
r, err := ip.client.ImagePull(context.Background(), ip.Script.ContainerImage, imagePullOptions)
if err != nil {
images, ierr := ip.client.ImageList(context.Background(), types.ImageListOptions{})
if ierr != nil {
// Well damn, something has gone really wrong here, just go ahead and abort there
// isn't much anything we can do to try and self-recover from this.
return ierr
}
for _, img := range images {
for _, t := range img.RepoTags {
if t != ip.Script.ContainerImage {
continue
}
log.WithFields(log.Fields{
"image": ip.Script.ContainerImage,
"err": err.Error(),
}).Warn("unable to pull requested image from remote source, however the image exists locally")
// Okay, we found a matching container image, in that case just go ahead and return
// from this function, since there is nothing else we need to do here.
return nil
}
}
return err
}
defer r.Close()
log.WithField("image", ip.Script.ContainerImage).Debug("pulling docker image... this could take a bit of time")
// Block continuation until the image has been pulled successfully.
scanner := bufio.NewScanner(r)
for scanner.Scan() {
@@ -287,7 +339,7 @@ func (ip *InstallationProcess) pullInstallationImage() error {
}
if err := scanner.Err(); err != nil {
return errors.WithStackIf(err)
return err
}
return nil
@@ -298,11 +350,11 @@ func (ip *InstallationProcess) pullInstallationImage() error {
// manner, if either one fails the error is returned.
func (ip *InstallationProcess) BeforeExecute() error {
if err := ip.writeScriptToDisk(); err != nil {
return errors.WrapIf(err, "failed to write installation script to disk")
return errors.WithMessage(err, "failed to write installation script to disk")
}
if err := ip.pullInstallationImage(); err != nil {
return errors.WrapIf(err, "failed to pull updated installation container image for server")
return errors.WithMessage(err, "failed to pull updated installation container image for server")
}
opts := types.ContainerRemoveOptions{
@@ -312,7 +364,7 @@ func (ip *InstallationProcess) BeforeExecute() error {
if err := ip.client.ContainerRemove(ip.context, ip.Server.Id()+"_installer", opts); err != nil {
if !client.IsErrNotFound(err) {
return errors.WrapIf(err, "failed to remove existing install container for server")
return errors.WithMessage(err, "failed to remove existing install container for server")
}
}
@@ -338,12 +390,12 @@ func (ip *InstallationProcess) AfterExecute(containerId string) error {
})
if err != nil && !client.IsErrNotFound(err) {
return errors.WithStackIf(err)
return err
}
f, err := os.OpenFile(ip.GetLogPath(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return errors.WithStackIf(err)
return err
}
defer f.Close()
@@ -372,15 +424,15 @@ func (ip *InstallationProcess) AfterExecute(containerId string) error {
| ------------------------------
`)
if err != nil {
return errors.WithStackIf(err)
return err
}
if err := tmpl.Execute(f, ip); err != nil {
return errors.WithStackIf(err)
return err
}
if _, err := io.Copy(f, reader); err != nil {
return errors.WithStackIf(err)
return err
}
return nil
@@ -448,7 +500,7 @@ func (ip *InstallationProcess) Execute() (string, error) {
r, err := ip.client.ContainerCreate(ip.context, conf, hostConf, nil, ip.Server.Id()+"_installer")
if err != nil {
return "", errors.WithStackIf(err)
return "", err
}
ip.Server.Log().WithField("container_id", r.ID).Info("running installation script for server in container")
@@ -468,7 +520,7 @@ func (ip *InstallationProcess) Execute() (string, error) {
select {
case err := <-eChan:
if err != nil {
return "", errors.WithStackIf(err)
return "", err
}
case <-sChan:
}
@@ -487,7 +539,7 @@ func (ip *InstallationProcess) StreamOutput(id string) error {
})
if err != nil {
return errors.WithStackIf(err)
return err
}
defer reader.Close()
@@ -500,7 +552,7 @@ func (ip *InstallationProcess) StreamOutput(id string) error {
if err := s.Err(); err != nil {
ip.Server.Log().WithFields(log.Fields{
"container_id": id,
"error": errors.WithStackIf(err),
"error": err,
}).Warn("error processing scanner line in installation output for server")
}
@@ -515,7 +567,7 @@ func (s *Server) SyncInstallState(successful bool) error {
err := api.New().SendInstallationStatus(s.Id(), successful)
if err != nil {
if !api.IsRequestError(err) {
return errors.WithStackIf(err)
return err
}
return errors.New(err.Error())

View File

@@ -1,7 +1,6 @@
package server
import (
"emperror.dev/errors"
"encoding/json"
"github.com/apex/log"
"github.com/pterodactyl/wings/api"
@@ -77,7 +76,7 @@ func (s *Server) StartEventListeners() {
s.Environment.SetState(environment.ProcessRunningState)
}
s.Log().WithField("error", errors.WithStackIf(err)).Error("failed to terminate environment after triggering throttle")
s.Log().WithField("error", err).Error("failed to terminate environment after triggering throttle")
}
}()
}
@@ -106,7 +105,7 @@ func (s *Server) StartEventListeners() {
stats := func(e events.Event) {
st := new(environment.Stats)
if err := json.Unmarshal([]byte(e.Data), st); err != nil {
s.Log().WithField("error", errors.WithStackIf(err)).Warn("failed to unmarshal server environment stats")
s.Log().WithField("error", err).Warn("failed to unmarshal server environment stats")
return
}
@@ -152,7 +151,7 @@ func (s *Server) onConsoleOutput(data string) {
processConfiguration := s.ProcessConfiguration()
// Check if the server is currently starting.
if s.GetState() == environment.ProcessStartingState {
if s.Environment.State() == environment.ProcessStartingState {
// Check if we should strip ansi color codes.
if processConfiguration.Startup.StripAnsi {
// Strip ansi color codes from the data string.

View File

@@ -1,12 +1,12 @@
package server
import (
"emperror.dev/errors"
"encoding/json"
"fmt"
"github.com/apex/log"
"github.com/creasty/defaults"
"github.com/gammazero/workerpool"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
@@ -35,7 +35,7 @@ func LoadDirectory() error {
configs, err := api.New().GetServers()
if err != nil {
if !api.IsRequestError(err) {
return errors.WithStackIf(err)
return err
}
return errors.New(err.Error())
@@ -89,12 +89,12 @@ func LoadDirectory() error {
func FromConfiguration(data api.ServerConfigurationResponse) (*Server, error) {
cfg := Configuration{}
if err := defaults.Set(&cfg); err != nil {
return nil, errors.WrapIf(err, "failed to set struct defaults for server configuration")
return nil, errors.WithMessage(err, "failed to set struct defaults for server configuration")
}
s := new(Server)
if err := defaults.Set(s); err != nil {
return nil, errors.WrapIf(err, "failed to set struct defaults for server")
return nil, errors.WithMessage(err, "failed to set struct defaults for server")
}
s.cfg = cfg

View File

@@ -49,7 +49,9 @@ func (s *Server) customMounts() []environment.Mount {
mounted := false
for _, allowed := range config.Get().AllowedMounts {
if !strings.HasPrefix(source, allowed) {
// Check if the source path is included in the allowed mounts list.
// filepath.Clean will strip all trailing slashes (unless the path is a root directory).
if !strings.HasPrefix(source, filepath.Clean(allowed)) {
continue
}

View File

@@ -2,7 +2,7 @@ package server
import (
"context"
"emperror.dev/errors"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/server/filesystem"
@@ -80,13 +80,13 @@ func (s *Server) HandlePowerAction(action PowerAction, waitSeconds ...int) error
// time than that passes an error will be propagated back up the chain and this
// request will be aborted.
if err := s.powerLock.Acquire(ctx, 1); err != nil {
return errors.WrapIf(err, "could not acquire lock on power state")
return errors.WithMessage(err, "could not acquire lock on power state")
}
} else {
// If no wait duration was provided we will attempt to immediately acquire the lock
// and bail out with a context deadline error if it is not acquired immediately.
if ok := s.powerLock.TryAcquire(1); !ok {
return errors.WrapIf(context.DeadlineExceeded, "could not acquire lock on power state")
return errors.WithMessage(context.DeadlineExceeded, "could not acquire lock on power state")
}
}
@@ -104,7 +104,7 @@ func (s *Server) HandlePowerAction(action PowerAction, waitSeconds ...int) error
switch action {
case PowerActionStart:
if s.GetState() != environment.ProcessOfflineState {
if s.Environment.State() != environment.ProcessOfflineState {
return ErrIsRunning
}
@@ -149,7 +149,7 @@ func (s *Server) HandlePowerAction(action PowerAction, waitSeconds ...int) error
func (s *Server) onBeforeStart() error {
s.Log().Info("syncing server configuration with panel")
if err := s.Sync(); err != nil {
return errors.WrapIf(err, "unable to sync server data from Panel instance")
return errors.WithMessage(err, "unable to sync server data from Panel instance")
}
// Disallow start & restart if the server is suspended. Do this check after performing a sync
@@ -185,7 +185,7 @@ func (s *Server) onBeforeStart() error {
s.PublishConsoleOutputFromDaemon("Ensuring file permissions are set correctly, this could take a few seconds...")
// Ensure all of the server file permissions are set correctly before booting the process.
if err := s.Filesystem().Chown("/"); err != nil {
return errors.WrapIf(err, "failed to chown root server directory during pre-boot process")
return errors.WithMessage(err, "failed to chown root server directory during pre-boot process")
}
}

View File

@@ -2,9 +2,9 @@ package server
import (
"context"
"emperror.dev/errors"
"fmt"
"github.com/apex/log"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
@@ -115,7 +115,7 @@ func (s *Server) Sync() error {
cfg, err := api.New().GetServerConfiguration(s.Id())
if err != nil {
if !api.IsRequestError(err) {
return errors.WithStackIf(err)
return err
}
if err.(*api.RequestError).Status == "404" {
@@ -131,7 +131,7 @@ func (s *Server) Sync() error {
func (s *Server) SyncWithConfiguration(cfg api.ServerConfigurationResponse) error {
// Update the data structure and persist it to the disk.
if err := s.UpdateDataStructure(cfg.Settings); err != nil {
return errors.WithStackIf(err)
return err
}
s.Lock()
@@ -171,7 +171,7 @@ func (s *Server) IsBootable() bool {
func (s *Server) CreateEnvironment() error {
// Ensure the data directory exists before getting too far through this process.
if err := s.EnsureDataDirectoryExists(); err != nil {
return errors.WithStackIf(err)
return err
}
return s.Environment.Create()

View File

@@ -1,7 +1,6 @@
package server
import (
"emperror.dev/errors"
"encoding/json"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
@@ -22,14 +21,14 @@ func CachedServerStates() (map[string]string, error) {
// Open the states file.
f, err := os.OpenFile(config.Get().System.GetStatesPath(), os.O_RDONLY|os.O_CREATE, 0644)
if err != nil {
return nil, errors.WithStackIf(err)
return nil, err
}
defer f.Close()
// Convert the json object to a map.
states := map[string]string{}
if err := json.NewDecoder(f).Decode(&states); err != nil && err != io.EOF {
return nil, errors.WithStackIf(err)
return nil, err
}
return states, nil
@@ -40,13 +39,13 @@ func saveServerStates() error {
// Get the states of all servers on the daemon.
states := map[string]string{}
for _, s := range GetServers().All() {
states[s.Id()] = s.GetState()
states[s.Id()] = s.Environment.State()
}
// Convert the map to a json object.
data, err := json.Marshal(states)
if err != nil {
return errors.WithStackIf(err)
return err
}
stateMutex.Lock()
@@ -54,7 +53,7 @@ func saveServerStates() error {
// Write the data to the file
if err := ioutil.WriteFile(config.Get().System.GetStatesPath(), data, 0644); err != nil {
return errors.WithStackIf(err)
return err
}
return nil
@@ -107,7 +106,7 @@ func (s *Server) OnStateChange() {
// automatically attempt to start the process back up for the user. This is done in a
// separate thread as to not block any actions currently taking place in the flow
// that called this function.
if (prevState == environment.ProcessStartingState || prevState == environment.ProcessRunningState) && s.GetState() == environment.ProcessOfflineState {
if (prevState == environment.ProcessStartingState || prevState == environment.ProcessRunningState) && s.Environment.State() == environment.ProcessOfflineState {
s.Log().Info("detected server as entering a crashed state; running crash handler")
go func(server *Server) {
@@ -115,6 +114,7 @@ func (s *Server) OnStateChange() {
if IsTooFrequentCrashError(err) {
server.Log().Info("did not restart server after crash; occurred too soon after the last")
} else {
s.PublishConsoleOutputFromDaemon("Server crash was detected but an error occurred while handling it.")
server.Log().WithField("error", err).Error("failed to handle server crash")
}
}
@@ -133,7 +133,7 @@ func (s *Server) GetState() string {
// environment state, it is simply the tracked state from this daemon instance, and
// not the response from Docker.
func (s *Server) IsRunning() bool {
st := s.GetState()
st := s.Environment.State()
return st == environment.ProcessRunningState || st == environment.ProcessStartingState
}

View File

@@ -1,10 +1,10 @@
package server
import (
"emperror.dev/errors"
"encoding/json"
"github.com/buger/jsonparser"
"github.com/imdario/mergo"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/environment"
)
@@ -18,7 +18,7 @@ import (
func (s *Server) UpdateDataStructure(data []byte) error {
src := new(Configuration)
if err := json.Unmarshal(data, src); err != nil {
return errors.WithStackIf(err)
return err
}
// Don't allow obviously corrupted data to pass through into this function. If the UUID
@@ -47,7 +47,7 @@ func (s *Server) UpdateDataStructure(data []byte) error {
// Merge the new data object that we have received with the existing server data object
// and then save it to the disk so it is persistent.
if err := mergo.Merge(&c, src, mergo.WithOverride); err != nil {
return errors.WithStackIf(err)
return err
}
// Don't explode if we're setting CPU limits to 0. Mergo sees that as an empty value
@@ -65,7 +65,7 @@ func (s *Server) UpdateDataStructure(data []byte) error {
// request is going to be boolean. Allegedly.
if v, err := jsonparser.GetBoolean(data, "container", "oom_disabled"); err != nil {
if err != jsonparser.KeyPathNotFoundError {
return errors.WithStackIf(err)
return err
}
} else {
c.Build.OOMDisabled = v
@@ -74,7 +74,7 @@ func (s *Server) UpdateDataStructure(data []byte) error {
// Mergo also cannot handle this boolean value.
if v, err := jsonparser.GetBoolean(data, "suspended"); err != nil {
if err != jsonparser.KeyPathNotFoundError {
return errors.WithStackIf(err)
return err
}
} else {
c.Suspended = v
@@ -82,7 +82,7 @@ func (s *Server) UpdateDataStructure(data []byte) error {
if v, err := jsonparser.GetBoolean(data, "skip_egg_scripts"); err != nil {
if err != jsonparser.KeyPathNotFoundError {
return errors.WithStackIf(err)
return err
}
} else {
c.SkipEggScripts = v
@@ -143,7 +143,7 @@ func (s *Server) SyncWithEnvironment() {
} else {
// Checks if the server is now in a suspended state. If so and a server process is currently running it
// will be gracefully stopped (and terminated if it refuses to stop).
if s.GetState() != environment.ProcessOfflineState {
if s.Environment.State() != environment.ProcessOfflineState {
s.Log().Info("server suspended with running process state, terminating now")
go func(s *Server) {

View File

@@ -1,7 +1,6 @@
package sftp
import (
"emperror.dev/errors"
"github.com/apex/log"
"github.com/patrickmn/go-cache"
"github.com/pkg/sftp"
@@ -58,14 +57,14 @@ func (fs FileSystem) Fileread(request *sftp.Request) (io.ReaderAt, error) {
if _, err := os.Stat(p); os.IsNotExist(err) {
return nil, sftp.ErrSshFxNoSuchFile
} else if err != nil {
fs.logger.WithField("error", errors.WithStackIf(err)).Error("error while processing file stat")
fs.logger.WithField("error", err).Error("error while processing file stat")
return nil, sftp.ErrSshFxFailure
}
file, err := os.Open(p)
if err != nil {
fs.logger.WithField("source", p).WithField("error", errors.WithStackIf(err)).Error("could not open file for reading")
fs.logger.WithField("source", p).WithField("error", err).Error("could not open file for reading")
return nil, sftp.ErrSshFxFailure
}
@@ -108,7 +107,7 @@ func (fs FileSystem) Filewrite(request *sftp.Request) (io.WriterAt, error) {
if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil {
l.WithFields(log.Fields{
"path": filepath.Dir(p),
"error": errors.WithStackIf(err),
"error": err,
}).Error("error making path for file")
return nil, sftp.ErrSshFxFailure
@@ -116,7 +115,7 @@ func (fs FileSystem) Filewrite(request *sftp.Request) (io.WriterAt, error) {
file, err := os.Create(p)
if err != nil {
l.WithField("error", errors.WithStackIf(err)).Error("failed to create file")
l.WithField("error", err).Error("failed to create file")
return nil, sftp.ErrSshFxFailure
}
@@ -124,7 +123,7 @@ func (fs FileSystem) Filewrite(request *sftp.Request) (io.WriterAt, error) {
// Not failing here is intentional. We still made the file, it is just owned incorrectly
// and will likely cause some issues.
if err := os.Chown(p, fs.User.Uid, fs.User.Gid); err != nil {
l.WithField("error", errors.WithStackIf(err)).Warn("failed to set permissions on file")
l.WithField("error", err).Warn("failed to set permissions on file")
}
return file, nil
@@ -133,7 +132,7 @@ func (fs FileSystem) Filewrite(request *sftp.Request) (io.WriterAt, error) {
// If the stat error isn't about the file not existing, there is some other issue
// at play and we need to go ahead and bail out of the process.
if statErr != nil {
l.WithField("error", errors.WithStackIf(statErr)).Error("encountered error performing file stat")
l.WithField("error", statErr).Error("encountered error performing file stat")
return nil, sftp.ErrSshFxFailure
}
@@ -159,14 +158,14 @@ func (fs FileSystem) Filewrite(request *sftp.Request) (io.WriterAt, error) {
return nil, sftp.ErrSSHFxNoSuchFile
}
l.WithField("flags", request.Flags).WithField("error", errors.WithStackIf(err)).Error("failed to open existing file on system")
l.WithField("flags", request.Flags).WithField("error", err).Error("failed to open existing file on system")
return nil, sftp.ErrSshFxFailure
}
// Not failing here is intentional. We still made the file, it is just owned incorrectly
// and will likely cause some issues.
if err := os.Chown(p, fs.User.Uid, fs.User.Gid); err != nil {
l.WithField("error", errors.WithStackIf(err)).Warn("error chowning file")
l.WithField("error", err).Warn("error chowning file")
}
return file, nil
@@ -220,7 +219,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
return sftp.ErrSSHFxNoSuchFile
}
l.WithField("error", errors.WithStackIf(err)).Error("failed to perform setstat on item")
l.WithField("error", err).Error("failed to perform setstat on item")
return sftp.ErrSSHFxFailure
}
return nil
@@ -234,7 +233,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
return sftp.ErrSSHFxNoSuchFile
}
l.WithField("target", target).WithField("error", errors.WithStackIf(err)).Error("failed to rename file")
l.WithField("target", target).WithField("error", err).Error("failed to rename file")
return sftp.ErrSshFxFailure
}
@@ -246,7 +245,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
}
if err := os.RemoveAll(p); err != nil {
l.WithField("error", errors.WithStackIf(err)).Error("failed to remove directory")
l.WithField("error", err).Error("failed to remove directory")
return sftp.ErrSshFxFailure
}
@@ -258,7 +257,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
}
if err := os.MkdirAll(p, 0755); err != nil {
l.WithField("error", errors.WithStackIf(err)).Error("failed to create directory")
l.WithField("error", err).Error("failed to create directory")
return sftp.ErrSshFxFailure
}
@@ -270,7 +269,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
}
if err := os.Symlink(p, target); err != nil {
l.WithField("target", target).WithField("error", errors.WithStackIf(err)).Error("failed to create symlink")
l.WithField("target", target).WithField("error", err).Error("failed to create symlink")
return sftp.ErrSshFxFailure
}
@@ -286,7 +285,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
return sftp.ErrSSHFxNoSuchFile
}
l.WithField("error", errors.WithStackIf(err)).Error("failed to remove a file")
l.WithField("error", err).Error("failed to remove a file")
return sftp.ErrSshFxFailure
}
@@ -305,7 +304,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
// and will likely cause some issues. There is no logical check for if the file was removed
// because both of those cases (Rmdir, Remove) have an explicit return rather than break.
if err := os.Chown(fileLocation, fs.User.Uid, fs.User.Gid); err != nil {
l.WithField("error", errors.WithStackIf(err)).Warn("error chowning file")
l.WithField("error", err).Warn("error chowning file")
}
return sftp.ErrSshFxOk
@@ -327,7 +326,7 @@ func (fs FileSystem) Filelist(request *sftp.Request) (sftp.ListerAt, error) {
files, err := ioutil.ReadDir(p)
if err != nil {
fs.logger.WithField("error", errors.WithStackIf(err)).Error("error while listing directory")
fs.logger.WithField("error", err).Error("error while listing directory")
return nil, sftp.ErrSshFxFailure
}
@@ -342,7 +341,7 @@ func (fs FileSystem) Filelist(request *sftp.Request) (sftp.ListerAt, error) {
if os.IsNotExist(err) {
return nil, sftp.ErrSshFxNoSuchFile
} else if err != nil {
fs.logger.WithField("source", p).WithField("error", errors.WithStackIf(err)).Error("error performing stat on file")
fs.logger.WithField("source", p).WithField("error", err).Error("error performing stat on file")
return nil, sftp.ErrSshFxFailure
}

View File

@@ -1,8 +1,8 @@
package sftp
import (
"emperror.dev/errors"
"github.com/apex/log"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/api"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/server"
@@ -28,14 +28,14 @@ func Initialize(config config.SystemConfiguration) error {
}
if err := New(s); err != nil {
return errors.WithStackIf(err)
return err
}
// Initialize the SFTP server in a background thread since this is
// a long running operation.
go func(s *Server) {
if err := s.Initialize(); err != nil {
log.WithField("subsystem", "sftp").WithField("error", errors.WithStackIf(err)).Error("failed to initialize SFTP subsystem")
log.WithField("subsystem", "sftp").WithField("error", err).Error("failed to initialize SFTP subsystem")
}
}(s)