Compare commits

...

37 Commits

Author SHA1 Message Date
Dane Everitt
1f3394b82d Update release.yml 2020-10-22 21:42:34 -07:00
Dane Everitt
bae63c4321 Fix bad yaml 2020-10-22 21:41:00 -07:00
Dane Everitt
f99640a42d Update CHANGELOG.md 2020-10-22 21:36:24 -07:00
Dane Everitt
c73d632e0d Use correct case-insensitive regex; closes pterodactyl/panel#2546 2020-10-22 20:22:27 -07:00
Dane Everitt
903902e123 Match multiple times 2020-10-22 20:17:58 -07:00
Dane Everitt
1c787b5f26 Fix handling of super long lines to not hit a scanner error; closes pterodactyl/panel#2549
Also fixes return handling in games like Minecraft to properly display the full line output.
2020-10-19 18:13:52 -07:00
Dane Everitt
3f9ac8b89a Fix local images not being pulled correctly; closes #2559 2020-10-19 16:18:33 -07:00
Dane Everitt
560c832cc6 Apply timezone cleaning to final result, closes #2546 2020-10-19 16:13:59 -07:00
Dane Everitt
13058ad64b Merge branch 'develop' of https://github.com/pterodactyl/wings into develop 2020-10-19 15:27:11 -07:00
Dane Everitt
305cd512a7 Update README.md 2020-10-19 15:27:10 -07:00
Matthew Penner
3cd17a2856 Add arm to release workflow 2020-10-17 15:55:05 -06:00
Matthew Penner
56789196d4 Disable flaky test so I can test cross-platform building 2020-10-17 15:47:30 -06:00
Matthew Penner
70804dd20f Remoe stat_arm.go, make stat_linux.go with with both 32 and 64 bit systems 2020-10-17 15:45:14 -06:00
Matthew Penner
19d821aab5 Fix arm build 2020-10-17 15:41:13 -06:00
Matthew Penner
4ce35d3d17 Fix race in filesystem_test.go 2020-10-17 15:31:40 -06:00
Matthew Penner
a62b588ace Add matrix to build-test.yml 2020-10-17 15:13:06 -06:00
Dane Everitt
9b54be06bb Remove unused config values 2020-10-17 14:09:02 -07:00
Dane Everitt
c031d37b91 Use single call to create/return the docker client 2020-10-17 14:04:56 -07:00
Dane Everitt
19051c99ef Support arm builds 2020-10-17 13:52:38 -07:00
Jakob
99fd416cee add version negotiation fix to changelog 2020-10-17 21:49:43 +02:00
Jakob
acf09180f0 Merge pull request #66 from pterodactyl/fix/version-negotiation
Fix version negotiation for installs and diagnostics
2020-10-17 21:43:00 +02:00
Jakob
b19fc88a95 add version negotiation to diagnostics docker client 2020-10-17 21:42:23 +02:00
Jakob
e185f597ba add version negotiation to server install docker client 2020-10-17 21:41:01 +02:00
Dane Everitt
3973747c9c Update README.md 2020-10-17 12:11:06 -07:00
Dane Everitt
947279a07c Don't abort entire boot process due to one bad server egg; closes pterodactyl/panel#2448 2020-10-17 12:06:47 -07:00
Dane Everitt
ad1ed0f24a Merge branch 'develop' of https://github.com/pterodactyl/wings into develop 2020-10-17 11:35:22 -07:00
Dane Everitt
80387bc294 Use more easily configurable timezone, remove /etc/timezone mounts from containers; closes pterodactyl/panel#2513
If this does not completely solve the issue in containers then we need to evaluate the image being used to determine what changes need to happen to the image itself to support the timezone.

