Merge branch 'develop' into schrej/refactor

This commit is contained in:
Dane Everitt
2021-01-25 19:31:16 -08:00
46 changed files with 1734 additions and 1661 deletions

View File

@@ -46,7 +46,7 @@ func (a *Archiver) Stat() (*filesystem.Stat, error) {
}
return &filesystem.Stat{
Info: s,
FileInfo: s,
Mimetype: "application/tar+gzip",
}, nil
}

View File

@@ -6,6 +6,16 @@ import (
"github.com/pterodactyl/wings/environment"
)
type EggConfiguration struct {
// The internal UUID of the Egg on the Panel.
ID string
// Maintains a list of files that are blacklisted for opening/editing/downloading
// or basically any type of access on the server by any user. This is NOT the same
// as a per-user denylist, this is defined at the Egg level.
FileDenylist []string `json:"file_denylist"`
}
type Configuration struct {
mu sync.RWMutex
@@ -34,6 +44,7 @@ type Configuration struct {
CrashDetectionEnabled bool `default:"true" json:"enabled" yaml:"enabled"`
Mounts []Mount `json:"mounts"`
Resources ResourceUsage `json:"resources"`
Egg EggConfiguration `json:"egg,omitempty"`
Container struct {
// Defines the Docker image that will be used for this server

View File

@@ -1,30 +0,0 @@
package server
import (
"os"
"github.com/pterodactyl/wings/server/filesystem"
)
func (s *Server) Filesystem() *filesystem.Filesystem {
return s.fs
}
// Ensures that the data directory for the server instance exists.
func (s *Server) EnsureDataDirectoryExists() error {
if _, err := os.Stat(s.fs.Path()); err != nil && !os.IsNotExist(err) {
return err
} else if err != nil {
// Create the server data directory because it does not currently exist
// on the system.
if err := os.MkdirAll(s.fs.Path(), 0700); err != nil {
return err
}
if err := s.fs.Chown("/"); err != nil {
s.Log().WithField("error", err).Warn("failed to chown server data directory")
}
}
return nil
}

View File

@@ -4,28 +4,29 @@ import (
"archive/tar"
"archive/zip"
"compress/gzip"
"emperror.dev/errors"
"fmt"
"github.com/mholt/archiver/v3"
"os"
"path/filepath"
"reflect"
"strings"
"sync/atomic"
"emperror.dev/errors"
"github.com/mholt/archiver/v3"
)
// Look through a given archive and determine if decompressing it would put the server over
// its allocated disk space limit.
func (fs *Filesystem) SpaceAvailableForDecompression(dir string, file string) (bool, error) {
// SpaceAvailableForDecompression looks through a given archive and determines
// if decompressing it would put the server over its allocated disk space limit.
func (fs *Filesystem) SpaceAvailableForDecompression(dir string, file string) error {
// Don't waste time trying to determine this if we know the server will have the space for
// it since there is no limit.
if fs.MaxDisk() <= 0 {
return true, nil
return nil
}
source, err := fs.SafePath(filepath.Join(dir, file))
if err != nil {
return false, err
return err
}
// Get the cached size in a parallel process so that if it is not cached we are not
@@ -38,32 +39,28 @@ func (fs *Filesystem) SpaceAvailableForDecompression(dir string, file string) (b
if atomic.AddInt64(&size, f.Size())+dirSize > fs.MaxDisk() {
return &Error{code: ErrCodeDiskSpace}
}
return nil
})
if err != nil {
if strings.HasPrefix(err.Error(), "format ") {
return false, &Error{code: ErrCodeUnknownArchive}
return &Error{code: ErrCodeUnknownArchive}
}
return false, err
return err
}
return true, err
return err
}
// Decompress a file in a given directory by using the archiver tool to infer the file
// type and go from there. This will walk over all of the files within the given archive
// and ensure that there is not a zip-slip attack being attempted by validating that the
// final path is within the server data directory.
// DecompressFile will decompress a file in a given directory by using the
// archiver tool to infer the file type and go from there. This will walk over
// all of the files within the given archive and ensure that there is not a
// zip-slip attack being attempted by validating that the final path is within
// the server data directory.
func (fs *Filesystem) DecompressFile(dir string, file string) error {
source, err := fs.SafePath(filepath.Join(dir, file))
if err != nil {
return err
}
// Make sure the file exists basically.
// Ensure that the source archive actually exists on the system.
if _, err := os.Stat(source); err != nil {
return err
}
@@ -79,7 +76,6 @@ func (fs *Filesystem) DecompressFile(dir string, file string) error {
}
var name string
switch s := f.Sys().(type) {
case *tar.Header:
name = s.Name
@@ -88,23 +84,28 @@ func (fs *Filesystem) DecompressFile(dir string, file string) error {
case *zip.FileHeader:
name = s.Name
default:
return errors.New(fmt.Sprintf("could not parse underlying data source with type %s", reflect.TypeOf(s).String()))
return &Error{
code: ErrCodeUnknownError,
resolved: filepath.Join(dir, f.Name()),
err: errors.New(fmt.Sprintf("could not parse underlying data source with type: %s", reflect.TypeOf(s).String())),
}
}
p, err := fs.SafePath(filepath.Join(dir, name))
if err != nil {
return errors.WithMessage(err, "failed to generate a safe path to server file")
p := filepath.Join(dir, name)
// If it is ignored, just don't do anything with the file and skip over it.
if err := fs.IsIgnored(p); err != nil {
return nil
}
return errors.WithMessage(fs.Writefile(p, f), "could not extract file from archive")
if err := fs.Writefile(p, f); err != nil {
return &Error{code: ErrCodeUnknownError, err: err, resolved: source}
}
return nil
})
if err != nil {
if strings.HasPrefix(err.Error(), "format ") {
return &Error{code: ErrCodeUnknownArchive}
}
return err
}
return nil
}

View File

@@ -1,11 +1,12 @@
package filesystem
import (
"emperror.dev/errors"
"fmt"
"github.com/apex/log"
"os"
"path/filepath"
"emperror.dev/errors"
"github.com/apex/log"
)
type ErrorCode string
@@ -15,61 +16,61 @@ const (
ErrCodeDiskSpace ErrorCode = "E_NODISK"
ErrCodeUnknownArchive ErrorCode = "E_UNKNFMT"
ErrCodePathResolution ErrorCode = "E_BADPATH"
ErrCodeDenylistFile ErrorCode = "E_DENYLIST"
ErrCodeUnknownError ErrorCode = "E_UNKNOWN"
)
type Error struct {
code ErrorCode
path string
code ErrorCode
// Contains the underlying error leading to this. This value may or may not be
// present, it is entirely dependent on how this error was triggered.
err error
// This contains the value of the final destination that triggered this specific
// error event.
resolved string
// This value is generally only present on errors stemming from a path resolution
// error. For everything else you should be setting and reading the resolved path
// value which will be far more useful.
path string
}
// Code returns the ErrorCode for this specific error instance.
func (e *Error) Code() ErrorCode {
return e.code
}
// Returns a human-readable error string to identify the Error by.
func (e *Error) Error() string {
switch e.code {
case ErrCodeIsDirectory:
return "filesystem: is a directory"
return fmt.Sprintf("filesystem: cannot perform action: [%s] is a directory", e.resolved)
case ErrCodeDiskSpace:
return "filesystem: not enough disk space"
case ErrCodeUnknownArchive:
return "filesystem: unknown archive format"
case ErrCodeDenylistFile:
r := e.resolved
if r == "" {
r = "<empty>"
}
return fmt.Sprintf("filesystem: file access prohibited: [%s] is on the denylist", r)
case ErrCodePathResolution:
r := e.resolved
if r == "" {
r = "<empty>"
}
return fmt.Sprintf("filesystem: server path [%s] resolves to a location outside the server root: %s", e.path, r)
case ErrCodeUnknownError:
fallthrough
default:
return fmt.Sprintf("filesystem: an error occurred: %s", e.Cause())
}
return "filesystem: unhandled error type"
}
// Returns the ErrorCode for this specific error instance.
func (e *Error) Code() ErrorCode {
return e.code
}
// Checks if the given error is one of the Filesystem errors.
func IsFilesystemError(err error) (*Error, bool) {
if e := errors.Unwrap(err); e != nil {
err = e
}
if fserr, ok := err.(*Error); ok {
return fserr, true
}
return nil, false
}
// Checks if "err" is a filesystem Error type. If so, it will then drop in and check
// that the error code is the same as the provided ErrorCode passed in "code".
func IsErrorCode(err error, code ErrorCode) bool {
if e, ok := IsFilesystemError(err); ok {
return e.code == code
}
return false
}
// Returns a new BadPathResolution error.
func NewBadPathResolution(path string, resolved string) *Error {
return &Error{code: ErrCodePathResolution, path: path, resolved: resolved}
// Cause returns the underlying cause of this filesystem error. In some causes
// there may not be a cause present, in which case nil will be returned.
func (e *Error) Cause() error {
return e.err
}
// Generates an error logger instance with some basic information.
@@ -86,10 +87,46 @@ func (fs *Filesystem) handleWalkerError(err error, f os.FileInfo) error {
if !IsErrorCode(err, ErrCodePathResolution) {
return err
}
if f != nil && f.IsDir() {
return filepath.SkipDir
}
return nil
}
// IsFilesystemError checks if the given error is one of the Filesystem errors.
func IsFilesystemError(err error) bool {
var fserr *Error
if err != nil && errors.As(err, &fserr) {
return true
}
return false
}
// IsErrorCode checks if "err" is a filesystem Error type. If so, it will then
// drop in and check that the error code is the same as the provided ErrorCode
// passed in "code".
func IsErrorCode(err error, code ErrorCode) bool {
var fserr *Error
if err != nil && errors.As(err, &fserr) {
return fserr.code == code
}
return false
}
// NewBadPathResolution returns a new BadPathResolution error.
func NewBadPathResolution(path string, resolved string) *Error {
return &Error{code: ErrCodePathResolution, path: path, resolved: resolved}
}
// WrapError wraps the provided error as a Filesystem error and attaches the
// provided resolved source to it. If the error is already a Filesystem error
// no action is taken.
func WrapError(err error, resolved string) *Error {
if err == nil {
return nil
}
if IsFilesystemError(err) {
return err.(*Error)
}
return &Error{code: ErrCodeUnknownError, err: err, resolved: resolved}
}

View File

@@ -2,11 +2,6 @@ package filesystem
import (
"bufio"
"emperror.dev/errors"
"github.com/gabriel-vasile/mimetype"
"github.com/karrick/godirwalk"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system"
"io"
"io/ioutil"
"os"
@@ -17,6 +12,13 @@ import (
"strings"
"sync"
"time"
"emperror.dev/errors"
"github.com/gabriel-vasile/mimetype"
"github.com/karrick/godirwalk"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/system"
ignore "github.com/sabhiram/go-gitignore"
)
type Filesystem struct {
@@ -25,6 +27,7 @@ type Filesystem struct {
lookupInProgress *system.AtomicBool
diskUsed int64
diskCheckInterval time.Duration
denylist *ignore.GitIgnore
// The maximum amount of disk space (in bytes) that this Filesystem instance can use.
diskLimit int64
@@ -35,42 +38,78 @@ type Filesystem struct {
isTest bool
}
// Creates a new Filesystem instance for a given server.
func New(root string, size int64) *Filesystem {
// New creates a new Filesystem instance for a given server.
func New(root string, size int64, denylist []string) *Filesystem {
return &Filesystem{
root: root,
diskLimit: size,
diskCheckInterval: time.Duration(config.Get().System.DiskCheckInterval),
lastLookupTime: &usageLookupTime{},
lookupInProgress: system.NewAtomicBool(false),
denylist: ignore.CompileIgnoreLines(denylist...),
}
}
// Returns the root path for the Filesystem instance.
// Path returns the root path for the Filesystem instance.
func (fs *Filesystem) Path() string {
return fs.root
}
// Returns a reader for a file instance.
func (fs *Filesystem) File(p string) (*os.File, os.FileInfo, error) {
// File returns a reader for a file instance as well as the stat information.
func (fs *Filesystem) File(p string) (*os.File, Stat, error) {
cleaned, err := fs.SafePath(p)
if err != nil {
return nil, nil, err
return nil, Stat{}, err
}
st, err := os.Stat(cleaned)
st, err := fs.Stat(cleaned)
if err != nil {
return nil, nil, err
return nil, Stat{}, err
}
if st.IsDir() {
return nil, nil, &Error{code: ErrCodeIsDirectory}
return nil, Stat{}, &Error{code: ErrCodeIsDirectory}
}
f, err := os.Open(cleaned)
if err != nil {
return nil, nil, err
return nil, Stat{}, err
}
return f, st, nil
}
// Acts by creating the given file and path on the disk if it is not present already. If
// it is present, the file is opened using the defaults which will truncate the contents.
// The opened file is then returned to the caller.
func (fs *Filesystem) Touch(p string, flag int) (*os.File, error) {
cleaned, err := fs.SafePath(p)
if err != nil {
return nil, err
}
f, err := os.OpenFile(cleaned, flag, 0644)
if err == nil {
return f, nil
}
// If the error is not because it doesn't exist then we just need to bail at this point.
if !errors.Is(err, os.ErrNotExist) {
return nil, err
}
// Create the path leading up to the file we're trying to create, setting the final perms
// on it as we go.
if err := os.MkdirAll(filepath.Dir(cleaned), 0755); err != nil {
return nil, err
}
if err := fs.Chown(filepath.Dir(cleaned)); err != nil {
return nil, err
}
o := &fileOpener{}
// Try to open the file now that we have created the pathing necessary for it, and then
// Chown that file so that the permissions don't mess with things.
f, err = o.open(cleaned, flag, 0644)
if err != nil {
return nil, err
}
_ = fs.Chown(cleaned)
return f, nil
}
// Reads a file on the system and returns it as a byte representation in a file
// reader. This is not the most memory efficient usage since it will be reading the
// entirety of the file into memory.
@@ -84,7 +123,8 @@ func (fs *Filesystem) Readfile(p string, w io.Writer) error {
return err
}
// Writes a file to the system. If the file does not already exist one will be created.
// Writefile writes a file to the system. If the file does not already exist one
// will be created.
func (fs *Filesystem) Writefile(p string, r io.Reader) error {
cleaned, err := fs.SafePath(p)
if err != nil {
@@ -99,7 +139,7 @@ func (fs *Filesystem) Writefile(p string, r io.Reader) error {
return err
} else if err == nil {
if stat.IsDir() {
return &Error{code: ErrCodeIsDirectory}
return &Error{code: ErrCodeIsDirectory, resolved: cleaned}
}
currentSize = stat.Size()
}
@@ -112,22 +152,9 @@ func (fs *Filesystem) Writefile(p string, r io.Reader) error {
return err
}
// If we were unable to stat the location because it did not exist, go ahead and create
// it now. We do this after checking the disk space so that we do not just create empty
// directories at random.
if err != nil {
if err := os.MkdirAll(filepath.Dir(cleaned), 0755); err != nil {
return err
}
if err := fs.Chown(filepath.Dir(cleaned)); err != nil {
return err
}
}
o := &fileOpener{}
// This will either create the file if it does not already exist, or open and
// truncate the existing file.
file, err := o.open(cleaned, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
// Touch the file and return the handle to it at this point. This will create the file
// and any necessary directories as needed.
file, err := fs.Touch(cleaned, os.O_RDWR|os.O_CREATE|os.O_TRUNC)
if err != nil {
return err
}
@@ -150,7 +177,6 @@ func (fs *Filesystem) CreateDirectory(name string, p string) error {
if err != nil {
return err
}
return os.MkdirAll(cleaned, 0755)
}
@@ -411,9 +437,9 @@ func (fo *fileOpener) open(path string, flags int, perm os.FileMode) (*os.File,
}
}
// Lists the contents of a given directory and returns stat information about each
// file and folder within it.
func (fs *Filesystem) ListDirectory(p string) ([]*Stat, error) {
// ListDirectory lists the contents of a given directory and returns stat
// information about each file and folder within it.
func (fs *Filesystem) ListDirectory(p string) ([]Stat, error) {
cleaned, err := fs.SafePath(p)
if err != nil {
return nil, err
@@ -429,7 +455,7 @@ func (fs *Filesystem) ListDirectory(p string) ([]*Stat, error) {
// You must initialize the output of this directory as a non-nil value otherwise
// when it is marshaled into a JSON object you'll just get 'null' back, which will
// break the panel badly.
out := make([]*Stat, len(files))
out := make([]Stat, len(files))
// Iterate over all of the files and directories returned and perform an async process
// to get the mime-type for them all.
@@ -456,15 +482,10 @@ func (fs *Filesystem) ListDirectory(p string) ([]*Stat, error) {
}
}
st := &Stat{
Info: f,
Mimetype: d,
}
st := Stat{FileInfo: f, Mimetype: d}
if m != nil {
st.Mimetype = m.String()
}
out[idx] = st
}(i, file)
}
@@ -474,17 +495,16 @@ func (fs *Filesystem) ListDirectory(p string) ([]*Stat, error) {
// Sort the output alphabetically to begin with since we've run the output
// through an asynchronous process and the order is gonna be very random.
sort.SliceStable(out, func(i, j int) bool {
if out[i].Info.Name() == out[j].Info.Name() || out[i].Info.Name() > out[j].Info.Name() {
if out[i].Name() == out[j].Name() || out[i].Name() > out[j].Name() {
return true
}
return false
})
// Then, sort it so that directories are listed first in the output. Everything
// will continue to be alphabetized at this point.
sort.SliceStable(out, func(i, j int) bool {
return out[i].Info.IsDir()
return out[i].IsDir()
})
return out, nil

View File

@@ -3,8 +3,6 @@ package filesystem
import (
"bytes"
"errors"
. "github.com/franela/goblin"
"github.com/pterodactyl/wings/config"
"io/ioutil"
"math/rand"
"os"
@@ -12,6 +10,9 @@ import (
"sync/atomic"
"testing"
"unicode/utf8"
. "github.com/franela/goblin"
"github.com/pterodactyl/wings/config"
)
func NewFs() (*Filesystem, *rootFs) {
@@ -33,7 +34,7 @@ func NewFs() (*Filesystem, *rootFs) {
rfs.reset()
fs := New(filepath.Join(tmpDir, "/server"), 0)
fs := New(filepath.Join(tmpDir, "/server"), 0, []string{})
fs.isTest = true
return fs, &rfs

View File

@@ -2,13 +2,29 @@ package filesystem
import (
"context"
"golang.org/x/sync/errgroup"
"os"
"path/filepath"
"strings"
"sync"
"golang.org/x/sync/errgroup"
)
// Checks if the given file or path is in the server's file denylist. If so, an Error
// is returned, otherwise nil is returned.
func (fs *Filesystem) IsIgnored(paths ...string) error {
for _, p := range paths {
sp, err := fs.SafePath(p)
if err != nil {
return err
}
if fs.denylist.MatchesPath(sp) {
return &Error{code: ErrCodeDenylistFile, path: p, resolved: sp}
}
}
return nil
}
// Normalizes a directory being passed in to ensure the user is not able to escape
// from their data directory. After normalization if the directory is still within their home
// path it is returned. If they managed to "escape" an error will be returned.

View File

@@ -2,14 +2,15 @@ package filesystem
import (
"encoding/json"
"github.com/gabriel-vasile/mimetype"
"os"
"strconv"
"time"
"github.com/gabriel-vasile/mimetype"
)
type Stat struct {
Info os.FileInfo
os.FileInfo
Mimetype string
}
@@ -26,50 +27,48 @@ func (s *Stat) MarshalJSON() ([]byte, error) {
Symlink bool `json:"symlink"`
Mime string `json:"mime"`
}{
Name: s.Info.Name(),
Name: s.Name(),
Created: s.CTime().Format(time.RFC3339),
Modified: s.Info.ModTime().Format(time.RFC3339),
Mode: s.Info.Mode().String(),
Modified: s.ModTime().Format(time.RFC3339),
Mode: s.Mode().String(),
// Using `&os.ModePerm` on the file's mode will cause the mode to only have the permission values, and nothing else.
ModeBits: strconv.FormatUint(uint64(s.Info.Mode()&os.ModePerm), 8),
Size: s.Info.Size(),
Directory: s.Info.IsDir(),
File: !s.Info.IsDir(),
Symlink: s.Info.Mode().Perm()&os.ModeSymlink != 0,
ModeBits: strconv.FormatUint(uint64(s.Mode()&os.ModePerm), 8),
Size: s.Size(),
Directory: s.IsDir(),
File: !s.IsDir(),
Symlink: s.Mode().Perm()&os.ModeSymlink != 0,
Mime: s.Mimetype,
})
}
// Stats a file or folder and returns the base stat object from go along with the
// MIME data that can be used for editing files.
func (fs *Filesystem) Stat(p string) (*Stat, error) {
// Stat stats a file or folder and returns the base stat object from go along
// with the MIME data that can be used for editing files.
func (fs *Filesystem) Stat(p string) (Stat, error) {
cleaned, err := fs.SafePath(p)
if err != nil {
return nil, err
return Stat{}, err
}
return fs.unsafeStat(cleaned)
}
func (fs *Filesystem) unsafeStat(p string) (*Stat, error) {
func (fs *Filesystem) unsafeStat(p string) (Stat, error) {
s, err := os.Stat(p)
if err != nil {
return nil, err
return Stat{}, err
}
var m *mimetype.MIME
if !s.IsDir() {
m, err = mimetype.DetectFile(p)
if err != nil {
return nil, err
return Stat{}, err
}
}
st := &Stat{
Info: s,
st := Stat{
FileInfo: s,
Mimetype: "inode/directory",
}
if m != nil {
st.Mimetype = m.String()
}

View File

@@ -5,9 +5,9 @@ import (
"time"
)
// Returns the time that the file/folder was created.
// CTime returns the time that the file/folder was created.
func (s *Stat) CTime() time.Time {
st := s.Info.Sys().(*syscall.Stat_t)
st := s.Sys().(*syscall.Stat_t)
return time.Unix(st.Ctimespec.Sec, st.Ctimespec.Nsec)
}

View File

@@ -7,7 +7,7 @@ import (
// Returns the time that the file/folder was created.
func (s *Stat) CTime() time.Time {
st := s.Info.Sys().(*syscall.Stat_t)
st := s.Sys().(*syscall.Stat_t)
// 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

@@ -8,5 +8,5 @@ import (
// However, I have no idea how to do this on windows, so we're skipping it
// for right now.
func (s *Stat) CTime() time.Time {
return s.Info.ModTime()
return s.ModTime()
}

View File

@@ -127,7 +127,7 @@ func NewInstallationProcess(s *Server, script *api.InstallationScript) (*Install
Server: s,
}
if c, err := environment.DockerClient(); err != nil {
if c, err := environment.Docker(); err != nil {
return nil, err
} else {
proc.client = c
@@ -326,7 +326,7 @@ func (ip *InstallationProcess) BeforeExecute() error {
// Returns the log path for the installation process.
func (ip *InstallationProcess) GetLogPath() string {
return filepath.Join(config.Get().System.GetInstallLogPath(), ip.Server.Id()+".log")
return filepath.Join(config.Get().System.LogDirectory, "/install", ip.Server.Id()+".log")
}
// Cleans up after the execution of the installation process. This grabs the logs from the
@@ -447,6 +447,14 @@ func (ip *InstallationProcess) Execute() (string, error) {
NetworkMode: container.NetworkMode(config.Get().Docker.Network.Mode),
}
// Ensure the root directory for the server exists properly before attempting
// to trigger the reinstall of the server. It is possible the directory would
// not exist when this runs if Wings boots with a missing directory and a user
// triggers a reinstall before trying to start the server.
if err := ip.Server.EnsureDataDirectoryExists(); err != nil {
return "", err
}
ip.Server.Log().WithField("install_script", ip.tempDir()+"/install.sh").Info("creating install container for server process")
// Remove the temporary directory when the installation process finishes for this server container.
defer func() {

View File

@@ -136,7 +136,7 @@ func (s *Server) StartEventListeners() {
}
}
s.Log().Info("registering event listeners: console, state, resources...")
s.Log().Debug("registering event listeners: console, state, resources...")
s.Environment.Events().On(environment.ConsoleOutputEvent, &console)
s.Environment.Events().On(environment.StateChangeEvent, &state)
s.Environment.Events().On(environment.ResourceEvent, &stats)

View File

@@ -90,7 +90,7 @@ func FromConfiguration(data api.ServerConfigurationResponse) (*Server, error) {
}
s.Archiver = Archiver{Server: s}
s.fs = filesystem.New(filepath.Join(config.Get().System.Data, s.Id()), s.DiskSpace())
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
// this logic in. When we're ready to support other environment we'll need to make

View File

@@ -3,6 +3,7 @@ package server
import (
"context"
"fmt"
"os"
"strings"
"sync"
@@ -224,3 +225,27 @@ func (s *Server) ProcessConfiguration() *api.ProcessConfiguration {
return s.procConfig
}
// Filesystem returns an instance of the filesystem for this server.
func (s *Server) Filesystem() *filesystem.Filesystem {
return s.fs
}
// EnsureDataDirectoryExists ensures that the data directory for the server
// instance exists.
func (s *Server) EnsureDataDirectoryExists() error {
if _, err := os.Lstat(s.fs.Path()); err != nil {
if os.IsNotExist(err) {
s.Log().Debug("server: creating root directory and setting permissions")
if err := os.MkdirAll(s.fs.Path(), 0700); err != nil {
return errors.WithStack(err)
}
if err := s.fs.Chown("/"); err != nil {
s.Log().WithField("error", err).Warn("server: failed to chown server data directory")
}
} else {
return errors.WrapIf(err, "server: failed to stat server root directory")
}
}
return nil
}