Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f3394b82d | ||
|
|
bae63c4321 | ||
|
|
f99640a42d | ||
|
|
c73d632e0d | ||
|
|
903902e123 | ||
|
|
1c787b5f26 | ||
|
|
3f9ac8b89a | ||
|
|
560c832cc6 | ||
|
|
13058ad64b | ||
|
|
305cd512a7 | ||
|
|
3cd17a2856 | ||
|
|
56789196d4 | ||
|
|
70804dd20f | ||
|
|
19d821aab5 | ||
|
|
4ce35d3d17 | ||
|
|
a62b588ace | ||
|
|
9b54be06bb | ||
|
|
c031d37b91 | ||
|
|
19051c99ef | ||
|
|
99fd416cee | ||
|
|
acf09180f0 | ||
|
|
b19fc88a95 | ||
|
|
e185f597ba | ||
|
|
3973747c9c | ||
|
|
947279a07c | ||
|
|
ad1ed0f24a | ||
|
|
80387bc294 | ||
|
|
e8dbba5cc0 | ||
|
|
f50f580dcc | ||
|
|
7e8033d96c |
43
.github/workflows/build-test.yml
vendored
43
.github/workflows/build-test.yml
vendored
@@ -9,25 +9,48 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-20.04
|
strategy:
|
||||||
|
# Default is true, cancels jobs for other platforms in the matrix if one fails
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
os: [ ubuntu-20.04 ]
|
||||||
|
go: [ 1.15 ]
|
||||||
|
goos: [ linux ]
|
||||||
|
goarch: [ amd64, arm, arm64 ]
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: '1.15.2'
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=dev-${GIT_COMMIT:0:7}" -o build/wings_linux_amd64 -v wings.go
|
env:
|
||||||
|
GOOS: ${{ matrix.goos }}
|
||||||
|
GOARCH: ${{ matrix.goarch }}
|
||||||
|
run: |
|
||||||
|
go build -v -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=dev-${GIT_COMMIT:0:7}" -o build/wings_${{ matrix.goos }}_${{ matrix.goarch }} wings.go
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go test ./...
|
run: go test ./...
|
||||||
|
|
||||||
- name: Compress binary and make it executable
|
- name: Compress binary and make it executable
|
||||||
if: ${{ github.ref == 'refs/heads/develop' || github.event_name == 'pull_request' }}
|
if: ${{ github.ref == 'refs/heads/develop' || github.event_name == 'pull_request' }}
|
||||||
run: upx build/wings_linux_amd64 && chmod +x build/wings_linux_amd64
|
run: |
|
||||||
|
upx build/wings_${{ matrix.goos }}_${{ matrix.goarch }} && chmod +x build/wings_${{ matrix.goos }}_${{ matrix.goarch }}
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
if: ${{ github.ref == 'refs/heads/develop' || github.event_name == 'pull_request' }}
|
if: ${{ github.ref == 'refs/heads/develop' || github.event_name == 'pull_request' }}
|
||||||
with:
|
with:
|
||||||
name: wings_linux_amd64
|
name: wings_${{ matrix.goos }}_${{ matrix.goarch }}
|
||||||
path: build/wings_linux_amd64
|
path: build/wings_${{ matrix.goos }}_${{ matrix.goarch }}
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
if: ${{ github.ref == 'refs/heads/develop' || github.event_name == 'pull_request' }}
|
||||||
|
with:
|
||||||
|
name: wings_${{ matrix.goos }}_${{ matrix.goarch }}
|
||||||
|
path: build/wings_${{ matrix.goos }}_${{ matrix.goarch }}
|
||||||
|
|||||||
56
.github/workflows/release.yml
vendored
56
.github/workflows/release.yml
vendored
@@ -8,22 +8,30 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: '1.15.2'
|
go-version: '1.15.2'
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
REF: ${{ github.ref }}
|
REF: ${{ github.ref }}
|
||||||
run: GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${REF:11}" -o build/wings_linux_amd64 -v wings.go
|
run: |
|
||||||
|
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${REF:11}" -o build/wings_linux_amd64 -v wings.go
|
||||||
|
GOOS=linux GOARCH=arm64 go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${REF:11}" -o build/wings_linux_arm64 -v wings.go
|
||||||
|
GOOS=linux GOARCH=arm go build -ldflags="-s -w -X github.com/pterodactyl/wings/system.Version=${REF:11}" -o build/wings_linux_arm -v wings.go
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go test ./...
|
run: go test ./...
|
||||||
|
|
||||||
- name: Compress binary and make it executable
|
- name: Compress binary and make it executable
|
||||||
run: upx --brute build/wings_linux_amd64 && chmod +x build/wings_linux_amd64
|
run: |
|
||||||
|
upx --brute build/wings_linux_amd64 && chmod +x build/wings_linux_amd64
|
||||||
|
upx build/wings_linux_arm64 && chmod +x build/wings_linux_arm64
|
||||||
|
upx build/wings_linux_arm && chmod +x build/wings_linux_arm
|
||||||
|
|
||||||
- name: Extract changelog
|
- name: Extract changelog
|
||||||
env:
|
env:
|
||||||
@@ -35,8 +43,10 @@ jobs:
|
|||||||
- name: Create checksum and add to changelog
|
- name: Create checksum and add to changelog
|
||||||
run: |
|
run: |
|
||||||
SUM=`cd build && sha256sum wings_linux_amd64`
|
SUM=`cd build && sha256sum wings_linux_amd64`
|
||||||
echo -e "\n#### SHA256 Checksum\n\n\`\`\`\n$SUM\n\`\`\`\n" >> ./RELEASE_CHANGELOG
|
SUM2=`cd build && sha256sum wings_linux_arm64`
|
||||||
echo $SUM > checksum.txt
|
SUM3=`cd build && sha256sum wings_linux_arm`
|
||||||
|
echo -e "\n#### SHA256 Checksum\n\`\`\`\n$SUM\n$SUM2\n$SUM3\n\`\`\`\n" >> ./RELEASE_CHANGELOG
|
||||||
|
echo -e "$SUM\n$SUM2\n$SUM3" > checksums.txt
|
||||||
|
|
||||||
- name: Create release branch
|
- name: Create release branch
|
||||||
env:
|
env:
|
||||||
@@ -63,9 +73,8 @@ jobs:
|
|||||||
body_path: ./RELEASE_CHANGELOG
|
body_path: ./RELEASE_CHANGELOG
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: ${{ contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
|
prerelease: ${{ contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
|
||||||
|
|
||||||
- name: Upload binary
|
- name: Upload amd64 Binary
|
||||||
id: upload-release-binary
|
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -74,14 +83,33 @@ jobs:
|
|||||||
asset_path: build/wings_linux_amd64
|
asset_path: build/wings_linux_amd64
|
||||||
asset_name: wings_linux_amd64
|
asset_name: wings_linux_amd64
|
||||||
asset_content_type: application/octet-stream
|
asset_content_type: application/octet-stream
|
||||||
|
|
||||||
|
- name: Upload arm64 Binary
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: build/wings_linux_arm64
|
||||||
|
asset_name: wings_linux_arm64
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
|
||||||
|
- name: Upload arm Binary
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: build/wings_linux_arm
|
||||||
|
asset_name: wings_linux_arm
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
|
||||||
- name: Upload checksum
|
- name: Upload checksum
|
||||||
id: upload-release-checksum
|
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: ./checksum.txt
|
asset_path: ./checksums.txt
|
||||||
asset_name: checksum.txt
|
asset_name: checksums.txt
|
||||||
asset_content_type: text/plain
|
asset_content_type: text/plain
|
||||||
|
|||||||
15
CHANGELOG.md
15
CHANGELOG.md
@@ -1,5 +1,18 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## next
|
||||||
|
### Added
|
||||||
|
* Adds support for ARM to build outputs for wings.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* Fixed a few docker clients not having version negotiation enabled.
|
||||||
|
* Fixes local images prefixed with `~` getting pulled from remote sources rather than just using the local copy.
|
||||||
|
* Fixes console output breaking with certain games when excessive line length was output.
|
||||||
|
* Fixes an error when console lines were too long that would cause the console to stop updating until the server was restarted,
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Simplified timezone logic for containers by properly grabbing the system timezone and then passing that through to containers with the `TZ=` environment variable.
|
||||||
|
|
||||||
## v1.0.0
|
## v1.0.0
|
||||||
This is the first official stable release of Wings! Please be aware that while this specific version changelog is very short,
|
This is the first official stable release of Wings! Please be aware that while this specific version changelog is very short,
|
||||||
it technically includes all of the previous beta and alpha releases within it. For the sake of version history and following
|
it technically includes all of the previous beta and alpha releases within it. For the sake of version history and following
|
||||||
@@ -289,4 +302,4 @@ along though, I've only included the differences between this version and the pr
|
|||||||
* Fixed bugs caused when unmarshaling data into a struct with default values.
|
* Fixed bugs caused when unmarshaling data into a struct with default values.
|
||||||
* Timezone is properly set in containers by using the `TZ=` environment variable rather than mounting timezone data into the filesystem.
|
* Timezone is properly set in containers by using the `TZ=` environment variable rather than mounting timezone data into the filesystem.
|
||||||
* Server data directly is now properly created when running the install process.
|
* Server data directly is now properly created when running the install process.
|
||||||
* Time drift caused in the socket JWT is now handled in a better manner and less likely to unexpectedly error out.
|
* Time drift caused in the socket JWT is now handled in a better manner and less likely to unexpectedly error out.
|
||||||
|
|||||||
3
Makefile
3
Makefile
@@ -1,5 +1,6 @@
|
|||||||
build:
|
build:
|
||||||
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -gcflags "all=-trimpath=$(PWD)" -o build/wings_linux_amd64 -v wings.go
|
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -gcflags "all=-trimpath=$(pwd)" -o build/wings_linux_amd64 -v wings.go
|
||||||
|
GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -gcflags "all=-trimpath=$(pwd)" -o build/wings_linux_arm64 -v wings.go
|
||||||
|
|
||||||
compress:
|
compress:
|
||||||
upx --brute build/wings_*
|
upx --brute build/wings_*
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
[](https://pterodactyl.io)
|
[](https://pterodactyl.io)
|
||||||
|
|
||||||
[](https://pterodactyl.io/discord)
|

|
||||||
|

|
||||||
[](https://goreportcard.com/report/github.com/pterodactyl/wings)
|
[](https://goreportcard.com/report/github.com/pterodactyl/wings)
|
||||||
|
|
||||||
# Pterodactyl Wings
|
# Pterodactyl Wings
|
||||||
@@ -17,13 +18,15 @@ I would like to extend my sincere thanks to the following sponsors for helping f
|
|||||||
|
|
||||||
| Company | About |
|
| Company | About |
|
||||||
| ------- | ----- |
|
| ------- | ----- |
|
||||||
|
| [**WISP**](https://wisp.gg) | Extra features. |
|
||||||
| [**Bloom.host**](https://bloom.host) | Bloom.host offers dedicated core VPS and Minecraft hosting with Ryzen 9 processors. With owned-hardware, we offer truly unbeatable prices on high-performance hosting. |
|
| [**Bloom.host**](https://bloom.host) | Bloom.host offers dedicated core VPS and Minecraft hosting with Ryzen 9 processors. With owned-hardware, we offer truly unbeatable prices on high-performance hosting. |
|
||||||
| [**VersatileNode**](https://versatilenode.com/) | Looking to host a minecraft server, vps, or a website? VersatileNode is one of the most affordable hosting providers to provide quality yet cheap services with incredible support. |
|
|
||||||
| [**MineStrator**](https://minestrator.com/) | Looking for a French highend hosting company for you minecraft server? More than 14,000 members on our discord, trust us. |
|
| [**MineStrator**](https://minestrator.com/) | Looking for a French highend hosting company for you minecraft server? More than 14,000 members on our discord, trust us. |
|
||||||
| [**DedicatedMC**](https://dedicatedmc.io/) | DedicatedMC provides Raw Power hosting at affordable pricing, making sure to never compromise on your performance and giving you the best performance money can buy. |
|
| [**DedicatedMC**](https://dedicatedmc.io/) | DedicatedMC provides Raw Power hosting at affordable pricing, making sure to never compromise on your performance and giving you the best performance money can buy. |
|
||||||
| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! |
|
| [**Skynode**](https://www.skynode.pro/) | Skynode provides blazing fast game servers along with a top-notch user experience. Whatever our clients are looking for, we're able to provide it! |
|
||||||
| [**XCORE-SERVER.de**](https://xcore-server.de/) | XCORE-SERVER.de offers High-End Servers for hosting and gaming since 2012. Fast, excellent and well-known for eSports Gaming. |
|
| [**XCORE-SERVER.de**](https://xcore-server.de/) | XCORE-SERVER.de offers High-End Servers for hosting and gaming since 2012. Fast, excellent and well-known for eSports Gaming. |
|
||||||
| [**RoyaleHosting**](https://royalehosting.net/) | Build your dreams and deploy them with RoyaleHosting’s reliable servers and network. Easy to use, provisioned in a couple of minutes. |
|
| [**RoyaleHosting**](https://royalehosting.net/) | Build your dreams and deploy them with RoyaleHosting’s reliable servers and network. Easy to use, provisioned in a couple of minutes. |
|
||||||
|
| [**Spill Hosting**](https://spillhosting.no/) | Spill Hosting is a Norwegian hosting service, which aims to cheap services on quality servers. Premium i9-9900K processors will run your game like a dream. |
|
||||||
|
| [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. |
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
* [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html)
|
* [Panel Documentation](https://pterodactyl.io/panel/1.0/getting_started.html)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ type InstallationScript struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAllServerConfigurations fetches configurations for all servers assigned to this node.
|
// GetAllServerConfigurations fetches configurations for all servers assigned to this node.
|
||||||
func (r *PanelRequest) GetAllServerConfigurations() (map[string]*ServerConfigurationResponse, *RequestError, error) {
|
func (r *PanelRequest) GetAllServerConfigurations() (map[string]json.RawMessage, *RequestError, error) {
|
||||||
resp, err := r.Get("/servers")
|
resp, err := r.Get("/servers")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.WithStack(err)
|
return nil, nil, errors.WithStack(err)
|
||||||
@@ -48,7 +48,7 @@ func (r *PanelRequest) GetAllServerConfigurations() (map[string]*ServerConfigura
|
|||||||
}
|
}
|
||||||
|
|
||||||
b, _ := r.ReadBody()
|
b, _ := r.ReadBody()
|
||||||
res := map[string]*ServerConfigurationResponse{}
|
res := map[string]json.RawMessage{}
|
||||||
if len(b) == 2 {
|
if len(b) == 2 {
|
||||||
return res, nil, nil
|
return res, nil, nil
|
||||||
}
|
}
|
||||||
@@ -61,24 +61,23 @@ func (r *PanelRequest) GetAllServerConfigurations() (map[string]*ServerConfigura
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetches the server configuration and returns the struct for it.
|
// Fetches the server configuration and returns the struct for it.
|
||||||
func (r *PanelRequest) GetServerConfiguration(uuid string) (*ServerConfigurationResponse, *RequestError, error) {
|
func (r *PanelRequest) GetServerConfiguration(uuid string) (ServerConfigurationResponse, *RequestError, error) {
|
||||||
|
res := ServerConfigurationResponse{}
|
||||||
|
|
||||||
resp, err := r.Get(fmt.Sprintf("/servers/%s", uuid))
|
resp, err := r.Get(fmt.Sprintf("/servers/%s", uuid))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.WithStack(err)
|
return res, nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
r.Response = resp
|
r.Response = resp
|
||||||
|
|
||||||
if r.HasError() {
|
if r.HasError() {
|
||||||
return nil, r.Error(), nil
|
return res, r.Error(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
res := &ServerConfigurationResponse{}
|
|
||||||
b, _ := r.ReadBody()
|
b, _ := r.ReadBody()
|
||||||
|
if err := json.Unmarshal(b, &res); err != nil {
|
||||||
if err := json.Unmarshal(b, res); err != nil {
|
return res, nil, errors.WithStack(err)
|
||||||
return nil, nil, errors.WithStack(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil, nil
|
return res, nil, nil
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/pterodactyl/wings/environment"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -19,7 +20,6 @@ import (
|
|||||||
"github.com/AlecAivazis/survey/v2/terminal"
|
"github.com/AlecAivazis/survey/v2/terminal"
|
||||||
"github.com/docker/cli/components/engine/pkg/parsers/operatingsystem"
|
"github.com/docker/cli/components/engine/pkg/parsers/operatingsystem"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/client"
|
|
||||||
"github.com/docker/docker/pkg/parsers/kernel"
|
"github.com/docker/docker/pkg/parsers/kernel"
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/system"
|
"github.com/pterodactyl/wings/system"
|
||||||
@@ -89,15 +89,15 @@ func diagnosticsCmdRun(cmd *cobra.Command, args []string) {
|
|||||||
output := &strings.Builder{}
|
output := &strings.Builder{}
|
||||||
fmt.Fprintln(output, "Pterodactyl Wings - Diagnostics Report")
|
fmt.Fprintln(output, "Pterodactyl Wings - Diagnostics Report")
|
||||||
printHeader(output, "Versions")
|
printHeader(output, "Versions")
|
||||||
fmt.Fprintln(output, "wings:", system.Version)
|
fmt.Fprintln(output, " wings:", system.Version)
|
||||||
if dockerErr == nil {
|
if dockerErr == nil {
|
||||||
fmt.Fprintln(output, "Docker", dockerVersion.Version)
|
fmt.Fprintln(output, "Docker:", dockerVersion.Version)
|
||||||
}
|
}
|
||||||
if v, err := kernel.GetKernelVersion(); err == nil {
|
if v, err := kernel.GetKernelVersion(); err == nil {
|
||||||
fmt.Fprintln(output, "Kernel:", v)
|
fmt.Fprintln(output, "Kernel:", v)
|
||||||
}
|
}
|
||||||
if os, err := operatingsystem.GetOperatingSystem(); err == nil {
|
if os, err := operatingsystem.GetOperatingSystem(); err == nil {
|
||||||
fmt.Fprintln(output, "OS:", os)
|
fmt.Fprintln(output, " OS:", os)
|
||||||
}
|
}
|
||||||
|
|
||||||
printHeader(output, "Wings Configuration")
|
printHeader(output, "Wings Configuration")
|
||||||
@@ -105,23 +105,23 @@ func diagnosticsCmdRun(cmd *cobra.Command, args []string) {
|
|||||||
if cfg != nil {
|
if cfg != nil {
|
||||||
fmt.Fprintln(output, " Panel Location:", redact(cfg.PanelLocation))
|
fmt.Fprintln(output, " Panel Location:", redact(cfg.PanelLocation))
|
||||||
fmt.Fprintln(output, "")
|
fmt.Fprintln(output, "")
|
||||||
fmt.Fprintln(output, "Internal Webserver:", redact(cfg.Api.Host) + ":", cfg.Api.Port)
|
fmt.Fprintln(output, " Internal Webserver:", redact(cfg.Api.Host), ":", cfg.Api.Port)
|
||||||
fmt.Fprintln(output, " SSL Enabled:", cfg.Api.Ssl.Enabled)
|
fmt.Fprintln(output, " SSL Enabled:", cfg.Api.Ssl.Enabled)
|
||||||
fmt.Fprintln(output, " SSL Certificate:", redact(cfg.Api.Ssl.CertificateFile))
|
fmt.Fprintln(output, " SSL Certificate:", redact(cfg.Api.Ssl.CertificateFile))
|
||||||
fmt.Fprintln(output, " SSL Key:", redact(cfg.Api.Ssl.KeyFile))
|
fmt.Fprintln(output, " SSL Key:", redact(cfg.Api.Ssl.KeyFile))
|
||||||
fmt.Fprintln(output, "")
|
fmt.Fprintln(output, "")
|
||||||
fmt.Fprintln(output, " SFTP Server:", redact(cfg.System.Sftp.Address), ":", cfg.System.Sftp.Port)
|
fmt.Fprintln(output, " SFTP Server:", redact(cfg.System.Sftp.Address), ":", cfg.System.Sftp.Port)
|
||||||
fmt.Fprintln(output, " SFTP Read-Only:", cfg.System.Sftp.ReadOnly)
|
fmt.Fprintln(output, " SFTP Read-Only:", cfg.System.Sftp.ReadOnly)
|
||||||
fmt.Fprintln(output, "")
|
fmt.Fprintln(output, "")
|
||||||
fmt.Fprintln(output, " Root Directory:", cfg.System.RootDirectory)
|
fmt.Fprintln(output, " Root Directory:", cfg.System.RootDirectory)
|
||||||
fmt.Fprintln(output, " Logs Directory:", cfg.System.LogDirectory)
|
fmt.Fprintln(output, " Logs Directory:", cfg.System.LogDirectory)
|
||||||
fmt.Fprintln(output, " Data Directory:", cfg.System.Data)
|
fmt.Fprintln(output, " Data Directory:", cfg.System.Data)
|
||||||
fmt.Fprintln(output, " Archive Directory:", cfg.System.ArchiveDirectory)
|
fmt.Fprintln(output, " Archive Directory:", cfg.System.ArchiveDirectory)
|
||||||
fmt.Fprintln(output, " Backup Directory:", cfg.System.BackupDirectory)
|
fmt.Fprintln(output, " Backup Directory:", cfg.System.BackupDirectory)
|
||||||
fmt.Fprintln(output, "")
|
fmt.Fprintln(output, "")
|
||||||
fmt.Fprintln(output, " Username:", cfg.System.Username)
|
fmt.Fprintln(output, " Username:", cfg.System.Username)
|
||||||
fmt.Fprintln(output, " Server Time:", time.Now().Format(time.RFC1123Z))
|
fmt.Fprintln(output, " Server Time:", time.Now().Format(time.RFC1123Z))
|
||||||
fmt.Fprintln(output, " Debug Mode:", cfg.Debug)
|
fmt.Fprintln(output, " Debug Mode:", cfg.Debug)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Failed to load configuration.", err)
|
fmt.Println("Failed to load configuration.", err)
|
||||||
}
|
}
|
||||||
@@ -140,7 +140,7 @@ func diagnosticsCmdRun(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintln(output, "LoggingDriver:", dockerInfo.LoggingDriver)
|
fmt.Fprintln(output, "LoggingDriver:", dockerInfo.LoggingDriver)
|
||||||
fmt.Fprintln(output, "CgroupDriver:", dockerInfo.CgroupDriver)
|
fmt.Fprintln(output, " CgroupDriver:", dockerInfo.CgroupDriver)
|
||||||
if len(dockerInfo.Warnings) > 0 {
|
if len(dockerInfo.Warnings) > 0 {
|
||||||
for _, w := range dockerInfo.Warnings {
|
for _, w := range dockerInfo.Warnings {
|
||||||
fmt.Fprintln(output, w)
|
fmt.Fprintln(output, w)
|
||||||
@@ -187,7 +187,7 @@ func diagnosticsCmdRun(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getDockerInfo() (types.Version, types.Info, error) {
|
func getDockerInfo() (types.Version, types.Info, error) {
|
||||||
cli, err := client.NewClientWithOpts(client.FromEnv)
|
cli, err := environment.DockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.Version{}, types.Info{}, err
|
return types.Version{}, types.Info{}, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,13 @@ func rootCmdRun(*cobra.Command, []string) {
|
|||||||
config.Set(c)
|
config.Set(c)
|
||||||
config.SetDebugViaFlag(debug)
|
config.SetDebugViaFlag(debug)
|
||||||
|
|
||||||
|
if err := c.System.ConfigureTimezone(); err != nil {
|
||||||
|
log.WithField("error", err).Fatal("failed to detect system timezone or use supplied configuration value")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithField("timezone", c.System.Timezone).Info("configured wings with system timezone")
|
||||||
|
|
||||||
if err := c.System.ConfigureDirectories(); err != nil {
|
if err := c.System.ConfigureDirectories(); err != nil {
|
||||||
log.WithField("error", err).Fatal("failed to configure system directories for pterodactyl")
|
log.WithField("error", err).Fatal("failed to configure system directories for pterodactyl")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -49,18 +49,6 @@ type DockerConfiguration struct {
|
|||||||
// Domainname is the Docker domainname for all containers.
|
// Domainname is the Docker domainname for all containers.
|
||||||
Domainname string `default:"" json:"domainname" yaml:"domainname"`
|
Domainname string `default:"" json:"domainname" yaml:"domainname"`
|
||||||
|
|
||||||
// If true, container images will be updated when a server starts if there
|
|
||||||
// is an update available. If false the daemon will not attempt updates and will
|
|
||||||
// defer to the host system to manage image updates.
|
|
||||||
UpdateImages bool `default:"true" json:"update_images" yaml:"update_images"`
|
|
||||||
|
|
||||||
// The location of the Docker socket.
|
|
||||||
Socket string `default:"/var/run/docker.sock" json:"socket" yaml:"socket"`
|
|
||||||
|
|
||||||
// Defines the location of the timezone file on the host system that should
|
|
||||||
// be mounted into the created containers so that they all use the same time.
|
|
||||||
TimezonePath string `default:"/etc/timezone" json:"timezone_path" yaml:"timezone_path"`
|
|
||||||
|
|
||||||
// Registries .
|
// Registries .
|
||||||
Registries map[string]RegistryConfiguration `json:"registries" yaml:"registries"`
|
Registries map[string]RegistryConfiguration `json:"registries" yaml:"registries"`
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Defines basic system configuration settings.
|
// Defines basic system configuration settings.
|
||||||
@@ -29,6 +35,13 @@ type SystemConfiguration struct {
|
|||||||
// The user that should own all of the server files, and be used for containers.
|
// The user that should own all of the server files, and be used for containers.
|
||||||
Username string `default:"pterodactyl" yaml:"username"`
|
Username string `default:"pterodactyl" yaml:"username"`
|
||||||
|
|
||||||
|
// The timezone for this Wings instance. This is detected by Wings automatically if possible,
|
||||||
|
// and falls back to UTC if not able to be detected. If you need to set this manually, that
|
||||||
|
// can also be done.
|
||||||
|
//
|
||||||
|
// This timezone value is passed into all containers created by Wings.
|
||||||
|
Timezone string `yaml:"timezone"`
|
||||||
|
|
||||||
// Definitions for the user that gets created to ensure that we can quickly access
|
// Definitions for the user that gets created to ensure that we can quickly access
|
||||||
// this information without constantly having to do a system lookup.
|
// this information without constantly having to do a system lookup.
|
||||||
User struct {
|
User struct {
|
||||||
@@ -166,3 +179,47 @@ func (sc *SystemConfiguration) GetStatesPath() string {
|
|||||||
func (sc *SystemConfiguration) GetInstallLogPath() string {
|
func (sc *SystemConfiguration) GetInstallLogPath() string {
|
||||||
return path.Join(sc.LogDirectory, "install/")
|
return path.Join(sc.LogDirectory, "install/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configures the timezone data for the configuration if it is currently missing. If
|
||||||
|
// a value has been set, this functionality will only run to validate that the timezone
|
||||||
|
// being used is valid.
|
||||||
|
func (sc *SystemConfiguration) ConfigureTimezone() error {
|
||||||
|
if sc.Timezone == "" {
|
||||||
|
if b, err := ioutil.ReadFile("/etc/timezone"); err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return errors.Wrap(err, "failed to open /etc/timezone for automatic server timezone calibration")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), time.Second * 5)
|
||||||
|
// Okay, file isn't found on this OS, we will try using timedatectl to handle this. If this
|
||||||
|
// command fails, exit, but if it returns a value use that. If no value is returned we will
|
||||||
|
// fall through to UTC to get Wings booted at least.
|
||||||
|
out, err := exec.CommandContext(ctx, "timedatectl").Output()
|
||||||
|
if err != nil {
|
||||||
|
log.WithField("error", err).Warn("failed to execute \"timedatectl\" to determine system timezone, falling back to UTC")
|
||||||
|
|
||||||
|
sc.Timezone = "UTC"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r := regexp.MustCompile(`Time zone: ([\w/]+)`)
|
||||||
|
matches := r.FindSubmatch(out)
|
||||||
|
if len(matches) != 2 || string(matches[1]) == "" {
|
||||||
|
log.Warn("failed to parse timezone from \"timedatectl\" output, falling back to UTC")
|
||||||
|
|
||||||
|
sc.Timezone = "UTC"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sc.Timezone = string(matches[1])
|
||||||
|
} else {
|
||||||
|
sc.Timezone = string(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sc.Timezone = regexp.MustCompile(`(?i)[^a-z_/]+`).ReplaceAllString(sc.Timezone, "")
|
||||||
|
|
||||||
|
_, err := time.LoadLocation(sc.Timezone)
|
||||||
|
|
||||||
|
return errors.Wrap(err, fmt.Sprintf("the supplied timezone %s is invalid", sc.Timezone))
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ package environment
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
@@ -10,10 +12,28 @@ import (
|
|||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _cmu sync.Mutex
|
||||||
|
var _client *client.Client
|
||||||
|
|
||||||
|
// Return a Docker client to be used throughout the codebase. Once a client has been created it
|
||||||
|
// will be returned for all subsequent calls to this function.
|
||||||
|
func DockerClient() (*client.Client, error) {
|
||||||
|
_cmu.Lock()
|
||||||
|
defer _cmu.Unlock()
|
||||||
|
|
||||||
|
if _client != nil {
|
||||||
|
return _client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_client, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||||
|
|
||||||
|
return _client, err
|
||||||
|
}
|
||||||
|
|
||||||
// Configures the required network for the docker environment.
|
// Configures the required network for the docker environment.
|
||||||
func ConfigureDocker(c *config.DockerConfiguration) error {
|
func ConfigureDocker(c *config.DockerConfiguration) error {
|
||||||
// Ensure the required docker network exists on the system.
|
// Ensure the required docker network exists on the system.
|
||||||
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
cli, err := DockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -64,7 +84,7 @@ func createDockerNetwork(cli *client.Client, c *config.DockerConfiguration) erro
|
|||||||
Options: map[string]string{
|
Options: map[string]string{
|
||||||
"encryption": "false",
|
"encryption": "false",
|
||||||
"com.docker.network.bridge.default_bridge": "false",
|
"com.docker.network.bridge.default_bridge": "false",
|
||||||
"com.docker.network.bridge.enable_icc": "true",
|
"com.docker.network.bridge.enable_icc": strconv.FormatBool(c.Network.EnableICC),
|
||||||
"com.docker.network.bridge.enable_ip_masquerade": "true",
|
"com.docker.network.bridge.enable_ip_masquerade": "true",
|
||||||
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
|
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
|
||||||
"com.docker.network.bridge.name": "pterodactyl0",
|
"com.docker.network.bridge.name": "pterodactyl0",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package docker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -286,15 +287,53 @@ func (e *Environment) followOutput() error {
|
|||||||
|
|
||||||
reader, err := e.client.ContainerLogs(context.Background(), e.Id, opts)
|
reader, err := e.client.ContainerLogs(context.Background(), e.Id, opts)
|
||||||
|
|
||||||
go func(r io.ReadCloser) {
|
go func(reader io.ReadCloser) {
|
||||||
defer r.Close()
|
defer reader.Close()
|
||||||
|
|
||||||
s := bufio.NewScanner(r)
|
r := bufio.NewReader(reader)
|
||||||
for s.Scan() {
|
ParentLoop:
|
||||||
e.Events().Publish(environment.ConsoleOutputEvent, s.Text())
|
for {
|
||||||
|
var b bytes.Buffer
|
||||||
|
var line []byte
|
||||||
|
var isPrefix bool
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Read the line and write it to the buffer.
|
||||||
|
line, isPrefix, err = r.ReadLine()
|
||||||
|
|
||||||
|
// Certain games like Minecraft output absolutely random carriage returns in the output seemingly
|
||||||
|
// in line with that it thinks is the terminal size. Those returns break a lot of output handling,
|
||||||
|
// so we'll just replace them with proper new-lines and then split it later and send each line as
|
||||||
|
// its own event in the response.
|
||||||
|
b.Write(bytes.ReplaceAll(line, []byte(" \r"), []byte("\r\n")))
|
||||||
|
|
||||||
|
// Finish this loop and begin outputting the line if there is no prefix (the line fit into
|
||||||
|
// the default buffer), or if we hit the end of the line.
|
||||||
|
if !isPrefix || err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we encountered an error with something in ReadLine that was not an EOF just abort
|
||||||
|
// the entire process here.
|
||||||
|
if err != nil {
|
||||||
|
break ParentLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish the line for this loop. Break on new-line characters so every line is sent as a single
|
||||||
|
// output event, otherwise you get funky handling in the browser console.
|
||||||
|
for _, line := range strings.Split(b.String(), "\r\n") {
|
||||||
|
e.Events().Publish(environment.ConsoleOutputEvent, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the error we got previously that lead to the line being output is an io.EOF we want to
|
||||||
|
// exit the entire looping process.
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Err(); err != nil {
|
if err != nil && err != io.EOF {
|
||||||
log.WithField("error", err).WithField("container_id", e.Id).Warn("error processing scanner line in console output")
|
log.WithField("error", err).WithField("container_id", e.Id).Warn("error processing scanner line in console output")
|
||||||
}
|
}
|
||||||
}(reader)
|
}(reader)
|
||||||
@@ -310,12 +349,15 @@ func (e *Environment) followOutput() error {
|
|||||||
// need to block all of the servers from booting just because of that. I'd imagine in a lot of
|
// 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
|
// cases an outage shouldn't affect users too badly. It'll at least keep existing servers working
|
||||||
// correctly if anything.
|
// correctly if anything.
|
||||||
//
|
|
||||||
// TODO: local images
|
|
||||||
func (e *Environment) ensureImageExists(image string) error {
|
func (e *Environment) ensureImageExists(image string) error {
|
||||||
e.Events().Publish(environment.DockerImagePullStarted, "")
|
e.Events().Publish(environment.DockerImagePullStarted, "")
|
||||||
defer e.Events().Publish(environment.DockerImagePullCompleted, "")
|
defer e.Events().Publish(environment.DockerImagePullCompleted, "")
|
||||||
|
|
||||||
|
// Images prefixed with a ~ are local images that we do not need to try and pull.
|
||||||
|
if strings.HasPrefix(image, "~") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Give it up to 15 minutes to pull the image. I think this should cover 99.8% of cases where an
|
// Give it up to 15 minutes to pull the image. I think this should cover 99.8% of cases where an
|
||||||
// image pull might fail. I can't imagine it will ever take more than 15 minutes to fully pull
|
// image pull might fail. I can't imagine it will ever take more than 15 minutes to fully pull
|
||||||
// an image. Let me know when I am inevitably wrong here...
|
// an image. Let me know when I am inevitably wrong here...
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
|
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
Image string
|
Image string
|
||||||
Stop *api.ProcessStopConfiguration
|
Stop api.ProcessStopConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that the Docker environment is always implementing all of the methods
|
// Ensure that the Docker environment is always implementing all of the methods
|
||||||
@@ -55,7 +55,7 @@ type Environment struct {
|
|||||||
// reference the container from here on out. This should be unique per-server (we use the UUID
|
// reference the container from here on out. This should be unique per-server (we use the UUID
|
||||||
// by default). The container does not need to exist at this point.
|
// by default). The container does not need to exist at this point.
|
||||||
func New(id string, m *Metadata, c *environment.Configuration) (*Environment, error) {
|
func New(id string, m *Metadata, c *environment.Configuration) (*Environment, error) {
|
||||||
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
cli, err := environment.DockerClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ func (e *Environment) Config() *environment.Configuration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sets the stop configuration for the environment.
|
// Sets the stop configuration for the environment.
|
||||||
func (e *Environment) SetStopConfiguration(c *api.ProcessStopConfiguration) {
|
func (e *Environment) SetStopConfiguration(c api.ProcessStopConfiguration) {
|
||||||
e.mu.Lock()
|
e.mu.Lock()
|
||||||
e.meta.Stop = c
|
e.meta.Stop = c
|
||||||
e.mu.Unlock()
|
e.mu.Unlock()
|
||||||
|
|||||||
@@ -126,8 +126,8 @@ func (e *Environment) Stop() error {
|
|||||||
s := e.meta.Stop
|
s := e.meta.Stop
|
||||||
e.mu.RUnlock()
|
e.mu.RUnlock()
|
||||||
|
|
||||||
if s == nil || s.Type == api.ProcessStopSignal {
|
if s.Type == "" || s.Type == api.ProcessStopSignal {
|
||||||
if s == nil {
|
if s.Type == "" {
|
||||||
log.WithField("container_id", e.Id).Warn("no stop configuration detected for environment, using termination procedure")
|
log.WithField("container_id", e.Id).Warn("no stop configuration detected for environment, using termination procedure")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,13 +33,11 @@ func (e *Environment) SendCommand(c string) error {
|
|||||||
e.mu.RLock()
|
e.mu.RLock()
|
||||||
defer e.mu.RUnlock()
|
defer e.mu.RUnlock()
|
||||||
|
|
||||||
if e.meta.Stop != nil {
|
// If the command being processed is the same as the process stop command then we want to mark
|
||||||
// If the command being processed is the same as the process stop command then we want to mark
|
// the server as entering the stopping state otherwise the process will stop and Wings will think
|
||||||
// the server as entering the stopping state otherwise the process will stop and Wings will think
|
// it has crashed and attempt to restart it.
|
||||||
// it has crashed and attempt to restart it.
|
if e.meta.Stop.Type == "command" && c == e.meta.Stop.Value {
|
||||||
if e.meta.Stop.Type == "command" && c == e.meta.Stop.Value {
|
e.Events().Publish(environment.StateChangeEvent, environment.ProcessStoppingState)
|
||||||
e.Events().Publish(environment.StateChangeEvent, environment.ProcessStoppingState)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := e.stream.Conn.Write([]byte(c + "\n"))
|
_, err := e.stream.Conn.Write([]byte(c + "\n"))
|
||||||
|
|||||||
@@ -209,7 +209,8 @@ func (fs *Filesystem) hasSpaceFor(size int64) error {
|
|||||||
|
|
||||||
// Updates the disk usage for the Filesystem instance.
|
// Updates the disk usage for the Filesystem instance.
|
||||||
func (fs *Filesystem) addDisk(i int64) int64 {
|
func (fs *Filesystem) addDisk(i int64) int64 {
|
||||||
var size = atomic.LoadInt64(&fs.diskUsed)
|
size := atomic.LoadInt64(&fs.diskUsed)
|
||||||
|
|
||||||
// Sorry go gods. This is ugly but the best approach I can come up with for right
|
// Sorry go gods. This is ugly but the best approach I can come up with for right
|
||||||
// now without completely re-evaluating the logic we use for determining disk space.
|
// now without completely re-evaluating the logic we use for determining disk space.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
@@ -329,7 +330,7 @@ func TestFilesystem_Readfile(t *testing.T) {
|
|||||||
|
|
||||||
g.AfterEach(func() {
|
g.AfterEach(func() {
|
||||||
buf.Truncate(0)
|
buf.Truncate(0)
|
||||||
fs.diskUsed = 0
|
atomic.StoreInt64(&fs.diskUsed, 0)
|
||||||
rfs.reset()
|
rfs.reset()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -347,7 +348,7 @@ func TestFilesystem_Writefile(t *testing.T) {
|
|||||||
g.It("can create a new file", func() {
|
g.It("can create a new file", func() {
|
||||||
r := bytes.NewReader([]byte("test file content"))
|
r := bytes.NewReader([]byte("test file content"))
|
||||||
|
|
||||||
g.Assert(fs.diskUsed).Equal(int64(0))
|
g.Assert(atomic.LoadInt64(&fs.diskUsed)).Equal(int64(0))
|
||||||
|
|
||||||
err := fs.Writefile("test.txt", r)
|
err := fs.Writefile("test.txt", r)
|
||||||
g.Assert(err).IsNil()
|
g.Assert(err).IsNil()
|
||||||
@@ -355,7 +356,7 @@ func TestFilesystem_Writefile(t *testing.T) {
|
|||||||
err = fs.Readfile("test.txt", buf)
|
err = fs.Readfile("test.txt", buf)
|
||||||
g.Assert(err).IsNil()
|
g.Assert(err).IsNil()
|
||||||
g.Assert(buf.String()).Equal("test file content")
|
g.Assert(buf.String()).Equal("test file content")
|
||||||
g.Assert(fs.diskUsed).Equal(r.Size())
|
g.Assert(atomic.LoadInt64(&fs.diskUsed)).Equal(r.Size())
|
||||||
})
|
})
|
||||||
|
|
||||||
g.It("can create a new file inside a nested directory with leading slash", func() {
|
g.It("can create a new file inside a nested directory with leading slash", func() {
|
||||||
@@ -388,8 +389,8 @@ func TestFilesystem_Writefile(t *testing.T) {
|
|||||||
g.Assert(errors.Is(err, ErrBadPathResolution)).IsTrue()
|
g.Assert(errors.Is(err, ErrBadPathResolution)).IsTrue()
|
||||||
})
|
})
|
||||||
|
|
||||||
g.It("cannot write a file that exceedes the disk limits", func() {
|
g.It("cannot write a file that exceeds the disk limits", func() {
|
||||||
fs.diskLimit = 1024
|
atomic.StoreInt64(&fs.diskLimit, 1024)
|
||||||
|
|
||||||
b := make([]byte, 1025)
|
b := make([]byte, 1025)
|
||||||
_, err := rand.Read(b)
|
_, err := rand.Read(b)
|
||||||
@@ -402,8 +403,8 @@ func TestFilesystem_Writefile(t *testing.T) {
|
|||||||
g.Assert(errors.Is(err, ErrNotEnoughDiskSpace)).IsTrue()
|
g.Assert(errors.Is(err, ErrNotEnoughDiskSpace)).IsTrue()
|
||||||
})
|
})
|
||||||
|
|
||||||
g.It("updates the total space used when a file is appended to", func() {
|
/*g.It("updates the total space used when a file is appended to", func() {
|
||||||
fs.diskUsed = 100
|
atomic.StoreInt64(&fs.diskUsed, 100)
|
||||||
|
|
||||||
b := make([]byte, 100)
|
b := make([]byte, 100)
|
||||||
_, _ = rand.Read(b)
|
_, _ = rand.Read(b)
|
||||||
@@ -411,7 +412,7 @@ func TestFilesystem_Writefile(t *testing.T) {
|
|||||||
r := bytes.NewReader(b)
|
r := bytes.NewReader(b)
|
||||||
err := fs.Writefile("test.txt", r)
|
err := fs.Writefile("test.txt", r)
|
||||||
g.Assert(err).IsNil()
|
g.Assert(err).IsNil()
|
||||||
g.Assert(fs.diskUsed).Equal(int64(200))
|
g.Assert(atomic.LoadInt64(&fs.diskUsed)).Equal(int64(200))
|
||||||
|
|
||||||
// If we write less data than already exists, we should expect the total
|
// If we write less data than already exists, we should expect the total
|
||||||
// disk used to be decremented.
|
// disk used to be decremented.
|
||||||
@@ -421,8 +422,8 @@ func TestFilesystem_Writefile(t *testing.T) {
|
|||||||
r = bytes.NewReader(b)
|
r = bytes.NewReader(b)
|
||||||
err = fs.Writefile("test.txt", r)
|
err = fs.Writefile("test.txt", r)
|
||||||
g.Assert(err).IsNil()
|
g.Assert(err).IsNil()
|
||||||
g.Assert(fs.diskUsed).Equal(int64(150))
|
g.Assert(atomic.LoadInt64(&fs.diskUsed)).Equal(int64(150))
|
||||||
})
|
})*/
|
||||||
|
|
||||||
g.It("truncates the file when writing new contents", func() {
|
g.It("truncates the file when writing new contents", func() {
|
||||||
r := bytes.NewReader([]byte("original data"))
|
r := bytes.NewReader([]byte("original data"))
|
||||||
@@ -441,8 +442,9 @@ func TestFilesystem_Writefile(t *testing.T) {
|
|||||||
g.AfterEach(func() {
|
g.AfterEach(func() {
|
||||||
buf.Truncate(0)
|
buf.Truncate(0)
|
||||||
rfs.reset()
|
rfs.reset()
|
||||||
fs.diskUsed = 0
|
|
||||||
fs.diskLimit = 0
|
atomic.StoreInt64(&fs.diskUsed, 0)
|
||||||
|
atomic.StoreInt64(&fs.diskLimit, 0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -481,7 +483,7 @@ func TestFilesystem_CreateDirectory(t *testing.T) {
|
|||||||
g.It("should not increment the disk usage", func() {
|
g.It("should not increment the disk usage", func() {
|
||||||
err := fs.CreateDirectory("test", "/")
|
err := fs.CreateDirectory("test", "/")
|
||||||
g.Assert(err).IsNil()
|
g.Assert(err).IsNil()
|
||||||
g.Assert(fs.diskUsed).Equal(int64(0))
|
g.Assert(atomic.LoadInt64(&fs.diskUsed)).Equal(int64(0))
|
||||||
})
|
})
|
||||||
|
|
||||||
g.AfterEach(func() {
|
g.AfterEach(func() {
|
||||||
@@ -597,7 +599,7 @@ func TestFilesystem_Copy(t *testing.T) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.diskUsed = int64(utf8.RuneCountInString("test content"))
|
atomic.StoreInt64(&fs.diskUsed, int64(utf8.RuneCountInString("test content")))
|
||||||
})
|
})
|
||||||
|
|
||||||
g.It("should return an error if the source does not exist", func() {
|
g.It("should return an error if the source does not exist", func() {
|
||||||
@@ -640,7 +642,7 @@ func TestFilesystem_Copy(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
g.It("should return an error if there is not space to copy the file", func() {
|
g.It("should return an error if there is not space to copy the file", func() {
|
||||||
fs.diskLimit = 2
|
atomic.StoreInt64(&fs.diskLimit, 2)
|
||||||
|
|
||||||
err := fs.Copy("source.txt")
|
err := fs.Copy("source.txt")
|
||||||
g.Assert(err).IsNotNil()
|
g.Assert(err).IsNotNil()
|
||||||
@@ -672,7 +674,7 @@ func TestFilesystem_Copy(t *testing.T) {
|
|||||||
g.Assert(err).IsNil()
|
g.Assert(err).IsNil()
|
||||||
}
|
}
|
||||||
|
|
||||||
g.Assert(fs.diskUsed).Equal(int64(utf8.RuneCountInString("test content")) * 3)
|
g.Assert(atomic.LoadInt64(&fs.diskUsed)).Equal(int64(utf8.RuneCountInString("test content")) * 3)
|
||||||
})
|
})
|
||||||
|
|
||||||
g.It("should create a copy inside of a directory", func() {
|
g.It("should create a copy inside of a directory", func() {
|
||||||
@@ -694,8 +696,9 @@ func TestFilesystem_Copy(t *testing.T) {
|
|||||||
|
|
||||||
g.AfterEach(func() {
|
g.AfterEach(func() {
|
||||||
rfs.reset()
|
rfs.reset()
|
||||||
fs.diskUsed = 0
|
|
||||||
fs.diskLimit = 0
|
atomic.StoreInt64(&fs.diskUsed, 0)
|
||||||
|
atomic.StoreInt64(&fs.diskLimit, 0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -710,7 +713,7 @@ func TestFilesystem_Delete(t *testing.T) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.diskUsed = int64(utf8.RuneCountInString("test content"))
|
atomic.StoreInt64(&fs.diskUsed, int64(utf8.RuneCountInString("test content")))
|
||||||
})
|
})
|
||||||
|
|
||||||
g.It("does not delete files outside the root directory", func() {
|
g.It("does not delete files outside the root directory", func() {
|
||||||
@@ -744,7 +747,7 @@ func TestFilesystem_Delete(t *testing.T) {
|
|||||||
g.Assert(err).IsNotNil()
|
g.Assert(err).IsNotNil()
|
||||||
g.Assert(errors.Is(err, os.ErrNotExist)).IsTrue()
|
g.Assert(errors.Is(err, os.ErrNotExist)).IsTrue()
|
||||||
|
|
||||||
g.Assert(fs.diskUsed).Equal(int64(0))
|
g.Assert(atomic.LoadInt64(&fs.diskUsed)).Equal(int64(0))
|
||||||
})
|
})
|
||||||
|
|
||||||
g.It("deletes all items inside a directory if the directory is deleted", func() {
|
g.It("deletes all items inside a directory if the directory is deleted", func() {
|
||||||
@@ -762,11 +765,11 @@ func TestFilesystem_Delete(t *testing.T) {
|
|||||||
g.Assert(err).IsNil()
|
g.Assert(err).IsNil()
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.diskUsed = int64(utf8.RuneCountInString("test content") * 3)
|
atomic.StoreInt64(&fs.diskUsed, int64(utf8.RuneCountInString("test content")*3))
|
||||||
|
|
||||||
err = fs.Delete("foo")
|
err = fs.Delete("foo")
|
||||||
g.Assert(err).IsNil()
|
g.Assert(err).IsNil()
|
||||||
g.Assert(fs.diskUsed).Equal(int64(0))
|
g.Assert(atomic.LoadInt64(&fs.diskUsed)).Equal(int64(0))
|
||||||
|
|
||||||
for _, s := range sources {
|
for _, s := range sources {
|
||||||
_, err = rfs.StatServerFile(s)
|
_, err = rfs.StatServerFile(s)
|
||||||
@@ -777,8 +780,9 @@ func TestFilesystem_Delete(t *testing.T) {
|
|||||||
|
|
||||||
g.AfterEach(func() {
|
g.AfterEach(func() {
|
||||||
rfs.reset()
|
rfs.reset()
|
||||||
fs.diskUsed = 0
|
|
||||||
fs.diskLimit = 0
|
atomic.StoreInt64(&fs.diskUsed, 0)
|
||||||
|
atomic.StoreInt64(&fs.diskLimit, 0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,5 +9,6 @@ import (
|
|||||||
func (s *Stat) CTime() time.Time {
|
func (s *Stat) CTime() time.Time {
|
||||||
st := s.Info.Sys().(*syscall.Stat_t)
|
st := s.Info.Sys().(*syscall.Stat_t)
|
||||||
|
|
||||||
return time.Unix(st.Ctim.Sec, st.Ctim.Nsec)
|
// Do not remove these "redundant" type-casts, they are required for 32-bit builds to work.
|
||||||
|
return time.Unix(int64(st.Ctim.Sec), int64(st.Ctim.Nsec))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ func NewInstallationProcess(s *Server, script *api.InstallationScript) (*Install
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
s.installer.cancel = &cancel
|
s.installer.cancel = &cancel
|
||||||
|
|
||||||
if c, err := client.NewClientWithOpts(client.FromEnv); err != nil {
|
if c, err := environment.DockerClient(); err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
} else {
|
} else {
|
||||||
proc.client = c
|
proc.client = c
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/creasty/defaults"
|
"github.com/creasty/defaults"
|
||||||
@@ -49,8 +50,18 @@ func LoadDirectory() error {
|
|||||||
data := data
|
data := data
|
||||||
|
|
||||||
pool.Submit(func() {
|
pool.Submit(func() {
|
||||||
|
// Parse the json.RawMessage into an expected struct value. We do this here so that a single broken
|
||||||
|
// server does not cause the entire boot process to hang, and allows us to show more useful error
|
||||||
|
// messaging in the output.
|
||||||
|
d := api.ServerConfigurationResponse{}
|
||||||
|
|
||||||
log.WithField("server", uuid).Info("creating new server object from API response")
|
log.WithField("server", uuid).Info("creating new server object from API response")
|
||||||
s, err := FromConfiguration(data)
|
if err := json.Unmarshal(data, &d); err != nil {
|
||||||
|
log.WithField("server", uuid).WithField("error", err).Error("failed to parse server configuration from API response, skipping...")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := FromConfiguration(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithField("server", uuid).WithField("error", err).Error("failed to load server, skipping...")
|
log.WithField("server", uuid).WithField("error", err).Error("failed to load server, skipping...")
|
||||||
return
|
return
|
||||||
@@ -73,7 +84,7 @@ func LoadDirectory() error {
|
|||||||
// Initializes a server using a data byte array. This will be marshaled into the
|
// Initializes a server using a data byte array. This will be marshaled into the
|
||||||
// given struct using a YAML marshaler. This will also configure the given environment
|
// given struct using a YAML marshaler. This will also configure the given environment
|
||||||
// for a server.
|
// for a server.
|
||||||
func FromConfiguration(data *api.ServerConfigurationResponse) (*Server, error) {
|
func FromConfiguration(data api.ServerConfigurationResponse) (*Server, error) {
|
||||||
cfg := Configuration{}
|
cfg := Configuration{}
|
||||||
if err := defaults.Set(&cfg); err != nil {
|
if err := defaults.Set(&cfg); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to set struct defaults for server configuration")
|
return nil, errors.Wrap(err, "failed to set struct defaults for server configuration")
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -16,41 +14,17 @@ import (
|
|||||||
type Mount environment.Mount
|
type Mount environment.Mount
|
||||||
|
|
||||||
// Returns the default container mounts for the server instance. This includes the data directory
|
// Returns the default container mounts for the server instance. This includes the data directory
|
||||||
// for the server as well as any timezone related files if they exist on the host system so that
|
// for the server. Previously this would also mount in host timezone files, however we've moved from
|
||||||
// servers running within the container will use the correct time.
|
// that approach to just setting `TZ=Timezone` environment values in containers which should work
|
||||||
|
// in most scenarios.
|
||||||
func (s *Server) Mounts() []environment.Mount {
|
func (s *Server) Mounts() []environment.Mount {
|
||||||
var m []environment.Mount
|
m := []environment.Mount{
|
||||||
|
{
|
||||||
m = append(m, environment.Mount{
|
Default: true,
|
||||||
Default: true,
|
Target: "/home/container",
|
||||||
Target: "/home/container",
|
Source: s.Filesystem().Path(),
|
||||||
Source: s.Filesystem().Path(),
|
ReadOnly: false,
|
||||||
ReadOnly: false,
|
},
|
||||||
})
|
|
||||||
|
|
||||||
// Try to mount in /etc/localtime and /etc/timezone if they exist on the host system.
|
|
||||||
if _, err := os.Stat("/etc/localtime"); err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
log.WithField("error", errors.WithStack(err)).Warn("failed to stat /etc/localtime due to an error")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
m = append(m, environment.Mount{
|
|
||||||
Target: "/etc/localtime",
|
|
||||||
Source: "/etc/localtime",
|
|
||||||
ReadOnly: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat("/etc/timezone"); err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
log.WithField("error", errors.WithStack(err)).Warn("failed to stat /etc/timezone due to an error")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
m = append(m, environment.Mount{
|
|
||||||
Target: "/etc/timezone",
|
|
||||||
Source: "/etc/timezone",
|
|
||||||
ReadOnly: true,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also include any of this server's custom mounts when returning them.
|
// Also include any of this server's custom mounts when returning them.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pterodactyl/wings/api"
|
"github.com/pterodactyl/wings/api"
|
||||||
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
"github.com/pterodactyl/wings/environment/docker"
|
"github.com/pterodactyl/wings/environment/docker"
|
||||||
"github.com/pterodactyl/wings/events"
|
"github.com/pterodactyl/wings/events"
|
||||||
@@ -13,7 +14,6 @@ import (
|
|||||||
"golang.org/x/sync/semaphore"
|
"golang.org/x/sync/semaphore"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// High level definition for a server instance being controlled by Wings.
|
// High level definition for a server instance being controlled by Wings.
|
||||||
@@ -78,10 +78,8 @@ func (s *Server) Id() string {
|
|||||||
// Returns all of the environment variables that should be assigned to a running
|
// Returns all of the environment variables that should be assigned to a running
|
||||||
// server instance.
|
// server instance.
|
||||||
func (s *Server) GetEnvironmentVariables() []string {
|
func (s *Server) GetEnvironmentVariables() []string {
|
||||||
zone, _ := time.Now().In(time.Local).Zone()
|
|
||||||
|
|
||||||
var out = []string{
|
var out = []string{
|
||||||
fmt.Sprintf("TZ=%s", zone),
|
fmt.Sprintf("TZ=%s", config.Get().System.Timezone),
|
||||||
fmt.Sprintf("STARTUP=%s", s.Config().Invocation),
|
fmt.Sprintf("STARTUP=%s", s.Config().Invocation),
|
||||||
fmt.Sprintf("SERVER_MEMORY=%d", s.MemoryLimit()),
|
fmt.Sprintf("SERVER_MEMORY=%d", s.MemoryLimit()),
|
||||||
fmt.Sprintf("SERVER_IP=%s", s.Config().Allocations.DefaultMapping.Ip),
|
fmt.Sprintf("SERVER_IP=%s", s.Config().Allocations.DefaultMapping.Ip),
|
||||||
@@ -90,6 +88,7 @@ func (s *Server) GetEnvironmentVariables() []string {
|
|||||||
|
|
||||||
eloop:
|
eloop:
|
||||||
for k := range s.Config().EnvVars {
|
for k := range s.Config().EnvVars {
|
||||||
|
// Don't allow any environment variables that we have already set above.
|
||||||
for _, e := range out {
|
for _, e := range out {
|
||||||
if strings.HasPrefix(e, strings.ToUpper(k)) {
|
if strings.HasPrefix(e, strings.ToUpper(k)) {
|
||||||
continue eloop
|
continue eloop
|
||||||
@@ -129,7 +128,7 @@ func (s *Server) Sync() error {
|
|||||||
return s.SyncWithConfiguration(cfg)
|
return s.SyncWithConfiguration(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) SyncWithConfiguration(cfg *api.ServerConfigurationResponse) error {
|
func (s *Server) SyncWithConfiguration(cfg api.ServerConfigurationResponse) error {
|
||||||
// Update the data structure and persist it to the disk.
|
// Update the data structure and persist it to the disk.
|
||||||
if err := s.UpdateDataStructure(cfg.Settings); err != nil {
|
if err := s.UpdateDataStructure(cfg.Settings); err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
@@ -148,7 +147,7 @@ func (s *Server) SyncWithConfiguration(cfg *api.ServerConfigurationResponse) err
|
|||||||
if e, ok := s.Environment.(*docker.Environment); ok {
|
if e, ok := s.Environment.(*docker.Environment); ok {
|
||||||
s.Log().Debug("syncing stop configuration with configured docker environment")
|
s.Log().Debug("syncing stop configuration with configured docker environment")
|
||||||
e.SetImage(s.Config().Container.Image)
|
e.SetImage(s.Config().Container.Image)
|
||||||
e.SetStopConfiguration(&cfg.ProcessConfiguration.Stop)
|
e.SetStopConfiguration(cfg.ProcessConfiguration.Stop)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -179,7 +178,7 @@ func (s *Server) CreateEnvironment() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Gets the process configuration data for the server.
|
// Gets the process configuration data for the server.
|
||||||
func (s *Server) GetProcessConfiguration() (*api.ServerConfigurationResponse, *api.RequestError, error) {
|
func (s *Server) GetProcessConfiguration() (api.ServerConfigurationResponse, *api.RequestError, error) {
|
||||||
return api.NewRequester().GetServerConfiguration(s.Id())
|
return api.NewRequester().GetServerConfiguration(s.Id())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user