diff --git a/router/router_server_backup.go b/router/router_server_backup.go index 3b318d8..b81074c 100644 --- a/router/router_server_backup.go +++ b/router/router_server_backup.go @@ -12,11 +12,11 @@ import ( func postServerBackup(c *gin.Context) { s := GetServer(c.Param("server")) - data := &backup.Backup{} + data := &backup.LocalBackup{} c.BindJSON(&data) - go func(b *backup.Backup, serv *server.Server) { - if err := serv.BackupRoot(b); err != nil { + go func(b *backup.LocalBackup, serv *server.Server) { + if err := serv.BackupLocal(b); err != nil { zap.S().Errorw("failed to generate backup for server", zap.Error(err)) } }(data, s) diff --git a/server/backup.go b/server/backup.go index 0203013..7740232 100644 --- a/server/backup.go +++ b/server/backup.go @@ -2,17 +2,41 @@ package server import ( "github.com/pkg/errors" + "github.com/pterodactyl/wings/api" "github.com/pterodactyl/wings/server/backup" "go.uber.org/zap" ) +// 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 { + r := api.NewRequester() + rerr, err := r.SendBackupStatus(uuid, ad.ToRequest(successful)) + if rerr != nil || err != nil { + if err != nil { + zap.S().Errorw( + "failed to notify panel of backup status due to internal code error", + zap.String("backup", s.Uuid), + zap.Error(err), + ) + + return err + } + + zap.S().Warnw(rerr.String(), zap.String("backup", uuid)) + + return errors.New(rerr.String()) + } + + return 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) BackupRoot(b *backup.Backup) error { - r, err := b.LocalBackup(s.Filesystem.Path()) - if err != nil { - if notifyError := b.NotifyPanel(r, false); notifyError != nil { +func (s *Server) BackupLocal(b *backup.LocalBackup) error { + if err := b.Backup(s.Filesystem.Path()); err != nil { + if notifyError := s.notifyPanelOfBackup(b.Identifier(), &backup.ArchiveDetails{}, false); notifyError != nil { zap.S().Warnw("failed to notify panel of failed backup state", zap.String("backup", b.Uuid), zap.Error(err)) } @@ -21,7 +45,8 @@ func (s *Server) BackupRoot(b *backup.Backup) error { // 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 := b.NotifyPanel(r, true); notifyError != nil { + ad := b.Details() + if notifyError := s.notifyPanelOfBackup(b.Identifier(), ad, true); notifyError != nil { b.Remove() return notifyError @@ -31,9 +56,9 @@ func (s *Server) BackupRoot(b *backup.Backup) error { // the frontend for the server. s.Events().PublishJson(BackupCompletedEvent+":"+b.Uuid, map[string]interface{}{ "uuid": b.Uuid, - "sha256_hash": r.Checksum, - "file_size": r.Size, + "sha256_hash": ad.Checksum, + "file_size": ad.Size, }) return nil -} \ No newline at end of file +} diff --git a/server/backup/backup.go b/server/backup/backup.go index 0819573..268b1a1 100644 --- a/server/backup/backup.go +++ b/server/backup/backup.go @@ -1,24 +1,30 @@ package backup import ( - "crypto/sha256" - "github.com/pkg/errors" "github.com/pterodactyl/wings/api" - "github.com/pterodactyl/wings/config" - "go.uber.org/zap" - "io" - "os" - "path" ) -type Backup struct { - // The UUID of this backup object. This must line up with a backup from - // the panel instance. - Uuid string `json:"uuid"` +type Backup interface { + // Returns the UUID of this backup as tracked by the panel instance. + Identifier() string - // An array of files to ignore when generating this backup. This should be - // compatible with a standard .gitignore structure. - IgnoredFiles []string `json:"ignored_files"` + // Generates a backup in whatever the configured source for the specific + // implementation is. + Backup(dir string) error + + // Returns a SHA256 checksum for the generated backup. + Checksum() ([]byte, error) + + // Returns the size of the generated backup. + Size() (int64, error) + + // Returns the path to the backup on the machine. This is not always the final + // storage location of the backup, simply the location we're using to store + // it until it is moved to the final spot. + Path() string + + // Returns details about the archive. + Details() *ArchiveDetails } type ArchiveDetails struct { @@ -33,71 +39,4 @@ func (ad *ArchiveDetails) ToRequest(successful bool) api.BackupRequest { Size: ad.Size, Successful: successful, } -} - -// Returns the path for this specific backup. -func (b *Backup) Path() string { - return path.Join(config.Get().System.BackupDirectory, b.Uuid+".tar.gz") -} - -// Returns the SHA256 checksum of a backup. -func (b *Backup) Checksum() ([]byte, error) { - h := sha256.New() - - f, err := os.Open(b.Path()) - if err != nil { - return []byte{}, errors.WithStack(err) - } - defer f.Close() - - if _, err := io.Copy(h, f); err != nil { - return []byte{}, errors.WithStack(err) - } - - return h.Sum(nil), nil -} - -// Removes a backup from the system. -func (b *Backup) Remove() error { - return os.Remove(b.Path()) -} - -// Notifies the panel of a backup's state and returns an error if one is encountered -// while performing this action. -func (b *Backup) NotifyPanel(ad *ArchiveDetails, successful bool) error { - r := api.NewRequester() - - rerr, err := r.SendBackupStatus(b.Uuid, ad.ToRequest(successful)) - if rerr != nil || err != nil { - if err != nil { - zap.S().Errorw( - "failed to notify panel of backup status due to internal code error", - zap.String("backup", b.Uuid), - zap.Error(err), - ) - - return err - } - - zap.S().Warnw(rerr.String(), zap.String("backup", b.Uuid)) - - return errors.New(rerr.String()) - } - - return nil -} - -// Ensures that the local backup destination for files exists. -func (b *Backup) ensureLocalBackupLocation() error { - d := config.Get().System.BackupDirectory - - if _, err := os.Stat(d); err != nil { - if !os.IsNotExist(err) { - return errors.WithStack(err) - } - - return os.MkdirAll(d, 0700) - } - - return nil -} +} \ No newline at end of file diff --git a/server/backup/backup_local.go b/server/backup/backup_local.go index 0e1e0bb..b6fab45 100644 --- a/server/backup/backup_local.go +++ b/server/backup/backup_local.go @@ -1,19 +1,35 @@ package backup import ( + "crypto/sha256" "encoding/hex" "github.com/mholt/archiver/v3" "github.com/pkg/errors" + "github.com/pterodactyl/wings/config" "go.uber.org/zap" + "io" "os" + "path" "strings" "sync" ) +type LocalBackup struct { + // The UUID of this backup object. This must line up with a backup from + // the panel instance. + Uuid string `json:"uuid"` + + // An array of files to ignore when generating this backup. This should be + // compatible with a standard .gitignore structure. + IgnoredFiles []string `json:"ignored_files"` +} + +var _ Backup = (*LocalBackup)(nil) + // Locates the backup for a server and returns the local path. This will obviously only // work if the backup was created as a local backup. -func LocateLocal(uuid string) (*Backup, os.FileInfo, error) { - b := &Backup{ +func LocateLocal(uuid string) (*LocalBackup, os.FileInfo, error) { + b := &LocalBackup{ Uuid: uuid, IgnoredFiles: nil, } @@ -30,26 +46,73 @@ func LocateLocal(uuid string) (*Backup, os.FileInfo, error) { return b, st, nil } +func (b *LocalBackup) Identifier() string { + return b.Uuid +} + +// Returns the path for this specific backup. +func (b *LocalBackup) Path() string { + return path.Join(config.Get().System.BackupDirectory, b.Uuid+".tar.gz") +} + +// Returns the SHA256 checksum of a backup. +func (b *LocalBackup) Checksum() ([]byte, error) { + h := sha256.New() + + f, err := os.Open(b.Path()) + if err != nil { + return []byte{}, errors.WithStack(err) + } + defer f.Close() + + if _, err := io.Copy(h, f); err != nil { + return []byte{}, errors.WithStack(err) + } + + return h.Sum(nil), nil +} + +// Removes a backup from the system. +func (b *LocalBackup) Remove() error { + return os.Remove(b.Path()) +} + // Generates a backup of the selected files and pushes it to the defined location // for this instance. -func (b *Backup) LocalBackup(dir string) (*ArchiveDetails, error) { +func (b *LocalBackup) Backup(dir string) error { if err := archiver.Archive([]string{dir}, b.Path()); err != nil { if strings.HasPrefix(err.Error(), "file already exists") { if rerr := os.Remove(b.Path()); rerr != nil { - return nil, errors.WithStack(rerr) + return errors.WithStack(rerr) } // Re-attempt this backup by calling it with the same information. - return b.LocalBackup(dir) + return b.Backup(dir) } // If there was some error with the archive, just go ahead and ensure the backup // is completely destroyed at this point. Ignore any errors from this function. os.Remove(b.Path()) - return nil, err + return errors.WithStack(err) } + return nil +} + +// Return the size of the generated backup. +func (b *LocalBackup) Size() (int64, error) { + st, err := os.Stat(b.Path()) + if err != nil { + return 0, errors.WithStack(err) + } + + return st.Size(), nil +} + +// Returns details of the archive by utilizing two go-routines to get the checksum and +// the size of the archive. +func (b *LocalBackup) Details() *ArchiveDetails { wg := sync.WaitGroup{} wg.Add(2) @@ -83,5 +146,20 @@ func (b *Backup) LocalBackup(dir string) (*ArchiveDetails, error) { return &ArchiveDetails{ Checksum: checksum, Size: sz, - }, nil + } } + +// Ensures that the local backup destination for files exists. +func (b *LocalBackup) ensureLocalBackupLocation() error { + d := config.Get().System.BackupDirectory + + if _, err := os.Stat(d); err != nil { + if !os.IsNotExist(err) { + return errors.WithStack(err) + } + + return os.MkdirAll(d, 0700) + } + + return nil +} \ No newline at end of file diff --git a/server/backup/backup_s3.go b/server/backup/backup_s3.go new file mode 100644 index 0000000..1dc45f5 --- /dev/null +++ b/server/backup/backup_s3.go @@ -0,0 +1,37 @@ +package backup + +type S3Backup struct { + // The UUID of this backup object. This must line up with a backup from + // the panel instance. + Uuid string + + // An array of files to ignore when generating this backup. This should be + // compatible with a standard .gitignore structure. + IgnoredFiles []string +} + +var _ Backup = (*S3Backup)(nil) + +func (s *S3Backup) Identifier() string { + return s.Uuid +} + +func (s *S3Backup) Backup(dir string) error { + panic("implement me") +} + +func (s *S3Backup) Checksum() ([]byte, error) { + return []byte(""), nil +} + +func (s *S3Backup) Size() (int64, error) { + return 0, nil +} + +func (s *S3Backup) Path() string { + return "" +} + +func (s *S3Backup) Details() *ArchiveDetails { + return &ArchiveDetails{} +}