package storage import ( "compress/bzip2" "fmt" "log/slog" "os" "path/filepath" "runtime" "github.com/AlexSSD7/linsk/constants" "github.com/AlexSSD7/linsk/imgbuilder" "github.com/pkg/errors" ) type Storage struct { logger *slog.Logger path string } func NewStorage(logger *slog.Logger, dataDir string) (*Storage, error) { dataDir = filepath.Clean(dataDir) err := os.MkdirAll(dataDir, 0700) if err != nil { return nil, fmt.Errorf("mkdir all data dir") } return &Storage{ logger: logger, path: dataDir, }, nil } func (s *Storage) CheckDownloadBaseImage() (string, error) { baseImagePath := filepath.Join(s.path, constants.GetAlpineBaseImageFileName()) _, err := os.Stat(baseImagePath) if err != nil { if !errors.Is(err, os.ErrNotExist) { return "", errors.Wrap(err, "stat base image path") } // Image doesn't exist. Download one. err := s.download(constants.GetAlpineBaseImageURL(), constants.GetAlpineBaseImageHash(), baseImagePath, nil) if err != nil { return "", errors.Wrap(err, "download base alpine image") } return baseImagePath, nil } // Image exists. Ensure that the hash is correct. err = validateFileHash(baseImagePath, constants.GetAlpineBaseImageHash()) if err != nil { return "", errors.Wrap(err, "validate hash of existing image") } return baseImagePath, nil } func (s *Storage) GetVMImagePath() string { return filepath.Join(s.path, constants.GetVMImageTags()+".qcow2") } func (s *Storage) GetAarch64EFIImagePath() string { return filepath.Join(s.path, constants.GetAarch64EFIImageName()) } func (s *Storage) RunCLIImageBuild(showBuilderVMDisplay bool, overwrite bool) int { vmImagePath := s.GetVMImagePath() removed, err := checkExistsOrRemove(vmImagePath, overwrite) if err != nil { slog.Error("Failed to check for (or remove if overwrite mode is on) existing VM image", "error", err.Error()) return 1 } baseImagePath, err := s.CheckDownloadBaseImage() if err != nil { slog.Error("Failed to check or download base VM image", "error", err.Error()) return 1 } biosPath, err := s.CheckDownloadVMBIOS() if err != nil { slog.Error("Failed to check or download VM BIOS", "error", err.Error()) return 1 } s.logger.Info("Building VM image", "tags", constants.GetAlpineBaseImageTags(), "overwriting", removed, "dst", vmImagePath) buildCtx, err := imgbuilder.NewBuildContext(s.logger.With("subcaller", "imgbuilder"), baseImagePath, vmImagePath, showBuilderVMDisplay, biosPath) if err != nil { slog.Error("Failed to create new image build context", "error", err.Error()) return 1 } exitCode := buildCtx.RunCLIBuild() if exitCode != 0 { return exitCode } err = os.Remove(baseImagePath) if err != nil { s.logger.Error("Failed to remove base image", "error", err.Error(), "path", baseImagePath) } else { s.logger.Info("Removed base image", "path", baseImagePath) } return 0 } func (s *Storage) CheckVMImageExists() (string, error) { p := s.GetVMImagePath() _, err := os.Stat(p) if err != nil { if !errors.Is(err, os.ErrNotExist) { return "", errors.Wrap(err, "stat vm image path") } // Image doesn't exist. return "", nil } // Image exists. Returning the full path. return p, nil } func (s *Storage) DataDirPath() string { return s.path } func (s *Storage) CheckDownloadVMBIOS() (string, error) { if runtime.GOARCH == "arm64" { p, err := s.CheckDownloadAarch64EFIImage() if err != nil { return "", errors.Wrap(err, "check/download aarch64 efi image") } return p, nil } // On x86_64, there is no requirement to supply QEMU with any BIOS images. return "", nil } func (s *Storage) CheckDownloadAarch64EFIImage() (string, error) { efiImagePath := s.GetAarch64EFIImagePath() _, err := os.Stat(efiImagePath) if err != nil { if !errors.Is(err, os.ErrNotExist) { return "", errors.Wrap(err, "stat base image path") } // EFI image doesn't exist. Download one. err := s.download(constants.GetAarch64EFIImageBZ2URL(), constants.GetAarch64EFIImageHash(), efiImagePath, bzip2.NewReader) if err != nil { return "", errors.Wrap(err, "download base alpine image") } return efiImagePath, nil } // EFI image exists. Ensure that the hash is correct. err = validateFileHash(efiImagePath, constants.GetAarch64EFIImageHash()) if err != nil { return "", errors.Wrap(err, "validate hash of existing image") } return efiImagePath, nil }