161 lines
4.0 KiB
Go
161 lines
4.0 KiB
Go
|
package debanator
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"net/http"
|
||
|
"time"
|
||
|
|
||
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||
|
"github.com/go-chi/chi/v5"
|
||
|
log "github.com/sirupsen/logrus"
|
||
|
"golang.org/x/exp/maps"
|
||
|
"pault.ag/go/debian/control"
|
||
|
"pault.ag/go/debian/dependency"
|
||
|
"pault.ag/go/debian/hashio"
|
||
|
"pault.ag/go/debian/version"
|
||
|
)
|
||
|
|
||
|
// A group of debs of a package for different arches/version
|
||
|
type LogicalPackage struct {
|
||
|
Name string
|
||
|
// arch:version:package
|
||
|
Arches map[dependency.Arch]map[version.Version]control.BinaryIndex
|
||
|
}
|
||
|
|
||
|
type hashedFile struct {
|
||
|
buf []byte
|
||
|
md5Hash control.MD5FileHash
|
||
|
sha1Hash control.SHA1FileHash
|
||
|
sha256Hash control.SHA256FileHash
|
||
|
}
|
||
|
|
||
|
type Repo struct {
|
||
|
packages []LogicalPackage
|
||
|
cache map[string]hashedFile
|
||
|
release []byte
|
||
|
}
|
||
|
|
||
|
func (r *Repo) GetArches() []dependency.Arch {
|
||
|
arches := make(map[dependency.Arch]struct{})
|
||
|
for _, lp := range r.packages {
|
||
|
for arch := range lp.Arches {
|
||
|
arches[arch] = struct{}{}
|
||
|
}
|
||
|
}
|
||
|
return maps.Keys(arches)
|
||
|
}
|
||
|
|
||
|
// Find the latest versions of all packages for the given arch
|
||
|
func (r *Repo) GetPackagesForArch(a dependency.Arch) []control.BinaryIndex {
|
||
|
out := []control.BinaryIndex{}
|
||
|
for _, p := range r.packages {
|
||
|
if versions, ok := p.Arches[a]; ok {
|
||
|
var latest version.Version
|
||
|
for v := range versions {
|
||
|
if version.Compare(v, latest) > 0 {
|
||
|
latest = v
|
||
|
}
|
||
|
}
|
||
|
out = append(out, p.Arches[a][latest])
|
||
|
}
|
||
|
}
|
||
|
return out
|
||
|
}
|
||
|
|
||
|
func (r *Repo) makePackagesFileForArch(arch dependency.Arch) error {
|
||
|
var b bytes.Buffer
|
||
|
w, hashers, err := hashio.NewHasherWriters([]string{"md5", "sha256", "sha1"}, &b)
|
||
|
enc, _ := control.NewEncoder(w)
|
||
|
for _, d := range r.GetPackagesForArch(arch) {
|
||
|
if err = enc.Encode(d); err != nil {
|
||
|
return fmt.Errorf("encoding package %s: %w", d.Package, err)
|
||
|
}
|
||
|
}
|
||
|
fname := fmt.Sprintf("main/binary-%s/Packages", arch)
|
||
|
hashes := make(map[string]control.FileHash)
|
||
|
for _, h := range hashers {
|
||
|
hashes[h.Name()] = control.FileHashFromHasher(fname, *h)
|
||
|
}
|
||
|
r.cache[fname] = hashedFile{
|
||
|
buf: b.Bytes(),
|
||
|
sha256Hash: control.SHA256FileHash{hashes["sha256"]},
|
||
|
sha1Hash: control.SHA1FileHash{hashes["sha1"]},
|
||
|
md5Hash: control.MD5FileHash{hashes["md5"]},
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Generate and cache all the Package/Repo files
|
||
|
func (r *Repo) GenerateFiles() error {
|
||
|
for _, arch := range r.GetArches() {
|
||
|
if err := r.makePackagesFileForArch(arch); err != nil {
|
||
|
return fmt.Errorf("generating files for arch %s: %w", arch, err)
|
||
|
}
|
||
|
}
|
||
|
r.makeRelease()
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (r *Repo) makeRelease() {
|
||
|
var rel bytes.Buffer
|
||
|
enc, _ := control.NewEncoder(&rel)
|
||
|
const dateFmt = "Mon, 02 Jan 2006 15:04:05 MST"
|
||
|
var md5s []control.MD5FileHash
|
||
|
var sha1s []control.SHA1FileHash
|
||
|
var sha256s []control.SHA256FileHash
|
||
|
for _, f := range r.cache {
|
||
|
md5s = append(md5s, f.md5Hash)
|
||
|
sha1s = append(sha1s, f.sha1Hash)
|
||
|
sha256s = append(sha256s, f.sha256Hash)
|
||
|
}
|
||
|
if err := enc.Encode(Release{
|
||
|
Suite: "stable",
|
||
|
Architectures: r.GetArches(),
|
||
|
Components: "main",
|
||
|
Date: time.Now().UTC().Format(dateFmt),
|
||
|
MD5Sum: md5s,
|
||
|
SHA1: sha1s,
|
||
|
SHA256: sha256s,
|
||
|
}); err != nil {
|
||
|
log.Fatal(err)
|
||
|
}
|
||
|
r.release = rel.Bytes()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
|
||
|
// Handle a deb/apt repository http request
|
||
|
func (r *Repo) GetHandler(keyring *crypto.KeyRing) http.Handler {
|
||
|
router := chi.NewRouter()
|
||
|
router.Get("/Release", func(w http.ResponseWriter, req *http.Request) {
|
||
|
if _, err := w.Write(r.release); err != nil {
|
||
|
log.Fatal(err)
|
||
|
}
|
||
|
})
|
||
|
router.Get("/Release.gpg", func(w http.ResponseWriter, req *http.Request) {
|
||
|
msg := crypto.NewPlainMessage(r.release)
|
||
|
sig, err := keyring.SignDetached(msg)
|
||
|
if err != nil {
|
||
|
log.Fatal(err)
|
||
|
}
|
||
|
sigStr, err := sig.GetArmored()
|
||
|
if err != nil {
|
||
|
log.Fatal(err)
|
||
|
}
|
||
|
io.WriteString(w, sigStr)
|
||
|
})
|
||
|
router.Get("/main/{arch}/Packages", func(w http.ResponseWriter, req *http.Request) {
|
||
|
h, ok := r.cache[fmt.Sprintf("main/%s/Packages", chi.URLParam(req, "arch"))]
|
||
|
if !ok {
|
||
|
w.WriteHeader(http.StatusBadRequest)
|
||
|
return
|
||
|
}
|
||
|
_, err := w.Write(h.buf); if err != nil {
|
||
|
log.Error(err)
|
||
|
}
|
||
|
})
|
||
|
return router
|
||
|
}
|