modularise backend a bit
This commit is contained in:
parent
ed18f87251
commit
7ae7166ac1
156
backend.go
156
backend.go
|
@ -1,154 +1,78 @@
|
|||
package debanator
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"pault.ag/go/debian/control"
|
||||
"pault.ag/go/debian/deb"
|
||||
"pault.ag/go/debian/dependency"
|
||||
"pault.ag/go/debian/version"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
// A backend to search for packages in
|
||||
type Backend interface {
|
||||
GetPackages()
|
||||
GetFiles() ([]DebFile, error)
|
||||
ServeFiles(string) http.Handler
|
||||
}
|
||||
|
||||
type ReaderAtCloser interface {
|
||||
io.ReaderAt
|
||||
io.ReadCloser
|
||||
}
|
||||
|
||||
// An abstract interface for reading a debfile. This could be coming from the local fs,
|
||||
// a remote webdav share, etc...
|
||||
type DebFile interface {
|
||||
GetReader() (ReaderAtCloser, error)
|
||||
GetName() string
|
||||
}
|
||||
|
||||
type FileBackend struct {
|
||||
path string
|
||||
}
|
||||
|
||||
// A deb file existing on the local filesystem
|
||||
type fsDebFile struct {
|
||||
path string
|
||||
}
|
||||
|
||||
func (f fsDebFile) GetReader() (ReaderAtCloser, error) {
|
||||
return os.Open(f.path)
|
||||
}
|
||||
|
||||
func (f fsDebFile) GetName() string {
|
||||
_, name := filepath.Split(f.path)
|
||||
return name
|
||||
}
|
||||
|
||||
func NewFileBackend(path string) FileBackend {
|
||||
return FileBackend{path}
|
||||
}
|
||||
|
||||
func BinaryIndexFromDeb(p string, basePath string) (*control.BinaryIndex, error) {
|
||||
f, err := os.Open(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
debFile, err := deb.Load(f, p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read deb: %w", err)
|
||||
}
|
||||
md5sum := md5.New()
|
||||
sha1sum := sha1.New()
|
||||
sha256sum := sha256.New()
|
||||
hashWriter := io.MultiWriter(md5sum, sha1sum, sha256sum)
|
||||
size, err := io.Copy(hashWriter, f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hash file: %w", err)
|
||||
}
|
||||
bi := control.BinaryIndex{
|
||||
Paragraph: control.Paragraph{
|
||||
Values: make(map[string]string),
|
||||
},
|
||||
Package: debFile.Control.Package,
|
||||
Source: debFile.Control.Source,
|
||||
Version: debFile.Control.Version,
|
||||
InstalledSize: fmt.Sprintf("%d", debFile.Control.InstalledSize),
|
||||
Size: strconv.Itoa(int(size)),
|
||||
Maintainer: debFile.Control.Maintainer,
|
||||
Architecture: debFile.Control.Architecture,
|
||||
MultiArch: debFile.Control.MultiArch,
|
||||
Description: debFile.Control.Description,
|
||||
Homepage: debFile.Control.Homepage,
|
||||
Section: debFile.Control.Section,
|
||||
// FIXME: gross, make this more centrally managed somehow
|
||||
Filename: path.Join("pool/main", strings.TrimPrefix(p, basePath)),
|
||||
Priority: debFile.Control.Priority,
|
||||
MD5sum: fmt.Sprintf("%x", md5sum.Sum(nil)),
|
||||
SHA1: fmt.Sprintf("%x", sha1sum.Sum(nil)),
|
||||
SHA256: fmt.Sprintf("%x", sha256sum.Sum(nil)),
|
||||
}
|
||||
if debFile.Control.Depends.String() != "" {
|
||||
bi.Paragraph.Set("Depends", debFile.Control.Depends.String())
|
||||
}
|
||||
if debFile.Control.Recommends.String() != "" {
|
||||
bi.Paragraph.Set("Recommends", debFile.Control.Recommends.String())
|
||||
}
|
||||
if debFile.Control.Suggests.String() != "" {
|
||||
bi.Paragraph.Set("Suggests", debFile.Control.Suggests.String())
|
||||
}
|
||||
if debFile.Control.Breaks.String() != "" {
|
||||
bi.Paragraph.Set("Breaks", debFile.Control.Breaks.String())
|
||||
}
|
||||
if debFile.Control.Replaces.String() != "" {
|
||||
bi.Paragraph.Set("Replaces", debFile.Control.Replaces.String())
|
||||
}
|
||||
if debFile.Control.BuiltUsing.String() != "" {
|
||||
bi.Paragraph.Set("BuiltUsing", debFile.Control.BuiltUsing.String())
|
||||
}
|
||||
return &bi, nil
|
||||
func (fb FileBackend) ServeFiles(prefix string) http.Handler {
|
||||
return http.StripPrefix(path.Join(prefix, "pool"), http.FileServer(http.Dir(fb.path)))
|
||||
}
|
||||
|
||||
func ScanDebs(debpath string) Repo {
|
||||
var debs []string
|
||||
fs.WalkDir(os.DirFS(debpath), ".", func(path string, dir fs.DirEntry, err error) error {
|
||||
func (fb FileBackend) GetFiles() ([]DebFile, error) {
|
||||
var debs []DebFile
|
||||
fs.WalkDir(os.DirFS(fb.path), ".", func(dirpath string, dir fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"path": path,
|
||||
"path": dirpath,
|
||||
"error": err,
|
||||
}).Warn("Error scanning for debs")
|
||||
return nil
|
||||
}
|
||||
if !dir.IsDir() && strings.HasSuffix(dir.Name(), ".deb"){
|
||||
debs = append(debs, path)
|
||||
if !dir.IsDir() && strings.HasSuffix(dir.Name(), ".deb") {
|
||||
debs = append(debs, DebFile(fsDebFile{
|
||||
filepath.Join(fb.path, dirpath),
|
||||
}))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
packs := make(map[string]LogicalPackage)
|
||||
for _, d := range debs {
|
||||
p := path.Join(debpath, d)
|
||||
bi, err := BinaryIndexFromDeb(p, debpath)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"path": p,
|
||||
"error": err,
|
||||
}).Error("Error processing deb file")
|
||||
continue
|
||||
}
|
||||
|
||||
packageName := bi.Package
|
||||
if _, ok := packs[packageName]; !ok {
|
||||
packs[packageName] = LogicalPackage{
|
||||
Name: packageName,
|
||||
Arches: make(map[dependency.Arch]map[version.Version]control.BinaryIndex),
|
||||
}
|
||||
}
|
||||
pack := packs[packageName]
|
||||
if _, ok := pack.Arches[bi.Architecture]; !ok {
|
||||
pack.Arches[bi.Architecture] = make(map[version.Version]control.BinaryIndex)
|
||||
}
|
||||
arch := pack.Arches[bi.Architecture]
|
||||
if _, ok := arch[bi.Version]; !ok {
|
||||
arch[bi.Version] = *bi
|
||||
} else {
|
||||
log.WithFields(log.Fields{
|
||||
"package": packageName,
|
||||
"arch": arch,
|
||||
"version": bi.Version.String(),
|
||||
}).Warn("Duplicate package/arch/version found, ignoring...")
|
||||
}
|
||||
}
|
||||
|
||||
return Repo{
|
||||
packages: maps.Values(packs),
|
||||
cache: make(map[string]hashedFile),
|
||||
}
|
||||
log.Infof("got files: %v", debs)
|
||||
return debs, nil
|
||||
}
|
||||
|
|
27
cmd/main.go
27
cmd/main.go
|
@ -31,23 +31,12 @@ func logMiddleware(h http.HandlerFunc) http.HandlerFunc {
|
|||
log.WithFields(log.Fields{
|
||||
"remote": r.RemoteAddr,
|
||||
"method": r.Method,
|
||||
"url": r.URL,
|
||||
"url": r.URL,
|
||||
"status": rec.status,
|
||||
}).Info("Got request")
|
||||
}
|
||||
}
|
||||
|
||||
func md(err error) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func unwrap[T any](val T, err error) T {
|
||||
md(err)
|
||||
return val
|
||||
}
|
||||
|
||||
func main() {
|
||||
listenAddr := *flag.String("listen", ":1612", "HTTP listen address")
|
||||
debPath := *flag.String("debpath", "debs", "Path to directory containing deb files.")
|
||||
|
@ -57,22 +46,23 @@ func main() {
|
|||
kb, err := os.ReadFile("privkey.gpg")
|
||||
if err != nil {
|
||||
log.Infof("Generating new key...")
|
||||
ecKey = unwrap(crypto.GenerateKey("Debanator", "packager@example.com", "x25519", 0))
|
||||
f := unwrap(os.Create("privkey.gpg"))
|
||||
ecKey = debanator.Unwrap(crypto.GenerateKey("Debanator", "packager@example.com", "x25519", 0))
|
||||
f := debanator.Unwrap(os.Create("privkey.gpg"))
|
||||
defer f.Close()
|
||||
armored := unwrap(ecKey.Armor())
|
||||
armored := debanator.Unwrap(ecKey.Armor())
|
||||
f.WriteString(armored)
|
||||
} else {
|
||||
log.Infof("Using existing key...")
|
||||
ecKey = unwrap(crypto.NewKeyFromArmored(string(kb)))
|
||||
ecKey = debanator.Unwrap(crypto.NewKeyFromArmored(string(kb)))
|
||||
}
|
||||
|
||||
signingKeyRing, err := crypto.NewKeyRing(ecKey)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
repo := debanator.ScanDebs(debPath)
|
||||
be := debanator.NewFileBackend(debPath)
|
||||
repo := debanator.NewRepoFromBackend(be, "/dists/stable")
|
||||
debanator.Md(repo.Populate())
|
||||
if err := repo.GenerateFiles(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -94,6 +84,5 @@ func main() {
|
|||
io.WriteString(w, pub)
|
||||
})
|
||||
r.Mount("/dists/stable", repo.GetHandler(signingKeyRing))
|
||||
r.Get("/pool/main/*", http.StripPrefix("/pool/main/", http.FileServer(http.Dir(debPath))).ServeHTTP)
|
||||
http.ListenAndServe(listenAddr, r)
|
||||
}
|
||||
|
|
199
package.go
199
package.go
|
@ -1,19 +1,17 @@
|
|||
package debanator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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/deb"
|
||||
"pault.ag/go/debian/dependency"
|
||||
"pault.ag/go/debian/hashio"
|
||||
"pault.ag/go/debian/version"
|
||||
)
|
||||
|
||||
|
@ -24,137 +22,58 @@ type LogicalPackage struct {
|
|||
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{}{}
|
||||
}
|
||||
func BinaryIndexFromDeb(r ReaderAtCloser, filePath string) (*control.BinaryIndex, error) {
|
||||
debFile, err := deb.Load(r, "fakepath")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read deb: %w", err)
|
||||
}
|
||||
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
|
||||
md5sum := md5.New()
|
||||
sha1sum := sha1.New()
|
||||
sha256sum := sha256.New()
|
||||
hashWriter := io.MultiWriter(md5sum, sha1sum, sha256sum)
|
||||
size, err := io.Copy(hashWriter, r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hash file: %w", err)
|
||||
}
|
||||
bi := control.BinaryIndex{
|
||||
Paragraph: control.Paragraph{
|
||||
Values: make(map[string]string),
|
||||
},
|
||||
Package: debFile.Control.Package,
|
||||
Source: debFile.Control.Source,
|
||||
Version: debFile.Control.Version,
|
||||
InstalledSize: fmt.Sprintf("%d", debFile.Control.InstalledSize),
|
||||
Size: strconv.Itoa(int(size)),
|
||||
Maintainer: debFile.Control.Maintainer,
|
||||
Architecture: debFile.Control.Architecture,
|
||||
MultiArch: debFile.Control.MultiArch,
|
||||
Description: debFile.Control.Description,
|
||||
Homepage: debFile.Control.Homepage,
|
||||
Section: debFile.Control.Section,
|
||||
// FIXME: gross, make this more centrally managed somehow
|
||||
Filename: strings.TrimPrefix(filePath, "/"),
|
||||
Priority: debFile.Control.Priority,
|
||||
MD5sum: fmt.Sprintf("%x", md5sum.Sum(nil)),
|
||||
SHA1: fmt.Sprintf("%x", sha1sum.Sum(nil)),
|
||||
SHA256: fmt.Sprintf("%x", sha256sum.Sum(nil)),
|
||||
}
|
||||
if debFile.Control.Depends.String() != "" {
|
||||
bi.Paragraph.Set("Depends", debFile.Control.Depends.String())
|
||||
}
|
||||
if debFile.Control.Recommends.String() != "" {
|
||||
bi.Paragraph.Set("Recommends", debFile.Control.Recommends.String())
|
||||
}
|
||||
if debFile.Control.Suggests.String() != "" {
|
||||
bi.Paragraph.Set("Suggests", debFile.Control.Suggests.String())
|
||||
}
|
||||
if debFile.Control.Breaks.String() != "" {
|
||||
bi.Paragraph.Set("Breaks", debFile.Control.Breaks.String())
|
||||
}
|
||||
if debFile.Control.Replaces.String() != "" {
|
||||
bi.Paragraph.Set("Replaces", debFile.Control.Replaces.String())
|
||||
}
|
||||
if debFile.Control.BuiltUsing.String() != "" {
|
||||
bi.Paragraph.Set("BuiltUsing", debFile.Control.BuiltUsing.String())
|
||||
}
|
||||
return &bi, nil
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ type Release struct {
|
|||
Architectures []dependency.Arch
|
||||
Components string
|
||||
Date string
|
||||
SHA1 []control.SHA1FileHash `delim:"\n" strip:"\n\r\t "`
|
||||
SHA256 []control.SHA256FileHash`delim:"\n" strip:"\n\r\t "`
|
||||
MD5Sum []control.MD5FileHash`delim:"\n" strip:"\n\r\t "`
|
||||
SHA1 []control.SHA1FileHash `delim:"\n" strip:"\n\r\t "`
|
||||
SHA256 []control.SHA256FileHash `delim:"\n" strip:"\n\r\t "`
|
||||
MD5Sum []control.MD5FileHash `delim:"\n" strip:"\n\r\t "`
|
||||
}
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
package debanator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
"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"
|
||||
)
|
||||
|
||||
const FILESPREFIX = "pool"
|
||||
|
||||
type hashedFile struct {
|
||||
buf []byte
|
||||
md5Hash control.MD5FileHash
|
||||
sha1Hash control.SHA1FileHash
|
||||
sha256Hash control.SHA256FileHash
|
||||
}
|
||||
|
||||
type Repo struct {
|
||||
// The prefix to serving http paths to the files provided by this package.
|
||||
// This is needed so that we can give absolute paths in Package files.
|
||||
filePrefix string
|
||||
be Backend
|
||||
packages []LogicalPackage
|
||||
cache map[string]hashedFile
|
||||
release []byte
|
||||
}
|
||||
|
||||
func NewRepoFromBackend(backend Backend, filePrefix string) Repo {
|
||||
return Repo{
|
||||
be: backend,
|
||||
cache: make(map[string]hashedFile),
|
||||
filePrefix: filePrefix,
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
FileHash: hashes["sha256"],
|
||||
},
|
||||
sha1Hash: control.SHA1FileHash{
|
||||
FileHash: hashes["sha1"]},
|
||||
md5Hash: control.MD5FileHash{FileHash: 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)
|
||||
}
|
||||
})
|
||||
router.Get(fmt.Sprintf("/%s/*", FILESPREFIX), r.be.ServeFiles(r.filePrefix).ServeHTTP)
|
||||
return router
|
||||
}
|
||||
|
||||
func (r *Repo) Populate() error {
|
||||
packs := make(map[string]LogicalPackage)
|
||||
files := Unwrap(r.be.GetFiles())
|
||||
for _, f := range files {
|
||||
rd := Unwrap(f.GetReader())
|
||||
bi, err := BinaryIndexFromDeb(rd, path.Join(r.filePrefix, FILESPREFIX, f.GetName()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("processing deb file: %w", err)
|
||||
}
|
||||
|
||||
packageName := bi.Package
|
||||
if _, ok := packs[packageName]; !ok {
|
||||
packs[packageName] = LogicalPackage{
|
||||
Name: packageName,
|
||||
Arches: make(map[dependency.Arch]map[version.Version]control.BinaryIndex),
|
||||
}
|
||||
}
|
||||
pack := packs[packageName]
|
||||
if _, ok := pack.Arches[bi.Architecture]; !ok {
|
||||
pack.Arches[bi.Architecture] = make(map[version.Version]control.BinaryIndex)
|
||||
}
|
||||
arch := pack.Arches[bi.Architecture]
|
||||
if _, ok := arch[bi.Version]; !ok {
|
||||
arch[bi.Version] = *bi
|
||||
} else {
|
||||
log.WithFields(log.Fields{
|
||||
"package": packageName,
|
||||
"arch": arch,
|
||||
"version": bi.Version.String(),
|
||||
}).Warn("Duplicate package/arch/version found, ignoring...")
|
||||
}
|
||||
}
|
||||
r.packages = maps.Values(packs)
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue