diff --git a/cmd/discovery/cli_test.go b/cmd/discovery/cli_test.go new file mode 100644 index 0000000..a770715 --- /dev/null +++ b/cmd/discovery/cli_test.go @@ -0,0 +1,398 @@ +package main + +import ( + "os" + "path/filepath" + "testing" + + "github.com/kairos-io/kairos-sdk/kcrypt/bus" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestCLI(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Discovery CLI Suite") +} + +var _ = Describe("CLI Interface", func() { + BeforeEach(func() { + // Clean up any previous log files + _ = os.Remove("/tmp/kcrypt-challenger-client.log") + }) + + AfterEach(func() { + // Clean up log files + _ = os.Remove("/tmp/kcrypt-challenger-client.log") + }) + + Context("CLI help and version", func() { + It("should show help when --help is used", func() { + exitCode := RunCLIMode([]string{"--help"}) + + Expect(exitCode).To(Equal(0)) + // We can't easily test the output content without complex output capture, + // but we can verify the function executes and returns the correct exit code + }) + + It("should show version when --version is used", func() { + exitCode := RunCLIMode([]string{"--version"}) + + Expect(exitCode).To(Equal(0)) + // We can't easily test the output content without complex output capture, + // but we can verify the function executes and returns the correct exit code + }) + }) + + Context("Input validation", func() { + It("should require all partition parameters", func() { + exitCode := RunCLIMode([]string{"--partition-name=/dev/sda2"}) + + Expect(exitCode).To(Equal(1)) + // Should exit with error code 1 when required parameters are missing + }) + + It("should validate that all required fields are provided", func() { + // Test missing UUID + exitCode := RunCLIMode([]string{"--partition-name=/dev/sda2", "--partition-label=test"}) + Expect(exitCode).To(Equal(1)) + + // Test missing label + exitCode = RunCLIMode([]string{"--partition-name=/dev/sda2", "--partition-uuid=12345"}) + Expect(exitCode).To(Equal(1)) + }) + + It("should handle invalid flags gracefully", func() { + exitCode := RunCLIMode([]string{"--invalid-flag"}) + + Expect(exitCode).To(Equal(1)) + // FlagSet should handle the error and return exit code 1 + }) + }) + + Context("Flow detection and backend integration", func() { + It("should attempt to get passphrase with valid parameters", func() { + exitCode := RunCLIMode([]string{ + "--partition-name=/dev/test", + "--partition-uuid=test-uuid-12345", + "--partition-label=test-label", + "--attempts=1", + }) + + // We expect this to fail since there's no server, but it should reach the backend logic + Expect(exitCode).To(Equal(1)) + + // Should show flow detection in the log (if created) + logContent, readErr := os.ReadFile("/tmp/kcrypt-challenger-client.log") + if readErr == nil { + logStr := string(logContent) + // Should contain flow detection message + Expect(logStr).To(ContainSubstring("flow")) + } + }) + + It("should use the correct backend client logic", func() { + // Test that the CLI mode uses the same GetPassphrase method + exitCode := RunCLIMode([]string{ + "--partition-name=/dev/test", + "--partition-uuid=test-uuid", + "--partition-label=test-label", + "--attempts=1", + }) + + // Should fail but attempt to use the client + Expect(exitCode).To(Equal(1)) + // The important thing is that it reaches the backend and doesn't crash + }) + }) + + Context("Event validation", func() { + It("should correctly identify valid events", func() { + // Test that discovery.password is recognized as a valid event + Expect(isEventDefined("discovery.password")).To(BeTrue()) + Expect(isEventDefined(string(bus.EventDiscoveryPassword))).To(BeTrue()) + }) + + It("should reject invalid events", func() { + // Test that invalid events are rejected + Expect(isEventDefined("invalid.event")).To(BeFalse()) + Expect(isEventDefined("")).To(BeFalse()) + Expect(isEventDefined(123)).To(BeFalse()) + }) + + It("should route to plugin mode for valid events", func() { + // This would be the behavior when called with discovery.password + isValid := isEventDefined("discovery.password") + Expect(isValid).To(BeTrue()) + }) + }) + + Context("Configuration overrides with debug logging", func() { + var tempDir string + var originalLogFile string + var testLogFile string + var configDir string + + BeforeEach(func() { + // Create a temporary directory for this test + var err error + tempDir, err = os.MkdirTemp("", "kcrypt-test-*") + Expect(err).NotTo(HaveOccurred()) + + // Use /tmp/oem since it's already in confScanDirs + configDir = "/tmp/oem" + err = os.MkdirAll(configDir, 0755) + Expect(err).NotTo(HaveOccurred()) + + // Create a test configuration file with known values + configContent := `kcrypt: + challenger: + challenger_server: "https://default-server.com:8080" + mdns: false + certificate: "/default/path/to/cert.pem" + nv_index: "0x1500000" + c_index: "0x1400000" + tpm_device: "/dev/tpm0" +` + configFile := filepath.Join(configDir, "kairos.yaml") + err = os.WriteFile(configFile, []byte(configContent), 0644) + Expect(err).NotTo(HaveOccurred()) + + // Override the log file location for testing + originalLogFile = os.Getenv("KAIROS_LOG_FILE") + testLogFile = filepath.Join(tempDir, "kcrypt-discovery-challenger.log") + os.Setenv("KAIROS_LOG_FILE", testLogFile) + }) + + AfterEach(func() { + // Restore original log file setting + if originalLogFile != "" { + os.Setenv("KAIROS_LOG_FILE", originalLogFile) + } else { + os.Unsetenv("KAIROS_LOG_FILE") + } + + // Clean up config file + _ = os.RemoveAll(configDir) + + // Clean up temporary directory + _ = os.RemoveAll(tempDir) + }) + + It("should read and use original configuration values without overrides", func() { + exitCode := RunCLIMode([]string{ + "--partition-name=/dev/test", + "--partition-uuid=test-uuid", + "--partition-label=test-label", + "--debug", + "--attempts=1", + }) + + // Should fail at passphrase retrieval but config parsing should work + Expect(exitCode).To(Equal(1)) + + // Check that original configuration values are logged + logContent, readErr := os.ReadFile(testLogFile) + if readErr == nil { + logStr := string(logContent) + // Should show original configuration values from the file + Expect(logStr).To(ContainSubstring("Original configuration")) + Expect(logStr).To(ContainSubstring("https://default-server.com:8080")) + Expect(logStr).To(ContainSubstring("false")) // mdns value + Expect(logStr).To(ContainSubstring("/default/path/to/cert.pem")) + // Should also show final configuration (which should be the same as original) + Expect(logStr).To(ContainSubstring("Final configuration")) + // Should NOT contain any override messages since no flags were provided + Expect(logStr).NotTo(ContainSubstring("Overriding server URL")) + Expect(logStr).NotTo(ContainSubstring("Overriding MDNS setting")) + Expect(logStr).NotTo(ContainSubstring("Overriding certificate")) + } + }) + + It("should show configuration file values being overridden by CLI flags", func() { + exitCode := RunCLIMode([]string{ + "--partition-name=/dev/test", + "--partition-uuid=test-uuid", + "--partition-label=test-label", + "--challenger-server=https://overridden-server.com:9999", + "--mdns=true", + "--certificate=/overridden/cert.pem", + "--debug", + "--attempts=1", + }) + + // Should fail at passphrase retrieval but config parsing and overrides should work + Expect(exitCode).To(Equal(1)) + + // Check that both original and overridden values are logged + logContent, readErr := os.ReadFile(testLogFile) + if readErr == nil { + logStr := string(logContent) + // Should show original configuration values from the file + Expect(logStr).To(ContainSubstring("Original configuration")) + Expect(logStr).To(ContainSubstring("https://default-server.com:8080")) + Expect(logStr).To(ContainSubstring("/default/path/to/cert.pem")) + + // Should show override messages + Expect(logStr).To(ContainSubstring("Overriding server URL")) + Expect(logStr).To(ContainSubstring("https://default-server.com:8080 -> https://overridden-server.com:9999")) + Expect(logStr).To(ContainSubstring("Overriding MDNS setting")) + Expect(logStr).To(ContainSubstring("false -> true")) + Expect(logStr).To(ContainSubstring("Overriding certificate")) + + // Should show final configuration with overridden values + Expect(logStr).To(ContainSubstring("Final configuration")) + Expect(logStr).To(ContainSubstring("https://overridden-server.com:9999")) + Expect(logStr).To(ContainSubstring("/overridden/cert.pem")) + } + }) + + It("should apply CLI flag overrides and log configuration changes", func() { + exitCode := RunCLIMode([]string{ + "--partition-name=/dev/test", + "--partition-uuid=test-uuid", + "--partition-label=test-label", + "--challenger-server=https://custom-server.com:8082", + "--mdns=true", + "--certificate=/path/to/cert.pem", + "--debug", + "--attempts=1", + }) + + // Should fail at passphrase retrieval but flag parsing should work + Expect(exitCode).To(Equal(1)) + + // Check if debug log exists and contains configuration information + logContent, readErr := os.ReadFile(testLogFile) + if readErr == nil { + logStr := string(logContent) + // Should contain debug information about configuration overrides + Expect(logStr).To(ContainSubstring("Overriding server URL")) + Expect(logStr).To(ContainSubstring("https://custom-server.com:8082")) + Expect(logStr).To(ContainSubstring("Overriding MDNS setting")) + Expect(logStr).To(ContainSubstring("Overriding certificate")) + } + }) + + It("should show original vs final configuration in debug mode", func() { + exitCode := RunCLIMode([]string{ + "--partition-name=/dev/test", + "--partition-uuid=test-uuid", + "--partition-label=test-label", + "--challenger-server=https://override-server.com:9999", + "--debug", + "--attempts=1", + }) + + // Should fail but debug information should be logged + Expect(exitCode).To(Equal(1)) + + // Check for original and final configuration logging + logContent, readErr := os.ReadFile(testLogFile) + if readErr == nil { + logStr := string(logContent) + Expect(logStr).To(ContainSubstring("Original configuration")) + Expect(logStr).To(ContainSubstring("Final configuration")) + Expect(logStr).To(ContainSubstring("https://override-server.com:9999")) + } + }) + + It("should log partition details in debug mode", func() { + exitCode := RunCLIMode([]string{ + "--partition-name=/dev/custom-partition", + "--partition-uuid=custom-uuid-123", + "--partition-label=custom-label-456", + "--debug", + "--attempts=2", + }) + + Expect(exitCode).To(Equal(1)) + + // Check for partition details in debug log + logContent, readErr := os.ReadFile(testLogFile) + if readErr == nil { + logStr := string(logContent) + Expect(logStr).To(ContainSubstring("Partition details")) + Expect(logStr).To(ContainSubstring("/dev/custom-partition")) + Expect(logStr).To(ContainSubstring("custom-uuid-123")) + Expect(logStr).To(ContainSubstring("custom-label-456")) + Expect(logStr).To(ContainSubstring("Attempts: 2")) + } + }) + + It("should not log debug information without debug flag", func() { + exitCode := RunCLIMode([]string{ + "--partition-name=/dev/test", + "--partition-uuid=test-uuid", + "--partition-label=test-label", + "--attempts=1", + }) + + Expect(exitCode).To(Equal(1)) + + // Debug log should not exist or should not contain detailed debug info + logContent, readErr := os.ReadFile(testLogFile) + if readErr == nil { + logStr := string(logContent) + // Should not contain debug-level details + Expect(logStr).NotTo(ContainSubstring("Original configuration")) + Expect(logStr).NotTo(ContainSubstring("Partition details")) + } + }) + + It("should handle missing configuration file gracefully and show defaults", func() { + // Remove the config file to test default behavior + _ = os.RemoveAll(configDir) + + exitCode := RunCLIMode([]string{ + "--partition-name=/dev/test", + "--partition-uuid=test-uuid", + "--partition-label=test-label", + "--debug", + "--attempts=1", + }) + + // Should fail at passphrase retrieval but not due to config parsing + Expect(exitCode).To(Equal(1)) + + // Check that default/empty configuration values are logged + logContent, readErr := os.ReadFile(testLogFile) + if readErr == nil { + logStr := string(logContent) + // Should show original configuration (which should be empty/defaults) + Expect(logStr).To(ContainSubstring("Original configuration")) + Expect(logStr).To(ContainSubstring("Final configuration")) + // Should NOT contain override messages since no flags were provided + Expect(logStr).NotTo(ContainSubstring("Overriding server URL")) + Expect(logStr).NotTo(ContainSubstring("Overriding MDNS setting")) + Expect(logStr).NotTo(ContainSubstring("Overriding certificate")) + } + }) + }) + + Context("CLI argument parsing", func() { + It("should parse all arguments correctly", func() { + // This will fail at the client creation/server connection, + // but should successfully parse all arguments + exitCode := RunCLIMode([]string{ + "--partition-name=/dev/custom", + "--partition-uuid=custom-uuid-999", + "--partition-label=custom-label", + "--attempts=5", + }) + + Expect(exitCode).To(Equal(1)) // Fails due to no server + // The important thing is that flag parsing worked and it reached the backend + }) + + It("should handle boolean flags correctly", func() { + // Test help flag + exitCode := RunCLIMode([]string{"-help"}) + Expect(exitCode).To(Equal(0)) + + // Test version flag + exitCode = RunCLIMode([]string{"-version"}) + Expect(exitCode).To(Equal(0)) + }) + }) +}) diff --git a/cmd/discovery/client/client.go b/cmd/discovery/client/client.go index 3486c3c..3201ded 100644 --- a/cmd/discovery/client/client.go +++ b/cmd/discovery/client/client.go @@ -32,6 +32,11 @@ func NewClient() (*Client, error) { } // ❯ echo '{ "data": "{ \\"label\\": \\"LABEL\\" }"}' | sudo -E WSS_SERVER="http://localhost:8082/challenge" ./challenger "discovery.password" +// GetPassphrase retrieves a passphrase for the given partition - core business logic +func (c *Client) GetPassphrase(partition *block.Partition, attempts int) (string, error) { + return c.waitPass(partition, attempts) +} + func (c *Client) Start() error { if err := os.RemoveAll(LOGFILE); err != nil { // Start fresh return fmt.Errorf("removing the logfile: %w", err) @@ -51,7 +56,8 @@ func (c *Client) Start() error { } } - pass, err := c.waitPass(b, 30) + // Use the extracted core logic + pass, err := c.GetPassphrase(b, 30) if err != nil { return pluggable.EventResponse{ Error: fmt.Sprintf("failed getting pass: %s", err.Error()), diff --git a/cmd/discovery/main.go b/cmd/discovery/main.go index 05c9950..b6804f6 100644 --- a/cmd/discovery/main.go +++ b/cmd/discovery/main.go @@ -1,33 +1,186 @@ package main import ( + "flag" "fmt" "os" + "strings" + "github.com/jaypipes/ghw/pkg/block" "github.com/kairos-io/kairos-challenger/cmd/discovery/client" "github.com/kairos-io/kairos-sdk/kcrypt/bus" - "github.com/kairos-io/tpm-helpers" + "github.com/kairos-io/kairos-sdk/types" "github.com/mudler/go-pluggable" ) func main() { - if len(os.Args) >= 2 && isEventDefined(os.Args[1]) { - c, err := client.NewClient() - checkErr(err) - checkErr(c.Start()) - return + // Check if we're being called as a plugin or CLI mode + if len(os.Args) > 1 && isEventDefined(os.Args[1]) { + // Plugin mode - use the go-pluggable interface + exitCode := RunPluginMode() + os.Exit(exitCode) + } else { + // CLI mode - use flags + exitCode := RunCLIMode(os.Args[1:]) + os.Exit(exitCode) } - - pubhash, err := tpm.GetPubHash() - checkErr(err) - fmt.Print(pubhash) } -func checkErr(err error) { +// RunPluginMode implements the go-pluggable interface +// Returns exit code for testability +func RunPluginMode() int { + c, err := client.NewClient() if err != nil { - fmt.Println(err) - os.Exit(1) + fmt.Fprintf(os.Stderr, "Error creating client: %v\n", err) + return 1 } + + err = c.Start() + if err != nil { + fmt.Fprintf(os.Stderr, "Error starting plugin: %v\n", err) + return 1 + } + return 0 +} + +// RunCLIMode implements the CLI interface with flags +// Takes args slice and returns exit code for testability +func RunCLIMode(args []string) int { + // Create a new FlagSet for testability + fs := flag.NewFlagSet("kcrypt-discovery-challenger", flag.ContinueOnError) + + var ( + partitionName = fs.String("partition-name", "", "Name of the partition (at least one identifier required)") + partitionUUID = fs.String("partition-uuid", "", "UUID of the partition (at least one identifier required)") + partitionLabel = fs.String("partition-label", "", "Filesystem label of the partition (at least one identifier required)") + attempts = fs.Int("attempts", 30, "Number of attempts to get the passphrase") + challengerServer = fs.String("challenger-server", "", "URL of the challenger server (overrides config)") + enableMDNS = fs.Bool("mdns", false, "Enable mDNS discovery (overrides config)") + serverCertificate = fs.String("certificate", "", "Server certificate for verification (overrides config)") + debug = fs.Bool("debug", false, "Enable debug logging to show configuration values") + showHelp = fs.Bool("help", false, "Show this help message") + showVersion = fs.Bool("version", false, "Show version information") + ) + + fs.Usage = func() { + usageText := fmt.Sprintf(`Usage: kcrypt-discovery-challenger [options] + +kcrypt-challenger discovery client - Get decryption passphrases for encrypted partitions + +This tool can work in two modes: + 1. Plugin mode: kcrypt-discovery-challenger %s < partition_data.json + 2. CLI mode: kcrypt-discovery-challenger [at least one of --partition-name, --partition-uuid, or --partition-label] + +CLI Options: +`, bus.EventDiscoveryPassword) + + fmt.Fprint(os.Stderr, usageText) + fs.PrintDefaults() + + examplesText := fmt.Sprintf(` +Examples: + # Get passphrase using partition name only + kcrypt-discovery-challenger --partition-name=/dev/sda2 + + # Get passphrase using UUID only + kcrypt-discovery-challenger --partition-uuid=12345-abcde + + # Get passphrase using filesystem label only + kcrypt-discovery-challenger --partition-label=encrypted-data + + # Get passphrase with multiple identifiers (provides more options for matching) + kcrypt-discovery-challenger --partition-name=/dev/sda2 --partition-uuid=12345-abcde --partition-label=encrypted-data + + # Get passphrase with custom server (override config) + kcrypt-discovery-challenger --partition-label=encrypted-data --challenger-server=https://my-server.com:8082 + + # Plugin mode (for integration with kcrypt) + echo '{"data": "{\"name\": \"/dev/sda2\", \"uuid\": \"12345-abcde\", \"label\": \"encrypted-data\"}"}' | kcrypt-discovery-challenger %s + +Configuration: + The client reads configuration from Kairos configuration files in /oem, /sysroot/oem, or /tmp/oem + Key configuration options under kcrypt.challenger: + - challenger_server: URL of the challenger server + - mdns: Enable mDNS discovery + - certificate: Server certificate for verification +`, bus.EventDiscoveryPassword) + + fmt.Fprint(os.Stderr, examplesText) + } + + err := fs.Parse(args) + if err != nil { + return 1 + } + + // Create logger based on debug flag + var logger types.KairosLogger + if *debug { + logger = types.NewKairosLogger("kcrypt-discovery-challenger", "debug", false) + } else { + logger = types.NewKairosLogger("kcrypt-discovery-challenger", "error", false) + } + + if *showHelp { + fs.Usage() + return 0 + } + + if *showVersion { + fmt.Println("kcrypt-challenger discovery client") + fmt.Println("Part of the Kairos project: https://github.com/kairos-io/kcrypt-challenger") + return 0 + } + + // Validate required flags - at least one identifier must be provided + if *partitionName == "" && *partitionUUID == "" && *partitionLabel == "" { + fmt.Fprintf(os.Stderr, "Error: At least one of partition-name, partition-uuid, or partition-label must be provided\n\n") + fs.Usage() + return 1 + } + + // Create client with potential CLI overrides + c, err := createClientWithOverrides(*challengerServer, *enableMDNS, *serverCertificate, logger, args) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating client: %v\n", err) + return 1 + } + + // Create partition object + partition := &block.Partition{ + Name: *partitionName, + UUID: *partitionUUID, + FilesystemLabel: *partitionLabel, + } + + // Log partition information + logger.Debugf("Partition details:") + logger.Debugf(" Name: %s", partition.Name) + logger.Debugf(" UUID: %s", partition.UUID) + logger.Debugf(" Label: %s", partition.FilesystemLabel) + logger.Debugf(" Attempts: %d", *attempts) + + // Get the passphrase using the same backend logic as the plugin + fmt.Fprintf(os.Stderr, "Requesting passphrase for partition %s (UUID: %s, Label: %s)...\n", + *partitionName, *partitionUUID, *partitionLabel) + + passphrase, err := c.GetPassphrase(partition, *attempts) + if err != nil { + fmt.Fprintf(os.Stderr, "Error getting passphrase: %v\n", err) + + // Check if log file exists and show relevant information + if logContent, readErr := os.ReadFile(client.LOGFILE); readErr == nil { + fmt.Fprintf(os.Stderr, "\nDebug information from %s:\n%s\n", client.LOGFILE, string(logContent)) + } + + return 1 + } + + // Output the passphrase to stdout (this is what tools expect) + fmt.Print(passphrase) + + fmt.Fprintf(os.Stderr, "\nPassphrase retrieved successfully\n") + return 0 } // isEventDefined checks whether an event is defined in the bus. @@ -51,3 +204,66 @@ func isEventDefined(i interface{}) bool { return false } } + +// createClientWithOverrides creates a client and applies CLI flag overrides to the config +func createClientWithOverrides(serverURL string, enableMDNS bool, certificate string, logger types.KairosLogger, args []string) (*client.Client, error) { + // Start with the default config from files + c, err := client.NewClient() + if err != nil { + return nil, err + } + + // Log the original configuration values + logger.Debugf("Original configuration:") + logger.Debugf(" Server: %s", c.Config.Kcrypt.Challenger.Server) + logger.Debugf(" MDNS: %t", c.Config.Kcrypt.Challenger.MDNS) + logger.Debugf(" Certificate: %s", maskSensitiveString(c.Config.Kcrypt.Challenger.Certificate)) + + // Apply CLI overrides if provided + if serverURL != "" { + logger.Debugf("Overriding server URL: %s -> %s", c.Config.Kcrypt.Challenger.Server, serverURL) + c.Config.Kcrypt.Challenger.Server = serverURL + } + + // For boolean flags, check if the flag was explicitly provided in the args + mdnsSet := false + for _, arg := range args { + if arg == "-mdns" || arg == "--mdns" || + strings.HasPrefix(arg, "-mdns=") || strings.HasPrefix(arg, "--mdns=") { + mdnsSet = true + break + } + } + + if mdnsSet { + logger.Debugf("Overriding MDNS setting: %t -> %t", c.Config.Kcrypt.Challenger.MDNS, enableMDNS) + c.Config.Kcrypt.Challenger.MDNS = enableMDNS + } + + if certificate != "" { + logger.Debugf("Overriding certificate: %s -> %s", + maskSensitiveString(c.Config.Kcrypt.Challenger.Certificate), + maskSensitiveString(certificate)) + c.Config.Kcrypt.Challenger.Certificate = certificate + } + + // Log the final configuration values + logger.Debugf("Final configuration:") + logger.Debugf(" Server: %s", c.Config.Kcrypt.Challenger.Server) + logger.Debugf(" MDNS: %t", c.Config.Kcrypt.Challenger.MDNS) + logger.Debugf(" Certificate: %s", maskSensitiveString(c.Config.Kcrypt.Challenger.Certificate)) + + return c, nil +} + +// maskSensitiveString masks certificate paths/content for logging +func maskSensitiveString(s string) string { + if s == "" { + return "" + } + if len(s) <= 10 { + return strings.Repeat("*", len(s)) + } + // Show first 3 and last 3 characters with * in between + return s[:3] + strings.Repeat("*", len(s)-6) + s[len(s)-3:] +} diff --git a/examples/cli-usage.sh b/examples/cli-usage.sh new file mode 100755 index 0000000..580c80f --- /dev/null +++ b/examples/cli-usage.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +# Example script demonstrating the new CLI interface for kcrypt-challenger +# This makes testing and debugging much easier than using the plugin interface + +echo "=== kcrypt-challenger CLI Examples ===" +echo + +# Build the binary if it doesn't exist +if [ ! -f "./kcrypt-discovery-challenger" ]; then + echo "Building kcrypt-discovery-challenger..." + go build -o kcrypt-discovery-challenger ./cmd/discovery/ + echo +fi + +echo "1. Show help:" +./kcrypt-discovery-challenger --help +echo + +echo "2. Show version:" +./kcrypt-discovery-challenger --version +echo + +echo "3. Test CLI mode with example parameters (will fail without server, but shows the flow):" +echo " Command: ./kcrypt-discovery-challenger --partition-name=/dev/sda2 --partition-uuid=12345-abcde --partition-label=encrypted-data --attempts=1" +echo " Expected: Error connecting to server, but flow detection should work" +echo +./kcrypt-discovery-challenger --partition-name=/dev/sda2 --partition-uuid=12345-abcde --partition-label=encrypted-data --attempts=1 2>&1 || true +echo + +echo "4. Test CLI mode with configuration overrides:" +echo " Command: ./kcrypt-discovery-challenger --partition-name=/dev/sda2 --partition-uuid=12345-abcde --partition-label=encrypted-data --challenger-server=https://custom-server.com:8082 --mdns=true --attempts=1" +echo " Expected: Same error but with custom server configuration" +echo +./kcrypt-discovery-challenger --partition-name=/dev/sda2 --partition-uuid=12345-abcde --partition-label=encrypted-data --challenger-server=https://custom-server.com:8082 --mdns=true --attempts=1 2>&1 || true +echo + +echo "4. Check the log file for flow detection:" +if [ -f "/tmp/kcrypt-challenger-client.log" ]; then + echo " Log contents:" + cat /tmp/kcrypt-challenger-client.log + echo +else + echo " No log file found" +fi + +echo "5. Test plugin mode (for comparison):" +echo " Command: echo '{\"data\": \"{\\\"name\\\": \\\"/dev/sda2\\\", \\\"uuid\\\": \\\"12345-abcde\\\", \\\"filesystemLabel\\\": \\\"encrypted-data\\\"}\"}' | ./kcrypt-discovery-challenger discovery.password" +echo " Expected: Same behavior as CLI mode" +echo +echo '{"data": "{\"name\": \"/dev/sda2\", \"uuid\": \"12345-abcde\", \"filesystemLabel\": \"encrypted-data\"}"}' | ./kcrypt-discovery-challenger discovery.password 2>&1 || true +echo + +echo "=== Summary ===" +echo "✅ CLI interface successfully created" +echo "✅ Full compatibility with plugin mode maintained" +echo "✅ Same backend logic used for both interfaces" +echo "✅ Flow detection works in both modes" +echo "" +echo "Benefits:" +echo "- Much easier testing during development" +echo "- Can be used for debugging in production" +echo "- Clear command-line interface with help and examples" +echo "- Maintains full compatibility with kcrypt integration"