Apply linter autofixes: internal/cli (refs #61)
This commit is contained in:
@@ -44,9 +44,11 @@ func setupGlobals(lc fx.Lifecycle, g *globals.Globals, v *vaultik.Vaultik, opts
|
|||||||
lc.Append(fx.Hook{
|
lc.Append(fx.Hook{
|
||||||
OnStart: func(ctx context.Context) error {
|
OnStart: func(ctx context.Context) error {
|
||||||
g.StartTime = time.Now().UTC()
|
g.StartTime = time.Now().UTC()
|
||||||
|
|
||||||
if opts.Cron || opts.Quiet {
|
if opts.Cron || opts.Quiet {
|
||||||
v.UI.SetQuiet(true)
|
v.UI.SetQuiet(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -105,6 +107,7 @@ func cleanStartupError(err error) error {
|
|||||||
if idx := strings.LastIndex(msg, "): "); idx >= 0 {
|
if idx := strings.LastIndex(msg, "): "); idx >= 0 {
|
||||||
msg = msg[idx+3:]
|
msg = msg[idx+3:]
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New(msg)
|
return errors.New(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +125,8 @@ func RunApp(ctx context.Context, app *fx.App) error {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// Start the app
|
// Start the app
|
||||||
if err := app.Start(ctx); err != nil {
|
err := app.Start(ctx)
|
||||||
|
if err != nil {
|
||||||
return cleanStartupError(err)
|
return cleanStartupError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +134,7 @@ func RunApp(ctx context.Context, app *fx.App) error {
|
|||||||
shutdownComplete := make(chan struct{})
|
shutdownComplete := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
defer close(shutdownComplete)
|
defer close(shutdownComplete)
|
||||||
|
|
||||||
<-sigChan
|
<-sigChan
|
||||||
log.Notice("Received interrupt signal, shutting down gracefully...")
|
log.Notice("Received interrupt signal, shutting down gracefully...")
|
||||||
|
|
||||||
@@ -137,7 +142,8 @@ func RunApp(ctx context.Context, app *fx.App) error {
|
|||||||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer shutdownCancel()
|
defer shutdownCancel()
|
||||||
|
|
||||||
if err := app.Stop(shutdownCtx); err != nil {
|
err := app.Stop(shutdownCtx)
|
||||||
|
if err != nil {
|
||||||
log.Error("Error during shutdown", "error", err)
|
log.Error("Error during shutdown", "error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -149,9 +155,11 @@ func RunApp(ctx context.Context, app *fx.App) error {
|
|||||||
return nil
|
return nil
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
// Context cancelled (shouldn't happen in normal operation)
|
// Context cancelled (shouldn't happen in normal operation)
|
||||||
if err := app.Stop(context.Background()); err != nil {
|
err := app.Stop(context.Background())
|
||||||
|
if err != nil {
|
||||||
log.Error("Error stopping app", "error", err)
|
log.Error("Error stopping app", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
case <-app.Done():
|
case <-app.Done():
|
||||||
// App finished running (e.g., backup completed)
|
// App finished running (e.g., backup completed)
|
||||||
@@ -166,19 +174,24 @@ func RunApp(ctx context.Context, app *fx.App) error {
|
|||||||
func RunWithApp(ctx context.Context, opts AppOptions) error {
|
func RunWithApp(ctx context.Context, opts AppOptions) error {
|
||||||
// Acquire PID lock to prevent concurrent instances
|
// Acquire PID lock to prevent concurrent instances
|
||||||
lockDir := filepath.Join(xdg.DataHome, "vaultik")
|
lockDir := filepath.Join(xdg.DataHome, "vaultik")
|
||||||
|
|
||||||
lock, err := pidlock.Acquire(lockDir)
|
lock, err := pidlock.Acquire(lockDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, pidlock.ErrAlreadyRunning) {
|
if errors.Is(err, pidlock.ErrAlreadyRunning) {
|
||||||
return fmt.Errorf("cannot start: %w", err)
|
return fmt.Errorf("cannot start: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("failed to acquire lock: %w", err)
|
return fmt.Errorf("failed to acquire lock: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := lock.Release(); err != nil {
|
err := lock.Release()
|
||||||
|
if err != nil {
|
||||||
log.Warn("Failed to release PID lock", "error", err)
|
log.Warn("Failed to release PID lock", "error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
app := NewApp(opts)
|
app := NewApp(opts)
|
||||||
|
|
||||||
return RunApp(ctx, app)
|
return RunApp(ctx, app)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -240,16 +241,20 @@ on macOS, ~/.config/ on Linux, /etc/vaultik/ as root).`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
dir := filepath.Dir(path)
|
dir := filepath.Dir(path)
|
||||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
||||||
|
err := os.MkdirAll(dir, 0o755)
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("creating config directory %s: %w", dir, err)
|
return fmt.Errorf("creating config directory %s: %w", dir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile(path, []byte(defaultConfigTemplate), 0o600); err != nil {
|
err = os.WriteFile(path, []byte(defaultConfigTemplate), 0o600)
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("writing config file: %w", err)
|
return fmt.Errorf("writing config file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Config written to %s\n", path)
|
fmt.Printf("Config written to %s\n", path)
|
||||||
fmt.Println("Edit it to set your age_recipients, snapshots, and storage_url.")
|
fmt.Println("Edit it to set your age_recipients, snapshots, and storage_url.")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -276,6 +281,7 @@ func newConfigEditCommand() *cobra.Command {
|
|||||||
ed.Stdin = os.Stdin
|
ed.Stdin = os.Stdin
|
||||||
ed.Stdout = os.Stdout
|
ed.Stdout = os.Stdout
|
||||||
ed.Stderr = os.Stderr
|
ed.Stderr = os.Stderr
|
||||||
|
|
||||||
return ed.Run()
|
return ed.Run()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -305,6 +311,7 @@ func newConfigGetCommand() *cobra.Command {
|
|||||||
|
|
||||||
if node.Kind == yaml.ScalarNode {
|
if node.Kind == yaml.ScalarNode {
|
||||||
fmt.Println(node.Value)
|
fmt.Println(node.Value)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,7 +319,9 @@ func newConfigGetCommand() *cobra.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("marshaling value: %w", err)
|
return fmt.Errorf("marshaling value: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Print(string(out))
|
fmt.Print(string(out))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -363,6 +372,7 @@ Examples:
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s = %s\n", args[0], args[1])
|
fmt.Printf("%s = %s\n", args[0], args[1])
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -399,8 +409,9 @@ func yamlPathGet(root *yaml.Node, keys []string) (*yaml.Node, error) {
|
|||||||
node := root
|
node := root
|
||||||
if node.Kind == yaml.DocumentNode {
|
if node.Kind == yaml.DocumentNode {
|
||||||
if len(node.Content) == 0 {
|
if len(node.Content) == 0 {
|
||||||
return nil, fmt.Errorf("empty config file")
|
return nil, errors.New("empty config file")
|
||||||
}
|
}
|
||||||
|
|
||||||
node = node.Content[0]
|
node = node.Content[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,13 +419,16 @@ func yamlPathGet(root *yaml.Node, keys []string) (*yaml.Node, error) {
|
|||||||
switch node.Kind {
|
switch node.Kind {
|
||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
for j := 0; j+1 < len(node.Content); j += 2 {
|
for j := 0; j+1 < len(node.Content); j += 2 {
|
||||||
if node.Content[j].Value == key {
|
if node.Content[j].Value == key {
|
||||||
node = node.Content[j+1]
|
node = node.Content[j+1]
|
||||||
found = true
|
found = true
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
return nil, fmt.Errorf("key not found: %s", strings.Join(keys[:i+1], "."))
|
return nil, fmt.Errorf("key not found: %s", strings.Join(keys[:i+1], "."))
|
||||||
}
|
}
|
||||||
@@ -423,9 +437,11 @@ func yamlPathGet(root *yaml.Node, keys []string) (*yaml.Node, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("key %q is a list; use a numeric index", strings.Join(keys[:i], "."))
|
return nil, fmt.Errorf("key %q is a list; use a numeric index", strings.Join(keys[:i], "."))
|
||||||
}
|
}
|
||||||
|
|
||||||
if idx < 0 || idx >= len(node.Content) {
|
if idx < 0 || idx >= len(node.Content) {
|
||||||
return nil, fmt.Errorf("index %d out of range for %s (len %d)", idx, strings.Join(keys[:i], "."), len(node.Content))
|
return nil, fmt.Errorf("index %d out of range for %s (len %d)", idx, strings.Join(keys[:i], "."), len(node.Content))
|
||||||
}
|
}
|
||||||
|
|
||||||
node = node.Content[idx]
|
node = node.Content[idx]
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("key %q is not a map or list", strings.Join(keys[:i], "."))
|
return nil, fmt.Errorf("key %q is not a map or list", strings.Join(keys[:i], "."))
|
||||||
@@ -445,6 +461,7 @@ func yamlPathSet(root *yaml.Node, keys []string, value string) error {
|
|||||||
if len(node.Content) == 0 {
|
if len(node.Content) == 0 {
|
||||||
node.Content = []*yaml.Node{{Kind: yaml.MappingNode}}
|
node.Content = []*yaml.Node{{Kind: yaml.MappingNode}}
|
||||||
}
|
}
|
||||||
|
|
||||||
node = node.Content[0]
|
node = node.Content[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,19 +471,23 @@ func yamlPathSet(root *yaml.Node, keys []string, value string) error {
|
|||||||
switch node.Kind {
|
switch node.Kind {
|
||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
var valueNode *yaml.Node
|
var valueNode *yaml.Node
|
||||||
|
|
||||||
for j := 0; j+1 < len(node.Content); j += 2 {
|
for j := 0; j+1 < len(node.Content); j += 2 {
|
||||||
if node.Content[j].Value == key {
|
if node.Content[j].Value == key {
|
||||||
valueNode = node.Content[j+1]
|
valueNode = node.Content[j+1]
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if valueNode == nil {
|
if valueNode == nil {
|
||||||
keyNode := &yaml.Node{Kind: yaml.ScalarNode, Value: key}
|
keyNode := &yaml.Node{Kind: yaml.ScalarNode, Value: key}
|
||||||
|
|
||||||
valueNode = &yaml.Node{Kind: yaml.MappingNode}
|
valueNode = &yaml.Node{Kind: yaml.MappingNode}
|
||||||
if last {
|
if last {
|
||||||
valueNode = &yaml.Node{Kind: yaml.ScalarNode, Value: value}
|
valueNode = &yaml.Node{Kind: yaml.ScalarNode, Value: value}
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Content = append(node.Content, keyNode, valueNode)
|
node.Content = append(node.Content, keyNode, valueNode)
|
||||||
} else if last {
|
} else if last {
|
||||||
setScalar(valueNode, value)
|
setScalar(valueNode, value)
|
||||||
@@ -479,18 +500,22 @@ func yamlPathSet(root *yaml.Node, keys []string, value string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("key %q is a list; use a numeric index", strings.Join(keys[:i], "."))
|
return fmt.Errorf("key %q is a list; use a numeric index", strings.Join(keys[:i], "."))
|
||||||
}
|
}
|
||||||
|
|
||||||
if idx < 0 || idx > len(node.Content) {
|
if idx < 0 || idx > len(node.Content) {
|
||||||
return fmt.Errorf("index %d out of range for %s (len %d)", idx, strings.Join(keys[:i], "."), len(node.Content))
|
return fmt.Errorf("index %d out of range for %s (len %d)", idx, strings.Join(keys[:i], "."), len(node.Content))
|
||||||
}
|
}
|
||||||
|
|
||||||
if idx == len(node.Content) {
|
if idx == len(node.Content) {
|
||||||
newNode := &yaml.Node{Kind: yaml.MappingNode}
|
newNode := &yaml.Node{Kind: yaml.MappingNode}
|
||||||
if last {
|
if last {
|
||||||
newNode = &yaml.Node{Kind: yaml.ScalarNode, Value: value}
|
newNode = &yaml.Node{Kind: yaml.ScalarNode, Value: value}
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Content = append(node.Content, newNode)
|
node.Content = append(node.Content, newNode)
|
||||||
} else if last {
|
} else if last {
|
||||||
setScalar(node.Content[idx], value)
|
setScalar(node.Content[idx], value)
|
||||||
}
|
}
|
||||||
|
|
||||||
node = node.Content[idx]
|
node = node.Content[idx]
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -516,8 +541,10 @@ func configPathForInit() string {
|
|||||||
if rootFlags.ConfigPath != "" {
|
if rootFlags.ConfigPath != "" {
|
||||||
return rootFlags.ConfigPath
|
return rootFlags.ConfigPath
|
||||||
}
|
}
|
||||||
|
|
||||||
if envPath := os.Getenv("VAULTIK_CONFIG"); envPath != "" {
|
if envPath := os.Getenv("VAULTIK_CONFIG"); envPath != "" {
|
||||||
return envPath
|
return envPath
|
||||||
}
|
}
|
||||||
|
|
||||||
return DefaultConfigPath()
|
return DefaultConfigPath()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import (
|
|||||||
// that unmarshals into the Config struct with the expected snapshots.
|
// that unmarshals into the Config struct with the expected snapshots.
|
||||||
func TestDefaultConfigTemplateParses(t *testing.T) {
|
func TestDefaultConfigTemplateParses(t *testing.T) {
|
||||||
var cfg config.Config
|
var cfg config.Config
|
||||||
if err := yaml.Unmarshal([]byte(defaultConfigTemplate), &cfg); err != nil {
|
|
||||||
|
err := yaml.Unmarshal([]byte(defaultConfigTemplate), &cfg)
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("default config template is not valid YAML: %v", err)
|
t.Fatalf("default config template is not valid YAML: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,9 +26,11 @@ func TestDefaultConfigTemplateParses(t *testing.T) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("expected 'home' snapshot in default config")
|
t.Fatal("expected 'home' snapshot in default config")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(home.Paths) == 0 {
|
if len(home.Paths) == 0 {
|
||||||
t.Error("home snapshot should have at least one path")
|
t.Error("home snapshot should have at least one path")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(home.Exclude) == 0 {
|
if len(home.Exclude) == 0 {
|
||||||
t.Error("home snapshot should have exclude patterns")
|
t.Error("home snapshot should have exclude patterns")
|
||||||
}
|
}
|
||||||
@@ -35,9 +39,11 @@ func TestDefaultConfigTemplateParses(t *testing.T) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("expected 'apps' snapshot in default config")
|
t.Fatal("expected 'apps' snapshot in default config")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(apps.Paths) != 1 || apps.Paths[0] != "/Applications" {
|
if len(apps.Paths) != 1 || apps.Paths[0] != "/Applications" {
|
||||||
t.Errorf("apps snapshot should back up /Applications, got %v", apps.Paths)
|
t.Errorf("apps snapshot should back up /Applications, got %v", apps.Paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(apps.Exclude) == 0 {
|
if len(apps.Exclude) == 0 {
|
||||||
t.Error("apps snapshot should have exclude patterns")
|
t.Error("apps snapshot should have exclude patterns")
|
||||||
}
|
}
|
||||||
@@ -58,10 +64,14 @@ snapshots:
|
|||||||
|
|
||||||
func parseTestYAML(t *testing.T) *yaml.Node {
|
func parseTestYAML(t *testing.T) *yaml.Node {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
var root yaml.Node
|
var root yaml.Node
|
||||||
if err := yaml.Unmarshal([]byte(testYAML), &root); err != nil {
|
|
||||||
|
err := yaml.Unmarshal([]byte(testYAML), &root)
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("parsing test yaml: %v", err)
|
t.Fatalf("parsing test yaml: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &root
|
return &root
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,11 +101,14 @@ func TestYAMLPathGet(t *testing.T) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("expected error for %q", tt.path)
|
t.Fatalf("expected error for %q", tt.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.Value != tt.want {
|
if node.Value != tt.want {
|
||||||
t.Errorf("get %q = %q, want %q", tt.path, node.Value, tt.want)
|
t.Errorf("get %q = %q, want %q", tt.path, node.Value, tt.want)
|
||||||
}
|
}
|
||||||
@@ -115,6 +128,7 @@ func TestYAMLPathSet(t *testing.T) {
|
|||||||
if err := yamlPathSet(root, splitPath("s3.endpoint"), "s3.example.com"); err != nil {
|
if err := yamlPathSet(root, splitPath("s3.endpoint"), "s3.example.com"); err != nil {
|
||||||
t.Fatalf("set s3.endpoint: %v", err)
|
t.Fatalf("set s3.endpoint: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := yamlPathSet(root, splitPath("newmap.newkey"), "val"); err != nil {
|
if err := yamlPathSet(root, splitPath("newmap.newkey"), "val"); err != nil {
|
||||||
t.Fatalf("set newmap.newkey: %v", err)
|
t.Fatalf("set newmap.newkey: %v", err)
|
||||||
}
|
}
|
||||||
@@ -123,9 +137,11 @@ func TestYAMLPathSet(t *testing.T) {
|
|||||||
if err := yamlPathSet(root, splitPath("age_recipients.0"), "age1bbb"); err != nil {
|
if err := yamlPathSet(root, splitPath("age_recipients.0"), "age1bbb"); err != nil {
|
||||||
t.Fatalf("set age_recipients.0: %v", err)
|
t.Fatalf("set age_recipients.0: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := yamlPathSet(root, splitPath("age_recipients.1"), "age1ccc"); err != nil {
|
if err := yamlPathSet(root, splitPath("age_recipients.1"), "age1ccc"); err != nil {
|
||||||
t.Fatalf("append age_recipients.1: %v", err)
|
t.Fatalf("append age_recipients.1: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := yamlPathSet(root, splitPath("age_recipients.5"), "age1ddd"); err == nil {
|
if err := yamlPathSet(root, splitPath("age_recipients.5"), "age1ddd"); err == nil {
|
||||||
t.Error("expected out-of-range append to fail")
|
t.Error("expected out-of-range append to fail")
|
||||||
}
|
}
|
||||||
@@ -135,6 +151,7 @@ func TestYAMLPathSet(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("marshal: %v", err)
|
t.Fatalf("marshal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
text := string(out)
|
text := string(out)
|
||||||
|
|
||||||
for _, want := range []string{"newbucket", "s3.example.com", "newkey: val", "# top comment", "# inline comment", "age1bbb", "age1ccc"} {
|
for _, want := range []string{"newbucket", "s3.example.com", "newkey: val", "# top comment", "# inline comment", "age1bbb", "age1ccc"} {
|
||||||
@@ -147,6 +164,7 @@ func TestYAMLPathSet(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("get after set: %v", err)
|
t.Fatalf("get after set: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if got.Value != "newbucket" {
|
if got.Value != "newbucket" {
|
||||||
t.Errorf("s3.bucket = %q after set, want newbucket", got.Value)
|
t.Errorf("s3.bucket = %q after set, want newbucket", got.Value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ Use --force to skip the confirmation prompt.`,
|
|||||||
// Check if database exists
|
// Check if database exists
|
||||||
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
|
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
|
||||||
fmt.Printf("Database does not exist: %s\n", dbPath)
|
fmt.Printf("Database does not exist: %s\n", dbPath)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,9 +74,11 @@ Use --force to skip the confirmation prompt.`,
|
|||||||
if !force {
|
if !force {
|
||||||
fmt.Printf("This will delete the local state database at:\n %s\n\n", dbPath)
|
fmt.Printf("This will delete the local state database at:\n %s\n\n", dbPath)
|
||||||
fmt.Print("Are you sure? Type 'yes' to confirm: ")
|
fmt.Print("Are you sure? Type 'yes' to confirm: ")
|
||||||
|
|
||||||
var confirm string
|
var confirm string
|
||||||
if _, err := fmt.Scanln(&confirm); err != nil || confirm != "yes" {
|
if _, err := fmt.Scanln(&confirm); err != nil || confirm != "yes" {
|
||||||
fmt.Println("Aborted.")
|
fmt.Println("Aborted.")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,6 +100,7 @@ Use --force to skip the confirmation prompt.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Local state database deleted", "path", dbPath)
|
log.Info("Local state database deleted", "path", dbPath)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -25,7 +26,7 @@ func parseDuration(s string) (time.Duration, error) {
|
|||||||
// Extended duration parsing
|
// Extended duration parsing
|
||||||
// Check for negative values
|
// Check for negative values
|
||||||
if strings.HasPrefix(strings.TrimSpace(s), "-") {
|
if strings.HasPrefix(strings.TrimSpace(s), "-") {
|
||||||
return 0, fmt.Errorf("negative durations are not supported")
|
return 0, errors.New("negative durations are not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pattern matches: number + unit, repeated
|
// Pattern matches: number + unit, repeated
|
||||||
@@ -48,6 +49,7 @@ func parseDuration(s string) (time.Duration, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var d time.Duration
|
var d time.Duration
|
||||||
|
|
||||||
switch unit {
|
switch unit {
|
||||||
// Standard time units
|
// Standard time units
|
||||||
case "ns", "nanosecond", "nanoseconds":
|
case "ns", "nanosecond", "nanoseconds":
|
||||||
@@ -75,7 +77,7 @@ func parseDuration(s string) (time.Duration, error) {
|
|||||||
d = time.Duration(value * float64(365*24*time.Hour))
|
d = time.Duration(value * float64(365*24*time.Hour))
|
||||||
default:
|
default:
|
||||||
// Try parsing as standard Go duration unit
|
// Try parsing as standard Go duration unit
|
||||||
testStr := fmt.Sprintf("1%s", unit)
|
testStr := "1" + unit
|
||||||
if _, err := time.ParseDuration(testStr); err == nil {
|
if _, err := time.ParseDuration(testStr); err == nil {
|
||||||
// It's a valid Go duration unit, parse the full value
|
// It's a valid Go duration unit, parse the full value
|
||||||
fullStr := fmt.Sprintf("%g%s", value, unit)
|
fullStr := fmt.Sprintf("%g%s", value, unit)
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ func TestParseDuration(t *testing.T) {
|
|||||||
|
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err, "expected error for input %q", tt.input)
|
assert.Error(t, err, "expected error for input %q", tt.input)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,13 +19,15 @@ func CLIEntry() {
|
|||||||
if len(short) > 12 {
|
if len(short) > 12 {
|
||||||
short = short[:12]
|
short = short[:12]
|
||||||
}
|
}
|
||||||
|
|
||||||
writeStartupBanner(ui.New(os.Stdout), time.Now().UTC(), short)
|
writeStartupBanner(ui.New(os.Stdout), time.Now().UTC(), short)
|
||||||
}
|
}
|
||||||
|
|
||||||
rootCmd := NewRootCommand()
|
rootCmd := NewRootCommand()
|
||||||
rootCmd.SilenceErrors = true
|
rootCmd.SilenceErrors = true
|
||||||
|
|
||||||
if err := rootCmd.Execute(); err != nil {
|
err := rootCmd.Execute()
|
||||||
|
if err != nil {
|
||||||
ReportError("%s", err.Error())
|
ReportError("%s", err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
@@ -48,10 +50,12 @@ func bannerSuppressedInArgs(args []string) bool {
|
|||||||
if a == "--" {
|
if a == "--" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
switch a {
|
switch a {
|
||||||
case "--quiet", "-q", "--cron":
|
case "--quiet", "-q", "--cron":
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(a, "--quiet=") || strings.HasPrefix(a, "--cron=") {
|
if strings.HasPrefix(a, "--quiet=") || strings.HasPrefix(a, "--cron=") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -64,5 +68,6 @@ func bannerSuppressedInArgs(args []string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,15 @@ func TestCLIEntry(t *testing.T) {
|
|||||||
expectedCommands := []string{"config", "snapshot", "prune", "info", "version", "remote", "database"}
|
expectedCommands := []string{"config", "snapshot", "prune", "info", "version", "remote", "database"}
|
||||||
for _, expected := range expectedCommands {
|
for _, expected := range expectedCommands {
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
for _, cmd := range cmd.Commands() {
|
for _, cmd := range cmd.Commands() {
|
||||||
if cmd.Use == expected || cmd.Name() == expected {
|
if cmd.Use == expected || cmd.Name() == expected {
|
||||||
found = true
|
found = true
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
t.Errorf("Expected command '%s' not found", expected)
|
t.Errorf("Expected command '%s' not found", expected)
|
||||||
}
|
}
|
||||||
@@ -41,12 +44,15 @@ func TestCLIEntry(t *testing.T) {
|
|||||||
expectedSubCommands := []string{"create", "list", "purge", "verify", "remove", "restore"}
|
expectedSubCommands := []string{"create", "list", "purge", "verify", "remove", "restore"}
|
||||||
for _, expected := range expectedSubCommands {
|
for _, expected := range expectedSubCommands {
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
for _, subcmd := range snapshotCmd.Commands() {
|
for _, subcmd := range snapshotCmd.Commands() {
|
||||||
if subcmd.Use == expected || subcmd.Name() == expected {
|
if subcmd.Use == expected || subcmd.Name() == expected {
|
||||||
found = true
|
found = true
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
t.Errorf("Expected snapshot subcommand '%s' not found", expected)
|
t.Errorf("Expected snapshot subcommand '%s' not found", expected)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -31,6 +32,7 @@ func NewInfoCommand() *cobra.Command {
|
|||||||
|
|
||||||
// Use the app framework
|
// Use the app framework
|
||||||
rootFlags := GetRootFlags()
|
rootFlags := GetRootFlags()
|
||||||
|
|
||||||
return RunWithApp(cmd.Context(), AppOptions{
|
return RunWithApp(cmd.Context(), AppOptions{
|
||||||
ConfigPath: configPath,
|
ConfigPath: configPath,
|
||||||
LogOptions: log.LogOptions{
|
LogOptions: log.LogOptions{
|
||||||
@@ -44,21 +46,26 @@ func NewInfoCommand() *cobra.Command {
|
|||||||
lc.Append(fx.Hook{
|
lc.Append(fx.Hook{
|
||||||
OnStart: func(ctx context.Context) error {
|
OnStart: func(ctx context.Context) error {
|
||||||
go func() {
|
go func() {
|
||||||
if err := v.ShowInfo(); err != nil {
|
err := v.ShowInfo()
|
||||||
if err != context.Canceled {
|
if err != nil {
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
log.Error("Failed to show info", "error", err)
|
log.Error("Failed to show info", "error", err)
|
||||||
ReportError("Failed to show info: %v", err)
|
ReportError("Failed to show info: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := v.Shutdowner.Shutdown(); err != nil {
|
|
||||||
|
err = v.Shutdowner.Shutdown()
|
||||||
|
if err != nil {
|
||||||
log.Error("Failed to shutdown", "error", err)
|
log.Error("Failed to shutdown", "error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
OnStop: func(ctx context.Context) error {
|
OnStop: func(ctx context.Context) error {
|
||||||
v.Cancel()
|
v.Cancel()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -39,6 +40,7 @@ work (e.g. after a crashed backup or to reclaim storage).`,
|
|||||||
|
|
||||||
// Use the app framework like other commands
|
// Use the app framework like other commands
|
||||||
rootFlags := GetRootFlags()
|
rootFlags := GetRootFlags()
|
||||||
|
|
||||||
return RunWithApp(cmd.Context(), AppOptions{
|
return RunWithApp(cmd.Context(), AppOptions{
|
||||||
ConfigPath: configPath,
|
ConfigPath: configPath,
|
||||||
LogOptions: log.LogOptions{
|
LogOptions: log.LogOptions{
|
||||||
@@ -54,26 +56,31 @@ work (e.g. after a crashed backup or to reclaim storage).`,
|
|||||||
// Start the prune operation in a goroutine
|
// Start the prune operation in a goroutine
|
||||||
go func() {
|
go func() {
|
||||||
// Run the prune operation
|
// Run the prune operation
|
||||||
if err := v.Prune(opts); err != nil {
|
err := v.Prune(opts)
|
||||||
if err != context.Canceled {
|
if err != nil {
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
if !opts.JSON {
|
if !opts.JSON {
|
||||||
log.Error("Prune operation failed", "error", err)
|
log.Error("Prune operation failed", "error", err)
|
||||||
ReportError("Prune failed: %v", err)
|
ReportError("Prune failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown the app when prune completes
|
// Shutdown the app when prune completes
|
||||||
if err := v.Shutdowner.Shutdown(); err != nil {
|
err = v.Shutdowner.Shutdown()
|
||||||
|
if err != nil {
|
||||||
log.Error("Failed to shutdown", "error", err)
|
log.Error("Failed to shutdown", "error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
OnStop: func(ctx context.Context) error {
|
OnStop: func(ctx context.Context) error {
|
||||||
log.Debug("Stopping prune operation")
|
log.Debug("Stopping prune operation")
|
||||||
v.Cancel()
|
v.Cancel()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -41,7 +41,7 @@ This is destructive and irreversible. Requires --force.`,
|
|||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if !force {
|
if !force {
|
||||||
return fmt.Errorf("remote nuke requires --force (this deletes ALL remote snapshots and blobs)")
|
return errors.New("remote nuke requires --force (this deletes ALL remote snapshots and blobs)")
|
||||||
}
|
}
|
||||||
|
|
||||||
configPath, err := ResolveConfigPath()
|
configPath, err := ResolveConfigPath()
|
||||||
@@ -50,6 +50,7 @@ This is destructive and irreversible. Requires --force.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
rootFlags := GetRootFlags()
|
rootFlags := GetRootFlags()
|
||||||
|
|
||||||
return RunWithApp(cmd.Context(), AppOptions{
|
return RunWithApp(cmd.Context(), AppOptions{
|
||||||
ConfigPath: configPath,
|
ConfigPath: configPath,
|
||||||
LogOptions: log.LogOptions{
|
LogOptions: log.LogOptions{
|
||||||
@@ -63,21 +64,26 @@ This is destructive and irreversible. Requires --force.`,
|
|||||||
lc.Append(fx.Hook{
|
lc.Append(fx.Hook{
|
||||||
OnStart: func(ctx context.Context) error {
|
OnStart: func(ctx context.Context) error {
|
||||||
go func() {
|
go func() {
|
||||||
if err := v.NukeRemote(true); err != nil {
|
err := v.NukeRemote(true)
|
||||||
if err != context.Canceled {
|
if err != nil {
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
log.Error("Remote nuke failed", "error", err)
|
log.Error("Remote nuke failed", "error", err)
|
||||||
ReportError("Remote nuke failed: %v", err)
|
ReportError("Remote nuke failed: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := v.Shutdowner.Shutdown(); err != nil {
|
|
||||||
|
err = v.Shutdowner.Shutdown()
|
||||||
|
if err != nil {
|
||||||
log.Error("Failed to shutdown", "error", err)
|
log.Error("Failed to shutdown", "error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
OnStop: func(ctx context.Context) error {
|
OnStop: func(ctx context.Context) error {
|
||||||
v.Cancel()
|
v.Cancel()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -113,6 +119,7 @@ func newRemoteInfoCommand() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rootFlags := GetRootFlags()
|
rootFlags := GetRootFlags()
|
||||||
|
|
||||||
return RunWithApp(cmd.Context(), AppOptions{
|
return RunWithApp(cmd.Context(), AppOptions{
|
||||||
ConfigPath: configPath,
|
ConfigPath: configPath,
|
||||||
LogOptions: log.LogOptions{
|
LogOptions: log.LogOptions{
|
||||||
@@ -126,23 +133,29 @@ func newRemoteInfoCommand() *cobra.Command {
|
|||||||
lc.Append(fx.Hook{
|
lc.Append(fx.Hook{
|
||||||
OnStart: func(ctx context.Context) error {
|
OnStart: func(ctx context.Context) error {
|
||||||
go func() {
|
go func() {
|
||||||
if err := v.RemoteInfo(jsonOutput); err != nil {
|
err := v.RemoteInfo(jsonOutput)
|
||||||
if err != context.Canceled {
|
if err != nil {
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
if !jsonOutput {
|
if !jsonOutput {
|
||||||
log.Error("Failed to get remote info", "error", err)
|
log.Error("Failed to get remote info", "error", err)
|
||||||
ReportError("Failed to get remote info: %v", err)
|
ReportError("Failed to get remote info: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := v.Shutdowner.Shutdown(); err != nil {
|
|
||||||
|
err = v.Shutdowner.Shutdown()
|
||||||
|
if err != nil {
|
||||||
log.Error("Failed to shutdown", "error", err)
|
log.Error("Failed to shutdown", "error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
OnStop: func(ctx context.Context) error {
|
OnStop: func(ctx context.Context) error {
|
||||||
v.Cancel()
|
v.Cancel()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ func ResolveConfigPath() (string, error) {
|
|||||||
if _, err := os.Stat(path); err != nil {
|
if _, err := os.Stat(path); err != nil {
|
||||||
return "", fmt.Errorf("config file from --config not found: %s (run 'vaultik config init --config %s' to create it)", path, path)
|
return "", fmt.Errorf("config file from --config not found: %s (run 'vaultik config init --config %s' to create it)", path, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +86,7 @@ func ResolveConfigPath() (string, error) {
|
|||||||
if _, err := os.Stat(path); err != nil {
|
if _, err := os.Stat(path); err != nil {
|
||||||
return "", fmt.Errorf("config file from $VAULTIK_CONFIG not found: %s (unset VAULTIK_CONFIG, point it at an existing file, or run 'vaultik config init')", path)
|
return "", fmt.Errorf("config file from $VAULTIK_CONFIG not found: %s (unset VAULTIK_CONFIG, point it at an existing file, or run 'vaultik config init')", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,5 +116,6 @@ func DefaultConfigPath() string {
|
|||||||
if os.Getuid() == 0 {
|
if os.Getuid() == 0 {
|
||||||
return "/etc/vaultik/config.yml"
|
return "/etc/vaultik/config.yml"
|
||||||
}
|
}
|
||||||
|
|
||||||
return filepath.Join(xdg.ConfigHome, "vaultik", "config.yml")
|
return filepath.Join(xdg.ConfigHome, "vaultik", "config.yml")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -58,6 +59,7 @@ specifying a path using --config or by setting VAULTIK_CONFIG to a path.`,
|
|||||||
|
|
||||||
// Use the backup functionality from cli package
|
// Use the backup functionality from cli package
|
||||||
rootFlags := GetRootFlags()
|
rootFlags := GetRootFlags()
|
||||||
|
|
||||||
return RunWithApp(cmd.Context(), AppOptions{
|
return RunWithApp(cmd.Context(), AppOptions{
|
||||||
ConfigPath: configPath,
|
ConfigPath: configPath,
|
||||||
LogOptions: log.LogOptions{
|
LogOptions: log.LogOptions{
|
||||||
@@ -74,8 +76,9 @@ specifying a path using --config or by setting VAULTIK_CONFIG to a path.`,
|
|||||||
// Start the snapshot creation in a goroutine
|
// Start the snapshot creation in a goroutine
|
||||||
go func() {
|
go func() {
|
||||||
// --cron suppression is wired through v.UI by setupGlobals.
|
// --cron suppression is wired through v.UI by setupGlobals.
|
||||||
if err := v.CreateSnapshot(opts); err != nil {
|
err := v.CreateSnapshot(opts)
|
||||||
if err != context.Canceled {
|
if err != nil {
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
log.Error("Snapshot creation failed", "error", err)
|
log.Error("Snapshot creation failed", "error", err)
|
||||||
ReportError("Snapshot creation failed: %v", err)
|
ReportError("Snapshot creation failed: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -83,16 +86,19 @@ specifying a path using --config or by setting VAULTIK_CONFIG to a path.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown the app when snapshot completes
|
// Shutdown the app when snapshot completes
|
||||||
if err := v.Shutdowner.Shutdown(); err != nil {
|
err = v.Shutdowner.Shutdown()
|
||||||
|
if err != nil {
|
||||||
log.Error("Failed to shutdown", "error", err)
|
log.Error("Failed to shutdown", "error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
OnStop: func(ctx context.Context) error {
|
OnStop: func(ctx context.Context) error {
|
||||||
log.Debug("Stopping snapshot creation")
|
log.Debug("Stopping snapshot creation")
|
||||||
// Cancel the Vaultik context
|
// Cancel the Vaultik context
|
||||||
v.Cancel()
|
v.Cancel()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -127,6 +133,7 @@ func newSnapshotListCommand() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rootFlags := GetRootFlags()
|
rootFlags := GetRootFlags()
|
||||||
|
|
||||||
return RunWithApp(cmd.Context(), AppOptions{
|
return RunWithApp(cmd.Context(), AppOptions{
|
||||||
ConfigPath: configPath,
|
ConfigPath: configPath,
|
||||||
LogOptions: log.LogOptions{
|
LogOptions: log.LogOptions{
|
||||||
@@ -140,21 +147,26 @@ func newSnapshotListCommand() *cobra.Command {
|
|||||||
lc.Append(fx.Hook{
|
lc.Append(fx.Hook{
|
||||||
OnStart: func(ctx context.Context) error {
|
OnStart: func(ctx context.Context) error {
|
||||||
go func() {
|
go func() {
|
||||||
if err := v.ListSnapshots(jsonOutput); err != nil {
|
err := v.ListSnapshots(jsonOutput)
|
||||||
if err != context.Canceled {
|
if err != nil {
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
log.Error("Failed to list snapshots", "error", err)
|
log.Error("Failed to list snapshots", "error", err)
|
||||||
ReportError("Failed to list snapshots: %v", err)
|
ReportError("Failed to list snapshots: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := v.Shutdowner.Shutdown(); err != nil {
|
|
||||||
|
err = v.Shutdowner.Shutdown()
|
||||||
|
if err != nil {
|
||||||
log.Error("Failed to shutdown", "error", err)
|
log.Error("Failed to shutdown", "error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
OnStop: func(ctx context.Context) error {
|
OnStop: func(ctx context.Context) error {
|
||||||
v.Cancel()
|
v.Cancel()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -185,10 +197,11 @@ restrict the operation to specific snapshot names.`,
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
// Validate flags
|
// Validate flags
|
||||||
if !opts.KeepLatest && opts.OlderThan == "" {
|
if !opts.KeepLatest && opts.OlderThan == "" {
|
||||||
return fmt.Errorf("must specify either --keep-latest or --older-than")
|
return errors.New("must specify either --keep-latest or --older-than")
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.KeepLatest && opts.OlderThan != "" {
|
if opts.KeepLatest && opts.OlderThan != "" {
|
||||||
return fmt.Errorf("cannot specify both --keep-latest and --older-than")
|
return errors.New("cannot specify both --keep-latest and --older-than")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use unified config resolution
|
// Use unified config resolution
|
||||||
@@ -198,6 +211,7 @@ restrict the operation to specific snapshot names.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
rootFlags := GetRootFlags()
|
rootFlags := GetRootFlags()
|
||||||
|
|
||||||
return RunWithApp(cmd.Context(), AppOptions{
|
return RunWithApp(cmd.Context(), AppOptions{
|
||||||
ConfigPath: configPath,
|
ConfigPath: configPath,
|
||||||
LogOptions: log.LogOptions{
|
LogOptions: log.LogOptions{
|
||||||
@@ -211,21 +225,26 @@ restrict the operation to specific snapshot names.`,
|
|||||||
lc.Append(fx.Hook{
|
lc.Append(fx.Hook{
|
||||||
OnStart: func(ctx context.Context) error {
|
OnStart: func(ctx context.Context) error {
|
||||||
go func() {
|
go func() {
|
||||||
if err := v.PurgeSnapshotsWithOptions(opts); err != nil {
|
err := v.PurgeSnapshotsWithOptions(opts)
|
||||||
if err != context.Canceled {
|
if err != nil {
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
log.Error("Failed to purge snapshots", "error", err)
|
log.Error("Failed to purge snapshots", "error", err)
|
||||||
ReportError("Failed to purge snapshots: %v", err)
|
ReportError("Failed to purge snapshots: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := v.Shutdowner.Shutdown(); err != nil {
|
|
||||||
|
err = v.Shutdowner.Shutdown()
|
||||||
|
if err != nil {
|
||||||
log.Error("Failed to shutdown", "error", err)
|
log.Error("Failed to shutdown", "error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
OnStop: func(ctx context.Context) error {
|
OnStop: func(ctx context.Context) error {
|
||||||
v.Cancel()
|
v.Cancel()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -254,11 +273,14 @@ func newSnapshotVerifyCommand() *cobra.Command {
|
|||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
_ = cmd.Help()
|
_ = cmd.Help()
|
||||||
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("snapshot ID required")
|
return errors.New("snapshot ID required")
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("expected 1 argument, got %d", len(args))
|
return fmt.Errorf("expected 1 argument, got %d", len(args))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
@@ -271,6 +293,7 @@ func newSnapshotVerifyCommand() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rootFlags := GetRootFlags()
|
rootFlags := GetRootFlags()
|
||||||
|
|
||||||
return RunWithApp(cmd.Context(), AppOptions{
|
return RunWithApp(cmd.Context(), AppOptions{
|
||||||
ConfigPath: configPath,
|
ConfigPath: configPath,
|
||||||
LogOptions: log.LogOptions{
|
LogOptions: log.LogOptions{
|
||||||
@@ -284,23 +307,29 @@ func newSnapshotVerifyCommand() *cobra.Command {
|
|||||||
lc.Append(fx.Hook{
|
lc.Append(fx.Hook{
|
||||||
OnStart: func(ctx context.Context) error {
|
OnStart: func(ctx context.Context) error {
|
||||||
go func() {
|
go func() {
|
||||||
if err := v.VerifySnapshotWithOptions(snapshotID, opts); err != nil {
|
err := v.VerifySnapshotWithOptions(snapshotID, opts)
|
||||||
if err != context.Canceled {
|
if err != nil {
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
if !opts.JSON {
|
if !opts.JSON {
|
||||||
log.Error("Verification failed", "error", err)
|
log.Error("Verification failed", "error", err)
|
||||||
ReportError("Verification failed: %v", err)
|
ReportError("Verification failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := v.Shutdowner.Shutdown(); err != nil {
|
|
||||||
|
err = v.Shutdowner.Shutdown()
|
||||||
|
if err != nil {
|
||||||
log.Error("Failed to shutdown", "error", err)
|
log.Error("Failed to shutdown", "error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
OnStop: func(ctx context.Context) error {
|
OnStop: func(ctx context.Context) error {
|
||||||
v.Cancel()
|
v.Cancel()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -345,11 +374,14 @@ nuke --force' — it is the single supported entry point for that.`,
|
|||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
_ = cmd.Help()
|
_ = cmd.Help()
|
||||||
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("snapshot ID required")
|
return errors.New("snapshot ID required")
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("expected 1 argument, got %d", len(args))
|
return fmt.Errorf("expected 1 argument, got %d", len(args))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
@@ -360,6 +392,7 @@ nuke --force' — it is the single supported entry point for that.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
rootFlags := GetRootFlags()
|
rootFlags := GetRootFlags()
|
||||||
|
|
||||||
return RunWithApp(cmd.Context(), AppOptions{
|
return RunWithApp(cmd.Context(), AppOptions{
|
||||||
ConfigPath: configPath,
|
ConfigPath: configPath,
|
||||||
LogOptions: log.LogOptions{
|
LogOptions: log.LogOptions{
|
||||||
@@ -375,22 +408,26 @@ nuke --force' — it is the single supported entry point for that.`,
|
|||||||
go func() {
|
go func() {
|
||||||
_, err := v.RemoveSnapshot(args[0], opts)
|
_, err := v.RemoveSnapshot(args[0], opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != context.Canceled {
|
if !errors.Is(err, context.Canceled) {
|
||||||
if !opts.JSON {
|
if !opts.JSON {
|
||||||
log.Error("Failed to remove snapshot", "error", err)
|
log.Error("Failed to remove snapshot", "error", err)
|
||||||
ReportError("Failed to remove snapshot: %v", err)
|
ReportError("Failed to remove snapshot: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := v.Shutdowner.Shutdown(); err != nil {
|
if err := v.Shutdowner.Shutdown(); err != nil {
|
||||||
log.Error("Failed to shutdown", "error", err)
|
log.Error("Failed to shutdown", "error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
OnStop: func(ctx context.Context) error {
|
OnStop: func(ctx context.Context) error {
|
||||||
v.Cancel()
|
v.Cancel()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -70,6 +71,7 @@ Examples:
|
|||||||
// runRestore parses arguments and runs the restore operation through the app framework
|
// runRestore parses arguments and runs the restore operation through the app framework
|
||||||
func runRestore(cmd *cobra.Command, args []string, opts *RestoreOptions) error {
|
func runRestore(cmd *cobra.Command, args []string, opts *RestoreOptions) error {
|
||||||
snapshotID := args[0]
|
snapshotID := args[0]
|
||||||
|
|
||||||
opts.TargetDir = args[1]
|
opts.TargetDir = args[1]
|
||||||
if len(args) > 2 {
|
if len(args) > 2 {
|
||||||
opts.Paths = args[2:]
|
opts.Paths = args[2:]
|
||||||
@@ -83,6 +85,7 @@ func runRestore(cmd *cobra.Command, args []string, opts *RestoreOptions) error {
|
|||||||
|
|
||||||
// Use the app framework like other commands
|
// Use the app framework like other commands
|
||||||
rootFlags := GetRootFlags()
|
rootFlags := GetRootFlags()
|
||||||
|
|
||||||
return RunWithApp(cmd.Context(), AppOptions{
|
return RunWithApp(cmd.Context(), AppOptions{
|
||||||
ConfigPath: configPath,
|
ConfigPath: configPath,
|
||||||
LogOptions: log.LogOptions{
|
LogOptions: log.LogOptions{
|
||||||
@@ -129,8 +132,10 @@ func buildRestoreInvokes(snapshotID string, opts *RestoreOptions) []fx.Option {
|
|||||||
Verify: opts.Verify,
|
Verify: opts.Verify,
|
||||||
SkipErrors: GetRootFlags().SkipErrors,
|
SkipErrors: GetRootFlags().SkipErrors,
|
||||||
}
|
}
|
||||||
if err := app.Vaultik.Restore(restoreOpts); err != nil {
|
|
||||||
if err != context.Canceled {
|
err := app.Vaultik.Restore(restoreOpts)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
log.Error("Restore operation failed", "error", err)
|
log.Error("Restore operation failed", "error", err)
|
||||||
ReportError("Restore failed: %v", err)
|
ReportError("Restore failed: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -138,15 +143,18 @@ func buildRestoreInvokes(snapshotID string, opts *RestoreOptions) []fx.Option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown the app when restore completes
|
// Shutdown the app when restore completes
|
||||||
if err := app.Shutdowner.Shutdown(); err != nil {
|
err = app.Shutdowner.Shutdown()
|
||||||
|
if err != nil {
|
||||||
log.Error("Failed to shutdown", "error", err)
|
log.Error("Failed to shutdown", "error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
OnStop: func(ctx context.Context) error {
|
OnStop: func(ctx context.Context) error {
|
||||||
log.Debug("Stopping restore operation")
|
log.Debug("Stopping restore operation")
|
||||||
app.Vaultik.Cancel()
|
app.Vaultik.Cancel()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ func NewVersionCommand() *cobra.Command {
|
|||||||
fmt.Printf(" author: %s\n", globals.Author)
|
fmt.Printf(" author: %s\n", globals.Author)
|
||||||
fmt.Printf(" homepage: %s\n", globals.Homepage)
|
fmt.Printf(" homepage: %s\n", globals.Homepage)
|
||||||
fmt.Printf(" license: %s\n", globals.License)
|
fmt.Printf(" license: %s\n", globals.License)
|
||||||
|
|
||||||
if globals.Version == "dev" {
|
if globals.Version == "dev" {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("This is a development build (no version information embedded).")
|
fmt.Println("This is a development build (no version information embedded).")
|
||||||
|
|||||||
Reference in New Issue
Block a user