1package action 2 3import ( 4 "context" 5 "errors" 6 "fmt" 7 8 "github.com/gopasspw/gopass/internal/tree" 9 10 "github.com/gopasspw/gopass/internal/diff" 11 "github.com/gopasspw/gopass/internal/notify" 12 "github.com/gopasspw/gopass/internal/out" 13 "github.com/gopasspw/gopass/internal/store" 14 "github.com/gopasspw/gopass/pkg/ctxutil" 15 "github.com/gopasspw/gopass/pkg/debug" 16 17 "github.com/fatih/color" 18 "github.com/urfave/cli/v2" 19) 20 21// Sync all stores with their remotes 22func (s *Action) Sync(c *cli.Context) error { 23 return s.sync(ctxutil.WithGlobalFlags(c), c.String("store")) 24} 25 26func (s *Action) sync(ctx context.Context, store string) error { 27 out.Printf(ctx, " Syncing with all remotes ...") 28 29 numEntries := 0 30 if l, err := s.Store.Tree(ctx); err == nil { 31 numEntries = len(l.List(tree.INF)) 32 } 33 numMPs := 0 34 35 mps := s.Store.MountPoints() 36 mps = append([]string{""}, mps...) 37 38 // sync all stores (root and all mounted sub stores) 39 for _, mp := range mps { 40 if store != "" { 41 if store != "root" && mp != store { 42 continue 43 } 44 if store == "root" && mp != "" { 45 continue 46 } 47 } 48 49 numMPs++ 50 _ = s.syncMount(ctx, mp) 51 } 52 out.OKf(ctx, "All done") 53 54 // Calculate number of changed entries. 55 // This is a rough estimate as additions and deletions 56 // might cancel each other out. 57 if l, err := s.Store.Tree(ctx); err == nil { 58 numEntries = len(l.List(tree.INF)) - numEntries 59 } 60 diff := "" 61 if numEntries > 0 { 62 diff = fmt.Sprintf(" Added %d entries", numEntries) 63 } else if numEntries < 0 { 64 diff = fmt.Sprintf(" Removed %d entries", -1*numEntries) 65 } 66 _ = notify.Notify(ctx, "gopass - sync", fmt.Sprintf("Finished. Synced %d remotes.%s", numMPs, diff)) 67 68 return nil 69} 70 71// syncMount syncs a single mount 72func (s *Action) syncMount(ctx context.Context, mp string) error { 73 ctxno := out.WithNewline(ctx, false) 74 name := mp 75 if mp == "" { 76 name = "<root>" 77 } 78 out.Printf(ctxno, color.GreenString("[%s] ", name)) 79 80 sub, err := s.Store.GetSubStore(mp) 81 if err != nil { 82 out.Errorf(ctx, "Failed to get sub store %q: %s", name, err) 83 return fmt.Errorf("failed to get sub stores (%s)", err) 84 } 85 86 if sub == nil { 87 out.Errorf(ctx, "Failed to get sub stores '%s: nil'", name) 88 return fmt.Errorf("failed to get sub stores (nil)") 89 } 90 91 l, err := sub.List(ctx, "") 92 if err != nil { 93 out.Errorf(ctx, "Failed to list store: %s", err) 94 } 95 96 // TODO: Remove this hard coded check 97 if sub.Storage().Name() == "fs" { 98 out.Printf(ctxno, "\n WARNING: Mount uses Storage backend 'fs'. Not syncing!\n") 99 } else { 100 out.Printf(ctxno, "\n "+color.GreenString("git pull and push ... ")) 101 if err := sub.Storage().Push(ctx, "", ""); err != nil { 102 if errors.Is(err, store.ErrGitNoRemote) { 103 out.Printf(ctx, "Skipped (no remote)") 104 debug.Log("Failed to push %q to its remote: %s", name, err) 105 return err 106 } 107 108 out.Errorf(ctx, "Failed to push %q to its remote: %s", name, err) 109 return err 110 } 111 out.Printf(ctxno, color.GreenString("OK")) 112 113 ln, err := sub.List(ctx, "") 114 if err != nil { 115 out.Errorf(ctx, "Failed to list store: %s", err) 116 } 117 118 added, removed := diff.List(l, ln) 119 debug.Log("diff - added: %d - removed: %d", added, removed) 120 if added > 0 { 121 out.Printf(ctxno, color.GreenString(" (Added %d entries)", added)) 122 } 123 if removed > 0 { 124 out.Printf(ctxno, color.GreenString(" (Removed %d entries)", removed)) 125 } 126 if added < 1 && removed < 1 { 127 out.Printf(ctxno, color.GreenString(" (no changes)")) 128 } 129 } 130 131 debug.Log("Syncing Mount %s. Exportkeys: %t", mp, ctxutil.IsExportKeys(ctx)) 132 var exported bool 133 if ctxutil.IsExportKeys(ctx) { 134 // import keys 135 out.Printf(ctxno, "\n "+color.GreenString("importing missing keys ... ")) 136 if err := sub.ImportMissingPublicKeys(ctx); err != nil { 137 out.Errorf(ctx, "Failed to import missing public keys for %q: %s", name, err) 138 return err 139 } 140 out.Printf(ctxno, color.GreenString("OK")) 141 142 // export keys 143 out.Printf(ctxno, "\n "+color.GreenString("exporting missing keys ... ")) 144 rs, err := sub.GetRecipients(ctx, "") 145 if err != nil { 146 out.Errorf(ctx, "Failed to load recipients for %q: %s", name, err) 147 return err 148 } 149 exported, err = sub.ExportMissingPublicKeys(ctx, rs) 150 if err != nil { 151 out.Errorf(ctx, "Failed to export missing public keys for %q: %s", name, err) 152 return err 153 } 154 } 155 156 // only run second push if we did export any keys 157 if exported { 158 if err := sub.Storage().Push(ctx, "", ""); err != nil { 159 out.Errorf(ctx, "Failed to push %q to its remote: %s", name, err) 160 return err 161 } 162 out.Printf(ctxno, color.GreenString("OK")) 163 } else { 164 out.Printf(ctxno, color.GreenString("nothing to do")) 165 } 166 out.Printf(ctx, "\n "+color.GreenString("done")) 167 return nil 168} 169