Compare commits

..

13 Commits

Author SHA1 Message Date
Dane Everitt
82b23ef638 Add support for automated docker build 2020-11-14 20:42:12 -08:00
Dane Everitt
d970ec35b7 Update dockerfile to build quicker 2020-11-14 19:22:29 -08:00
Dane Everitt
e2872e786e Tiny optimization for handling string output from server console 2020-11-12 21:41:35 -08:00
Dane Everitt
f81e35d960 Merge branch 'develop' of https://github.com/pterodactyl/wings into develop 2020-11-12 20:50:18 -08:00
Dane Everitt
672fb860ea Don't add a stack for deadline exceeded when terminating 2020-11-12 20:50:15 -08:00
Dane Everitt
8081c83de4 Allow disabling disk checking with 0 value for check interval 2020-11-12 20:50:03 -08:00
Dane Everitt
f379d0e54a Merge pull request #68 from gzzchh/develop
Turn off the CGO to make it static linked
2020-11-11 10:58:49 -08:00
Dane Everitt
ffb6bd72ef Don't accidentally reset a server's state when starting it after a system reboot; closes pterodactyl/panel#2695 2020-11-10 21:21:20 -08:00
Dane Everitt
488ef9de54 Merge branch 'develop' of https://github.com/pterodactyl/wings into develop 2020-11-10 20:36:42 -08:00
Dane Everitt
34349d4b48 Don't report a context cancelation as an error 2020-11-10 20:36:40 -08:00
Matthew Penner
2197d82957 Clean paths from AllowedMounts to ensure consistency 2020-11-09 16:58:01 -07:00
Dane Everitt
20ece60a72 Fix files not being editable correctly in the panel; closes pterodactyl/panel#2679 2020-11-08 17:06:40 -08:00
gzzchh
33e584b447 Turn off the CGO to make it static linked 2020-11-08 14:42:55 +08:00
16 changed files with 159 additions and 75 deletions

View File

@@ -1,12 +1,10 @@
name: "Build & Test"
name: Run Tests
on:
push:
branches-ignore:
- 'master'
- 'release/**'
pull_request:
jobs:
build:
strategy:
@@ -32,6 +30,7 @@ jobs:
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: 0
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

View File

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

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

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

View File

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

View File

@@ -1,5 +1,22 @@
# Changelog
## v1.1.2
### Fixed
* Fixes binaries built as part of the release process not being usable in MUSL based environments (such as our Docker images).
* Fixes server states being incorrectly set back to offline when a server is started after a system restart.
### Changed
* Improved logic for cleaning `allowed_mount` paths for consistency.
* Certain context cancelation deadline errors are no longer wrong reported at an error level (since they're expected).
* Very minor micro-optimizations for some string handling with server console output.
### Added
* Added a hidden option to disable all disk checking for servers by setting the `disk_check_interval` to `0` in the config file.
## v1.1.1
### Fixed
* Fixes certain files returning invalid data in the request due to a bad header set after sending data down the line.
## v1.1.0
This release **requires** `Panel@1.1.0` or later to run due to API changes.

View File

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

View File

@@ -250,13 +250,12 @@ func rootCmdRun(*cobra.Command, []string) {
if err := s.Environment.Attach(); err != nil {
s.Log().WithField("error", errors.WithStackIf(err)).Warn("failed to attach to running server environment")
}
return
}
// Addresses potentially invalid data in the stored file that can cause Wings to lose
// track of what the actual server state is.
} else {
// At this point we've determined that the server should indeed be in an offline state, so we'll
// make a call to set that state just to ensure we don't ever accidentally end up with some invalid
// state being tracked.
s.Environment.SetState(environment.ProcessOfflineState)
}
})
}

View File

