package main import ( "encoding/json" "fmt" "log" "os" "os/exec" "strings" "github.com/spf13/cobra" "debian-bootc-image-builder/internal/config" "debian-bootc-image-builder/internal/ux" ) func main() { // Global flags var verbose, debug, runDiagnostics bool rootCmd := &cobra.Command{ Use: "debian-bootc-image-builder", Short: "Create a bootable image from a Debian bootc container", Long: `Create a bootable image from a Debian bootc container. This tool builds bootable disk images from Debian bootc containers using APT for package management and OSBuild for image generation. Examples: # Build a qcow2 image debian-bootc-image-builder build git.raines.xyz/particle-os/debian-bootc:latest # Build multiple image types debian-bootc-image-builder build --type qcow2 --type ami git.raines.xyz/particle-os/debian-bootc:latest # Run system diagnostics debian-bootc-image-builder diagnose # Show detailed help debian-bootc-image-builder build --help`, } // Add global flags rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose output") rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Enable debug output (includes verbose)") rootCmd.PersistentFlags().BoolVar(&runDiagnostics, "diagnose", false, "Run system diagnostics before operations") // Load configuration cfg, err := config.LoadConfig("") if err != nil { if verbose || debug { log.Printf("Warning: Could not load configuration: %v", err) log.Println("Using default settings...") } } buildCmd := &cobra.Command{ Use: "build IMAGE_NAME", Short: "Build a bootable image from a Debian bootc container", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { // Enable debug mode if requested if debug { verbose = true } // Run diagnostics if requested if runDiagnostics { ux.PrintInfo(os.Stdout, "Running system diagnostics...") troubleshooter := ux.NewTroubleshootingGuide(verbose) results := troubleshooter.RunDiagnostics() troubleshooter.PrintDiagnostics(results) // Check for critical issues criticalIssues := 0 for _, result := range results { if result.Critical && strings.Contains(result.Status, "❌") { criticalIssues++ } } if criticalIssues > 0 { return ux.NewUserError( ux.ErrorTypeValidation, fmt.Sprintf("Found %d critical system issues", criticalIssues), "System diagnostics failed", "Resolve the critical issues above before proceeding", nil, ) } } // Validate inputs validator := ux.NewValidator(verbose) // Validate image reference imgref := args[0] if result := validator.ValidateImageReference(imgref); !result.Valid { validator.PrintValidationResult(result) return ux.ValidationError(result.Message, nil) } // Get and validate command flags imageTypes, _ := cmd.Flags().GetStringArray("type") if result := validator.ValidateImageTypes(imageTypes); !result.Valid { validator.PrintValidationResult(result) return ux.ValidationError(result.Message, nil) } targetArch, _ := cmd.Flags().GetString("target-arch") if result := validator.ValidateArchitecture(targetArch); !result.Valid { validator.PrintValidationResult(result) return ux.ValidationError(result.Message, nil) } rootfsType, _ := cmd.Flags().GetString("rootfs") if result := validator.ValidateRootfsType(rootfsType); !result.Valid { validator.PrintValidationResult(result) return ux.ValidationError(result.Message, nil) } aptcache, _ := cmd.Flags().GetString("aptcache") if result := validator.ValidateDirectory(aptcache, "APT cache"); !result.Valid { validator.PrintValidationResult(result) return ux.ValidationError(result.Message, nil) } configPath, _ := cmd.Flags().GetString("config") if result := validator.ValidateConfigFile(configPath); !result.Valid { validator.PrintValidationResult(result) return ux.ValidationError(result.Message, nil) } distroDefPaths, _ := cmd.Flags().GetStringArray("distro-def-path") for _, path := range distroDefPaths { if result := validator.ValidateDistroDefPath(path); !result.Valid { validator.PrintValidationResult(result) return ux.ValidationError(result.Message, nil) } } // Create progress reporter progress := ux.NewProgressReporter(os.Stdout, verbose) progress.AddStep("validation", "Input validation") progress.AddStep("config", "Configuration loading") progress.AddStep("manifest", "Manifest generation") progress.AddStep("output", "Output file creation") // Start validation step progress.StartStep(0) progress.CompleteStep(0) // Configuration step progress.StartStep(1) if verbose { ux.PrintInfo(os.Stdout, fmt.Sprintf("Building image from: %s", imgref)) if cfg != nil { ux.PrintInfo(os.Stdout, fmt.Sprintf("Using configuration from: %s", cfg.ActiveRegistry)) registry, err := cfg.GetActiveRegistry() if err == nil { ux.PrintInfo(os.Stdout, fmt.Sprintf("Registry: %s/%s", registry.BaseURL, registry.Namespace)) } } } progress.CompleteStep(1) // Manifest generation step progress.StartStep(2) manifest, _, err := manifestFromCobra(cmd, args, nil) if err != nil { progress.FailStep(2, err) return ux.ManifestError("Cannot generate manifest", err) } progress.CompleteStep(2) // Output step progress.StartStep(3) outputDir := "./output" if err := os.MkdirAll(outputDir, 0755); err != nil { progress.FailStep(3, err) return ux.FilesystemError("Cannot create output directory", err) } manifestFile := fmt.Sprintf("%s/manifest.json", outputDir) manifestData, err := json.MarshalIndent(manifest, "", " ") if err != nil { progress.FailStep(3, err) return ux.ManifestError("Cannot marshal manifest", err) } if err := os.WriteFile(manifestFile, manifestData, 0644); err != nil { progress.FailStep(3, err) return ux.FilesystemError("Cannot write manifest file", err) } progress.CompleteStep(3) // OSBuild execution step progress.AddStep("osbuild", "OSBuild image generation") progress.StartStep(4) if verbose { ux.PrintInfo(os.Stdout, "Running OSBuild to generate image...") } // Run OSBuild with the generated manifest if err := runOSBuild(manifestFile, outputDir, verbose); err != nil { progress.FailStep(4, err) return ux.BuildError("OSBuild execution failed", err) } progress.CompleteStep(4) // Print summary progress.PrintSummary() ux.PrintSuccess(os.Stdout, fmt.Sprintf("Image built successfully in: %s", outputDir)) if verbose { ux.PrintInfo(os.Stdout, "Built using OSBuild with APT integration for Debian package management.") ux.PrintInfo(os.Stdout, "Check the output directory for generated image files.") } return nil }, } diagnoseCmd := &cobra.Command{ Use: "diagnose", Short: "Run system diagnostics to check prerequisites", Long: `Run comprehensive system diagnostics to check if your system is ready for building Debian bootc images. This command checks: - Required tools (apt-cache, podman, qemu-img, file) - System resources (disk space, memory) - File permissions - Network connectivity - Container runtime functionality`, RunE: func(cmd *cobra.Command, args []string) error { // Enable debug mode if requested if debug { verbose = true } ux.PrintInfo(os.Stdout, "Running comprehensive system diagnostics...") troubleshooter := ux.NewTroubleshootingGuide(verbose) results := troubleshooter.RunDiagnostics() troubleshooter.PrintDiagnostics(results) // Check for critical issues criticalIssues := 0 warnings := 0 for _, result := range results { if result.Critical && strings.Contains(result.Status, "❌") { criticalIssues++ } else if strings.Contains(result.Status, "⚠️") { warnings++ } } fmt.Println() if criticalIssues > 0 { ux.PrintError(os.Stdout, fmt.Sprintf("Found %d critical issues that must be resolved", criticalIssues)) ux.PrintInfo(os.Stdout, "Please resolve the critical issues above before building images.") return ux.NewUserError( ux.ErrorTypeValidation, fmt.Sprintf("System has %d critical issues", criticalIssues), "System diagnostics failed", "Resolve the critical issues above before proceeding", nil, ) } else if warnings > 0 { ux.PrintWarning(os.Stdout, fmt.Sprintf("Found %d warnings (non-critical)", warnings)) ux.PrintInfo(os.Stdout, "Your system is ready, but consider addressing the warnings above.") } else { ux.PrintSuccess(os.Stdout, "All diagnostics passed - your system is ready for building images!") } return nil }, } versionCmd := &cobra.Command{ Use: "version", Short: "Show version information", RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("debian-bootc-image-builder v0.1.0") fmt.Println("Debian bootc image builder with APT integration") fmt.Println("Built with comprehensive error handling and UX improvements") return nil }, } // Add flags to build command buildCmd.Flags().String("config", "", "Path to configuration file") buildCmd.Flags().StringArray("type", []string{"qcow2"}, "Image types to build") buildCmd.Flags().String("aptcache", "/aptcache", "APT cache directory") buildCmd.Flags().String("target-arch", "amd64", "Target architecture") buildCmd.Flags().String("rootfs", "", "Root filesystem type") buildCmd.Flags().String("build-container", "", "Use a custom container for the image build") buildCmd.Flags().StringArray("distro-def-path", []string{"./data/defs"}, "Path to distribution definition files") buildCmd.Flags().Bool("librepo", false, "Use librepo for package downloads") rootCmd.AddCommand(buildCmd) rootCmd.AddCommand(diagnoseCmd) rootCmd.AddCommand(versionCmd) if err := rootCmd.Execute(); err != nil { // Use our enhanced error formatting fmt.Print(ux.FormatError(err)) os.Exit(1) } } // runOSBuild executes OSBuild with the given manifest file func runOSBuild(manifestFile, outputDir string, verbose bool) error { // Use the system osbuild command cmd := exec.Command("osbuild", "--output-directory", outputDir, manifestFile) if verbose { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr } if err := cmd.Run(); err != nil { return fmt.Errorf("osbuild execution failed: %w", err) } return nil }