github.com/abemedia/appcast@v0.4.0/integrations/apt/release.go (about)

     1  package apt
     2  
     3  import (
     4  	"crypto/md5"
     5  	"crypto/sha1"
     6  	"crypto/sha256"
     7  	"fmt"
     8  	"io/fs"
     9  	"os"
    10  	"path/filepath"
    11  	"sort"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/abemedia/appcast/integrations/apt/deb"
    16  	"github.com/abemedia/appcast/pkg/crypto/pgp"
    17  	"golang.org/x/mod/semver"
    18  )
    19  
    20  func release(key *pgp.PrivateKey, algos CompressionAlgo, p []*Package) (string, error) {
    21  	dir, err := os.MkdirTemp("", "")
    22  	if err != nil {
    23  		return "", err
    24  	}
    25  
    26  	stable := make([]*Package, 0, len(p))
    27  	for _, pkg := range p {
    28  		if semver.Prerelease("v"+pkg.Version) == "" {
    29  			stable = append(stable, pkg)
    30  		}
    31  	}
    32  	if err = releaseSuite(key, algos, stable, "stable", dir); err != nil {
    33  		return "", err
    34  	}
    35  
    36  	// If not all packages are stable publish a separate `edge` dist.
    37  	if len(p) > len(stable) {
    38  		if err = releaseSuite(key, algos, p, "edge", dir); err != nil {
    39  			return "", err
    40  		}
    41  	}
    42  
    43  	return dir, nil
    44  }
    45  
    46  func releaseSuite(key *pgp.PrivateKey, algos CompressionAlgo, p []*Package, suite, root string) error {
    47  	dir := filepath.Join(root, "dists", suite)
    48  
    49  	r := Releases{
    50  		Suite:      suite,
    51  		Codename:   suite,
    52  		Components: "main",
    53  	}
    54  
    55  	byArch := map[string][]*Package{}
    56  	for _, pkg := range p {
    57  		byArch[pkg.Architecture] = append(byArch[pkg.Architecture], pkg)
    58  	}
    59  
    60  	{
    61  		var as []string
    62  		for a, pkgs := range byArch {
    63  			if err := releaseArch(algos, pkgs, suite, a, dir); err != nil {
    64  				return err
    65  			}
    66  			as = append(as, a)
    67  		}
    68  		sort.Strings(as)
    69  		r.Architectures = strings.Join(as, " ")
    70  	}
    71  
    72  	dirfs := os.DirFS(dir)
    73  	err := fs.WalkDir(dirfs, ".", func(path string, d fs.DirEntry, err error) error {
    74  		if err != nil || d.IsDir() {
    75  			return err
    76  		}
    77  
    78  		b, err := fs.ReadFile(dirfs, path)
    79  		if err != nil {
    80  			return err
    81  		}
    82  
    83  		r.MD5Sum += fmt.Sprintf("\n%x %d %s", md5.Sum(b), len(b), path)
    84  		r.SHA1 += fmt.Sprintf("\n%x %d %s", sha1.Sum(b), len(b), path)
    85  		r.SHA256 += fmt.Sprintf("\n%x %d %s", sha256.Sum256(b), len(b), path)
    86  
    87  		return nil
    88  	})
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	return writeRelease(dir, r, key)
    94  }
    95  
    96  func releaseArch(algos CompressionAlgo, p []*Package, suite, arch, root string) error {
    97  	r := Release{
    98  		Archive:      suite,
    99  		Suite:        suite,
   100  		Component:    "main",
   101  		Architecture: arch,
   102  	}
   103  
   104  	dir := filepath.Join(root, "main", "binary-"+arch)
   105  	if err := os.MkdirAll(dir, os.ModePerm); err != nil {
   106  		return err
   107  	}
   108  	if err := writeFile(filepath.Join(dir, "Release"), r); err != nil {
   109  		return err
   110  	}
   111  	return writePackages(filepath.Join(dir, "Packages"), p, algos)
   112  }
   113  
   114  func writeFile(path string, v any) error {
   115  	f, err := os.Create(path)
   116  	if err != nil {
   117  		return err
   118  	}
   119  	w, err := compress(filepath.Ext(path))(f)
   120  	if err != nil {
   121  		return err
   122  	}
   123  	if err = deb.NewEncoder(w).Encode(v); err != nil {
   124  		return err
   125  	}
   126  	if err = w.Close(); err != nil {
   127  		return err
   128  	}
   129  	return f.Close()
   130  }
   131  
   132  func writePackages(path string, v []*Package, algos CompressionAlgo) error {
   133  	b, err := deb.Marshal(v)
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	for _, ext := range compressionExtensions(algos) {
   139  		f, err := os.Create(path + ext)
   140  		if err != nil {
   141  			return err
   142  		}
   143  		w, err := compress(ext)(f)
   144  		if err != nil {
   145  			return err
   146  		}
   147  		if _, err = w.Write(b); err != nil {
   148  			return err
   149  		}
   150  		if err = w.Close(); err != nil {
   151  			return err
   152  		}
   153  		if err = f.Close(); err != nil {
   154  			return err
   155  		}
   156  	}
   157  
   158  	return nil
   159  }
   160  
   161  var timeNow = time.Now //nolint:gochecknoglobals
   162  
   163  func writeRelease(dir string, r Releases, key *pgp.PrivateKey) error {
   164  	r.Date = timeNow().UTC()
   165  
   166  	b, err := deb.Marshal(r)
   167  	if err != nil {
   168  		return err
   169  	}
   170  
   171  	if err = os.WriteFile(filepath.Join(dir, "Release"), b, 0o600); err != nil {
   172  		return err
   173  	}
   174  
   175  	if key != nil {
   176  		sig, err := pgp.Sign(key, b)
   177  		if err != nil {
   178  			return err
   179  		}
   180  		if err = os.WriteFile(filepath.Join(dir, "Release.gpg"), sig, 0o600); err != nil {
   181  			return err
   182  		}
   183  		b, err = pgp.SignText(key, b)
   184  		if err != nil {
   185  			return err
   186  		}
   187  	}
   188  
   189  	return os.WriteFile(filepath.Join(dir, "InRelease"), b, 0o600)
   190  }