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 }