ref pterodactyl/panel#2239
ref pterodactyl/panel#2329
ref pterodactyl/panel#2389
2020-10-17 11:35:20 -07:00
Dane Everitt
e8dbba5cc0 Merge pull request #65 from AreYouRlyScared/patch-3
Update diagnostics.go
2020-10-14 08:22:30 -07:00
Charles Morgan
f50f580dcc Update diagnostics.go 2020-10-14 05:06:14 -04:00
Dane Everitt
7e8033d96c Update README.md 2020-10-12 21:12:36 -07:00
Dane Everitt
1b94eadc68 Update CHANGELOG.md 2020-10-11 16:27:44 -07:00
Dane Everitt
3a496af9f1 Merge pull request #64 from parkervcp/fix_file_parser
fix file parsing
2020-10-11 15:27:53 -07:00
Dane Everitt
7f4771068f Code cleanup 2020-10-11 15:27:27 -07:00
Dane Everitt
3463c223e8 Merge branch 'develop' into pr/64 2020-10-11 15:25:07 -07:00
Dane Everitt
6c291d9b0e Minor tweaks to diagnostics for cleaner info, changes endpoint; closes pterodactyl/panel#2463 2020-10-11 15:23:55 -07:00
Dane Everitt
e29789114c Fix server disk usage not being reported properly; closes pterodactyl/panel#2445 2020-10-11 15:02:37 -07:00
Michael Parker
4c9aaeb769 fix file parsing
resolves #2393

loads, edits, and re-writes the file instead of inline edits that seem to break.

This inefficient but it works in my testing.
2020-10-07 18:41:05 -04:00
25 changed files with 651 additions and 200 deletions

View File

@@ -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 }}

View File

@@ -8,8 +8,10 @@ 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'
@@ -17,13 +19,19 @@ jobs:
- 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:
@@ -64,8 +74,7 @@ jobs:
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 }}
@@ -75,13 +84,32 @@ jobs:
asset_name: wings_linux_amd64 asset_name: wings_linux_amd64
asset_content_type: application/octet-stream asset_content_type: application/octet-stream
- name: Upload checksum - name: Upload arm64 Binary
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: build/wings_linux_arm64
asset_name: checksum.txt 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
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./checksums.txt
asset_name: checksums.txt
asset_content_type: text/plain asset_content_type: text/plain

View File

