Compare commits

..

1 Commits

Author SHA1 Message Date
Matthew Penner
dafbbab2ed metrics: initial commit 2021-06-22 08:17:02 -06:00
69 changed files with 713 additions and 1227 deletions

View File

@@ -2,17 +2,17 @@ name: Run Tests
on:
push:
branches:
- develop
- 'develop'
pull_request:
branches:
- develop
- 'develop'
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [ ubuntu-20.04 ]
go: [ '^1.16' ]
go: [ '^1.15', '^1.16' ]
goos: [ linux ]
goarch: [ amd64, arm64 ]
runs-on: ${{ matrix.os }}
@@ -60,7 +60,7 @@ jobs:
run: go test ./...
- name: Upload Artifact
uses: actions/upload-artifact@v2
if: ${{ matrix.go == '^1.16' && (github.ref == 'refs/heads/develop' || github.event_name == 'pull_request') }}
if: ${{ matrix.go == '^1.15' && (github.ref == 'refs/heads/develop' || github.event_name == 'pull_request') }}
with:
name: wings_${{ matrix.goos }}_${{ matrix.goarch }}
path: build/wings_${{ matrix.goos }}_${{ matrix.goarch }}

View File

@@ -2,29 +2,30 @@ name: CodeQL
on:
push:
branches:
- develop
- 'develop'
pull_request:
branches:
- develop
- 'develop'
schedule:
- cron: '0 9 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
language:
- go
steps:
- uses: actions/checkout@v2
- name: Code Checkout
uses: actions/checkout@v2
- name: Checkout Head
run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
- uses: github/codeql-action/autobuild@v1
- uses: github/codeql-action/analyze@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -2,7 +2,8 @@ name: Publish Docker Image
on:
push:
branches:
- develop
- 'develop'
tags:
- 'v*'
jobs:
@@ -43,7 +44,6 @@ jobs:
build-args: |
VERSION=${{ steps.build_info.outputs.version_tag }}
labels: ${{ steps.docker_meta.outputs.labels }}
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.docker_meta.outputs.tags }}
- name: Release Development Build
@@ -53,6 +53,5 @@ jobs:
build-args: |
VERSION=dev-${{ steps.build_info.outputs.short_sha }}
labels: ${{ steps.docker_meta.outputs.labels }}
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }}

View File

@@ -11,7 +11,7 @@ jobs:
uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '^1.16'
go-version: '^1.15'
- name: Build
env:
REF: ${{ github.ref }}

View File

@@ -1,26 +1,5 @@
# Changelog
## v1.4.6
### Fixed
* Environment variable starting with the same prefix no longer get merged into a single environment variable value (skipping all but the first).
* The `start_on_completion` flag for server installs will now properly start the server.
* Fixes socket files unintentionally causing backups to be aborted.
* Files extracted from a backup now have their preior mode properly set on the restored files, rather than defaulting to 0644.
* Fixes logrotate issues due to a bad user configuration on some systems.
### Updated
* The minimum Go version required to compile Wings is now `go1.16`.
### Deprecated
> Both of these deprecations will be removed in `Wings@2.0.0`.
* The `Server.Id()` method has been deprecated in favor of `Server.ID()`.
* The `directory` field on the `/api/servers/:server/files/pull` endpoint is deprecated and should be updated to use `root` instead for consistency with other endpoints.
## v1.4.5
### Changed
* Upped the process limit for a container from `256` to `512` in order to address edge-cases for some games that spawn a lot of processes.
## v1.4.4
### Added
* **[security]** Adds support for limiting the total number of pids any one container can have active at once to prevent malicious users from impacting other instances on the same node.

View File

@@ -1,5 +1,5 @@
# Stage 1 (Build)
FROM --platform=$BUILDPLATFORM golang:1.16-alpine AS builder
FROM golang:1.15-alpine3.12 AS builder
ARG VERSION
RUN apk add --update --no-cache git make upx
@@ -14,10 +14,9 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-o wings \
wings.go
RUN upx wings
RUN echo "ID=\"distroless\"" > /etc/os-release
# Stage 2 (Final)
FROM gcr.io/distroless/static:latest
COPY --from=builder /etc/os-release /etc/os-release
FROM busybox:1.33.0
RUN echo "ID=\"busybox\"" > /etc/os-release
COPY --from=builder /app/wings /usr/bin/
CMD [ "/usr/bin/wings", "--config", "/etc/pterodactyl/config.yml" ]
CMD [ "wings", "--config", "/etc/pterodactyl/config.yml" ]

View File

@@ -14,9 +14,8 @@ import (
"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/spf13/cobra"
"github.com/pterodactyl/wings/config"
"github.com/spf13/cobra"
)
var (

View File

@@ -21,12 +21,11 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/docker/pkg/parsers/operatingsystem"
"github.com/spf13/cobra"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/loggers/cli"
"github.com/pterodactyl/wings/system"
"github.com/spf13/cobra"
)
const DefaultHastebinUrl = "https://ptero.co"

View File

