Dominik Schulz 792f8b07e2
[chore] Initial fixes and added a warning for CryptFS and JJFS (#3270)
These backends are not ready, yet.

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
2025-11-12 21:04:55 +01:00

224 lines
6.5 KiB
Go

package action
import (
"context"
"fmt"
"github.com/gopasspw/gopass/internal/action/exit"
"github.com/gopasspw/gopass/internal/backend"
"github.com/gopasspw/gopass/internal/config"
"github.com/gopasspw/gopass/internal/cui"
"github.com/gopasspw/gopass/internal/out"
"github.com/gopasspw/gopass/pkg/ctxutil"
"github.com/gopasspw/gopass/pkg/debug"
"github.com/gopasspw/gopass/pkg/fsutil"
"github.com/gopasspw/gopass/pkg/termio"
"github.com/urfave/cli/v2"
)
const logo = `
__ _ _ _ _ _ ___ ___
/'_ '\ /'_'\ ( '_'\ /'_' )/',__)/',__)
( (_) |( (_) )| (_) )( (_| |\__, \\__, \
'\__ |'\___/'| ,__/''\__,_)(____/(____/
( )_) | | |
\___/' (_)
`
// IsInitialized returns an error if the store is not properly
// prepared.
func (s *Action) IsInitialized(c *cli.Context) error {
ctx := ctxutil.WithGlobalFlags(c)
inited, err := s.Store.IsInitialized(ctx)
if err != nil {
return exit.Error(exit.Unknown, err, "Failed to initialize store: %s", err)
}
if inited {
debug.Log("Store is fully initialized and ready to go\n\nAll systems go. 🚀\n")
name := c.Args().First()
// setting the mount point here is not enough when we're using the REPL mode
ctx = config.WithMount(ctx, s.Store.MountPoint(name))
s.printReminder(ctx)
if c.Command.Name != "sync" && !c.Bool("nosync") {
_ = s.autoSync(ctx)
}
return nil
}
debug.Log("Store needs to be initialized.\n\nAbort. Abort. Abort. 🚫\n")
if !ctxutil.IsInteractive(ctx) {
return exit.Error(exit.NotInitialized, nil, "password-store is not initialized. Try '%s init'", s.Name)
}
out.Printf(ctx, logo)
out.Printf(ctx, "🌟 Welcome to gopass!")
out.Noticef(ctx, "No existing configuration found.")
contSetup, err := termio.AskForBool(ctx, "❓ Do you want to continue to setup?", false)
if err != nil {
return err
}
if contSetup {
return s.Setup(c)
}
out.Printf(ctx, "☝ Please run 'gopass setup'")
return exit.Error(exit.NotInitialized, err, "not initialized")
}
// Init a new password store with a first gpg id.
func (s *Action) Init(c *cli.Context) error {
ctx := ctxutil.WithGlobalFlags(c)
path := c.String("path")
alias := c.String("store")
ctx, err := initParseContext(ctx, c)
if err != nil {
return err
}
out.Printf(ctx, "🍭 Initializing a new password store ...")
if name := termio.DetectName(c.Context, c); name != "" {
ctx = ctxutil.WithUsername(ctx, name)
}
if email := termio.DetectEmail(c.Context, c); email != "" {
ctx = ctxutil.WithEmail(ctx, email)
}
inited, err := s.Store.IsInitialized(ctx)
if err != nil {
return exit.Error(exit.Unknown, err, "Failed to initialized store: %s", err)
}
if inited {
out.Errorf(ctx, "Store is already initialized!")
}
if err := s.init(ctx, alias, path, c.Args().Slice()...); err != nil {
return exit.Error(exit.Unknown, err, "Failed to initialize store: %s", err)
}
return nil
}
func initParseContext(ctx context.Context, c *cli.Context) (context.Context, error) {
if c.IsSet("crypto") {
var err error
ctx, err = backend.WithCryptoBackendString(ctx, c.String("crypto"))
if err != nil {
return ctx, exit.Error(exit.Unknown, err, "Failed to set crypto backend: %s", err)
}
}
if c.IsSet("storage") {
var err error
ctx, err = backend.WithStorageBackendString(ctx, c.String("storage"))
if err != nil {
return ctx, exit.Error(exit.Unknown, err, "Failed to set storage backend: %s", err)
}
}
if !backend.HasCryptoBackend(ctx) {
debug.Log("Using default Crypto Backend (GPGCLI)")
ctx = backend.WithCryptoBackend(ctx, backend.GPGCLI)
}
if !backend.HasStorageBackend(ctx) {
debug.Log("Using default storage backend (GitFS)")
ctx = backend.WithStorageBackend(ctx, backend.GitFS)
}
sb := backend.GetStorageBackend(ctx)
if sb == backend.CryptFS {
out.Warning(ctx, "⚠ CryptFS is an experimental backend. Use at your own risk! ⚠")
}
if sb == backend.JJFS {
out.Warning(ctx, "⚠ JJFS is an experimental backend. Use at your own risk! ⚠")
}
return ctx, nil
}
func (s *Action) init(ctx context.Context, alias, path string, keys ...string) error {
if path == "" {
if alias != "" {
path = config.PwStoreDir(alias)
} else {
path = s.Store.Path()
}
}
path = fsutil.CleanPath(path)
debug.Log("Initializing Store %q in %q for %+v", alias, path, keys)
out.Printf(ctx, "🔑 Searching for usable private Keys ...")
debug.Log("Checking private keys for: %+v", keys)
crypto := s.getCryptoFor(ctx, alias)
// private key selection doesn't matter for plain. save one question.
// TODO should ask the backend
if crypto.Name() == "plain" {
keys, _ = crypto.ListIdentities(ctx)
}
if len(keys) < 1 {
if crypto.Name() != "age" {
out.Notice(ctx, "Hint: Use 'gopass init <subkey> to use subkeys!'")
}
nk, err := cui.AskForPrivateKey(ctx, crypto, "🎮 Please select a private key for encrypting secrets:")
if err != nil {
out.Noticef(ctx, "Hint: Use 'gopass setup --crypto %s' to be guided through an initial setup instead of 'gopass init'", crypto.Name())
return fmt.Errorf("failed to read user input: %w", err)
}
keys = []string{nk}
}
debug.Log("Initializing sub store - Alias: %q - Path: %q - Keys: %+v", alias, path, keys)
if err := s.Store.Init(ctx, alias, path, keys...); err != nil {
return fmt.Errorf("failed to init store %q at %q: %w", alias, path, err)
}
if alias != "" && path != "" {
debug.Log("Mounting sub store %q -> %q", alias, path)
if err := s.Store.AddMount(ctx, alias, path); err != nil {
return fmt.Errorf("failed to add mount %q: %w", alias, err)
}
}
if backend.HasStorageBackend(ctx) {
bn := backend.StorageBackendName(backend.GetStorageBackend(ctx))
debug.Log("Initializing RCS (%s) ...", bn)
if err := s.rcsInit(ctx, alias, ctxutil.GetUsername(ctx), ctxutil.GetEmail(ctx)); err != nil {
debug.Log("Stacktrace: %+v\n", err)
out.Errorf(ctx, "❌ Failed to init Version Control (%s): %s", bn, err)
}
debug.Log("RCS initialized as %s", s.Store.Storage(ctx, alias).Name())
} else {
debug.Log("not initializing RCS backend ...")
}
out.Printf(ctx, "🏁 Password store %s initialized for:", path)
s.printRecipients(ctx, alias)
return nil
}
func (s *Action) printRecipients(ctx context.Context, alias string) {
crypto := s.Store.Crypto(ctx, alias)
for _, recipient := range s.Store.ListRecipients(ctx, alias) {
if kl, err := crypto.FindRecipients(ctx, recipient); err == nil && len(kl) > 0 {
recipient = crypto.FormatKey(ctx, kl[0], "")
}
out.Printf(ctx, "📩 "+recipient)
}
}
func (s *Action) getCryptoFor(ctx context.Context, name string) backend.Crypto {
return s.Store.Crypto(ctx, name)
}