@@ -1,5 +1,254 @@
# 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
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
along though, I've only included the differences between this version and the previous RC build.
### Fixed
* Fixes file parser not properly appending newline character to modified lines.
* Fixes server disk usage not being properly reported back to the API and websocket.
### Changed
* Changes diagnostics endpoint URL to be `ptero.co` rather than `hastebin.com`.
* Diagnostics report now includes system time for easier debugging of logs and container issues.
## v1.0.0-rc.7
### Fixed
* **[Security]** Prevents the `allowed_mounts` configuration value from being set by a remote API call.
* Fixes an unexpected error when attempting to make a copy of an archive file.
* Fixes certain expected filesystems errors being returned to the API as a 500 error rather than a 4XX series error.
* Fixes a panic crash when there is no text on a line and the parser is attempting to determine if the line is a comment.
* Fixes multiple filesystem operations to correctly check, increment, and decrement disk usage for a server.
* Fixes a negative disk space usage issue when deleting a file due to a bad int swap.
* Errors from a websocket connection being closed will no longer spam the console.
* Fixes an extraneous `.` in the install script for servers causing errors in some scenarios.
* Fixes unexpected error spam due to a change in how `os.ErrNotExist` is returned from some functions.
### Changed
* Stacktrace is no longer emitted when warning that an image exists locally.
* Configuration parser now attempts to create the directory structure leading to a configuration file if it is missing.
* If a file name is too long for the system a nice error is returned to the caller.
* Enables client version negotiation for Docker to support more versions.
* Disk space errors are no longer logged into the wings logs.
* Servers can no longer be reinstalled while another power action is currently running. This avoids data collisions and wings running into issues with the container state.
* Wings now uses `1024` rather than `1000` bytes when calculating the disk space usage for a server to match how the Panel reports usage.
* JWT errors in the websocket are now sent back to the connection as a specific event type allowing them to be handled even if the timer fails to execute or executes but is not being listened for.
* A server struct is no longer embedded in the virtual filesystem allowing for easier testing and modularization of the codebase.
* Server websockets are now closed when a server is deleted, disconnecting any currently connected clients.
### Removed
* **[Security]** Removes the `SafeJoin` function which could wrongly assume the state of a file and allow a user to escape the root if the developer implementing the call calls `Stat` rather than `Lstat`.
## v1.0.0-rc.6
### Fixed
* Fixes race condition when checking if the running state of a server has changed.
* Fixes files mistakenly unpacking themselves to the root directory rather than the directory they are told to unpack to.
* Fixes console output not being sent to the websocket in the same order that it was received.
* Fixes a file busy error causing a 500 error response when unpacking an archive rather than a 400 error with a message indicating what is wrong to the user.
* Fixes docker image not properly updating when a server is started.
### Changed
* Replaces fragile event bus logic with a more robust and easier to understand system. This fixes all remaining console and stats output issues that have been reported.
* Cleans up API response error messaging to avoid empty errors being logged which cannot be acted upon.
* Adds support for retrying a file write a few times with a back-off if the file is busy when the write occurs.
### Added
* Docker image pull data is shown in the console when an admin is connected to the websocket.
* Adds console throttling to stop a server if too much data is being piped out of the console. This logic mimics the logic present in the old Nodejs daemon, but with a 2x line count limit (1000 -> 2000) per period.
## v1.0.0-rc.5
### Fixed
* Fixes long standing bug with console output not properly sending back to the client with server stats in certain edge case scenarios. This was "fixed" in `rc.4` but ended up breaking many servers in significantly more painful manners, but did allow us to better track down the issue.
* Fixes build flags during release process to correctly strip unnessary code allowing the final binary size to be reduced back down to `~5MB`
* Fixes Wings returning the last `16384` lines of the log file when connecting to the websocket.
* Fixes pre-boot actions always running for a server start event even if the server is already running.
### Added
* Adds support for configuring the amount of time that can elapse between server disk size checking before the value is considered stale.
## v1.0.0-rc.4
### Fixed
* Fixes server files being inaccessible if the root data path is a symlink to another location on the machine.
* Fixes some console output being written to the logs accidentally truncating other lines due to special ANSI sequences being output.
* Fixes `server.properties` files getting mangled by the automatic configuration editor when booting a server.
* Fixes a missed stream close when stopping resource polling that would lead to memory leaks.
* Fixes port bindings being incorrectly re-assigned when using `127.0.0.1` with Docker. These are now properly re-mapped to the `pterodactyl0` interface so that networking operates as expected for the server.
* Fixes handling of values within arrays in `yaml` and `json` configuration files.
* Fixes a 304 error being returned rather than a 400 error when a file upload is invalid.
* Fixes deadlocks and response delays when processing server console and stat events. This was causing server consoles to become completely unresponsive after random periods of time and was only resolvable via a Wings restart.
### Changed
* Allows a stale value to be present when starting a server if the server is allowed an unlimited amount of disk space.
* Removed all remaining traces of the `zap` logging library from the codebase.
* Servers no longer auto-restart as if they crashed when a stop command is manually sent via the server console to the instance.
* Changes the CORS checks to allow `*` as a remote origin.
### Added
* Added an auto-generated logrotate file which is written to the normal logrotate directory when wings is first started.
* Added additional debug logging within the internals of the resource polling for servers to better trace unexpected behavior.
* Adds additional logic check to avoid even trying to stop a suspended server if it is already stopped.
## v1.0.0-rc.3
### Fixed
* Errors during the backup process are now correctly reported to the Panel and logged into the output correctly.
* Empty directories are no longer pushed into the backup file list (which was causing errors to occur previously).
* Covered an edge case to prevent errors if a file gets deleted while a backup is in progress.
* Fixed a bug causing Wings to panic and crash if an invalid environment variable value was passed through. These invalid values are now logged to the output to better detect and an empty string is returned in their place.
* Fixed startup variables and other server information not properly being updated when a server is restarted.
* Suspension state of a server is now properly returned by the API.
* Fixes an error being thrown if a backup does not exist on the local machine. A 404 is now properly returned and handled by the Panel when this occurs.
* Fixes an error when attempting to move or rename a folder due to the target being created accidentally before the rename occurred.
* Fixes install scripts running even when the checkbox to not run them on installation was selected in the Panel.
### Changed
* Disk space checking modified to not block in as many parts of the codebase and allow returning a stale cache value where that is appropriate. Thanks @cyberkitsune!
* SFTP package code is now merged into the codebase to make keeping up with all of the code simpler, and reduce the complexity of the SFTP server.
### Added
* Added the ability for a node to be configured to skip file permissions checking when starting a server.
* Added console output message to indicate that server disk space checking is occurring rather than silently sitting there.
## v1.0.0-rc.2
### Fixed
* Fixes significant performance degradation due to excessive `syscall` actions when determining directory sizes on large servers. This was previously causing CPU & I/O lockups on servers and should be significantly more performant and less impactful on the system now.
* Fixes panic crash when booting wings with no log directory created.
### Changed
* Changed default interval for disk space calculation from every 60 seconds to every 2.5 minutes.
## v1.0.0-rc.1
### Fixed
* Servers are no longer incorrectly marked as stopping when they are, in fact, offline.
* Release build version is now correctly output when starting wings.
* Termination signals can now always be sent to a server instance even if the instance is currently starting/stopping.
* Removed the file chown on wings boot to avoid slowing down the boot process unnecessarily when working with hundreds of servers on a node.
* Fixed a multitude of race conditions throughout the code that cropped up during testing and made power handling even more robust for server instances.
* In general multiple fragile areas of the codebase have been improved and are more likely to return useful errors should they break.
* Addressed serious CPU usage issues when generating backups, and additionally decreased the amount of time they take to generate.
### Changed
* Internal server now uses more secure and recommended TLS settings.
* Environment handling is now completely separate from the server package itself, allowing the environments to no longer be tightly coupled to the server.
* `/tmp` directory mounted into containers can now be programmatically managed and uses better defaults to avoid people even needing to edit it.
### Added
* Wings logs are now properly persisted to the disk.
* Adds the ability for an egg to use ANSI-stripped matching when determining if a server is done booting.
## v1.0.0-beta.9
### Fixed
* Fixes server resource usage stats not being returned correctly by the API endpoint.
* Fixes an exception thrown when attempting to write server installation logs.
* Fixes error handling to provide a more accurate stack-trace in more scenarios where one is missing initially.
* Fixes a memory leak and zombie event listeners when disconnecting from a server's websocket.
* Fixes a race condition when wings is attempting to register/de-register event subscribers.
* Server data directories now correctly have their permissions set recursively when booting Wings.
* Fixes a race condition when a server's console stream was not fully closed before the next power action was started.
### Changed
* Server power handling is now handled in a synchronous manner. This avoids endless bugs and race conditions that would crop up if someone triggered two restart processes back to back. The new logic prevents performing any additional power actions until the currently executing action is completed.
* Server disk usage is now correctly calculated when restarting the daemon as long as the server data directory exists.
* Multiple code pathways within the boot process were cleaned up and modified to be less IO intensive and overall easier to reason about as a developer.
* Additional timezone data is now mounted into containers to better improve the ability for instances running to be using the correct timezone.
### Added
* Adds basic internal file upload endpoints (these are currently not exposed anywhere in the Panel).
* Added additional process events for installation start and completion.
* Additional CORS allowed origins for the websocket can now be defined in the configuration file.
* Adds the ability to authenticate with a docker registry when pulling images.
## v1.0.0-beta.8
### Fixed
* Server state is synced with the Panel before performing a reinstall to ensure that the latest information is used.
* Wings no longer crashes when a non-string environment variable value is passed through.
* SFTP server authentication no longer attempts to contact the Panel to validate credentials if the format is known to already be incorrect.
* Some previously missing error stacks are now properly returned when encountered.
* Renaming a file no longer triggers an error if the base path does not exist.
* Disk space is now properly calculated for a server even if it is assigned unlimited space.
* **[Security]** Prevent symlink files from unintentionally chmodding their target file when a server is booted when the target file resides outside the server data directory.
* **[Security]** Cleans up multiple code pathways that may unintentionally allow a malicious user to impact files outside their home directory.
* Fixes numerous race conditions during a server's boot process that may lead to unintended data states.
* Fixes an error thrown when attempting to delete a file that points to a symlink outside the server data directory.
* Deleting a symlink will no longer attempt to remove the source file, only the symlink itself.
* Websocket no longer blocks when handling a long running process for a user.
### Changed
* Install log for servers is now more detailed and more useful for debugging what might be going wrong.
* Certain file management API endpoints now support passing through multiple paths at once to make mass actions easier to perform.
* Re-worked file walking implementation to not cause a server crash when working with very large directories, and avoid race conditions when recursively walking directories.
* Server configuration structure re-worked to be more manageable in the code base and avoid additional race conditions and complexity while making on-the-fly changes to it.
### Added
* Support for configuring additional file mount points in a container via the Panel.
* Support for automated SSL certificate generation when booting the Daemon.
* Added wings diagnostics command.
* New API endpoints to compress and decompress files on a server.
## v1.0.0-beta.7
### Fixed
* Stacktraces are now displayed once in the error output, rather than twice when certain errors are encountered.
* Additional errors that did not previously output a stack trace have been fixed to output.
* Fixes a bug where server space available would be triggered when creating a new server from a remote configuration before that file location existed on the disk, leading to an error.
* Fixes context timeouts while pulling server docker images. Time was upped from 10 seconds to 15 minutes.
* Configuration file replacement values are now properly de-escaped when writing to the disk. `\/no\/more\/slashes`
* `.properties` files are now saved correctly to the disk with newlines, rather than shoved on a single line.
* `./wings configure` command can now properly save the configuration to the disk.
* Custom SSL locations are no longer obliterated when making changes to the Node's configuration via the Panel.
### Changed
* Exclusive lock is now acquired when performing a server installation to avoid two install processes being triggered at the same time. This also allows an install to be properly canceled if the server is deleted before it is completed.
## v1.0.0-beta.6
### Fixed
* Server status is no longer sent to all connected websocket clients when a new client connects to the socket.
* Server disk usage is sent back over the socket when connecting.
* Default configuration for the SFTP server is now properly returned to `on` rather than `off`
* Server boot is no longer blocked if there is an error fetching the Docker image as long as that image exists on the host.
* The websocket no longer gets locked up when Wings attempts to send an error to the client.
* Fixed a crash loop when an error is thrown during pre-boot phase of server startup.
* Errors with `BindJSON` in API endpoints are now properly handled and returned.
* Fixed warning about Gin running in non-release mode, even when the binary is running in release mode.
### Changed
* Switched logging libraries to output data in a clearer format more suited to the CLI where this application runs.
* Cleaner debug messaging in debug mode from the router.
## v1.0.0-beta.5
### Fixed
* Default config location settled on `/etc/pterodactyl/config.yml`; wings will now check all of the previous locations for the configuration and move it automatically to the new location.
* Deleting a server no longer fails the process if the container cannot be found.
* Fixes permissions checking for subusers connecting to the SFTP instance.
* S3 backups now properly send back hash data to the panel.
* Server installation containers are now always deleted, even if the installer process fails for the instance.
* Files and folders with special characters and spaces in them no longer return a 404 error.
* Servers using eggs with bad configurations will no longer cause the daemon to fail booting; these bad configurations are simply skipped over and a warning is emitted into the logs.
* Environment variables passed to containers no longer contain improper quotes around them.
* Matching on array indexes in configurations now works correctly; `foo[0]` is quietly transformed into the proper `foo.0` syntax.
### Added
* New banner error message when the daemon is unable to locate the configuration file. This should better clarify what the user needs to do in order to resolve the issue.
* Adds ability to configure the default networking driver used by docker.
## v1.0.0-beta.4
### Fixed
* Fixes unexpected nil-pointer panic when attempting to start some newly created servers, or any server that was missing a container on the system.
* Fixes memory usage of process being reported differently than the `docker stats` output leading to some confusion. These numbers should be more correct now.
* Fixes possible nil-pointer panic when detecting a deleted container as being crashed.
## v1.0.0-beta.3 ## v1.0.0-beta.3
### Fixed ### Fixed
* Daemon will no longer crash if someone requests a websocket for a deleted server. * Daemon will no longer crash if someone requests a websocket for a deleted server.
@@ -11,6 +260,39 @@
### Changed ### Changed
* Memory overhead for containers is now 5/10/15% higher than the passed limit to account for JVM heap and prevent crashing. * Memory overhead for containers is now 5/10/15% higher than the passed limit to account for JVM heap and prevent crashing.
## v1.0.0-beta.2
### Changed
* Backup functionality made significantly more modular to ease adding additional methods in the future.
* Websocket permissions changed to use same name as in panel.
* Container memory hard-limits are now adjusted up by 15% (< 2G memory), 10% (< 4G memory), or 5% to avoid unexpected OOM crashes for memory heavy games.
* Wings executable is now 80% smaller thanks to better compilation arguments.
### Added
* Adds support for ignoring files and directories when generating a new backup.
* Added internal directory walker with callback-continue support.
### Fixed
* Fixed linux distro matching when booting the daemon.
* Fixes DNS to be configurable for docker containers that are made for servers.
* Fixes incorrect file truncation when making modifications to a server's configuration files.
## v1.0.0-beta.1
### Added
* Added support for passing specific threads to the docker environment when running a server.
* Added support for reinstalling an existing server.
* Added support for restarting a server.
* Adds support for transferring servers between daemon instances.
* Added auto-deploy command to fetch configuration from Panel automatically.
### Changed
* Server file and backup downloads now hit a direct endpoint with a one-time JWT to avoid proxying large downloads through the panel.
### Fixed
* Fixes a go routine causing a memory and CPU leak.
* Fixed improper chown of server directories on boot.
* Miscellaneous fixes to CPU usage, abandoned go-routines, and race conditions.
* Fixes support for Alpine based systems.
## v1.0.0-alpha.2 ## v1.0.0-alpha.2
### Added ### Added
* Ability to run an installation process for a server and notify the panel when completed. * Ability to run an installation process for a server and notify the panel when completed.

