Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82b23ef638 | ||
|
|
d970ec35b7 | ||
|
|
e2872e786e | ||
|
|
f81e35d960 | ||
|
|
672fb860ea | ||
|
|
8081c83de4 | ||
|
|
f379d0e54a | ||
|
|
ffb6bd72ef | ||
|
|
488ef9de54 | ||
|
|
34349d4b48 | ||
|
|
2197d82957 | ||
|
|
20ece60a72 | ||
|
|
33e584b447 |
5
.github/workflows/build-test.yml
vendored
5
.github/workflows/build-test.yml
vendored
@@ -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
|
||||
|
||||
|
||||
14
.github/workflows/codeql-analysis.yml
vendored
14
.github/workflows/codeql-analysis.yml
vendored
@@ -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,12 +13,11 @@ 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
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
|
||||
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:
|
||||
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 ./...
|
||||
|
||||
17
CHANGELOG.md
17
CHANGELOG.md
@@ -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.
|
||||
|
||||
|
||||
11
Dockerfile
11
Dockerfile
@@ -1,14 +1,11 @@
|
||||
# ----------------------------------
|
||||
# Pterodactyl Panel Dockerfile
|
||||
# ----------------------------------
|
||||
|
||||
FROM golang:1.15-alpine
|
||||
ARG VERSION="develop"
|
||||
COPY . /go/wings/
|
||||
WORKDIR /go/wings/
|
||||
RUN apk add --no-cache upx \
|
||||
&& go build -ldflags="-s -w" \
|
||||
&& upx --brute wings
|
||||
&& CGO_ENABLED=0 go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${VERSION}" \
|
||||
&& upx wings
|
||||
|
||||
FROM alpine:latest
|
||||
COPY --from=0 /go/wings/wings /usr/bin/
|
||||
CMD ["wings","--config", "/etc/pterodactyl/config.yml"]
|
||||
CMD ["wings", "--config", "/etc/pterodactyl/config.yml"]
|
||||
11
cmd/root.go
11
cmd/root.go
@@ -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
|
||||
} else {
|
||||
// At this point we've determined that the server should indeed be in an offline state, so we'll
|
||||
// make a call to set that state just to ensure we don't ever accidentally end up with some invalid
|
||||
// state being tracked.
|
||||
s.Environment.SetState(environment.ProcessOfflineState)
|
||||
}
|
||||
|
||||
// Addresses potentially invalid data in the stored file that can cause Wings to lose
|
||||
// track of what the actual server state is.
|
||||
s.Environment.SetState(environment.ProcessOfflineState)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -35,19 +35,28 @@ func getServerFileContents(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("X-Mime-Type", st.Mimetype)
|
||||
c.Header("Content-Length", strconv.Itoa(int(st.Info.Size())))
|
||||
|
||||
// If a download parameter is included in the URL go ahead and attach the necessary headers
|
||||
// so that the file can be downloaded.
|
||||
if c.Query("download") != "" {
|
||||
c.Header("Content-Disposition", "attachment; filename="+st.Info.Name())
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
}
|
||||
|
||||
// TODO(dane): should probably come up with a different approach here. If an error is encountered
|
||||
// by this Readfile call you'll end up causing a (recovered) panic in the program because so many
|
||||
// headers have already been set. We should probably add a RawReadfile that just returns the file
|
||||
// to be read and then we can stream from that safely without error.
|
||||
//
|
||||
// Until that becomes a problem though I'm just going to leave this how it is. The panic is recovered
|
||||
// and a normal 500 error is returned to the client to my knowledge. It is also very unlikely to
|
||||
// happen since we're doing so much before this point that would normally throw an error if there
|
||||
// was a problem with the file.
|
||||
if err := s.Filesystem().Readfile(p, c.Writer); err != nil {
|
||||
TrackedServerError(err, s).AbortFilesystemError(c)
|
||||
return
|
||||
} else {
|
||||
c.Header("X-Mime-Type", st.Mimetype)
|
||||
c.Header("Content-Length", strconv.Itoa(int(st.Info.Size())))
|
||||
|
||||
// If a download parameter is included in the URL go ahead and attach the necessary headers
|
||||
// so that the file can be downloaded.
|
||||
if c.Query("download") != "" {
|
||||
c.Header("Content-Disposition", "attachment; filename="+st.Info.Name())
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user