//go:build mage package main import ( "encoding/json" "fmt" "io" "net/http" "os" "path/filepath" "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) if err != nil { return fmt.Errorf("unable to load the configuration; %v", err) } switch name { case "forgejo": if err := downloadForgejo(cfg.Forgejo.Version); err != nil { return fmt.Errorf("an error occurred whilst getting the forgejo binary; %w", err) } case "gotosocial": if err := downloadGoToSocial(cfg.GoToSocial.Version); err != nil { return fmt.Errorf("an error occurred whilst getting the packages for GoToSocial; %w", err) } default: fmt.Printf("'%s' has no files to download.\n", name) } return nil } // Download Forgejo type forgejoDownload struct { Downloads map[string]forgejoFiles `json:"downloads"` } type forgejoFiles struct { Binary string `json:"binary"` Signature string `json:"signature"` Digest string `json:"digest"` } const ( forgejoBinaryFileFormat string = "forgejo-%s-linux-amd64" forgejoDigestExtension string = ".sha256" forgejoSignatureExtension string = ".asc" forgejoDownloadJson string = "./magefiles/forgejo/download.json" ) 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() 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 } defer f.Close() decoder := json.NewDecoder(f) if err = decoder.Decode(&data); err != nil { return data, 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 }