View File

@@ -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_*

View File

@@ -1,6 +1,7 @@
[![Logo Image](https://cdn.pterodactyl.io/logos/new/pterodactyl_logo.png)](https://pterodactyl.io) [![Logo Image](https://cdn.pterodactyl.io/logos/new/pterodactyl_logo.png)](https://pterodactyl.io)
[![Discord](https://img.shields.io/discord/122900397965705216.svg?style=flat-square&label=Discord)](https://pterodactyl.io/discord) ![Discord](https://img.shields.io/discord/122900397965705216?label=Discord&logo=Discord&logoColor=white)
![GitHub Releases](https://img.shields.io/github/downloads/pterodactyl/wings/latest/total)
[![Go Report Card](https://goreportcard.com/badge/github.com/pterodactyl/wings)](https://goreportcard.com/report/github.com/pterodactyl/wings) [![Go Report Card](https://goreportcard.com/badge/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 RoyaleHostings reliable servers and network. Easy to use, provisioned in a couple of minutes. | | [**RoyaleHosting**](https://royalehosting.net/) | Build your dreams and deploy them with RoyaleHostings 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)

View File

@@ -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

View File

@@ -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"
@@ -13,20 +14,20 @@ import (
"path" "path"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"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"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
const DefaultHastebinUrl = "https://hastebin.com" const DefaultHastebinUrl = "https://ptero.co"
const DefaultLogLines = 50 const DefaultLogLines = 200
var ( var (
diagnosticsArgs struct { diagnosticsArgs struct {
@@ -88,37 +89,39 @@ 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")
cfg, err := config.ReadConfiguration(config.DefaultLocation) cfg, err := config.ReadConfiguration(config.DefaultLocation)
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, "Api Host:", redact(cfg.Api.Host)) fmt.Fprintln(output, "")
fmt.Fprintln(output, "Api Port:", cfg.Api.Port) fmt.Fprintln(output, " Internal Webserver:", redact(cfg.Api.Host), ":", cfg.Api.Port)
fmt.Fprintln(output, "Api Ssl Enabled:", cfg.Api.Ssl.Enabled) fmt.Fprintln(output, " SSL Enabled:", cfg.Api.Ssl.Enabled)
fmt.Fprintln(output, "Api Ssl Certificate:", redact(cfg.Api.Ssl.CertificateFile)) fmt.Fprintln(output, " SSL Certificate:", redact(cfg.Api.Ssl.CertificateFile))
fmt.Fprintln(output, "Api Ssl Key:", redact(cfg.Api.Ssl.KeyFile)) fmt.Fprintln(output, " SSL Key:", redact(cfg.Api.Ssl.KeyFile))
fmt.Fprintln(output, "Sftp Address:", redact(cfg.System.Sftp.Address)) fmt.Fprintln(output, "")
fmt.Fprintln(output, "Sftp Port:", 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, "Sftp Diskchecking Disabled:", cfg.System.Sftp.DisableDiskChecking) fmt.Fprintln(output, "")
fmt.Fprintln(output, "System Root Directory:", cfg.System.RootDirectory) fmt.Fprintln(output, " Root Directory:", cfg.System.RootDirectory)
fmt.Fprintln(output, "System Logs Directory:", cfg.System.LogDirectory) fmt.Fprintln(output, " Logs Directory:", cfg.System.LogDirectory)
fmt.Fprintln(output, "System Data Directory:", cfg.System.Data) fmt.Fprintln(output, " Data Directory:", cfg.System.Data)
fmt.Fprintln(output, "System Archive Directory:", cfg.System.ArchiveDirectory) fmt.Fprintln(output, " Archive Directory:", cfg.System.ArchiveDirectory)
fmt.Fprintln(output, "System Backup Directory:", cfg.System.BackupDirectory) fmt.Fprintln(output, " Backup Directory:", cfg.System.BackupDirectory)
fmt.Fprintln(output, "System Username:", cfg.System.Username) fmt.Fprintln(output, "")
fmt.Fprintln(output, "Debug Enabled:", cfg.Debug) fmt.Fprintln(output, " Username:", cfg.System.Username)
fmt.Fprintln(output, " Server Time:", time.Now().Format(time.RFC1123Z))
fmt.Fprintln(output, " Debug Mode:", cfg.Debug)
} else { } else {
fmt.Println("Failed to load configuration.", err) fmt.Println("Failed to load configuration.", err)
} }
@@ -137,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)
@@ -184,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
} }

View File

@@ -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

View File

@@ -73,9 +73,6 @@ type Configuration struct {
// Defines the configuration of the internal SFTP server. // Defines the configuration of the internal SFTP server.
type SftpConfiguration struct { type SftpConfiguration struct {
// If set to true disk checking will not be performed. This will prevent the SFTP
// server from checking the total size of a directory when uploading files.
DisableDiskChecking bool `default:"false" yaml:"disable_disk_checking"`
// The bind address of the SFTP server. // The bind address of the SFTP server.
Address string `default:"0.0.0.0" json:"bind_address" yaml:"bind_address"` Address string `default:"0.0.0.0" json:"bind_address" yaml:"bind_address"`
// The bind port of the SFTP server. // The bind port of the SFTP server.

View File

@@ -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"`

View File

@@ -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))
}

View File

@@ -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",

View File

@@ -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...

View File

@@ -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()

View File

@@ -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")
} }

View File

@@ -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"))

View File

@@ -390,39 +390,26 @@ func (f *ConfigurationFile) parseYamlFile(path string) error {
// scanning a file and performing a replacement. You should attempt to use anything other // scanning a file and performing a replacement. You should attempt to use anything other
// than this function where possible. // than this function where possible.
func (f *ConfigurationFile) parseTextFile(path string) error { func (f *ConfigurationFile) parseTextFile(path string) error {
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0644) input, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return err return errors.WithStack(err)
} }
defer file.Close()
scanner := bufio.NewScanner(file) lines := strings.Split(string(input), "\n")
for scanner.Scan() { for i, line := range lines {
hasReplaced := false
t := scanner.Text()
// Iterate over the potential replacements for the line and check if there are
// any matches.
for _, replace := range f.Replace { for _, replace := range f.Replace {
if !strings.HasPrefix(t, replace.Match) { // If this line doesn't match what we expect for the replacement, move on to the next
// line. Otherwise, update the line to have the replacement value.
if !strings.HasPrefix(line, replace.Match) {
continue continue
} }
hasReplaced = true lines[i] = replace.ReplaceWith.String()
t = strings.Replace(t, replace.Match, replace.ReplaceWith.String(), 1)
}
// If there was a replacement that occurred on this specific line, do a write to the file
// immediately to write that modified content to the disk.
if hasReplaced {
if _, err := file.WriteAt([]byte(t+"\n"), int64(len(scanner.Bytes()))); err != nil {
return err
}
} }
} }
if err := scanner.Err(); err != nil { if err := ioutil.WriteFile(path, []byte(strings.Join(lines, "\n")), 0644); err != nil {
return err return errors.WithStack(err)
} }
return nil return nil

View File

@@ -75,6 +75,16 @@ func (fs *Filesystem) HasSpaceAvailable(allowStaleValue bool) bool {
return size <= fs.MaxDisk() return size <= fs.MaxDisk()
} }
// Returns the cached value for the amount of disk space used by the filesystem. Do not rely on this
// function for critical logical checks. It should only be used in areas where the actual disk usage
// does not need to be perfect, e.g. API responses for server resource usage.
func (fs *Filesystem) CachedUsage() int64 {
fs.mu.RLock()
defer fs.mu.RUnlock()
return fs.diskUsed
}
// Internal helper function to allow other parts of the codebase to check the total used disk space // Internal helper function to allow other parts of the codebase to check the total used disk space
// as needed without overly taxing the system. This will prioritize the value from the cache to avoid // as needed without overly taxing the system. This will prioritize the value from the cache to avoid
// excessive IO usage. We will only walk the filesystem and determine the size of the directory if there // excessive IO usage. We will only walk the filesystem and determine the size of the directory if there
@@ -199,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.
// //

View File

@@ -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)
}) })
}) })
} }

