/ pkg / system / installation.go
installation.go
  1  package system
  2  
  3  import (
  4  	"bytes"
  5  	"fmt"
  6  	"io"
  7  	"log"
  8  	"os"
  9  	"os/exec"
 10  	"strings"
 11  
 12  	"github.com/dell/csi-baremetal/pkg/base/linuxutils/lsblk"
 13  	dogeboxd "github.com/dogeorg/dogeboxd/pkg"
 14  	"github.com/dogeorg/dogeboxd/pkg/utils"
 15  	"github.com/sirupsen/logrus"
 16  )
 17  
 18  const DBXRootSecret = "yes-i-will-destroy-everything-on-this-disk"
 19  
 20  func GetInstallationMode(dbxState dogeboxd.DogeboxState) (dogeboxd.BootstrapInstallationMode, error) {
 21  	// First, check if we're already installed.
 22  	if _, err := os.Stat("/opt/dbx-installed"); err == nil {
 23  		return dogeboxd.BootstrapInstallationModeIsInstalled, nil
 24  	} else if !os.IsNotExist(err) {
 25  		return "", fmt.Errorf("error checking for RO installation media: %v", err)
 26  	}
 27  
 28  	// If we're not already installed, but we've been configured, no install for you.
 29  	if dbxState.InitialState.HasFullyConfigured {
 30  		return dogeboxd.BootstrapInstallationModeCannotInstall, nil
 31  	}
 32  
 33  	// Check if we're running on RO installation media. If so, must install.
 34  	if _, err := os.Stat("/opt/ro-media"); err == nil {
 35  		return dogeboxd.BootstrapInstallationModeMustInstall, nil
 36  	} else if !os.IsNotExist(err) {
 37  		return "", fmt.Errorf("error checking for RO installation media: %v", err)
 38  	}
 39  
 40  	// Otherwise, the user can optionally install.
 41  	return dogeboxd.BootstrapInstallationModeCanInstalled, nil
 42  }
 43  
 44  const (
 45  	one_gigabyte            = 1024 * 1024 * 1024
 46  	ten_gigabytes           = 10 * one_gigabyte
 47  	three_hundred_gigabytes = 300 * one_gigabyte
 48  )
 49  
 50  func GetSystemDisks() ([]dogeboxd.SystemDisk, error) {
 51  	lsb := lsblk.NewLSBLK(logrus.New())
 52  
 53  	devices, err := lsb.GetBlockDevices("")
 54  	if err != nil {
 55  		return []dogeboxd.SystemDisk{}, err
 56  	}
 57  
 58  	disks := []dogeboxd.SystemDisk{}
 59  
 60  	for _, device := range devices {
 61  		disk := dogeboxd.SystemDisk{
 62  			Name:       device.Name,
 63  			Size:       device.Size.Int64,
 64  			SizePretty: utils.PrettyPrintDiskSize(device.Size.Int64),
 65  		}
 66  
 67  		// We will likely never see loop devices in the wild,
 68  		// but it's useful to support these for development.
 69  		isOKDevice := device.Type == "disk" || device.Type == "loop"
 70  
 71  		isMounted := device.MountPoint != ""
 72  		hasChildren := len(device.Children) > 0
 73  
 74  		isSuitableInstallSize := device.Size.Int64 >= ten_gigabytes
 75  		isSuitableStorageSize := device.Size.Int64 >= three_hundred_gigabytes
 76  
 77  		isSuitableDevice := isOKDevice && device.Size.Int64 > 0
 78  		isAlreadyUsed := isMounted || hasChildren
 79  
 80  		// This block package only seems to return a single mount point.
 81  		// So we need to check if we're mounted at either / or /nix/store
 82  		// to "reliably" determine if this is our boot media.
 83  		if device.MountPoint == "/" || device.MountPoint == "/nix/store" || device.MountPoint == "/nix/.ro-store" {
 84  			disk.BootMedia = true
 85  		}
 86  
 87  		// Check if any of our children are mounted as boot.
 88  		for _, child := range device.Children {
 89  			if child.MountPoint == "/" || child.MountPoint == "/nix/store" || device.MountPoint == "/nix/.ro-store" {
 90  				disk.BootMedia = true
 91  			}
 92  		}
 93  
 94  		// Even for devices we don't class as "usable" for storage, if we're
 95  		// booting off it, we need to let the user select it (ie. no external storage)
 96  		isUsableStorageDevice := isSuitableDevice || disk.BootMedia
 97  
 98  		disk.Suitability = dogeboxd.SystemDiskSuitability{
 99  			Install: dogeboxd.SystemDiskSuitabilityEntry{
100  				Usable: isSuitableDevice,
101  				SizeOK: isSuitableInstallSize,
102  			},
103  			Storage: dogeboxd.SystemDiskSuitabilityEntry{
104  				Usable: isUsableStorageDevice,
105  				SizeOK: isSuitableStorageSize,
106  			},
107  			IsAlreadyUsed: isAlreadyUsed,
108  		}
109  
110  		disks = append(disks, disk)
111  	}
112  
113  	return disks, nil
114  }
115  
116  func InitStorageDevice(dbxState dogeboxd.DogeboxState) (string, error) {
117  	if dbxState.StorageDevice == "" || dbxState.InitialState.HasFullyConfigured {
118  		return "", nil
119  	}
120  
121  	cmd := exec.Command("sudo", "_dbxroot", "prepare-storage-device", "--print", "--disk", dbxState.StorageDevice, "--dbx-secret", DBXRootSecret)
122  
123  	var out bytes.Buffer
124  	cmd.Stdout = io.MultiWriter(&out, os.Stdout)
125  	cmd.Stderr = io.MultiWriter(&out, os.Stderr)
126  
127  	// Execute the command
128  	err := cmd.Run()
129  	if err != nil {
130  		return "", fmt.Errorf("failed to execute _dbxroot prepare-storage-device: %w", err)
131  	}
132  
133  	output := out.String()
134  
135  	lines := strings.Split(strings.TrimSpace(output), "\n")
136  	partitionName := ""
137  	if len(lines) > 0 {
138  		lastLine := lines[len(lines)-1]
139  		parts := strings.Split(lastLine, " ")
140  		if len(parts) > 0 {
141  			partitionName = parts[len(parts)-1]
142  		}
143  	}
144  
145  	if partitionName == "" {
146  		return "", fmt.Errorf("failed to get partition name")
147  	}
148  
149  	return partitionName, nil
150  }
151  
152  func GetBuildType() (string, error) {
153  	buildType, err := os.ReadFile("/opt/build-type")
154  	if err != nil {
155  		if os.IsNotExist(err) {
156  			return "unknown", nil
157  		}
158  		return "", fmt.Errorf("failed to read build type: %w", err)
159  	}
160  	return strings.TrimSpace(string(buildType)), nil
161  }
162  
163  func InstallToDisk(config dogeboxd.ServerConfig, dbxState dogeboxd.DogeboxState, name string) error {
164  	if config.DevMode {
165  		log.Printf("Dev mode enabled, skipping installation. You probably do not want to do this. re-run without dev mode if you do.")
166  		return nil
167  	}
168  
169  	if !config.Recovery {
170  		return fmt.Errorf("installation can only be done in recovery mode")
171  	}
172  
173  	installMode, err := GetInstallationMode(dbxState)
174  	if err != nil {
175  		return err
176  	}
177  
178  	if installMode != dogeboxd.BootstrapInstallationModeMustInstall && installMode != dogeboxd.BootstrapInstallationModeCanInstalled {
179  		return fmt.Errorf("installation is not possible with current system state: %s", installMode)
180  	}
181  
182  	disks, err := GetSystemDisks()
183  	if err != nil {
184  		return err
185  	}
186  
187  	// Check if the specified disk name exists in possibleDisks
188  	diskExists := false
189  	for _, disk := range disks {
190  		if disk.Name == name && disk.Suitability.Install.Usable {
191  			diskExists = true
192  			break
193  		}
194  	}
195  
196  	if !diskExists {
197  		return fmt.Errorf("specified disk '%s' not found in list of possible install disks", name)
198  	}
199  
200  	buildType, err := GetBuildType()
201  	if err != nil {
202  		log.Printf("Failed to get build type: %v", err)
203  		return err
204  	}
205  
206  	log.Printf("Starting to install to disk %s", name)
207  
208  	var installFn func(string) error
209  
210  	installFn = dbxrootInstallToDisk
211  
212  	// For the T6, we need to write the root FS over the EMMC
213  	// with DD, as we need all the arm-specific bootloaders and such.
214  	if buildType == "nanopc-T6" {
215  		installFn = dbxrootDDToDisk
216  	}
217  
218  	if err := installFn(name); err != nil {
219  		log.Printf("Failed to install to disk: %v", err)
220  		return err
221  	}
222  
223  	log.Printf("Installation completed successfully")
224  
225  	return nil
226  }
227  
228  func dbxrootInstallToDisk(disk string) error {
229  	cmd := exec.Command("sudo", "_dbxroot", "install-to-disk", "--disk", disk, "--dbx-secret", DBXRootSecret)
230  	cmd.Stdout = os.Stdout
231  	cmd.Stderr = os.Stderr
232  	return cmd.Run()
233  }
234  
235  func dbxrootDDToDisk(toDisk string) error {
236  	cmd := exec.Command("sudo", "_dbxroot", "dd-to-disk", "--target-disk", toDisk, "--dbx-secret", DBXRootSecret)
237  	cmd.Stdout = os.Stdout
238  	cmd.Stderr = os.Stderr
239  	return cmd.Run()
240  }