2020-04-06 01:00:33 +00:00
|
|
|
package server
|
|
|
|
|
2020-08-01 22:20:39 +00:00
|
|
|
import (
|
|
|
|
"context"
|
2022-01-23 17:49:35 +00:00
|
|
|
"fmt"
|
2021-01-10 01:22:39 +00:00
|
|
|
"time"
|
|
|
|
|
2020-12-16 05:56:53 +00:00
|
|
|
"emperror.dev/errors"
|
2022-01-23 17:49:35 +00:00
|
|
|
"github.com/google/uuid"
|
2022-10-06 15:58:42 +00:00
|
|
|
|
2020-08-28 02:02:22 +00:00
|
|
|
"github.com/pterodactyl/wings/config"
|
2020-09-12 06:11:57 +00:00
|
|
|
"github.com/pterodactyl/wings/environment"
|
2020-08-01 22:20:39 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type PowerAction string
|
|
|
|
|
|
|
|
// The power actions that can be performed for a given server. This taps into the given server
|
|
|
|
// environment and performs them in a way that prevents a race condition from occurring. For
|
|
|
|
// example, sending two "start" actions back to back will not process the second action until
|
|
|
|
// the first action has been completed.
|
|
|
|
//
|
2021-08-02 21:07:00 +00:00
|
|
|
// This utilizes a workerpool with a limit of one worker so that all the actions execute
|
2020-08-01 22:20:39 +00:00
|
|
|
// in a sync manner.
|
|
|
|
const (
|
|
|
|
PowerActionStart = "start"
|
|
|
|
PowerActionStop = "stop"
|
|
|
|
PowerActionRestart = "restart"
|
|
|
|
PowerActionTerminate = "kill"
|
|
|
|
)
|
|
|
|
|
2021-08-02 21:07:00 +00:00
|
|
|
// IsValid checks if the power action being received is valid.
|
2020-08-01 22:20:39 +00:00
|
|
|
func (pa PowerAction) IsValid() bool {
|
|
|
|
return pa == PowerActionStart ||
|
|
|
|
pa == PowerActionStop ||
|
|
|
|
pa == PowerActionTerminate ||
|
|
|
|
pa == PowerActionRestart
|
2020-04-06 01:00:33 +00:00
|
|
|
}
|
|
|
|
|
2020-08-11 04:38:42 +00:00
|
|
|
func (pa PowerAction) IsStart() bool {
|
|
|
|
return pa == PowerActionStart || pa == PowerActionRestart
|
|
|
|
}
|
|
|
|
|
2022-01-23 17:49:35 +00:00
|
|
|
// ExecutingPowerAction checks if there is currently a power action being
|
|
|
|
// processed for the server.
|
|
|
|
func (s *Server) ExecutingPowerAction() bool {
|
|
|
|
return s.powerLock.IsLocked()
|
2020-09-26 03:02:38 +00:00
|
|
|
}
|
|
|
|
|
2021-08-02 21:07:00 +00:00
|
|
|
// HandlePowerAction is a helper function that can receive a power action and then process the
|
|
|
|
// actions that need to occur for it. This guards against someone calling Start() twice at the
|
|
|
|
// same time, or trying to restart while another restart process is currently running.
|
2020-08-01 22:20:39 +00:00
|
|
|
//
|
|
|
|
// However, the code design for the daemon does depend on the user correctly calling this
|
|
|
|
// function rather than making direct calls to the start/stop/restart functions on the
|
|
|
|
// environment struct.
|
|
|
|
func (s *Server) HandlePowerAction(action PowerAction, waitSeconds ...int) error {
|
2022-01-23 17:49:35 +00:00
|
|
|
if s.IsInstalling() || s.IsTransferring() || s.IsRestoring() {
|
|
|
|
if s.IsRestoring() {
|
|
|
|
return ErrServerIsRestoring
|
|
|
|
} else if s.IsTransferring() {
|
|
|
|
return ErrServerIsTransferring
|
|
|
|
}
|
2020-12-25 21:32:41 +00:00
|
|
|
return ErrServerIsInstalling
|
|
|
|
}
|
|
|
|
|
2022-01-23 17:49:35 +00:00
|
|
|
lockId, _ := uuid.NewUUID()
|
|
|
|
log := s.Log().WithField("lock_id", lockId.String()).WithField("action", action)
|
2020-12-25 21:32:41 +00:00
|
|
|
|
2022-01-23 17:49:35 +00:00
|
|
|
cleanup := func() {
|
|
|
|
log.Info("releasing exclusive lock for power action")
|
|
|
|
s.powerLock.Release()
|
2021-03-12 23:19:35 +00:00
|
|
|
}
|
|
|
|
|
2022-01-23 17:49:35 +00:00
|
|
|
var wait int
|
|
|
|
if len(waitSeconds) > 0 && waitSeconds[0] > 0 {
|
|
|
|
wait = waitSeconds[0]
|
2020-08-01 22:20:39 +00:00
|
|
|
}
|
|
|
|
|
2022-01-23 17:49:35 +00:00
|
|
|
log.WithField("wait_seconds", wait).Debug("acquiring power action lock for instance")
|
2020-08-14 03:37:35 +00:00
|
|
|
// Only attempt to acquire a lock on the process if this is not a termination event. We want to
|
|
|
|
// just allow those events to pass right through for good reason. If a server is currently trying
|
|
|
|
// to process a power action but has gotten stuck you still should be able to pass through the
|
|
|
|
// terminate event. The good news here is that doing that oftentimes will get the stuck process to
|
|
|
|
// move again, and naturally continue through the process.
|
|
|
|
if action != PowerActionTerminate {
|
|
|
|
// Determines if we should wait for the lock or not. If a value greater than 0 is passed
|
|
|
|
// into this function we will wait that long for a lock to be acquired.
|
2022-01-23 17:49:35 +00:00
|
|
|
if wait > 0 {
|
|
|
|
ctx, cancel := context.WithTimeout(s.ctx, time.Second*time.Duration(wait))
|
2021-02-02 03:09:24 +00:00
|
|
|
defer cancel()
|
|
|
|
|
2020-08-14 03:37:35 +00:00
|
|
|
// Attempt to acquire a lock on the power action lock for up to 30 seconds. If more
|
|
|
|
// time than that passes an error will be propagated back up the chain and this
|
|
|
|
// request will be aborted.
|
2022-01-23 17:49:35 +00:00
|
|
|
if err := s.powerLock.TryAcquire(ctx); err != nil {
|
|
|
|
return errors.Wrap(err, fmt.Sprintf("could not acquire lock on power action after %d seconds", wait))
|
2020-08-14 03:37:35 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// If no wait duration was provided we will attempt to immediately acquire the lock
|
|
|
|
// and bail out with a context deadline error if it is not acquired immediately.
|
2022-01-23 17:49:35 +00:00
|
|
|
if err := s.powerLock.Acquire(); err != nil {
|
|
|
|
return errors.Wrap(err, "failed to acquire exclusive lock for power actions")
|
2020-08-14 03:37:35 +00:00
|
|
|
}
|
2020-08-01 22:20:39 +00:00
|
|
|
}
|
2020-08-14 03:37:35 +00:00
|
|
|
|
2022-01-23 17:49:35 +00:00
|
|
|
log.Info("acquired exclusive lock on power actions, processing event...")
|
|
|
|
defer cleanup()
|
2020-08-01 22:20:39 +00:00
|
|
|
} else {
|
2022-01-23 17:49:35 +00:00
|
|
|
// Still try to acquire the lock if terminating, and it is available, just so that
|
|
|
|
// other power actions are blocked until it has completed. However, if it cannot be
|
|
|
|
// acquired we won't stop the entire process.
|
|
|
|
//
|
|
|
|
// If we did successfully acquire the lock, make sure we release it once we're done
|
|
|
|
// executiong the power actions.
|
|
|
|
if err := s.powerLock.Acquire(); err == nil {
|
|
|
|
log.Info("acquired exclusive lock on power actions, processing event...")
|
|
|
|
defer cleanup()
|
|
|
|
} else {
|
|
|
|
log.Warn("failed to acquire exclusive lock, ignoring failure for termination event")
|
2020-08-01 22:20:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch action {
|
|
|
|
case PowerActionStart:
|
2020-11-20 21:35:29 +00:00
|
|
|
if s.Environment.State() != environment.ProcessOfflineState {
|
2020-09-12 06:11:57 +00:00
|
|
|
return ErrIsRunning
|
|
|
|
}
|
|
|
|
|
2020-08-14 04:36:23 +00:00
|
|
|
// Run the pre-boot logic for the server before processing the environment start.
|
|
|
|
if err := s.onBeforeStart(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-09-11 21:13:19 +00:00
|
|
|
return s.Environment.Start(s.Context())
|
2020-08-01 22:20:39 +00:00
|
|
|
case PowerActionStop:
|
2022-01-31 23:40:15 +00:00
|
|
|
fallthrough
|
2020-08-01 22:20:39 +00:00
|
|
|
case PowerActionRestart:
|
2022-01-31 23:40:15 +00:00
|
|
|
// We're specifically waiting for the process to be stopped here, otherwise the lock is
|
|
|
|
// released too soon, and you can rack up all sorts of issues.
|
2022-02-01 00:09:08 +00:00
|
|
|
if err := s.Environment.WaitForStop(s.Context(), time.Minute*10, true); err != nil {
|
2020-08-19 03:43:28 +00:00
|
|
|
// Even timeout errors should be bubbled back up the stack. If the process didn't stop
|
|
|
|
// nicely, but the terminate argument was passed then the server is stopped without an
|
|
|
|
// error being returned.
|
|
|
|
//
|
|
|
|
// However, if terminate is not passed you'll get a context deadline error. We could
|
|
|
|
// probably handle that nicely here, but I'd rather just pass it back up the stack for now.
|
|
|
|
// Either way, any type of error indicates we should not attempt to start the server back
|
|
|
|
// up.
|
|
|
|
return err
|
2020-08-14 04:36:23 +00:00
|
|
|
}
|
|
|
|
|
2022-01-31 23:40:15 +00:00
|
|
|
if action == PowerActionStop {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-14 04:36:23 +00:00
|
|
|
// Now actually try to start the process by executing the normal pre-boot logic.
|
|
|
|
if err := s.onBeforeStart(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-09-11 21:13:19 +00:00
|
|
|
return s.Environment.Start(s.Context())
|
2020-08-01 22:20:39 +00:00
|
|
|
case PowerActionTerminate:
|
2024-06-29 18:31:36 +00:00
|
|
|
return s.Environment.Terminate(s.Context(), "SIGKILL")
|
2020-08-01 22:20:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return errors.New("attempting to handle unknown power action")
|
2020-04-06 01:00:33 +00:00
|
|
|
}
|
2020-08-14 04:36:23 +00:00
|
|
|
|
|
|
|
// Execute a few functions before actually calling the environment start commands. This ensures
|
|
|
|
// that everything is ready to go for environment booting, and that the server can even be started.
|
|
|
|
func (s *Server) onBeforeStart() error {
|
|
|
|
s.Log().Info("syncing server configuration with panel")
|
|
|
|
if err := s.Sync(); err != nil {
|
2020-11-28 23:57:10 +00:00
|
|
|
return errors.WithMessage(err, "unable to sync server data from Panel instance")
|
2020-08-14 04:36:23 +00:00
|
|
|
}
|
|
|
|
|
2020-08-28 04:08:33 +00:00
|
|
|
// Disallow start & restart if the server is suspended. Do this check after performing a sync
|
|
|
|
// action with the Panel to ensure that we have the most up-to-date information for that server.
|
|
|
|
if s.IsSuspended() {
|
2020-09-12 06:11:57 +00:00
|
|
|
return ErrSuspended
|
2020-08-28 04:08:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure we sync the server information with the environment so that any new environment variables
|
|
|
|
// and process resource limits are correctly applied.
|
|
|
|
s.SyncWithEnvironment()
|
|
|
|
|
2020-09-01 05:10:57 +00:00
|
|
|
// If a server has unlimited disk space, we don't care enough to block the startup to check remaining.
|
|
|
|
// However, we should trigger a size anyway, as it'd be good to kick it off for other processes.
|
|
|
|
if s.DiskSpace() <= 0 {
|
2020-09-27 19:24:08 +00:00
|
|
|
s.Filesystem().HasSpaceAvailable(true)
|
2020-09-01 05:10:57 +00:00
|
|
|
} else {
|
|
|
|
s.PublishConsoleOutputFromDaemon("Checking server disk space usage, this could take a few seconds...")
|
2020-12-20 19:08:01 +00:00
|
|
|
if err := s.Filesystem().HasSpaceErr(false); err != nil {
|
|
|
|
return err
|
2020-09-01 05:10:57 +00:00
|
|
|
}
|
2020-08-14 04:36:23 +00:00
|
|
|
}
|
|
|
|
|
2020-08-19 03:27:42 +00:00
|
|
|
// Update the configuration files defined for the server before beginning the boot process.
|
|
|
|
// This process executes a bunch of parallel updates, so we just block until that process
|
2020-09-05 19:08:40 +00:00
|
|
|
// is complete. Any errors as a result of this will just be bubbled out in the logger,
|
2021-08-02 21:07:00 +00:00
|
|
|
// we don't need to actively do anything about it at this point, worse comes to worst the
|
2020-08-19 03:27:42 +00:00
|
|
|
// server starts in a weird state and the user can manually adjust.
|
|
|
|
s.PublishConsoleOutputFromDaemon("Updating process configuration files...")
|
2022-02-01 00:30:07 +00:00
|
|
|
s.Log().Debug("updating server configuration files...")
|
2020-08-14 04:36:23 +00:00
|
|
|
s.UpdateConfigurationFiles()
|
2022-02-01 00:30:07 +00:00
|
|
|
s.Log().Debug("updated server configuration files")
|
2020-08-14 04:36:23 +00:00
|
|
|
|
2020-08-28 02:02:22 +00:00
|
|
|
if config.Get().System.CheckPermissionsOnBoot {
|
|
|
|
s.PublishConsoleOutputFromDaemon("Ensuring file permissions are set correctly, this could take a few seconds...")
|
2021-08-02 21:07:00 +00:00
|
|
|
// Ensure all the server file permissions are set correctly before booting the process.
|
2022-02-01 00:30:07 +00:00
|
|
|
s.Log().Debug("chowning server root directory...")
|
2020-09-27 19:24:08 +00:00
|
|
|
if err := s.Filesystem().Chown("/"); err != nil {
|
2020-11-28 23:57:10 +00:00
|
|
|
return errors.WithMessage(err, "failed to chown root server directory during pre-boot process")
|
2020-08-28 02:02:22 +00:00
|
|
|
}
|
2020-08-19 03:27:42 +00:00
|
|
|
}
|
|
|
|
|
2022-01-31 23:40:15 +00:00
|
|
|
s.Log().Info("completed server preflight, starting boot process...")
|
2020-08-14 04:36:23 +00:00
|
|
|
return nil
|
2020-08-19 03:43:28 +00:00
|
|
|
}
|