package vaultik import ( "bytes" "context" "fmt" "io" "os" "git.eeqj.de/sneak/vaultik/internal/config" "git.eeqj.de/sneak/vaultik/internal/crypto" "git.eeqj.de/sneak/vaultik/internal/database" "git.eeqj.de/sneak/vaultik/internal/globals" "git.eeqj.de/sneak/vaultik/internal/snapshot" "git.eeqj.de/sneak/vaultik/internal/storage" "github.com/spf13/afero" "go.uber.org/fx" ) // Vaultik contains all dependencies needed for vaultik operations type Vaultik struct { Globals *globals.Globals Config *config.Config DB *database.DB Repositories *database.Repositories Storage storage.Storer ScannerFactory snapshot.ScannerFactory SnapshotManager *snapshot.SnapshotManager Shutdowner fx.Shutdowner Fs afero.Fs // Context management ctx context.Context cancel context.CancelFunc // IO Stdout io.Writer Stderr io.Writer Stdin io.Reader } // VaultikParams contains all parameters for New that can be provided by fx type VaultikParams struct { fx.In Globals *globals.Globals Config *config.Config DB *database.DB Repositories *database.Repositories Storage storage.Storer ScannerFactory snapshot.ScannerFactory SnapshotManager *snapshot.SnapshotManager Shutdowner fx.Shutdowner Fs afero.Fs `optional:"true"` } // New creates a new Vaultik instance with proper context management // It automatically includes crypto capabilities if age_secret_key is configured func New(params VaultikParams) *Vaultik { ctx, cancel := context.WithCancel(context.Background()) // Use provided filesystem or default to OS filesystem fs := params.Fs if fs == nil { fs = afero.NewOsFs() } // Set filesystem on SnapshotManager params.SnapshotManager.SetFilesystem(fs) return &Vaultik{ Globals: params.Globals, Config: params.Config, DB: params.DB, Repositories: params.Repositories, Storage: params.Storage, ScannerFactory: params.ScannerFactory, SnapshotManager: params.SnapshotManager, Shutdowner: params.Shutdowner, Fs: fs, ctx: ctx, cancel: cancel, Stdout: os.Stdout, Stderr: os.Stderr, Stdin: os.Stdin, } } // Context returns the Vaultik's context func (v *Vaultik) Context() context.Context { return v.ctx } // SetContext sets the Vaultik's context (primarily for testing) func (v *Vaultik) SetContext(ctx context.Context) { v.ctx = ctx } // Cancel cancels the Vaultik's context func (v *Vaultik) Cancel() { v.cancel() } // CanDecrypt returns true if this Vaultik instance has decryption capabilities func (v *Vaultik) CanDecrypt() bool { return v.Config.AgeSecretKey != "" } // GetEncryptor creates a new Encryptor instance based on the configured age recipients // Returns an error if no recipients are configured func (v *Vaultik) GetEncryptor() (*crypto.Encryptor, error) { if len(v.Config.AgeRecipients) == 0 { return nil, fmt.Errorf("no age recipients configured") } return crypto.NewEncryptor(v.Config.AgeRecipients) } // GetDecryptor creates a new Decryptor instance based on the configured age secret key // Returns an error if no secret key is configured func (v *Vaultik) GetDecryptor() (*crypto.Decryptor, error) { if v.Config.AgeSecretKey == "" { return nil, fmt.Errorf("no age secret key configured") } return crypto.NewDecryptor(v.Config.AgeSecretKey) } // GetFilesystem returns the filesystem instance used by Vaultik func (v *Vaultik) GetFilesystem() afero.Fs { return v.Fs } // printfStdout writes formatted output to stdout. func (v *Vaultik) printfStdout(format string, args ...any) { _, _ = fmt.Fprintf(v.Stdout, format, args...) } // printlnStdout writes a line to stdout. func (v *Vaultik) printlnStdout(args ...any) { _, _ = fmt.Fprintln(v.Stdout, args...) } // printfStderr writes formatted output to stderr. func (v *Vaultik) printfStderr(format string, args ...any) { _, _ = fmt.Fprintf(v.Stderr, format, args...) } // scanStdin reads a line of input from stdin. func (v *Vaultik) scanStdin(a ...any) (int, error) { return fmt.Fscanln(v.Stdin, a...) } // TestVaultik wraps a Vaultik with captured stdout/stderr for testing type TestVaultik struct { *Vaultik Stdout *bytes.Buffer Stderr *bytes.Buffer Stdin *bytes.Buffer } // NewForTesting creates a minimal Vaultik instance for testing purposes. // Only the Storage field is populated; other fields are nil. // Returns a TestVaultik that captures stdout/stderr in buffers. func NewForTesting(storage storage.Storer) *TestVaultik { ctx, cancel := context.WithCancel(context.Background()) stdout := &bytes.Buffer{} stderr := &bytes.Buffer{} stdin := &bytes.Buffer{} return &TestVaultik{ Vaultik: &Vaultik{ Storage: storage, ctx: ctx, cancel: cancel, Stdout: stdout, Stderr: stderr, Stdin: stdin, }, Stdout: stdout, Stderr: stderr, Stdin: stdin, } }