* EE-319: backup endpoint (#193) * feat(backup): * add an orbiter to block writes while backup * add backup handler * add an ability to tar.gz a dir * add aes encryption support * EE-320: restore endpoint (#196) * feat(backup): * add restore handler * re-init system state after restore * feat(backup): Update server to respect readonly lock (#199) * feat(backup): EE-322 Add backup and restore screen (#198) Co-authored-by: Simon Meng <simon.meng@portainer.io> * name archive as portainer-backup_yyyy-mm-dd_hh-mm-ss * backup custom templates and edge jobs * restart http and proxy servers after restore to re-init internal state * feat(backup): EE-322 hide password field if password protect toggle is off * feat(backup): EE-322 add tooltip for password field of restore backup * feat(backup): EE-322 wait for backend restart after restoring * Shutdown background go-routines * changed restore err message when cannot extract * fix: symlinks are ignored from backups * replace single admin check with a restartable monitor (#238) * clean log Co-authored-by: Maxime Bajeux <max.bajeux@gmail.com> Co-authored-by: cong meng <mcpacino@gmail.com> Co-authored-by: Simon Meng <simon.meng@portainer.io>
69 lines
1.4 KiB
Go
69 lines
1.4 KiB
Go
package backup
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
func copyPath(path string, toDir string) error {
|
|
info, err := os.Stat(path)
|
|
if err != nil && errors.Is(err, os.ErrNotExist) {
|
|
// skip copy if file does not exist
|
|
return nil
|
|
}
|
|
|
|
if !info.IsDir() {
|
|
destination := filepath.Join(toDir, info.Name())
|
|
return copyFile(path, destination)
|
|
}
|
|
|
|
return copyDir(path, toDir)
|
|
}
|
|
|
|
func copyDir(fromDir, toDir string) error {
|
|
cleanedSourcePath := filepath.Clean(fromDir)
|
|
parentDirectory := filepath.Dir(cleanedSourcePath)
|
|
err := filepath.Walk(cleanedSourcePath, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
destination := filepath.Join(toDir, strings.TrimPrefix(path, parentDirectory))
|
|
if info.IsDir() {
|
|
return nil // skip directory creations
|
|
}
|
|
|
|
if info.Mode()&os.ModeSymlink != 0 { // entry is a symlink
|
|
return nil // don't copy symlinks
|
|
}
|
|
|
|
return copyFile(path, destination)
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
// copies regular a file from src to dst
|
|
func copyFile(src, dst string) error {
|
|
from, err := os.Open(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer from.Close()
|
|
|
|
// has to include 'execute' bit, otherwise fails. MkdirAll follows `mkdir -m` restrictions
|
|
if err := os.MkdirAll(filepath.Dir(dst), 0744); err != nil {
|
|
return err
|
|
}
|
|
to, err := os.Create(dst)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer to.Close()
|
|
|
|
_, err = io.Copy(to, from)
|
|
return err
|
|
}
|