330 lines
10 KiB
Go
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
|
|
}
|
|
|