mirror of https://github.com/portainer/portainer
111 lines
2.7 KiB
Go
111 lines
2.7 KiB
Go
package backup
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/portainer/portainer/api/archive"
|
|
"github.com/portainer/portainer/api/crypto"
|
|
"github.com/portainer/portainer/api/dataservices"
|
|
"github.com/portainer/portainer/api/filesystem"
|
|
"github.com/portainer/portainer/api/http/offlinegate"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
const rwxr__r__ os.FileMode = 0744
|
|
|
|
var filesToBackup = []string{
|
|
"certs",
|
|
"compose",
|
|
"config.json",
|
|
"custom_templates",
|
|
"edge_jobs",
|
|
"edge_stacks",
|
|
"extensions",
|
|
"portainer.key",
|
|
"portainer.pub",
|
|
"tls",
|
|
}
|
|
|
|
// 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 dataservices.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")
|
|
}
|
|
|
|
{
|
|
// new export
|
|
exportFilename := path.Join(backupDirPath, fmt.Sprintf("export-%d.json", time.Now().Unix()))
|
|
|
|
err := datastore.Export(exportFilename)
|
|
if err != nil {
|
|
log.Error().Err(err).Str("filename", exportFilename).Msg("failed to export")
|
|
} else {
|
|
log.Debug().Str("filename", exportFilename).Msg("file exported")
|
|
}
|
|
}
|
|
|
|
if err := backupDb(backupDirPath, datastore); err != nil {
|
|
return "", errors.Wrap(err, "Failed to backup database")
|
|
}
|
|
|
|
for _, filename := range filesToBackup {
|
|
err := filesystem.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 dataservices.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
|
|
}
|