Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
471886dd34 | ||
|
|
b63a491b5e | ||
|
|
6902422229 | ||
|
|
5f5b2bc84e | ||
|
|
81a411a42c | ||
|
|
37c6b85489 | ||
|
|
0e3778ac47 |
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.3.1
|
||||||
|
### Fixed
|
||||||
|
* Fixes an error being returned to the client when attempting to restart a server when the container no longer exists on the machine.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Updated server transfer logic to use newer file archiving tools to avoid frequent errors when transferring symlinked files.
|
||||||
|
|
||||||
## v1.3.0
|
## v1.3.0
|
||||||
### Fixed
|
### Fixed
|
||||||
* Fixes improper error handling when attempting to create a new Docker network.
|
* Fixes improper error handling when attempting to create a new Docker network.
|
||||||
|
|||||||
@@ -3,9 +3,14 @@ package docker
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"emperror.dev/errors"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
@@ -15,16 +20,9 @@ import (
|
|||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
"github.com/pterodactyl/wings/system"
|
"github.com/pterodactyl/wings/system"
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type imagePullStatus struct {
|
var ErrNotAttached = errors.Sentinel("not attached to instance")
|
||||||
Status string `json:"status"`
|
|
||||||
Progress string `json:"progress"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// A custom console writer that allows us to keep a function blocked until the
|
// A custom console writer that allows us to keep a function blocked until the
|
||||||
// given stream is properly closed. This does nothing special, only exists to
|
// given stream is properly closed. This does nothing special, only exists to
|
||||||
@@ -38,14 +36,14 @@ func (nw noopWriter) Write(b []byte) (int, error) {
|
|||||||
return len(b), nil
|
return len(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attaches to the docker container itself and ensures that we can pipe data in and out
|
// Attach attaches to the docker container itself and ensures that we can pipe
|
||||||
// of the process stream. This should not be used for reading console data as you *will*
|
// data in and out of the process stream. This should not be used for reading
|
||||||
// miss important output at the beginning because of the time delay with attaching to the
|
// console data as you *will* miss important output at the beginning because of
|
||||||
// output.
|
// the time delay with attaching to the output.
|
||||||
//
|
//
|
||||||
// Calling this function will poll resources for the container in the background until the
|
// Calling this function will poll resources for the container in the background
|
||||||
// provided context is canceled by the caller. Failure to cancel said context will cause
|
// until the provided context is canceled by the caller. Failure to cancel said
|
||||||
// background memory leaks as the goroutine will not exit.
|
// context will cause background memory leaks as the goroutine will not exit.
|
||||||
func (e *Environment) Attach() error {
|
func (e *Environment) Attach() error {
|
||||||
if e.IsAttached() {
|
if e.IsAttached() {
|
||||||
return nil
|
return nil
|
||||||
@@ -108,27 +106,15 @@ func (e *Environment) Attach() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Environment) resources() container.Resources {
|
// InSituUpdate performs an in-place update of the Docker container's resource
|
||||||
l := e.Configuration.Limits()
|
// limits without actually making any changes to the operational state of the
|
||||||
|
// container. This allows memory, cpu, and IO limitations to be adjusted on the
|
||||||
return container.Resources{
|
// fly for individual instances.
|
||||||
Memory: l.BoundedMemoryLimit(),
|
|
||||||
MemoryReservation: l.MemoryLimit * 1_000_000,
|
|
||||||
MemorySwap: l.ConvertedSwap(),
|
|
||||||
CPUQuota: l.ConvertedCpuLimit(),
|
|
||||||
CPUPeriod: 100_000,
|
|
||||||
CPUShares: 1024,
|
|
||||||
BlkioWeight: l.IoWeight,
|
|
||||||
OomKillDisable: &l.OOMDisabled,
|
|
||||||
CpusetCpus: l.Threads,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Performs an in-place update of the Docker container's resource limits without actually
|
|
||||||
// making any changes to the operational state of the container. This allows memory, cpu,
|
|
||||||
// and IO limitations to be adjusted on the fly for individual instances.
|
|
||||||
func (e *Environment) InSituUpdate() error {
|
func (e *Environment) InSituUpdate() error {
|
||||||
if _, err := e.client.ContainerInspect(context.Background(), e.Id); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if _, err := e.client.ContainerInspect(ctx, e.Id); err != nil {
|
||||||
// If the container doesn't exist for some reason there really isn't anything
|
// If the container doesn't exist for some reason there really isn't anything
|
||||||
// we can do to fix that in this process (it doesn't make sense at least). In those
|
// we can do to fix that in this process (it doesn't make sense at least). In those
|
||||||
// cases just return without doing anything since we still want to save the configuration
|
// cases just return without doing anything since we still want to save the configuration
|
||||||
@@ -138,25 +124,24 @@ func (e *Environment) InSituUpdate() error {
|
|||||||
if client.IsErrNotFound(err) {
|
if client.IsErrNotFound(err) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
return errors.Wrap(err, "environment/docker: could not inspect container")
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u := container.UpdateConfig{
|
// CPU pinning cannot be removed once it is applied to a container. The same is true
|
||||||
|
// for removing memory limits, a container must be re-created.
|
||||||
|
//
|
||||||
|
// @see https://github.com/moby/moby/issues/41946
|
||||||
|
if _, err := e.client.ContainerUpdate(ctx, e.Id, container.UpdateConfig{
|
||||||
Resources: e.resources(),
|
Resources: e.resources(),
|
||||||
|
}); err != nil {
|
||||||
|
return errors.Wrap(err, "environment/docker: could not update container")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
|
||||||
defer cancel()
|
|
||||||
if _, err := e.client.ContainerUpdate(ctx, e.Id, u); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new container for the server using all of the data that is currently
|
// Create creates a new container for the server using all of the data that is
|
||||||
// available for it. If the container already exists it will be returnee.
|
// currently available for it. If the container already exists it will be
|
||||||
|
// returned.
|
||||||
func (e *Environment) Create() error {
|
func (e *Environment) Create() error {
|
||||||
// If the container already exists don't hit the user with an error, just return
|
// If the container already exists don't hit the user with an error, just return
|
||||||
// the current information about it which is what we would do when creating the
|
// the current information about it which is what we would do when creating the
|
||||||
@@ -251,23 +236,8 @@ func (e *Environment) Create() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Environment) convertMounts() []mount.Mount {
|
// Destroy will remove the Docker container from the server. If the container
|
||||||
var out []mount.Mount
|
// is currently running it will be forcibly stopped by Docker.
|
||||||
|
|
||||||
for _, m := range e.Configuration.Mounts() {
|
|
||||||
out = append(out, mount.Mount{
|
|
||||||
Type: mount.TypeBind,
|
|
||||||
Source: m.Source,
|
|
||||||
Target: m.Target,
|
|
||||||
ReadOnly: m.ReadOnly,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the Docker container from the machine. If the container is currently running
|
|
||||||
// it will be forcibly stopped by Docker.
|
|
||||||
func (e *Environment) Destroy() error {
|
func (e *Environment) Destroy() 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)
|
||||||
@@ -291,9 +261,55 @@ func (e *Environment) Destroy() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attaches to the log for the container. This avoids us missing crucial output that
|
// SendCommand sends the specified command to the stdin of the running container
|
||||||
// happens in the split seconds before the code moves from 'Starting' to 'Attaching'
|
// instance. There is no confirmation that this data is sent successfully, only
|
||||||
// on the process.
|
// that it gets pushed into the stdin.
|
||||||
|
func (e *Environment) SendCommand(c string) error {
|
||||||
|
if !e.IsAttached() {
|
||||||
|
return errors.Wrap(ErrNotAttached, "environment/docker: cannot send command to container")
|
||||||
|
}
|
||||||
|
|
||||||
|
e.mu.RLock()
|
||||||
|
defer e.mu.RUnlock()
|
||||||
|
|
||||||
|
// 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 it has crashed and attempt to restart it.
|
||||||
|
if e.meta.Stop.Type == "command" && c == e.meta.Stop.Value {
|
||||||
|
e.SetState(environment.ProcessStoppingState)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := e.stream.Conn.Write([]byte(c + "\n"))
|
||||||
|
|
||||||
|
return errors.Wrap(err, "environment/docker: could not write to container stream")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Readlog reads the log file for the server. This does not care if the server
|
||||||
|
// is running or not, it will simply try to read the last X bytes of the file
|
||||||
|
// and return them.
|
||||||
|
func (e *Environment) Readlog(lines int) ([]string, error) {
|
||||||
|
r, err := e.client.ContainerLogs(context.Background(), e.Id, types.ContainerLogsOptions{
|
||||||
|
ShowStdout: true,
|
||||||
|
ShowStderr: true,
|
||||||
|
Tail: strconv.Itoa(lines),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
var out []string
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
for scanner.Scan() {
|
||||||
|
out = append(out, scanner.Text())
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attaches to the log for the container. This avoids us missing crucial output
|
||||||
|
// that happens in the split seconds before the code moves from 'Starting' to
|
||||||
|
// 'Attaching' on the process.
|
||||||
func (e *Environment) followOutput() error {
|
func (e *Environment) followOutput() error {
|
||||||
if exists, err := e.Exists(); !exists {
|
if exists, err := e.Exists(); !exists {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -346,14 +362,19 @@ func (e *Environment) scanOutput(reader io.ReadCloser) {
|
|||||||
go e.followOutput()
|
go e.followOutput()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pulls the image from Docker. If there is an error while pulling the image from the source
|
type imagePullStatus struct {
|
||||||
// but the image already exists locally, we will report that error to the logger but continue
|
Status string `json:"status"`
|
||||||
// with the process.
|
Progress string `json:"progress"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pulls the image from Docker. If there is an error while pulling the image
|
||||||
|
// from the source but the image already exists locally, we will report that
|
||||||
|
// error to the logger but continue with the process.
|
||||||
//
|
//
|
||||||
// The reasoning behind this is that Quay has had some serious outages as of late, and we don't
|
// The reasoning behind this is that Quay has had some serious outages as of
|
||||||
// need to block all of the servers from booting just because of that. I'd imagine in a lot of
|
// late, and we don't need to block all of the servers from booting just because
|
||||||
// cases an outage shouldn't affect users too badly. It'll at least keep existing servers working
|
// of that. I'd imagine in a lot of cases an outage shouldn't affect users too
|
||||||
// correctly if anything.
|
// badly. It'll at least keep existing servers working correctly if anything.
|
||||||
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, "")
|
||||||
@@ -447,3 +468,34 @@ func (e *Environment) ensureImageExists(image string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Environment) convertMounts() []mount.Mount {
|
||||||
|
var out []mount.Mount
|
||||||
|
|
||||||
|
for _, m := range e.Configuration.Mounts() {
|
||||||
|
out = append(out, mount.Mount{
|
||||||
|
Type: mount.TypeBind,
|
||||||
|
Source: m.Source,
|
||||||
|
Target: m.Target,
|
||||||
|
ReadOnly: m.ReadOnly,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Environment) resources() container.Resources {
|
||||||
|
l := e.Configuration.Limits()
|
||||||
|
|
||||||
|
return container.Resources{
|
||||||
|
Memory: l.BoundedMemoryLimit(),
|
||||||
|
MemoryReservation: l.MemoryLimit * 1_000_000,
|
||||||
|
MemorySwap: l.ConvertedSwap(),
|
||||||
|
CPUQuota: l.ConvertedCpuLimit(),
|
||||||
|
CPUPeriod: 100_000,
|
||||||
|
CPUShares: 1024,
|
||||||
|
BlkioWeight: l.IoWeight,
|
||||||
|
OomKillDisable: &l.OOMDisabled,
|
||||||
|
CpusetCpus: l.Threads,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ package docker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
@@ -9,11 +14,6 @@ import (
|
|||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/pterodactyl/wings/environment"
|
"github.com/pterodactyl/wings/environment"
|
||||||
"github.com/pterodactyl/wings/remote"
|
"github.com/pterodactyl/wings/remote"
|
||||||
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Run before the container starts and get the process configuration from the Panel.
|
// Run before the container starts and get the process configuration from the Panel.
|
||||||
@@ -174,7 +174,7 @@ func (e *Environment) Stop() error {
|
|||||||
e.SetState(environment.ProcessOfflineState)
|
e.SetState(environment.ProcessOfflineState)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return errors.Wrap(err, "environment/docker: cannot stop container")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -208,7 +208,9 @@ func (e *Environment) WaitForStop(seconds uint, terminate bool) error {
|
|||||||
return ctxErr
|
return ctxErr
|
||||||
}
|
}
|
||||||
case err := <-errChan:
|
case err := <-errChan:
|
||||||
if err != nil {
|
// If the error stems from the container not existing there is no point in wasting
|
||||||
|
// CPU time to then try and terminate it.
|
||||||
|
if err != nil && !client.IsErrNotFound(err) {
|
||||||
if terminate {
|
if terminate {
|
||||||
l := log.WithField("container_id", e.Id)
|
l := log.WithField("container_id", e.Id)
|
||||||
if errors.Is(err, context.DeadlineExceeded) {
|
if errors.Is(err, context.DeadlineExceeded) {
|
||||||
@@ -219,8 +221,7 @@ func (e *Environment) WaitForStop(seconds uint, terminate bool) error {
|
|||||||
|
|
||||||
return e.Terminate(os.Kill)
|
return e.Terminate(os.Kill)
|
||||||
}
|
}
|
||||||
|
return errors.WrapIf(err, "environment/docker: error waiting on container to enter \"not-running\" state")
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
case <-ok:
|
case <-ok:
|
||||||
}
|
}
|
||||||
@@ -232,7 +233,12 @@ func (e *Environment) WaitForStop(seconds uint, terminate bool) error {
|
|||||||
func (e *Environment) Terminate(signal os.Signal) error {
|
func (e *Environment) Terminate(signal os.Signal) error {
|
||||||
c, err := e.client.ContainerInspect(context.Background(), e.Id)
|
c, err := e.client.ContainerInspect(context.Background(), e.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
// Treat missing containers as an okay error state, means it is obviously
|
||||||
|
// already terminated at this point.
|
||||||
|
if client.IsErrNotFound(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.State.Running {
|
if !c.State.Running {
|
||||||
@@ -249,13 +255,10 @@ func (e *Environment) Terminate(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")
|
sig := strings.TrimSuffix(strings.TrimPrefix(signal.String(), "signal "), "ed")
|
||||||
|
|
||||||
if err := e.client.ContainerKill(context.Background(), e.Id, sig); err != nil && !client.IsErrNotFound(err) {
|
if err := e.client.ContainerKill(context.Background(), e.Id, sig); err != nil && !client.IsErrNotFound(err) {
|
||||||
return err
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
e.SetState(environment.ProcessOfflineState)
|
e.SetState(environment.ProcessOfflineState)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
package docker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"emperror.dev/errors"
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/pterodactyl/wings/environment"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type dockerLogLine struct {
|
|
||||||
Log string `json:"log"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrNotAttached = errors.New("not attached to instance")
|
|
||||||
|
|
||||||
func (e *Environment) setStream(s *types.HijackedResponse) {
|
|
||||||
e.mu.Lock()
|
|
||||||
defer e.mu.Unlock()
|
|
||||||
|
|
||||||
e.stream = s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sends the specified command to the stdin of the running container instance. There is no
|
|
||||||
// confirmation that this data is sent successfully, only that it gets pushed into the stdin.
|
|
||||||
func (e *Environment) SendCommand(c string) error {
|
|
||||||
if !e.IsAttached() {
|
|
||||||
return ErrNotAttached
|
|
||||||
}
|
|
||||||
|
|
||||||
e.mu.RLock()
|
|
||||||
defer e.mu.RUnlock()
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// it has crashed and attempt to restart it.
|
|
||||||
if e.meta.Stop.Type == "command" && c == e.meta.Stop.Value {
|
|
||||||
e.SetState(environment.ProcessStoppingState)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := e.stream.Conn.Write([]byte(c + "\n"))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reads the log file for the server. This does not care if the server is running or not, it will
|
|
||||||
// simply try to read the last X bytes of the file and return them.
|
|
||||||
func (e *Environment) Readlog(lines int) ([]string, error) {
|
|
||||||
r, err := e.client.ContainerLogs(context.Background(), e.Id, types.ContainerLogsOptions{
|
|
||||||
ShowStdout: true,
|
|
||||||
ShowStderr: true,
|
|
||||||
Tail: strconv.Itoa(lines),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer r.Close()
|
|
||||||
|
|
||||||
var out []string
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(r)
|
|
||||||
for scanner.Scan() {
|
|
||||||
out = append(out, scanner.Text())
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Docker stores the logs for server output in a JSON format. This function will iterate over the JSON
|
|
||||||
// that was read from the log file and parse it into a more human readable format.
|
|
||||||
func (e *Environment) parseLogToStrings(b []byte) ([]string, error) {
|
|
||||||
hasError := false
|
|
||||||
var out []string
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(bytes.NewReader(b))
|
|
||||||
for scanner.Scan() {
|
|
||||||
var l dockerLogLine
|
|
||||||
|
|
||||||
// Unmarshal the contents and allow up to a single error before bailing out of the process. We
|
|
||||||
// do this because if you're arbitrarily reading a length of the file you'll likely end up
|
|
||||||
// with the first line in the output being improperly formatted JSON. In those cases we want to
|
|
||||||
// just skip over it. However if we see another error we're going to bail out because that is an
|
|
||||||
// abnormal situation.
|
|
||||||
if err := json.Unmarshal([]byte(scanner.Text()), &l); err != nil {
|
|
||||||
if hasError {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hasError = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
out = append(out, l.Log)
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
@@ -204,12 +204,6 @@ func deleteServer(c *gin.Context) {
|
|||||||
s.Events().Destroy()
|
s.Events().Destroy()
|
||||||
s.Websockets().CancelAll()
|
s.Websockets().CancelAll()
|
||||||
|
|
||||||
// Delete the server's archive if it exists. We intentionally don't return
|
|
||||||
// here, if the archive fails to delete, the server can still be removed.
|
|
||||||
if err := s.Archiver.DeleteIfExists(); err != nil {
|
|
||||||
s.Log().WithField("error", err).Warn("failed to delete server archive during deletion process")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove any pending remote file downloads for the server.
|
// Remove any pending remote file downloads for the server.
|
||||||
for _, dl := range downloader.ByServer(s.Id()) {
|
for _, dl := range downloader.ByServer(s.Id()) {
|
||||||
dl.Cancel()
|
dl.Cancel()
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ func postServerBackup(c *gin.Context) {
|
|||||||
// This endpoint will block until the backup is fully restored allowing for a
|
// This endpoint will block until the backup is fully restored allowing for a
|
||||||
// spinner to be displayed in the Panel UI effectively.
|
// spinner to be displayed in the Panel UI effectively.
|
||||||
//
|
//
|
||||||
// TODO: stop the server if it is running; internally mark it as suspended
|
// TODO: stop the server if it is running
|
||||||
func postServerRestoreBackup(c *gin.Context) {
|
func postServerRestoreBackup(c *gin.Context) {
|
||||||
s := middleware.ExtractServer(c)
|
s := middleware.ExtractServer(c)
|
||||||
client := middleware.ExtractApiClient(c)
|
client := middleware.ExtractApiClient(c)
|
||||||
@@ -84,9 +84,19 @@ func postServerRestoreBackup(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.SetRestoring(true)
|
||||||
|
hasError := true
|
||||||
|
defer func() {
|
||||||
|
if !hasError {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.SetRestoring(false)
|
||||||
|
}()
|
||||||
|
|
||||||
logger.Info("processing server backup restore request")
|
logger.Info("processing server backup restore request")
|
||||||
if data.TruncateDirectory {
|
if data.TruncateDirectory {
|
||||||
logger.Info(`recieved "truncate_directory" flag in request: deleting server files`)
|
logger.Info("received \"truncate_directory\" flag in request: deleting server files")
|
||||||
if err := s.Filesystem().TruncateRootDirectory(); err != nil {
|
if err := s.Filesystem().TruncateRootDirectory(); err != nil {
|
||||||
middleware.CaptureAndAbort(c, err)
|
middleware.CaptureAndAbort(c, err)
|
||||||
return
|
return
|
||||||
@@ -109,7 +119,9 @@ func postServerRestoreBackup(c *gin.Context) {
|
|||||||
s.Events().Publish(server.DaemonMessageEvent, "Completed server restoration from local backup.")
|
s.Events().Publish(server.DaemonMessageEvent, "Completed server restoration from local backup.")
|
||||||
s.Events().Publish(server.BackupRestoreCompletedEvent, "")
|
s.Events().Publish(server.BackupRestoreCompletedEvent, "")
|
||||||
logger.Info("completed server restoration from local backup")
|
logger.Info("completed server restoration from local backup")
|
||||||
|
s.SetRestoring(false)
|
||||||
}(s, b, logger)
|
}(s, b, logger)
|
||||||
|
hasError = false
|
||||||
c.Status(http.StatusAccepted)
|
c.Status(http.StatusAccepted)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -136,7 +148,7 @@ func postServerRestoreBackup(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
// Don't allow content types that we know are going to give us problems.
|
// Don't allow content types that we know are going to give us problems.
|
||||||
if res.Header.Get("Content-Type") == "" || !strings.Contains("application/x-gzip application/gzip", res.Header.Get("Content-Type")) {
|
if res.Header.Get("Content-Type") == "" || !strings.Contains("application/x-gzip application/gzip", res.Header.Get("Content-Type")) {
|
||||||
res.Body.Close()
|
_ = res.Body.Close()
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "The provided backup link is not a supported content type. \"" + res.Header.Get("Content-Type") + "\" is not application/x-gzip.",
|
"error": "The provided backup link is not a supported content type. \"" + res.Header.Get("Content-Type") + "\" is not application/x-gzip.",
|
||||||
})
|
})
|
||||||
@@ -151,8 +163,10 @@ func postServerRestoreBackup(c *gin.Context) {
|
|||||||
s.Events().Publish(server.DaemonMessageEvent, "Completed server restoration from S3 backup.")
|
s.Events().Publish(server.DaemonMessageEvent, "Completed server restoration from S3 backup.")
|
||||||
s.Events().Publish(server.BackupRestoreCompletedEvent, "")
|
s.Events().Publish(server.BackupRestoreCompletedEvent, "")
|
||||||
logger.Info("completed server restoration from S3 backup")
|
logger.Info("completed server restoration from S3 backup")
|
||||||
|
s.SetRestoring(false)
|
||||||
}(s, c.Param("backup"), logger)
|
}(s, c.Param("backup"), logger)
|
||||||
|
|
||||||
|
hasError = false
|
||||||
c.Status(http.StatusAccepted)
|
c.Status(http.StatusAccepted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import (
|
|||||||
"github.com/pterodactyl/wings/router/middleware"
|
"github.com/pterodactyl/wings/router/middleware"
|
||||||
"github.com/pterodactyl/wings/router/tokens"
|
"github.com/pterodactyl/wings/router/tokens"
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
|
"github.com/pterodactyl/wings/server/filesystem"
|
||||||
"github.com/pterodactyl/wings/system"
|
"github.com/pterodactyl/wings/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,6 +52,10 @@ type serverTransferRequest struct {
|
|||||||
Server json.RawMessage `json:"server"`
|
Server json.RawMessage `json:"server"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getArchivePath(sID string) string {
|
||||||
|
return filepath.Join(config.Get().System.ArchiveDirectory, sID+".tar.gz")
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the archive for a server so that it can be transferred to a new node.
|
// Returns the archive for a server so that it can be transferred to a new node.
|
||||||
func getServerArchive(c *gin.Context) {
|
func getServerArchive(c *gin.Context) {
|
||||||
auth := strings.SplitN(c.GetHeader("Authorization"), " ", 2)
|
auth := strings.SplitN(c.GetHeader("Authorization"), " ", 2)
|
||||||
@@ -77,36 +82,51 @@ func getServerArchive(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
st, err := s.Archiver.Stat()
|
archivePath := getArchivePath(s.Id())
|
||||||
|
|
||||||
|
// Stat the archive file.
|
||||||
|
st, err := os.Lstat(archivePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
WithError(c, err)
|
_ = WithError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.AbortWithStatus(http.StatusNotFound)
|
c.AbortWithStatus(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
checksum, err := s.Archiver.Checksum()
|
// Compute sha1 checksum.
|
||||||
|
h := sha256.New()
|
||||||
|
f, err := os.Open(archivePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
NewServerError(err, s).SetMessage("failed to calculate checksum").Abort(c)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if _, err := io.Copy(h, bufio.NewReader(f)); err != nil {
|
||||||
|
_ = f.Close()
|
||||||
|
_ = WithError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
_ = WithError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
checksum := hex.EncodeToString(h.Sum(nil))
|
||||||
|
|
||||||
file, err := os.Open(s.Archiver.Path())
|
// Stream the file to the client.
|
||||||
|
f, err = os.Open(archivePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WithError(c, err)
|
_ = WithError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer f.Close()
|
||||||
|
|
||||||
c.Header("X-Checksum", checksum)
|
c.Header("X-Checksum", checksum)
|
||||||
c.Header("X-Mime-Type", st.Mimetype)
|
c.Header("X-Mime-Type", "application/tar+gzip")
|
||||||
c.Header("Content-Length", strconv.Itoa(int(st.Size())))
|
c.Header("Content-Length", strconv.Itoa(int(st.Size())))
|
||||||
c.Header("Content-Disposition", "attachment; filename="+strconv.Quote(s.Archiver.Name()))
|
c.Header("Content-Disposition", "attachment; filename="+strconv.Quote(s.Id()+".tar.gz"))
|
||||||
c.Header("Content-Type", "application/octet-stream")
|
c.Header("Content-Type", "application/octet-stream")
|
||||||
|
|
||||||
bufio.NewReader(file).WriteTo(c.Writer)
|
_, _ = bufio.NewReader(f).WriteTo(c.Writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func postServerArchive(c *gin.Context) {
|
func postServerArchive(c *gin.Context) {
|
||||||
@@ -164,8 +184,13 @@ func postServerArchive(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create an archive of the entire server's data directory.
|
||||||
|
a := &filesystem.Archive{
|
||||||
|
BasePath: s.Filesystem().Path(),
|
||||||
|
}
|
||||||
|
|
||||||
// Attempt to get an archive of the server.
|
// Attempt to get an archive of the server.
|
||||||
if err := s.Archiver.Archive(); err != nil {
|
if err := a.Create(getArchivePath(s.Id())); err != nil {
|
||||||
sendTransferLog("An error occurred while archiving the server: " + err.Error())
|
sendTransferLog("An error occurred while archiving the server: " + err.Error())
|
||||||
l.WithField("error", err).Error("failed to get transfer archive for server")
|
l.WithField("error", err).Error("failed to get transfer archive for server")
|
||||||
return
|
return
|
||||||
@@ -227,7 +252,7 @@ func (str serverTransferRequest) downloadArchive() (*http.Response, error) {
|
|||||||
|
|
||||||
// Returns the path to the local archive on the system.
|
// Returns the path to the local archive on the system.
|
||||||
func (str serverTransferRequest) path() string {
|
func (str serverTransferRequest) path() string {
|
||||||
return filepath.Join(config.Get().System.ArchiveDirectory, str.ServerID+".tar.gz")
|
return getArchivePath(str.ServerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates the archive location on this machine by first checking that the required file
|
// Creates the archive location on this machine by first checking that the required file
|
||||||
@@ -260,17 +285,16 @@ func (str serverTransferRequest) removeArchivePath() {
|
|||||||
// expected value from the transfer request. The string value returned is the computed
|
// expected value from the transfer request. The string value returned is the computed
|
||||||
// checksum on the system.
|
// checksum on the system.
|
||||||
func (str serverTransferRequest) verifyChecksum(matches string) (bool, string, error) {
|
func (str serverTransferRequest) verifyChecksum(matches string) (bool, string, error) {
|
||||||
file, err := os.Open(str.path())
|
f, err := os.Open(str.path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", err
|
return false, "", err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer f.Close()
|
||||||
hash := sha256.New()
|
h := sha256.New()
|
||||||
buf := make([]byte, 1024*4)
|
if _, err := io.Copy(h, bufio.NewReader(f)); err != nil {
|
||||||
if _, err := io.CopyBuffer(hash, file, buf); err != nil {
|
|
||||||
return false, "", err
|
return false, "", err
|
||||||
}
|
}
|
||||||
checksum := hex.EncodeToString(hash.Sum(nil))
|
checksum := hex.EncodeToString(h.Sum(nil))
|
||||||
return checksum == matches, checksum, nil
|
return checksum == matches, checksum, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +386,7 @@ func postTransfer(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
if res.StatusCode != 200 {
|
if res.StatusCode != http.StatusOK {
|
||||||
data.log().WithField("error", err).WithField("status", res.StatusCode).Error("unexpected error response from transfer endpoint")
|
data.log().WithField("error", err).WithField("status", res.StatusCode).Error("unexpected error response from transfer endpoint")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"emperror.dev/errors"
|
|
||||||
"github.com/mholt/archiver/v3"
|
|
||||||
"github.com/pterodactyl/wings/config"
|
|
||||||
"github.com/pterodactyl/wings/server/filesystem"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Archiver represents a Server Archiver.
|
|
||||||
type Archiver struct {
|
|
||||||
Server *Server
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path returns the path to the server's archive.
|
|
||||||
func (a *Archiver) Path() string {
|
|
||||||
return filepath.Join(config.Get().System.ArchiveDirectory, a.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of the server's archive.
|
|
||||||
func (a *Archiver) Name() string {
|
|
||||||
return a.Server.Id() + ".tar.gz"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exists returns a boolean based off if the archive exists.
|
|
||||||
func (a *Archiver) Exists() bool {
|
|
||||||
if _, err := os.Stat(a.Path()); os.IsNotExist(err) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stat stats the archive file.
|
|
||||||
func (a *Archiver) Stat() (*filesystem.Stat, error) {
|
|
||||||
s, err := os.Stat(a.Path())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &filesystem.Stat{
|
|
||||||
FileInfo: s,
|
|
||||||
Mimetype: "application/tar+gzip",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Archive creates an archive of the server and deletes the previous one.
|
|
||||||
func (a *Archiver) Archive() error {
|
|
||||||
path := a.Server.Filesystem().Path()
|
|
||||||
|
|
||||||
// Get the list of root files and directories to archive.
|
|
||||||
var files []string
|
|
||||||
fileInfo, err := ioutil.ReadDir(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range fileInfo {
|
|
||||||
f := filepath.Join(path, file.Name())
|
|
||||||
// If the file is a symlink we cannot safely assume that the result of a filepath.Join() will be
|
|
||||||
// a safe destination. We need to check if the file is a symlink, and if so pass off to the SafePath
|
|
||||||
// function to resolve it to the final destination.
|
|
||||||
//
|
|
||||||
// ioutil.ReadDir() calls Lstat, so this will work correctly. If it did not call Lstat, but rather
|
|
||||||
// just did a normal Stat call, this would fail since that would be looking at the symlink destination
|
|
||||||
// and not the actual file in this listing.
|
|
||||||
if file.Mode()&os.ModeSymlink != 0 {
|
|
||||||
f, err = a.Server.Filesystem().SafePath(filepath.Join(path, file.Name()))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
files = append(files, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := a.DeleteIfExists(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return archiver.NewTarGz().Archive(files, a.Path())
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteIfExists deletes the archive if it exists.
|
|
||||||
func (a *Archiver) DeleteIfExists() error {
|
|
||||||
if _, err := a.Stat(); err != nil {
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.WithMessage(os.Remove(a.Path()), "archiver: failed to delete archive from system")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checksum computes a SHA256 checksum of the server's archive.
|
|
||||||
func (a *Archiver) Checksum() (string, error) {
|
|
||||||
file, err := os.Open(a.Path())
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
hash := sha256.New()
|
|
||||||
|
|
||||||
buf := make([]byte, 1024*4)
|
|
||||||
if _, err := io.CopyBuffer(hash, file, buf); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return hex.EncodeToString(hash.Sum(nil)), nil
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@ package backup
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/pterodactyl/wings/server/filesystem"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ func (b *LocalBackup) WithLogContext(c map[string]interface{}) {
|
|||||||
// Generate generates a backup of the selected files and pushes it to the
|
// Generate generates a backup of the selected files and pushes it to the
|
||||||
// defined location for this instance.
|
// defined location for this instance.
|
||||||
func (b *LocalBackup) Generate(basePath, ignore string) (*ArchiveDetails, error) {
|
func (b *LocalBackup) Generate(basePath, ignore string) (*ArchiveDetails, error) {
|
||||||
a := &Archive{
|
a := &filesystem.Archive{
|
||||||
BasePath: basePath,
|
BasePath: basePath,
|
||||||
Ignore: ignore,
|
Ignore: ignore,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/pterodactyl/wings/server/filesystem"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -47,7 +48,7 @@ func (s *S3Backup) WithLogContext(c map[string]interface{}) {
|
|||||||
func (s *S3Backup) Generate(basePath, ignore string) (*ArchiveDetails, error) {
|
func (s *S3Backup) Generate(basePath, ignore string) (*ArchiveDetails, error) {
|
||||||
defer s.Remove()
|
defer s.Remove()
|
||||||
|
|
||||||
a := &Archive{
|
a := &filesystem.Archive{
|
||||||
BasePath: basePath,
|
BasePath: basePath,
|
||||||
Ignore: ignore,
|
Ignore: ignore,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ var (
|
|||||||
ErrSuspended = errors.New("server is currently in a suspended state")
|
ErrSuspended = errors.New("server is currently in a suspended state")
|
||||||
ErrServerIsInstalling = errors.New("server is currently installing")
|
ErrServerIsInstalling = errors.New("server is currently installing")
|
||||||
ErrServerIsTransferring = errors.New("server is currently being transferred")
|
ErrServerIsTransferring = errors.New("server is currently being transferred")
|
||||||
|
ErrServerIsRestoring = errors.New("server is currently being restored")
|
||||||
)
|
)
|
||||||
|
|
||||||
type crashTooFrequent struct {
|
type crashTooFrequent struct {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package backup
|
package filesystem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mholt/archiver/v3"
|
"github.com/mholt/archiver/v3"
|
||||||
"github.com/pterodactyl/wings/server/backup"
|
|
||||||
"github.com/pterodactyl/wings/system"
|
"github.com/pterodactyl/wings/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,7 +38,7 @@ func (fs *Filesystem) CompressFiles(dir string, paths []string) (os.FileInfo, er
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
a := &backup.Archive{BasePath: cleanedRootDir, Files: cleaned}
|
a := &Archive{BasePath: cleanedRootDir, Files: cleaned}
|
||||||
d := path.Join(
|
d := path.Join(
|
||||||
cleanedRootDir,
|
cleanedRootDir,
|
||||||
fmt.Sprintf("archive-%s.tar.gz", strings.ReplaceAll(time.Now().Format(time.RFC3339), ":", "")),
|
fmt.Sprintf("archive-%s.tar.gz", strings.ReplaceAll(time.Now().Format(time.RFC3339), ":", "")),
|
||||||
@@ -144,4 +143,3 @@ func (fs *Filesystem) DecompressFile(dir string, file string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -151,6 +151,14 @@ func (s *Server) SetTransferring(state bool) {
|
|||||||
s.transferring.Store(state)
|
s.transferring.Store(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) IsRestoring() bool {
|
||||||
|
return s.restoring.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) SetRestoring(state bool) {
|
||||||
|
s.restoring.Store(state)
|
||||||
|
}
|
||||||
|
|
||||||
// Removes the installer container for the server.
|
// Removes the installer container for the server.
|
||||||
func (ip *InstallationProcess) RemoveContainer() error {
|
func (ip *InstallationProcess) RemoveContainer() error {
|
||||||
err := ip.client.ContainerRemove(ip.context, ip.Server.Id()+"_installer", types.ContainerRemoveOptions{
|
err := ip.client.ContainerRemove(ip.context, ip.Server.Id()+"_installer", types.ContainerRemoveOptions{
|
||||||
|
|||||||
@@ -175,7 +175,6 @@ func (m *Manager) InitServer(data remote.ServerConfigurationResponse) (*Server,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Archiver = Archiver{Server: s}
|
|
||||||
s.fs = filesystem.New(filepath.Join(config.Get().System.Data, s.Id()), s.DiskSpace(), s.Config().Egg.FileDenylist)
|
s.fs = filesystem.New(filepath.Join(config.Get().System.Data, s.Id()), s.DiskSpace(), s.Config().Egg.FileDenylist)
|
||||||
|
|
||||||
// Right now we only support a Docker based environment, so I'm going to hard code
|
// Right now we only support a Docker based environment, so I'm going to hard code
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ func (s *Server) HandlePowerAction(action PowerAction, waitSeconds ...int) error
|
|||||||
return ErrServerIsTransferring
|
return ErrServerIsTransferring
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.IsRestoring() {
|
||||||
|
return ErrServerIsRestoring
|
||||||
|
}
|
||||||
|
|
||||||
if s.powerLock == nil {
|
if s.powerLock == nil {
|
||||||
s.powerLock = semaphore.NewWeighted(1)
|
s.powerLock = semaphore.NewWeighted(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ type Server struct {
|
|||||||
crasher CrashHandler
|
crasher CrashHandler
|
||||||
|
|
||||||
resources ResourceUsage
|
resources ResourceUsage
|
||||||
Archiver Archiver `json:"-"`
|
|
||||||
Environment environment.ProcessEnvironment `json:"-"`
|
Environment environment.ProcessEnvironment `json:"-"`
|
||||||
|
|
||||||
fs *filesystem.Filesystem
|
fs *filesystem.Filesystem
|
||||||
@@ -61,6 +60,7 @@ type Server struct {
|
|||||||
// installer process is still running.
|
// installer process is still running.
|
||||||
installing *system.AtomicBool
|
installing *system.AtomicBool
|
||||||
transferring *system.AtomicBool
|
transferring *system.AtomicBool
|
||||||
|
restoring *system.AtomicBool
|
||||||
|
|
||||||
// The console throttler instance used to control outputs.
|
// The console throttler instance used to control outputs.
|
||||||
throttler *ConsoleThrottler
|
throttler *ConsoleThrottler
|
||||||
@@ -80,6 +80,7 @@ func New(client remote.Client) (*Server, error) {
|
|||||||
client: client,
|
client: client,
|
||||||
installing: system.NewAtomicBool(false),
|
installing: system.NewAtomicBool(false),
|
||||||
transferring: system.NewAtomicBool(false),
|
transferring: system.NewAtomicBool(false),
|
||||||
|
restoring: system.NewAtomicBool(false),
|
||||||
}
|
}
|
||||||
if err := defaults.Set(&s); err != nil {
|
if err := defaults.Set(&s); err != nil {
|
||||||
return nil, errors.Wrap(err, "server: could not set default values for struct")
|
return nil, errors.Wrap(err, "server: could not set default values for struct")
|
||||||
|
|||||||
Reference in New Issue
Block a user