@@ -4,6 +4,7 @@ import (
"crypto/tls"
"errors"
"fmt"
"github.com/pterodactyl/wings/metrics"
log2 "log"
"net/http"
"os"
@@ -20,10 +21,6 @@ import (
"github.com/gammazero/workerpool"
"github.com/mitchellh/colorstring"
"github.com/pkg/profile"
"github.com/spf13/cobra"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/loggers/cli"
@@ -32,6 +29,9 @@ import (
"github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/sftp"
"github.com/pterodactyl/wings/system"
"github.com/spf13/cobra"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
)
var (
@@ -41,7 +41,7 @@ var (
var rootCommand = &cobra.Command{
Use: "wings",
Short: "Runs the API server allowing programmatic control of game servers for Pterodactyl Panel.",
Short: "Runs the API server allowing programatic control of game servers for Pterodactyl Panel.",
PreRun: func(cmd *cobra.Command, args []string) {
initConfig()
initLogging()
@@ -91,9 +91,9 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
case "mem":
defer profile.Start(profile.MemProfile).Stop()
case "alloc":
defer profile.Start(profile.MemProfile, profile.MemProfileAllocs).Stop()
defer profile.Start(profile.MemProfile, profile.MemProfileAllocs()).Stop()
case "heap":
defer profile.Start(profile.MemProfile, profile.MemProfileHeap).Stop()
defer profile.Start(profile.MemProfile, profile.MemProfileHeap()).Stop()
case "routines":
defer profile.Start(profile.GoroutineProfile).Stop()
case "mutex":
@@ -123,6 +123,11 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
log.WithField("error", err).Fatal("failed to configure system directories for pterodactyl")
return
}
if err := config.EnableLogRotation(); err != nil {
log.WithField("error", err).Fatal("failed to configure log rotation on the system")
return
}
log.WithField("username", config.Get().System.User).Info("checking for pterodactyl system user")
if err := config.EnsurePterodactylUser(); err != nil {
log.WithField("error", err).Fatal("failed to create pterodactyl system user")
@@ -132,10 +137,9 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
"uid": config.Get().System.User.Uid,
"gid": config.Get().System.User.Gid,
}).Info("configured system user successfully")
if err := config.EnableLogRotation(); err != nil {
log.WithField("error", err).Fatal("failed to configure log rotation on the system")
return
}
done := make(chan bool)
go metrics.Initialize(done)
pclient := remote.New(
config.Get().PanelLocation,
@@ -160,7 +164,7 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
// Just for some nice log output.
for _, s := range manager.All() {
log.WithField("server", s.ID()).Info("finished loading configuration for server")
log.WithField("server", s.Id()).Info("finished loading configuration for server")
}
states, err := manager.ReadStates()
@@ -199,17 +203,23 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
continue
}
if states[s.Id()] == environment.ProcessRunningState {
metrics.ServerStatus.WithLabelValues(s.Id()).Set(1)
} else {
metrics.ServerStatus.WithLabelValues(s.Id()).Set(0)
}
pool.Submit(func() {
s.Log().Info("configuring server environment and restoring to previous state")
var st string
if state, exists := states[s.ID()]; exists {
if state, exists := states[s.Id()]; exists {
st = state
}
r, err := s.Environment.IsRunning()
// We ignore missing containers because we don't want to actually block booting of wings at this
// point. If we didn't do this, and you pruned all the images and then started wings you could
// end up waiting a long period of time for all the images to be re-pulled on Wings boot rather
// point. If we didn't do this and you pruned all of the images and then started wings you could
// end up waiting a long period of time for all of the images to be re-pulled on Wings boot rather
// than when the server itself is started.
if err != nil && !client.IsErrNotFound(err) {
s.Log().WithField("error", err).Error("error checking server environment status")
@@ -247,10 +257,10 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
})
}
// Wait until all the servers are ready to go before we fire up the SFTP and HTTP servers.
// Wait until all of the servers are ready to go before we fire up the SFTP and HTTP servers.
pool.StopWait()
defer func() {
// Cancel the context on all the running servers at this point, even though the
// Cancel the context on all of the running servers at this point, even though the
// program is just shutting down.
for _, s := range manager.All() {
s.CtxCancel()
@@ -267,7 +277,7 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
go func() {
log.Info("updating server states on Panel: marking installing/restoring servers as normal")
// Update all the servers on the Panel to be in a valid state if they're
// Update all of the servers on the Panel to be in a valid state if they're
// currently marked as installing/restoring now that Wings is restarted.
if err := pclient.ResetServersState(cmd.Context()); err != nil {
log.WithField("error", err).Error("failed to reset server states on Panel: some instances may be stuck in an installing/restoring state unexpectedly")
@@ -346,10 +356,11 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
if err := s.ListenAndServe(); err != nil {
log.WithField("error", err).Fatal("failed to configure HTTP server")
}
<-done
}
// Reads the configuration from the disk and then sets up the global singleton
// with all the configuration values.
// with all of the configuration values.
func initConfig() {
if !strings.HasPrefix(configPath, "/") {
d, err := os.Getwd()

View File

@@ -21,9 +21,8 @@ import (
"github.com/cobaugh/osrelease"
"github.com/creasty/defaults"
"github.com/gbrlsnchs/jwt/v3"
"gopkg.in/yaml.v2"
"github.com/pterodactyl/wings/system"
"gopkg.in/yaml.v2"
)
const DefaultLocation = "/etc/pterodactyl/config.yml"
@@ -54,7 +53,7 @@ var _jwtAlgo *jwt.HMACSHA
var _debugViaFlag bool
// Locker specific to writing the configuration to the disk, this happens
// in areas that might already be locked, so we don't want to crash the process.
// in areas that might already be locked so we don't want to crash the process.
var _writeLock sync.Mutex
// SftpConfiguration defines the configuration of the internal SFTP server.
@@ -92,6 +91,12 @@ type ApiConfiguration struct {
UploadLimit int `default:"100" json:"upload_limit" yaml:"upload_limit"`
}
// MetricsConfiguration .
type MetricsConfiguration struct {
// Bind .
Bind string `default:":9000" yaml:"bind"`
}
// RemoteQueryConfiguration defines the configuration settings for remote requests
// from Wings to the Panel.
type RemoteQueryConfiguration struct {
@@ -264,6 +269,7 @@ type Configuration struct {
Api ApiConfiguration `json:"api" yaml:"api"`
System SystemConfiguration `json:"system" yaml:"system"`
Docker DockerConfiguration `json:"docker" yaml:"docker"`
Metrics MetricsConfiguration `json:"metrics" yaml:"metrics"`
// Defines internal throttling configurations for server processes to prevent
// someone from running an endless loop that spams data to logs.
@@ -395,7 +401,7 @@ func EnsurePterodactylUser() error {
}
// Our way of detecting if wings is running inside of Docker.
if sysName == "distroless" {
if sysName == "busybox" {
_config.System.Username = system.FirstNotEmpty(os.Getenv("WINGS_USERNAME"), "pterodactyl")
_config.System.User.Uid = system.MustInt(system.FirstNotEmpty(os.Getenv("WINGS_UID"), "988"))
_config.System.User.Gid = system.MustInt(system.FirstNotEmpty(os.Getenv("WINGS_GID"), "988"))
@@ -539,7 +545,8 @@ func EnableLogRotation() error {
}
defer f.Close()
t, err := template.New("logrotate").Parse(`{{.LogDirectory}}/wings.log {
t, err := template.New("logrotate").Parse(`
{{.LogDirectory}}/wings.log {
size 10M
compress
delaycompress
@@ -547,8 +554,9 @@ func EnableLogRotation() error {
maxage 7
missingok
notifempty
create 0640 {{.User.Uid}} {{.User.Gid}}
postrotate
/usr/bin/systemctl kill -s HUP wings.service >/dev/null 2>&1 || true
killall -SIGHUP wings
endscript
}`)
if err != nil {

View File

@@ -60,7 +60,7 @@ type DockerConfiguration struct {
// at any given moment. This is a security concern in shared-hosting environments where a
// malicious process could create enough processes to cause the host node to run out of
// available pids and crash.
ContainerPidLimit int64 `default:"512" json:"container_pid_limit" yaml:"container_pid_limit"`
ContainerPidLimit int64 `default:"256" json:"container_pid_limit" yaml:"container_pid_limit"`
// InstallLimits defines the limits on the installer containers that prevents a server's
// installation process from unintentionally consuming more resources than expected. This

View File

@@ -5,7 +5,6 @@ import (
"strconv"
"github.com/docker/go-connections/nat"
"github.com/pterodactyl/wings/config"
)
@@ -20,7 +19,7 @@ type Allocations struct {
Port int `json:"port"`
} `json:"default"`
// Mappings contains all the ports that should be assigned to a given server
// Mappings contains all of the ports that should be assigned to a given server
// attached to the IP they correspond to.
Mappings map[string][]int `json:"mappings"`
}
@@ -63,7 +62,7 @@ func (a *Allocations) DockerBindings() nat.PortMap {
iface := config.Get().Docker.Network.Interface
out := a.Bindings()
// Loop over all the bindings for this container, and convert any that reference 127.0.0.1
// Loop over all of the bindings for this container, and convert any that reference 127.0.0.1
// to use the pterodactyl0 network interface IP, as that is the true local for what people are
// trying to do when creating servers.
for p, binds := range out {

View File

@@ -10,7 +10,6 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/pterodactyl/wings/config"
)

View File

@@ -17,7 +17,6 @@ import (
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/client"
"github.com/docker/docker/daemon/logger/jsonfilelog"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/system"
@@ -140,7 +139,7 @@ func (e *Environment) InSituUpdate() error {
return nil
}
// Create creates a new container for the server using all the data that is
// Create creates a new container for the server using all of the data that is
// currently available for it. If the container already exists it will be
// returned.
func (e *Environment) Create() error {
@@ -193,7 +192,7 @@ func (e *Environment) Create() error {
PortBindings: a.DockerBindings(),
// Configure the mounts for this container. First mount the server data directory
// into the container as an r/w bind.
// into the container as a r/w bind.
Mounts: e.convertMounts(),
// Configure the /tmp folder mapping in containers. This is necessary for some
@@ -341,9 +340,11 @@ func (e *Environment) scanOutput(reader io.ReadCloser) {
events := e.Events()
if err := system.ScanReader(reader, func(line string) {
err := system.ScanReader(reader, func(line string) {
events.Publish(environment.ConsoleOutputEvent, line)
}); err != nil && err != io.EOF {
})
if err != nil && err != io.EOF {
log.WithField("error", err).WithField("container_id", e.Id).Warn("error processing scanner line in console output")
return
}
@@ -353,7 +354,7 @@ func (e *Environment) scanOutput(reader io.ReadCloser) {
return
}
// Close the current reader before starting a new one, the defer will still run,
// Close the current reader before starting a new one, the defer will still run
// but it will do nothing if we already closed the stream.
_ = reader.Close()
@@ -371,7 +372,7 @@ type imagePullStatus struct {
// error to the logger but continue with the process.
//
// The reasoning behind this is that Quay has had some serious outages as of
// late, and we don't need to block all the servers from booting just because
// late, and we don't need to block all of the servers from booting just because
// of that. I'd imagine in a lot of cases an outage shouldn't affect users too
// badly. It'll at least keep existing servers working correctly if anything.
func (e *Environment) ensureImageExists(image string) error {

View File

@@ -3,6 +3,7 @@ package docker
import (
"context"
"fmt"
"github.com/pterodactyl/wings/metrics"
"io"
"sync"
@@ -10,7 +11,6 @@ import (
"github.com/apex/log"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/events"
"github.com/pterodactyl/wings/remote"
@@ -22,7 +22,7 @@ type Metadata struct {
Stop remote.ProcessStopConfiguration
}
// Ensure that the Docker environment is always implementing all the methods
// Ensure that the Docker environment is always implementing all of the methods
// from the base environment interface.
var _ environment.ProcessEnvironment = (*Environment)(nil)
@@ -213,5 +213,15 @@ func (e *Environment) SetState(state string) {
// If the state changed make sure we update the internal tracking to note that.
e.st.Store(state)
e.Events().Publish(environment.StateChangeEvent, state)
if state == environment.ProcessRunningState || state == environment.ProcessOfflineState {
val := 0
if state == environment.ProcessRunningState {
val = 1
} else {
metrics.ResetServer(e.Id)
}
metrics.ServerStatus.WithLabelValues(e.Id).Set(float64(val))
}
}
}

View File

@@ -12,7 +12,6 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/remote"
)
@@ -82,7 +81,7 @@ func (e *Environment) Start() error {
return e.Attach()
}
// Truncate the log file, so we don't end up outputting a bunch of useless log information
// Truncate the log file so we don't end up outputting a bunch of useless log information
// to the websocket and whatnot. Check first that the path and file exist before trying
// to truncate them.
if _, err := os.Stat(c.LogPath); err == nil {
@@ -243,7 +242,7 @@ func (e *Environment) Terminate(signal os.Signal) error {
}
if !c.State.Running {
// If the container is not running, but we're not already in a stopped state go ahead
// If the container is not running but we're not already in a stopped state go ahead
// and update things to indicate we should be completely stopped now. Set to stopping
// first so crash detection is not triggered.
if e.st.Load() != environment.ProcessOfflineState {

View File

@@ -2,14 +2,13 @@ package docker
import (
"context"
"emperror.dev/errors"
"encoding/json"
"github.com/docker/docker/api/types"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/metrics"
"io"
"math"
"emperror.dev/errors"
"github.com/docker/docker/api/types"
"github.com/pterodactyl/wings/environment"
)
// Attach to the instance and then automatically emit an event whenever the resource usage for the
@@ -62,6 +61,11 @@ func (e *Environment) pollResources(ctx context.Context) error {
st.Network.TxBytes += nw.TxBytes
}
metrics.ServerCPU.WithLabelValues(e.Id).Set(st.CpuAbsolute)
metrics.ServerMemory.WithLabelValues(e.Id).Set(float64(st.Memory))
metrics.ServerNetworkRx.WithLabelValues(e.Id).Set(float64(st.Network.RxBytes))
metrics.ServerNetworkTx.WithLabelValues(e.Id).Set(float64(st.Network.TxBytes))
if b, err := json.Marshal(st); err != nil {
e.log().WithField("error", err).Warn("error while marshaling stats object for environment")
} else {
@@ -75,8 +79,9 @@ func (e *Environment) pollResources(ctx context.Context) error {
// value which can be rather confusing to people trying to compare panel usage to
// their stats output.
//
// This math is from their CLI repository in order to show the same values to avoid people
// bothering me about it. It should also reflect a slightly more correct memory value anyways.
// This math is straight up lifted from their CLI repository in order to show the same
// values to avoid people bothering me about it. It should also reflect a slightly more
// correct memory value anyways.
//
// @see https://github.com/docker/cli/blob/96e1d1d6/cli/command/container/stats_helpers.go#L227-L249
func calculateDockerMemory(stats types.MemoryStats) uint64 {

View File

@@ -7,7 +7,6 @@ import (
"github.com/apex/log"
"github.com/docker/docker/api/types/container"
"github.com/pterodactyl/wings/config"
)
@@ -26,7 +25,7 @@ type Mount struct {
// that we're mounting into the container at the Target location.
Source string `json:"source"`
// Whether the directory is being mounted as read-only. It is up to the environment to
// Whether or not the directory is being mounted as read-only. It is up to the environment to
// handle this value correctly and ensure security expectations are met with its usage.
ReadOnly bool `json:"read_only"`
}

View File

@@ -12,7 +12,7 @@ type Stats struct {
// The total amount of memory this container or resource can use. Inside Docker this is
// going to be higher than you'd expect because we're automatically allocating overhead
// abilities for the container, so it's not going to be a perfect match.
// abilities for the container, so its not going to be a perfect match.
MemoryLimit uint64 `json:"memory_limit_bytes"`
// The absolute CPU usage is the amount of CPU used in relation to the entire system and

View File

@@ -30,7 +30,7 @@ func (e *EventBus) Publish(topic string, data string) {
// Some of our topics for the socket support passing a more specific namespace,
// such as "backup completed:1234" to indicate which specific backup was completed.
//
// In these cases, we still need to send the event using the standard listener
// In these cases, we still need to the send the event using the standard listener
// name of "backup completed".
if strings.Contains(topic, ":") {
parts := strings.SplitN(topic, ":", 2)
@@ -43,7 +43,7 @@ func (e *EventBus) Publish(topic string, data string) {
e.mu.RLock()
defer e.mu.RUnlock()
// Acquire a read lock and loop over all the channels registered for the topic. This
// Acquire a read lock and loop over all of the channels registered for the topic. This
// avoids a panic crash if the process tries to unregister the channel while this routine
// is running.
if cp, ok := e.pools[t]; ok {
@@ -65,7 +65,7 @@ func (e *EventBus) Publish(topic string, data string) {
}
}
// PublishJson publishes a JSON message to a given topic.
// Publishes a JSON message to a given topic.
func (e *EventBus) PublishJson(topic string, data interface{}) error {
b, err := json.Marshal(data)
if err != nil {
@@ -77,7 +77,7 @@ func (e *EventBus) PublishJson(topic string, data interface{}) error {
return nil
}
// On adds a callback function that will be executed each time one of the events using the topic
// Register a callback function that will be executed each time one of the events using the topic
// name is called.
func (e *EventBus) On(topic string, callback *func(Event)) {
e.mu.Lock()
@@ -97,7 +97,7 @@ func (e *EventBus) On(topic string, callback *func(Event)) {
e.pools[topic].Add(callback)
}
// Off removes an event listener from the bus.
// Removes an event listener from the bus.
func (e *EventBus) Off(topic string, callback *func(Event)) {
e.mu.Lock()
defer e.mu.Unlock()
@@ -107,7 +107,7 @@ func (e *EventBus) Off(topic string, callback *func(Event)) {
}
}
// Destroy removes all the event listeners that have been registered for any topic. Also stops the worker
// Removes all of the event listeners that have been registered for any topic. Also stops the worker
// pool to close that routine.
func (e *EventBus) Destroy() {
e.mu.Lock()

98
go.mod
View File

@@ -1,72 +1,82 @@
module github.com/pterodactyl/wings
go 1.16
go 1.14
require (
emperror.dev/errors v0.8.0
github.com/AlecAivazis/survey/v2 v2.2.15
github.com/Jeffail/gabs/v2 v2.6.1
github.com/Microsoft/go-winio v0.5.0 // indirect
github.com/Microsoft/hcsshim v0.8.20 // indirect
github.com/AlecAivazis/survey/v2 v2.2.7
github.com/Jeffail/gabs/v2 v2.6.0
github.com/Microsoft/go-winio v0.4.16 // indirect
github.com/Microsoft/hcsshim v0.8.14 // indirect
github.com/NYTimes/logrotate v1.0.0
github.com/andybalholm/brotli v1.0.3 // indirect
github.com/andybalholm/brotli v1.0.1 // indirect
github.com/apex/log v1.9.0
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
github.com/beevik/etree v1.1.0
github.com/buger/jsonparser v1.1.1
github.com/cenkalti/backoff/v4 v4.1.1
github.com/buger/jsonparser v1.1.0
github.com/cenkalti/backoff/v4 v4.1.0
github.com/cobaugh/osrelease v0.0.0-20181218015638-a93a0a55a249
github.com/containerd/containerd v1.5.5 // indirect
github.com/containerd/containerd v1.4.3 // indirect
github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c // indirect
github.com/creasty/defaults v1.5.1
github.com/docker/docker v20.10.7+incompatible
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v20.10.1+incompatible
github.com/docker/go-connections v0.4.0
github.com/fatih/color v1.12.0
github.com/docker/go-metrics v0.0.1 // indirect
github.com/fatih/color v1.10.0
github.com/franela/goblin v0.0.0-20200825194134-80c0062ed6cd
github.com/gabriel-vasile/mimetype v1.3.1
github.com/gammazero/workerpool v1.1.2
github.com/gbrlsnchs/jwt/v3 v3.0.1
github.com/gin-gonic/gin v1.7.2
github.com/go-playground/validator/v10 v10.8.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.3.0
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gabriel-vasile/mimetype v1.1.2
github.com/gammazero/deque v0.0.0-20201010052221-3932da5530cc // indirect
github.com/gammazero/workerpool v1.1.1
github.com/gbrlsnchs/jwt/v3 v3.0.0
github.com/gin-gonic/gin v1.6.3
github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/golang/snappy v0.0.2 // indirect
github.com/google/go-cmp v0.5.2 // indirect
github.com/google/uuid v1.1.2
github.com/gorilla/mux v1.7.4 // indirect
github.com/gorilla/websocket v1.4.2
github.com/iancoleman/strcase v0.2.0
github.com/icza/dyno v0.0.0-20210726202311-f1bafe5d9996
github.com/imdario/mergo v0.3.12
github.com/iancoleman/strcase v0.1.2
github.com/icza/dyno v0.0.0-20200205103839-49cb13720835
github.com/imdario/mergo v0.3.9
github.com/juju/ratelimit v1.0.1
github.com/karrick/godirwalk v1.16.1
github.com/klauspost/compress v1.13.2 // indirect
github.com/klauspost/compress v1.11.4 // indirect
github.com/klauspost/pgzip v1.2.5
github.com/magefile/mage v1.11.0 // indirect
github.com/magiconair/properties v1.8.5
github.com/leodido/go-urn v1.2.1 // indirect
github.com/magefile/mage v1.10.0 // indirect
github.com/magiconair/properties v1.8.4
github.com/mattn/go-colorable v0.1.8
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mholt/archiver/v3 v3.5.0
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/nwaples/rardecode v1.1.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pierrec/lz4/v4 v4.1.8 // indirect
github.com/pkg/profile v1.6.0
github.com/pkg/sftp v1.13.2
github.com/prometheus/common v0.30.0 // indirect
github.com/prometheus/procfs v0.7.1 // indirect
github.com/pierrec/lz4/v4 v4.1.2 // indirect
github.com/pkg/profile v1.5.0
github.com/pkg/sftp v1.12.0
github.com/prometheus/client_golang v1.9.0 // indirect
github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f
github.com/spf13/cobra v1.2.1
github.com/stretchr/testify v1.7.0
github.com/ulikunitz/xz v0.5.10 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
google.golang.org/genproto v0.0.0-20210729151513-df9385d47c1b // indirect
github.com/sirupsen/logrus v1.7.0 // indirect
github.com/spf13/cobra v1.1.1
github.com/stretchr/testify v1.6.1
github.com/ugorji/go v1.2.2 // indirect
github.com/ulikunitz/xz v0.5.9 // indirect
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 // indirect
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
golang.org/x/text v0.3.4 // indirect
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d // indirect
google.golang.org/grpc v1.34.0 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/ini.v1 v1.62.0
gopkg.in/yaml.v2 v2.4.0

995
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,6 @@ import (
"emperror.dev/errors"
"github.com/asaskevich/govalidator"
"github.com/buger/jsonparser"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/remote"
"github.com/pterodactyl/wings/server"
@@ -17,7 +16,7 @@ type Installer struct {
server *server.Server
}
// New validates the received data to ensure that all the required fields
// New validates the received data to ensure that all of the required fields
// have been passed along in the request. This should be manually run before
// calling Execute().
func New(ctx context.Context, manager *server.Manager, data []byte) (*Installer, error) {
@@ -30,7 +29,6 @@ func New(ctx context.Context, manager *server.Manager, data []byte) (*Installer,
Suspended: false,
Invocation: getString(data, "invocation"),
SkipEggScripts: getBoolean(data, "skip_egg_scripts"),
StartOnCompletion: getBoolean(data, "start_on_completion"),
Build: environment.Limits{
MemoryLimit: getInt(data, "build", "memory"),
Swap: getInt(data, "build", "swap"),
@@ -86,7 +84,7 @@ func New(ctx context.Context, manager *server.Manager, data []byte) (*Installer,
// Uuid returns the UUID associated with this installer instance.
func (i *Installer) Uuid() string {
return i.server.ID()
return i.server.Id()
}
// Server returns the server instance.

View File

@@ -1,18 +1,17 @@
package cli
import (
"emperror.dev/errors"
"fmt"
"github.com/apex/log"
"github.com/apex/log/handlers/cli"
color2 "github.com/fatih/color"
"github.com/mattn/go-colorable"
"io"
"os"
"strings"
"sync"
"time"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/apex/log/handlers/cli"
color2 "github.com/fatih/color"
"github.com/mattn/go-colorable"
)
var Default = New(os.Stderr, true)

107
metrics/metrics.go Normal file
View File

@@ -0,0 +1,107 @@
package metrics
import (
"github.com/apex/log"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/pterodactyl/wings/config"
"net/http"
"time"
)
type Metrics struct {
handler http.Handler
}
const (
namespace = "pterodactyl"
subsystem = "wings"
)
var (
bootTimeSeconds = promauto.NewGauge(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "boot_time_seconds",
Help: "Boot time of this instance since epoch (1970)",
})
timeSeconds = promauto.NewGauge(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "time_seconds",
Help: "System time in seconds since epoch (1970)",
})
ServerStatus = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "server_status",
}, []string{"server_id"})
ServerCPU = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "server_cpu",
}, []string{"server_id"})
ServerMemory = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "server_memory",
}, []string{"server_id"})
ServerNetworkRx = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "server_network_rx",
}, []string{"server_id"})
ServerNetworkTx = promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "server_network_tx",
}, []string{"server_id"})
HTTPRequestsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "http_requests_total",
}, []string{"method", "route_path", "raw_path", "raw_query", "code"})
)
func Initialize(done chan bool) {
bootTimeSeconds.Set(float64(time.Now().UnixNano()) / 1e9)
ticker := time.NewTicker(time.Second)
go func() {
defer ticker.Stop()
for {
select {
case <-done:
// Received a "signal" on the done channel.
log.Debug("metrics: done")
return
case t := <-ticker.C:
// Update the current time.
timeSeconds.Set(float64(t.UnixNano()) / 1e9)
}
}
}()
if err := http.ListenAndServe(config.Get().Metrics.Bind, promhttp.Handler()); err != nil && err != http.ErrServerClosed {
log.WithField("error", err).Error("failed to start metrics server")
}
}
// DeleteServer will remove any existing labels from being scraped by Prometheus.
// Any previously scraped data will still be persisted by Prometheus.
func DeleteServer(sID string) {
ServerStatus.DeleteLabelValues(sID)
ServerCPU.DeleteLabelValues(sID)
ServerMemory.DeleteLabelValues(sID)
ServerNetworkRx.DeleteLabelValues(sID)
ServerNetworkTx.DeleteLabelValues(sID)
}
// ResetServer will reset a server's metrics to their default values except the status.
func ResetServer(sID string) {
ServerCPU.WithLabelValues(sID).Set(0)
ServerMemory.WithLabelValues(sID).Set(0)
ServerNetworkRx.WithLabelValues(sID).Set(0)
ServerNetworkTx.WithLabelValues(sID).Set(0)
}

View File

@@ -15,10 +15,9 @@ import (
"github.com/buger/jsonparser"
"github.com/icza/dyno"
"github.com/magiconair/properties"
"github.com/pterodactyl/wings/config"
"gopkg.in/ini.v1"
"gopkg.in/yaml.v2"
"github.com/pterodactyl/wings/config"
)
// The file parsing options that are available for a server configuration file.

View File

@@ -15,7 +15,6 @@ import (
"emperror.dev/errors"
"github.com/apex/log"
"github.com/cenkalti/backoff/v4"
"github.com/pterodactyl/wings/system"
)
@@ -119,7 +118,7 @@ func (c *client) requestOnce(ctx context.Context, method, path string, body io.R
return &Response{res}, err
}
// request executes an HTTP request against the Panel API. If there is an error
// request executes a HTTP request against the Panel API. If there is an error
// encountered with the request it will be retried using an exponential backoff.
// If the error returned from the Panel is due to API throttling or because there
// are invalid authentication credentials provided the request will _not_ be
@@ -171,7 +170,7 @@ func (c *client) request(ctx context.Context, method, path string, body io.Reade
// This allows for issues with DNS resolution, or rare race conditions due to
// slower SQL queries on the Panel to potentially self-resolve without just
// immediately failing the first request. The example below shows the amount of
// time that has elapsed between each call to the handler when an error is
// time that has ellapsed between each call to the handler when an error is
// returned. You can tweak these values as needed to get the effect you desire.
//
// If maxAttempts is a value greater than 0 the backoff will be capped at a total

View File

@@ -6,7 +6,6 @@ import (
"strings"
"github.com/apex/log"
"github.com/pterodactyl/wings/parser"
)
@@ -33,7 +32,7 @@ type Pagination struct {
// ServerConfigurationResponse holds the server configuration data returned from
// the Panel. When a server process is started, Wings communicates with the
// Panel to fetch the latest build information as well as get all the details
// Panel to fetch the latest build information as well as get all of the details
// needed to parse the given Egg.
//
// This means we do not need to hit Wings each time part of the server is

View File

@@ -2,26 +2,26 @@ package downloader
import (
"context"
"emperror.dev/errors"
"encoding/json"
"fmt"
"github.com/google/uuid"
"github.com/pterodactyl/wings/server"
"io"
"net"
"net/http"
"net/url"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"time"
"emperror.dev/errors"
"github.com/google/uuid"
"github.com/pterodactyl/wings/server"
)
var client = &http.Client{
Timeout: time.Hour * 12,
// Disallow any redirect on an HTTP call. This is a security requirement: do not modify
// Disallow any redirect on a HTTP call. This is a security requirement: do not modify
// this logic without first ensuring that the new target location IS NOT within the current
// instance's local network.
//
@@ -36,14 +36,18 @@ var client = &http.Client{
}
var instance = &Downloader{
// Tracks all the active downloads.
// Tracks all of the active downloads.
downloadCache: make(map[string]*Download),
// Tracks all the downloads active for a given server instance. This is
// Tracks all of the downloads active for a given server instance. This is
// primarily used to make things quicker and keep the code a little more
// legible throughout here.
serverCache: make(map[string][]string),
}
// Regex to match the end of an IPv4/IPv6 address. This allows the port to be removed
// so that we are just working with the raw IP address in question.
var ipMatchRegex = regexp.MustCompile(`(:\d+)$`)
// Internal IP ranges that should be blocked if the resource requested resolves within.
var internalRanges = []*net.IPNet{
mustParseCIDR("127.0.0.1/8"),
@@ -56,11 +60,9 @@ var internalRanges = []*net.IPNet{
mustParseCIDR("fc00::/7"),
}
const (
ErrInternalResolution = errors.Sentinel("downloader: destination resolves to internal network location")
ErrInvalidIPAddress = errors.Sentinel("downloader: invalid IP address")
ErrDownloadFailed = errors.Sentinel("downloader: download request failed")
)
const ErrInternalResolution = errors.Sentinel("downloader: destination resolves to internal network location")
const ErrInvalidIPAddress = errors.Sentinel("downloader: invalid IP address")
const ErrDownloadFailed = errors.Sentinel("downloader: download request failed")
type Counter struct {
total int
@@ -75,8 +77,8 @@ func (c *Counter) Write(p []byte) (int, error) {
}
type DownloadRequest struct {
Directory string
URL *url.URL
Directory string
}
type Download struct {
@@ -88,7 +90,7 @@ type Download struct {
cancelFunc *context.CancelFunc
}
// New starts a new tracked download which allows for cancellation later on by calling
// Starts a new tracked download which allows for cancellation later on by calling
// the Downloader.Cancel function.
func New(s *server.Server, r DownloadRequest) *Download {
dl := Download{
@@ -100,14 +102,14 @@ func New(s *server.Server, r DownloadRequest) *Download {
return &dl
}
// ByServer returns all the tracked downloads for a given server instance.
// Returns all of the tracked downloads for a given server instance.
func ByServer(sid string) []*Download {
instance.mu.Lock()
defer instance.mu.Unlock()
var downloads []*Download
if v, ok := instance.serverCache[sid]; ok {
for _, id := range v {
if dl, ok := instance.downloadCache[id]; ok {
if dl, dlok := instance.downloadCache[id]; dlok {
downloads = append(downloads, dl)
}
}
@@ -115,7 +117,7 @@ func ByServer(sid string) []*Download {
return downloads
}
// ByID returns a single Download matching a given identifier. If no download is found
// Returns a single Download matching a given identifier. If no download is found
// the second argument in the response will be false.
func ByID(dlid string) *Download {
return instance.find(dlid)
@@ -132,7 +134,7 @@ func (dl Download) MarshalJSON() ([]byte, error) {
})
}
// Execute executes a given download for the server and begins writing the file to the disk. Once
// Executes a given download for the server and begins writing the file to the disk. Once
// completed the download will be removed from the cache.
func (dl *Download) Execute() error {
ctx, cancel := context.WithTimeout(context.Background(), time.Hour*12)
@@ -183,7 +185,7 @@ func (dl *Download) Execute() error {
return nil
}
// Cancel cancels a running download and frees up the associated resources. If a file is being
// Cancels a running download and frees up the associated resources. If a file is being
// written a partial file will remain present on the disk.
func (dl *Download) Cancel() {
if dl.cancelFunc != nil {
@@ -192,12 +194,12 @@ func (dl *Download) Cancel() {
instance.remove(dl.Identifier)
}
// BelongsTo checks if the given download belongs to the provided server.
// Checks if the given download belongs to the provided server.
func (dl *Download) BelongsTo(s *server.Server) bool {
return dl.server.ID() == s.ID()
return dl.server.Id() == s.Id()
}
// Progress returns the current progress of the download as a float value between 0 and 1 where
// Returns the current progress of the download as a float value between 0 and 1 where
// 1 indicates that the download is completed.
func (dl *Download) Progress() float64 {
dl.mu.RLock()
@@ -230,19 +232,15 @@ func (dl *Download) isExternalNetwork(ctx context.Context) error {
// This cluster-fuck of math and integer shit converts an integer IP into a proper IPv4.
// For example: 16843009 would become 1.1.1.1
//if i, err := strconv.ParseInt(host, 10, 64); err == nil {
// host = strconv.FormatInt((i>>24)&0xFF, 10) + "." + strconv.FormatInt((i>>16)&0xFF, 10) + "." + strconv.FormatInt((i>>8)&0xFF, 10) + "." + strconv.FormatInt(i&0xFF, 10)
//}
if _, _, err := net.SplitHostPort(host); err != nil {
if !strings.Contains(err.Error(), "missing port in address") {
return errors.WithStack(err)
if i, err := strconv.ParseInt(host, 10, 64); err == nil {
host = strconv.FormatInt((i>>24)&0xFF, 10) + "." + strconv.FormatInt((i>>16)&0xFF, 10) + "." + strconv.FormatInt((i>>8)&0xFF, 10) + "." + strconv.FormatInt(i&0xFF, 10)
}
switch dl.req.URL.Scheme {
case "http":
host += ":80"
case "https":
host += ":443"
if !ipMatchRegex.MatchString(host) {
if dl.req.URL.Scheme == "https" {
host = host + ":443"
} else {
host = host + ":80"
}
}
@@ -252,11 +250,7 @@ func (dl *Download) isExternalNetwork(ctx context.Context) error {
}
_ = c.Close()
ipStr, _, err := net.SplitHostPort(c.RemoteAddr().String())
if err != nil {
return errors.WithStack(err)
}
ip := net.ParseIP(ipStr)
ip := net.ParseIP(ipMatchRegex.ReplaceAllString(c.RemoteAddr().String(), ""))
if ip == nil {
return errors.WithStack(ErrInvalidIPAddress)
}
@@ -271,7 +265,7 @@ func (dl *Download) isExternalNetwork(ctx context.Context) error {
return nil
}
// Downloader represents a global downloader that keeps track of all currently processing downloads
// Defines a global downloader struct that keeps track of all currently processing downloads
// for the machine.
type Downloader struct {
mu sync.RWMutex
@@ -279,11 +273,11 @@ type Downloader struct {
serverCache map[string][]string
}
// track tracks a download in the internal cache for this instance.
// Tracks a download in the internal cache for this instance.
func (d *Downloader) track(dl *Download) {
d.mu.Lock()
defer d.mu.Unlock()
sid := dl.server.ID()
sid := dl.server.Id()
if _, ok := d.downloadCache[dl.Identifier]; !ok {
d.downloadCache[dl.Identifier] = dl
if _, ok := d.serverCache[sid]; !ok {
@@ -293,7 +287,7 @@ func (d *Downloader) track(dl *Download) {
}
}
// find finds a given download entry using the provided ID and returns it.
// Finds a given download entry using the provided ID and returns it.
func (d *Downloader) find(dlid string) *Download {
d.mu.RLock()
defer d.mu.RUnlock()
@@ -303,24 +297,24 @@ func (d *Downloader) find(dlid string) *Download {
return nil
}
// remove removes the given download reference from the cache storing them. This also updates
// Remove the given download reference from the cache storing them. This also updates
// the slice of active downloads for a given server to not include this download.
func (d *Downloader) remove(dlID string) {
func (d *Downloader) remove(dlid string) {
d.mu.Lock()
defer d.mu.Unlock()
if _, ok := d.downloadCache[dlID]; !ok {
if _, ok := d.downloadCache[dlid]; !ok {
return
}
sID := d.downloadCache[dlID].server.ID()
delete(d.downloadCache, dlID)
if tracked, ok := d.serverCache[sID]; ok {
sid := d.downloadCache[dlid].server.Id()
delete(d.downloadCache, dlid)
if tracked, ok := d.serverCache[sid]; ok {
var out []string
for _, k := range tracked {
if k != dlID {
if k != dlid {
out = append(out, k)
}
}
d.serverCache[sID] = out
d.serverCache[sid] = out
}
}

View File

@@ -10,7 +10,6 @@ import (
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/server/filesystem"
)

View File

@@ -2,7 +2,6 @@ package router
import (
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/router/middleware"
"github.com/pterodactyl/wings/server"
)

View File

@@ -3,16 +3,17 @@ package middleware
import (
"context"
"crypto/subtle"
"github.com/pterodactyl/wings/metrics"
"io"
"net/http"
"os"
"strconv"
"strings"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/remote"
"github.com/pterodactyl/wings/server"
@@ -63,7 +64,7 @@ func (re *RequestError) Abort(c *gin.Context, status int) {
// server triggered this error.
if s, ok := c.Get("server"); ok {
if s, ok := s.(*server.Server); ok {
event = event.WithField("server_id", s.ID())
event = event.WithField("server_id", s.Id())
}
}
@@ -263,14 +264,14 @@ func ServerExists() gin.HandlerFunc {
if c.Param("server") != "" {
manager := ExtractManager(c)
s = manager.Find(func(s *server.Server) bool {
return c.Param("server") == s.ID()
return c.Param("server") == s.Id()
})
}
if s == nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": "The requested resource does not exist on this instance."})
return
}
c.Set("logger", ExtractLogger(c).WithField("server_id", s.ID()))
c.Set("logger", ExtractLogger(c).WithField("server_id", s.Id()))
c.Set("server", s)
c.Next()
}
@@ -353,3 +354,19 @@ func ExtractManager(c *gin.Context) *server.Manager {
}
panic("middleware/middleware: cannot extract server manager: not present in context")
}
func Metrics() gin.HandlerFunc {
return func(c *gin.Context) {
path := c.Request.URL.Path
rawQuery := c.Request.URL.RawQuery
c.Next()
// Skip over the server websocket endpoint.
if strings.HasSuffix(c.FullPath(), "/ws") {
return
}
metrics.HTTPRequestsTotal.WithLabelValues(c.Request.Method, c.FullPath(), path, rawQuery, strconv.Itoa(c.Writer.Status())).Inc()
}
}

View File

@@ -3,7 +3,6 @@ package router
import (
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/remote"
"github.com/pterodactyl/wings/router/middleware"
"github.com/pterodactyl/wings/server"
@@ -15,6 +14,7 @@ func Configure(m *server.Manager, client remote.Client) *gin.Engine {
router := gin.New()
router.Use(gin.Recovery())
router.Use(middleware.Metrics())
router.Use(middleware.AttachRequestID(), middleware.CaptureErrors(), middleware.SetAccessControlHeaders())
router.Use(middleware.AttachServerManager(m), middleware.AttachApiClient(client))
// @todo log this into a different file so you can setup IP blocking for abusive requests and such.

View File

@@ -8,7 +8,6 @@ import (
"strconv"
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/router/middleware"
"github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server/backup"

View File

@@ -10,7 +10,6 @@ import (
"emperror.dev/errors"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/router/downloader"
"github.com/pterodactyl/wings/router/middleware"
"github.com/pterodactyl/wings/router/tokens"
@@ -196,7 +195,7 @@ func deleteServer(c *gin.Context) {
s.Websockets().CancelAll()
// Remove any pending remote file downloads for the server.
for _, dl := range downloader.ByServer(s.ID()) {
for _, dl := range downloader.ByServer(s.Id()) {
dl.Cancel()
}
@@ -221,7 +220,7 @@ func deleteServer(c *gin.Context) {
}(s.Filesystem().Path())
middleware.ExtractManager(c).Remove(func(server *server.Server) bool {
return server.ID() == s.ID()
return server.Id() == s.Id()
})
// Deallocate the reference to this server.

View File

@@ -8,7 +8,6 @@ import (
"emperror.dev/errors"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/router/middleware"
"github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/server/backup"
@@ -43,7 +42,7 @@ func postServerBackup(c *gin.Context) {
// Attach the server ID and the request ID to the adapter log context for easier
// parsing in the logs.
adapter.WithLogContext(map[string]interface{}{
"server": s.ID(),
"server": s.Id(),
"request_id": c.GetString("request_id"),
})

View File

@@ -16,13 +16,12 @@ import (
"emperror.dev/errors"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
"github.com/pterodactyl/wings/router/downloader"
"github.com/pterodactyl/wings/router/middleware"
"github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/server/filesystem"
"golang.org/x/sync/errgroup"
)
// getServerFileContents returns the contents of a file on the server.
@@ -246,7 +245,7 @@ func postServerWriteFile(c *gin.Context) {
func getServerPullingFiles(c *gin.Context) {
s := ExtractServer(c)
c.JSON(http.StatusOK, gin.H{
"downloads": downloader.ByServer(s.ID()),
"downloads": downloader.ByServer(s.Id()),
})
}
@@ -254,20 +253,13 @@ func getServerPullingFiles(c *gin.Context) {
func postServerPullRemoteFile(c *gin.Context) {
s := ExtractServer(c)
var data struct {
// Deprecated
Directory string `binding:"required_without=RootPath,omitempty" json:"directory"`
RootPath string `binding:"required_without=Directory,omitempty" json:"root"`
URL string `binding:"required" json:"url"`
Directory string `binding:"required,omitempty" json:"directory"`
}
if err := c.BindJSON(&data); err != nil {
return
}
// Handle the deprecated Directory field in the struct until it is removed.
if data.Directory != "" && data.RootPath == "" {
data.RootPath = data.Directory
}
u, err := url.Parse(data.URL)
if err != nil {
if e, ok := err.(*url.Error); ok {
@@ -285,7 +277,7 @@ func postServerPullRemoteFile(c *gin.Context) {
return
}
// Do not allow more than three simultaneous remote file downloads at one time.
if len(downloader.ByServer(s.ID())) >= 3 {
if len(downloader.ByServer(s.Id())) >= 3 {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"error": "This server has reached its limit of 3 simultaneous remote file downloads at once. Please wait for one to complete before trying again.",
})
@@ -293,11 +285,11 @@ func postServerPullRemoteFile(c *gin.Context) {
}
dl := downloader.New(s, downloader.DownloadRequest{
Directory: data.RootPath,
URL: u,
Directory: data.Directory,
})
// Execute this pull in a separate thread since it may take a long time to complete.
// Execute this pull in a seperate thread since it may take a long time to complete.
go func() {
s.Log().WithField("download_id", dl.Identifier).WithField("url", u.String()).Info("starting pull of remote file to disk")
if err := dl.Execute(); err != nil {

View File

@@ -7,7 +7,6 @@ import (
"github.com/gin-gonic/gin"
ws "github.com/gorilla/websocket"
"github.com/pterodactyl/wings/router/middleware"
"github.com/pterodactyl/wings/router/websocket"
)

View File

@@ -2,14 +2,11 @@ package router
import (
"bytes"
"context"
"errors"
"net/http"
"strings"
"github.com/apex/log"
"github.com/gin-gonic/gin"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/installer"
"github.com/pterodactyl/wings/router/middleware"
@@ -68,30 +65,14 @@ func postCreateServer(c *gin.Context) {
// cycle. If there are any errors they will be logged and communicated back
// to the Panel where a reinstall may take place.
go func(i *installer.Installer) {
if err := i.Server().CreateEnvironment(); err != nil {
err := i.Server().CreateEnvironment()
if err != nil {
i.Server().Log().WithField("error", err).Error("failed to create server environment during install process")
return
}
if err := i.Server().Install(false); err != nil {
log.WithFields(log.Fields{"server": i.Uuid(), "error": err}).Error("failed to run install process for server")
return
}
if i.Server().Config().StartOnCompletion {
log.WithField("server_id", i.Server().ID()).Debug("starting server after successful installation")
if err := i.Server().HandlePowerAction(server.PowerActionStart, 30); err != nil {
if errors.Is(err, context.DeadlineExceeded) {
log.WithFields(log.Fields{"server_id": i.Server().ID(), "action": "start"}).
Warn("could not acquire a lock while attempting to perform a power action")
} else {
log.WithFields(log.Fields{"server_id": i.Server().ID(), "action": "start", "error": err}).
Error("encountered error processing a server power action in the background")
}
}
} else {
log.WithField("server_id", i.Server().ID()).
Debug("skipping automatic start after successful server installation")
}
}(install)

View File

@@ -23,7 +23,6 @@ import (
"github.com/juju/ratelimit"
"github.com/mholt/archiver/v3"
"github.com/mitchellh/colorstring"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/installer"
"github.com/pterodactyl/wings/remote"
@@ -76,14 +75,14 @@ func getServerArchive(c *gin.Context) {
}
s := ExtractServer(c)
if token.Subject != s.ID() {
if token.Subject != s.Id() {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
"error": "Missing required token subject, or subject is not valid for the requested server.",
})
return
}
archivePath := getArchivePath(s.ID())
archivePath := getArchivePath(s.Id())
// Stat the archive file.
st, err := os.Lstat(archivePath)
@@ -124,7 +123,7 @@ func getServerArchive(c *gin.Context) {
c.Header("X-Checksum", checksum)
c.Header("X-Mime-Type", "application/tar+gzip")
c.Header("Content-Length", strconv.Itoa(int(st.Size())))
c.Header("Content-Disposition", "attachment; filename="+strconv.Quote(s.ID()+".tar.gz"))
c.Header("Content-Disposition", "attachment; filename="+strconv.Quote(s.Id()+".tar.gz"))
c.Header("Content-Type", "application/octet-stream")
_, _ = bufio.NewReader(f).WriteTo(c.Writer)
@@ -135,7 +134,7 @@ func postServerArchive(c *gin.Context) {
manager := middleware.ExtractManager(c)
go func(s *server.Server) {
l := log.WithField("server", s.ID())
l := log.WithField("server", s.Id())
// This function automatically adds the Source Node prefix and Timestamp to the log
// output before sending it over the websocket.
@@ -158,7 +157,7 @@ func postServerArchive(c *gin.Context) {
s.Events().Publish(server.TransferStatusEvent, "failure")
sendTransferLog("Attempting to notify panel of archive failure..")
if err := manager.Client().SetArchiveStatus(s.Context(), s.ID(), false); err != nil {
if err := manager.Client().SetArchiveStatus(s.Context(), s.Id(), false); err != nil {
if !remote.IsRequestError(err) {
sendTransferLog("Failed to notify panel of archive failure: " + err.Error())
l.WithField("error", err).Error("failed to notify panel of failed archive status")
@@ -191,7 +190,7 @@ func postServerArchive(c *gin.Context) {
}
// Attempt to get an archive of the server.
if err := a.Create(getArchivePath(s.ID())); err != nil {
if err := a.Create(getArchivePath(s.Id())); err != nil {
sendTransferLog("An error occurred while archiving the server: " + err.Error())
l.WithField("error", err).Error("failed to get transfer archive for server")
return
@@ -200,7 +199,7 @@ func postServerArchive(c *gin.Context) {
sendTransferLog("Successfully created archive, attempting to notify panel..")
l.Info("successfully created server transfer archive, notifying panel..")
if err := manager.Client().SetArchiveStatus(s.Context(), s.ID(), true); err != nil {
if err := manager.Client().SetArchiveStatus(s.Context(), s.Id(), true); err != nil {
if !remote.IsRequestError(err) {
sendTransferLog("Failed to notify panel of archive success: " + err.Error())
l.WithField("error", err).Error("failed to notify panel of successful archive status")
@@ -361,7 +360,7 @@ func postTransfer(c *gin.Context) {
sendTransferLog("Server transfer failed, check Wings logs for additional information.")
s.Events().Publish(server.TransferStatusEvent, "failure")
manager.Remove(func(match *server.Server) bool {
return match.ID() == s.ID()
return match.Id() == s.Id()
})
// If the transfer status was successful but the request failed, act like the transfer failed.

View File

@@ -1,11 +1,9 @@
package tokens
import (
"time"
"github.com/gbrlsnchs/jwt/v3"
"github.com/pterodactyl/wings/config"
"time"
)
type TokenData interface {

View File

@@ -1,10 +1,9 @@
package tokens
import (
"github.com/patrickmn/go-cache"
"sync"
"time"
"github.com/patrickmn/go-cache"
)
type TokenStore struct {

View File

@@ -2,12 +2,11 @@ package tokens
import (
"encoding/json"
"github.com/apex/log"
"github.com/gbrlsnchs/jwt/v3"
"strings"
"sync"
"time"
"github.com/apex/log"
"github.com/gbrlsnchs/jwt/v3"
)
// The time at which Wings was booted. No JWT's created before this time are allowed to

View File

@@ -2,10 +2,9 @@ package websocket
import (
"context"
"time"
"github.com/pterodactyl/wings/events"
"github.com/pterodactyl/wings/server"
"time"
)
// Checks the time to expiration on the JWT every 30 seconds until the token has

View File

@@ -2,24 +2,22 @@ package websocket
import (
"context"
"emperror.dev/errors"
"encoding/json"
"fmt"
"net/http"
"strings"
"sync"
"time"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/gbrlsnchs/jwt/v3"
"github.com/google/uuid"
"github.com/gorilla/websocket"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/environment/docker"
"github.com/pterodactyl/wings/router/tokens"
"github.com/pterodactyl/wings/server"
"net/http"
"strings"
"sync"
"time"
)
const (
@@ -57,10 +55,11 @@ func IsJwtError(err error) bool {
errors.Is(err, jwt.ErrExpValidation)
}
// NewTokenPayload parses a JWT into a websocket token payload.
// Parses a JWT into a websocket token payload.
func NewTokenPayload(token []byte) (*tokens.WebsocketPayload, error) {
var payload tokens.WebsocketPayload
if err := tokens.ParseToken(token, &payload); err != nil {
payload := tokens.WebsocketPayload{}
err := tokens.ParseToken(token, &payload)
if err != nil {
return nil, err
}
@@ -181,7 +180,7 @@ func (h *Handler) unsafeSendJson(v interface{}) error {
return h.Connection.WriteJSON(v)
}
// TokenValid checks if the JWT is still valid.
// Checks if the JWT is still valid.
func (h *Handler) TokenValid() error {
j := h.GetJwt()
if j == nil {
@@ -200,14 +199,14 @@ func (h *Handler) TokenValid() error {
return ErrJwtNoConnectPerm
}
if h.server.ID() != j.GetServerUuid() {
if h.server.Id() != j.GetServerUuid() {
return ErrJwtUuidMismatch
}
return nil
}
// SendErrorJson sends an error back to the connected websocket instance by checking the permissions
// Sends an error back to the connected websocket instance by checking the permissions
// of the token. If the user has the "receive-errors" grant we will send back the actual
// error message, otherwise we just send back a standard error message.
func (h *Handler) SendErrorJson(msg Message, err error, shouldLog ...bool) error {
@@ -237,7 +236,7 @@ func (h *Handler) SendErrorJson(msg Message, err error, shouldLog ...bool) error
return h.unsafeSendJson(wsm)
}
// GetErrorMessage converts an error message into a more readable representation and returns a UUID
// Converts an error message into a more readable representation and returns a UUID
// that can be cross-referenced to find the specific error that triggered.
func (h *Handler) GetErrorMessage(msg string) (string, uuid.UUID) {
u := uuid.Must(uuid.NewRandom())
@@ -247,7 +246,13 @@ func (h *Handler) GetErrorMessage(msg string) (string, uuid.UUID) {
return m, u
}
// GetJwt returns the JWT for the websocket in a race-safe manner.
// Sets the JWT for the websocket in a race-safe manner.
func (h *Handler) setJwt(token *tokens.WebsocketPayload) {
h.Lock()
h.jwt = token
h.Unlock()
}
func (h *Handler) GetJwt() *tokens.WebsocketPayload {
h.RLock()
defer h.RUnlock()
@@ -255,14 +260,7 @@ func (h *Handler) GetJwt() *tokens.WebsocketPayload {
return h.jwt
}
// setJwt sets the JWT for the websocket in a race-safe manner.
func (h *Handler) setJwt(token *tokens.WebsocketPayload) {
h.Lock()
h.jwt = token
h.Unlock()
}
// HandleInbound handles an inbound socket request and route it to the proper action.
// Handle the inbound socket request and route it to the proper server action.
func (h *Handler) HandleInbound(m Message) error {
if m.Event != AuthenticationEvent {
if err := h.TokenValid(); err != nil {

View File

@@ -2,14 +2,12 @@ package server
import (
"io"
"io/fs"
"io/ioutil"
"os"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/docker/docker/client"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/remote"
"github.com/pterodactyl/wings/server/backup"
@@ -62,7 +60,7 @@ func (s *Server) Backup(b backup.BackupInterface) error {
ignored := b.Ignored()
if b.Ignored() == "" {
if i, err := s.getServerwideIgnoredFiles(); err != nil {
log.WithField("server", s.ID()).WithField("error", err).Warn("failed to get server-wide ignored files")
log.WithField("server", s.Id()).WithField("error", err).Warn("failed to get server-wide ignored files")
} else {
ignored = i
}
@@ -152,12 +150,9 @@ func (s *Server) RestoreBackup(b backup.BackupInterface, reader io.ReadCloser) (
// Attempt to restore the backup to the server by running through each entry
// in the file one at a time and writing them to the disk.
s.Log().Debug("starting file writing process for backup restoration")
err = b.Restore(s.Context(), reader, func(file string, r io.Reader, mode fs.FileMode) error {
err = b.Restore(s.Context(), reader, func(file string, r io.Reader) error {
s.Events().Publish(DaemonMessageEvent, "(restoring): "+file)
if err := s.Filesystem().Writefile(file, r); err != nil {
return err
}
return s.Filesystem().Chmod(file, mode)
return s.Filesystem().Writefile(file, r)
})
return errors.WithStackIf(err)

View File

@@ -5,16 +5,14 @@ import (
"crypto/sha1"
"encoding/hex"
"io"
"io/fs"
"os"
"path"
"emperror.dev/errors"
"github.com/apex/log"
"golang.org/x/sync/errgroup"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/remote"
"golang.org/x/sync/errgroup"
)
type AdapterType string
@@ -26,7 +24,7 @@ const (
// RestoreCallback is a generic restoration callback that exists for both local
// and remote backups allowing the files to be restored.
type RestoreCallback func(file string, r io.Reader, mode fs.FileMode) error
type RestoreCallback func(file string, r io.Reader) error
// noinspection GoNameStartsWithPackageName
type BackupInterface interface {

View File

@@ -6,10 +6,10 @@ import (
"os"
"emperror.dev/errors"
"github.com/mholt/archiver/v3"
"github.com/pterodactyl/wings/remote"
"github.com/pterodactyl/wings/server/filesystem"
"github.com/mholt/archiver/v3"
"github.com/pterodactyl/wings/remote"
)
type LocalBackup struct {
@@ -85,10 +85,12 @@ func (b *LocalBackup) Restore(ctx context.Context, _ io.Reader, callback Restore
// Stop walking if the context is canceled.
return archiver.ErrStopWalk
default:
{
if f.IsDir() {
return nil
}
return callback(filesystem.ExtractNameFromArchive(f), f, f.Mode())
return callback(filesystem.ExtractNameFromArchive(f), f)
}
}
})
}

View File

@@ -13,11 +13,9 @@ import (
"emperror.dev/errors"
"github.com/cenkalti/backoff/v4"
"github.com/pterodactyl/wings/server/filesystem"
"github.com/juju/ratelimit"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/remote"
)
@@ -116,7 +114,7 @@ func (s *S3Backup) Restore(ctx context.Context, r io.Reader, callback RestoreCal
return err
}
if header.Typeflag == tar.TypeReg {
if err := callback(header.Name, tr, header.FileInfo().Mode()); err != nil {
if err := callback(header.Name, tr); err != nil {
return err
}
}

View File

@@ -33,9 +33,7 @@ type Configuration struct {
// By default this is false, however if selected within the Panel while installing or re-installing a
// server, specific installation scripts will be skipped for the server process.
SkipEggScripts bool `json:"skip_egg_scripts"`
StartOnCompletion bool `json:"start_on_completion"`
SkipEggScripts bool `default:"false" json:"skip_egg_scripts"`
// An array of environment variables that should be passed along to the running
// server process.
@@ -43,7 +41,7 @@ type Configuration struct {
Allocations environment.Allocations `json:"allocations"`
Build environment.Limits `json:"build"`
CrashDetectionEnabled bool `json:"crash_detection_enabled"`
CrashDetectionEnabled bool `default:"true" json:"enabled" yaml:"enabled"`
Mounts []Mount `json:"mounts"`
Egg EggConfiguration `json:"egg,omitempty"`
@@ -56,30 +54,34 @@ type Configuration struct {
func (s *Server) Config() *Configuration {
s.cfg.mu.RLock()
defer s.cfg.mu.RUnlock()
return &s.cfg
}
// DiskSpace returns the amount of disk space available to a server in bytes.
// Returns the amount of disk space available to a server in bytes.
func (s *Server) DiskSpace() int64 {
s.cfg.mu.RLock()
defer s.cfg.mu.RUnlock()
return s.cfg.Build.DiskSpace * 1024.0 * 1024.0
}
func (s *Server) MemoryLimit() int64 {
s.cfg.mu.RLock()
defer s.cfg.mu.RUnlock()
return s.cfg.Build.MemoryLimit
}
func (c *Configuration) GetUuid() string {
c.mu.RLock()
defer c.mu.RUnlock()
return c.Uuid
}
func (c *Configuration) SetSuspended(s bool) {
c.mu.Lock()
defer c.mu.Unlock()
c.Suspended = s
c.mu.Unlock()
}

View File

@@ -9,7 +9,6 @@ import (
"emperror.dev/errors"
"github.com/mitchellh/colorstring"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system"
)

View File

@@ -3,7 +3,6 @@ package filesystem
import (
"archive/tar"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
@@ -14,9 +13,8 @@ import (
"github.com/juju/ratelimit"
"github.com/karrick/godirwalk"
"github.com/klauspost/pgzip"
ignore "github.com/sabhiram/go-gitignore"
"github.com/pterodactyl/wings/config"
"github.com/sabhiram/go-gitignore"
)
const memory = 4 * 1024
@@ -158,15 +156,9 @@ func (a *Archive) addToArchive(p string, rp string, w *tar.Writer) error {
return errors.WrapIff(err, "failed executing os.Lstat on '%s'", rp)
}
// Skip socket files as they are unsupported by archive/tar.
// Error will come from tar#FileInfoHeader: "archive/tar: sockets not supported"
if s.Mode()&fs.ModeSocket != 0 {
return nil
}
// Resolve the symlink target if the file is a symlink.
var target string
if s.Mode()&fs.ModeSymlink != 0 {
if s.Mode()&os.ModeSymlink != 0 {
// Read the target of the symlink. If there are any errors we will dump them out to
// the logs, but we're not going to stop the backup. There are far too many cases of
// symlinks causing all sorts of unnecessary pain in this process. Sucks to suck if
@@ -188,7 +180,7 @@ func (a *Archive) addToArchive(p string, rp string, w *tar.Writer) error {
}
// Fix the header name if the file is not a symlink.
if s.Mode()&fs.ModeSymlink == 0 {
if s.Mode()&os.ModeSymlink == 0 {
header.Name = rp
}

View File

@@ -132,10 +132,6 @@ func (fs *Filesystem) DecompressFile(dir string, file string) error {
if err := fs.Writefile(p, f); err != nil {
return wrapError(err, source)
}
// Update the file permissions to the one set in the archive.
if err := fs.Chmod(p, f.Mode()); err != nil {
return wrapError(err, source)
}
return nil
})
if err != nil {

View File

@@ -17,10 +17,9 @@ import (
"emperror.dev/errors"
"github.com/gabriel-vasile/mimetype"
"github.com/karrick/godirwalk"
ignore "github.com/sabhiram/go-gitignore"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system"
ignore "github.com/sabhiram/go-gitignore"
)
type Filesystem struct {

View File

@@ -12,7 +12,6 @@ import (
"unicode/utf8"
. "github.com/franela/goblin"
"github.com/pterodactyl/wings/config"
)

View File

@@ -17,7 +17,6 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/client"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/remote"
@@ -89,7 +88,7 @@ func (s *Server) Reinstall() error {
// Internal installation function used to simplify reporting back to the Panel.
func (s *Server) internalInstall() error {
script, err := s.client.GetInstallationScript(s.Context(), s.ID())
script, err := s.client.GetInstallationScript(s.Context(), s.Id())
if err != nil {
return err
}
@@ -157,7 +156,7 @@ func (s *Server) SetRestoring(state bool) {
// Removes the installer container for the server.
func (ip *InstallationProcess) RemoveContainer() error {
err := ip.client.ContainerRemove(ip.context, ip.Server.ID()+"_installer", types.ContainerRemoveOptions{
err := ip.client.ContainerRemove(ip.context, ip.Server.Id()+"_installer", types.ContainerRemoveOptions{
RemoveVolumes: true,
Force: true,
})
@@ -207,7 +206,7 @@ func (ip *InstallationProcess) Run() error {
// Returns the location of the temporary data for the installation process.
func (ip *InstallationProcess) tempDir() string {
return filepath.Join(os.TempDir(), "pterodactyl/", ip.Server.ID())
return filepath.Join(os.TempDir(), "pterodactyl/", ip.Server.Id())
}
// Writes the installation script to a temporary file on the host machine so that it
@@ -330,7 +329,7 @@ func (ip *InstallationProcess) BeforeExecute() error {
// Returns the log path for the installation process.
func (ip *InstallationProcess) GetLogPath() string {
return filepath.Join(config.Get().System.LogDirectory, "/install", ip.Server.ID()+".log")
return filepath.Join(config.Get().System.LogDirectory, "/install", ip.Server.Id()+".log")
}
// Cleans up after the execution of the installation process. This grabs the logs from the
@@ -366,7 +365,7 @@ func (ip *InstallationProcess) AfterExecute(containerId string) error {
|
| Details
| ------------------------------
Server UUID: {{.Server.ID}}
Server UUID: {{.Server.Id}}
Container Image: {{.Script.ContainerImage}}
Container Entrypoint: {{.Script.Entrypoint}}
@@ -470,7 +469,7 @@ func (ip *InstallationProcess) Execute() (string, error) {
}
}()
r, err := ip.client.ContainerCreate(ctx, conf, hostConf, nil, nil, ip.Server.ID()+"_installer")
r, err := ip.client.ContainerCreate(ctx, conf, hostConf, nil, nil, ip.Server.Id()+"_installer")
if err != nil {
return "", err
}
@@ -574,5 +573,5 @@ func (ip *InstallationProcess) resourceLimits() container.Resources {
// server is. A boolean value of "true" means everything was successful, "false"
// means something went wrong and the server must be deleted and re-created.
func (s *Server) SyncInstallState(successful bool) error {
return s.client.SetInstallationStatus(s.Context(), s.ID(), successful)
return s.client.SetInstallationStatus(s.Context(), s.Id(), successful)
}

View File

@@ -7,7 +7,6 @@ import (
"sync"
"github.com/apex/log"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/events"
@@ -41,7 +40,7 @@ func (dsl *diskSpaceLimiter) Reset() {
// 15 seconds, and terminate it forcefully if it does not stop.
//
// This function is only executed one time, so whenever a server is marked as booting the limiter
// should be reset, so it can properly be triggered as needed.
// should be reset so it can properly be triggered as needed.
func (dsl *diskSpaceLimiter) Trigger() {
dsl.o.Do(func() {
dsl.server.PublishConsoleOutputFromDaemon("Server is exceeding the assigned disk space limit, stopping process now.")
@@ -51,7 +50,7 @@ func (dsl *diskSpaceLimiter) Trigger() {
})
}
// StartEventListeners adds all the internal event listeners we want to use for a server. These listeners can only be
// Adds all of the internal event listeners we want to use for a server. These listeners can only be
// removed by deleting the server as they should last for the duration of the process' lifetime.
func (s *Server) StartEventListeners() {
console := func(e events.Event) {
@@ -107,15 +106,15 @@ func (s *Server) StartEventListeners() {
}
stats := func(e events.Event) {
var st environment.Stats
if err := json.Unmarshal([]byte(e.Data), &st); err != nil {
st := new(environment.Stats)
if err := json.Unmarshal([]byte(e.Data), st); err != nil {
s.Log().WithField("error", err).Warn("failed to unmarshal server environment stats")
return
}
// Update the server resource tracking object with the resources we got here.
s.resources.mu.Lock()
s.resources.Stats = st
s.resources.Stats = *st
s.resources.mu.Unlock()
// If there is no disk space available at this point, trigger the server disk limiter logic

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/pterodactyl/wings/metrics"
"io"
"io/ioutil"
"os"
@@ -15,7 +16,6 @@ import (
"emperror.dev/errors"
"github.com/apex/log"
"github.com/gammazero/workerpool"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/environment/docker"
@@ -29,9 +29,9 @@ type Manager struct {
servers []*Server
}
// NewManager returns a new server manager instance. This will boot up all the
// servers that are currently present on the filesystem and set them into the
// manager.
// NewManager returns a new server manager instance. This will boot up all of
// the servers that are currently present on the filesystem and set them into
// the manager.
func NewManager(ctx context.Context, client remote.Client) (*Manager, error) {
m := NewEmptyManager(client)
if err := m.init(ctx); err != nil {
@@ -53,7 +53,7 @@ func (m *Manager) Client() remote.Client {
return m.client
}
// Put replaces all the current values in the collection with the value that
// Put replaces all of the current values in the collection with the value that
// is passed through.
func (m *Manager) Put(s []*Server) {
m.mu.Lock()
@@ -61,7 +61,7 @@ func (m *Manager) Put(s []*Server) {
m.mu.Unlock()
}
// All returns all the items in the collection.
// All returns all of the items in the collection.
func (m *Manager) All() []*Server {
m.mu.RLock()
defer m.mu.RUnlock()
@@ -73,13 +73,16 @@ func (m *Manager) Add(s *Server) {
m.mu.Lock()
m.servers = append(m.servers, s)
m.mu.Unlock()
// Add the server to the metrics with a offline status.
metrics.ServerStatus.WithLabelValues(s.Id()).Set(0)
}
// Get returns a single server instance and a boolean value indicating if it was
// found in the global collection or not.
func (m *Manager) Get(uuid string) (*Server, bool) {
match := m.Find(func(server *Server) bool {
return server.ID() == uuid
return server.Id() == uuid
})
return match, match != nil
}
@@ -118,6 +121,9 @@ func (m *Manager) Remove(filter func(match *Server) bool) {
for _, v := range m.servers {
if !filter(v) {
r = append(r, v)
} else {
// Delete the server from the metric.
metrics.DeleteServer(v.Id())
}
}
m.servers = r
@@ -131,7 +137,7 @@ func (m *Manager) Remove(filter func(match *Server) bool) {
func (m *Manager) PersistStates() error {
states := map[string]string{}
for _, s := range m.All() {
states[s.ID()] = s.Environment.State()
states[s.Id()] = s.Environment.State()
}
data, err := json.Marshal(states)
if err != nil {
@@ -176,11 +182,11 @@ func (m *Manager) InitServer(data remote.ServerConfigurationResponse) (*Server,
return nil, err
}
s.fs = filesystem.New(filepath.Join(config.Get().System.Data, s.ID()), s.DiskSpace(), s.Config().Egg.FileDenylist)
s.fs = filesystem.New(filepath.Join(config.Get().System.Data, s.Id()), s.DiskSpace(), s.Config().Egg.FileDenylist)
// Right now we only support a Docker based environment, so I'm going to hard code
// this logic in. When we're ready to support other environment we'll need to make
// some modifications here, obviously.
// some modifications here obviously.
settings := environment.Settings{
Mounts: s.Mounts(),
Allocations: s.cfg.Allocations,
@@ -192,7 +198,7 @@ func (m *Manager) InitServer(data remote.ServerConfigurationResponse) (*Server,
Image: s.Config().Container.Image,
}
if env, err := docker.New(s.ID(), &meta, envCfg); err != nil {
if env, err := docker.New(s.Id(), &meta, envCfg); err != nil {
return nil, err
} else {
s.Environment = env
@@ -213,7 +219,7 @@ func (m *Manager) InitServer(data remote.ServerConfigurationResponse) (*Server,
return s, nil
}
// initializeFromRemoteSource iterates over a given directory and loads all
// initializeFromRemoteSource iterates over a given directory and loads all of
// the servers listed before returning them to the calling function.
func (m *Manager) init(ctx context.Context) error {
log.Info("fetching list of servers from API")
@@ -253,7 +259,7 @@ func (m *Manager) init(ctx context.Context) error {
})
}
// Wait until we've processed all the configuration files in the directory
// Wait until we've processed all of the configuration files in the directory
// before continuing.
pool.StopWait()

View File

@@ -5,7 +5,6 @@ import (
"strings"
"github.com/apex/log"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
)

View File

@@ -6,10 +6,9 @@ import (
"time"
"emperror.dev/errors"
"golang.org/x/sync/semaphore"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"golang.org/x/sync/semaphore"
)
type PowerAction string
@@ -19,7 +18,7 @@ type PowerAction string
// example, sending two "start" actions back to back will not process the second action until
// the first action has been completed.
//
// This utilizes a workerpool with a limit of one worker so that all the actions execute
// This utilizes a workerpool with a limit of one worker so that all of the actions execute
// in a sync manner.
const (
PowerActionStart = "start"
@@ -28,7 +27,7 @@ const (
PowerActionTerminate = "kill"
)
// IsValid checks if the power action being received is valid.
// Checks if the power action being received is valid.
func (pa PowerAction) IsValid() bool {
return pa == PowerActionStart ||
pa == PowerActionStop ||
@@ -40,7 +39,7 @@ func (pa PowerAction) IsStart() bool {
return pa == PowerActionStart || pa == PowerActionRestart
}
// ExecutingPowerAction checks if there is currently a power action being processed for the server.
// Check if there is currently a power action being processed for the server.
func (s *Server) ExecutingPowerAction() bool {
if s.powerLock == nil {
return false
@@ -55,9 +54,9 @@ func (s *Server) ExecutingPowerAction() bool {
return !ok
}
// HandlePowerAction is a helper function that can receive a power action and then process the
// actions that need to occur for it. This guards against someone calling Start() twice at the
// same time, or trying to restart while another restart process is currently running.
// Helper function that can receive a power action and then process the actions that need
// to occur for it. This guards against someone calling Start() twice at the same time, or
// trying to restart while another restart process is currently running.
//
// However, the code design for the daemon does depend on the user correctly calling this
// function rather than making direct calls to the start/stop/restart functions on the
@@ -108,7 +107,7 @@ func (s *Server) HandlePowerAction(action PowerAction, waitSeconds ...int) error
// Release the lock once the process being requested has finished executing.
defer s.powerLock.Release(1)
} else {
// Still try to acquire the lock if terminating, and it is available, just so that other power
// Still try to acquire the lock if terminating and it is available, just so that other power
// actions are blocked until it has completed. However, if it is unavailable we won't stop
// the entire process.
if ok := s.powerLock.TryAcquire(1); ok {
@@ -191,14 +190,14 @@ func (s *Server) onBeforeStart() error {
// Update the configuration files defined for the server before beginning the boot process.
// This process executes a bunch of parallel updates, so we just block until that process
// is complete. Any errors as a result of this will just be bubbled out in the logger,
// we don't need to actively do anything about it at this point, worse comes to worst the
// we don't need to actively do anything about it at this point, worst comes to worst the
// server starts in a weird state and the user can manually adjust.
s.PublishConsoleOutputFromDaemon("Updating process configuration files...")
s.UpdateConfigurationFiles()
if config.Get().System.CheckPermissionsOnBoot {
s.PublishConsoleOutputFromDaemon("Ensuring file permissions are set correctly, this could take a few seconds...")
// Ensure all 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 {
return errors.WithMessage(err, "failed to chown root server directory during pre-boot process")
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/pterodactyl/wings/system"
)
// ResourceUsage defines the current resource usage for a given server instance. If a server is offline you
// Defines the current resource usage for a given server instance. If a server is offline you
// should obviously expect memory and CPU usage to be 0. However, disk will always be returned
// since that is not dependent on the server being running to collect that data.
type ResourceUsage struct {
@@ -26,7 +26,7 @@ type ResourceUsage struct {
Disk int64 `json:"disk_bytes"`
}
// Proc returns the current resource usage stats for the server instance. This returns
// Returns the current resource usage stats for the server instance. This returns
// a copy of the tracked resources, so making any changes to the response will not
// have the desired outcome for you most likely.
func (s *Server) Proc() ResourceUsage {
@@ -38,12 +38,11 @@ func (s *Server) Proc() ResourceUsage {
return s.resources
}
// Reset resets the usages values to zero, used when a server is stopped to ensure we don't hold
// Resets the usages values to zero, used when a server is stopped to ensure we don't hold
// onto any values incorrectly.
func (ru *ResourceUsage) Reset() {
ru.mu.Lock()
defer ru.mu.Unlock()
ru.Memory = 0
ru.CpuAbsolute = 0
ru.Network.TxBytes = 0

View File

@@ -11,8 +11,6 @@ import (
"emperror.dev/errors"
"github.com/apex/log"
"github.com/creasty/defaults"
"golang.org/x/sync/semaphore"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
"github.com/pterodactyl/wings/environment/docker"
@@ -20,6 +18,7 @@ import (
"github.com/pterodactyl/wings/remote"
"github.com/pterodactyl/wings/server/filesystem"
"github.com/pterodactyl/wings/system"
"golang.org/x/sync/semaphore"
)
// Server is the high level definition for a server instance being controlled
@@ -94,17 +93,9 @@ func New(client remote.Client) (*Server, error) {
return &s, nil
}
// ID returns the UUID for the server instance.
func (s *Server) ID() string {
return s.Config().GetUuid()
}
// Id returns the UUID for the server instance. This function is deprecated
// in favor of Server.ID().
//
// Deprecated
// Id returns the UUID for the server instance.
func (s *Server) Id() string {
return s.ID()
return s.Config().GetUuid()
}
// Cancels the context assigned to this server instance. Assuming background tasks
@@ -138,7 +129,7 @@ eloop:
for k := range s.Config().EnvVars {
// Don't allow any environment variables that we have already set above.
for _, e := range out {
if strings.HasPrefix(e, strings.ToUpper(k)+"=") {
if strings.HasPrefix(e, strings.ToUpper(k)) {
continue eloop
}
}
@@ -150,7 +141,7 @@ eloop:
}
func (s *Server) Log() *log.Entry {
return log.WithField("server", s.ID())
return log.WithField("server", s.Id())
}
// Sync syncs the state of the server on the Panel with Wings. This ensures that
@@ -160,7 +151,7 @@ func (s *Server) Log() *log.Entry {
// This also means mass actions can be performed against servers on the Panel
// and they will automatically sync with Wings when the server is started.
func (s *Server) Sync() error {
cfg, err := s.client.GetServerConfiguration(s.Context(), s.ID())
cfg, err := s.client.GetServerConfiguration(s.Context(), s.Id())
if err != nil {
if err := remote.AsRequestError(err); err != nil && err.StatusCode() == http.StatusNotFound {
return &serverDoesNotExist{}
@@ -255,7 +246,7 @@ func (s *Server) EnsureDataDirectoryExists() error {
return nil
}
// OnStateChange sets the state of the server internally. This function handles crash detection as
// Sets the state of the server internally. This function handles crash detection as
// well as reporting to event listeners for the server.
func (s *Server) OnStateChange() {
prevState := s.resources.State.Load()
@@ -270,7 +261,7 @@ func (s *Server) OnStateChange() {
s.Events().Publish(StatusEvent, st)
}
// Reset the resource usage to 0 when the process fully stops so that all the UI
// Reset the resource usage to 0 when the process fully stops so that all of the UI
// views in the Panel correctly display 0.
if st == environment.ProcessOfflineState {
s.resources.Reset()
@@ -302,7 +293,7 @@ func (s *Server) OnStateChange() {
}
// IsRunning determines if the server state is running or not. This is different
// from the environment state, it is simply the tracked state from this daemon
// than the environment state, it is simply the tracked state from this daemon
// instance, and not the response from Docker.
func (s *Server) IsRunning() bool {
st := s.Environment.State()

View File

@@ -6,7 +6,6 @@ import (
"emperror.dev/errors"
"github.com/buger/jsonparser"
"github.com/imdario/mergo"
"github.com/pterodactyl/wings/environment"
)
@@ -26,7 +25,7 @@ func (s *Server) UpdateDataStructure(data []byte) error {
// Don't allow obviously corrupted data to pass through into this function. If the UUID
// doesn't match something has gone wrong and the API is attempting to meld this server
// instance into a totally different one, which would be bad.
if src.Uuid != "" && s.ID() != "" && src.Uuid != s.ID() {
if src.Uuid != "" && s.Id() != "" && src.Uuid != s.Id() {
return errors.New("server/update: attempting to merge a data stack with an invalid UUID")
}

View File

@@ -12,7 +12,7 @@ type WebsocketBag struct {
conns map[uuid.UUID]*context.CancelFunc
}
// Websockets returns the websocket bag which contains all the currently open websocket connections
// Returns the websocket bag which contains all of the currently open websocket connections
// for the server instance.
func (s *Server) Websockets() *WebsocketBag {
s.wsBagLocker.Lock()
@@ -25,7 +25,7 @@ func (s *Server) Websockets() *WebsocketBag {
return s.wsBag
}
// Push adds a new websocket connection to the end of the stack.
// Adds a new websocket connection to the stack.
func (w *WebsocketBag) Push(u uuid.UUID, cancel *context.CancelFunc) {
w.mu.Lock()
defer w.mu.Unlock()
@@ -37,14 +37,14 @@ func (w *WebsocketBag) Push(u uuid.UUID, cancel *context.CancelFunc) {
w.conns[u] = cancel
}
// Remove removes a connection from the stack.
// Removes a connection from the stack.
func (w *WebsocketBag) Remove(u uuid.UUID) {
w.mu.Lock()
delete(w.conns, u)
w.mu.Unlock()
}
// CancelAll cancels all the stored cancel functions which has the effect of disconnecting
// Cancels all of the stored cancel functions which has the effect of disconnecting
// every listening websocket for the server.
func (w *WebsocketBag) CancelAll() {
w.mu.Lock()

View File

@@ -11,10 +11,9 @@ import (
"emperror.dev/errors"
"github.com/apex/log"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/server/filesystem"
"golang.org/x/crypto/ssh"
)
const (

View File

@@ -18,11 +18,10 @@ import (
"emperror.dev/errors"
"github.com/apex/log"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/remote"
"github.com/pterodactyl/wings/server"
"golang.org/x/crypto/ssh"
)
// Usernames all follow the same format, so don't even bother hitting the API if the username is not
@@ -133,7 +132,7 @@ func (c *SFTPServer) AcceptInbound(conn net.Conn, config *ssh.ServerConfig) {
if uuid == "" {
return false
}
return s.ID() == uuid
return s.Id() == uuid
})
if srv == nil {
continue