Compare commits
14 Commits
add-compre
...
ede6a3ffcd
| Author | SHA1 | Date | |
|---|---|---|---|
| ede6a3ffcd | |||
| d8a51804d2 | |||
| 76f4421eb3 | |||
| 53ac868c5d | |||
| 8c4ea2b870 | |||
| 597b560398 | |||
| 1e2eced092 | |||
| 815b35c7ae | |||
| 9c66674683 | |||
| 49de277648 | |||
| ed5d777d05 | |||
| 76e047bbb2 | |||
|
|
ddc23f8057 | ||
| cafb3d45b8 |
@@ -7,9 +7,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// defaultMaxBlobCacheBytes is the default maximum size of the disk blob cache (10 GB).
|
|
||||||
const defaultMaxBlobCacheBytes = 10 << 30 // 10 GiB
|
|
||||||
|
|
||||||
// blobDiskCacheEntry tracks a cached blob on disk.
|
// blobDiskCacheEntry tracks a cached blob on disk.
|
||||||
type blobDiskCacheEntry struct {
|
type blobDiskCacheEntry struct {
|
||||||
key string
|
key string
|
||||||
|
|||||||
@@ -109,12 +109,36 @@ func (v *Vaultik) Restore(opts *RestoreOptions) error {
|
|||||||
|
|
||||||
// Step 5: Restore files
|
// Step 5: Restore files
|
||||||
result := &RestoreResult{}
|
result := &RestoreResult{}
|
||||||
blobCache, err := newBlobDiskCache(defaultMaxBlobCacheBytes)
|
blobCache, err := newBlobDiskCache(4 * v.Config.BlobSizeLimit.Int64())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating blob cache: %w", err)
|
return fmt.Errorf("creating blob cache: %w", err)
|
||||||
}
|
}
|
||||||
defer func() { _ = blobCache.Close() }()
|
defer func() { _ = blobCache.Close() }()
|
||||||
|
|
||||||
|
// Calculate total bytes for progress bar
|
||||||
|
var totalBytesExpected int64
|
||||||
|
for _, file := range files {
|
||||||
|
totalBytesExpected += file.Size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create progress bar if output is a terminal
|
||||||
|
var bar *progressbar.ProgressBar
|
||||||
|
if isTerminal() {
|
||||||
|
bar = progressbar.NewOptions64(
|
||||||
|
totalBytesExpected,
|
||||||
|
progressbar.OptionSetDescription("Restoring"),
|
||||||
|
progressbar.OptionSetWriter(os.Stderr),
|
||||||
|
progressbar.OptionShowBytes(true),
|
||||||
|
progressbar.OptionShowCount(),
|
||||||
|
progressbar.OptionSetWidth(40),
|
||||||
|
progressbar.OptionThrottle(100*time.Millisecond),
|
||||||
|
progressbar.OptionOnCompletion(func() {
|
||||||
|
fmt.Fprint(os.Stderr, "\n")
|
||||||
|
}),
|
||||||
|
progressbar.OptionSetRenderBlankState(true),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
for i, file := range files {
|
for i, file := range files {
|
||||||
if v.ctx.Err() != nil {
|
if v.ctx.Err() != nil {
|
||||||
return v.ctx.Err()
|
return v.ctx.Err()
|
||||||
@@ -122,11 +146,17 @@ func (v *Vaultik) Restore(opts *RestoreOptions) error {
|
|||||||
|
|
||||||
if err := v.restoreFile(v.ctx, repos, file, opts.TargetDir, identity, chunkToBlobMap, blobCache, result); err != nil {
|
if err := v.restoreFile(v.ctx, repos, file, opts.TargetDir, identity, chunkToBlobMap, blobCache, result); err != nil {
|
||||||
log.Error("Failed to restore file", "path", file.Path, "error", err)
|
log.Error("Failed to restore file", "path", file.Path, "error", err)
|
||||||
|
result.FilesFailed++
|
||||||
|
result.FailedFiles = append(result.FailedFiles, file.Path.String())
|
||||||
// Continue with other files
|
// Continue with other files
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Progress logging
|
// Update progress bar
|
||||||
|
if bar != nil {
|
||||||
|
_ = bar.Add64(file.Size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progress logging (for non-terminal or structured logs)
|
||||||
if (i+1)%100 == 0 || i+1 == len(files) {
|
if (i+1)%100 == 0 || i+1 == len(files) {
|
||||||
log.Info("Restore progress",
|
log.Info("Restore progress",
|
||||||
"files", fmt.Sprintf("%d/%d", i+1, len(files)),
|
"files", fmt.Sprintf("%d/%d", i+1, len(files)),
|
||||||
@@ -135,6 +165,10 @@ func (v *Vaultik) Restore(opts *RestoreOptions) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if bar != nil {
|
||||||
|
_ = bar.Finish()
|
||||||
|
}
|
||||||
|
|
||||||
result.Duration = time.Since(startTime)
|
result.Duration = time.Since(startTime)
|
||||||
|
|
||||||
log.Info("Restore complete",
|
log.Info("Restore complete",
|
||||||
@@ -151,6 +185,13 @@ func (v *Vaultik) Restore(opts *RestoreOptions) error {
|
|||||||
result.Duration.Round(time.Second),
|
result.Duration.Round(time.Second),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if result.FilesFailed > 0 {
|
||||||
|
_, _ = fmt.Fprintf(v.Stdout, "\nWARNING: %d file(s) failed to restore:\n", result.FilesFailed)
|
||||||
|
for _, path := range result.FailedFiles {
|
||||||
|
_, _ = fmt.Fprintf(v.Stdout, " - %s\n", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Run verification if requested
|
// Run verification if requested
|
||||||
if opts.Verify {
|
if opts.Verify {
|
||||||
if err := v.verifyRestoredFiles(v.ctx, repos, files, opts.TargetDir, result); err != nil {
|
if err := v.verifyRestoredFiles(v.ctx, repos, files, opts.TargetDir, result); err != nil {
|
||||||
@@ -171,6 +212,10 @@ func (v *Vaultik) Restore(opts *RestoreOptions) error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if result.FilesFailed > 0 {
|
||||||
|
return fmt.Errorf("%d file(s) failed to restore", result.FilesFailed)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,24 @@ func (v *Vaultik) CreateSnapshot(opts *SnapshotCreateOptions) error {
|
|||||||
v.printfStdout("\nAll %d snapshots completed in %s\n", len(snapshotNames), time.Since(overallStartTime).Round(time.Second))
|
v.printfStdout("\nAll %d snapshots completed in %s\n", len(snapshotNames), time.Since(overallStartTime).Round(time.Second))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prune old snapshots and unreferenced blobs if --prune was specified
|
||||||
|
if opts.Prune {
|
||||||
|
log.Info("Pruning enabled - deleting old snapshots and unreferenced blobs")
|
||||||
|
v.printlnStdout("\nPruning old snapshots (keeping latest)...")
|
||||||
|
|
||||||
|
if err := v.PurgeSnapshots(true, "", true); err != nil {
|
||||||
|
return fmt.Errorf("prune: purging old snapshots: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v.printlnStdout("Pruning unreferenced blobs...")
|
||||||
|
|
||||||
|
if err := v.PruneBlobs(&PruneOptions{Force: true}); err != nil {
|
||||||
|
return fmt.Errorf("prune: removing unreferenced blobs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Pruning complete")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,11 +324,6 @@ func (v *Vaultik) createNamedSnapshot(opts *SnapshotCreateOptions, hostname, sna
|
|||||||
}
|
}
|
||||||
v.printfStdout("Duration: %s\n", formatDuration(snapshotDuration))
|
v.printfStdout("Duration: %s\n", formatDuration(snapshotDuration))
|
||||||
|
|
||||||
if opts.Prune {
|
|
||||||
log.Info("Pruning enabled - will delete old snapshots after snapshot")
|
|
||||||
// TODO: Implement pruning
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1004,16 +1017,16 @@ func (v *Vaultik) deleteSnapshotFromLocalDB(snapshotID string) error {
|
|||||||
|
|
||||||
// Delete related records first to avoid foreign key constraints
|
// Delete related records first to avoid foreign key constraints
|
||||||
if err := v.Repositories.Snapshots.DeleteSnapshotFiles(v.ctx, snapshotID); err != nil {
|
if err := v.Repositories.Snapshots.DeleteSnapshotFiles(v.ctx, snapshotID); err != nil {
|
||||||
log.Error("Failed to delete snapshot files", "snapshot_id", snapshotID, "error", err)
|
return fmt.Errorf("deleting snapshot files for %s: %w", snapshotID, err)
|
||||||
}
|
}
|
||||||
if err := v.Repositories.Snapshots.DeleteSnapshotBlobs(v.ctx, snapshotID); err != nil {
|
if err := v.Repositories.Snapshots.DeleteSnapshotBlobs(v.ctx, snapshotID); err != nil {
|
||||||
log.Error("Failed to delete snapshot blobs", "snapshot_id", snapshotID, "error", err)
|
return fmt.Errorf("deleting snapshot blobs for %s: %w", snapshotID, err)
|
||||||
}
|
}
|
||||||
if err := v.Repositories.Snapshots.DeleteSnapshotUploads(v.ctx, snapshotID); err != nil {
|
if err := v.Repositories.Snapshots.DeleteSnapshotUploads(v.ctx, snapshotID); err != nil {
|
||||||
log.Error("Failed to delete snapshot uploads", "snapshot_id", snapshotID, "error", err)
|
return fmt.Errorf("deleting snapshot uploads for %s: %w", snapshotID, err)
|
||||||
}
|
}
|
||||||
if err := v.Repositories.Snapshots.Delete(v.ctx, snapshotID); err != nil {
|
if err := v.Repositories.Snapshots.Delete(v.ctx, snapshotID); err != nil {
|
||||||
log.Error("Failed to delete snapshot record", "snapshot_id", snapshotID, "error", err)
|
return fmt.Errorf("deleting snapshot record %s: %w", snapshotID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
23
internal/vaultik/snapshot_prune_test.go
Normal file
23
internal/vaultik/snapshot_prune_test.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package vaultik
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestSnapshotCreateOptions_PruneFlag verifies the Prune field exists on
|
||||||
|
// SnapshotCreateOptions and can be set.
|
||||||
|
func TestSnapshotCreateOptions_PruneFlag(t *testing.T) {
|
||||||
|
opts := &SnapshotCreateOptions{
|
||||||
|
Prune: true,
|
||||||
|
}
|
||||||
|
if !opts.Prune {
|
||||||
|
t.Error("Expected Prune to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
opts2 := &SnapshotCreateOptions{
|
||||||
|
Prune: false,
|
||||||
|
}
|
||||||
|
if opts2.Prune {
|
||||||
|
t.Error("Expected Prune to be false")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user