Compare commits
12 Commits
release/v1
...
develop
Author | SHA1 | Date | |
---|---|---|---|
|
d739948989 | ||
|
ac260bd5ee | ||
|
2f4a0d7262 | ||
|
1d8b383682 | ||
|
934bf2493d | ||
|
29e4425e21 | ||
|
5a15612754 | ||
|
ad1ae862a9 | ||
|
3114a3b82e | ||
|
500f217514 | ||
|
9ffbcdcdb1 | ||
|
9b341db2db |
6
.github/workflows/push.yaml
vendored
6
.github/workflows/push.yaml
vendored
|
@ -16,7 +16,7 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-22.04]
|
os: [ubuntu-22.04]
|
||||||
go: ["1.21.9", "1.22.2"]
|
go: ["1.22.5"]
|
||||||
goos: [linux]
|
goos: [linux]
|
||||||
goarch: [amd64, arm64]
|
goarch: [amd64, arm64]
|
||||||
|
|
||||||
|
@ -62,14 +62,14 @@ jobs:
|
||||||
|
|
||||||
- name: Upload Release Artifact
|
- name: Upload Release Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ (github.ref == 'refs/heads/develop' || github.event_name == 'pull_request') && matrix.go == '1.21.8' }}
|
if: ${{ (github.ref == 'refs/heads/develop' || github.event_name == 'pull_request') && matrix.go == '1.22.5' }}
|
||||||
with:
|
with:
|
||||||
name: wings_linux_${{ matrix.goarch }}
|
name: wings_linux_${{ matrix.goarch }}
|
||||||
path: dist/wings
|
path: dist/wings
|
||||||
|
|
||||||
- name: Upload Debug Artifact
|
- name: Upload Debug Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ (github.ref == 'refs/heads/develop' || github.event_name == 'pull_request') && matrix.go == '1.21.8' }}
|
if: ${{ (github.ref == 'refs/heads/develop' || github.event_name == 'pull_request') && matrix.go == '1.22.5' }}
|
||||||
with:
|
with:
|
||||||
name: wings_linux_${{ matrix.goarch }}_debug
|
name: wings_linux_${{ matrix.goarch }}_debug
|
||||||
path: dist/wings_debug
|
path: dist/wings_debug
|
||||||
|
|
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "1.21.9"
|
go-version: "1.22.5"
|
||||||
|
|
||||||
- name: Build release binaries
|
- name: Build release binaries
|
||||||
env:
|
env:
|
||||||
|
|
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -1,5 +1,15 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.11.14
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Support relative file paths for the Wings config ([#180](https://github.com/pterodactyl/wings/pull/180))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Folders not being sorted before files properly ([#5078](https://github.com/pterodactyl/panel/issues/5078)
|
||||||
|
|
||||||
## v1.11.13
|
## v1.11.13
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Stage 1 (Build)
|
# Stage 1 (Build)
|
||||||
FROM golang:1.21.9-alpine AS builder
|
FROM golang:1.22.5-alpine AS builder
|
||||||
|
|
||||||
ARG VERSION
|
ARG VERSION
|
||||||
RUN apk add --update --no-cache git make
|
RUN apk add --update --no-cache git make
|
||||||
|
|
|
@ -19,8 +19,9 @@ I would like to extend my sincere thanks to the following sponsors for helping f
|
||||||
[Interested in becoming a sponsor?](https://github.com/sponsors/matthewpi)
|
[Interested in becoming a sponsor?](https://github.com/sponsors/matthewpi)
|
||||||
|
|
||||||
| Company | About |
|
| Company | About |
|
||||||
|--------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|--------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. |
|
| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. |
|
||||||
|
| [**CodeNode LLC**](https://codenode.gg/) | Looking for simplicity? Well, look no further! CodeNode has got you covered with everything you need at the rock-bottom price of $1.75 per GB, including dedicated IPs in Dallas, Texas, and Amsterdam, Netherlands. We're not just good, we're the best in the game! |
|
||||||
| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. |
|
| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. |
|
||||||
| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! |
|
| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! |
|
||||||
| [**HostEZ**](https://hostez.io) | US & EU Rust & Minecraft Hosting. DDoS Protected bare metal, VPS and colocation with low latency, high uptime and maximum availability. EZ! |
|
| [**HostEZ**](https://hostez.io) | US & EU Rust & Minecraft Hosting. DDoS Protected bare metal, VPS and colocation with low latency, high uptime and maximum availability. EZ! |
|
||||||
|
|
19
cmd/root.go
19
cmd/root.go
|
@ -13,7 +13,6 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/NYTimes/logrotate"
|
"github.com/NYTimes/logrotate"
|
||||||
|
@ -113,6 +112,9 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
|
||||||
if err := config.EnsurePterodactylUser(); err != nil {
|
if err := config.EnsurePterodactylUser(); err != nil {
|
||||||
log.WithField("error", err).Fatal("failed to create pterodactyl system user")
|
log.WithField("error", err).Fatal("failed to create pterodactyl system user")
|
||||||
}
|
}
|
||||||
|
if err := config.ConfigurePasswd(); err != nil {
|
||||||
|
log.WithField("error", err).Fatal("failed to configure container passwd file")
|
||||||
|
}
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"username": config.Get().System.Username,
|
"username": config.Get().System.Username,
|
||||||
"uid": config.Get().System.User.Uid,
|
"uid": config.Get().System.User.Uid,
|
||||||
|
@ -379,13 +381,14 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
|
||||||
// Reads the configuration from the disk and then sets up the global singleton
|
// Reads the configuration from the disk and then sets up the global singleton
|
||||||
// with all the configuration values.
|
// with all the configuration values.
|
||||||
func initConfig() {
|
func initConfig() {
|
||||||
if !strings.HasPrefix(configPath, "/") {
|
if !filepath.IsAbs(configPath) {
|
||||||
d, err := os.Getwd()
|
d, err := filepath.Abs(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log2.Fatalf("cmd/root: could not determine directory: %s", err)
|
log2.Fatalf("cmd/root: failed to get path to config file: %s", err)
|
||||||
}
|
}
|
||||||
configPath = path.Clean(path.Join(d, configPath))
|
configPath = d
|
||||||
}
|
}
|
||||||
|
|
||||||
err := config.FromFile(configPath)
|
err := config.FromFile(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
@ -440,18 +443,18 @@ in all copies or substantial portions of the Software.%s`), system.Version, time
|
||||||
}
|
}
|
||||||
|
|
||||||
func exitWithConfigurationNotice() {
|
func exitWithConfigurationNotice() {
|
||||||
fmt.Print(colorstring.Color(`
|
fmt.Printf(colorstring.Color(`
|
||||||
[_red_][white][bold]Error: Configuration File Not Found[reset]
|
[_red_][white][bold]Error: Configuration File Not Found[reset]
|
||||||
|
|
||||||
Wings was not able to locate your configuration file, and therefore is not
|
Wings was not able to locate your configuration file, and therefore is not
|
||||||
able to complete its boot process. Please ensure you have copied your instance
|
able to complete its boot process. Please ensure you have copied your instance
|
||||||
configuration file into the default location below.
|
configuration file into the default location below.
|
||||||
|
|
||||||
Default Location: /etc/pterodactyl/config.yml
|
Default Location: %s
|
||||||
|
|
||||||
[yellow]This is not a bug with this software. Please do not make a bug report
|
[yellow]This is not a bug with this software. Please do not make a bug report
|
||||||
for this issue, it will be closed.[reset]
|
for this issue, it will be closed.[reset]
|
||||||
|
|
||||||
`))
|
`), config.DefaultLocation)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,6 +172,25 @@ type SystemConfiguration struct {
|
||||||
Gid int `yaml:"gid"`
|
Gid int `yaml:"gid"`
|
||||||
} `yaml:"user"`
|
} `yaml:"user"`
|
||||||
|
|
||||||
|
// Passwd controls the mounting of a generated passwd files into containers started by Wings.
|
||||||
|
Passwd struct {
|
||||||
|
// Enable controls whether generated passwd files should be mounted into containers.
|
||||||
|
//
|
||||||
|
// By default this option is disabled and Wings will not mount any additional passwd
|
||||||
|
// files into containers.
|
||||||
|
Enable bool `yaml:"enabled" default:"false"`
|
||||||
|
|
||||||
|
// Directory is the directory on disk where the generated files will be stored.
|
||||||
|
// This directory may be temporary as it will be re-created whenever Wings is started.
|
||||||
|
//
|
||||||
|
// This path **WILL** be both written to by Wings and mounted into containers created by
|
||||||
|
// Wings. If you are running Wings itself in a container, this path will need to be mounted
|
||||||
|
// into the Wings container as the exact path on the host, which should match the value
|
||||||
|
// specified here. If you are using SELinux, you will need to make sure this file has the
|
||||||
|
// correct SELinux context in order for containers to use it.
|
||||||
|
Directory string `yaml:"directory" default:"/run/wings/etc"`
|
||||||
|
} `yaml:"passwd"`
|
||||||
|
|
||||||
// The amount of time in seconds that can elapse before a server's disk space calculation is
|
// The amount of time in seconds that can elapse before a server's disk space calculation is
|
||||||
// considered stale and a re-check should occur. DANGER: setting this value too low can seriously
|
// considered stale and a re-check should occur. DANGER: setting this value too low can seriously
|
||||||
// impact system performance and cause massive I/O bottlenecks and high CPU usage for the Wings
|
// impact system performance and cause massive I/O bottlenecks and high CPU usage for the Wings
|
||||||
|
@ -497,6 +516,37 @@ func EnsurePterodactylUser() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConfigurePasswd generates required passwd files for use with containers started by Wings.
|
||||||
|
func ConfigurePasswd() error {
|
||||||
|
passwd := _config.System.Passwd
|
||||||
|
if !passwd.Enable {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v := []byte(fmt.Sprintf(
|
||||||
|
`root:x:0:
|
||||||
|
container:x:%d:
|
||||||
|
nogroup:x:65534:`,
|
||||||
|
_config.System.User.Gid,
|
||||||
|
))
|
||||||
|
if err := os.WriteFile(filepath.Join(passwd.Directory, "group"), v, 0o644); err != nil {
|
||||||
|
return fmt.Errorf("failed to write file to %s/group: %v", passwd.Directory, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v = []byte(fmt.Sprintf(
|
||||||
|
`root:x:0:0::/root:/bin/sh
|
||||||
|
container:x:%d:%d::/home/container:/bin/sh
|
||||||
|
nobody:x:65534:65534::/var/empty:/bin/sh
|
||||||
|
`,
|
||||||
|
_config.System.User.Uid,
|
||||||
|
_config.System.User.Gid,
|
||||||
|
))
|
||||||
|
if err := os.WriteFile(filepath.Join(passwd.Directory, "passwd"), v, 0o644); err != nil {
|
||||||
|
return fmt.Errorf("failed to write file to %s/passwd: %v", passwd.Directory, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// FromFile reads the configuration from the provided file and stores it in the
|
// FromFile reads the configuration from the provided file and stores it in the
|
||||||
// global singleton for this instance.
|
// global singleton for this instance.
|
||||||
func FromFile(path string) error {
|
func FromFile(path string) error {
|
||||||
|
@ -561,6 +611,13 @@ func ConfigureDirectories() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _config.System.Passwd.Enable {
|
||||||
|
log.WithField("path", _config.System.Passwd.Directory).Debug("ensuring passwd directory exists")
|
||||||
|
if err := os.MkdirAll(_config.System.Passwd.Directory, 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
|
@ -143,42 +142,49 @@ func (e *Environment) Stop(ctx context.Context) error {
|
||||||
s := e.meta.Stop
|
s := e.meta.Stop
|
||||||
e.mu.RUnlock()
|
e.mu.RUnlock()
|
||||||
|
|
||||||
// A native "stop" as the Type field value will just skip over all of this
|
|
||||||
// logic and end up only executing the container stop command (which may or
|
|
||||||
// may not work as expected).
|
|
||||||
if s.Type == "" || s.Type == remote.ProcessStopSignal {
|
|
||||||
if s.Type == "" {
|
|
||||||
log.WithField("container_id", e.Id).Warn("no stop configuration detected for environment, using termination procedure")
|
|
||||||
}
|
|
||||||
|
|
||||||
signal := os.Kill
|
|
||||||
// Handle a few common cases, otherwise just fall through and just pass along
|
|
||||||
// the os.Kill signal to the process.
|
|
||||||
switch strings.ToUpper(s.Value) {
|
|
||||||
case "SIGABRT":
|
|
||||||
signal = syscall.SIGABRT
|
|
||||||
case "SIGINT":
|
|
||||||
signal = syscall.SIGINT
|
|
||||||
case "SIGTERM":
|
|
||||||
signal = syscall.SIGTERM
|
|
||||||
}
|
|
||||||
return e.Terminate(ctx, signal)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the process is already offline don't switch it back to stopping. Just leave it how
|
// If the process is already offline don't switch it back to stopping. Just leave it how
|
||||||
// it is and continue through to the stop handling for the process.
|
// it is and continue through to the stop handling for the process.
|
||||||
if e.st.Load() != environment.ProcessOfflineState {
|
if e.st.Load() != environment.ProcessOfflineState {
|
||||||
e.SetState(environment.ProcessStoppingState)
|
e.SetState(environment.ProcessStoppingState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle signal based actions
|
||||||
|
if s.Type == remote.ProcessStopSignal {
|
||||||
|
log.WithField("signal_value", s.Value).Debug("stopping server using signal")
|
||||||
|
|
||||||
|
// Handle some common signals - Default to SIGKILL
|
||||||
|
signal := "SIGKILL"
|
||||||
|
switch strings.ToUpper(s.Value) {
|
||||||
|
case "SIGABRT":
|
||||||
|
signal = "SIGABRT"
|
||||||
|
case "SIGINT", "C":
|
||||||
|
signal = "SIGINT"
|
||||||
|
case "SIGTERM":
|
||||||
|
signal = "SIGTERM"
|
||||||
|
case "SIGKILL":
|
||||||
|
signal = "SIGKILL"
|
||||||
|
default:
|
||||||
|
log.Info("Unrecognised signal requested, defaulting to SIGKILL")
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.SignalContainer(ctx, signal)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle command based stops
|
||||||
// Only attempt to send the stop command to the instance if we are actually attached to
|
// Only attempt to send the stop command to the instance if we are actually attached to
|
||||||
// the instance. If we are not for some reason, just send the container stop event.
|
// the instance. If we are not for some reason, just send the container stop event.
|
||||||
if e.IsAttached() && s.Type == remote.ProcessStopCommand {
|
if e.IsAttached() && s.Type == remote.ProcessStopCommand {
|
||||||
return e.SendCommand(s.Value)
|
return e.SendCommand(s.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow the stop action to run for however long it takes, similar to executing a command
|
if s.Type == "" {
|
||||||
// and using a different logic pathway to wait for the container to stop successfully.
|
log.WithField("container_id", e.Id).Warn("no stop configuration detected for environment, using native docker stop")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to a native docker stop. As we aren't passing a signal to ContainerStop docker will
|
||||||
|
// attempt to stop the container using the default stop signal, SIGTERM, unless
|
||||||
|
// another signal was specified in the Dockerfile
|
||||||
//
|
//
|
||||||
// Using a negative timeout here will allow the container to stop gracefully,
|
// Using a negative timeout here will allow the container to stop gracefully,
|
||||||
// rather than forcefully terminating it. Value is in seconds, but -1 is
|
// rather than forcefully terminating it. Value is in seconds, but -1 is
|
||||||
|
@ -224,7 +230,7 @@ func (e *Environment) WaitForStop(ctx context.Context, duration time.Duration, t
|
||||||
|
|
||||||
doTermination := func(s string) error {
|
doTermination := func(s string) error {
|
||||||
e.log().WithField("step", s).WithField("duration", duration).Warn("container stop did not complete in time, terminating process...")
|
e.log().WithField("step", s).WithField("duration", duration).Warn("container stop did not complete in time, terminating process...")
|
||||||
return e.Terminate(ctx, os.Kill)
|
return e.Terminate(ctx, "SIGKILL")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We pass through the timed context for this stop action so that if one of the
|
// We pass through the timed context for this stop action so that if one of the
|
||||||
|
@ -268,8 +274,8 @@ func (e *Environment) WaitForStop(ctx context.Context, duration time.Duration, t
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terminate forcefully terminates the container using the signal provided.
|
// Sends the specified signal to the container in an attempt to stop it.
|
||||||
func (e *Environment) Terminate(ctx context.Context, signal os.Signal) error {
|
func (e *Environment) SignalContainer(ctx context.Context, signal string) error {
|
||||||
c, err := e.ContainerInspect(ctx)
|
c, err := e.ContainerInspect(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Treat missing containers as an okay error state, means it is obviously
|
// Treat missing containers as an okay error state, means it is obviously
|
||||||
|
@ -294,11 +300,27 @@ func (e *Environment) Terminate(ctx context.Context, signal os.Signal) error {
|
||||||
|
|
||||||
// We set it to stopping than offline to prevent crash detection from being triggered.
|
// We set it to stopping than offline to prevent crash detection from being triggered.
|
||||||
e.SetState(environment.ProcessStoppingState)
|
e.SetState(environment.ProcessStoppingState)
|
||||||
sig := strings.TrimSuffix(strings.TrimPrefix(signal.String(), "signal "), "ed")
|
if err := e.client.ContainerKill(ctx, e.Id, signal); err != nil && !client.IsErrNotFound(err) {
|
||||||
if err := e.client.ContainerKill(ctx, e.Id, sig); err != nil && !client.IsErrNotFound(err) {
|
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminate forcefully terminates the container using the signal provided.
|
||||||
|
// then sets its state to stopped.
|
||||||
|
func (e *Environment) Terminate(ctx context.Context, signal string) error {
|
||||||
|
|
||||||
|
// Send the signal to the container to kill it
|
||||||
|
if err := e.SignalContainer(ctx, signal); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We expect Terminate to instantly kill the container
|
||||||
|
// so go ahead and mark it as dead and clean up
|
||||||
e.SetState(environment.ProcessOfflineState)
|
e.SetState(environment.ProcessOfflineState)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package environment
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pterodactyl/wings/events"
|
"github.com/pterodactyl/wings/events"
|
||||||
|
@ -72,7 +71,7 @@ type ProcessEnvironment interface {
|
||||||
|
|
||||||
// Terminate stops a running server instance using the provided signal. This function
|
// Terminate stops a running server instance using the provided signal. This function
|
||||||
// is a no-op if the server is already stopped.
|
// is a no-op if the server is already stopped.
|
||||||
Terminate(ctx context.Context, signal os.Signal) error
|
Terminate(ctx context.Context, signal string) error
|
||||||
|
|
||||||
// Destroys the environment removing any containers that were created (in Docker
|
// Destroys the environment removing any containers that were created (in Docker
|
||||||
// environments at least).
|
// environments at least).
|
||||||
|
|
|
@ -34,7 +34,7 @@ type Mount struct {
|
||||||
// Limits is the build settings for a given server that impact docker container
|
// Limits is the build settings for a given server that impact docker container
|
||||||
// creation and resource limits for a server instance.
|
// creation and resource limits for a server instance.
|
||||||
type Limits struct {
|
type Limits struct {
|
||||||
// The total amount of memory in megabytes that this server is allowed to
|
// The total amount of memory in mebibytes that this server is allowed to
|
||||||
// use on the host system.
|
// use on the host system.
|
||||||
MemoryLimit int64 `json:"memory_limit"`
|
MemoryLimit int64 `json:"memory_limit"`
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ func (l Limits) MemoryOverheadMultiplier() float64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l Limits) BoundedMemoryLimit() int64 {
|
func (l Limits) BoundedMemoryLimit() int64 {
|
||||||
return int64(math.Round(float64(l.MemoryLimit) * l.MemoryOverheadMultiplier() * 1_000_000))
|
return int64(math.Round(float64(l.MemoryLimit) * l.MemoryOverheadMultiplier() * 1024 * 1024))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConvertedSwap returns the amount of swap available as a total in bytes. This
|
// ConvertedSwap returns the amount of swap available as a total in bytes. This
|
||||||
|
@ -90,7 +90,7 @@ func (l Limits) ConvertedSwap() int64 {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
return (l.Swap * 1_000_000) + l.BoundedMemoryLimit()
|
return (l.Swap * 1024 * 1024) + l.BoundedMemoryLimit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessLimit returns the process limit for a container. This is currently
|
// ProcessLimit returns the process limit for a container. This is currently
|
||||||
|
@ -105,7 +105,7 @@ func (l Limits) AsContainerResources() container.Resources {
|
||||||
pids := l.ProcessLimit()
|
pids := l.ProcessLimit()
|
||||||
resources := container.Resources{
|
resources := container.Resources{
|
||||||
Memory: l.BoundedMemoryLimit(),
|
Memory: l.BoundedMemoryLimit(),
|
||||||
MemoryReservation: l.MemoryLimit * 1_000_000,
|
MemoryReservation: l.MemoryLimit * 1024 * 1024,
|
||||||
MemorySwap: l.ConvertedSwap(),
|
MemorySwap: l.ConvertedSwap(),
|
||||||
BlkioWeight: l.IoWeight,
|
BlkioWeight: l.IoWeight,
|
||||||
OomKillDisable: &l.OOMDisabled,
|
OomKillDisable: &l.OOMDisabled,
|
||||||
|
|
36
flake.lock
36
flake.lock
|
@ -5,11 +5,11 @@
|
||||||
"nixpkgs-lib": "nixpkgs-lib"
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1706830856,
|
"lastModified": 1719994518,
|
||||||
"narHash": "sha256-a0NYyp+h9hlb7ddVz4LUn1vT/PLwqfrWYcHMvFB1xYg=",
|
"narHash": "sha256-pQMhCCHyQGRzdfAkdJ4cIWiw+JNuWsTX7f0ZYSyz0VY=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "b253292d9c0a5ead9bc98c4e9a26c6312e27d69f",
|
"rev": "9227223f6d922fee3c7b190b2cc238a99527bbb7",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -20,11 +20,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1707956935,
|
"lastModified": 1721562059,
|
||||||
"narHash": "sha256-ZL2TrjVsiFNKOYwYQozpbvQSwvtV/3Me7Zwhmdsfyu4=",
|
"narHash": "sha256-Tybxt65eyOARf285hMHIJ2uul8SULjFZbT9ZaEeUnP8=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "a4d4fe8c5002202493e87ec8dbc91335ff55552c",
|
"rev": "68c9ed8bbed9dfce253cc91560bf9043297ef2fe",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -36,20 +36,14 @@
|
||||||
},
|
},
|
||||||
"nixpkgs-lib": {
|
"nixpkgs-lib": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"dir": "lib",
|
"lastModified": 1719876945,
|
||||||
"lastModified": 1706550542,
|
"narHash": "sha256-Fm2rDDs86sHy0/1jxTOKB1118Q0O3Uc7EC0iXvXKpbI=",
|
||||||
"narHash": "sha256-UcsnCG6wx++23yeER4Hg18CXWbgNpqNXcHIo5/1Y+hc=",
|
"type": "tarball",
|
||||||
"owner": "NixOS",
|
"url": "https://github.com/NixOS/nixpkgs/archive/5daf0514482af3f97abaefc78a6606365c9108e2.tar.gz"
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "97b17f32362e475016f942bbdfda4a4a72a8a652",
|
|
||||||
"type": "github"
|
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"dir": "lib",
|
"type": "tarball",
|
||||||
"owner": "NixOS",
|
"url": "https://github.com/NixOS/nixpkgs/archive/5daf0514482af3f97abaefc78a6606365c9108e2.tar.gz"
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
|
@ -66,11 +60,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1707300477,
|
"lastModified": 1721769617,
|
||||||
"narHash": "sha256-qQF0fEkHlnxHcrKIMRzOETnRBksUK048MXkX0SOmxvA=",
|
"narHash": "sha256-6Pqa0bi5nV74IZcENKYRToRNM5obo1EQ+3ihtunJ014=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "treefmt-nix",
|
"repo": "treefmt-nix",
|
||||||
"rev": "ac599dab59a66304eb511af07b3883114f061b9d",
|
"rev": "8db8970be1fb8be9c845af7ebec53b699fe7e009",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package filesystem
|
package filesystem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
@ -164,6 +166,8 @@ func (fs *Filesystem) DirectorySize(root string) (int64, error) {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hardLinks []uint64
|
||||||
|
|
||||||
var size atomic.Int64
|
var size atomic.Int64
|
||||||
err = fs.unixFS.WalkDirat(dirfd, name, func(dirfd int, name, _ string, d ufs.DirEntry, err error) error {
|
err = fs.unixFS.WalkDirat(dirfd, name, func(dirfd int, name, _ string, d ufs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -180,8 +184,16 @@ func (fs *Filesystem) DirectorySize(root string) (int64, error) {
|
||||||
return errors.Wrap(err, "lstatat err")
|
return errors.Wrap(err, "lstatat err")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: detect if info is a hard-link and de-duplicate it.
|
var sysFileInfo = info.Sys().(*unix.Stat_t)
|
||||||
// ref; https://github.com/pterodactyl/wings/pull/181/files
|
if sysFileInfo.Nlink > 1 {
|
||||||
|
// Hard links have the same inode number
|
||||||
|
if slices.Contains(hardLinks, sysFileInfo.Ino) {
|
||||||
|
// Don't add hard links size twice
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
hardLinks = append(hardLinks, sysFileInfo.Ino)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
size.Add(info.Size())
|
size.Add(info.Size())
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -480,9 +480,9 @@ func (fs *Filesystem) ListDirectory(p string) ([]Stat, error) {
|
||||||
case a.IsDir() && b.IsDir():
|
case a.IsDir() && b.IsDir():
|
||||||
return 0
|
return 0
|
||||||
case a.IsDir():
|
case a.IsDir():
|
||||||
return 1
|
|
||||||
default:
|
|
||||||
return -1
|
return -1
|
||||||
|
default:
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,21 @@ func (s *Server) Mounts() []environment.Mount {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle mounting a generated `/etc/passwd` if the feature is enabled.
|
||||||
|
if passwd := config.Get().System.Passwd; passwd.Enable {
|
||||||
|
s.Log().WithFields(log.Fields{"source_path": passwd.Directory}).Info("mouting generated /etc/{group,passwd} to workaround UID/GID issues")
|
||||||
|
m = append(m, environment.Mount{
|
||||||
|
Source: filepath.Join(passwd.Directory, "group"),
|
||||||
|
Target: "/etc/group",
|
||||||
|
ReadOnly: true,
|
||||||
|
})
|
||||||
|
m = append(m, environment.Mount{
|
||||||
|
Source: filepath.Join(passwd.Directory, "passwd"),
|
||||||
|
Target: "/etc/passwd",
|
||||||
|
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.
|
||||||
return append(m, s.customMounts()...)
|
return append(m, s.customMounts()...)
|
||||||
}
|
}
|
||||||
|
@ -56,14 +71,12 @@ func (s *Server) customMounts() []environment.Mount {
|
||||||
if !strings.HasPrefix(source, filepath.Clean(allowed)) {
|
if !strings.HasPrefix(source, filepath.Clean(allowed)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
mounted = true
|
mounted = true
|
||||||
mounts = append(mounts, environment.Mount{
|
mounts = append(mounts, environment.Mount{
|
||||||
Source: source,
|
Source: source,
|
||||||
Target: target,
|
Target: target,
|
||||||
ReadOnly: m.ReadOnly,
|
ReadOnly: m.ReadOnly,
|
||||||
})
|
})
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ package server
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
|
@ -161,7 +160,7 @@ func (s *Server) HandlePowerAction(action PowerAction, waitSeconds ...int) error
|
||||||
|
|
||||||
return s.Environment.Start(s.Context())
|
return s.Environment.Start(s.Context())
|
||||||
case PowerActionTerminate:
|
case PowerActionTerminate:
|
||||||
return s.Environment.Terminate(s.Context(), os.Kill)
|
return s.Environment.Terminate(s.Context(), "SIGKILL")
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New("attempting to handle unknown power action")
|
return errors.New("attempting to handle unknown power action")
|
||||||
|
|
|
@ -28,6 +28,7 @@ func (s *Server) SyncWithEnvironment() {
|
||||||
Mounts: s.Mounts(),
|
Mounts: s.Mounts(),
|
||||||
Allocations: cfg.Allocations,
|
Allocations: cfg.Allocations,
|
||||||
Limits: cfg.Build,
|
Limits: cfg.Build,
|
||||||
|
Labels: cfg.Labels,
|
||||||
})
|
})
|
||||||
|
|
||||||
// For Docker specific environments we also want to update the configured image
|
// For Docker specific environments we also want to update the configured image
|
||||||
|
|
Loading…
Reference in New Issue
Block a user