package main import ( "bytes" "fmt" "os" "os/exec" "strconv" "strings" ) func findAvailableVMID(config *Config) (int, error) { sshCmd := func(args ...string) *exec.Cmd { fullArgs := []string{ "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", fmt.Sprintf("%s@%s", strings.Split(config.ProxmoxUser, "@")[0], config.ProxmoxHost), } fullArgs = append(fullArgs, args...) return exec.Command("ssh", fullArgs...) } cmd := sshCmd("qm", "list") var stdout bytes.Buffer cmd.Stdout = &stdout if err := cmd.Run(); err != nil { return 0, fmt.Errorf("failed to list VMs: %w", err) } usedIDs := make(map[int]bool) lines := strings.Split(stdout.String(), "\n") for _, line := range lines[1:] { fields := strings.Fields(line) if len(fields) > 0 { if vmid, err := strconv.Atoi(fields[0]); err == nil { usedIDs[vmid] = true } } } for vmid := 10000; vmid < 20000; vmid++ { if !usedIDs[vmid] { return vmid, nil } } return 0, fmt.Errorf("no available VM ID found in range 10000-19999") } func buildNetworkConfig(config *Config) string { netConfig := fmt.Sprintf("virtio,bridge=%s", config.Bridge) if config.VlanTag > 0 { netConfig += fmt.Sprintf(",tag=%d", config.VlanTag) } return netConfig } func createProxmoxVM(config *Config) error { fmt.Println("Creating Proxmox template...") sshCmd := func(args ...string) *exec.Cmd { fullArgs := []string{ "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", fmt.Sprintf("%s@%s", strings.Split(config.ProxmoxUser, "@")[0], config.ProxmoxHost), } fullArgs = append(fullArgs, args...) return exec.Command("ssh", fullArgs...) } scpCmd := func(src, dst string) *exec.Cmd { return exec.Command("scp", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", src, fmt.Sprintf("%s@%s:%s", strings.Split(config.ProxmoxUser, "@")[0], config.ProxmoxHost, dst), ) } imageName := fmt.Sprintf("vm-%d-disk-0.qcow2", config.VMID) remotePath := fmt.Sprintf("/tmp/%s", imageName) fmt.Println("Uploading image to Proxmox host...") cmd := scpCmd(config.ImageURL, remotePath) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { return fmt.Errorf("failed to upload image: %w", err) } fmt.Println("Creating VM and converting to template...") commands := [][]string{ {"qm", "create", fmt.Sprintf("%d", config.VMID), "--name", config.VMName, "--memory", fmt.Sprintf("%d", config.Memory), "--cores", fmt.Sprintf("%d", config.Cores), "--net0", buildNetworkConfig(config), }, {"qm", "importdisk", fmt.Sprintf("%d", config.VMID), remotePath, config.Storage}, {"qm", "set", fmt.Sprintf("%d", config.VMID), "--scsihw", "virtio-scsi-pci", "--scsi0", fmt.Sprintf("%s:vm-%d-disk-0", config.Storage, config.VMID), }, {"qm", "set", fmt.Sprintf("%d", config.VMID), "--ide2", fmt.Sprintf("%s:cloudinit", config.Storage), }, {"qm", "set", fmt.Sprintf("%d", config.VMID), "--boot", "c", "--bootdisk", "scsi0", }, {"qm", "set", fmt.Sprintf("%d", config.VMID), "--serial0", "socket", "--vga", "serial0", }, {"qm", "template", fmt.Sprintf("%d", config.VMID)}, {"rm", "-f", remotePath}, } for _, cmdArgs := range commands { fmt.Printf("Running: %s\n", strings.Join(cmdArgs, " ")) cmd := sshCmd(cmdArgs...) var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr if err := cmd.Run(); err != nil { fmt.Println(stdout.String()) fmt.Println(stderr.String()) return fmt.Errorf("failed to execute command '%s': %w", strings.Join(cmdArgs, " "), err) } if stdout.Len() > 0 { fmt.Println(stdout.String()) } } fmt.Printf("\nTemplate %s (ID: %d) created successfully!\n", config.VMName, config.VMID) fmt.Printf("You can clone it with: qm clone %d --name \n", config.VMID) return nil }