package cli import ( "fmt" "os" "os/exec" "path/filepath" "github.com/neutrino2211/at-rund/internal/vm" "github.com/spf13/cobra" ) var ( buildRuntime string buildTarget string ) var buildCmd = &cobra.Command{ Use: "build", Short: "Build runtime images", Long: `Builds container or Firecracker images from Nix runtime definitions.`, RunE: runBuild, } func init() { buildCmd.Flags().StringVarP(&buildRuntime, "runtime", "r", "", "build specific runtime (default: all)") buildCmd.Flags().StringVarP(&buildTarget, "target", "t", "container", "build target: container or firecracker") } func runBuild(cmd *cobra.Command, args []string) error { // Find runtimes to build flakePath := vm.GetFlakePath() if flakePath == "" { return fmt.Errorf("could not find nix/runtimes directory") } var runtimes []string if buildRuntime != "" { runtimes = []string{buildRuntime} } else { entries, err := os.ReadDir(flakePath) if err != nil { return fmt.Errorf("failed to read runtimes directory: %w", err) } for _, e := range entries { if e.IsDir() { // Check if it has a flake.nix if _, err := os.Stat(filepath.Join(flakePath, e.Name(), "flake.nix")); err == nil { runtimes = append(runtimes, e.Name()) } } } } if len(runtimes) == 0 { return fmt.Errorf("no runtimes found in %s", flakePath) } fmt.Printf("Building %d runtime(s) for %s: %v\n\n", len(runtimes), buildTarget, runtimes) switch buildTarget { case "container": return buildContainerImages(runtimes, flakePath) case "firecracker": return buildFirecrackerImages(runtimes, flakePath) default: return fmt.Errorf("unknown target: %s (valid: container, firecracker)", buildTarget) } } func buildContainerImages(runtimes []string, flakePath string) error { // Check nix is available if _, err := exec.LookPath("nix"); err != nil { return fmt.Errorf("nix not found in PATH") } // Check docker/podman is available containerRuntime := "docker" if _, err := exec.LookPath("docker"); err != nil { if _, err := exec.LookPath("podman"); err != nil { return fmt.Errorf("docker or podman required for container builds") } containerRuntime = "podman" } for _, runtime := range runtimes { imageName := fmt.Sprintf("at-rund-%s:latest", runtime) fmt.Printf("Building %s...\n", imageName) runtimeDir := filepath.Join(flakePath, runtime) // Step 1: Build container image with Nix fmt.Printf(" [1/2] Building OCI image with Nix...\n") resultLink := filepath.Join(os.TempDir(), fmt.Sprintf("at-rund-%s-container", runtime)) nixCmd := exec.Command("nix", "build", runtimeDir+"#container", "--out-link", resultLink) nixCmd.Stdout = os.Stdout nixCmd.Stderr = os.Stderr if err := nixCmd.Run(); err != nil { return fmt.Errorf("nix build failed for %s: %w", runtime, err) } // Step 2: Load into Docker/Podman fmt.Printf(" [2/2] Loading image into %s...\n", containerRuntime) loadCmd := exec.Command(containerRuntime, "load", "-i", resultLink) loadCmd.Stdout = os.Stdout loadCmd.Stderr = os.Stderr if err := loadCmd.Run(); err != nil { return fmt.Errorf("failed to load image for %s: %w", runtime, err) } // Clean up the result link os.Remove(resultLink) fmt.Printf(" ✓ Built %s\n", imageName) } fmt.Println("\nContainer images built successfully.") return nil } func buildFirecrackerImages(runtimes []string, flakePath string) error { // Check nix is available if _, err := exec.LookPath("nix"); err != nil { return fmt.Errorf("nix not found in PATH") } dir := configDir() imagesDir := filepath.Join(dir, "images") for _, runtime := range runtimes { fmt.Printf("Building %s for Firecracker...\n", runtime) outputDir := filepath.Join(imagesDir, runtime) if err := os.MkdirAll(outputDir, 0755); err != nil { return fmt.Errorf("failed to create output dir: %w", err) } // TODO: Implement actual Firecracker image build // nix build path/to/runtime#firecracker -o ${outputDir} fmt.Printf(" [stub] Firecracker build not yet implemented\n") fmt.Printf(" [stub] Would output to %s/kernel, %s/rootfs.ext4\n", outputDir, outputDir) } return nil }