@@ -47,12 +47,6 @@ type Configuration struct {
System SystemConfiguration `json:"system" yaml:"system"`
Docker DockerConfiguration `json:"docker" yaml:"docker"`
// The amount of time in seconds that should elapse between disk usage checks
// run by the daemon. Setting a higher number can result in better IO performance
// at an increased risk of a malicious user creating a process that goes over
// the assigned disk limits.
DiskCheckTimeout int `yaml:"disk_check_timeout"`
// Defines internal throttling configurations for server processes to prevent
// someone from running an endless loop that spams data to logs.
Throttles ConsoleThrottles

View File

@@ -53,6 +53,10 @@ type SystemConfiguration struct {
// considered stale and a re-check should occur. DANGER: setting this value too low can seriously
// impact system performance and cause massive I/O bottlenecks and high CPU usage for the Wings
// process.
//
// Set to 0 to disable disk checking entirely. This will always return 0 for the disk space used
// by a server and should only be set in extreme scenarios where performance is critical and
// disk usage is not a concern.
DiskCheckInterval int64 `default:"150" yaml:"disk_check_interval"`
// Determines if Wings should detect a server that stops with a normal exit code of

View File

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

View File

@@ -70,7 +70,12 @@ func (e *Environment) Attach() error {
// indicates that the container is no longer running.
go func(ctx context.Context) {
if err := e.pollResources(ctx); err != nil {
log.WithField("environment_id", e.Id).WithField("error", errors.WithStackIf(err)).Error("error during environment resource polling")
l := log.WithField("environment_id", e.Id)
if !errors.Is(err, context.Canceled) {
l.WithField("error", errors.WithStackIf(err)).Error("error during environment resource polling")
} else {
l.Warn("stopping server resource polling: context canceled")
}
}
}(ctx)
@@ -291,9 +296,20 @@ func (e *Environment) followOutput() error {
defer reader.Close()
r := bufio.NewReader(reader)
// Micro-optimization to create these replacements one time when this routine
// fires up, rather than on every line that is executed.
cr := []byte(" \r")
crr := []byte("\r\n")
// Avoid constantly re-allocating memory when we're flooding lines through this
// function by using the same buffer for the duration of the call and just truncating
// the value back to 0 every loop.
var str strings.Builder
ParentLoop:
for {
var b bytes.Buffer
str.Reset()
var line []byte
var isPrefix bool
@@ -305,7 +321,7 @@ func (e *Environment) followOutput() error {
// in line with that it thinks is the terminal size. Those returns break a lot of output handling,
// so we'll just replace them with proper new-lines and then split it later and send each line as
// its own event in the response.
b.Write(bytes.ReplaceAll(line, []byte(" \r"), []byte("\r\n")))
str.Write(bytes.Replace(line, cr, crr, -1))
// Finish this loop and begin outputting the line if there is no prefix (the line fit into
// the default buffer), or if we hit the end of the line.
@@ -322,7 +338,7 @@ func (e *Environment) followOutput() error {
// Publish the line for this loop. Break on new-line characters so every line is sent as a single
// output event, otherwise you get funky handling in the browser console.
for _, line := range strings.Split(b.String(), "\r\n") {
for _, line := range strings.Split(str.String(), "\r\n") {
e.Events().Publish(environment.ConsoleOutputEvent, line)
}

View File

@@ -183,7 +183,7 @@ func (e *Environment) WaitForStop(seconds uint, terminate bool) error {
case <-ctx.Done():
if ctxErr := ctx.Err(); ctxErr != nil {
if terminate {
log.WithField("container_id", e.Id).Debug("server did not stop in time, executing process termination")
log.WithField("container_id", e.Id).Info("server did not stop in time, executing process termination")
return errors.WithStackIf(e.Terminate(os.Kill))
}
@@ -193,7 +193,12 @@ func (e *Environment) WaitForStop(seconds uint, terminate bool) error {
case err := <-errChan:
if err != nil {
if terminate {
log.WithField("container_id", e.Id).WithField("error", errors.WithStackIf(err)).Warn("error while waiting for container stop, attempting process termination")
l := log.WithField("container_id", e.Id)
if errors.Is(err, context.DeadlineExceeded) {
l.Warn("deadline exceeded for container stop; terminating process")
} else {
l.WithField("error", errors.WithStackIf(err)).Warn("error while waiting for container stop; terminating process")
}
return errors.WithStackIf(e.Terminate(os.Kill))
}

View File

@@ -15,15 +15,14 @@ import (
// Attach to the instance and then automatically emit an event whenever the resource usage for the
// server process changes.
func (e *Environment) pollResources(ctx context.Context) error {
l := log.WithField("container_id", e.Id)
l.Debug("starting resource polling for container")
defer l.Debug("stopped resource polling for container")
if e.st.Load() == environment.ProcessOfflineState {
return errors.New("cannot enable resource polling on a stopped server")
}
l := log.WithField("container_id", e.Id)
l.Debug("starting resource polling for container")
defer l.Debug("stopped resource polling for container")
stats, err := e.client.ContainerStats(context.Background(), e.Id, true)
if err != nil {
return errors.WithStackIf(err)
@@ -35,7 +34,7 @@ func (e *Environment) pollResources(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return ctx.Err()
return errors.WithStackIf(ctx.Err())
default:
var v *types.StatsJSON

View File

@@ -35,10 +35,6 @@ func getServerFileContents(c *gin.Context) {
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("Content-Length", strconv.Itoa(int(st.Info.Size())))
@@ -48,6 +44,19 @@ func getServerFileContents(c *gin.Context) {
c.Header("Content-Disposition", "attachment; filename="+st.Info.Name())
c.Header("Content-Type", "application/octet-stream")
}
// TODO(dane): should probably come up with a different approach here. If an error is encountered
// by this Readfile call you'll end up causing a (recovered) panic in the program because so many
// headers have already been set. We should probably add a RawReadfile that just returns the file
// to be read and then we can stream from that safely without error.
//
// Until that becomes a problem though I'm just going to leave this how it is. The panic is recovered
// and a normal 500 error is returned to the client to my knowledge. It is also very unlikely to
// happen since we're doing so much before this point that would normally throw an error if there
// was a problem with the file.
if err := s.Filesystem().Readfile(p, c.Writer); err != nil {
TrackedServerError(err, s).AbortFilesystemError(c)
return
}
}

View File

@@ -97,6 +97,11 @@ func (fs *Filesystem) CachedUsage() int64 {
// This is primarily to avoid a bunch of I/O operations from piling up on the server, especially on servers
// with a large amount of files.
func (fs *Filesystem) DiskUsage(allowStaleValue bool) (int64, error) {
// A disk check interval of 0 means this functionality is completely disabled.
if fs.diskCheckInterval == 0 {
return 0, nil
}
if !fs.lastLookupTime.Get().After(time.Now().Add(time.Second * fs.diskCheckInterval * -1)) {
// If we are now allowing a stale response go ahead and perform the lookup and return the fresh
// value. This is a blocking operation to the calling process.

View File

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