package raid import ( "fmt" "os" "os/exec" "strconv" "strings" ) /* mdadm manager This script handles the interaction with mdadm */ type StorageDeviceMeta struct { Name string Size int64 RO bool DevType string MountPoint string } // List all the storage device in the system, set minSize to 0 for no filter func (m *Manager) ListAllStorageDevices(minSize int64) ([]*StorageDeviceMeta, error) { cmd := exec.Command("sudo", "lsblk", "-b") output, err := cmd.CombinedOutput() if err != nil { return nil, fmt.Errorf("lsblk error: %v", err) } // Split the output into lines lines := strings.Split(string(output), "\n") var devices []*StorageDeviceMeta // Parse each line to extract device information for _, line := range lines[1:] { // Skip the header line fields := strings.Fields(line) if len(fields) < 7 { continue } size, err := strconv.ParseInt(fields[3], 10, 64) if err != nil { return nil, fmt.Errorf("error parsing device size: %v", err) } ro := fields[4] == "1" device := &StorageDeviceMeta{ Name: fields[0], Size: size, RO: ro, DevType: fields[5], MountPoint: fields[6], } // Filter devices based on minimum size if size >= minSize { devices = append(devices, device) } } return devices, nil } // Return the uuid of the disk by its path name (e.g. /dev/sda) func (m *Manager) GetDiskUUIDByPath(devicePath string) (string, error) { cmd := exec.Command("sudo", "blkid", devicePath) output, err := cmd.CombinedOutput() if err != nil { return "", fmt.Errorf("blkid error: %v", err) } // Parse the output to extract the UUID fields := strings.Fields(string(output)) for _, field := range fields { if strings.HasPrefix(field, "UUID=") { uuid := strings.TrimPrefix(field, "UUID=\"") uuid = strings.TrimSuffix(uuid, "\"") return uuid, nil } } return "", fmt.Errorf("UUID not found for device %s", devicePath) } // CreateRAIDDevice creates a RAID device using the mdadm command. func (m *Manager) CreateRAIDDevice(devName string, raidLevel int, raidDeviceIds []string, spareDeviceIds []string) error { //Calculate the size of the raid devices raidDev := len(raidDeviceIds) spareDevice := len(spareDeviceIds) //Validate the number of disk is enough for the raid if raidLevel == 0 && raidDev < 2 { return fmt.Errorf("not enough disks for raid0") } else if raidLevel == 1 && raidDev < 2 { return fmt.Errorf("not enough disks for raid1") } else if raidLevel == 5 && raidDev < 3 { return fmt.Errorf("not enough disk for raid5") } else if raidLevel == 6 && raidDev < 4 { return fmt.Errorf("not enough disk for raid6") } // Concatenate RAID and spare device arrays allDeviceIds := append(raidDeviceIds, spareDeviceIds...) // Build the mdadm command cmdArgs := []string{"mdadm", "--create", devName, fmt.Sprintf("--level=%d", raidLevel), fmt.Sprintf("--raid-devices=%d", raidDev), fmt.Sprintf("--spare-devices=%d", spareDevice)} cmdArgs = append(cmdArgs, allDeviceIds...) cmd := exec.Command("sudo", cmdArgs...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr fmt.Println(cmdArgs) err := cmd.Run() if err != nil { return fmt.Errorf("error running mdadm command: %v", err) } return nil } // DANGER: Wipe the whole disk given the disk path func (m *Manager) WipeDisk(diskPath string) error { // Unmount the disk isMounted, _ := DeviceIsMounted(diskPath) if isMounted { umountCmd := exec.Command("sudo", "umount", diskPath) if err := umountCmd.Run(); err != nil { return fmt.Errorf("error unmounting disk %s: %v", diskPath, err) } } // Wipe all filesystem signatures on each of the partitions wipeCmd1 := exec.Command("sudo", "wipefs", "--all", "--force", diskPath+"?") if err := wipeCmd1.Run(); err != nil { return fmt.Errorf("error wiping filesystem signatures on %s?: %v", diskPath, err) } // Wipe all filesystem signatures on the entire disk wipeCmd2 := exec.Command("sudo", "wipefs", "--all", "--force", diskPath) if err := wipeCmd2.Run(); err != nil { return fmt.Errorf("error wiping filesystem signatures on %s: %v", diskPath, err) } return nil }