deb-bootc-image-builder-new/bib/cmd/debian-bootc-image-builder/main.go
2025-09-05 07:10:12 -07:00

330 lines
10 KiB
Go

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
}