- Created new internal/vaultik package with unified Vaultik struct - Moved all command methods (snapshot, info, prune, verify) from CLI to vaultik package - Implemented single constructor that handles crypto capabilities automatically - Added CanDecrypt() method to check if decryption is available - Updated all CLI commands to use the new vaultik.Vaultik struct - Removed old fragmented App structs and WithCrypto wrapper - Fixed context management - Vaultik now owns its context lifecycle - Cleaned up package imports and dependencies This creates a cleaner separation between CLI/Cobra code and business logic, with all vaultik operations now centralized in the internal/vaultik package.
140 lines
3.9 KiB
Go
140 lines
3.9 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"git.eeqj.de/sneak/vaultik/internal/config"
|
|
"git.eeqj.de/sneak/vaultik/internal/database"
|
|
"git.eeqj.de/sneak/vaultik/internal/globals"
|
|
"git.eeqj.de/sneak/vaultik/internal/log"
|
|
"git.eeqj.de/sneak/vaultik/internal/s3"
|
|
"git.eeqj.de/sneak/vaultik/internal/snapshot"
|
|
"github.com/spf13/cobra"
|
|
"go.uber.org/fx"
|
|
)
|
|
|
|
// FetchOptions contains options for the fetch command
|
|
type FetchOptions struct {
|
|
}
|
|
|
|
// FetchApp contains all dependencies needed for fetch
|
|
type FetchApp struct {
|
|
Globals *globals.Globals
|
|
Config *config.Config
|
|
Repositories *database.Repositories
|
|
S3Client *s3.Client
|
|
DB *database.DB
|
|
Shutdowner fx.Shutdowner
|
|
}
|
|
|
|
// NewFetchCommand creates the fetch command
|
|
func NewFetchCommand() *cobra.Command {
|
|
opts := &FetchOptions{}
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "fetch <snapshot-id> <file-path> <target-path>",
|
|
Short: "Extract single file from backup",
|
|
Long: `Download and decrypt a single file from a backup snapshot.
|
|
|
|
This command extracts a specific file from the snapshot and saves it to the target path.
|
|
The age_secret_key must be configured in the config file for decryption.`,
|
|
Args: cobra.ExactArgs(3),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
snapshotID := args[0]
|
|
filePath := args[1]
|
|
targetPath := args[2]
|
|
|
|
// Use unified config resolution
|
|
configPath, err := ResolveConfigPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Use the app framework like other commands
|
|
rootFlags := GetRootFlags()
|
|
return RunWithApp(cmd.Context(), AppOptions{
|
|
ConfigPath: configPath,
|
|
LogOptions: log.LogOptions{
|
|
Verbose: rootFlags.Verbose,
|
|
Debug: rootFlags.Debug,
|
|
},
|
|
Modules: []fx.Option{
|
|
snapshot.Module,
|
|
s3.Module,
|
|
fx.Provide(fx.Annotate(
|
|
func(g *globals.Globals, cfg *config.Config, repos *database.Repositories,
|
|
s3Client *s3.Client, db *database.DB, shutdowner fx.Shutdowner) *FetchApp {
|
|
return &FetchApp{
|
|
Globals: g,
|
|
Config: cfg,
|
|
Repositories: repos,
|
|
S3Client: s3Client,
|
|
DB: db,
|
|
Shutdowner: shutdowner,
|
|
}
|
|
},
|
|
)),
|
|
},
|
|
Invokes: []fx.Option{
|
|
fx.Invoke(func(app *FetchApp, lc fx.Lifecycle) {
|
|
lc.Append(fx.Hook{
|
|
OnStart: func(ctx context.Context) error {
|
|
// Start the fetch operation in a goroutine
|
|
go func() {
|
|
// Run the fetch operation
|
|
if err := app.runFetch(ctx, snapshotID, filePath, targetPath, opts); err != nil {
|
|
if err != context.Canceled {
|
|
log.Error("Fetch operation failed", "error", err)
|
|
}
|
|
}
|
|
|
|
// Shutdown the app when fetch completes
|
|
if err := app.Shutdowner.Shutdown(); err != nil {
|
|
log.Error("Failed to shutdown", "error", err)
|
|
}
|
|
}()
|
|
return nil
|
|
},
|
|
OnStop: func(ctx context.Context) error {
|
|
log.Debug("Stopping fetch operation")
|
|
return nil
|
|
},
|
|
})
|
|
}),
|
|
},
|
|
})
|
|
},
|
|
}
|
|
|
|
return cmd
|
|
}
|
|
|
|
// runFetch executes the fetch operation
|
|
func (app *FetchApp) runFetch(ctx context.Context, snapshotID, filePath, targetPath string, opts *FetchOptions) error {
|
|
// Check for age_secret_key
|
|
if app.Config.AgeSecretKey == "" {
|
|
return fmt.Errorf("age_secret_key missing from config - required for fetch")
|
|
}
|
|
|
|
log.Info("Starting fetch operation",
|
|
"snapshot_id", snapshotID,
|
|
"file_path", filePath,
|
|
"target_path", targetPath,
|
|
"bucket", app.Config.S3.Bucket,
|
|
"prefix", app.Config.S3.Prefix,
|
|
)
|
|
|
|
// TODO: Implement fetch logic
|
|
// 1. Download and decrypt database from S3
|
|
// 2. Find the file metadata and chunk list
|
|
// 3. Download and decrypt only the necessary blobs
|
|
// 4. Reconstruct the file from chunks
|
|
// 5. Write file to target path with proper metadata
|
|
|
|
fmt.Printf("Fetching %s from snapshot %s to %s\n", filePath, snapshotID, targetPath)
|
|
fmt.Println("TODO: Implement fetch logic")
|
|
|
|
return nil
|
|
}
|