From f5537e7a0e5b15545a866a669169f6e5d9e3c0c3 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sat, 9 May 2015 21:50:10 +0200 Subject: [PATCH 1/3] Refactor configuration of cache dir and repository --- cache.go | 14 ++++++++++---- cache_test.go | 2 +- cmd/restic/cmd_cache.go | 2 +- cmd/restic/cmd_key.go | 41 +++++++++++++++++++++++++---------------- cmd/restic/main.go | 36 ++++++++++++++++++++---------------- test/backend.go | 5 ----- 6 files changed, 57 insertions(+), 43 deletions(-) diff --git a/cache.go b/cache.go index 089c3f257..4cdde46f0 100644 --- a/cache.go +++ b/cache.go @@ -18,10 +18,16 @@ type Cache struct { base string } -func NewCache(repo *repository.Repository) (*Cache, error) { - cacheDir, err := getCacheDir() - if err != nil { - return nil, err +// NewCache returns a new cache at cacheDir. If it is the empty string, the +// default cache location is chosen. +func NewCache(repo *repository.Repository, cacheDir string) (*Cache, error) { + var err error + + if cacheDir == "" { + cacheDir, err = getCacheDir() + if err != nil { + return nil, err + } } basedir := filepath.Join(cacheDir, repo.Config.ID) diff --git a/cache_test.go b/cache_test.go index 47f5d9ad9..6f342dc39 100644 --- a/cache_test.go +++ b/cache_test.go @@ -11,7 +11,7 @@ func TestCache(t *testing.T) { repo := SetupRepo(t) defer TeardownRepo(t, repo) - _, err := restic.NewCache(repo) + _, err := restic.NewCache(repo, "") OK(t, err) arch := restic.NewArchiver(repo) diff --git a/cmd/restic/cmd_cache.go b/cmd/restic/cmd_cache.go index c6fafd786..2dc2b73b7 100644 --- a/cmd/restic/cmd_cache.go +++ b/cmd/restic/cmd_cache.go @@ -32,7 +32,7 @@ func (cmd CmdCache) Execute(args []string) error { return err } - cache, err := restic.NewCache(s) + cache, err := restic.NewCache(s, opts.CacheDir) if err != nil { return err } diff --git a/cmd/restic/cmd_key.go b/cmd/restic/cmd_key.go index 344d16750..86e390eef 100644 --- a/cmd/restic/cmd_key.go +++ b/cmd/restic/cmd_key.go @@ -56,15 +56,28 @@ func listKeys(s *repository.Repository) error { return nil } -func addKey(s *repository.Repository) error { - pw := readPassword("RESTIC_NEWPASSWORD", "enter password for new key: ") - pw2 := readPassword("RESTIC_NEWPASSWORD", "enter password again: ") +func getNewPassword() (string, error) { + newPassword := os.Getenv("RESTIC_NEWPASSWORD") - if pw != pw2 { - return errors.New("passwords do not match") + if newPassword == "" { + newPassword = readPassword("enter password for new key: ") + newPassword2 := readPassword("enter password again: ") + + if newPassword != newPassword2 { + return "", errors.New("passwords do not match") + } } - id, err := repository.AddKey(s, pw, s.Key()) + return newPassword, nil +} + +func addKey(repo *repository.Repository) error { + newPassword, err := getNewPassword() + if err != nil { + return err + } + + id, err := repository.AddKey(repo, newPassword, repo.Key()) if err != nil { return fmt.Errorf("creating new key failed: %v\n", err) } @@ -88,22 +101,18 @@ func deleteKey(repo *repository.Repository, name string) error { return nil } -func changePassword(s *repository.Repository) error { - pw := readPassword("RESTIC_NEWPASSWORD", "enter password for new key: ") - pw2 := readPassword("RESTIC_NEWPASSWORD", "enter password again: ") - - if pw != pw2 { - return errors.New("passwords do not match") +func changePassword(repo *repository.Repository) error { + newPassword, err := getNewPassword() + if err != nil { + return err } - // add new key - id, err := repository.AddKey(s, pw, s.Key()) + id, err := repository.AddKey(repo, newPassword, repo.Key()) if err != nil { return fmt.Errorf("creating new key failed: %v\n", err) } - // remove old key - err = s.Backend().Remove(backend.Key, s.KeyName()) + err = repo.Backend().Remove(backend.Key, repo.KeyName()) if err != nil { return err } diff --git a/cmd/restic/main.go b/cmd/restic/main.go index 805caf236..5dbf9eb13 100644 --- a/cmd/restic/main.go +++ b/cmd/restic/main.go @@ -20,7 +20,10 @@ import ( var version = "compiled manually" var opts struct { - Repo string `short:"r" long:"repo" description:"Repository directory to backup to/restore from"` + Repo string `short:"r" long:"repo" description:"Repository directory to backup to/restore from"` + CacheDir string ` long:"cache-dir" description:"Directory to use as a local cache"` + + password string } var parser = flags.NewParser(&opts, flags.Default) @@ -33,15 +36,7 @@ func errx(code int, format string, data ...interface{}) { os.Exit(code) } -func readPassword(env string, prompt string) string { - if env != "" { - p := os.Getenv(env) - - if p != "" { - return p - } - } - +func readPassword(prompt string) string { fmt.Fprint(os.Stderr, prompt) pw, err := terminal.ReadPassword(int(os.Stdin.Fd())) if err != nil { @@ -59,11 +54,15 @@ func (cmd CmdInit) Execute(args []string) error { return errors.New("Please specify repository location (-r)") } - pw := readPassword("RESTIC_PASSWORD", "enter password for new backend: ") - pw2 := readPassword("RESTIC_PASSWORD", "enter password again: ") + if opts.password == "" { + pw := readPassword("enter password for new backend: ") + pw2 := readPassword("enter password again: ") - if pw != pw2 { - errx(1, "passwords do not match") + if pw != pw2 { + errx(1, "passwords do not match") + } + + opts.password = pw } be, err := create(opts.Repo) @@ -73,7 +72,7 @@ func (cmd CmdInit) Execute(args []string) error { } s := repository.New(be) - err = s.Init(pw) + err = s.Init(opts.password) if err != nil { fmt.Fprintf(os.Stderr, "creating key in backend at %s failed: %v\n", opts.Repo, err) os.Exit(1) @@ -145,7 +144,11 @@ func OpenRepo() (*repository.Repository, error) { s := repository.New(be) - err = s.SearchKey(readPassword("RESTIC_PASSWORD", "enter password for repository: ")) + if opts.password == "" { + opts.password = readPassword("enter password for repository: ") + } + + err = s.SearchKey(opts.password) if err != nil { return nil, fmt.Errorf("unable to open repo: %v", err) } @@ -170,6 +173,7 @@ func main() { // defer profile.Start(profile.MemProfileRate(100000), profile.ProfilePath(".")).Stop() // defer profile.Start(profile.CPUProfile, profile.ProfilePath(".")).Stop() opts.Repo = os.Getenv("RESTIC_REPOSITORY") + opts.password = os.Getenv("RESTIC_PASSWORD") debug.Log("restic", "main %#v", os.Args) diff --git a/test/backend.go b/test/backend.go index 173ecee9d..f1374d14d 100644 --- a/test/backend.go +++ b/test/backend.go @@ -3,7 +3,6 @@ package test_helper import ( "flag" "io/ioutil" - "os" "path/filepath" "testing" @@ -25,10 +24,6 @@ func SetupRepo(t testing.TB) *repository.Repository { b, err := local.Create(filepath.Join(tempdir, "repo")) OK(t, err) - // set cache dir below temp dir - err = os.Setenv("RESTIC_CACHE", filepath.Join(tempdir, "cache")) - OK(t, err) - repo := repository.New(b) OK(t, repo.Init(*TestPassword)) return repo From fd80499954462853834f1e9ff267f0af808075ea Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sat, 9 May 2015 22:06:08 +0200 Subject: [PATCH 2/3] Refactor terminal recognition, add --quiet parameter --- cmd/restic/cmd_backup.go | 17 ++++++---------- cmd/restic/main.go | 42 ++++++++++++++++++++++++++++++++++------ 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/cmd/restic/cmd_backup.go b/cmd/restic/cmd_backup.go index 08a5ff46d..96e4f01eb 100644 --- a/cmd/restic/cmd_backup.go +++ b/cmd/restic/cmd_backup.go @@ -97,7 +97,7 @@ func (cmd CmdBackup) Usage() string { } func newScanProgress() *restic.Progress { - if !terminal.IsTerminal(int(os.Stdout.Fd())) { + if disableProgress() { return nil } @@ -113,7 +113,7 @@ func newScanProgress() *restic.Progress { } func newArchiveProgress(todo restic.Stat) *restic.Progress { - if !terminal.IsTerminal(int(os.Stdout.Fd())) { + if disableProgress() { return nil } @@ -194,7 +194,7 @@ func (cmd CmdBackup) Execute(args []string) error { return fmt.Errorf("invalid id %q: %v", cmd.Parent, err) } - fmt.Printf("found parent snapshot %v\n", parentSnapshotID) + verbosePrintf("found parent snapshot %v\n", parentSnapshotID.Str()) } // Find last snapshot to set it as parent, if not already set @@ -226,11 +226,11 @@ func (cmd CmdBackup) Execute(args []string) error { } if parentSnapshotID != nil { - fmt.Printf("using parent snapshot %v\n", parentSnapshotID) + verbosePrintf("using parent snapshot %v\n", parentSnapshotID) } } - fmt.Printf("scan %v\n", target) + verbosePrintf("scan %v\n", target) stat, err := restic.Scan(target, newScanProgress()) @@ -252,12 +252,7 @@ func (cmd CmdBackup) Execute(args []string) error { return err } - plen, err := s.PrefixLength(backend.Snapshot) - if err != nil { - return err - } - - fmt.Printf("snapshot %s saved\n", id[:plen/2]) + verbosePrintf("snapshot %s saved\n", id.Str()) return nil } diff --git a/cmd/restic/main.go b/cmd/restic/main.go index 5dbf9eb13..b34755c84 100644 --- a/cmd/restic/main.go +++ b/cmd/restic/main.go @@ -20,8 +20,9 @@ import ( var version = "compiled manually" var opts struct { - Repo string `short:"r" long:"repo" description:"Repository directory to backup to/restore from"` - CacheDir string ` long:"cache-dir" description:"Directory to use as a local cache"` + Repo string `short:"r" long:"repo" description:"Repository directory to backup to/restore from"` + CacheDir string ` long:"cache-dir" description:"Directory to use as a local cache"` + Quiet bool `short:"q" long:"quiet" default:"false" description:"Do not output comprehensive progress report"` password string } @@ -47,6 +48,34 @@ func readPassword(prompt string) string { return string(pw) } +func disableProgress() bool { + if opts.Quiet { + return true + } + + if !terminal.IsTerminal(int(os.Stdout.Fd())) { + return true + } + + return false +} + +func silenceRequested() bool { + if opts.Quiet { + return true + } + + return false +} + +func verbosePrintf(format string, args ...interface{}) { + if silenceRequested() { + return + } + + fmt.Printf(format, args...) +} + type CmdInit struct{} func (cmd CmdInit) Execute(args []string) error { @@ -78,10 +107,11 @@ func (cmd CmdInit) Execute(args []string) error { os.Exit(1) } - fmt.Printf("created restic backend %v at %s\n", s.Config.ID[:10], opts.Repo) - - fmt.Println("Please note that knowledge of your password is required to access the repository.") - fmt.Println("Losing your password means that your data is irrecoverably lost.") + verbosePrintf("created restic backend %v at %s\n", s.Config.ID[:10], opts.Repo) + verbosePrintf("\n") + verbosePrintf("Please note that knowledge of your password is required to access\n") + verbosePrintf("the repository. Losing your password means that your data is\n") + verbosePrintf("irrecoverably lost.\n") return nil } From 9c375ea382de555bd570d6e3ca6098fee4f33e27 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 10 May 2015 01:08:24 +0200 Subject: [PATCH 3/3] Refactor backup a bit --- cmd/restic/cmd_backup.go | 65 +++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/cmd/restic/cmd_backup.go b/cmd/restic/cmd_backup.go index 96e4f01eb..d8f2a3509 100644 --- a/cmd/restic/cmd_backup.go +++ b/cmd/restic/cmd_backup.go @@ -9,6 +9,7 @@ import ( "github.com/restic/restic" "github.com/restic/restic/backend" + "github.com/restic/restic/repository" "golang.org/x/crypto/ssh/terminal" ) @@ -162,6 +163,43 @@ func newArchiveProgress(todo restic.Stat) *restic.Progress { return archiveProgress } +func samePaths(expected, actual []string) bool { + if expected == nil || actual == nil { + return true + } + + if len(expected) != len(actual) { + return false + } + for i := range expected { + if expected[i] != actual[i] { + return false + } + } + + return true +} + +func findLatestSnapshot(repo *repository.Repository, targets []string) (backend.ID, error) { + var ( + latest time.Time + latestID backend.ID + ) + + for snapshotID := range repo.List(backend.Snapshot, make(chan struct{})) { + snapshot, err := restic.LoadSnapshot(repo, snapshotID) + if err != nil { + return nil, fmt.Errorf("Error listing snapshot: %v", err) + } + if snapshot.Time.After(latest) && samePaths(snapshot.Paths, targets) { + latest = snapshot.Time + latestID = snapshotID + } + } + + return latestID, nil +} + func (cmd CmdBackup) Execute(args []string) error { if len(args) == 0 { return fmt.Errorf("wrong number of parameters, Usage: %s", cmd.Usage()) @@ -199,30 +237,9 @@ func (cmd CmdBackup) Execute(args []string) error { // Find last snapshot to set it as parent, if not already set if !cmd.Force && parentSnapshotID == nil { - samePaths := func(expected, actual []string) bool { - if len(expected) != len(actual) { - return false - } - for i := range expected { - if expected[i] != actual[i] { - return false - } - } - return true - } - - var latest time.Time - - for id := range s.List(backend.Snapshot, make(chan struct{})) { - snapshot, err := restic.LoadSnapshot(s, id) - if err != nil { - return fmt.Errorf("Error listing snapshot: %v", err) - } - if snapshot.Time.After(latest) && samePaths(snapshot.Paths, target) { - latest = snapshot.Time - parentSnapshotID = id - } - + parentSnapshotID, err = findLatestSnapshot(s, target) + if err != nil { + return err } if parentSnapshotID != nil {