2020-04-14 05:01:07 +00:00
|
|
|
package backup
|
|
|
|
|
|
|
|
import (
|
2021-05-02 19:28:36 +00:00
|
|
|
"context"
|
2020-08-24 00:34:48 +00:00
|
|
|
"crypto/sha1"
|
2020-05-02 22:02:02 +00:00
|
|
|
"encoding/hex"
|
|
|
|
"io"
|
2021-07-15 21:37:38 +00:00
|
|
|
"io/fs"
|
2020-05-02 22:02:02 +00:00
|
|
|
"os"
|
|
|
|
"path"
|
2021-01-17 02:06:22 +00:00
|
|
|
|
2021-05-02 19:28:36 +00:00
|
|
|
"emperror.dev/errors"
|
2021-01-17 02:06:22 +00:00
|
|
|
"github.com/apex/log"
|
2022-11-06 20:38:30 +00:00
|
|
|
"github.com/mholt/archiver/v4"
|
2021-07-15 21:37:38 +00:00
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
|
2021-01-17 02:06:22 +00:00
|
|
|
"github.com/pterodactyl/wings/config"
|
2021-02-02 05:28:46 +00:00
|
|
|
"github.com/pterodactyl/wings/remote"
|
2020-04-14 05:01:07 +00:00
|
|
|
)
|
|
|
|
|
2022-11-06 20:38:30 +00:00
|
|
|
var format = archiver.CompressedArchive{
|
|
|
|
Compression: archiver.Gz{},
|
|
|
|
Archival: archiver.Tar{},
|
|
|
|
}
|
|
|
|
|
2020-12-28 00:16:40 +00:00
|
|
|
type AdapterType string
|
|
|
|
|
2020-04-26 23:21:58 +00:00
|
|
|
const (
|
2020-12-28 00:16:40 +00:00
|
|
|
LocalBackupAdapter AdapterType = "wings"
|
|
|
|
S3BackupAdapter AdapterType = "s3"
|
2020-04-26 23:21:58 +00:00
|
|
|
)
|
|
|
|
|
2021-01-19 05:20:58 +00:00
|
|
|
// RestoreCallback is a generic restoration callback that exists for both local
|
|
|
|
// and remote backups allowing the files to be restored.
|
2022-11-06 20:38:30 +00:00
|
|
|
type RestoreCallback func(file string, info fs.FileInfo, r io.ReadCloser) error
|
2021-01-19 05:20:58 +00:00
|
|
|
|
2020-05-02 22:02:02 +00:00
|
|
|
// noinspection GoNameStartsWithPackageName
|
|
|
|
type BackupInterface interface {
|
2021-02-02 05:28:46 +00:00
|
|
|
// SetClient sets the API request client on the backup interface.
|
2022-09-26 17:14:57 +00:00
|
|
|
SetClient(remote.Client)
|
2021-01-19 05:20:58 +00:00
|
|
|
// Identifier returns the UUID of this backup as tracked by the panel
|
|
|
|
// instance.
|
2020-04-17 20:46:36 +00:00
|
|
|
Identifier() string
|
2021-01-19 05:20:58 +00:00
|
|
|
// WithLogContext attaches additional context to the log output for this
|
|
|
|
// backup.
|
2020-12-28 00:16:40 +00:00
|
|
|
WithLogContext(map[string]interface{})
|
2021-01-19 05:20:58 +00:00
|
|
|
// Generate creates a backup in whatever the configured source for the
|
|
|
|
// specific implementation is.
|
2022-09-26 17:14:57 +00:00
|
|
|
Generate(context.Context, string, string) (*ArchiveDetails, error)
|
2021-01-19 05:20:58 +00:00
|
|
|
// Ignored returns the ignored files for this backup instance.
|
2020-12-25 19:52:57 +00:00
|
|
|
Ignored() string
|
2021-01-19 05:20:58 +00:00
|
|
|
// Checksum returns a SHA1 checksum for the generated backup.
|
2020-04-17 20:46:36 +00:00
|
|
|
Checksum() ([]byte, error)
|
2021-01-19 05:20:58 +00:00
|
|
|
// Size returns the size of the generated backup.
|
2020-04-17 20:46:36 +00:00
|
|
|
Size() (int64, error)
|
2021-01-19 05:20:58 +00:00
|
|
|
// Path 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.
|
2020-04-17 20:46:36 +00:00
|
|
|
Path() string
|
2021-01-19 05:20:58 +00:00
|
|
|
// Details returns details about the archive.
|
2022-09-26 17:14:57 +00:00
|
|
|
Details(context.Context, []remote.BackupPart) (*ArchiveDetails, error)
|
2021-01-19 05:20:58 +00:00
|
|
|
// Remove removes a backup file.
|
2020-04-26 23:43:18 +00:00
|
|
|
Remove() error
|
2021-01-19 05:20:58 +00:00
|
|
|
// Restore is called when a backup is ready to be restored to the disk from
|
|
|
|
// the given source. Not every backup implementation will support this nor
|
|
|
|
// will every implementation require a reader be provided.
|
2022-09-26 17:14:57 +00:00
|
|
|
Restore(context.Context, io.Reader, RestoreCallback) error
|
2021-05-02 19:28:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Backup 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.
|
|
|
|
Ignore string `json:"ignore"`
|
|
|
|
|
|
|
|
client remote.Client
|
|
|
|
adapter AdapterType
|
|
|
|
logContext map[string]interface{}
|
2020-04-14 05:01:07 +00:00
|
|
|
}
|
|
|
|
|
2021-02-02 05:28:46 +00:00
|
|
|
func (b *Backup) SetClient(c remote.Client) {
|
|
|
|
b.client = c
|
|
|
|
}
|
|
|
|
|
2020-05-02 22:02:02 +00:00
|
|
|
func (b *Backup) Identifier() string {
|
|
|
|
return b.Uuid
|
2020-04-14 05:01:07 +00:00
|
|
|
}
|
|
|
|
|
2021-05-02 19:28:36 +00:00
|
|
|
// Path returns the path for this specific backup.
|
2020-05-02 22:02:02 +00:00
|
|
|
func (b *Backup) Path() string {
|
|
|
|
return path.Join(config.Get().System.BackupDirectory, b.Identifier()+".tar.gz")
|
|
|
|
}
|
|
|
|
|
2021-05-02 19:28:36 +00:00
|
|
|
// Size returns the size of the generated backup.
|
2020-05-02 22:02:02 +00:00
|
|
|
func (b *Backup) Size() (int64, error) {
|
|
|
|
st, err := os.Stat(b.Path())
|
|
|
|
if err != nil {
|
2020-11-28 23:57:10 +00:00
|
|
|
return 0, err
|
2020-04-14 05:01:07 +00:00
|
|
|
}
|
2020-05-02 22:02:02 +00:00
|
|
|
|
|
|
|
return st.Size(), nil
|
2020-04-26 23:21:58 +00:00
|
|
|
}
|
2020-05-02 22:02:02 +00:00
|
|
|
|
2021-05-02 19:28:36 +00:00
|
|
|
// Checksum returns the SHA256 checksum of a backup.
|
2020-05-02 22:02:02 +00:00
|
|
|
func (b *Backup) Checksum() ([]byte, error) {
|
2020-08-24 00:34:48 +00:00
|
|
|
h := sha1.New()
|
2020-05-02 22:02:02 +00:00
|
|
|
|
|
|
|
f, err := os.Open(b.Path())
|
|
|
|
if err != nil {
|
2020-11-28 23:57:10 +00:00
|
|
|
return nil, err
|
2020-05-02 22:02:02 +00:00
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
2020-08-24 00:18:40 +00:00
|
|
|
buf := make([]byte, 1024*4)
|
|
|
|
if _, err := io.CopyBuffer(h, f, buf); err != nil {
|
|
|
|
return nil, err
|
2020-05-02 22:02:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return h.Sum(nil), nil
|
|
|
|
}
|
|
|
|
|
2021-05-02 19:28:36 +00:00
|
|
|
// Details returns both the checksum and size of the archive currently stored on
|
|
|
|
// the disk to the caller.
|
2022-09-26 17:14:57 +00:00
|
|
|
func (b *Backup) Details(ctx context.Context, parts []remote.BackupPart) (*ArchiveDetails, error) {
|
|
|
|
ad := ArchiveDetails{ChecksumType: "sha1", Parts: parts}
|
2021-05-02 19:28:36 +00:00
|
|
|
g, ctx := errgroup.WithContext(ctx)
|
2020-12-27 19:54:18 +00:00
|
|
|
|
2021-05-02 19:28:36 +00:00
|
|
|
g.Go(func() error {
|
2020-05-02 22:02:02 +00:00
|
|
|
resp, err := b.Checksum()
|
|
|
|
if err != nil {
|
2021-05-02 19:28:36 +00:00
|
|
|
return err
|
2020-05-02 22:02:02 +00:00
|
|
|
}
|
2021-05-02 19:28:36 +00:00
|
|
|
ad.Checksum = hex.EncodeToString(resp)
|
|
|
|
return nil
|
|
|
|
})
|
2020-05-02 22:02:02 +00:00
|
|
|
|
2021-05-02 19:28:36 +00:00
|
|
|
g.Go(func() error {
|
|
|
|
s, err := b.Size()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2020-05-02 22:02:02 +00:00
|
|
|
}
|
2021-05-02 19:28:36 +00:00
|
|
|
ad.Size = s
|
|
|
|
return nil
|
|
|
|
})
|
2020-05-02 22:02:02 +00:00
|
|
|
|
2021-05-02 19:28:36 +00:00
|
|
|
if err := g.Wait(); err != nil {
|
|
|
|
return nil, errors.WithStackDepth(err, 1)
|
2020-05-02 22:02:02 +00:00
|
|
|
}
|
2021-05-02 19:28:36 +00:00
|
|
|
return &ad, nil
|
2020-05-02 22:02:02 +00:00
|
|
|
}
|
|
|
|
|
2020-12-25 19:52:57 +00:00
|
|
|
func (b *Backup) Ignored() string {
|
|
|
|
return b.Ignore
|
2020-08-24 00:18:40 +00:00
|
|
|
}
|
2020-12-28 00:16:40 +00:00
|
|
|
|
|
|
|
// Returns a logger instance for this backup with the additional context fields
|
|
|
|
// assigned to the output.
|
|
|
|
func (b *Backup) log() *log.Entry {
|
|
|
|
l := log.WithField("backup", b.Identifier()).WithField("adapter", b.adapter)
|
|
|
|
for k, v := range b.logContext {
|
|
|
|
l = l.WithField(k, v)
|
|
|
|
}
|
|
|
|
return l
|
|
|
|
}
|
2021-05-02 19:28:36 +00:00
|
|
|
|
|
|
|
type ArchiveDetails struct {
|
2022-09-26 17:14:57 +00:00
|
|
|
Checksum string `json:"checksum"`
|
|
|
|
ChecksumType string `json:"checksum_type"`
|
|
|
|
Size int64 `json:"size"`
|
|
|
|
Parts []remote.BackupPart `json:"parts"`
|
2021-05-02 19:28:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ToRequest returns a request object.
|
|
|
|
func (ad *ArchiveDetails) ToRequest(successful bool) remote.BackupRequest {
|
|
|
|
return remote.BackupRequest{
|
|
|
|
Checksum: ad.Checksum,
|
|
|
|
ChecksumType: ad.ChecksumType,
|
|
|
|
Size: ad.Size,
|
|
|
|
Successful: successful,
|
2022-09-26 17:14:57 +00:00
|
|
|
Parts: ad.Parts,
|
2021-05-02 19:28:36 +00:00
|
|
|
}
|
2021-08-02 21:07:00 +00:00
|
|
|
}
|