Merge branch 'develop' into v2
This commit is contained in:
		
						commit
						6653466ca8
					
				
							
								
								
									
										2
									
								
								.github/workflows/build-test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/build-test.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -12,7 +12,7 @@ jobs:
 | 
				
			||||||
      fail-fast: false
 | 
					      fail-fast: false
 | 
				
			||||||
      matrix:
 | 
					      matrix:
 | 
				
			||||||
        os: [ ubuntu-20.04 ]
 | 
					        os: [ ubuntu-20.04 ]
 | 
				
			||||||
        go: [ '^1.16' ]
 | 
					        go: [ '^1.17' ]
 | 
				
			||||||
        goos: [ linux ]
 | 
					        goos: [ linux ]
 | 
				
			||||||
        goarch: [ amd64, arm64 ]
 | 
					        goarch: [ amd64, arm64 ]
 | 
				
			||||||
    runs-on: ${{ matrix.os }}
 | 
					    runs-on: ${{ matrix.os }}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -11,7 +11,7 @@ jobs:
 | 
				
			||||||
        uses: actions/checkout@v2
 | 
					        uses: actions/checkout@v2
 | 
				
			||||||
      - uses: actions/setup-go@v2
 | 
					      - uses: actions/setup-go@v2
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          go-version: '^1.16'
 | 
					          go-version: '^1.17'
 | 
				
			||||||
      - name: Build
 | 
					      - name: Build
 | 
				
			||||||
        env:
 | 
					        env:
 | 
				
			||||||
          REF: ${{ github.ref }}
 | 
					          REF: ${{ github.ref }}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										40
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| 
						 | 
					@ -1,5 +1,45 @@
 | 
				
			||||||
# Changelog
 | 
					# Changelog
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.5.0
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					* Fixes a race condition when setting the application name in the console output for a server.
 | 
				
			||||||
 | 
					* Fixes a server being reinstalled causing the `file_denylist` parameter for an Egg to be ignored until Wings is restarted.
 | 
				
			||||||
 | 
					* Fixes YAML file parser not correctly setting boolean values.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Added
 | 
				
			||||||
 | 
					* Exposes `8080` in the default Docker setup to better support proxy tools.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Changed
 | 
				
			||||||
 | 
					* Releases are now built using `Go 1.17` — the minimum version required to build Wings remains `Go 1.16`.
 | 
				
			||||||
 | 
					* Simplifed the logic powering server updates to only pull information from the Panel rather than trying to accept updated values. All parts of Wings needing the most up-to-date server details should call `Server#Sync()` to fetch the latest stored build information.
 | 
				
			||||||
 | 
					* `Installer#New()` no longer requires passing all of the server data as a byte slice, rather a new `Installer#ServerDetails` struct is exposed which can be passed and accepts a UUID and if the server should be started after the installer finishes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Removed
 | 
				
			||||||
 | 
					* Removes complicated (and unused) logic during the server installation process that was a hold-over from legacy Wings architectures.
 | 
				
			||||||
 | 
					* Removes the `PATCH /api/servers/:server` endpoint — if you were previously using this API call it should be replaced with `POST /api/servers/:server/sync`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## v1.4.7
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					* SFTP access is now properly denied if a server is suspended.
 | 
				
			||||||
 | 
					* Correctly uses `start_on_completion` and `crash_detection_enabled` for servers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 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
 | 
					## v1.4.5
 | 
				
			||||||
### Changed
 | 
					### 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.
 | 
					* 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.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
# Stage 1 (Build)
 | 
					# Stage 1 (Build)
 | 
				
			||||||
FROM --platform=$BUILDPLATFORM golang:1.16-alpine AS builder
 | 
					FROM --platform=$BUILDPLATFORM golang:1.17-alpine AS builder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ARG VERSION
 | 
					ARG VERSION
 | 
				
			||||||
RUN apk add --update --no-cache git make upx
 | 
					RUN apk add --update --no-cache git make upx
 | 
				
			||||||
| 
						 | 
					@ -19,5 +19,8 @@ RUN echo "ID=\"distroless\"" > /etc/os-release
 | 
				
			||||||
# Stage 2 (Final)
 | 
					# Stage 2 (Final)
 | 
				
			||||||
FROM gcr.io/distroless/static:latest
 | 
					FROM gcr.io/distroless/static:latest
 | 
				
			||||||
COPY --from=builder /etc/os-release /etc/os-release
 | 
					COPY --from=builder /etc/os-release /etc/os-release
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY --from=builder /app/wings /usr/bin/
 | 
					COPY --from=builder /app/wings /usr/bin/
 | 
				
			||||||
