Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04d714f19c | ||
|
|
a18f60bd05 | ||
|
|
deea5babbc | ||
|
|
e41b3dc09a | ||
|
|
6366794838 | ||
|
|
c01a39d881 | ||
|
|
3f2ce59766 | ||
|
|
bcf0c72e47 | ||
|
|
bd5892b70c | ||
|
|
70ea61f22f | ||
|
|
1b0c2e1764 | ||
|
|
3d532f6e0b | ||
|
|
81fd1a3758 | ||
|
|
b52c3fb61e | ||
|
|
bc3d92f9e6 | ||
|
|
ee08829a28 | ||
|
|
83f0d2c953 | ||
|
|
605be3ebad | ||
|
|
ca6dc2c964 | ||
|
|
dc41126e25 | ||
|
|
da4c542724 | ||
|
|
e7d93a5248 | ||
|
|
51aa4c73cd | ||
|
|
4a7510d36f | ||
|
|
ba0a1a651e | ||
|
|
068f41393d | ||
|
|
169e8b8f8b | ||
|
|
c6e2889075 | ||
|
|
f62f714863 | ||
|
|
da9ace5d9d | ||
|
|
97345123ce | ||
|
|
8aa9105ed3 | ||
|
|
e8088f85d0 | ||
|
|
0c8476c79b | ||
|
|
a7a66e8bc0 | ||
|
|
65a861a9b6 | ||
|
|
de51fd1c51 | ||
|
|
40c70673cd | ||
|
|
73b221d022 | ||
|
|
287b286940 | ||
|
|
1d0e85cf55 | ||
|
|
30ec6dc78d | ||
|
|
121a4d1146 | ||
|
|
b9be373671 | ||
|
|
aedd0e406c | ||
|
|
82b23ef638 | ||
|
|
d970ec35b7 | ||
|
|
e2872e786e | ||
|
|
f81e35d960 | ||
|
|
672fb860ea | ||
|
|
8081c83de4 | ||
|
|
f379d0e54a | ||
|
|
ffb6bd72ef | ||
|
|
488ef9de54 | ||
|
|
34349d4b48 | ||
|
|
2197d82957 | ||
|
|
20ece60a72 | ||
|
|
33e584b447 |
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
github: [DaneEveritt]
|
||||||
|
custom: ["https://paypal.me/PterodactylSoftware"]
|
||||||
36
.github/workflows/build-test.yml
vendored
36
.github/workflows/build-test.yml
vendored
@@ -1,12 +1,10 @@
|
|||||||
name: "Build & Test"
|
name: Run Tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches-ignore:
|
branches-ignore:
|
||||||
- 'master'
|
- 'master'
|
||||||
- 'release/**'
|
- 'release/**'
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
@@ -15,7 +13,7 @@ jobs:
|
|||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
os: [ ubuntu-20.04 ]
|
os: [ ubuntu-20.04 ]
|
||||||
go: [ 1.15 ]
|
go: [ 1.15.6 ]
|
||||||
goos: [ linux ]
|
goos: [ linux ]
|
||||||
goarch: [ amd64, arm, arm64 ]
|
goarch: [ amd64, arm, arm64 ]
|
||||||
|
|
||||||
@@ -28,12 +26,40 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
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
|
- name: Build
|
||||||
env:
|
env:
|
||||||
GOOS: ${{ matrix.goos }}
|
GOOS: ${{ matrix.goos }}
|
||||||
GOARCH: ${{ matrix.goarch }}
|
GOARCH: ${{ matrix.goarch }}
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
SRC_PATH: github.com/pterodactyl/wings
|
||||||
run: |
|
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
|
- name: Test
|
||||||
run: go test ./...
|
run: go test ./...
|
||||||
|
|||||||
11
.github/workflows/codeql-analysis.yml
vendored
11
.github/workflows/codeql-analysis.yml
vendored
@@ -1,16 +1,12 @@
|
|||||||
name: "Code scanning - action"
|
name: CodeQL Scanning
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- 'develop'
|
||||||
pull_request:
|
pull_request:
|
||||||
schedule:
|
|
||||||
- cron: '0 21 * * 6'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
CodeQL-Build:
|
CodeQL-Build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@@ -18,7 +14,6 @@ jobs:
|
|||||||
# We must fetch at least the immediate parents so that if this is
|
# We must fetch at least the immediate parents so that if this is
|
||||||
# a pull request then we can checkout the head.
|
# a pull request then we can checkout the head.
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|
||||||
# If this run was triggered by a pull request event, then checkout
|
# If this run was triggered by a pull request event, then checkout
|
||||||
# the head of the pull request instead of the merge commit.
|
# the head of the pull request instead of the merge commit.
|
||||||
- run: git checkout HEAD^2
|
- run: git checkout HEAD^2
|
||||||
|
|||||||
47
.github/workflows/docker.yml
vendored
Normal file
47
.github/workflows/docker.yml
vendored
Normal 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 }}
|
||||||
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -1,10 +1,8 @@
|
|||||||
name: "Release"
|
name: Create Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
@@ -20,9 +18,9 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
REF: ${{ github.ref }}
|
REF: ${{ github.ref }}
|
||||||
run: |
|
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
|
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
|
||||||
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=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=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
|
- name: Test
|
||||||
run: go test ./...
|
run: go test ./...
|
||||||
|
|||||||
47
.travis.yml
47
.travis.yml
@@ -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
|
|
||||||
34
CHANGELOG.md
34
CHANGELOG.md
@@ -1,5 +1,39 @@
|
|||||||
# Changelog
|
# 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
|
## v1.1.0
|
||||||
This release **requires** `Panel@1.1.0` or later to run due to API changes.
|
This release **requires** `Panel@1.1.0` or later to run due to API changes.
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
# ----------------------------------
|
|
||||||
# Pterodactyl Panel Dockerfile
|
|
||||||
# ----------------------------------
|
|
||||||
|
|
||||||
FROM golang:1.15-alpine
|
FROM golang:1.15-alpine
|
||||||
|
ARG VERSION="develop"
|
||||||
COPY . /go/wings/
|
COPY . /go/wings/
|
||||||
WORKDIR /go/wings/
|
WORKDIR /go/wings/
|
||||||
RUN apk add --no-cache upx \
|
RUN apk add --no-cache upx \
|
||||||
&& go build -ldflags="-s -w" \
|
&& CGO_ENABLED=0 go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${VERSION}" \
|
||||||
&& upx --brute wings
|
&& upx wings
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
COPY --from=0 /go/wings/wings /usr/bin/
|
COPY --from=0 /go/wings/wings /usr/bin/
|
||||||
|
|||||||
@@ -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. |
|
| [**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. |
|
| [**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! |
|
| [**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 RoyaleHosting’s reliable servers and network. Easy to use, provisioned in a couple of minutes. |
|
| [**RoyaleHosting**](https://royalehosting.net/) | Build your dreams and deploy them with RoyaleHosting’s 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. |
|
| [**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. |
|
| [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. |
|
||||||
|
|||||||
10
api/api.go
10
api/api.go
@@ -2,10 +2,10 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/system"
|
"github.com/pterodactyl/wings/system"
|
||||||
"io"
|
"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) {
|
func (r *Request) Make(method, url string, body io.Reader, opts ...func(r *http.Request)) (*Response, error) {
|
||||||
req, err := http.NewRequest(method, url, body)
|
req, err := http.NewRequest(method, url, body)
|
||||||
if err != nil {
|
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))
|
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) {
|
func (r *Request) Post(url string, data interface{}) (*Response, error) {
|
||||||
b, err := json.Marshal(data)
|
b, err := json.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.Make(http.MethodPost, r.Endpoint(url), bytes.NewBuffer(b))
|
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 {
|
func (r *Response) Bind(v interface{}) error {
|
||||||
b, err := r.Read()
|
b, err := r.Read()
|
||||||
if err != nil {
|
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
|
// Returns the error message from the API call as a string. The error message will be formatted
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// backupUploadIDs stores a cache of active S3 backups.
|
||||||
|
var backupUploadIDs = cache.New(time.Hour*3, time.Minute*5)
|
||||||
|
|
||||||
type BackupRemoteUploadResponse struct {
|
type BackupRemoteUploadResponse struct {
|
||||||
CompleteMultipartUpload string `json:"complete_multipart_upload"`
|
UploadID string `json:"upload_id"`
|
||||||
AbortMultipartUpload string `json:"abort_multipart_upload"`
|
|
||||||
Parts []string `json:"parts"`
|
Parts []string `json:"parts"`
|
||||||
PartSize int64 `json:"part_size"`
|
PartSize int64 `json:"part_size"`
|
||||||
}
|
}
|
||||||
@@ -16,7 +19,7 @@ type BackupRemoteUploadResponse struct {
|
|||||||
func (r *Request) GetBackupRemoteUploadURLs(backup string, size int64) (*BackupRemoteUploadResponse, error) {
|
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)})
|
resp, err := r.Get(fmt.Sprintf("/backups/%s", backup), Q{"size": strconv.FormatInt(size, 10)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@@ -26,13 +29,19 @@ func (r *Request) GetBackupRemoteUploadURLs(backup string, size int64) (*BackupR
|
|||||||
|
|
||||||
var res BackupRemoteUploadResponse
|
var res BackupRemoteUploadResponse
|
||||||
if err := resp.Bind(&res); err != nil {
|
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
|
return &res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type BackupRequest struct {
|
type BackupRequest struct {
|
||||||
|
UploadID string `json:"upload_id"`
|
||||||
Checksum string `json:"checksum"`
|
Checksum string `json:"checksum"`
|
||||||
ChecksumType string `json:"checksum_type"`
|
ChecksumType string `json:"checksum_type"`
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
@@ -42,9 +51,14 @@ type BackupRequest struct {
|
|||||||
// Notifies the panel that a specific backup has been completed and is now
|
// Notifies the panel that a specific backup has been completed and is now
|
||||||
// available for a user to view and download.
|
// available for a user to view and download.
|
||||||
func (r *Request) SendBackupStatus(backup string, data BackupRequest) error {
|
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)
|
resp, err := r.Post(fmt.Sprintf("/backups/%s", backup), data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
@@ -57,7 +56,7 @@ type RawServerData struct {
|
|||||||
func (r *Request) GetServers() ([]RawServerData, error) {
|
func (r *Request) GetServers() ([]RawServerData, error) {
|
||||||
resp, err := r.Get("/servers", Q{"per_page": strconv.Itoa(int(config.Get().RemoteQuery.BootServersPerPage))})
|
resp, err := r.Get("/servers", Q{"per_page": strconv.Itoa(int(config.Get().RemoteQuery.BootServersPerPage))})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@@ -67,7 +66,7 @@ func (r *Request) GetServers() ([]RawServerData, error) {
|
|||||||
|
|
||||||
var res allServerResponse
|
var res allServerResponse
|
||||||
if err := resp.Bind(&res); err != nil {
|
if err := resp.Bind(&res); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
@@ -117,7 +116,7 @@ func (r *Request) GetServers() ([]RawServerData, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := g.Wait(); err != nil {
|
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)
|
resp, err := r.Get(fmt.Sprintf("/servers/%s", uuid), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cfg, errors.WithStackIf(err)
|
return cfg, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@@ -139,7 +138,7 @@ func (r *Request) GetServerConfiguration(uuid string) (ServerConfigurationRespon
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := resp.Bind(&cfg); err != nil {
|
if err := resp.Bind(&cfg); err != nil {
|
||||||
return cfg, errors.WithStackIf(err)
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
@@ -150,7 +149,7 @@ func (r *Request) GetInstallationScript(uuid string) (InstallationScript, error)
|
|||||||
var is InstallationScript
|
var is InstallationScript
|
||||||
resp, err := r.Get(fmt.Sprintf("/servers/%s/install", uuid), nil)
|
resp, err := r.Get(fmt.Sprintf("/servers/%s/install", uuid), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return is, errors.WithStackIf(err)
|
return is, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@@ -159,7 +158,7 @@ func (r *Request) GetInstallationScript(uuid string) (InstallationScript, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := resp.Bind(&is); err != nil {
|
if err := resp.Bind(&is); err != nil {
|
||||||
return is, errors.WithStackIf(err)
|
return is, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return is, nil
|
return is, nil
|
||||||
@@ -169,7 +168,7 @@ func (r *Request) GetInstallationScript(uuid string) (InstallationScript, error)
|
|||||||
func (r *Request) SendInstallationStatus(uuid string, successful bool) error {
|
func (r *Request) SendInstallationStatus(uuid string, successful bool) error {
|
||||||
resp, err := r.Post(fmt.Sprintf("/servers/%s/install", uuid), D{"successful": successful})
|
resp, err := r.Post(fmt.Sprintf("/servers/%s/install", uuid), D{"successful": successful})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
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 {
|
func (r *Request) SendArchiveStatus(uuid string, successful bool) error {
|
||||||
resp, err := r.Post(fmt.Sprintf("/servers/%s/archive", uuid), D{"successful": successful})
|
resp, err := r.Post(fmt.Sprintf("/servers/%s/archive", uuid), D{"successful": successful})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@@ -193,7 +192,7 @@ func (r *Request) SendArchiveStatus(uuid string, successful bool) error {
|
|||||||
func (r *Request) SendTransferFailure(uuid string) error {
|
func (r *Request) SendTransferFailure(uuid string) error {
|
||||||
resp, err := r.Get(fmt.Sprintf("/servers/%s/transfer/failure", uuid), nil)
|
resp, err := r.Get(fmt.Sprintf("/servers/%s/transfer/failure", uuid), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@@ -203,7 +202,7 @@ func (r *Request) SendTransferFailure(uuid string) error {
|
|||||||
func (r *Request) SendTransferSuccess(uuid string) error {
|
func (r *Request) SendTransferSuccess(uuid string) error {
|
||||||
resp, err := r.Get(fmt.Sprintf("/servers/%s/transfer/success", uuid), nil)
|
resp, err := r.Get(fmt.Sprintf("/servers/%s/transfer/success", uuid), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ import (
|
|||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/AlecAivazis/survey/v2/terminal"
|
"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/api/types"
|
||||||
"github.com/docker/docker/pkg/parsers/kernel"
|
"github.com/docker/docker/pkg/parsers/kernel"
|
||||||
|
"github.com/docker/docker/pkg/parsers/operatingsystem"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/system"
|
"github.com/pterodactyl/wings/system"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|||||||
30
cmd/root.go
30
cmd/root.go
@@ -19,7 +19,7 @@ import (
|
|||||||
"github.com/pterodactyl/wings/loggers/cli"
|
"github.com/pterodactyl/wings/loggers/cli"
|
||||||
"golang.org/x/crypto/acme/autocert"
|
"golang.org/x/crypto/acme/autocert"
|
||||||
|
|
||||||
"emperror.dev/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pkg/profile"
|
"github.com/pkg/profile"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
@@ -77,7 +77,7 @@ func readConfiguration() (*config.Configuration, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s, err := os.Stat(p); err != nil {
|
if s, err := os.Stat(p); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
} else if s.IsDir() {
|
} else if s.IsDir() {
|
||||||
return nil, errors.New("cannot use directory as configuration file path")
|
return nil, errors.New("cannot use directory as configuration file path")
|
||||||
}
|
}
|
||||||
@@ -199,7 +199,7 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
|
|
||||||
states, err := server.CachedServerStates()
|
states, err := server.CachedServerStates()
|
||||||
if err != nil {
|
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
|
// 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.
|
// as a result will result in a slow boot.
|
||||||
if !r && (st == environment.ProcessRunningState || st == environment.ProcessStartingState) {
|
if !r && (st == environment.ProcessRunningState || st == environment.ProcessStartingState) {
|
||||||
if err := s.HandlePowerAction(server.PowerActionStart); err != nil {
|
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()) {
|
} else if r || (!r && s.IsRunning()) {
|
||||||
// If the server is currently running on Docker, mark the process as being in that state.
|
// 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)
|
s.Environment.SetState(environment.ProcessRunningState)
|
||||||
if err := s.Environment.Attach(); err != nil {
|
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")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
return
|
// 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.
|
||||||
// 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)
|
s.Environment.SetState(environment.ProcessOfflineState)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,7 +336,6 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
if err := s.ListenAndServeTLS("", ""); err != nil {
|
if err := s.ListenAndServeTLS("", ""); err != nil {
|
||||||
log.WithFields(log.Fields{"auto_tls": true, "tls_hostname": tlsHostname, "error": err}).
|
log.WithFields(log.Fields{"auto_tls": true, "tls_hostname": tlsHostname, "error": err}).
|
||||||
Fatal("failed to configure HTTP server using auto-tls")
|
Fatal("failed to configure HTTP server using auto-tls")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -345,9 +343,8 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
|
|
||||||
// Check if main http server should run with TLS.
|
// Check if main http server should run with TLS.
|
||||||
if c.Api.Ssl.Enabled {
|
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")
|
log.WithFields(log.Fields{"auto_tls": false, "error": err}).Fatal("failed to configure HTTPS server")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -356,7 +353,6 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
s.TLSConfig = nil
|
s.TLSConfig = nil
|
||||||
if err := s.ListenAndServe(); err != nil {
|
if err := s.ListenAndServe(); err != nil {
|
||||||
log.WithField("error", err).Fatal("failed to configure HTTP server")
|
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.
|
// in the code without having to pass around a logger instance.
|
||||||
func configureLogging(logDir string, debug bool) error {
|
func configureLogging(logDir string, debug bool) error {
|
||||||
if err := os.MkdirAll(path.Join(logDir, "/install"), 0700); err != nil {
|
if err := os.MkdirAll(path.Join(logDir, "/install"), 0700); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
p := filepath.Join(logDir, "/wings.log")
|
p := filepath.Join(logDir, "/wings.log")
|
||||||
w, err := logrotate.NewFile(p)
|
w, err := logrotate.NewFile(p)
|
||||||
if err != nil {
|
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 {
|
if debug {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/cobaugh/osrelease"
|
"github.com/cobaugh/osrelease"
|
||||||
"github.com/creasty/defaults"
|
"github.com/creasty/defaults"
|
||||||
"github.com/gbrlsnchs/jwt/v3"
|
"github.com/gbrlsnchs/jwt/v3"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@@ -47,12 +47,6 @@ type Configuration struct {
|
|||||||
System SystemConfiguration `json:"system" yaml:"system"`
|
System SystemConfiguration `json:"system" yaml:"system"`
|
||||||
Docker DockerConfiguration `json:"docker" yaml:"docker"`
|
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
|
// Defines internal throttling configurations for server processes to prevent
|
||||||
// someone from running an endless loop that spams data to logs.
|
// someone from running an endless loop that spams data to logs.
|
||||||
Throttles ConsoleThrottles
|
Throttles ConsoleThrottles
|
||||||
@@ -198,7 +192,7 @@ func GetJwtAlgorithm() *jwt.HMACSHA {
|
|||||||
func NewFromPath(path string) (*Configuration, error) {
|
func NewFromPath(path string) (*Configuration, error) {
|
||||||
c := new(Configuration)
|
c := new(Configuration)
|
||||||
if err := defaults.Set(c); err != nil {
|
if err := defaults.Set(c); err != nil {
|
||||||
return c, errors.WithStackIf(err)
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.unsafeSetPath(path)
|
c.unsafeSetPath(path)
|
||||||
@@ -236,12 +230,12 @@ func (c *Configuration) EnsurePterodactylUser() (*user.User, error) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return u, c.setSystemUser(u)
|
return u, c.setSystemUser(u)
|
||||||
} else if _, ok := err.(user.UnknownUserError); !ok {
|
} else if _, ok := err.(user.UnknownUserError); !ok {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sysName, err := getSystemName()
|
sysName, err := getSystemName()
|
||||||
if err != nil {
|
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)
|
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
|
// We have to create the group first on Alpine, so do that here before continuing on
|
||||||
// to the user creation process.
|
// to the user creation process.
|
||||||
if _, err := exec.Command("addgroup", "-S", c.System.Username).Output(); err != nil {
|
if _, err := exec.Command("addgroup", "-S", c.System.Username).Output(); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
split := strings.Split(command, " ")
|
split := strings.Split(command, " ")
|
||||||
if _, err := exec.Command(split[0], split[1:]...).Output(); err != nil {
|
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 {
|
if u, err := user.Lookup(c.System.Username); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return u, c.setSystemUser(u)
|
return u, c.setSystemUser(u)
|
||||||
}
|
}
|
||||||
@@ -306,11 +300,11 @@ func (c *Configuration) WriteToDisk() error {
|
|||||||
|
|
||||||
b, err := yaml.Marshal(&ccopy)
|
b, err := yaml.Marshal(&ccopy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ioutil.WriteFile(c.GetPath(), b, 0644); err != nil {
|
if err := ioutil.WriteFile(c.GetPath(), b, 0644); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -320,7 +314,7 @@ func (c *Configuration) WriteToDisk() error {
|
|||||||
func getSystemName() (string, error) {
|
func getSystemName() (string, error) {
|
||||||
// use osrelease to get release version and ID
|
// use osrelease to get release version and ID
|
||||||
if release, err := osrelease.Read(); err != nil {
|
if release, err := osrelease.Read(); err != nil {
|
||||||
return "", errors.WithStackIf(err)
|
return "", err
|
||||||
} else {
|
} else {
|
||||||
return release["ID"], nil
|
return release["ID"], nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
@@ -73,7 +72,7 @@ func (c RegistryConfiguration) Base64() (string, error) {
|
|||||||
|
|
||||||
b, err := json.Marshal(authConfig)
|
b, err := json.Marshal(authConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.WithStackIf(err)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return base64.URLEncoding.EncodeToString(b), nil
|
return base64.URLEncoding.EncodeToString(b), nil
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@@ -53,13 +53,12 @@ type SystemConfiguration struct {
|
|||||||
// considered stale and a re-check should occur. DANGER: setting this value too low can seriously
|
// 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
|
// impact system performance and cause massive I/O bottlenecks and high CPU usage for the Wings
|
||||||
// process.
|
// 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"`
|
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
|
// 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
|
// 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
|
// 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"`
|
WebsocketLogCount int `default:"150" yaml:"websocket_log_count"`
|
||||||
|
|
||||||
Sftp SftpConfiguration `yaml:"sftp"`
|
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
|
// Ensures that all of the system directories exist on the system. These directories are
|
||||||
@@ -94,7 +107,7 @@ func (sc *SystemConfiguration) ConfigureDirectories() error {
|
|||||||
// that.
|
// that.
|
||||||
if d, err := filepath.EvalSymlinks(sc.Data); err != nil {
|
if d, err := filepath.EvalSymlinks(sc.Data); err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
} else if d != sc.Data {
|
} else if d != sc.Data {
|
||||||
sc.Data = d
|
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) {
|
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() {
|
} else if (err != nil && os.IsNotExist(err)) || !st.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat("/etc/logrotate.d/wings"); err != nil && !os.IsNotExist(err) {
|
if _, err := os.Stat("/etc/logrotate.d/wings"); err != nil && !os.IsNotExist(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
} else if err == nil {
|
} else if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -147,7 +160,7 @@ func (sc *SystemConfiguration) EnableLogRotation() error {
|
|||||||
// it so files can be rotated easily.
|
// it so files can be rotated easily.
|
||||||
f, err := os.Create("/etc/logrotate.d/wings")
|
f, err := os.Create("/etc/logrotate.d/wings")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
@@ -167,10 +180,10 @@ func (sc *SystemConfiguration) EnableLogRotation() error {
|
|||||||
}`)
|
}`)
|
||||||
|
|
||||||
if err != nil {
|
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.
|
// Returns the location of the JSON file that tracks server states.
|
||||||
@@ -190,7 +203,7 @@ func (sc *SystemConfiguration) ConfigureTimezone() error {
|
|||||||
if sc.Timezone == "" {
|
if sc.Timezone == "" {
|
||||||
if b, err := ioutil.ReadFile("/etc/timezone"); err != nil {
|
if b, err := ioutil.ReadFile("/etc/timezone"); err != nil {
|
||||||
if !os.IsNotExist(err) {
|
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)
|
ctx, _ := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
@@ -224,5 +237,5 @@ func (sc *SystemConfiguration) ConfigureTimezone() error {
|
|||||||
|
|
||||||
_, err := time.LoadLocation(sc.Timezone)
|
_, 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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
version: '3.5'
|
version: '3.8'
|
||||||
services:
|
services:
|
||||||
daemon:
|
wings:
|
||||||
build: .
|
image: ghcr.io/pterodactyl/wings:latest
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
- daemon0
|
- wings0
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
- "2022:2022"
|
- "2022:2022"
|
||||||
tty: true
|
tty: true
|
||||||
environment:
|
environment:
|
||||||
- "DEBUG=false"
|
TZ: "UTC"
|
||||||
- "TZ=UTC" # change to the three letter timezone of your choosing
|
DEBUG: "false"
|
||||||
volumes:
|
volumes:
|
||||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||||
- "/var/lib/docker/containers/:/var/lib/docker/containers/"
|
- "/var/lib/docker/containers/:/var/lib/docker/containers/"
|
||||||
@@ -19,17 +19,16 @@ services:
|
|||||||
- "/var/lib/pterodactyl/:/var/lib/pterodactyl/"
|
- "/var/lib/pterodactyl/:/var/lib/pterodactyl/"
|
||||||
- "/var/log/pterodactyl/:/var/log/pterodactyl/"
|
- "/var/log/pterodactyl/:/var/log/pterodactyl/"
|
||||||
- "/tmp/pterodactyl/:/tmp/pterodactyl/"
|
- "/tmp/pterodactyl/:/tmp/pterodactyl/"
|
||||||
## you may need /srv/daemon-data if you are upgrading from an old daemon
|
# you may need /srv/daemon-data if you are upgrading from an old daemon
|
||||||
## - "/srv/daemon-data/:/srv/daemon-data/"
|
#- "/srv/daemon-data/:/srv/daemon-data/"
|
||||||
## Required for ssl if you user let's encrypt. uncomment to use.
|
# Required for ssl if you user let's encrypt. uncomment to use.
|
||||||
## - "/etc/letsencrypt/:/etc/letsencrypt/"
|
#- "/etc/letsencrypt/:/etc/letsencrypt/"
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
daemon0:
|
wings0:
|
||||||
name: daemon0
|
name: wings0
|
||||||
driver: bridge
|
driver: bridge
|
||||||
ipam:
|
ipam:
|
||||||
config:
|
config:
|
||||||
- subnet: "172.21.0.0/16"
|
- subnet: "172.21.0.0/16"
|
||||||
driver_opts:
|
driver_opts:
|
||||||
com.docker.network.bridge.name: daemon0
|
com.docker.network.bridge.name: wings0
|
||||||
@@ -38,15 +38,16 @@ func (a *Allocations) Bindings() nat.PortMap {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
binding := []nat.PortBinding{
|
binding := nat.PortBinding{
|
||||||
{
|
|
||||||
HostIP: ip,
|
HostIP: ip,
|
||||||
HostPort: strconv.Itoa(port),
|
HostPort: strconv.Itoa(port),
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out[nat.Port(fmt.Sprintf("%d/tcp", port))] = binding
|
tcp := nat.Port(fmt.Sprintf("%d/tcp", port))
|
||||||
out[nat.Port(fmt.Sprintf("%d/udp", port))] = binding
|
udp := nat.Port(fmt.Sprintf("%d/udp", port))
|
||||||
|
|
||||||
|
out[tcp] = append(out[tcp], binding)
|
||||||
|
out[udp] = append(out[udp], binding)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
@@ -13,6 +12,7 @@ import (
|
|||||||
"github.com/docker/docker/api/types/mount"
|
"github.com/docker/docker/api/types/mount"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/daemon/logger/jsonfilelog"
|
"github.com/docker/docker/daemon/logger/jsonfilelog"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
"io"
|
"io"
|
||||||
@@ -36,7 +36,7 @@ func (e *Environment) Attach() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := e.followOutput(); err != nil {
|
if err := e.followOutput(); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := types.ContainerAttachOptions{
|
opts := types.ContainerAttachOptions{
|
||||||
@@ -48,7 +48,7 @@ func (e *Environment) Attach() error {
|
|||||||
|
|
||||||
// Set the stream again with the container.
|
// Set the stream again with the container.
|
||||||
if st, err := e.client.ContainerAttach(context.Background(), e.Id, opts); err != nil {
|
if st, err := e.client.ContainerAttach(context.Background(), e.Id, opts); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
} else {
|
} else {
|
||||||
e.SetStream(&st)
|
e.SetStream(&st)
|
||||||
}
|
}
|
||||||
@@ -70,14 +70,19 @@ func (e *Environment) Attach() error {
|
|||||||
// indicates that the container is no longer running.
|
// indicates that the container is no longer running.
|
||||||
go func(ctx context.Context) {
|
go func(ctx context.Context) {
|
||||||
if err := e.pollResources(ctx); err != nil {
|
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)
|
}(ctx)
|
||||||
|
|
||||||
// Stream the reader output to the console which will then fire off events and handle console
|
// Stream the reader output to the console which will then fire off events and handle console
|
||||||
// throttling and sending the output to the user.
|
// throttling and sending the output to the user.
|
||||||
if _, err := io.Copy(console, e.stream.Reader); err != nil {
|
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)
|
}(c)
|
||||||
|
|
||||||
@@ -115,7 +120,7 @@ func (e *Environment) InSituUpdate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
u := container.UpdateConfig{
|
u := container.UpdateConfig{
|
||||||
@@ -125,7 +130,7 @@ func (e *Environment) InSituUpdate() error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if _, err := e.client.ContainerUpdate(ctx, e.Id, u); err != nil {
|
if _, err := e.client.ContainerUpdate(ctx, e.Id, u); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -140,12 +145,12 @@ func (e *Environment) Create() error {
|
|||||||
if _, err := e.client.ContainerInspect(context.Background(), e.Id); err == nil {
|
if _, err := e.client.ContainerInspect(context.Background(), e.Id); err == nil {
|
||||||
return nil
|
return nil
|
||||||
} else if !client.IsErrNotFound(err) {
|
} else if !client.IsErrNotFound(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to pull the requested image before creating the container.
|
// Try to pull the requested image before creating the container.
|
||||||
if err := e.ensureImageExists(e.meta.Image); err != nil {
|
if err := e.ensureImageExists(e.meta.Image); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
a := e.Configuration.Allocations()
|
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 {
|
if _, err := e.client.ContainerCreate(context.Background(), conf, hostConf, nil, e.Id); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -272,7 +277,7 @@ func (e *Environment) Destroy() error {
|
|||||||
func (e *Environment) followOutput() error {
|
func (e *Environment) followOutput() error {
|
||||||
if exists, err := e.Exists(); !exists {
|
if exists, err := e.Exists(); !exists {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New(fmt.Sprintf("no such container: %s", e.Id))
|
return errors.New(fmt.Sprintf("no such container: %s", e.Id))
|
||||||
@@ -291,9 +296,19 @@ func (e *Environment) followOutput() error {
|
|||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
|
|
||||||
r := bufio.NewReader(reader)
|
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:
|
ParentLoop:
|
||||||
for {
|
for {
|
||||||
var b bytes.Buffer
|
str.Reset()
|
||||||
var line []byte
|
var line []byte
|
||||||
var isPrefix bool
|
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,
|
// 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
|
// 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.
|
// 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
|
// 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.
|
// 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
|
// 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.
|
// 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)
|
e.Events().Publish(environment.ConsoleOutputEvent, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,7 +353,7 @@ func (e *Environment) followOutput() error {
|
|||||||
}
|
}
|
||||||
}(reader)
|
}(reader)
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pulls the image from Docker. If there is an error while pulling the image from the source
|
// Pulls the image from Docker. If there is an error while pulling the image from the source
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package docker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
@@ -156,7 +155,7 @@ func (e *Environment) ExitState() (uint32, bool, error) {
|
|||||||
return 1, false, nil
|
return 1, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, false, errors.WithStackIf(err)
|
return 0, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return uint32(c.State.ExitCode), c.State.OOMKilled, nil
|
return uint32(c.State.ExitCode), c.State.OOMKilled, nil
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ package docker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
"os"
|
"os"
|
||||||
@@ -26,7 +26,7 @@ func (e *Environment) OnBeforeStart() error {
|
|||||||
// the Panel is usee.
|
// the Panel is usee.
|
||||||
if err := e.client.ContainerRemove(context.Background(), e.Id, types.ContainerRemoveOptions{RemoveVolumes: true}); err != nil {
|
if err := e.client.ContainerRemove(context.Background(), e.Id, types.ContainerRemoveOptions{RemoveVolumes: true}); err != nil {
|
||||||
if !client.IsErrNotFound(err) {
|
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
|
// @see https://github.com/pterodactyl/panel/issues/2000
|
||||||
if !client.IsErrNotFound(err) {
|
if !client.IsErrNotFound(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If the server is running update our internal state and continue on with the attach.
|
// 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.
|
// to truncate them.
|
||||||
if _, err := os.Stat(c.LogPath); err == nil {
|
if _, err := os.Stat(c.LogPath); err == nil {
|
||||||
if err := os.Truncate(c.LogPath, 0); 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
|
// exists on the system, and rebuild the container if that is required for server booting to
|
||||||
// occur.
|
// occur.
|
||||||
if err := e.OnBeforeStart(); err != nil {
|
if err := e.OnBeforeStart(); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := e.client.ContainerStart(ctx, e.Id, types.ContainerStartOptions{}); err != nil {
|
if err := e.client.ContainerStart(ctx, e.Id, types.ContainerStartOptions{}); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// No errors, good to continue through.
|
// 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.
|
// will be terminated forcefully depending on the value of the second argument.
|
||||||
func (e *Environment) WaitForStop(seconds uint, terminate bool) error {
|
func (e *Environment) WaitForStop(seconds uint, terminate bool) error {
|
||||||
if err := e.Stop(); err != nil {
|
if err := e.Stop(); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(seconds)*time.Second)
|
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():
|
case <-ctx.Done():
|
||||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||||
if terminate {
|
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:
|
case err := <-errChan:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if terminate {
|
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) {
|
||||||
return errors.WithStackIf(e.Terminate(os.Kill))
|
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(err)
|
return e.Terminate(os.Kill)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
case <-ok:
|
case <-ok:
|
||||||
}
|
}
|
||||||
@@ -210,7 +215,7 @@ func (e *Environment) WaitForStop(seconds uint, terminate bool) error {
|
|||||||
func (e *Environment) Terminate(signal os.Signal) error {
|
func (e *Environment) Terminate(signal os.Signal) error {
|
||||||
c, err := e.client.ContainerInspect(context.Background(), e.Id)
|
c, err := e.client.ContainerInspect(context.Background(), e.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.State.Running {
|
if !c.State.Running {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package docker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
@@ -15,18 +15,17 @@ import (
|
|||||||
// Attach to the instance and then automatically emit an event whenever the resource usage for the
|
// Attach to the instance and then automatically emit an event whenever the resource usage for the
|
||||||
// server process changes.
|
// server process changes.
|
||||||
func (e *Environment) pollResources(ctx context.Context) error {
|
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 {
|
if e.st.Load() == environment.ProcessOfflineState {
|
||||||
return errors.New("cannot enable resource polling on a stopped server")
|
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)
|
stats, err := e.client.ContainerStats(context.Background(), e.Id, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer stats.Body.Close()
|
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 := dec.Decode(&v); err != nil {
|
||||||
if err != io.EOF {
|
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 {
|
} else {
|
||||||
l.Debug("io.EOF encountered during stats decode, stopping polling...")
|
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 {
|
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 {
|
} else {
|
||||||
e.Events().Publish(environment.ResourceEvent, string(b))
|
e.Events().Publish(environment.ResourceEvent, string(b))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
@@ -15,7 +15,7 @@ type dockerLogLine struct {
|
|||||||
Log string `json:"log"`
|
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) {
|
func (e *Environment) setStream(s *types.HijackedResponse) {
|
||||||
e.mu.Lock()
|
e.mu.Lock()
|
||||||
@@ -42,7 +42,7 @@ func (e *Environment) SendCommand(c string) error {
|
|||||||
|
|
||||||
_, err := e.stream.Conn.Write([]byte(c + "\n"))
|
_, 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
|
// 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),
|
Tail: strconv.Itoa(lines),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package events
|
package events
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/gammazero/workerpool"
|
"github.com/gammazero/workerpool"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -69,7 +68,7 @@ func (e *EventBus) Publish(topic string, data string) {
|
|||||||
func (e *EventBus) PublishJson(topic string, data interface{}) error {
|
func (e *EventBus) PublishJson(topic string, data interface{}) error {
|
||||||
b, err := json.Marshal(data)
|
b, err := json.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Publish(topic, string(b))
|
e.Publish(topic, string(b))
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -3,7 +3,6 @@ module github.com/pterodactyl/wings
|
|||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
emperror.dev/errors v0.8.0
|
|
||||||
github.com/AlecAivazis/survey/v2 v2.1.0
|
github.com/AlecAivazis/survey/v2 v2.1.0
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||||
github.com/Jeffail/gabs/v2 v2.5.1
|
github.com/Jeffail/gabs/v2 v2.5.1
|
||||||
@@ -18,7 +17,6 @@ require (
|
|||||||
github.com/containerd/containerd v1.3.7 // indirect
|
github.com/containerd/containerd v1.3.7 // indirect
|
||||||
github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b // indirect
|
github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b // indirect
|
||||||
github.com/creasty/defaults v1.5.0
|
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/distribution v2.7.1+incompatible // indirect
|
||||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible
|
github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
@@ -58,6 +56,7 @@ require (
|
|||||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
|
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/profile v1.5.0
|
||||||
github.com/pkg/sftp v1.11.0
|
github.com/pkg/sftp v1.11.0
|
||||||
github.com/prometheus/common v0.11.1 // indirect
|
github.com/prometheus/common v0.11.1 // indirect
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -1,7 +1,5 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
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=
|
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 h1:AT4+23hOFopXYZaNGugbk7MWItkz0SfTmH/Hk92KeeE=
|
||||||
github.com/AlecAivazis/survey/v2 v2.1.0/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk=
|
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=
|
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/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/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/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 h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
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=
|
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.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.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.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 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
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.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 h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
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=
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package installer
|
package installer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/asaskevich/govalidator"
|
"github.com/asaskevich/govalidator"
|
||||||
"github.com/buger/jsonparser"
|
"github.com/buger/jsonparser"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
"github.com/pterodactyl/wings/server"
|
"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.
|
// Unmarshal the environment variables from the request into the server struct.
|
||||||
if b, _, _, err := jsonparser.Get(data, "environment"); err != nil {
|
if b, _, _, err := jsonparser.Get(data, "environment"); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
cfg.EnvVars = make(environment.Variables)
|
cfg.EnvVars = make(environment.Variables)
|
||||||
if err := json.Unmarshal(b, &cfg.EnvVars); err != nil {
|
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.
|
// Unmarshal the allocation mappings from the request into the server struct.
|
||||||
if b, _, _, err := jsonparser.Get(data, "allocations", "mappings"); err != nil {
|
if b, _, _, err := jsonparser.Get(data, "allocations", "mappings"); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
cfg.Allocations.Mappings = make(map[string][]int)
|
cfg.Allocations.Mappings = make(map[string][]int)
|
||||||
if err := json.Unmarshal(b, &cfg.Allocations.Mappings); err != nil {
|
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)
|
c, err := api.New().GetServerConfiguration(cfg.Uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !api.IsRequestError(err) {
|
if !api.IsRequestError(err) {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New(err.Error())
|
return nil, errors.New(err.Error())
|
||||||
|
|||||||
@@ -6,16 +6,17 @@ import (
|
|||||||
"github.com/apex/log/handlers/cli"
|
"github.com/apex/log/handlers/cli"
|
||||||
color2 "github.com/fatih/color"
|
color2 "github.com/fatih/color"
|
||||||
"github.com/mattn/go-colorable"
|
"github.com/mattn/go-colorable"
|
||||||
"emperror.dev/errors"
|
"github.com/pkg/errors"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Default = New(os.Stderr, true)
|
var Default = New(os.Stderr, true)
|
||||||
|
|
||||||
var bold = color2.New(color2.Bold)
|
var bold = color2.New(color2.Bold)
|
||||||
|
var boldred = color2.New(color2.Bold, color2.FgRed)
|
||||||
|
|
||||||
var Strings = [...]string{
|
var Strings = [...]string{
|
||||||
log.DebugLevel: "DEBUG",
|
log.DebugLevel: "DEBUG",
|
||||||
@@ -60,7 +61,6 @@ func (h *Handler) HandleLog(e *log.Entry) error {
|
|||||||
if name == "source" {
|
if name == "source" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(h.Writer, " %s=%v", color.Sprint(name), e.Fields.Get(name))
|
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" {
|
if name != "error" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var br = color2.New(color2.Bold, color2.FgRed)
|
|
||||||
if err, ok := e.Fields.Get("error").(error); ok {
|
if err, ok := e.Fields.Get("error").(error); ok {
|
||||||
fmt.Fprintf(h.Writer, "\n%s%+v\n\n", br.Sprintf("Stacktrace:"), getErrorStack(err, false))
|
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 {
|
} else {
|
||||||
fmt.Fprintf(h.Writer, "\n%s%+v\n\n", br.Sprintf("Invalid Error:"), err)
|
fmt.Fprintf(h.Writer, "\n%s\n%+v\n\n", boldred.Sprintf("Stacktrace:"), err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ package parser
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/Jeffail/gabs/v2"
|
"github.com/Jeffail/gabs/v2"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/buger/jsonparser"
|
"github.com/buger/jsonparser"
|
||||||
"github.com/iancoleman/strcase"
|
"github.com/iancoleman/strcase"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -76,13 +76,13 @@ func (cfr *ConfigurationFileReplacement) getKeyValue(value []byte) interface{} {
|
|||||||
func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error) {
|
func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error) {
|
||||||
parsed, err := gabs.ParseJSON(data)
|
parsed, err := gabs.ParseJSON(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range f.Replace {
|
for _, v := range f.Replace {
|
||||||
value, err := f.LookupConfigurationValue(v)
|
value, err := f.LookupConfigurationValue(v)
|
||||||
if err != nil {
|
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
|
// 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
|
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 {
|
} else {
|
||||||
@@ -110,7 +110,7 @@ func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error
|
|||||||
continue
|
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)
|
_, err = c.SetP(value, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
i, _ := strconv.Atoi(matches[2])
|
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])
|
ct, err := c.ArrayElementP(i, matches[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if i != 0 || (!errors.Is(err, gabs.ErrNotArray) && !errors.Is(err, gabs.ErrNotFound)) {
|
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)
|
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
|
// 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 there is not an object structure detected (no matches[3] available).
|
||||||
if _, err = c.SetP(t, matches[1]); err != nil {
|
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
|
// 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.
|
// to match additional elements. In those cases the server will just have to be rebooted or something.
|
||||||
ct, err = c.ArrayElementP(0, matches[1])
|
ct, err = c.ArrayElementP(0, matches[1])
|
||||||
if err != nil {
|
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 {
|
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
|
return nil
|
||||||
@@ -253,7 +253,7 @@ func (f *ConfigurationFile) LookupConfigurationValue(cfr ConfigurationFileReplac
|
|||||||
match, _, _, err := jsonparser.Get(f.configuration, path...)
|
match, _, _, err := jsonparser.Get(f.configuration, path...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != jsonparser.KeyPathNotFoundError {
|
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")
|
log.WithFields(log.Fields{"path": path, "filename": f.FileName}).Debug("attempted to load a configuration value that does not exist")
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ package parser
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/beevik/etree"
|
"github.com/beevik/etree"
|
||||||
"github.com/buger/jsonparser"
|
"github.com/buger/jsonparser"
|
||||||
"github.com/icza/dyno"
|
"github.com/icza/dyno"
|
||||||
"github.com/magiconair/properties"
|
"github.com/magiconair/properties"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
@@ -166,17 +166,17 @@ func (f *ConfigurationFile) Parse(path string, internal bool) error {
|
|||||||
|
|
||||||
b := strings.TrimSuffix(path, filepath.Base(path))
|
b := strings.TrimSuffix(path, filepath.Base(path))
|
||||||
if err := os.MkdirAll(b, 0755); err != nil {
|
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 {
|
} else {
|
||||||
if _, err := os.Create(path); err != nil {
|
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 f.Parse(path, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses an xml file.
|
// Parses an xml file.
|
||||||
@@ -348,12 +348,12 @@ func (f *ConfigurationFile) parseJsonFile(path string) error {
|
|||||||
func (f *ConfigurationFile) parseYamlFile(path string) error {
|
func (f *ConfigurationFile) parseYamlFile(path string) error {
|
||||||
b, err := readFileBytes(path)
|
b, err := readFileBytes(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
i := make(map[string]interface{})
|
i := make(map[string]interface{})
|
||||||
if err := yaml.Unmarshal(b, &i); err != nil {
|
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
|
// 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.
|
// makes working with unknown JSON significantly easier.
|
||||||
jsonBytes, err := json.Marshal(dyno.ConvertMapI2MapS(i))
|
jsonBytes, err := json.Marshal(dyno.ConvertMapI2MapS(i))
|
||||||
if err != nil {
|
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
|
// Now that the data is converted, treat it just like JSON and pass it to the
|
||||||
// iterator function to update values as necessary.
|
// iterator function to update values as necessary.
|
||||||
data, err := f.IterateOverJson(jsonBytes)
|
data, err := f.IterateOverJson(jsonBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remarshal the JSON into YAML format before saving it back to the disk.
|
// Remarshal the JSON into YAML format before saving it back to the disk.
|
||||||
marshaled, err := yaml.Marshal(data.Data())
|
marshaled, err := yaml.Marshal(data.Data())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ioutil.WriteFile(path, marshaled, 0644)
|
return ioutil.WriteFile(path, marshaled, 0644)
|
||||||
@@ -386,7 +386,7 @@ func (f *ConfigurationFile) parseYamlFile(path string) error {
|
|||||||
func (f *ConfigurationFile) parseTextFile(path string) error {
|
func (f *ConfigurationFile) parseTextFile(path string) error {
|
||||||
input, err := ioutil.ReadFile(path)
|
input, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
lines := strings.Split(string(input), "\n")
|
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 {
|
if err := ioutil.WriteFile(path, []byte(strings.Join(lines, "\n")), 0644); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -415,7 +415,7 @@ func (f *ConfigurationFile) parsePropertiesFile(path string) error {
|
|||||||
// Open the file.
|
// Open the file.
|
||||||
f2, err := os.Open(path)
|
f2, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var s strings.Builder
|
var s strings.Builder
|
||||||
@@ -437,20 +437,20 @@ func (f *ConfigurationFile) parsePropertiesFile(path string) error {
|
|||||||
|
|
||||||
// Handle any scanner errors.
|
// Handle any scanner errors.
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode the properties file.
|
// Decode the properties file.
|
||||||
p, err := properties.LoadFile(path, properties.UTF8)
|
p, err := properties.LoadFile(path, properties.UTF8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace any values that need to be replaced.
|
// Replace any values that need to be replaced.
|
||||||
for _, replace := range f.Replace {
|
for _, replace := range f.Replace {
|
||||||
data, err := f.LookupConfigurationValue(replace)
|
data, err := f.LookupConfigurationValue(replace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
v, ok := p.Get(replace.Match)
|
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 {
|
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.
|
// Open the file for writing.
|
||||||
w, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
w, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer w.Close()
|
defer w.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
"github.com/pterodactyl/wings/server/filesystem"
|
"github.com/pterodactyl/wings/server/filesystem"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -75,7 +75,7 @@ func (e *RequestError) AbortWithStatus(status int, c *gin.Context) {
|
|||||||
if status >= 500 {
|
if status >= 500 {
|
||||||
e.logger().WithField("error", e.Err).Error("encountered HTTP/500 error while handling request")
|
e.logger().WithField("error", e.Err).Error("encountered HTTP/500 error while handling request")
|
||||||
|
|
||||||
c.Error(errors.WithStackIf(e))
|
c.Error(e)
|
||||||
} else {
|
} else {
|
||||||
e.logger().WithField("error", e.Err).Debug("encountered non-HTTP/500 error while handling request")
|
e.logger().WithField("error", e.Err).Debug("encountered non-HTTP/500 error while handling request")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ func Configure() *gin.Engine {
|
|||||||
files.POST("/delete", postServerDeleteFiles)
|
files.POST("/delete", postServerDeleteFiles)
|
||||||
files.POST("/compress", postServerCompressFiles)
|
files.POST("/compress", postServerCompressFiles)
|
||||||
files.POST("/decompress", postServerDecompressFiles)
|
files.POST("/decompress", postServerDecompressFiles)
|
||||||
|
files.POST("/chmod", postServerChmodFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
backup := server.Group("/backup")
|
backup := server.Group("/backup")
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package router
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/router/tokens"
|
"github.com/pterodactyl/wings/router/tokens"
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -227,7 +227,7 @@ func deleteServer(c *gin.Context) {
|
|||||||
if err := os.RemoveAll(p); err != nil {
|
if err := os.RemoveAll(p); err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"path": p,
|
"path": p,
|
||||||
"error": errors.WithStackIf(err),
|
"error": err,
|
||||||
}).Warn("failed to remove server files during deletion process")
|
}).Warn("failed to remove server files during deletion process")
|
||||||
}
|
}
|
||||||
}(s.Filesystem().Path())
|
}(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
|
// preventing any JWT generated before the current time from being used to connect to
|
||||||
// the socket or send along commands.
|
// the socket or send along commands.
|
||||||
func postServerDenyWSTokens(c *gin.Context) {
|
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 {
|
if err := c.BindJSON(&data); err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ package router
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
"github.com/apex/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/router/tokens"
|
"github.com/pterodactyl/wings/router/tokens"
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
"github.com/pterodactyl/wings/server/filesystem"
|
"github.com/pterodactyl/wings/server/filesystem"
|
||||||
@@ -35,10 +36,6 @@ func getServerFileContents(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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("X-Mime-Type", st.Mimetype)
|
||||||
c.Header("Content-Length", strconv.Itoa(int(st.Info.Size())))
|
c.Header("Content-Length", strconv.Itoa(int(st.Info.Size())))
|
||||||
|
|
||||||
@@ -48,6 +45,19 @@ func getServerFileContents(c *gin.Context) {
|
|||||||
c.Header("Content-Disposition", "attachment; filename="+st.Info.Name())
|
c.Header("Content-Disposition", "attachment; filename="+st.Info.Name())
|
||||||
c.Header("Content-Type", "application/octet-stream")
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,6 +366,68 @@ func postServerDecompressFiles(c *gin.Context) {
|
|||||||
c.Status(http.StatusNoContent)
|
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) {
|
func postServerUploadFiles(c *gin.Context) {
|
||||||
token := tokens.UploadPayload{}
|
token := tokens.UploadPayload{}
|
||||||
if err := tokens.ParseToken([]byte(c.Query("token")), &token); err != nil {
|
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 {
|
func handleFileUpload(p string, s *server.Server, header *multipart.FileHeader) error {
|
||||||
file, err := header.Open()
|
file, err := header.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
if err := s.Filesystem().Writefile(p, file); err != nil {
|
if err := s.Filesystem().Writefile(p, file); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -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 you pass through manual locations in the API call this logic will be skipped.
|
||||||
if strings.HasPrefix(cfg.Api.Ssl.KeyFile, "/etc/letsencrypt/live/") {
|
if strings.HasPrefix(cfg.Api.Ssl.KeyFile, "/etc/letsencrypt/live/") {
|
||||||
cfg.Api.Ssl.KeyFile = ccopy.Api.Ssl.KeyFile
|
cfg.Api.Ssl.KeyFile = strings.ToLower(ccopy.Api.Ssl.KeyFile)
|
||||||
cfg.Api.Ssl.CertificateFile = ccopy.Api.Ssl.CertificateFile
|
cfg.Api.Ssl.CertificateFile = strings.ToLower(ccopy.Api.Ssl.CertificateFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Set(&cfg)
|
config.Set(&cfg)
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/buger/jsonparser"
|
"github.com/buger/jsonparser"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/mholt/archiver/v3"
|
"github.com/mholt/archiver/v3"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/installer"
|
"github.com/pterodactyl/wings/installer"
|
||||||
@@ -94,27 +94,40 @@ func postServerArchive(c *gin.Context) {
|
|||||||
s := GetServer(c.Param("server"))
|
s := GetServer(c.Param("server"))
|
||||||
|
|
||||||
go func(s *server.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 {
|
if err := s.Archiver.Archive(); err != nil {
|
||||||
s.Log().WithField("error", err).Error("failed to get archive for server")
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Log().Debug("successfully created server archive, notifying panel")
|
s.Log().Debug("successfully created server archive, notifying panel")
|
||||||
|
|
||||||
r := api.New()
|
if err := r.SendArchiveStatus(s.Id(), true); err != nil {
|
||||||
err := r.SendArchiveStatus(s.Id(), true)
|
|
||||||
if err != nil {
|
|
||||||
if !api.IsRequestError(err) {
|
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
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Log().Debug("successfully notified panel of archive status")
|
s.Log().Info("successfully notified panel of successful archive status")
|
||||||
}(s)
|
}(s)
|
||||||
|
|
||||||
c.Status(http.StatusAccepted)
|
c.Status(http.StatusAccepted)
|
||||||
@@ -140,8 +153,7 @@ func postTransfer(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
l.Info("server transfer failed, notifying panel")
|
l.Info("server transfer failed, notifying panel")
|
||||||
err := api.New().SendTransferFailure(serverID)
|
if err := api.New().SendTransferFailure(serverID); err != nil {
|
||||||
if err != nil {
|
|
||||||
if !api.IsRequestError(err) {
|
if !api.IsRequestError(err) {
|
||||||
l.WithField("error", err).Error("failed to notify panel with transfer failure")
|
l.WithField("error", err).Error("failed to notify panel with transfer failure")
|
||||||
return
|
return
|
||||||
@@ -157,7 +169,7 @@ func postTransfer(c *gin.Context) {
|
|||||||
// Make a new GET request to the URL the panel gave us.
|
// Make a new GET request to the URL the panel gave us.
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
if err != 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +179,7 @@ func postTransfer(c *gin.Context) {
|
|||||||
// Execute the http request.
|
// Execute the http request.
|
||||||
res, err := client.Do(req)
|
res, err := client.Do(req)
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
@@ -176,13 +188,11 @@ func postTransfer(c *gin.Context) {
|
|||||||
if res.StatusCode != 200 {
|
if res.StatusCode != 200 {
|
||||||
_, err := ioutil.ReadAll(res.Body)
|
_, err := ioutil.ReadAll(res.Body)
|
||||||
if err != nil {
|
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
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,16 +200,14 @@ func postTransfer(c *gin.Context) {
|
|||||||
archivePath := filepath.Join(config.Get().System.ArchiveDirectory, serverID+".tar.gz")
|
archivePath := filepath.Join(config.Get().System.ArchiveDirectory, serverID+".tar.gz")
|
||||||
|
|
||||||
// Check if the archive already exists and delete it if it does.
|
// Check if the archive already exists and delete it if it does.
|
||||||
_, err = os.Stat(archivePath)
|
if _, err = os.Stat(archivePath); err != nil {
|
||||||
if err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
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
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := os.Remove(archivePath); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,8 +215,7 @@ func postTransfer(c *gin.Context) {
|
|||||||
// Create the file.
|
// Create the file.
|
||||||
file, err := os.Create(archivePath)
|
file, err := os.Create(archivePath)
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,24 +223,35 @@ func postTransfer(c *gin.Context) {
|
|||||||
buf := make([]byte, 1024*4)
|
buf := make([]byte, 1024*4)
|
||||||
_, err = io.CopyBuffer(file, res.Body, buf)
|
_, err = io.CopyBuffer(file, res.Body, buf)
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the file so it can be opened to verify the checksum.
|
// Close the file so it can be opened to verify the checksum.
|
||||||
if err := file.Close(); err != nil {
|
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
|
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.
|
// Open the archive file for computing a checksum.
|
||||||
file, err = os.Open(archivePath)
|
file, err = os.Open(archivePath)
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +259,7 @@ func postTransfer(c *gin.Context) {
|
|||||||
hash := sha256.New()
|
hash := sha256.New()
|
||||||
buf = make([]byte, 1024*4)
|
buf = make([]byte, 1024*4)
|
||||||
if _, err := io.CopyBuffer(hash, file, buf); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +271,7 @@ func postTransfer(c *gin.Context) {
|
|||||||
|
|
||||||
// Close the file.
|
// Close the file.
|
||||||
if err := file.Close(); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,7 +287,7 @@ func postTransfer(c *gin.Context) {
|
|||||||
// Create a new server installer (note this does not execute the install script)
|
// Create a new server installer (note this does not execute the install script)
|
||||||
i, err := installer.New(serverData)
|
i, err := installer.New(serverData)
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,9 +300,9 @@ func postTransfer(c *gin.Context) {
|
|||||||
return
|
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 {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,12 +317,11 @@ func postTransfer(c *gin.Context) {
|
|||||||
err = api.New().SendTransferSuccess(serverID)
|
err = api.New().SendTransferSuccess(serverID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !api.IsRequestError(err) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
l.WithField("error", err.Error()).Error("panel responded with error after transfer success")
|
l.WithField("error", err.Error()).Error("panel responded with error after transfer success")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ package websocket
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/gbrlsnchs/jwt/v3"
|
"github.com/gbrlsnchs/jwt/v3"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
"github.com/pterodactyl/wings/environment/docker"
|
"github.com/pterodactyl/wings/environment/docker"
|
||||||
@@ -32,19 +32,18 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
sync.RWMutex
|
sync.RWMutex `json:"-"`
|
||||||
|
Connection *websocket.Conn `json:"-"`
|
||||||
Connection *websocket.Conn
|
jwt *tokens.WebsocketPayload
|
||||||
jwt *tokens.WebsocketPayload `json:"-"`
|
|
||||||
server *server.Server
|
server *server.Server
|
||||||
uuid uuid.UUID
|
uuid uuid.UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrJwtNotPresent = errors.Sentinel("jwt: no jwt present")
|
ErrJwtNotPresent = errors.New("jwt: no jwt present")
|
||||||
ErrJwtNoConnectPerm = errors.Sentinel("jwt: missing connect permission")
|
ErrJwtNoConnectPerm = errors.New("jwt: missing connect permission")
|
||||||
ErrJwtUuidMismatch = errors.Sentinel("jwt: server uuid mismatch")
|
ErrJwtUuidMismatch = errors.New("jwt: server uuid mismatch")
|
||||||
ErrJwtOnDenylist = errors.Sentinel("jwt: created too far in past (denylist)")
|
ErrJwtOnDenylist = errors.New("jwt: created too far in past (denylist)")
|
||||||
)
|
)
|
||||||
|
|
||||||
func IsJwtError(err error) bool {
|
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()
|
u, err := uuid.NewRandom()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Handler{
|
return &Handler{
|
||||||
@@ -130,7 +129,6 @@ func (h *Handler) SendJson(v *Message) error {
|
|||||||
Event: JwtErrorEvent,
|
Event: JwtErrorEvent,
|
||||||
Args: []string{err.Error()},
|
Args: []string{err.Error()},
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,8 +217,11 @@ func (h *Handler) SendErrorJson(msg Message, err error, shouldLog ...bool) error
|
|||||||
Event: ErrorEvent,
|
Event: ErrorEvent,
|
||||||
Args: []string{"an unexpected error was encountered while handling this request"},
|
Args: []string{"an unexpected error was encountered while handling this request"},
|
||||||
}
|
}
|
||||||
|
|
||||||
if isJWTError || (j != nil && j.HasPermission(PermissionReceiveErrors)) {
|
if isJWTError || (j != nil && j.HasPermission(PermissionReceiveErrors)) {
|
||||||
|
if isJWTError {
|
||||||
wsm.Event = JwtErrorEvent
|
wsm.Event = JwtErrorEvent
|
||||||
|
}
|
||||||
wsm.Args = []string{err.Error()}
|
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)) {
|
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}).
|
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)
|
return h.unsafeSendJson(wsm)
|
||||||
@@ -267,7 +268,6 @@ func (h *Handler) HandleInbound(m Message) error {
|
|||||||
Event: JwtErrorEvent,
|
Event: JwtErrorEvent,
|
||||||
Args: []string{err.Error()},
|
Args: []string{err.Error()},
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -311,7 +311,7 @@ func (h *Handler) HandleInbound(m Message) error {
|
|||||||
|
|
||||||
// On every authentication event, send the current server status back
|
// On every authentication event, send the current server status back
|
||||||
// to the client. :)
|
// to the client. :)
|
||||||
state := h.server.GetState()
|
state := h.server.Environment.State()
|
||||||
h.SendJson(&Message{
|
h.SendJson(&Message{
|
||||||
Event: server.StatusEvent,
|
Event: server.StatusEvent,
|
||||||
Args: []string{state},
|
Args: []string{state},
|
||||||
@@ -398,7 +398,7 @@ func (h *Handler) HandleInbound(m Message) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.server.GetState() == environment.ProcessOfflineState {
|
if h.server.Environment.State() == environment.ProcessOfflineState {
|
||||||
return nil
|
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.
|
// so that we can better handle this and only set the environment to booted once we're attached.
|
||||||
//
|
//
|
||||||
// Or maybe just an IsBooted function?
|
// 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, ok := h.server.Environment.(*docker.Environment); ok {
|
||||||
if !e.IsAttached() {
|
if !e.IsAttached() {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"github.com/mholt/archiver/v3"
|
"github.com/mholt/archiver/v3"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/server/filesystem"
|
"github.com/pterodactyl/wings/server/filesystem"
|
||||||
"io"
|
"io"
|
||||||
@@ -41,7 +41,7 @@ func (a *Archiver) Exists() bool {
|
|||||||
func (a *Archiver) Stat() (*filesystem.Stat, error) {
|
func (a *Archiver) Stat() (*filesystem.Stat, error) {
|
||||||
s, err := os.Stat(a.Path())
|
s, err := os.Stat(a.Path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &filesystem.Stat{
|
return &filesystem.Stat{
|
||||||
@@ -58,7 +58,7 @@ func (a *Archiver) Archive() error {
|
|||||||
var files []string
|
var files []string
|
||||||
fileInfo, err := ioutil.ReadDir(path)
|
fileInfo, err := ioutil.ReadDir(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range fileInfo {
|
for _, file := range fileInfo {
|
||||||
@@ -94,17 +94,17 @@ func (a *Archiver) DeleteIfExists() error {
|
|||||||
return nil
|
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.
|
// Checksum computes a SHA256 checksum of the server's archive.
|
||||||
func (a *Archiver) Checksum() (string, error) {
|
func (a *Archiver) Checksum() (string, error) {
|
||||||
file, err := os.Open(a.Path())
|
file, err := os.Open(a.Path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.WithStack(err)
|
return "", err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ func (a *Archiver) Checksum() (string, error) {
|
|||||||
|
|
||||||
buf := make([]byte, 1024*4)
|
buf := make([]byte, 1024*4)
|
||||||
if _, err := io.CopyBuffer(hash, file, buf); err != nil {
|
if _, err := io.CopyBuffer(hash, file, buf); err != nil {
|
||||||
return "", errors.WithStack(err)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return hex.EncodeToString(hash.Sum(nil)), nil
|
return hex.EncodeToString(hash.Sum(nil)), nil
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/server/backup"
|
"github.com/pterodactyl/wings/server/backup"
|
||||||
"os"
|
"os"
|
||||||
@@ -13,15 +13,14 @@ import (
|
|||||||
// Notifies the panel of a backup's state and returns an error if one is encountered
|
// Notifies the panel of a backup's state and returns an error if one is encountered
|
||||||
// while performing this action.
|
// while performing this action.
|
||||||
func (s *Server) notifyPanelOfBackup(uuid string, ad *backup.ArchiveDetails, successful bool) error {
|
func (s *Server) notifyPanelOfBackup(uuid string, ad *backup.ArchiveDetails, successful bool) error {
|
||||||
err := api.New().SendBackupStatus(uuid, ad.ToRequest(successful))
|
if err := api.New().SendBackupStatus(uuid, ad.ToRequest(successful)); err != nil {
|
||||||
if err != nil {
|
|
||||||
if !api.IsRequestError(err) {
|
if !api.IsRequestError(err) {
|
||||||
s.Log().WithFields(log.Fields{
|
s.Log().WithFields(log.Fields{
|
||||||
"backup": uuid,
|
"backup": uuid,
|
||||||
"error": err,
|
"error": err,
|
||||||
}).Error("failed to notify panel of backup status due to wings error")
|
}).Error("failed to notify panel of backup status due to wings error")
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New(err.Error())
|
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"))
|
f, err := os.Open(path.Join(s.Filesystem().Path(), ".pteroignore"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
return nil, errors.WithStack(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scanner := bufio.NewScanner(f)
|
scanner := bufio.NewScanner(f)
|
||||||
@@ -49,7 +48,7 @@ func (s *Server) getServerwideIgnoredFiles() ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
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.
|
// Get the included files based on the root path and the ignored files provided.
|
||||||
inc, err := s.GetIncludedBackupFiles(b.Ignored())
|
inc, err := s.GetIncludedBackupFiles(b.Ignored())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ad, err := b.Generate(inc, s.Filesystem().Path())
|
ad, err := b.Generate(inc, s.Filesystem().Path())
|
||||||
@@ -89,6 +88,11 @@ func (s *Server) Backup(b backup.BackupInterface) error {
|
|||||||
"backup": b.Identifier(),
|
"backup": b.Identifier(),
|
||||||
"error": notifyError,
|
"error": notifyError,
|
||||||
}).Warn("failed to notify panel of failed backup state")
|
}).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{}{
|
s.Events().PublishJson(BackupCompletedEvent+":"+b.Identifier(), map[string]interface{}{
|
||||||
@@ -99,15 +103,17 @@ func (s *Server) Backup(b backup.BackupInterface) error {
|
|||||||
"file_size": 0,
|
"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
|
// 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.
|
// 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 {
|
if notifyError := s.notifyPanelOfBackup(b.Identifier(), ad, true); notifyError != nil {
|
||||||
b.Remove()
|
b.Remove()
|
||||||
|
s.Log().WithField("error", notifyError).Info("failed to notify panel of successful backup state")
|
||||||
return errors.WithStackIf(err)
|
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
|
// Emit an event over the socket so we can update the backup in realtime on
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package backup
|
|||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
gzip "github.com/klauspost/pgzip"
|
gzip "github.com/klauspost/pgzip"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/remeh/sizedwaitgroup"
|
"github.com/remeh/sizedwaitgroup"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"io"
|
"io"
|
||||||
@@ -26,7 +26,7 @@ type Archive struct {
|
|||||||
func (a *Archive) Create(dst string, ctx context.Context) error {
|
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)
|
f, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ func (a *Archive) Create(dst string, ctx context.Context) error {
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return errors.WithStackIf(ctx.Err())
|
return ctx.Err()
|
||||||
default:
|
default:
|
||||||
return a.addToArchive(p, tw)
|
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")
|
log.WithField("location", dst).Warn("failed to delete corrupted backup archive")
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -91,7 +91,7 @@ func (a *Archive) addToArchive(p string, w *tar.Writer) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
@@ -102,17 +102,15 @@ func (a *Archive) addToArchive(p string, w *tar.Writer) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
header := &tar.Header{
|
name := strings.TrimPrefix(p, a.TrimPrefix)
|
||||||
// Trim the long server path from the name of the file so that the resulting
|
header, err := tar.FileInfoHeader(s, name)
|
||||||
// archive is exactly how the user would see it in the panel file manager.
|
if err != nil {
|
||||||
Name: strings.TrimPrefix(p, a.TrimPrefix),
|
return errors.WithMessage(err, "failed to get tar#FileInfoHeader for "+name)
|
||||||
Size: s.Size(),
|
|
||||||
Mode: int64(s.Mode()),
|
|
||||||
ModTime: s.ModTime(),
|
|
||||||
}
|
}
|
||||||
|
header.Name = name
|
||||||
|
|
||||||
// These actions must occur sequentially, even if this function is called multiple
|
// These actions must occur sequentially, even if this function is called multiple
|
||||||
// in parallel. You'll get some nasty panic's otherwise.
|
// 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()
|
defer a.Unlock()
|
||||||
|
|
||||||
if err := w.WriteHeader(header); err != nil {
|
if err := w.WriteHeader(header); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := make([]byte, 4*1024)
|
buf := make([]byte, 4*1024)
|
||||||
if _, err := io.CopyBuffer(w, f, buf); err != nil {
|
if _, err := io.CopyBuffer(w, io.LimitReader(f, header.Size), buf); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return errors.WithMessage(err, "failed to copy "+header.Name+" to archive")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package backup
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
@@ -87,7 +86,7 @@ func (b *Backup) Path() string {
|
|||||||
func (b *Backup) Size() (int64, error) {
|
func (b *Backup) Size() (int64, error) {
|
||||||
st, err := os.Stat(b.Path())
|
st, err := os.Stat(b.Path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, errors.WithStackIf(err)
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return st.Size(), nil
|
return st.Size(), nil
|
||||||
@@ -99,7 +98,7 @@ func (b *Backup) Checksum() ([]byte, error) {
|
|||||||
|
|
||||||
f, err := os.Open(b.Path())
|
f, err := os.Open(b.Path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
@@ -128,6 +127,7 @@ func (b *Backup) Details() *ArchiveDetails {
|
|||||||
"backup": b.Identifier(),
|
"backup": b.Identifier(),
|
||||||
"error": err,
|
"error": err,
|
||||||
}).Error("failed to calculate checksum for backup")
|
}).Error("failed to calculate checksum for backup")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
checksum = hex.EncodeToString(resp)
|
checksum = hex.EncodeToString(resp)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package backup
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ func LocateLocal(uuid string) (*LocalBackup, os.FileInfo, error) {
|
|||||||
|
|
||||||
st, err := os.Stat(b.Path())
|
st, err := os.Stat(b.Path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.WithStackIf(err)
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if st.IsDir() {
|
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 {
|
if err := a.Create(b.Path(), context.Background()); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.Details(), nil
|
return b.Details(), nil
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package backup
|
package backup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package backup
|
package backup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
@@ -11,7 +9,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type S3Backup struct {
|
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 {
|
if err := a.Create(s.Path(), context.Background()); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rc, err := os.Open(s.Path())
|
rc, err := os.Open(s.Path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rc.Close()
|
defer rc.Close()
|
||||||
|
|
||||||
if err := s.generateRemoteRequest(rc); err != nil {
|
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.
|
// Removes a backup from the system.
|
||||||
@@ -76,10 +73,12 @@ func (s *S3Backup) generateRemoteRequest(rc io.ReadCloser) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
l := log.WithFields(log.Fields{
|
||||||
"backup_id": s.Uuid,
|
"backup_id": s.Uuid,
|
||||||
"adapter": "s3",
|
"adapter": "s3",
|
||||||
}).Info("attempting to upload backup..")
|
})
|
||||||
|
|
||||||
|
l.Info("attempting to upload backup..")
|
||||||
|
|
||||||
handlePart := func(part string, size int64) (string, error) {
|
handlePart := func(part string, size int64) (string, error) {
|
||||||
r, err := http.NewRequest(http.MethodPut, part, nil)
|
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")
|
r.Header.Add("Content-Type", "application/x-gzip")
|
||||||
|
|
||||||
// Limit the reader to the size of the part.
|
// 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,
|
// 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
|
// 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
|
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)
|
partCount := len(urls.Parts)
|
||||||
for i, part := range urls.Parts {
|
for i, part := range urls.Parts {
|
||||||
// Get the size for the current part.
|
// Get the size for the current part.
|
||||||
@@ -129,67 +124,13 @@ func (s *S3Backup) generateRemoteRequest(rc io.ReadCloser) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to upload the part.
|
// Attempt to upload the part.
|
||||||
etag, err := handlePart(part, partSize)
|
if _, err := handlePart(part, partSize); err != nil {
|
||||||
if err != nil {
|
l.WithField("part_id", part).WithError(err).Warn("failed to upload part")
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
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{
|
l.Info("backup has been successfully uploaded")
|
||||||
"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)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/colorstring"
|
"github.com/mitchellh/colorstring"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/system"
|
"github.com/pterodactyl/wings/system"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrTooMuchConsoleData = errors.Sentinel("console is outputting too much data")
|
var ErrTooMuchConsoleData = errors.New("console is outputting too much data")
|
||||||
|
|
||||||
type ConsoleThrottler struct {
|
type ConsoleThrottler struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -45,11 +45,10 @@ func (s *Server) handleServerCrash() error {
|
|||||||
// No point in doing anything here if the server isn't currently offline, there
|
// 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
|
// 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.
|
// 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 {
|
if !s.Config().CrashDetectionEnabled {
|
||||||
s.Log().Debug("server triggered crash detection but handler is disabled for server process")
|
s.Log().Debug("server triggered crash detection but handler is disabled for server process")
|
||||||
|
s.PublishConsoleOutputFromDaemon("Aborting automatic restart, crash detection is disabled for this instance.")
|
||||||
s.PublishConsoleOutputFromDaemon("Server detected as crashed; crash detection is disabled for this instance.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -57,14 +56,13 @@ func (s *Server) handleServerCrash() error {
|
|||||||
|
|
||||||
exitCode, oomKilled, err := s.Environment.ExitState()
|
exitCode, oomKilled, err := s.Environment.ExitState()
|
||||||
if err != nil {
|
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
|
// 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.
|
// 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")
|
s.Log().Debug("server exited with successful exit code; system is configured to not detect this as a crash")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,11 +71,14 @@ func (s *Server) handleServerCrash() error {
|
|||||||
s.PublishConsoleOutputFromDaemon(fmt.Sprintf("Out of memory: %t", oomKilled))
|
s.PublishConsoleOutputFromDaemon(fmt.Sprintf("Out of memory: %t", oomKilled))
|
||||||
|
|
||||||
c := s.crasher.LastCrashTime()
|
c := s.crasher.LastCrashTime()
|
||||||
// If the last crash time was within the last 60 seconds we do not want to perform
|
timeout := config.Get().System.CrashDetection.Timeout
|
||||||
// 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.")
|
|
||||||
|
|
||||||
|
// 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{}
|
return &crashTooFrequent{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import "emperror.dev/errors"
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
var ErrIsRunning = errors.Sentinel("server is running")
|
var ErrIsRunning = errors.New("server is running")
|
||||||
var ErrSuspended = errors.Sentinel("server is currently in a suspended state")
|
var ErrSuspended = errors.New("server is currently in a suspended state")
|
||||||
|
|
||||||
type crashTooFrequent struct {
|
type crashTooFrequent struct {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/pterodactyl/wings/server/filesystem"
|
"github.com/pterodactyl/wings/server/filesystem"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
@@ -13,12 +12,12 @@ func (s *Server) Filesystem() *filesystem.Filesystem {
|
|||||||
// Ensures that the data directory for the server instance exists.
|
// Ensures that the data directory for the server instance exists.
|
||||||
func (s *Server) EnsureDataDirectoryExists() error {
|
func (s *Server) EnsureDataDirectoryExists() error {
|
||||||
if _, err := os.Stat(s.fs.Path()); err != nil && !os.IsNotExist(err) {
|
if _, err := os.Stat(s.fs.Path()); err != nil && !os.IsNotExist(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
// Create the server data directory because it does not currently exist
|
// Create the server data directory because it does not currently exist
|
||||||
// on the system.
|
// on the system.
|
||||||
if err := os.MkdirAll(s.fs.Path(), 0700); err != nil {
|
if err := os.MkdirAll(s.fs.Path(), 0700); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.fs.Chown("/"); err != nil {
|
if err := s.fs.Chown("/"); err != nil {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package filesystem
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/karrick/godirwalk"
|
"github.com/karrick/godirwalk"
|
||||||
"github.com/pterodactyl/wings/server/backup"
|
"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...)
|
i, err := ignore.CompileIgnoreLines(ignored...)
|
||||||
if err != nil {
|
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
|
// 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
|
// 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), ":", "")))
|
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 {
|
if err := a.Create(d, context.Background()); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Stat(d)
|
f, err := os.Stat(d)
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import (
|
|||||||
"archive/tar"
|
"archive/tar"
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mholt/archiver/v3"
|
"github.com/mholt/archiver/v3"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -47,10 +47,10 @@ func (fs *Filesystem) SpaceAvailableForDecompression(dir string, file string) (b
|
|||||||
return false, ErrUnknownArchiveFormat
|
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
|
// 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 {
|
func (fs *Filesystem) DecompressFile(dir string, file string) error {
|
||||||
source, err := fs.SafePath(filepath.Join(dir, file))
|
source, err := fs.SafePath(filepath.Join(dir, file))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the file exists basically.
|
// Make sure the file exists basically.
|
||||||
if _, err := os.Stat(source); err != nil {
|
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
|
// 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))
|
p, err := fs.SafePath(filepath.Join(dir, name))
|
||||||
if err != nil {
|
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 err != nil {
|
||||||
if strings.HasPrefix(err.Error(), "format ") {
|
if strings.HasPrefix(err.Error(), "format ") {
|
||||||
return errors.WithStackIf(ErrUnknownArchiveFormat)
|
return ErrUnknownArchiveFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package filesystem
|
package filesystem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/karrick/godirwalk"
|
"github.com/karrick/godirwalk"
|
||||||
"sync"
|
"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
|
// 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.
|
// with a large amount of files.
|
||||||
func (fs *Filesystem) DiskUsage(allowStaleValue bool) (int64, error) {
|
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 !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
|
// 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.
|
// 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) {
|
func (fs *Filesystem) DirectorySize(dir string) (int64, error) {
|
||||||
d, err := fs.SafePath(dir)
|
d, err := fs.SafePath(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, errors.WithStackIf(err)
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var size int64
|
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.
|
// 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)
|
s, err := fs.DiskUsage(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s + size) > fs.MaxDisk() {
|
if (s + size) > fs.MaxDisk() {
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package filesystem
|
package filesystem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrIsDirectory = errors.Sentinel("filesystem: is a directory")
|
var ErrIsDirectory = errors.New("filesystem: is a directory")
|
||||||
var ErrNotEnoughDiskSpace = errors.Sentinel("filesystem: not enough disk space")
|
var ErrNotEnoughDiskSpace = errors.New("filesystem: not enough disk space")
|
||||||
var ErrUnknownArchiveFormat = errors.Sentinel("filesystem: unknown archive format")
|
var ErrUnknownArchiveFormat = errors.New("filesystem: unknown archive format")
|
||||||
|
|
||||||
type BadPathResolutionError struct {
|
type BadPathResolutionError struct {
|
||||||
path string
|
path string
|
||||||
@@ -23,6 +23,7 @@ func (b *BadPathResolutionError) Error() string {
|
|||||||
if r == "" {
|
if r == "" {
|
||||||
r = "<empty>"
|
r = "<empty>"
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("filesystem: server path [%s] resolves to a location outside the server root: %s", b.path, r)
|
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.
|
// 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 {
|
func (fs *Filesystem) handleWalkerError(err error, f os.FileInfo) error {
|
||||||
if !IsBadPathResolutionError(err) {
|
if !IsBadPathResolutionError(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if f != nil && f.IsDir() {
|
if f != nil && f.IsDir() {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package filesystem
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/gabriel-vasile/mimetype"
|
"github.com/gabriel-vasile/mimetype"
|
||||||
"github.com/karrick/godirwalk"
|
"github.com/karrick/godirwalk"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/system"
|
"github.com/pterodactyl/wings/system"
|
||||||
"io"
|
"io"
|
||||||
@@ -60,27 +60,27 @@ func (fs *Filesystem) Readfile(p string, w io.Writer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if st, err := os.Stat(cleaned); err != nil {
|
if st, err := os.Stat(cleaned); err != nil {
|
||||||
return errors.WithStack(err)
|
return err
|
||||||
} else if st.IsDir() {
|
} else if st.IsDir() {
|
||||||
return errors.WithStack(ErrIsDirectory)
|
return ErrIsDirectory
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Open(cleaned)
|
f, err := os.Open(cleaned)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
_, err = bufio.NewReader(f).WriteTo(w)
|
_, 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.
|
// 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 {
|
func (fs *Filesystem) Writefile(p string, r io.Reader) error {
|
||||||
cleaned, err := fs.SafePath(p)
|
cleaned, err := fs.SafePath(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentSize int64
|
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.
|
// 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 stat, err := os.Stat(cleaned); err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Dir(cleaned), 0755); err != nil {
|
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 {
|
if err := fs.Chown(filepath.Dir(cleaned)); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if stat.IsDir() {
|
if stat.IsDir() {
|
||||||
return errors.WithStack(ErrIsDirectory)
|
return ErrIsDirectory
|
||||||
}
|
}
|
||||||
|
|
||||||
currentSize = stat.Size()
|
currentSize = stat.Size()
|
||||||
@@ -119,7 +119,7 @@ func (fs *Filesystem) Writefile(p string, r io.Reader) error {
|
|||||||
// truncate the existing file.
|
// truncate the existing file.
|
||||||
file, err := o.open(cleaned, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
file, err := o.open(cleaned, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
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 {
|
func (fs *Filesystem) CreateDirectory(name string, p string) error {
|
||||||
cleaned, err := fs.SafePath(path.Join(p, name))
|
cleaned, err := fs.SafePath(path.Join(p, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.MkdirAll(cleaned, 0755)
|
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 {
|
func (fs *Filesystem) Rename(from string, to string) error {
|
||||||
cleanedFrom, err := fs.SafePath(from)
|
cleanedFrom, err := fs.SafePath(from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanedTo, err := fs.SafePath(to)
|
cleanedTo, err := fs.SafePath(to)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the target file or directory already exists the rename function will fail, so just
|
// 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.
|
// we're not at the root directory level.
|
||||||
if d != fs.Path() {
|
if d != fs.Path() {
|
||||||
if mkerr := os.MkdirAll(d, 0755); mkerr != nil {
|
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 {
|
func (fs *Filesystem) Chown(path string) error {
|
||||||
cleaned, err := fs.SafePath(path)
|
cleaned, err := fs.SafePath(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if fs.isTest {
|
if fs.isTest {
|
||||||
@@ -197,7 +197,7 @@ func (fs *Filesystem) Chown(path string) error {
|
|||||||
|
|
||||||
// Start by just chowning the initial path that we received.
|
// Start by just chowning the initial path that we received.
|
||||||
if err := os.Chown(cleaned, uid, gid); err != nil {
|
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
|
// 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
|
// 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
|
// 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
|
// 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.
|
// does exist, we'll just continue to the next loop and try again.
|
||||||
if _, err := fs.Stat(path.Join(dir, n)); err != nil {
|
if _, err := fs.Stat(path.Join(dir, n)); err != nil {
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
return "", errors.WithStackIf(err)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
@@ -268,12 +285,12 @@ func (fs *Filesystem) findCopySuffix(dir string, name string, extension string)
|
|||||||
func (fs *Filesystem) Copy(p string) error {
|
func (fs *Filesystem) Copy(p string) error {
|
||||||
cleaned, err := fs.SafePath(p)
|
cleaned, err := fs.SafePath(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := os.Stat(cleaned)
|
s, err := os.Stat(cleaned)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
} else if s.IsDir() || !s.Mode().IsRegular() {
|
} else if s.IsDir() || !s.Mode().IsRegular() {
|
||||||
// If this is a directory or not a regular file, just throw a not-exist error
|
// 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.
|
// 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)
|
source, err := os.Open(cleaned)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package filesystem
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -26,7 +25,7 @@ func (fs *Filesystem) SafePath(p string) (string, error) {
|
|||||||
// is truly pointing to.
|
// is truly pointing to.
|
||||||
ep, err := filepath.EvalSymlinks(r)
|
ep, err := filepath.EvalSymlinks(r)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return "", errors.WithStackIf(err)
|
return "", err
|
||||||
} else if os.IsNotExist(err) {
|
} else if os.IsNotExist(err) {
|
||||||
// The requested directory doesn't exist, so at this point we need to iterate up the
|
// 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.
|
// 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.
|
// Block until all of the routines finish and have returned a value.
|
||||||
return cleaned, errors.WithStackIf(g.Wait())
|
return cleaned, g.Wait()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package filesystem
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"emperror.dev/errors"
|
|
||||||
. "github.com/franela/goblin"
|
. "github.com/franela/goblin"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package filesystem
|
package filesystem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/gabriel-vasile/mimetype"
|
"github.com/gabriel-vasile/mimetype"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ func (s *Stat) MarshalJSON() ([]byte, error) {
|
|||||||
Created string `json:"created"`
|
Created string `json:"created"`
|
||||||
Modified string `json:"modified"`
|
Modified string `json:"modified"`
|
||||||
Mode string `json:"mode"`
|
Mode string `json:"mode"`
|
||||||
|
ModeBits string `json:"mode_bits"`
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
Directory bool `json:"directory"`
|
Directory bool `json:"directory"`
|
||||||
File bool `json:"file"`
|
File bool `json:"file"`
|
||||||
@@ -29,6 +30,8 @@ func (s *Stat) MarshalJSON() ([]byte, error) {
|
|||||||
Created: s.CTime().Format(time.RFC3339),
|
Created: s.CTime().Format(time.RFC3339),
|
||||||
Modified: s.Info.ModTime().Format(time.RFC3339),
|
Modified: s.Info.ModTime().Format(time.RFC3339),
|
||||||
Mode: s.Info.Mode().String(),
|
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(),
|
Size: s.Info.Size(),
|
||||||
Directory: s.Info.IsDir(),
|
Directory: s.Info.IsDir(),
|
||||||
File: !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) {
|
func (fs *Filesystem) unsafeStat(p string) (*Stat, error) {
|
||||||
s, err := os.Stat(p)
|
s, err := os.Stat(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var m *mimetype.MIME
|
var m *mimetype.MIME
|
||||||
if !s.IsDir() {
|
if !s.IsDir() {
|
||||||
m, err = mimetype.DetectFile(p)
|
m, err = mimetype.DetectFile(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/mount"
|
"github.com/docker/docker/api/types/mount"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"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
|
// 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.
|
// does not touch any existing files for the server, other than what the script modifies.
|
||||||
func (s *Server) Reinstall() error {
|
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")
|
s.Log().Debug("waiting for server instance to enter a stopped state")
|
||||||
if err := s.Environment.WaitForStop(10, true); err != nil {
|
if err := s.Environment.WaitForStop(10, true); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -90,7 +91,7 @@ func (s *Server) internalInstall() error {
|
|||||||
script, err := api.New().GetInstallationScript(s.Id())
|
script, err := api.New().GetInstallationScript(s.Id())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !api.IsRequestError(err) {
|
if !api.IsRequestError(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New(err.Error())
|
return errors.New(err.Error())
|
||||||
@@ -98,7 +99,7 @@ func (s *Server) internalInstall() error {
|
|||||||
|
|
||||||
p, err := NewInstallationProcess(s, &script)
|
p, err := NewInstallationProcess(s, &script)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Log().Info("beginning installation process for server")
|
s.Log().Info("beginning installation process for server")
|
||||||
@@ -130,7 +131,7 @@ func NewInstallationProcess(s *Server, script *api.InstallationScript) (*Install
|
|||||||
s.installer.cancel = &cancel
|
s.installer.cancel = &cancel
|
||||||
|
|
||||||
if c, err := environment.DockerClient(); err != nil {
|
if c, err := environment.DockerClient(); err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
proc.client = c
|
proc.client = c
|
||||||
proc.context = ctx
|
proc.context = ctx
|
||||||
@@ -193,7 +194,7 @@ func (ip *InstallationProcess) RemoveContainer() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil && !client.IsErrNotFound(err) {
|
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 {
|
if err := ip.BeforeExecute(); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cid, err := ip.Execute()
|
cid, err := ip.Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ip.RemoveContainer()
|
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
|
// 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
|
// 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.
|
// ioutil.TempDir call expects this base to exist, it won't create it for you.
|
||||||
if err := os.MkdirAll(ip.tempDir(), 0700); err != nil {
|
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)
|
f, err := os.OpenFile(filepath.Join(ip.tempDir(), "install.sh"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||||
if err != nil {
|
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()
|
defer f.Close()
|
||||||
|
|
||||||
@@ -265,7 +266,7 @@ func (ip *InstallationProcess) writeScriptToDisk() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Flush()
|
w.Flush()
|
||||||
@@ -275,11 +276,62 @@ func (ip *InstallationProcess) writeScriptToDisk() error {
|
|||||||
|
|
||||||
// Pulls the docker image to be used for the installation container.
|
// Pulls the docker image to be used for the installation container.
|
||||||
func (ip *InstallationProcess) pullInstallationImage() error {
|
func (ip *InstallationProcess) pullInstallationImage() error {
|
||||||
r, err := ip.client.ImagePull(ip.context, ip.Script.ContainerImage, types.ImagePullOptions{})
|
// Get a registry auth configuration from the config.
|
||||||
if err != nil {
|
var registryAuth *config.RegistryConfiguration
|
||||||
return errors.WithStackIf(err)
|
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.
|
// Block continuation until the image has been pulled successfully.
|
||||||
scanner := bufio.NewScanner(r)
|
scanner := bufio.NewScanner(r)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
@@ -287,7 +339,7 @@ func (ip *InstallationProcess) pullInstallationImage() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -298,11 +350,11 @@ func (ip *InstallationProcess) pullInstallationImage() error {
|
|||||||
// manner, if either one fails the error is returned.
|
// manner, if either one fails the error is returned.
|
||||||
func (ip *InstallationProcess) BeforeExecute() error {
|
func (ip *InstallationProcess) BeforeExecute() error {
|
||||||
if err := ip.writeScriptToDisk(); err != nil {
|
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 {
|
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{
|
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 err := ip.client.ContainerRemove(ip.context, ip.Server.Id()+"_installer", opts); err != nil {
|
||||||
if !client.IsErrNotFound(err) {
|
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) {
|
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)
|
f, err := os.OpenFile(ip.GetLogPath(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
@@ -372,15 +424,15 @@ func (ip *InstallationProcess) AfterExecute(containerId string) error {
|
|||||||
| ------------------------------
|
| ------------------------------
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tmpl.Execute(f, ip); err != nil {
|
if err := tmpl.Execute(f, ip); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := io.Copy(f, reader); err != nil {
|
if _, err := io.Copy(f, reader); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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")
|
r, err := ip.client.ContainerCreate(ip.context, conf, hostConf, nil, ip.Server.Id()+"_installer")
|
||||||
if err != nil {
|
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")
|
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 {
|
select {
|
||||||
case err := <-eChan:
|
case err := <-eChan:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.WithStackIf(err)
|
return "", err
|
||||||
}
|
}
|
||||||
case <-sChan:
|
case <-sChan:
|
||||||
}
|
}
|
||||||
@@ -487,7 +539,7 @@ func (ip *InstallationProcess) StreamOutput(id string) error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
@@ -500,7 +552,7 @@ func (ip *InstallationProcess) StreamOutput(id string) error {
|
|||||||
if err := s.Err(); err != nil {
|
if err := s.Err(); err != nil {
|
||||||
ip.Server.Log().WithFields(log.Fields{
|
ip.Server.Log().WithFields(log.Fields{
|
||||||
"container_id": id,
|
"container_id": id,
|
||||||
"error": errors.WithStackIf(err),
|
"error": err,
|
||||||
}).Warn("error processing scanner line in installation output for server")
|
}).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)
|
err := api.New().SendInstallationStatus(s.Id(), successful)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !api.IsRequestError(err) {
|
if !api.IsRequestError(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New(err.Error())
|
return errors.New(err.Error())
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
@@ -77,7 +76,7 @@ func (s *Server) StartEventListeners() {
|
|||||||
s.Environment.SetState(environment.ProcessRunningState)
|
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) {
|
stats := func(e events.Event) {
|
||||||
st := new(environment.Stats)
|
st := new(environment.Stats)
|
||||||
if err := json.Unmarshal([]byte(e.Data), st); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +151,7 @@ func (s *Server) onConsoleOutput(data string) {
|
|||||||
processConfiguration := s.ProcessConfiguration()
|
processConfiguration := s.ProcessConfiguration()
|
||||||
|
|
||||||
// Check if the server is currently starting.
|
// 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.
|
// Check if we should strip ansi color codes.
|
||||||
if processConfiguration.Startup.StripAnsi {
|
if processConfiguration.Startup.StripAnsi {
|
||||||
// Strip ansi color codes from the data string.
|
// Strip ansi color codes from the data string.
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/creasty/defaults"
|
"github.com/creasty/defaults"
|
||||||
"github.com/gammazero/workerpool"
|
"github.com/gammazero/workerpool"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
@@ -35,7 +35,7 @@ func LoadDirectory() error {
|
|||||||
configs, err := api.New().GetServers()
|
configs, err := api.New().GetServers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !api.IsRequestError(err) {
|
if !api.IsRequestError(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New(err.Error())
|
return errors.New(err.Error())
|
||||||
@@ -89,12 +89,12 @@ func LoadDirectory() error {
|
|||||||
func FromConfiguration(data api.ServerConfigurationResponse) (*Server, error) {
|
func FromConfiguration(data api.ServerConfigurationResponse) (*Server, error) {
|
||||||
cfg := Configuration{}
|
cfg := Configuration{}
|
||||||
if err := defaults.Set(&cfg); err != nil {
|
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)
|
s := new(Server)
|
||||||
if err := defaults.Set(s); err != nil {
|
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
|
s.cfg = cfg
|
||||||
|
|||||||
@@ -49,7 +49,9 @@ func (s *Server) customMounts() []environment.Mount {
|
|||||||
|
|
||||||
mounted := false
|
mounted := false
|
||||||
for _, allowed := range config.Get().AllowedMounts {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
"github.com/pterodactyl/wings/server/filesystem"
|
"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
|
// time than that passes an error will be propagated back up the chain and this
|
||||||
// request will be aborted.
|
// request will be aborted.
|
||||||
if err := s.powerLock.Acquire(ctx, 1); err != nil {
|
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 {
|
} else {
|
||||||
// If no wait duration was provided we will attempt to immediately acquire the lock
|
// 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.
|
// and bail out with a context deadline error if it is not acquired immediately.
|
||||||
if ok := s.powerLock.TryAcquire(1); !ok {
|
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 {
|
switch action {
|
||||||
case PowerActionStart:
|
case PowerActionStart:
|
||||||
if s.GetState() != environment.ProcessOfflineState {
|
if s.Environment.State() != environment.ProcessOfflineState {
|
||||||
return ErrIsRunning
|
return ErrIsRunning
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ func (s *Server) HandlePowerAction(action PowerAction, waitSeconds ...int) error
|
|||||||
func (s *Server) onBeforeStart() error {
|
func (s *Server) onBeforeStart() error {
|
||||||
s.Log().Info("syncing server configuration with panel")
|
s.Log().Info("syncing server configuration with panel")
|
||||||
if err := s.Sync(); err != nil {
|
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
|
// 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...")
|
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.
|
// Ensure all of the server file permissions are set correctly before booting the process.
|
||||||
if err := s.Filesystem().Chown("/"); err != nil {
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
@@ -115,7 +115,7 @@ func (s *Server) Sync() error {
|
|||||||
cfg, err := api.New().GetServerConfiguration(s.Id())
|
cfg, err := api.New().GetServerConfiguration(s.Id())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !api.IsRequestError(err) {
|
if !api.IsRequestError(err) {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err.(*api.RequestError).Status == "404" {
|
if err.(*api.RequestError).Status == "404" {
|
||||||
@@ -131,7 +131,7 @@ func (s *Server) Sync() error {
|
|||||||
func (s *Server) SyncWithConfiguration(cfg api.ServerConfigurationResponse) error {
|
func (s *Server) SyncWithConfiguration(cfg api.ServerConfigurationResponse) error {
|
||||||
// Update the data structure and persist it to the disk.
|
// Update the data structure and persist it to the disk.
|
||||||
if err := s.UpdateDataStructure(cfg.Settings); err != nil {
|
if err := s.UpdateDataStructure(cfg.Settings); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Lock()
|
s.Lock()
|
||||||
@@ -171,7 +171,7 @@ func (s *Server) IsBootable() bool {
|
|||||||
func (s *Server) CreateEnvironment() error {
|
func (s *Server) CreateEnvironment() error {
|
||||||
// Ensure the data directory exists before getting too far through this process.
|
// Ensure the data directory exists before getting too far through this process.
|
||||||
if err := s.EnsureDataDirectoryExists(); err != nil {
|
if err := s.EnsureDataDirectoryExists(); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.Environment.Create()
|
return s.Environment.Create()
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
@@ -22,14 +21,14 @@ func CachedServerStates() (map[string]string, error) {
|
|||||||
// Open the states file.
|
// Open the states file.
|
||||||
f, err := os.OpenFile(config.Get().System.GetStatesPath(), os.O_RDONLY|os.O_CREATE, 0644)
|
f, err := os.OpenFile(config.Get().System.GetStatesPath(), os.O_RDONLY|os.O_CREATE, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
// Convert the json object to a map.
|
// Convert the json object to a map.
|
||||||
states := map[string]string{}
|
states := map[string]string{}
|
||||||
if err := json.NewDecoder(f).Decode(&states); err != nil && err != io.EOF {
|
if err := json.NewDecoder(f).Decode(&states); err != nil && err != io.EOF {
|
||||||
return nil, errors.WithStackIf(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return states, nil
|
return states, nil
|
||||||
@@ -40,13 +39,13 @@ func saveServerStates() error {
|
|||||||
// Get the states of all servers on the daemon.
|
// Get the states of all servers on the daemon.
|
||||||
states := map[string]string{}
|
states := map[string]string{}
|
||||||
for _, s := range GetServers().All() {
|
for _, s := range GetServers().All() {
|
||||||
states[s.Id()] = s.GetState()
|
states[s.Id()] = s.Environment.State()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the map to a json object.
|
// Convert the map to a json object.
|
||||||
data, err := json.Marshal(states)
|
data, err := json.Marshal(states)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
stateMutex.Lock()
|
stateMutex.Lock()
|
||||||
@@ -54,7 +53,7 @@ func saveServerStates() error {
|
|||||||
|
|
||||||
// Write the data to the file
|
// Write the data to the file
|
||||||
if err := ioutil.WriteFile(config.Get().System.GetStatesPath(), data, 0644); err != nil {
|
if err := ioutil.WriteFile(config.Get().System.GetStatesPath(), data, 0644); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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
|
// 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
|
// separate thread as to not block any actions currently taking place in the flow
|
||||||
// that called this function.
|
// 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")
|
s.Log().Info("detected server as entering a crashed state; running crash handler")
|
||||||
|
|
||||||
go func(server *Server) {
|
go func(server *Server) {
|
||||||
@@ -115,6 +114,7 @@ func (s *Server) OnStateChange() {
|
|||||||
if IsTooFrequentCrashError(err) {
|
if IsTooFrequentCrashError(err) {
|
||||||
server.Log().Info("did not restart server after crash; occurred too soon after the last")
|
server.Log().Info("did not restart server after crash; occurred too soon after the last")
|
||||||
} else {
|
} 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")
|
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
|
// environment state, it is simply the tracked state from this daemon instance, and
|
||||||
// not the response from Docker.
|
// not the response from Docker.
|
||||||
func (s *Server) IsRunning() bool {
|
func (s *Server) IsRunning() bool {
|
||||||
st := s.GetState()
|
st := s.Environment.State()
|
||||||
|
|
||||||
return st == environment.ProcessRunningState || st == environment.ProcessStartingState
|
return st == environment.ProcessRunningState || st == environment.ProcessStartingState
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/buger/jsonparser"
|
"github.com/buger/jsonparser"
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
func (s *Server) UpdateDataStructure(data []byte) error {
|
func (s *Server) UpdateDataStructure(data []byte) error {
|
||||||
src := new(Configuration)
|
src := new(Configuration)
|
||||||
if err := json.Unmarshal(data, src); err != nil {
|
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
|
// 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
|
// 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.
|
// and then save it to the disk so it is persistent.
|
||||||
if err := mergo.Merge(&c, src, mergo.WithOverride); err != nil {
|
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
|
// 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.
|
// request is going to be boolean. Allegedly.
|
||||||
if v, err := jsonparser.GetBoolean(data, "container", "oom_disabled"); err != nil {
|
if v, err := jsonparser.GetBoolean(data, "container", "oom_disabled"); err != nil {
|
||||||
if err != jsonparser.KeyPathNotFoundError {
|
if err != jsonparser.KeyPathNotFoundError {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c.Build.OOMDisabled = v
|
c.Build.OOMDisabled = v
|
||||||
@@ -74,7 +74,7 @@ func (s *Server) UpdateDataStructure(data []byte) error {
|
|||||||
// Mergo also cannot handle this boolean value.
|
// Mergo also cannot handle this boolean value.
|
||||||
if v, err := jsonparser.GetBoolean(data, "suspended"); err != nil {
|
if v, err := jsonparser.GetBoolean(data, "suspended"); err != nil {
|
||||||
if err != jsonparser.KeyPathNotFoundError {
|
if err != jsonparser.KeyPathNotFoundError {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c.Suspended = v
|
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 v, err := jsonparser.GetBoolean(data, "skip_egg_scripts"); err != nil {
|
||||||
if err != jsonparser.KeyPathNotFoundError {
|
if err != jsonparser.KeyPathNotFoundError {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c.SkipEggScripts = v
|
c.SkipEggScripts = v
|
||||||
@@ -143,7 +143,7 @@ func (s *Server) SyncWithEnvironment() {
|
|||||||
} else {
|
} else {
|
||||||
// Checks if the server is now in a suspended state. If so and a server process is currently running it
|
// 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).
|
// 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")
|
s.Log().Info("server suspended with running process state, terminating now")
|
||||||
|
|
||||||
go func(s *Server) {
|
go func(s *Server) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package sftp
|
package sftp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/patrickmn/go-cache"
|
"github.com/patrickmn/go-cache"
|
||||||
"github.com/pkg/sftp"
|
"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) {
|
if _, err := os.Stat(p); os.IsNotExist(err) {
|
||||||
return nil, sftp.ErrSshFxNoSuchFile
|
return nil, sftp.ErrSshFxNoSuchFile
|
||||||
} else if err != nil {
|
} 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
|
return nil, sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open(p)
|
file, err := os.Open(p)
|
||||||
if err != nil {
|
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
|
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 {
|
if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil {
|
||||||
l.WithFields(log.Fields{
|
l.WithFields(log.Fields{
|
||||||
"path": filepath.Dir(p),
|
"path": filepath.Dir(p),
|
||||||
"error": errors.WithStackIf(err),
|
"error": err,
|
||||||
}).Error("error making path for file")
|
}).Error("error making path for file")
|
||||||
|
|
||||||
return nil, sftp.ErrSshFxFailure
|
return nil, sftp.ErrSshFxFailure
|
||||||
@@ -116,7 +115,7 @@ func (fs FileSystem) Filewrite(request *sftp.Request) (io.WriterAt, error) {
|
|||||||
|
|
||||||
file, err := os.Create(p)
|
file, err := os.Create(p)
|
||||||
if err != nil {
|
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
|
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
|
// Not failing here is intentional. We still made the file, it is just owned incorrectly
|
||||||
// and will likely cause some issues.
|
// and will likely cause some issues.
|
||||||
if err := os.Chown(p, fs.User.Uid, fs.User.Gid); err != nil {
|
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
|
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
|
// 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.
|
// at play and we need to go ahead and bail out of the process.
|
||||||
if statErr != nil {
|
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
|
return nil, sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
@@ -159,14 +158,14 @@ func (fs FileSystem) Filewrite(request *sftp.Request) (io.WriterAt, error) {
|
|||||||
return nil, sftp.ErrSSHFxNoSuchFile
|
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
|
return nil, sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not failing here is intentional. We still made the file, it is just owned incorrectly
|
// Not failing here is intentional. We still made the file, it is just owned incorrectly
|
||||||
// and will likely cause some issues.
|
// and will likely cause some issues.
|
||||||
if err := os.Chown(p, fs.User.Uid, fs.User.Gid); err != nil {
|
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
|
return file, nil
|
||||||
@@ -220,7 +219,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
|
|||||||
return sftp.ErrSSHFxNoSuchFile
|
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 sftp.ErrSSHFxFailure
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -234,7 +233,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
|
|||||||
return sftp.ErrSSHFxNoSuchFile
|
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
|
return sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
@@ -246,7 +245,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := os.RemoveAll(p); err != nil {
|
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
|
return sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
@@ -258,7 +257,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(p, 0755); err != nil {
|
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
|
return sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
@@ -270,7 +269,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Symlink(p, target); err != nil {
|
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
|
return sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
@@ -286,7 +285,7 @@ func (fs FileSystem) Filecmd(request *sftp.Request) error {
|
|||||||
return sftp.ErrSSHFxNoSuchFile
|
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
|
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
|
// 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.
|
// 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 {
|
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
|
return sftp.ErrSshFxOk
|
||||||
@@ -327,7 +326,7 @@ func (fs FileSystem) Filelist(request *sftp.Request) (sftp.ListerAt, error) {
|
|||||||
|
|
||||||
files, err := ioutil.ReadDir(p)
|
files, err := ioutil.ReadDir(p)
|
||||||
if err != nil {
|
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
|
return nil, sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
@@ -342,7 +341,7 @@ func (fs FileSystem) Filelist(request *sftp.Request) (sftp.ListerAt, error) {
|
|||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return nil, sftp.ErrSshFxNoSuchFile
|
return nil, sftp.ErrSshFxNoSuchFile
|
||||||
} else if err != nil {
|
} 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
|
return nil, sftp.ErrSshFxFailure
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package sftp
|
package sftp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
@@ -28,14 +28,14 @@ func Initialize(config config.SystemConfiguration) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := New(s); err != nil {
|
if err := New(s); err != nil {
|
||||||
return errors.WithStackIf(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the SFTP server in a background thread since this is
|
// Initialize the SFTP server in a background thread since this is
|
||||||
// a long running operation.
|
// a long running operation.
|
||||||
go func(s *Server) {
|
go func(s *Server) {
|
||||||
if err := s.Initialize(); err != nil {
|
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)
|
}(s)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user