diff --git a/magefiles/const.go b/magefiles/const.go new file mode 100644 index 0000000..28f8503 --- /dev/null +++ b/magefiles/const.go @@ -0,0 +1,11 @@ +//go:build mage + +package main + +const ( + configFile string = "./config/services.json" + rootBuildDir string = "./build" + templateExtension string = ".gotmpl" + rootTemplatesDir string = "./templates" + rootAssetsDir string = "./assets" +) diff --git a/magefiles/download.go b/magefiles/download.go index 0d24639..e7f893e 100644 --- a/magefiles/download.go +++ b/magefiles/download.go @@ -13,14 +13,6 @@ import ( "github.com/magefile/mage/sh" ) -const ( - configFile string = "./config/services.json" - rootBuildDir string = "./build" - templateExtension string = ".gotmpl" - rootTemplatesDir string = "./templates" - rootAssetsDir string = "./assets" -) - // Download downloads the binaries for a given service. func Download(name string) error { cfg, err := newConfig(configFile) @@ -44,9 +36,228 @@ func Download(name string) error { return nil } -// Download Forgejo +// downloadForgejo downloads and validates the Forgejo files. +func downloadForgejo(version string) error { + var ( + forgejoBinaryFileFormat = "forgejo-%s-linux-amd64" + forgejoDigestExtension = ".sha256" + forgejoSignatureExtension = ".asc" + forgejoJson = "./magefiles/forgejo.json" + ) -type forgejoDownload struct { + destinationDir := filepath.Join(rootBuildDir, "forgejo") + + binaryPath := filepath.Join( + destinationDir, + fmt.Sprintf(forgejoBinaryFileFormat, version), + ) + + signaturePath := binaryPath + forgejoSignatureExtension + + checksumPath := binaryPath + forgejoDigestExtension + + data, err := newForgejoInfo(forgejoJson) + if err != nil { + return err + } + + pack := downloadPack{ + destinationDir: destinationDir, + packages: []pack{ + { + file: object{ + source: data.Downloads[version].Binary, + destination: binaryPath, + }, + gpgSignature: object{ + source: data.Downloads[version].Signature, + destination: signaturePath, + }, + }, + }, + validateGPGSignature: true, + checksum: object{ + source: data.Downloads[version].Digest, + destination: checksumPath, + }, + validateChecksum: true, + } + + if err := download(pack); err != nil { + return err + } + + return nil +} + +// downloadGoToSocial downloads and validates the files for GoToSocial. +func downloadGoToSocial(version string) error { + destinationDir := filepath.Join(rootBuildDir, "gotosocial") + + binaryTarUrl := fmt.Sprintf( + "https://github.com/superseriousbusiness/gotosocial/releases/download/v%s/gotosocial_%s_linux_amd64.tar.gz", + version, + version, + ) + binaryTarFilepath := filepath.Join(destinationDir, fmt.Sprintf("gotosocial_%s_linux_amd64.tar.gz", version)) + + webAssetsTarUrl := fmt.Sprintf( + "https://github.com/superseriousbusiness/gotosocial/releases/download/v%s/gotosocial_%s_web-assets.tar.gz", + version, + version, + ) + webAssetsFilepath := filepath.Join(destinationDir, fmt.Sprintf("gotosocial_%s_web-assets.tar.gz", version)) + + checksumUrl := fmt.Sprintf( + "https://github.com/superseriousbusiness/gotosocial/releases/download/v%s/checksums.txt", + version, + ) + checksumFilePath := filepath.Join(destinationDir, fmt.Sprintf("gotosocial_%s_checksums.txt", version)) + + pack := downloadPack{ + destinationDir: destinationDir, + packages: []pack{ + { + file: object{ + source: binaryTarUrl, + destination: binaryTarFilepath, + }, + }, + { + file: object{ + source: webAssetsTarUrl, + destination: webAssetsFilepath, + }, + }, + }, + validateGPGSignature: false, + checksum: object{ + source: checksumUrl, + destination: checksumFilePath, + }, + validateChecksum: true, + } + + if err := download(pack); err != nil { + return err + } + + return nil +} + +type downloadPack struct { + destinationDir string + packages []pack + checksum object + validateGPGSignature bool + validateChecksum bool +} + +type pack struct { + file object + gpgSignature object +} + +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(pack downloadPack) error { + if err := os.MkdirAll(pack.destinationDir, 0o750); err != nil { + return fmt.Errorf("unable to make '%s'; %w", pack.destinationDir, err) + } + + var objects []object + + for i := range pack.packages { + objects = append(objects, pack.packages[i].file) + if pack.validateGPGSignature { + objects = append(objects, pack.packages[i].gpgSignature) + } + } + + if pack.validateChecksum { + objects = append(objects, pack.checksum) + } + + 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 pack.validateGPGSignature { + for i := range pack.packages { + if err := sh.Run("gpg", "--verify", pack.packages[i].gpgSignature.destination, pack.packages[i].file.destination); err != nil { + return fmt.Errorf("GPG verification failed for '%s'; %w", pack.packages[i].file.destination, err) + } + } + } + + if pack.validateChecksum { + startDir, err := os.Getwd() + if err != nil { + return err + } + + if err := os.Chdir(pack.destinationDir); err != nil { + return err + } + + checksum := filepath.Base(pack.checksum.destination) + + if err := sh.Run("sha256sum", "--check", "--ignore-missing", checksum); err != nil { + return err + } + + if err := os.Chdir(startDir); err != nil { + return err + } + + } + + return nil +} + +type forgejoInfo struct { Downloads map[string]forgejoFiles `json:"downloads"` } @@ -56,225 +267,20 @@ type forgejoFiles struct { Digest string `json:"digest"` } -const ( - forgejoBinaryFileFormat string = "forgejo-%s-linux-amd64" - forgejoDigestExtension string = ".sha256" - forgejoSignatureExtension string = ".asc" - forgejoDownloadJson string = "./magefiles/forgejo/download.json" -) +func newForgejoInfo(path string) (forgejoInfo, error) { + var info forgejoInfo -func downloadForgejo(version string) error { - downloadFolder := filepath.Join(rootBuildDir, "forgejo") - - if err := os.MkdirAll(downloadFolder, 0o750); err != nil { - return fmt.Errorf("unable to make %s; %w", downloadFolder, err) - } - - binaryPath := filepath.Join( - downloadFolder, - fmt.Sprintf(forgejoBinaryFileFormat, version), - ) - signaturePath := binaryPath + forgejoSignatureExtension - digestPath := binaryPath + forgejoDigestExtension - - _, err := os.Stat(binaryPath) - if err == nil { - fmt.Printf("Forgejo %s is already downloaded.\n", version) - return nil - } - - data, err := newForgejoDownloadData() + f, err := os.Open(path) if err != nil { - return err - } - - downloads := []struct { - url string - path string - }{ - { - url: data.Downloads[version].Binary, - path: binaryPath, - }, - { - url: data.Downloads[version].Signature, - path: signaturePath, - }, - { - url: data.Downloads[version].Digest, - path: digestPath, - }, - } - - for _, v := range downloads { - if err := func() error { - download, err := os.Create(v.path) - if err != nil { - return fmt.Errorf("unable to create %s; %w", v.path, err) - } - defer download.Close() - - client := http.Client{ - CheckRedirect: func(r *http.Request, _ []*http.Request) error { - r.URL.Opaque = r.URL.Path - return nil - }, - } - - resp, err := client.Get(v.url) - if err != nil { - return err - } - defer resp.Body.Close() - - size, err := io.Copy(download, resp.Body) - if err != nil { - return err - } - - fmt.Printf("Downloaded %s with size %d.\n", v.path, size) - - return nil - }(); err != nil { - return err - } - } - - if err = sh.Run("gpg", "--verify", signaturePath, binaryPath); err != nil { - return fmt.Errorf("GPG verification failed; %w", err) - } - - if err := os.Chdir(downloadFolder); err != nil { - return err - } - - if err := sh.Run("sha256sum", "--check", fmt.Sprintf(forgejoBinaryFileFormat+forgejoDigestExtension, version)); err != nil { - return err - } - - if err := os.Chdir("../.."); err != nil { - return err - } - - return nil -} - -func newForgejoDownloadData() (forgejoDownload, error) { - var data forgejoDownload - - f, err := os.Open(forgejoDownloadJson) - if err != nil { - return data, err + return info, err } defer f.Close() decoder := json.NewDecoder(f) - if err = decoder.Decode(&data); err != nil { - return data, err + if err = decoder.Decode(&info); err != nil { + return info, err } - return data, nil -} - -// Download GTS - -func downloadGoToSocial(version string) error { - downloadFolder := filepath.Join(rootBuildDir, "gotosocial") - - if err := os.MkdirAll(downloadFolder, 0o750); err != nil { - return fmt.Errorf("unable to make %s; %w", downloadFolder, err) - } - - binaryTarUrl := fmt.Sprintf( - "https://github.com/superseriousbusiness/gotosocial/releases/download/v%s/gotosocial_%s_linux_amd64.tar.gz", - version, - version, - ) - binaryTarFilepath := filepath.Join(downloadFolder, fmt.Sprintf("gotosocial_%s_linux_amd64.tar.gz", version)) - - webAssetsTarUrl := fmt.Sprintf( - "https://github.com/superseriousbusiness/gotosocial/releases/download/v%s/gotosocial_%s_web-assets.tar.gz", - version, - version, - ) - webAssetsFilepath := filepath.Join(downloadFolder, fmt.Sprintf("gotosocial_%s_web-assets.tar.gz", version)) - - checksumUrl := fmt.Sprintf( - "https://github.com/superseriousbusiness/gotosocial/releases/download/v%s/checksums.txt", - version, - ) - checksumFilePath := filepath.Join(downloadFolder, fmt.Sprintf("gotosocial_%s_checksums.txt", version)) - - _, err := os.Stat(binaryTarFilepath) - if err == nil { - fmt.Printf("GoToSocial %s is already downloaded.\n", version) - return nil - } - - downloads := []struct { - url string - path string - }{ - { - url: binaryTarUrl, - path: binaryTarFilepath, - }, - { - url: webAssetsTarUrl, - path: webAssetsFilepath, - }, - { - url: checksumUrl, - path: checksumFilePath, - }, - } - - for _, v := range downloads { - if err := func() error { - download, err := os.Create(v.path) - if err != nil { - return fmt.Errorf("unable to create %s; %w", v.path, err) - } - defer download.Close() - - client := http.Client{ - CheckRedirect: func(r *http.Request, _ []*http.Request) error { - r.URL.Opaque = r.URL.Path - return nil - }, - } - - resp, err := client.Get(v.url) - if err != nil { - return err - } - defer resp.Body.Close() - - size, err := io.Copy(download, resp.Body) - if err != nil { - return err - } - - fmt.Printf("Downloaded %s with size %d.\n", v.path, size) - - return nil - }(); err != nil { - return err - } - } - - if err := os.Chdir(downloadFolder); err != nil { - return err - } - - if err := sh.Run("sha256sum", "--check", "--ignore-missing", fmt.Sprintf("gotosocial_%s_checksums.txt", version)); err != nil { - return err - } - - if err := os.Chdir("../.."); err != nil { - return err - } - - return nil + return info, nil } diff --git a/magefiles/forgejo/download.json b/magefiles/forgejo.json similarity index 100% rename from magefiles/forgejo/download.json rename to magefiles/forgejo.json