package download import ( "fmt" "io" "net/http" "os" "path/filepath" "github.com/magefile/mage/sh" ) type Bundle struct { DestinationDir string Packages []Pack Checksum Checksum ValidateGPGSignature bool } type Pack struct { File Object GPGSignature Object } type Checksum struct { File Object Validate bool ValidateFunc func() error } type Object struct { Source string Destination string } // Download downloads all the files in the download pack, // verifies all the GPG signatures (if enabled) and // verifies the checksums (if enabled). func Download(bundle Bundle) error { if err := os.MkdirAll(bundle.DestinationDir, 0o750); err != nil { return fmt.Errorf("unable to make '%s'; %w", bundle.DestinationDir, err) } var objects []Object for i := range bundle.Packages { objects = append(objects, bundle.Packages[i].File) if bundle.ValidateGPGSignature { objects = append(objects, bundle.Packages[i].GPGSignature) } } if bundle.Checksum.Validate { objects = append(objects, bundle.Checksum.File) } for _, object := range objects { if err := func() error { _, err := os.Stat(object.Destination) if err == nil { fmt.Printf("%s is already downloaded.\n", object.Destination) return nil } file, err := os.Create(object.Destination) if err != nil { return fmt.Errorf("unable to create %s; %w", object.Destination, err) } defer file.Close() client := http.Client{ CheckRedirect: func(r *http.Request, _ []*http.Request) error { r.URL.Opaque = r.URL.Path return nil }, } resp, err := client.Get(object.Source) if err != nil { return err } defer resp.Body.Close() size, err := io.Copy(file, resp.Body) if err != nil { return err } fmt.Printf("Downloaded %s with size %d.\n", object.Destination, size) return nil }(); err != nil { return err } } if bundle.ValidateGPGSignature { for i := range bundle.Packages { if err := sh.RunV("gpg", "--verify", bundle.Packages[i].GPGSignature.Destination, bundle.Packages[i].File.Destination); err != nil { return fmt.Errorf("GPG verification failed for '%s'; %w", bundle.Packages[i].File.Destination, err) } } } if bundle.Checksum.Validate { var err error if bundle.Checksum.ValidateFunc != nil { err = bundle.Checksum.ValidateFunc() } else { err = validateChecksum(bundle.DestinationDir, bundle.Checksum.File.Destination) } if err != nil { return fmt.Errorf("checksum validation failed; %w", err) } } return nil } func validateChecksum(destinationDir, checksumPath string) error { startDir, err := os.Getwd() if err != nil { return err } if err := os.Chdir(destinationDir); err != nil { return err } checksum := filepath.Base(checksumPath) if err := sh.RunV("sha256sum", "--check", "--ignore-missing", checksum); err != nil { return err } if err := os.Chdir(startDir); err != nil { return err } return nil }