CMD [ "/usr/bin/wings", "--config", "/etc/pterodactyl/config.yml" ]
 | 
					CMD [ "/usr/bin/wings", "--config", "/etc/pterodactyl/config.yml" ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					EXPOSE 8080
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,6 +32,9 @@ I would like to extend my sincere thanks to the following sponsors for helping f
 | 
				
			||||||
| [**HostBend**](https://hostbend.com/) | HostBend offers a variety of solutions for developers, students, and others who have a tight budget but don't want to compromise quality and support. |
 | 
					| [**HostBend**](https://hostbend.com/) | HostBend offers a variety of solutions for developers, students, and others who have a tight budget but don't want to compromise quality and support. |
 | 
				
			||||||
| [**Capitol Hosting Solutions**](https://capitolsolutions.cloud/) | CHS is *the* budget friendly hosting company for Australian and American gamers, offering a variety of plans from Web Hosting to Game Servers; Custom Solutions too! |
 | 
					| [**Capitol Hosting Solutions**](https://capitolsolutions.cloud/) | CHS is *the* budget friendly hosting company for Australian and American gamers, offering a variety of plans from Web Hosting to Game Servers; Custom Solutions too! |
 | 
				
			||||||
| [**ByteAnia**](https://byteania.com/?utm_source=pterodactyl) | ByteAnia offers the best performing and most affordable **Ryzen 5000 Series hosting** on the market for *unbeatable prices*! |
 | 
					| [**ByteAnia**](https://byteania.com/?utm_source=pterodactyl) | ByteAnia offers the best performing and most affordable **Ryzen 5000 Series hosting** on the market for *unbeatable prices*! |
 | 
				
			||||||
 | 
					| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. |
 | 
				
			||||||
 | 
					| [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa.|
 | 
				
			||||||
 | 
					| [**RocketNode**](https://rocketnode.net) | RocketNode is a VPS and Game Server provider that offers the best performing VPS and Game hosting Solutions at affordable prices! |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Documentation
 | 
					## Documentation
 | 
				
			||||||
* [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html)
 | 
					* [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,72 +2,32 @@ package installer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"encoding/json"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"emperror.dev/errors"
 | 
						"emperror.dev/errors"
 | 
				
			||||||
	"github.com/asaskevich/govalidator"
 | 
						"github.com/asaskevich/govalidator"
 | 
				
			||||||
	"github.com/buger/jsonparser"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/pterodactyl/wings/environment"
 | 
					 | 
				
			||||||
	"github.com/pterodactyl/wings/remote"
 | 
						"github.com/pterodactyl/wings/remote"
 | 
				
			||||||
	"github.com/pterodactyl/wings/server"
 | 
						"github.com/pterodactyl/wings/server"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Installer struct {
 | 
					type Installer struct {
 | 
				
			||||||
	server            *server.Server
 | 
						server            *server.Server
 | 
				
			||||||
 | 
						StartOnCompletion bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ServerDetails struct {
 | 
				
			||||||
 | 
						UUID              string `json:"uuid"`
 | 
				
			||||||
 | 
						StartOnCompletion bool   `json:"start_on_completion"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// New validates the received data to ensure that all the required fields
 | 
					// New validates the received data to ensure that all the required fields
 | 
				
			||||||
// have been passed along in the request. This should be manually run before
 | 
					// have been passed along in the request. This should be manually run before
 | 
				
			||||||
// calling Execute().
 | 
					// calling Execute().
 | 
				
			||||||
func New(ctx context.Context, manager *server.Manager, data []byte) (*Installer, error) {
 | 
					func New(ctx context.Context, manager *server.Manager, details ServerDetails) (*Installer, error) {
 | 
				
			||||||
	if !govalidator.IsUUIDv4(getString(data, "uuid")) {
 | 
						if !govalidator.IsUUIDv4(details.UUID) {
 | 
				
			||||||
		return nil, NewValidationError("uuid provided was not in a valid format")
 | 
							return nil, NewValidationError("uuid provided was not in a valid format")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cfg := &server.Configuration{
 | 
						c, err := manager.Client().GetServerConfiguration(ctx, details.UUID)
 | 
				
			||||||
		Uuid:              getString(data, "uuid"),
 | 
					 | 
				
			||||||
		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"),
 | 
					 | 
				
			||||||
			IoWeight:    uint16(getInt(data, "build", "io")),
 | 
					 | 
				
			||||||
			CpuLimit:    getInt(data, "build", "cpu"),
 | 
					 | 
				
			||||||
			DiskSpace:   getInt(data, "build", "disk"),
 | 
					 | 
				
			||||||
			Threads:     getString(data, "build", "threads"),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		CrashDetectionEnabled: true,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cfg.Allocations.DefaultMapping.Ip = getString(data, "allocations", "default", "ip")
 | 
					 | 
				
			||||||
	cfg.Allocations.DefaultMapping.Port = int(getInt(data, "allocations", "default", "port"))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Unmarshal the environment variables from the request into the server struct.
 | 
					 | 
				
			||||||
	if b, _, _, err := jsonparser.Get(data, "environment"); err != nil {
 | 
					 | 
				
			||||||
		return nil, errors.WithStackIf(err)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		cfg.EnvVars = make(environment.Variables)
 | 
					 | 
				
			||||||
		if err := json.Unmarshal(b, &cfg.EnvVars); err != nil {
 | 
					 | 
				
			||||||
			return nil, errors.WrapIf(err, "installer: could not unmarshal environment variables for server")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Unmarshal the allocation mappings from the request into the server struct.
 | 
					 | 
				
			||||||
	if b, _, _, err := jsonparser.Get(data, "allocations", "mappings"); err != nil {
 | 
					 | 
				
			||||||
		return nil, errors.WithStackIf(err)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		cfg.Allocations.Mappings = make(map[string][]int)
 | 
					 | 
				
			||||||
		if err := json.Unmarshal(b, &cfg.Allocations.Mappings); err != nil {
 | 
					 | 
				
			||||||
			return nil, errors.Wrap(err, "installer: could not unmarshal allocation mappings")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cfg.Container.Image = getString(data, "container", "image")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	c, err := manager.Client().GetServerConfiguration(ctx, cfg.Uuid)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if !remote.IsRequestError(err) {
 | 
							if !remote.IsRequestError(err) {
 | 
				
			||||||
			return nil, errors.WithStackIf(err)
 | 
								return nil, errors.WithStackIf(err)
 | 
				
			||||||
| 
						 | 
					@ -81,35 +41,11 @@ func New(ctx context.Context, manager *server.Manager, data []byte) (*Installer,
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, errors.WrapIf(err, "installer: could not init server instance")
 | 
							return nil, errors.WrapIf(err, "installer: could not init server instance")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return &Installer{server: s}, nil
 | 
						i := Installer{server: s, StartOnCompletion: details.StartOnCompletion}
 | 
				
			||||||
}
 | 
						return &i, nil
 | 
				
			||||||
 | 
					 | 
				
			||||||
// Uuid returns the UUID associated with this installer instance.
 | 
					 | 
				
			||||||
func (i *Installer) Uuid() string {
 | 
					 | 
				
			||||||
	return i.server.ID()
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Server returns the server instance.
 | 
					// Server returns the server instance.
 | 
				
			||||||
func (i *Installer) Server() *server.Server {
 | 
					func (i *Installer) Server() *server.Server {
 | 
				
			||||||
	return i.server
 | 
						return i.server
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// Returns a string value from the JSON data provided.
 | 
					 | 
				
			||||||
func getString(data []byte, key ...string) string {
 | 
					 | 
				
			||||||
	value, _ := jsonparser.GetString(data, key...)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return value
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Returns an int value from the JSON data provided.
 | 
					 | 
				
			||||||
func getInt(data []byte, key ...string) int64 {
 | 
					 | 
				
			||||||
	value, _ := jsonparser.GetInt(data, key...)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return value
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func getBoolean(data []byte, key ...string) bool {
 | 
					 | 
				
			||||||
	value, _ := jsonparser.GetBoolean(data, key...)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return value
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,19 +48,19 @@ func readFileBytes(path string) ([]byte, error) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Gets the value of a key based on the value type defined.
 | 
					// Gets the value of a key based on the value type defined.
 | 
				
			||||||
func (cfr *ConfigurationFileReplacement) getKeyValue(value []byte) interface{} {
 | 
					func (cfr *ConfigurationFileReplacement) getKeyValue(value string) interface{} {
 | 
				
			||||||
	if cfr.ReplaceWith.Type() == jsonparser.Boolean {
 | 
						if cfr.ReplaceWith.Type() == jsonparser.Boolean {
 | 
				
			||||||
		v, _ := strconv.ParseBool(string(value))
 | 
							v, _ := strconv.ParseBool(value)
 | 
				
			||||||
		return v
 | 
							return v
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Try to parse into an int, if this fails just ignore the error and continue
 | 
						// Try to parse into an int, if this fails just ignore the error and continue
 | 
				
			||||||
	// through, returning the string.
 | 
						// through, returning the string.
 | 
				
			||||||
	if v, err := strconv.Atoi(string(value)); err == nil {
 | 
						if v, err := strconv.Atoi(value); err == nil {
 | 
				
			||||||
		return v
 | 
							return v
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return string(value)
 | 
						return value
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Iterate over an unstructured JSON/YAML/etc. interface and set all of the required
 | 
					// Iterate over an unstructured JSON/YAML/etc. interface and set all of the required
 | 
				
			||||||
| 
						 | 
					@ -97,22 +97,21 @@ func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error
 | 
				
			||||||
			// If the child is a null value, nothing will happen. Seems reasonable as of the
 | 
								// If the child is a null value, nothing will happen. Seems reasonable as of the
 | 
				
			||||||
			// time this code is being written.
 | 
								// time this code is being written.
 | 
				
			||||||
			for _, child := range parsed.Path(strings.Trim(parts[0], ".")).Children() {
 | 
								for _, child := range parsed.Path(strings.Trim(parts[0], ".")).Children() {
 | 
				
			||||||
				if err := v.SetAtPathway(child, strings.Trim(parts[1], "."), []byte(value)); err != nil {
 | 
									if err := v.SetAtPathway(child, strings.Trim(parts[1], "."), value); err != nil {
 | 
				
			||||||
					if errors.Is(err, gabs.ErrNotFound) {
 | 
										if errors.Is(err, gabs.ErrNotFound) {
 | 
				
			||||||
						continue
 | 
											continue
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					 | 
				
			||||||
					return nil, errors.WithMessage(err, "failed to set config value of array child")
 | 
										return nil, errors.WithMessage(err, "failed to set config value of array child")
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			if err = v.SetAtPathway(parsed, v.Match, []byte(value)); err != nil {
 | 
					 | 
				
			||||||
				if errors.Is(err, gabs.ErrNotFound) {
 | 
					 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				return nil, errors.WithMessage(err, "unable to set config value at pathway: "+v.Match)
 | 
							if err := v.SetAtPathway(parsed, v.Match, value); err != nil {
 | 
				
			||||||
 | 
								if errors.Is(err, gabs.ErrNotFound) {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								return nil, errors.WithMessage(err, "unable to set config value at pathway: "+v.Match)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -132,13 +131,10 @@ func setValueAtPath(c *gabs.Container, path string, value interface{}) error {
 | 
				
			||||||
	var err error
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	matches := checkForArrayElement.FindStringSubmatch(path)
 | 
						matches := checkForArrayElement.FindStringSubmatch(path)
 | 
				
			||||||
	if len(matches) < 3 {
 | 
					 | 
				
			||||||
		// Only update the value if the pathway actually exists in the configuration, otherwise
 | 
					 | 
				
			||||||
		// do nothing.
 | 
					 | 
				
			||||||
		if c.ExistsP(path) {
 | 
					 | 
				
			||||||
			_, err = c.SetP(value, path)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if we are **NOT** updating an array element.
 | 
				
			||||||
 | 
						if len(matches) < 3 {
 | 
				
			||||||
 | 
							_, err = c.SetP(value, path)
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -196,32 +192,34 @@ func setValueAtPath(c *gabs.Container, path string, value interface{}) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Sets the value at a specific pathway, but checks if we were looking for a specific
 | 
					// Sets the value at a specific pathway, but checks if we were looking for a specific
 | 
				
			||||||
// value or not before doing it.
 | 
					// value or not before doing it.
 | 
				
			||||||
func (cfr *ConfigurationFileReplacement) SetAtPathway(c *gabs.Container, path string, value []byte) error {
 | 
					func (cfr *ConfigurationFileReplacement) SetAtPathway(c *gabs.Container, path string, value string) error {
 | 
				
			||||||
	if cfr.IfValue == "" {
 | 
						if cfr.IfValue == "" {
 | 
				
			||||||
		return setValueAtPath(c, path, cfr.getKeyValue(value))
 | 
							return setValueAtPath(c, path, cfr.getKeyValue(value))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// If this is a regex based matching, we need to get a little more creative since
 | 
						// Check if we are replacing instead of overwriting.
 | 
				
			||||||
	// we're only going to replacing part of the string, and not the whole thing.
 | 
						if strings.HasPrefix(cfr.IfValue, "regex:") {
 | 
				
			||||||
	if c.ExistsP(path) && strings.HasPrefix(cfr.IfValue, "regex:") {
 | 
							// Doing a regex replacement requires an existing value.
 | 
				
			||||||
		// We're doing some regex here.
 | 
							// TODO: Do we try passing an empty string to the regex?
 | 
				
			||||||
 | 
							if c.ExistsP(path) {
 | 
				
			||||||
 | 
								return gabs.ErrNotFound
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		r, err := regexp.Compile(strings.TrimPrefix(cfr.IfValue, "regex:"))
 | 
							r, err := regexp.Compile(strings.TrimPrefix(cfr.IfValue, "regex:"))
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			log.WithFields(log.Fields{"if_value": strings.TrimPrefix(cfr.IfValue, "regex:"), "error": err}).
 | 
								log.WithFields(log.Fields{"if_value": strings.TrimPrefix(cfr.IfValue, "regex:"), "error": err}).
 | 
				
			||||||
				Warn("configuration if_value using invalid regexp, cannot perform replacement")
 | 
									Warn("configuration if_value using invalid regexp, cannot perform replacement")
 | 
				
			||||||
 | 
					 | 
				
			||||||
			return nil
 | 
								return nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// If the path exists and there is a regex match, go ahead and attempt the replacement
 | 
							v := strings.Trim(c.Path(path).String(), "\"")
 | 
				
			||||||
		// using the value we got from the key. This will only replace the one match.
 | 
					 | 
				
			||||||
		v := strings.Trim(string(c.Path(path).Bytes()), "\"")
 | 
					 | 
				
			||||||
		if r.Match([]byte(v)) {
 | 
							if r.Match([]byte(v)) {
 | 
				
			||||||
			return setValueAtPath(c, path, r.ReplaceAllString(v, string(value)))
 | 
								return setValueAtPath(c, path, r.ReplaceAllString(v, value))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return nil
 | 
						if c.ExistsP(path) && !bytes.Equal(c.Bytes(), []byte(cfr.IfValue)) {
 | 
				
			||||||
	} else if !c.ExistsP(path) || (c.ExistsP(path) && !bytes.Equal(c.Bytes(), []byte(cfr.IfValue))) {
 | 
					 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -57,17 +57,22 @@ func (cv *ReplaceValue) Type() jsonparser.ValueType {
 | 
				
			||||||
// handle casting the UTF-8 sequence into the expected value, switching something
 | 
					// handle casting the UTF-8 sequence into the expected value, switching something
 | 
				
			||||||
// like "\u00a7Foo" into "§Foo".
 | 
					// like "\u00a7Foo" into "§Foo".
 | 
				
			||||||
func (cv *ReplaceValue) String() string {
 | 
					func (cv *ReplaceValue) String() string {
 | 
				
			||||||
	if cv.Type() != jsonparser.String {
 | 
						switch cv.Type() {
 | 
				
			||||||
		if cv.Type() == jsonparser.Null {
 | 
						case jsonparser.String:
 | 
				
			||||||
			return "<nil>"
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return "<invalid>"
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
		str, err := jsonparser.ParseString(cv.value)
 | 
							str, err := jsonparser.ParseString(cv.value)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			panic(errors.Wrap(err, "parser: could not parse value"))
 | 
								panic(errors.Wrap(err, "parser: could not parse value"))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return str
 | 
							return str
 | 
				
			||||||
 | 
						case jsonparser.Null:
 | 
				
			||||||
 | 
							return "<nil>"
 | 
				
			||||||
 | 
						case jsonparser.Boolean:
 | 
				
			||||||
 | 
							return string(cv.value)
 | 
				
			||||||
 | 
						case jsonparser.Number:
 | 
				
			||||||
 | 
							return string(cv.value)
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return "<invalid>"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ConfigurationParser string
 | 
					type ConfigurationParser string
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,11 +6,11 @@ import (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/pterodactyl/wings/remote"
 | 
						"github.com/pterodactyl/wings/remote"
 | 
				
			||||||
	"github.com/pterodactyl/wings/router/middleware"
 | 
						"github.com/pterodactyl/wings/router/middleware"
 | 
				
			||||||
	"github.com/pterodactyl/wings/server"
 | 
						wserver "github.com/pterodactyl/wings/server"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configure configures the routing infrastructure for this daemon instance.
 | 
					// Configure configures the routing infrastructure for this daemon instance.
 | 
				
			||||||
func Configure(m *server.Manager, client remote.Client) *gin.Engine {
 | 
					func Configure(m *wserver.Manager, client remote.Client) *gin.Engine {
 | 
				
			||||||
	gin.SetMode("release")
 | 
						gin.SetMode("release")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	router := gin.New()
 | 
						router := gin.New()
 | 
				
			||||||
| 
						 | 
					@ -63,7 +63,6 @@ func Configure(m *server.Manager, client remote.Client) *gin.Engine {
 | 
				
			||||||
	server.Use(middleware.RequireAuthorization(), middleware.ServerExists())
 | 
						server.Use(middleware.RequireAuthorization(), middleware.ServerExists())
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		server.GET("", getServer)
 | 
							server.GET("", getServer)
 | 
				
			||||||
		server.PATCH("", patchServer)
 | 
					 | 
				
			||||||
		server.DELETE("", deleteServer)
 | 
							server.DELETE("", deleteServer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		server.GET("/logs", getServerLogs)
 | 
							server.GET("/logs", getServerLogs)
 | 
				
			||||||
| 
						 | 
					@ -71,6 +70,7 @@ func Configure(m *server.Manager, client remote.Client) *gin.Engine {
 | 
				
			||||||
		server.POST("/commands", postServerCommands)
 | 
							server.POST("/commands", postServerCommands)
 | 
				
			||||||
		server.POST("/install", postServerInstall)
 | 
							server.POST("/install", postServerInstall)
 | 
				
			||||||
		server.POST("/reinstall", postServerReinstall)
 | 
							server.POST("/reinstall", postServerReinstall)
 | 
				
			||||||
 | 
							server.POST("/sync", postServerSync)
 | 
				
			||||||
		server.POST("/ws/deny", postServerDenyWSTokens)
 | 
							server.POST("/ws/deny", postServerDenyWSTokens)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// This archive request causes the archive to start being created
 | 
							// This archive request causes the archive to start being created
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,6 @@
 | 
				
			||||||
package router
 | 
					package router
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
					 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
| 
						 | 
					@ -10,7 +9,6 @@ import (
 | 
				
			||||||
	"emperror.dev/errors"
 | 
						"emperror.dev/errors"
 | 
				
			||||||
	"github.com/apex/log"
 | 
						"github.com/apex/log"
 | 
				
			||||||
	"github.com/gin-gonic/gin"
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/pterodactyl/wings/router/downloader"
 | 
						"github.com/pterodactyl/wings/router/downloader"
 | 
				
			||||||
	"github.com/pterodactyl/wings/router/middleware"
 | 
						"github.com/pterodactyl/wings/router/middleware"
 | 
				
			||||||
	"github.com/pterodactyl/wings/router/tokens"
 | 
						"github.com/pterodactyl/wings/router/tokens"
 | 
				
			||||||
| 
						 | 
					@ -130,21 +128,18 @@ func postServerCommands(c *gin.Context) {
 | 
				
			||||||
	c.Status(http.StatusNoContent)
 | 
						c.Status(http.StatusNoContent)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Updates information about a server internally.
 | 
					// postServerSync will accept a POST request and trigger a re-sync of the given
 | 
				
			||||||
func patchServer(c *gin.Context) {
 | 
					// server against the Panel. This can be manually triggered when needed by an
 | 
				
			||||||
 | 
					// external system, or triggered by the Panel itself when modifications are made
 | 
				
			||||||
 | 
					// to the build of a server internally.
 | 
				
			||||||
 | 
					func postServerSync(c *gin.Context) {
 | 
				
			||||||
	s := ExtractServer(c)
 | 
						s := ExtractServer(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	buf := bytes.Buffer{}
 | 
						if err := s.Sync(); err != nil {
 | 
				
			||||||
	buf.ReadFrom(c.Request.Body)
 | 
							WithError(c, err)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
	if err := s.UpdateDataStructure(buf.Bytes()); err != nil {
 | 
					 | 
				
			||||||
		NewServerError(err, s).Abort(c)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	s.SyncWithEnvironment()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		c.Status(http.StatusNoContent)
 | 
							c.Status(http.StatusNoContent)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Performs a server installation in a background thread.
 | 
					// Performs a server installation in a background thread.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -254,13 +254,20 @@ func getServerPullingFiles(c *gin.Context) {
 | 
				
			||||||
func postServerPullRemoteFile(c *gin.Context) {
 | 
					func postServerPullRemoteFile(c *gin.Context) {
 | 
				
			||||||
	s := ExtractServer(c)
 | 
						s := ExtractServer(c)
 | 
				
			||||||
	var data struct {
 | 
						var data struct {
 | 
				
			||||||
		RootPath string `binding:"required,omitempty" json:"root"`
 | 
							// 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"`
 | 
							URL       string `binding:"required" json:"url"`
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if err := c.BindJSON(&data); err != nil {
 | 
						if err := c.BindJSON(&data); err != nil {
 | 
				
			||||||
		return
 | 
							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)
 | 
						u, err := url.Parse(data.URL)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if e, ok := err.(*url.Error); ok {
 | 
							if e, ok := err.(*url.Error); ok {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,6 @@
 | 
				
			||||||
package router
 | 
					package router
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
					 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
| 
						 | 
					@ -44,10 +43,13 @@ func getAllServers(c *gin.Context) {
 | 
				
			||||||
// for it.
 | 
					// for it.
 | 
				
			||||||
func postCreateServer(c *gin.Context) {
 | 
					func postCreateServer(c *gin.Context) {
 | 
				
			||||||
	manager := middleware.ExtractManager(c)
 | 
						manager := middleware.ExtractManager(c)
 | 
				
			||||||
	buf := bytes.Buffer{}
 | 
					 | 
				
			||||||
	buf.ReadFrom(c.Request.Body)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	install, err := installer.New(c.Request.Context(), manager, buf.Bytes())
 | 
						details := installer.ServerDetails{}
 | 
				
			||||||
 | 
						if err := c.BindJSON(&details); err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						install, err := installer.New(c.Request.Context(), manager, details)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if installer.IsValidationError(err) {
 | 
							if installer.IsValidationError(err) {
 | 
				
			||||||
			c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
 | 
								c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
 | 
				
			||||||
| 
						 | 
					@ -74,24 +76,21 @@ func postCreateServer(c *gin.Context) {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err := i.Server().Install(false); err != nil {
 | 
							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")
 | 
								log.WithFields(log.Fields{"server": i.Server().ID(), "error": err}).Error("failed to run install process for server")
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if i.Server().Config().StartOnCompletion {
 | 
							if i.StartOnCompletion {
 | 
				
			||||||
			log.WithField("server_id", i.Server().ID()).Debug("starting server after successful installation")
 | 
								log.WithField("server_id", i.Server().ID()).Debug("starting server after successful installation")
 | 
				
			||||||
			if err := i.Server().HandlePowerAction(server.PowerActionStart, 30); err != nil {
 | 
								if err := i.Server().HandlePowerAction(server.PowerActionStart, 30); err != nil {
 | 
				
			||||||
				if errors.Is(err, context.DeadlineExceeded) {
 | 
									if errors.Is(err, context.DeadlineExceeded) {
 | 
				
			||||||
					log.WithFields(log.Fields{"server_id": i.Server().ID(), "action": "start"}).
 | 
										log.WithFields(log.Fields{"server_id": i.Server().ID(), "action": "start"}).Warn("could not acquire a lock while attempting to perform a power action")
 | 
				
			||||||
						Warn("could not acquire a lock while attempting to perform a power action")
 | 
					 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					log.WithFields(log.Fields{"server_id": i.Server().ID(), "action": "start", "error": err}).
 | 
										log.WithFields(log.Fields{"server_id": i.Server().ID(), "action": "start", "error": err}).Error("encountered error processing a server power action in the background")
 | 
				
			||||||
						Error("encountered error processing a server power action in the background")
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			log.WithField("server_id", i.Server().ID()).
 | 
								log.WithField("server_id", i.Server().ID()).Debug("skipping automatic start after successful server installation")
 | 
				
			||||||
				Debug("skipping automatic start after successful server installation")
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}(install)
 | 
						}(install)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,6 @@ import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"crypto/sha256"
 | 
						"crypto/sha256"
 | 
				
			||||||
	"encoding/hex"
 | 
						"encoding/hex"
 | 
				
			||||||
	"encoding/json"
 | 
					 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
| 
						 | 
					@ -50,7 +49,7 @@ type serverTransferRequest struct {
 | 
				
			||||||
	ServerID string                  `binding:"required" json:"server_id"`
 | 
						ServerID string                  `binding:"required" json:"server_id"`
 | 
				
			||||||
	URL      string                  `binding:"required" json:"url"`
 | 
						URL      string                  `binding:"required" json:"url"`
 | 
				
			||||||
	Token    string                  `binding:"required" json:"token"`
 | 
						Token    string                  `binding:"required" json:"token"`
 | 
				
			||||||
	Server   json.RawMessage `json:"server"`
 | 
						Server   installer.ServerDetails `json:"server"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getArchivePath(sID string) string {
 | 
					func getArchivePath(sID string) string {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,6 +5,7 @@ import (
 | 
				
			||||||
	"io/fs"
 | 
						"io/fs"
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"emperror.dev/errors"
 | 
						"emperror.dev/errors"
 | 
				
			||||||
	"github.com/apex/log"
 | 
						"github.com/apex/log"
 | 
				
			||||||
| 
						 | 
					@ -152,12 +153,15 @@ func (s *Server) RestoreBackup(b backup.BackupInterface, reader io.ReadCloser) (
 | 
				
			||||||
	// Attempt to restore the backup to the server by running through each entry
 | 
						// 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.
 | 
						// in the file one at a time and writing them to the disk.
 | 
				
			||||||
	s.Log().Debug("starting file writing process for backup restoration")
 | 
						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, mode fs.FileMode, atime, mtime time.Time) error {
 | 
				
			||||||
		s.Events().Publish(DaemonMessageEvent, "(restoring): "+file)
 | 
							s.Events().Publish(DaemonMessageEvent, "(restoring): "+file)
 | 
				
			||||||
		if err := s.Filesystem().Writefile(file, r); err != nil {
 | 
							if err := s.Filesystem().Writefile(file, r); err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return s.Filesystem().Chmod(file, mode)
 | 
							if err := s.Filesystem().Chmod(file, mode); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return s.Filesystem().Chtimes(file, atime, mtime)
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return errors.WithStackIf(err)
 | 
						return errors.WithStackIf(err)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,7 @@ import (
 | 
				
			||||||
	"io/fs"
 | 
						"io/fs"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"emperror.dev/errors"
 | 
						"emperror.dev/errors"
 | 
				
			||||||
	"github.com/apex/log"
 | 
						"github.com/apex/log"
 | 
				
			||||||
| 
						 | 
					@ -26,7 +27,7 @@ const (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RestoreCallback is a generic restoration callback that exists for both local
 | 
					// RestoreCallback is a generic restoration callback that exists for both local
 | 
				
			||||||
// and remote backups allowing the files to be restored.
 | 
					// 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, mode fs.FileMode, atime, mtime time.Time) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// noinspection GoNameStartsWithPackageName
 | 
					// noinspection GoNameStartsWithPackageName
 | 
				
			||||||
type BackupInterface interface {
 | 
					type BackupInterface interface {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -88,7 +88,7 @@ func (b *LocalBackup) Restore(ctx context.Context, _ io.Reader, callback Restore
 | 
				
			||||||
			if f.IsDir() {
 | 
								if f.IsDir() {
 | 
				
			||||||
				return nil
 | 
									return nil
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			return callback(filesystem.ExtractNameFromArchive(f), f, f.Mode())
 | 
								return callback(filesystem.ExtractNameFromArchive(f), f, f.Mode(), f.ModTime(), f.ModTime())
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -116,7 +116,7 @@ func (s *S3Backup) Restore(ctx context.Context, r io.Reader, callback RestoreCal
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if header.Typeflag == tar.TypeReg {
 | 
							if header.Typeflag == tar.TypeReg {
 | 
				
			||||||
			if err := callback(header.Name, tr, header.FileInfo().Mode()); err != nil {
 | 
								if err := callback(header.Name, tr, header.FileInfo().Mode(), header.AccessTime, header.ModTime); err != nil {
 | 
				
			||||||
				return err
 | 
									return err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,8 +35,6 @@ type Configuration struct {
 | 
				
			||||||
	// server, specific installation scripts will be skipped for the server process.
 | 
						// server, specific installation scripts will be skipped for the server process.
 | 
				
			||||||
	SkipEggScripts bool `json:"skip_egg_scripts"`
 | 
						SkipEggScripts bool `json:"skip_egg_scripts"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	StartOnCompletion bool `json:"start_on_completion"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// An array of environment variables that should be passed along to the running
 | 
						// An array of environment variables that should be passed along to the running
 | 
				
			||||||
	// server process.
 | 
						// server process.
 | 
				
			||||||
	EnvVars environment.Variables `json:"environment"`
 | 
						EnvVars environment.Variables `json:"environment"`
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,6 +19,8 @@ import (
 | 
				
			||||||
// a server.
 | 
					// a server.
 | 
				
			||||||
var appName string
 | 
					var appName string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var appNameSync sync.Once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var ErrTooMuchConsoleData = errors.New("console is outputting too much data")
 | 
					var ErrTooMuchConsoleData = errors.New("console is outputting too much data")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ConsoleThrottler struct {
 | 
					type ConsoleThrottler struct {
 | 
				
			||||||
| 
						 | 
					@ -131,9 +133,9 @@ func (s *Server) Throttler() *ConsoleThrottler {
 | 
				
			||||||
// PublishConsoleOutputFromDaemon sends output to the server console formatted
 | 
					// PublishConsoleOutputFromDaemon sends output to the server console formatted
 | 
				
			||||||
// to appear correctly as being sent from Wings.
 | 
					// to appear correctly as being sent from Wings.
 | 
				
			||||||
func (s *Server) PublishConsoleOutputFromDaemon(data string) {
 | 
					func (s *Server) PublishConsoleOutputFromDaemon(data string) {
 | 
				
			||||||
	if appName == "" {
 | 
						appNameSync.Do(func() {
 | 
				
			||||||
		appName = config.Get().AppName
 | 
							appName = config.Get().AppName
 | 
				
			||||||
	}
 | 
						})
 | 
				
			||||||
	s.Events().Publish(
 | 
						s.Events().Publish(
 | 
				
			||||||
		ConsoleOutputEvent,
 | 
							ConsoleOutputEvent,
 | 
				
			||||||
		colorstring.Color(fmt.Sprintf("[yellow][bold][%s Daemon]:[default] %s", appName, data)),
 | 
							colorstring.Color(fmt.Sprintf("[yellow][bold][%s Daemon]:[default] %s", appName, data)),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -136,6 +136,10 @@ func (fs *Filesystem) DecompressFile(dir string, file string) error {
 | 
				
			||||||
		if err := fs.Chmod(p, f.Mode()); err != nil {
 | 
							if err := fs.Chmod(p, f.Mode()); err != nil {
 | 
				
			||||||
			return wrapError(err, source)
 | 
								return wrapError(err, source)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							// Update the file modification time to the one set in the archive.
 | 
				
			||||||
 | 
							if err := fs.Chtimes(p, f.ModTime(), f.ModTime()); err != nil {
 | 
				
			||||||
 | 
								return wrapError(err, source)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -528,3 +528,20 @@ func (fs *Filesystem) ListDirectory(p string) ([]Stat, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return out, nil
 | 
						return out, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (fs *Filesystem) Chtimes(path string, atime, mtime time.Time) error {
 | 
				
			||||||
 | 
						cleaned, err := fs.SafePath(path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if fs.isTest {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := os.Chtimes(cleaned, atime, mtime); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -172,8 +172,11 @@ func (m *Manager) InitServer(data remote.ServerConfigurationResponse) (*Server,
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if err := s.UpdateDataStructure(data.Settings); err != nil {
 | 
					
 | 
				
			||||||
		return nil, err
 | 
						// Setup the base server configuration data which will be used for all of the
 | 
				
			||||||
 | 
						// remaining functionality in this call.
 | 
				
			||||||
 | 
						if err := s.SyncWithConfiguration(data); err != nil {
 | 
				
			||||||
 | 
							return nil, errors.WithStackIf(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)
 | 
				
			||||||
| 
						 | 
					@ -200,11 +203,6 @@ func (m *Manager) InitServer(data remote.ServerConfigurationResponse) (*Server,
 | 
				
			||||||
		s.Throttler().StartTimer(s.Context())
 | 
							s.Throttler().StartTimer(s.Context())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Forces the configuration to be synced with the panel.
 | 
					 | 
				
			||||||
	if err := s.SyncWithConfiguration(data); err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// If the server's data directory exists, force disk usage calculation.
 | 
						// If the server's data directory exists, force disk usage calculation.
 | 
				
			||||||
	if _, err := os.Stat(s.Filesystem().Path()); err == nil {
 | 
						if _, err := os.Stat(s.Filesystem().Path()); err == nil {
 | 
				
			||||||
		s.Filesystem().HasSpaceAvailable(true)
 | 
							s.Filesystem().HasSpaceAvailable(true)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ package server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
| 
						 | 
					@ -15,7 +16,6 @@ import (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/pterodactyl/wings/config"
 | 
						"github.com/pterodactyl/wings/config"
 | 
				
			||||||
	"github.com/pterodactyl/wings/environment"
 | 
						"github.com/pterodactyl/wings/environment"
 | 
				
			||||||
	"github.com/pterodactyl/wings/environment/docker"
 | 
					 | 
				
			||||||
	"github.com/pterodactyl/wings/events"
 | 
						"github.com/pterodactyl/wings/events"
 | 
				
			||||||
	"github.com/pterodactyl/wings/remote"
 | 
						"github.com/pterodactyl/wings/remote"
 | 
				
			||||||
	"github.com/pterodactyl/wings/server/filesystem"
 | 
						"github.com/pterodactyl/wings/server/filesystem"
 | 
				
			||||||
| 
						 | 
					@ -99,6 +99,14 @@ func (s *Server) ID() string {
 | 
				
			||||||
	return s.Config().GetUuid()
 | 
						return s.Config().GetUuid()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Id returns the UUID for the server instance. This function is deprecated
 | 
				
			||||||
 | 
					// in favor of Server.ID().
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Deprecated
 | 
				
			||||||
 | 
					func (s *Server) Id() string {
 | 
				
			||||||
 | 
						return s.ID()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Cancels the context assigned to this server instance. Assuming background tasks
 | 
					// Cancels the context assigned to this server instance. Assuming background tasks
 | 
				
			||||||
// are using this server's context for things, all of the background tasks will be
 | 
					// are using this server's context for things, all of the background tasks will be
 | 
				
			||||||
// stopped as a result.
 | 
					// stopped as a result.
 | 
				
			||||||
| 
						 | 
					@ -159,31 +167,48 @@ func (s *Server) Sync() error {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return errors.WithStackIf(err)
 | 
							return errors.WithStackIf(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return s.SyncWithConfiguration(cfg)
 | 
					
 | 
				
			||||||
 | 
						if err := s.SyncWithConfiguration(cfg); err != nil {
 | 
				
			||||||
 | 
							return errors.WithStackIf(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update the disk space limits for the server whenever the configuration for
 | 
				
			||||||
 | 
						// it changes.
 | 
				
			||||||
 | 
						s.fs.SetDiskLimit(s.DiskSpace())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s.SyncWithEnvironment()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SyncWithConfiguration accepts a configuration object for a server and will
 | 
				
			||||||
 | 
					// sync all of the values with the existing server state. This only replaces the
 | 
				
			||||||
 | 
					// existing configuration and process configuration for the server. The
 | 
				
			||||||
 | 
					// underlying environment will not be affected. This is because this function
 | 
				
			||||||
 | 
					// can be called from scoped where the server may not be fully initialized,
 | 
				
			||||||
 | 
					// therefore other things like the filesystem and environment may not exist yet.
 | 
				
			||||||
func (s *Server) SyncWithConfiguration(cfg remote.ServerConfigurationResponse) error {
 | 
					func (s *Server) SyncWithConfiguration(cfg remote.ServerConfigurationResponse) error {
 | 
				
			||||||
	// Update the data structure and persist it to the disk.
 | 
						c := Configuration{}
 | 
				
			||||||
	if err := s.UpdateDataStructure(cfg.Settings); err != nil {
 | 
						if err := json.Unmarshal(cfg.Settings, &c); err != nil {
 | 
				
			||||||
		return err
 | 
							return errors.WithStackIf(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s.cfg.mu.Lock()
 | 
				
			||||||
 | 
						defer s.cfg.mu.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lock the new configuration. Since we have the defered Unlock above we need
 | 
				
			||||||
 | 
						// to make sure that the NEW configuration object is already locked since that
 | 
				
			||||||
 | 
						// defer is running on the memory address for "s.cfg.mu" which we're explcitly
 | 
				
			||||||
 | 
						// changing on the next line.
 | 
				
			||||||
 | 
						c.mu.Lock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//goland:noinspection GoVetCopyLock
 | 
				
			||||||
 | 
						s.cfg = c
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	s.Lock()
 | 
						s.Lock()
 | 
				
			||||||
	s.procConfig = cfg.ProcessConfiguration
 | 
						s.procConfig = cfg.ProcessConfiguration
 | 
				
			||||||
	s.Unlock()
 | 
						s.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Update the disk space limits for the server whenever the configuration
 | 
					 | 
				
			||||||
	// for it changes.
 | 
					 | 
				
			||||||
	s.fs.SetDiskLimit(s.DiskSpace())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// If this is a Docker environment we need to sync the stop configuration with it so that
 | 
					 | 
				
			||||||
	// the process isn't just terminated when a user requests it be stopped.
 | 
					 | 
				
			||||||
	if e, ok := s.Environment.(*docker.Environment); ok {
 | 
					 | 
				
			||||||
		s.Log().Debug("syncing stop configuration with configured docker environment")
 | 
					 | 
				
			||||||
		e.SetImage(s.Config().Container.Image)
 | 
					 | 
				
			||||||
		e.SetStopConfiguration(cfg.ProcessConfiguration.Stop)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										131
									
								
								server/update.go
									
									
									
									
									
								
							
							
						
						
									
										131
									
								
								server/update.go
									
									
									
									
									
								
							| 
						 | 
					@ -1,128 +1,41 @@
 | 
				
			||||||
package server
 | 
					package server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/json"
 | 
						"github.com/pterodactyl/wings/environment/docker"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"emperror.dev/errors"
 | 
					 | 
				
			||||||
	"github.com/buger/jsonparser"
 | 
					 | 
				
			||||||
	"github.com/imdario/mergo"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/pterodactyl/wings/environment"
 | 
						"github.com/pterodactyl/wings/environment"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdateDataStructure merges data passed through in JSON form into the existing
 | 
					// SyncWithEnvironment updates the environment for the server to match any of
 | 
				
			||||||
// server object. Any changes to the build settings will apply immediately in
 | 
					// the changed data. This pushes new settings and environment variables to the
 | 
				
			||||||
// the environment if the environment supports it.
 | 
					// environment. In addition, the in-situ update method is called on the
 | 
				
			||||||
 | 
					// environment which will allow environments that make use of it (such as Docker)
 | 
				
			||||||
 | 
					// to immediately apply some settings without having to wait on a server to
 | 
				
			||||||
 | 
					// restart.
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// The server will be marked as requiring a rebuild on the next boot sequence,
 | 
					// This functionality allows a server's resources limits to be modified on the
 | 
				
			||||||
// it is up to the specific environment to determine what needs to happen when
 | 
					// fly and have them apply right away allowing for dynamic resource allocation
 | 
				
			||||||
// that is the case.
 | 
					// and responses to abusive server processes.
 | 
				
			||||||
func (s *Server) UpdateDataStructure(data []byte) error {
 | 
					 | 
				
			||||||
	src := new(Configuration)
 | 
					 | 
				
			||||||
	if err := json.Unmarshal(data, src); err != nil {
 | 
					 | 
				
			||||||
		return errors.Wrap(err, "server/update: could not unmarshal source data into Configuration struct")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 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() {
 | 
					 | 
				
			||||||
		return errors.New("server/update: attempting to merge a data stack with an invalid UUID")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Grab a copy of the configuration to work on.
 | 
					 | 
				
			||||||
	c := *s.Config()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Lock our copy of the configuration since the deferred unlock will end up acting upon this
 | 
					 | 
				
			||||||
	// new memory address rather than the old one. If we don't lock this, the deferred unlock will
 | 
					 | 
				
			||||||
	// cause a panic when it goes to run. However, since we only update s.cfg at the end, if there
 | 
					 | 
				
			||||||
	// is an error before that point we'll still properly unlock the original configuration for the
 | 
					 | 
				
			||||||
	// server.
 | 
					 | 
				
			||||||
	c.mu.Lock()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Lock the server configuration while we're doing this merge to avoid anything
 | 
					 | 
				
			||||||
	// trying to overwrite it or make modifications while we're sorting out what we
 | 
					 | 
				
			||||||
	// need to do.
 | 
					 | 
				
			||||||
	s.cfg.mu.Lock()
 | 
					 | 
				
			||||||
	defer s.cfg.mu.Unlock()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Merge the new data object that we have received with the existing server data object
 | 
					 | 
				
			||||||
	// and then save it to the disk so it is persistent.
 | 
					 | 
				
			||||||
	if err := mergo.Merge(&c, src, mergo.WithOverride); err != nil {
 | 
					 | 
				
			||||||
		return errors.WithStack(err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Don't explode if we're setting CPU limits to 0. Mergo sees that as an empty value
 | 
					 | 
				
			||||||
	// so it won't override the value we've passed through in the API call. However, we can
 | 
					 | 
				
			||||||
	// safely assume that we're passing through valid data structures here. I foresee this
 | 
					 | 
				
			||||||
	// backfiring at some point, but until then...
 | 
					 | 
				
			||||||
	c.Build = src.Build
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Mergo can't quite handle this boolean value correctly, so for now we'll just
 | 
					 | 
				
			||||||
	// handle this edge case manually since none of the other data passed through in this
 | 
					 | 
				
			||||||
	// request is going to be boolean. Allegedly.
 | 
					 | 
				
			||||||
	if v, err := jsonparser.GetBoolean(data, "container", "oom_disabled"); err != nil {
 | 
					 | 
				
			||||||
		if err != jsonparser.KeyPathNotFoundError {
 | 
					 | 
				
			||||||
			return errors.WithStack(err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		c.Build.OOMDisabled = v
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Mergo also cannot handle this boolean value.
 | 
					 | 
				
			||||||
	if v, err := jsonparser.GetBoolean(data, "suspended"); err != nil {
 | 
					 | 
				
			||||||
		if err != jsonparser.KeyPathNotFoundError {
 | 
					 | 
				
			||||||
			return errors.WithStack(err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		c.Suspended = v
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if v, err := jsonparser.GetBoolean(data, "skip_egg_scripts"); err != nil {
 | 
					 | 
				
			||||||
		if err != jsonparser.KeyPathNotFoundError {
 | 
					 | 
				
			||||||
			return errors.WithStack(err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		c.SkipEggScripts = v
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Environment and Mappings should be treated as a full update at all times, never a
 | 
					 | 
				
			||||||
	// true patch, otherwise we can't know what we're passing along.
 | 
					 | 
				
			||||||
	if src.EnvVars != nil && len(src.EnvVars) > 0 {
 | 
					 | 
				
			||||||
		c.EnvVars = src.EnvVars
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if src.Allocations.Mappings != nil && len(src.Allocations.Mappings) > 0 {
 | 
					 | 
				
			||||||
		c.Allocations.Mappings = src.Allocations.Mappings
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if src.Mounts != nil && len(src.Mounts) > 0 {
 | 
					 | 
				
			||||||
		c.Mounts = src.Mounts
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Update the configuration once we have a lock on the configuration object.
 | 
					 | 
				
			||||||
	s.cfg = c
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Updates the environment for the server to match any of the changed data. This pushes new settings and
 | 
					 | 
				
			||||||
// environment variables to the environment. In addition, the in-situ update method is called on the
 | 
					 | 
				
			||||||
// environment which will allow environments that make use of it (such as Docker) to immediately apply
 | 
					 | 
				
			||||||
// some settings without having to wait on a server to restart.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// This functionality allows a server's resources limits to be modified on the fly and have them apply
 | 
					 | 
				
			||||||
// right away allowing for dynamic resource allocation and responses to abusive server processes.
 | 
					 | 
				
			||||||
func (s *Server) SyncWithEnvironment() {
 | 
					func (s *Server) SyncWithEnvironment() {
 | 
				
			||||||
	s.Log().Debug("syncing server settings with environment")
 | 
						s.Log().Debug("syncing server settings with environment")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cfg := s.Config()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Update the environment settings using the new information from this server.
 | 
						// Update the environment settings using the new information from this server.
 | 
				
			||||||
	s.Environment.Config().SetSettings(environment.Settings{
 | 
						s.Environment.Config().SetSettings(environment.Settings{
 | 
				
			||||||
		Mounts:      s.Mounts(),
 | 
							Mounts:      s.Mounts(),
 | 
				
			||||||
		Allocations: s.Config().Allocations,
 | 
							Allocations: cfg.Allocations,
 | 
				
			||||||
		Limits:      s.Config().Build,
 | 
							Limits:      cfg.Build,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// For Docker specific environments we also want to update the configured image
 | 
				
			||||||
 | 
						// and stop configuration.
 | 
				
			||||||
 | 
						if e, ok := s.Environment.(*docker.Environment); ok {
 | 
				
			||||||
 | 
							s.Log().Debug("syncing stop configuration with configured docker environment")
 | 
				
			||||||
 | 
							e.SetImage(cfg.Container.Image)
 | 
				
			||||||
 | 
							e.SetStopConfiguration(s.ProcessConfiguration().Stop)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// If build limits are changed, environment variables also change. Plus, any modifications to
 | 
						// If build limits are changed, environment variables also change. Plus, any modifications to
 | 
				
			||||||
	// the startup command also need to be properly propagated to this environment.
 | 
						// the startup command also need to be properly propagated to this environment.
 | 
				
			||||||
	//
 | 
						//
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,7 @@ import (
 | 
				
			||||||
	"golang.org/x/crypto/ssh"
 | 
						"golang.org/x/crypto/ssh"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/pterodactyl/wings/config"
 | 
						"github.com/pterodactyl/wings/config"
 | 
				
			||||||
 | 
						"github.com/pterodactyl/wings/server"
 | 
				
			||||||
	"github.com/pterodactyl/wings/server/filesystem"
 | 
						"github.com/pterodactyl/wings/server/filesystem"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,8 +27,10 @@ const (
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Handler struct {
 | 
					type Handler struct {
 | 
				
			||||||
	permissions []string
 | 
					 | 
				
			||||||
	mu sync.Mutex
 | 
						mu sync.Mutex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						permissions []string
 | 
				
			||||||
 | 
						server      *server.Server
 | 
				
			||||||
	fs          *filesystem.Filesystem
 | 
						fs          *filesystem.Filesystem
 | 
				
			||||||
	logger      *log.Entry
 | 
						logger      *log.Entry
 | 
				
			||||||
	ro          bool
 | 
						ro          bool
 | 
				
			||||||
| 
						 | 
					@ -35,11 +38,12 @@ type Handler struct {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Returns a new connection handler for the SFTP server. This allows a given user
 | 
					// Returns a new connection handler for the SFTP server. This allows a given user
 | 
				
			||||||
// to access the underlying filesystem.
 | 
					// to access the underlying filesystem.
 | 
				
			||||||
func NewHandler(sc *ssh.ServerConn, fs *filesystem.Filesystem) *Handler {
 | 
					func NewHandler(sc *ssh.ServerConn, srv *server.Server) *Handler {
 | 
				
			||||||
	return &Handler{
 | 
						return &Handler{
 | 
				
			||||||
		fs:          fs,
 | 
					 | 
				
			||||||
		ro:          config.Get().System.Sftp.ReadOnly,
 | 
					 | 
				
			||||||
		permissions: strings.Split(sc.Permissions.Extensions["permissions"], ","),
 | 
							permissions: strings.Split(sc.Permissions.Extensions["permissions"], ","),
 | 
				
			||||||
 | 
							server:      srv,
 | 
				
			||||||
 | 
							fs:          srv.Filesystem(),
 | 
				
			||||||
 | 
							ro:          config.Get().System.Sftp.ReadOnly,
 | 
				
			||||||
		logger: log.WithFields(log.Fields{
 | 
							logger: log.WithFields(log.Fields{
 | 
				
			||||||
			"subsystem": "sftp",
 | 
								"subsystem": "sftp",
 | 
				
			||||||
			"username":  sc.User(),
 | 
								"username":  sc.User(),
 | 
				
			||||||
| 
						 | 
					@ -278,6 +282,10 @@ func (h *Handler) Filelist(request *sftp.Request) (sftp.ListerAt, error) {
 | 
				
			||||||
// Determines if a user has permission to perform a specific action on the SFTP server. These
 | 
					// Determines if a user has permission to perform a specific action on the SFTP server. These
 | 
				
			||||||
// permissions are defined and returned by the Panel API.
 | 
					// permissions are defined and returned by the Panel API.
 | 
				
			||||||
func (h *Handler) can(permission string) bool {
 | 
					func (h *Handler) can(permission string) bool {
 | 
				
			||||||
 | 
						if h.server.IsSuspended() {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// SFTPServer owners and super admins have their permissions returned as '[*]' via the Panel
 | 
						// SFTPServer owners and super admins have their permissions returned as '[*]' via the Panel
 | 
				
			||||||
	// API, so for the sake of speed do an initial check for that before iterating over the
 | 
						// API, so for the sake of speed do an initial check for that before iterating over the
 | 
				
			||||||
	// entire array of permissions.
 | 
						// entire array of permissions.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -139,7 +139,7 @@ func (c *SFTPServer) AcceptInbound(conn net.Conn, config *ssh.ServerConfig) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Spin up a SFTP server instance for the authenticated user's server allowing
 | 
							// Spin up a SFTP server instance for the authenticated user's server allowing
 | 
				
			||||||
		// them access to the underlying filesystem.
 | 
							// them access to the underlying filesystem.
 | 
				
			||||||
		handler := sftp.NewRequestServer(channel, NewHandler(sconn, srv.Filesystem()).Handlers())
 | 
							handler := sftp.NewRequestServer(channel, NewHandler(sconn, srv).Handlers())
 | 
				
			||||||
		if err := handler.Serve(); err == io.EOF {
 | 
							if err := handler.Serve(); err == io.EOF {
 | 
				
			||||||
			_ = handler.Close()
 | 
								_ = handler.Close()
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user