package server import ( "fmt" "github.com/apex/log" "github.com/creasty/defaults" "github.com/gammazero/workerpool" "github.com/pkg/errors" "github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/environment" "github.com/pterodactyl/wings/environment/docker" "github.com/pterodactyl/wings/server/filesystem" "os" "path/filepath" "runtime" "time" ) var servers = NewCollection(nil) func GetServers() *Collection { return servers } // Iterates over a given directory and loads all of the servers listed before returning // them to the calling function. func LoadDirectory() error { if len(servers.items) != 0 { return errors.New("cannot call LoadDirectory with a non-nil collection") } log.Info("fetching list of servers from API") configs, rerr, err := api.NewRequester().GetAllServerConfigurations() if err != nil || rerr != nil { if err != nil { return errors.WithStack(err) } return errors.New(rerr.String()) } start := time.Now() log.WithField("total_configs", len(configs)).Info("processing servers returned by the API") pool := workerpool.New(runtime.NumCPU()) for uuid, data := range configs { uuid := uuid data := data pool.Submit(func() { log.WithField("server", uuid).Info("creating new server object from API response") s, err := FromConfiguration(data) if err != nil { log.WithField("server", uuid).WithField("error", err).Error("failed to load server, skipping...") return } servers.Add(s) }) } // Wait until we've processed all of the configuration files in the directory // before continuing. pool.StopWait() diff := time.Now().Sub(start) log.WithField("duration", fmt.Sprintf("%s", diff)).Info("finished processing server configurations") return nil } // Initializes a server using a data byte array. This will be marshaled into the // given struct using a YAML marshaler. This will also configure the given environment // for a server. func FromConfiguration(data *api.ServerConfigurationResponse) (*Server, error) { cfg := Configuration{} if err := defaults.Set(&cfg); err != nil { return nil, errors.Wrap(err, "failed to set struct defaults for server configuration") } s := new(Server) if err := defaults.Set(s); err != nil { return nil, errors.Wrap(err, "failed to set struct defaults for server") } s.cfg = cfg if err := s.UpdateDataStructure(data.Settings); err != nil { return nil, err } s.resources = ResourceUsage{} defaults.Set(&s.resources) s.Archiver = Archiver{Server: s} s.fs = filesystem.New(filepath.Join(config.Get().System.Data, s.Id()), s.DiskSpace()) // 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 // some modifications here obviously. settings := environment.Settings{ Mounts: s.Mounts(), Allocations: s.cfg.Allocations, Limits: s.cfg.Build, } envCfg := environment.NewConfiguration(settings, s.GetEnvironmentVariables()) meta := docker.Metadata{ Image: s.Config().Container.Image, } if env, err := docker.New(s.Id(), &meta, envCfg); err != nil { return nil, err } else { s.Environment = env s.StartEventListeners() s.Throttler().StartTimer() } // Forces the configuration to be synced with the panel. if err := s.SyncWithConfiguration(data); err != nil { return nil, err } // If the server's data directory exists, force disk usage calculation. if _, err := os.Stat(s.Filesystem().Path()); err == nil { s.Filesystem().HasSpaceAvailable(true) } return s, nil }