wings/server/backup/backup.go
Ethan Alicea 4390bad36b
Please enter the commit message for your changes. Lines starting
with '#' will be ignored, and an empty message aborts the commit.

 Author:    Ethan Alicea <64653625+Tech-Gamer@users.noreply.github.com>

 On branch develop
 Your branch is up to date with 'origin/develop'.

 Changes to be committed:
	modified:   .github/workflows/push.yaml
	modified:   .github/workflows/release.yaml
	modified:   CHANGELOG.md
	modified:   Dockerfile
	modified:   Makefile
	modified:   README.md
	modified:   cmd/configure.go
	modified:   cmd/diagnostics.go
	modified:   cmd/root.go
	modified:   config/config.go
	modified:   environment/allocations.go
	modified:   environment/docker.go
	modified:   environment/docker/api.go
	modified:   environment/docker/container.go
	modified:   environment/docker/environment.go
	modified:   environment/docker/power.go
	modified:   environment/docker/stats.go
	modified:   environment/environment.go
	modified:   environment/settings.go
	modified:   events/events.go
	modified:   go.mod
	modified:   internal/cron/activity_cron.go
	modified:   internal/cron/cron.go
	modified:   internal/cron/sftp_cron.go
	modified:   internal/database/database.go
	modified:   internal/progress/progress.go
	modified:   internal/progress/progress_test.go
	modified:   loggers/cli/cli.go
	new file:   oryxBuildBinary
	modified:   parser/parser.go
	modified:   remote/http.go
	modified:   remote/servers.go
	modified:   remote/types.go
	modified:   router/downloader/downloader.go
	modified:   router/middleware.go
	modified:   router/middleware/middleware.go
	modified:   router/middleware/request_error.go
	modified:   router/router.go
	modified:   router/router_download.go
	modified:   router/router_server.go
	modified:   router/router_server_backup.go
	modified:   router/router_server_files.go
	modified:   router/router_server_transfer.go
	modified:   router/router_server_ws.go
	modified:   router/router_system.go
	modified:   router/router_transfer.go
	modified:   router/tokens/parser.go
	modified:   router/websocket/listeners.go
	modified:   router/websocket/websocket.go
	modified:   server/activity.go
	modified:   server/backup.go
	modified:   server/backup/backup.go
	modified:   server/backup/backup_local.go
	modified:   server/backup/backup_s3.go
	modified:   server/configuration.go
	modified:   server/console.go
	modified:   server/crash.go
	modified:   server/events.go
	modified:   server/filesystem/archive.go
	modified:   server/filesystem/filesystem.go
	modified:   server/filesystem/filesystem_test.go
	modified:   server/install.go
	modified:   server/installer/installer.go
	modified:   server/listeners.go
	modified:   server/manager.go
	modified:   server/mounts.go
	modified:   server/power.go
	modified:   server/power_test.go
	modified:   server/resources.go
	modified:   server/server.go
	modified:   server/transfer/archive.go
	modified:   server/transfer/source.go
	modified:   server/transfer/transfer.go
	modified:   server/update.go
	modified:   sftp/event.go
	modified:   sftp/handler.go
	modified:   sftp/server.go
	modified:   wings.go
2023-09-11 17:22:09 +00:00

186 lines
4.8 KiB
Go

package backup
import (
"context"
"crypto/sha1"
"encoding/hex"
"io"
"io/fs"
"os"
"path"
"emperror.dev/errors"
"github.com/apex/log"
"github.com/mholt/archiver/v4"
"golang.org/x/sync/errgroup"
"github.com/Tech-Gamer/nwy-wings/config"
"github.com/Tech-Gamer/nwy-wings/remote"
)
var format = archiver.CompressedArchive{
Compression: archiver.Gz{},
Archival: archiver.Tar{},
}
type AdapterType string
const (
LocalBackupAdapter AdapterType = "wings"
S3BackupAdapter AdapterType = "s3"
)
// RestoreCallback is a generic restoration callback that exists for both local
// and remote backups allowing the files to be restored.
type RestoreCallback func(file string, info fs.FileInfo, r io.ReadCloser) error
// noinspection GoNameStartsWithPackageName
type BackupInterface interface {
// SetClient sets the API request client on the backup interface.
SetClient(remote.Client)
// Identifier returns the UUID of this backup as tracked by the panel
// instance.
Identifier() string
// WithLogContext attaches additional context to the log output for this
// backup.
WithLogContext(map[string]interface{})
// Generate creates a backup in whatever the configured source for the
// specific implementation is.
Generate(context.Context, string, string) (*ArchiveDetails, error)
// Ignored returns the ignored files for this backup instance.
Ignored() string
// Checksum returns a SHA1 checksum for the generated backup.
Checksum() ([]byte, error)
// Size returns the size of the generated backup.
Size() (int64, error)
// 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.
Path() string
// Details returns details about the archive.
Details(context.Context, []remote.BackupPart) (*ArchiveDetails, error)
// Remove removes a backup file.
Remove() error
// 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.
Restore(context.Context, io.Reader, RestoreCallback) error
}
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{}
}
func (b *Backup) SetClient(c remote.Client) {
b.client = c
}
func (b *Backup) Identifier() string {
return b.Uuid
}
// Path returns the path for this specific backup.
func (b *Backup) Path() string {
return path.Join(config.Get().System.BackupDirectory, b.Identifier()+".tar.gz")
}
// Size returns the size of the generated backup.
func (b *Backup) Size() (int64, error) {
st, err := os.Stat(b.Path())
if err != nil {
return 0, err
}
return st.Size(), nil
}
// Checksum returns the SHA256 checksum of a backup.
func (b *Backup) Checksum() ([]byte, error) {
h := sha1.New()
f, err := os.Open(b.Path())
if err != nil {
return nil, err
}
defer f.Close()
buf := make([]byte, 1024*4)
if _, err := io.CopyBuffer(h, f, buf); err != nil {
return nil, err
}
return h.Sum(nil), nil
}
// Details returns both the checksum and size of the archive currently stored on
// the disk to the caller.
func (b *Backup) Details(ctx context.Context, parts []remote.BackupPart) (*ArchiveDetails, error) {
ad := ArchiveDetails{ChecksumType: "sha1", Parts: parts}
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error {
resp, err := b.Checksum()
if err != nil {
return err
}
ad.Checksum = hex.EncodeToString(resp)
return nil
})
g.Go(func() error {
s, err := b.Size()
if err != nil {
return err
}
ad.Size = s
return nil
})
if err := g.Wait(); err != nil {
return nil, errors.WithStackDepth(err, 1)
}
return &ad, nil
}
func (b *Backup) Ignored() string {
return b.Ignore
}
// 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
}
type ArchiveDetails struct {
Checksum string `json:"checksum"`
ChecksumType string `json:"checksum_type"`
Size int64 `json:"size"`
Parts []remote.BackupPart `json:"parts"`
}
// 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,
Parts: ad.Parts,
}
}