37baabe134
* EE-384: add endpoint to set auto backup (#224) * EE-383: add endpoint to fetch backup settings (#231) * add get backup settings handler * add api docs desc * EE-382: restore from s3 (#233) * EE-381: add GET backup status handler (#234) * EE-385: Add S3 backup execute handler (#237) * add s3 backup execute handler * refactories inside `./api/backup/backup_scheduler.go` and `./api/backup/backup_scheduler.go` * fix tests * EE-375: added backup to S3 form * EE-376: added restore from S3 form * EE-377: Update Home screen to display last backup run status * update backup service with back end endpoints. * restart admin monitor during s3 restores * use go 1.13 * go 1.13 compatibility * EE-375: added cron-validator lib * EE-375: using enum to compare form types * EE-375: validate cron rule field * try fix windows build * EE-375 EE-376 backup and restore forms validation changes * fix(autobackup): update autobackup settings validation rules (#260) * fix(autobackup): automate backup to s3 fe update (#261) * EE-292: fixed typo in property. * EE-292: updated auto backup front end validation. * EE-292: updated lib to validate cron rule in front end * fix dependencies * bumped libcompose version Co-authored-by: Hui <arris_li@hotmail.com> Co-authored-by: Felix Han <felix.han@portainer.io> Co-authored-by: fhanportainer <79428273+fhanportainer@users.noreply.github.com>
113 lines
3.2 KiB
Go
113 lines
3.2 KiB
Go
package backup
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
portainer "github.com/portainer/portainer/api"
|
|
"github.com/portainer/portainer/api/archive"
|
|
"github.com/portainer/portainer/api/crypto"
|
|
"github.com/portainer/portainer/api/http/offlinegate"
|
|
"github.com/portainer/portainer/api/s3"
|
|
)
|
|
|
|
const rwxr__r__ os.FileMode = 0744
|
|
|
|
var filesToBackup = []string{"compose", "config.json", "custom_templates", "edge_jobs", "edge_stacks", "extensions", "portainer.key", "portainer.pub", "tls"}
|
|
|
|
func BackupToS3(settings portainer.S3BackupSettings, gate *offlinegate.OfflineGate, datastore portainer.DataStore, filestorePath string) error {
|
|
archivePath, err := CreateBackupArchive(settings.Password, gate, datastore, filestorePath)
|
|
if err != nil {
|
|
log.Printf("[ERROR] failed to backup: %s \n", err)
|
|
return err
|
|
}
|
|
archiveReader, err := os.Open(archivePath)
|
|
if err != nil {
|
|
log.Println("[ERROR] failed to open backup file")
|
|
return err
|
|
}
|
|
defer os.RemoveAll(filepath.Dir(archivePath))
|
|
|
|
archiveName := fmt.Sprintf("portainer-backup_%s", filepath.Base(archivePath))
|
|
|
|
s3session, err := s3.NewSession(settings.Region, settings.AccessKeyID, settings.SecretAccessKey)
|
|
if err != nil {
|
|
log.Printf("[ERROR] %s \n", err)
|
|
return err
|
|
}
|
|
if err := s3.Upload(s3session, archiveReader, settings.BucketName, archiveName); err != nil {
|
|
log.Printf("[ERROR] failed to upload backup to S3: %s \n", err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Creates a tar.gz system archive and encrypts it if password is not empty. Returns a path to the archive file.
|
|
func CreateBackupArchive(password string, gate *offlinegate.OfflineGate, datastore portainer.DataStore, filestorePath string) (string, error) {
|
|
unlock := gate.Lock()
|
|
defer unlock()
|
|
|
|
backupDirPath := filepath.Join(filestorePath, "backup", time.Now().Format("2006-01-02_15-04-05"))
|
|
if err := os.MkdirAll(backupDirPath, rwxr__r__); err != nil {
|
|
return "", errors.Wrap(err, "Failed to create backup dir")
|
|
}
|
|
|
|
if err := backupDb(backupDirPath, datastore); err != nil {
|
|
return "", errors.Wrap(err, "Failed to backup database")
|
|
}
|
|
|
|
for _, filename := range filesToBackup {
|
|
err := copyPath(filepath.Join(filestorePath, filename), backupDirPath)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "Failed to create backup file")
|
|
}
|
|
}
|
|
|
|
archivePath, err := archive.TarGzDir(backupDirPath)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "Failed to make an archive")
|
|
}
|
|
|
|
if password != "" {
|
|
archivePath, err = encrypt(archivePath, password)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "Failed to encrypt backup with the password")
|
|
}
|
|
}
|
|
|
|
return archivePath, nil
|
|
}
|
|
|
|
func backupDb(backupDirPath string, datastore portainer.DataStore) error {
|
|
backupWriter, err := os.Create(filepath.Join(backupDirPath, "portainer.db"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = datastore.BackupTo(backupWriter); err != nil {
|
|
return err
|
|
}
|
|
return backupWriter.Close()
|
|
}
|
|
|
|
func encrypt(path string, passphrase string) (string, error) {
|
|
in, err := os.Open(path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer in.Close()
|
|
|
|
outFileName := fmt.Sprintf("%s.encrypted", path)
|
|
out, err := os.Create(outFileName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
err = crypto.AesEncrypt(in, out, []byte(passphrase))
|
|
|
|
return outFileName, err
|
|
}
|