package server import ( "io/ioutil" "os" "emperror.dev/errors" "github.com/apex/log" "github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/server/backup" ) // Notifies the panel of a backup's state and returns an error if one is encountered // while performing this action. func (s *Server) notifyPanelOfBackup(uuid string, ad *backup.ArchiveDetails, successful bool) error { if err := api.New().SendBackupStatus(uuid, ad.ToRequest(successful)); err != nil { if !api.IsRequestError(err) { s.Log().WithFields(log.Fields{ "backup": uuid, "error": err, }).Error("failed to notify panel of backup status due to wings error") return err } return errors.New(err.Error()) } return nil } // Get all of the ignored files for a server based on its .pteroignore file in the root. func (s *Server) getServerwideIgnoredFiles() (string, error) { f, st, err := s.Filesystem().File(".pteroignore") if err != nil { if errors.Is(err, os.ErrNotExist) { return "", nil } return "", err } defer f.Close() if st.Mode()&os.ModeSymlink != 0 || st.Size() > 32*1024 { // Don't read a symlinked ignore file, or a file larger than 32KiB in size. return "", nil } b, err := ioutil.ReadAll(f) if err != nil { return "", err } return string(b), nil } // Performs a server backup and then emits the event over the server websocket. We // let the actual backup system handle notifying the panel of the status, but that // won't emit a websocket event. func (s *Server) Backup(b backup.BackupInterface) error { ignored := b.Ignored() if b.Ignored() == "" { if i, err := s.getServerwideIgnoredFiles(); err != nil { log.WithField("server", s.Id()).WithField("error", err).Warn("failed to get server-wide ignored files") } else { ignored = i } } ad, err := b.Generate(s.Filesystem().Path(), ignored) if err != nil { if err := s.notifyPanelOfBackup(b.Identifier(), &backup.ArchiveDetails{}, false); err != nil { s.Log().WithFields(log.Fields{ "backup": b.Identifier(), "error": err, }).Warn("failed to notify panel of failed backup state") } else { s.Log().WithField("backup", b.Identifier()).Info("notified panel of failed backup state") } _ = s.Events().PublishJson(BackupCompletedEvent+":"+b.Identifier(), map[string]interface{}{ "uuid": b.Identifier(), "is_successful": false, "checksum": "", "checksum_type": "sha1", "file_size": 0, }) return errors.WrapIf(err, "backup: error while generating server backup") } // Try to notify the panel about the status of this backup. If for some reason this request // fails, delete the archive from the daemon and return that error up the chain to the caller. if notifyError := s.notifyPanelOfBackup(b.Identifier(), ad, true); notifyError != nil { _ = b.Remove() s.Log().WithField("error", notifyError).Info("failed to notify panel of successful backup state") return err } else { s.Log().WithField("backup", b.Identifier()).Info("notified panel of successful backup state") } // Emit an event over the socket so we can update the backup in realtime on // the frontend for the server. _ = s.Events().PublishJson(BackupCompletedEvent+":"+b.Identifier(), map[string]interface{}{ "uuid": b.Identifier(), "is_successful": true, "checksum": ad.Checksum, "checksum_type": "sha1", "file_size": ad.Size, }) return nil }