View File

@@ -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))
} }

View File

@@ -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

View File

@@ -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")

View File

@@ -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.

View File

@@ -1,6 +1,7 @@
package server package server
import ( import (
"encoding/json"
"github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment"
"sync" "sync"
) )
@@ -17,17 +18,34 @@ type ResourceUsage struct {
// The current server status. // The current server status.
State string `json:"state" default:"offline"` State string `json:"state" default:"offline"`
// The current disk space being used by the server. This is cached to prevent slow lookup // The current disk space being used by the server. This value is not guaranteed to be accurate
// issues on frequent refreshes. // at all times. It is "manually" set whenever server.Proc() is called. This is kind of just a
// hacky solution for now to avoid passing events all over the place.
Disk int64 `json:"disk_bytes"` Disk int64 `json:"disk_bytes"`
} }
// Alias the resource usage so that we don't infinitely recurse when marshaling the struct.
type IResourceUsage ResourceUsage
// Custom marshaler to ensure that the object is locked when we're converting it to JSON in
// order to avoid race conditions.
func (ru *ResourceUsage) MarshalJSON() ([]byte, error) {
ru.mu.Lock()
defer ru.mu.Unlock()
return json.Marshal(IResourceUsage(*ru))
}
// Returns the resource usage stats for the server instance. If the server is not running, only the // Returns the resource usage stats for the server instance. If the server is not running, only the
// disk space currently used will be returned. When the server is running all of the other stats will // disk space currently used will be returned. When the server is running all of the other stats will
// be returned. // be returned.
// //
// When a process is stopped all of the stats are zeroed out except for the disk. // When a process is stopped all of the stats are zeroed out except for the disk.
func (s *Server) Proc() *ResourceUsage { func (s *Server) Proc() *ResourceUsage {
s.resources.SetDisk(s.Filesystem().CachedUsage())
// Get a read lock on the resources at this point. Don't do this before setting
// the disk, otherwise you'll cause a deadlock.
s.resources.mu.RLock() s.resources.mu.RLock()
defer s.resources.mu.RUnlock() defer s.resources.mu.RUnlock()
@@ -35,11 +53,9 @@ func (s *Server) Proc() *ResourceUsage {
} }
func (s *Server) emitProcUsage() { func (s *Server) emitProcUsage() {
s.resources.mu.RLock() if err := s.Events().PublishJson(StatsEvent, s.Proc()); err != nil {
if err := s.Events().PublishJson(StatsEvent, s.resources); err != nil {
s.Log().WithField("error", err).Warn("error while emitting server resource usage to listeners") s.Log().WithField("error", err).Warn("error while emitting server resource usage to listeners")
} }
s.resources.mu.RUnlock()
} }
// Returns the servers current state. // Returns the servers current state.

View File

@@ -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())
} }