diff --git a/wizard/cmd/wizard/main.go b/wizard/cmd/wizard/main.go index cee0bdb..295d04a 100644 --- a/wizard/cmd/wizard/main.go +++ b/wizard/cmd/wizard/main.go @@ -46,7 +46,7 @@ func run() error { switch args[0] { case "apply": - return commands.HandleApply() + return commands.HandleApply(*forcePtr) case "discover": return commands.HandleDiscover(*forcePtr) case "firedrill": diff --git a/wizard/internal/commands/apply.go b/wizard/internal/commands/apply.go index 4e435db..1789d9c 100644 --- a/wizard/internal/commands/apply.go +++ b/wizard/internal/commands/apply.go @@ -12,8 +12,8 @@ import ( "gopkg.in/yaml.v3" ) -func HandleApply() error { - applied, err := ApplyDesiredState() +func HandleApply(force bool) error { + applied, err := ApplyDesiredState(force) if err == nil && applied { fmt.Printf(" %sRun 'honeywire firedrill' to test deployed sensors.%s\n\n", cli.Dim, cli.Reset) } @@ -22,7 +22,7 @@ func HandleApply() error { // ApplyDesiredState reconciles the node against the Hub's desired state // and returns true if sensors were actually deployed. -func ApplyDesiredState() (bool, error) { +func ApplyDesiredState(force bool) (bool, error) { app, err := loadApp() if err != nil { return false, err @@ -88,6 +88,10 @@ func ApplyDesiredState() (bool, error) { return false, nil } + if err := runPreFlightChecks(force); err != nil { + return false, err + } + if err := deploy.Apply(ctx, composeData); err != nil { return false, fmt.Errorf("reconciliation failed: %w", err) } diff --git a/wizard/internal/commands/discover.go b/wizard/internal/commands/discover.go index ef3edc1..6275ead 100644 --- a/wizard/internal/commands/discover.go +++ b/wizard/internal/commands/discover.go @@ -233,7 +233,13 @@ func applySuggestions(app *app.App, recs []*discovery.Recommendation, force bool func runPreFlightChecks(force bool) error { var hasWarnings bool - checks := []func() (string, error){system.CheckMemory, system.CheckLoad, system.CheckDiskSpace} + checkDocker := func() (string, error) { + if _, err := deploy.ValidateDockerState(); err != nil { + return err.Error(), nil + } + return "", nil + } + checks := []func() (string, error){system.CheckMemory, system.CheckLoad, system.CheckDiskSpace, checkDocker} for _, check := range checks { if warning, err := check(); err == nil && warning != "" { fmt.Printf("%s%s%s\n", cli.Yellow, warning, cli.Reset) diff --git a/wizard/internal/commands/linking.go b/wizard/internal/commands/linking.go index 2e0b1cd..0b1308c 100644 --- a/wizard/internal/commands/linking.go +++ b/wizard/internal/commands/linking.go @@ -166,7 +166,7 @@ func linkExistingNode(hubURL, apiKey string, force bool) error { if cli.IsTerminal() { if nodeInfo.PendingConfig { if cli.ConfirmAction("Apply Hub's desired state now", force) { - applied, err := ApplyDesiredState() + applied, err := ApplyDesiredState(force) if err == nil && applied { if cli.ConfirmAction("Trigger a firedrill to test deployed sensors", force) { return HandleFiredrill() diff --git a/wizard/internal/commands/onboarding.go b/wizard/internal/commands/onboarding.go index 263f29c..1ab5022 100644 --- a/wizard/internal/commands/onboarding.go +++ b/wizard/internal/commands/onboarding.go @@ -74,7 +74,7 @@ func HandleInteractiveMenu(force bool) error { switch choice { case "1": - return HandleApply() + return HandleApply(force) case "2": return HandleDiscover(force) case "3": diff --git a/wizard/internal/commands/teardown.go b/wizard/internal/commands/teardown.go index 3ba156e..5d67860 100644 --- a/wizard/internal/commands/teardown.go +++ b/wizard/internal/commands/teardown.go @@ -33,6 +33,13 @@ func HandleTeardown(force bool) error { fmt.Printf("%s[!] Failed to remove config file: %v%s\n", cli.Yellow, err, cli.Reset) } - fmt.Printf("\n%s✅ All HoneyWire sensors and configurations have been successfully removed.%s\n\n", cli.Green, cli.Reset) + fmt.Printf("\n%s[*] Removing HoneyWire CLI binary...%s\n", cli.Dim, cli.Reset) + if execPath, err := os.Executable(); err == nil { + if err := os.Remove(execPath); err != nil && !os.IsNotExist(err) { + fmt.Printf("%s[!] Failed to remove CLI binary (%s): %v%s\n", cli.Yellow, execPath, err, cli.Reset) + } + } + + fmt.Printf("\n%s✅ All HoneyWire sensors, configurations, and the CLI tool have been successfully removed.%s\n\n", cli.Green, cli.Reset) return nil } diff --git a/wizard/internal/deploy/docker.go b/wizard/internal/deploy/docker.go index 329af25..194519d 100644 --- a/wizard/internal/deploy/docker.go +++ b/wizard/internal/deploy/docker.go @@ -62,11 +62,12 @@ func checkDaemon() error { ctx, cancel := context.WithTimeout(context.Background(), commandTimeout) defer cancel() - var stderr bytes.Buffer + var stdout, stderr bytes.Buffer // codeql[go/command-injection] Hardcoded/trusted CLI arguments. // nosemgrep: go.lang.security.audit.dangerous-exec-command.dangerous-exec-command cmd := exec.CommandContext(ctx, "docker", "info", "--format", "{{json .}}") + cmd.Stdout = &stdout cmd.Stderr = &stderr if err := cmd.Run(); err != nil { @@ -75,6 +76,22 @@ func checkDaemon() error { // We pass the actual system error to generateRemediationError now return generateRemediationError(mainReason, details, err) } + + var info struct { + SecurityOptions []string `json:"SecurityOptions"` + } + if err := json.Unmarshal(stdout.Bytes(), &info); err == nil { + for _, opt := range info.SecurityOptions { + if strings.Contains(strings.ToLower(opt), "rootless") { + return generateRemediationError( + "Docker is running in Rootless mode.", + "HoneyWire sensors require raw network access and iptables manipulation, which cannot be done in Rootless mode. Please install a standard Rootful Docker daemon.", + nil, + ) + } + } + } + return nil }