package main import ( "fmt" "os" "os/exec" "path/filepath" "strings" ) func main() { if len(os.Args) < 2 { fmt.Println("Usage: go run create-working-bootable.go [output-path]") os.Exit(1) } rootfsPath := os.Args[1] outputPath := "debian-working-bootable.img" if len(os.Args) > 2 { outputPath = os.Args[2] } fmt.Printf("Creating working bootable image from rootfs: %s\n", rootfsPath) fmt.Printf("Output: %s\n", outputPath) // Check if rootfs exists if _, err := os.Stat(rootfsPath); os.IsNotExist(err) { fmt.Printf("Error: rootfs path does not exist: %s\n", rootfsPath) os.Exit(1) } // Create a 5GB raw disk image imageSize := "5G" fmt.Printf("Creating %s raw disk image...\n", imageSize) // Use qemu-img to create the image cmd := exec.Command("qemu-img", "create", "-f", "raw", outputPath, imageSize) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Printf("Error creating image: %v\n", err) os.Exit(1) } // Create a loop device fmt.Println("Setting up loop device...") cmd = exec.Command("sudo", "losetup", "--find", "--show", outputPath) output, err := cmd.Output() if err != nil { fmt.Printf("Error setting up loop device: %v\n", err) os.Exit(1) } loopDevice := strings.TrimSpace(string(output)) fmt.Printf("Loop device: %s\n", loopDevice) // Clean up loop device on exit defer func() { fmt.Printf("Cleaning up loop device: %s\n", loopDevice) exec.Command("sudo", "losetup", "-d", loopDevice).Run() }() // Create partition table (GPT) fmt.Println("Creating GPT partition table...") cmd = exec.Command("sudo", "parted", loopDevice, "mklabel", "gpt") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Printf("Error creating partition table: %v\n", err) os.Exit(1) } // Create a single partition fmt.Println("Creating partition...") cmd = exec.Command("sudo", "parted", loopDevice, "mkpart", "primary", "ext4", "1MiB", "100%") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Printf("Error creating partition: %v\n", err) os.Exit(1) } // Get the partition device partitionDevice := loopDevice + "p1" fmt.Printf("Partition device: %s\n", partitionDevice) // Format the partition with ext4 fmt.Println("Formatting partition with ext4...") cmd = exec.Command("sudo", "mkfs.ext4", partitionDevice) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Printf("Error formatting partition: %v\n", err) os.Exit(1) } // Create mount point mountPoint := "/tmp/particle-os-mount" if err := os.MkdirAll(mountPoint, 0755); err != nil { fmt.Printf("Error creating mount point: %v\n", err) os.Exit(1) } // Mount the partition fmt.Printf("Mounting partition to %s...\n", mountPoint) cmd = exec.Command("sudo", "mount", partitionDevice, mountPoint) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Printf("Error mounting partition: %v\n", err) os.Exit(1) } // Clean up mount on exit defer func() { fmt.Printf("Unmounting %s...\n", mountPoint) exec.Command("sudo", "umount", mountPoint).Run() }() // Copy rootfs content fmt.Printf("Copying rootfs content from %s to %s...\n", rootfsPath, mountPoint) cmd = exec.Command("sudo", "cp", "-a", rootfsPath+"/.", mountPoint+"/") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Printf("Error copying rootfs: %v\n", err) os.Exit(1) } // Fix permissions after copy fmt.Println("Fixing permissions...") cmd = exec.Command("sudo", "chown", "-R", "root:root", mountPoint) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Printf("Warning: could not fix ownership: %v\n", err) } // Install a kernel using debootstrap or apt fmt.Println("Installing kernel and bootloader...") // Bind mount necessary directories for package installation bindDirs := []string{"/dev", "/proc", "/sys"} for _, dir := range bindDirs { bindMount := filepath.Join(mountPoint, dir) if err := exec.Command("sudo", "mount", "--bind", dir, bindMount).Run(); err != nil { fmt.Printf("Warning: could not bind mount %s: %v\n", dir, err) } } // Update package lists fmt.Println("Updating package lists...") cmd = exec.Command("sudo", "chroot", mountPoint, "apt", "update") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Printf("Warning: apt update failed: %v\n", err) } // Install kernel and essential packages fmt.Println("Installing kernel and essential packages...") kernelPackages := []string{ "linux-image-amd64", // Main kernel "linux-headers-amd64", // Kernel headers "grub-pc", // GRUB bootloader "grub-pc-bin", // GRUB binaries "os-prober", // OS detection "initramfs-tools", // Initial RAM disk "systemd-sysv", // Systemd "systemd", // Systemd "udev", // Device management "console-setup", // Console setup "keyboard-configuration", // Keyboard config "locales", // Locales "tzdata", // Timezone data } for _, pkg := range kernelPackages { fmt.Printf("Installing %s...\n", pkg) cmd = exec.Command("sudo", "chroot", mountPoint, "apt", "install", "-y", "--no-install-recommends", pkg) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Printf("Warning: failed to install %s: %v\n", pkg, err) } } // Generate initramfs fmt.Println("Generating initramfs...") cmd = exec.Command("sudo", "chroot", mountPoint, "update-initramfs", "-u", "-k", "all") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Printf("Warning: initramfs generation failed: %v\n", err) } // Install GRUB bootloader fmt.Println("Installing GRUB bootloader...") cmd = exec.Command("sudo", "chroot", mountPoint, "grub-install", "--target=i386-pc", loopDevice) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Printf("Warning: GRUB installation failed: %v\n", err) } else { fmt.Println("GRUB installed successfully") } // Generate GRUB config fmt.Println("Generating GRUB configuration...") cmd = exec.Command("sudo", "chroot", mountPoint, "update-grub") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Printf("Warning: GRUB config generation failed: %v\n", err) } // Create a simple fstab fmt.Println("Creating fstab...") fstabContent := `# /etc/fstab for particle-os /dev/sda1 / ext4 rw,errors=remount-ro 0 1 proc /proc proc defaults 0 0 sysfs /sys sysfs defaults 0 0 devpts /dev/pts devpts gid=5,mode=620 0 0 tmpfs /run tmpfs defaults 0 0 ` fstabFile := filepath.Join(mountPoint, "etc", "fstab") cmd = exec.Command("sudo", "sh", "-c", fmt.Sprintf("cat > %s << 'EOF'\n%sEOF", fstabFile, fstabContent)) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Printf("Warning: could not create fstab: %v\n", err) } // Unbind mount directories for _, dir := range bindDirs { bindMount := filepath.Join(mountPoint, dir) exec.Command("sudo", "umount", bindMount).Run() } fmt.Printf("\n✅ Working bootable image created successfully: %s\n", outputPath) fmt.Printf("Image size: %s\n", imageSize) fmt.Printf("Filesystem: ext4\n") fmt.Printf("Bootloader: GRUB with kernel\n") fmt.Printf("\nTo test the image:\n") fmt.Printf("qemu-system-x86_64 -m 2G -drive file=%s,format=raw -nographic -serial stdio\n", outputPath) }