clonepack/internal/clone/apt/metadata_gen.go
GnomeZworc c4776d81dd
add apt gestion
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-04-25 19:03:26 +02:00

148 lines
4.4 KiB
Go

package apt
import (
"bytes"
"compress/gzip"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"os"
"path/filepath"
)
// InitEmptyRepo creates the directory structure for an APT mirror and writes an
// empty Release file so APT clients don't get a 404 before the first clone.
func InitEmptyRepo(localDir string, cfg Config) error {
if err := os.MkdirAll(filepath.Join(localDir, "pool"), 0o755); err != nil {
return err
}
for _, component := range cfg.Components {
for _, arch := range cfg.Architectures {
dir := filepath.Join(localDir, "dists", cfg.Suite, component, "binary-"+arch)
if err := os.MkdirAll(dir, 0o755); err != nil {
return err
}
}
}
releasePath := filepath.Join(localDir, "dists", cfg.Suite, "Release")
if _, err := os.Stat(releasePath); os.IsNotExist(err) {
releaseData := GenerateRelease(cfg.Suite, cfg.Suite, cfg.Components, cfg.Architectures, nil)
if err := atomicWrite(releasePath, releaseData); err != nil {
return fmt.Errorf("write empty Release: %w", err)
}
}
return nil
}
// RegenerateMetadata rebuilds local Packages, Packages.gz, and Release files
// for all configured component/arch pairs, keeping only packages present on disk.
func RegenerateMetadata(localDir string, cfg Config) error {
suite := cfg.Suite
var releaseEntries []ReleaseEntry
for _, component := range cfg.Components {
for _, arch := range cfg.Architectures {
entries, err := regenerateComponentArch(localDir, suite, component, arch)
if err != nil {
return fmt.Errorf("regenerate %s/%s: %w", component, arch, err)
}
releaseEntries = append(releaseEntries, entries...)
}
}
// Write dists/{suite}/Release.
releaseData := GenerateRelease(suite, suite, cfg.Components, cfg.Architectures, releaseEntries)
releasePath := filepath.Join(localDir, "dists", suite, "Release")
if err := os.MkdirAll(filepath.Dir(releasePath), 0o755); err != nil {
return fmt.Errorf("create dists dir: %w", err)
}
if err := atomicWrite(releasePath, releaseData); err != nil {
return fmt.Errorf("write Release: %w", err)
}
return nil
}
// regenerateComponentArch regenerates Packages and Packages.gz for one component/arch
// and returns the two ReleaseEntry values (plain then gz) for the Release file.
func regenerateComponentArch(localDir, suite, component, arch string) ([]ReleaseEntry, error) {
binDir := filepath.Join(localDir, "dists", suite, component, "binary-"+arch)
upstreamGzPath := filepath.Join(binDir, upstreamPackagesGz)
// 1. Read upstream Packages.gz.
gzData, err := os.ReadFile(upstreamGzPath)
if err != nil {
// Not cloned yet — skip silently.
return nil, nil
}
// 2. Decompress.
gz, err := gzip.NewReader(bytes.NewReader(gzData))
if err != nil {
return nil, fmt.Errorf("open gzip %s: %w", upstreamGzPath, err)
}
rawPackages, err := io.ReadAll(gz)
gz.Close()
if err != nil {
return nil, fmt.Errorf("decompress %s: %w", upstreamGzPath, err)
}
// 3. Parse all stanzas.
allPkgs, err := ParsePackages(rawPackages)
if err != nil {
return nil, fmt.Errorf("parse upstream Packages: %w", err)
}
// 4. Filter: keep only packages present on disk.
checker := diskPresenceChecker(localDir)
localPkgs := FilterPackages(allPkgs, checker)
// 5. Re-emit filtered stanzas.
plainBytes := emitPackages(localPkgs)
// 6. Gzip the filtered Packages.
var gzBuf bytes.Buffer
gzw := gzip.NewWriter(&gzBuf)
if _, err := gzw.Write(plainBytes); err != nil {
return nil, fmt.Errorf("gzip write: %w", err)
}
if err := gzw.Close(); err != nil {
return nil, fmt.Errorf("gzip close: %w", err)
}
gzBytes := gzBuf.Bytes()
// 7. Write Packages and Packages.gz.
if err := os.MkdirAll(binDir, 0o755); err != nil {
return nil, err
}
plainPath := filepath.Join(binDir, "Packages")
if err := atomicWrite(plainPath, plainBytes); err != nil {
return nil, fmt.Errorf("write Packages: %w", err)
}
gzPath := filepath.Join(binDir, "Packages.gz")
if err := atomicWrite(gzPath, gzBytes); err != nil {
return nil, fmt.Errorf("write Packages.gz: %w", err)
}
// 8. Compute SHA256 hashes.
plainSum := sha256.Sum256(plainBytes)
gzSum := sha256.Sum256(gzBytes)
// Release paths are relative to dists/{suite}/.
relBase := component + "/binary-" + arch + "/"
return []ReleaseEntry{
{
Hash: hex.EncodeToString(plainSum[:]),
Size: int64(len(plainBytes)),
Path: relBase + "Packages",
},
{
Hash: hex.EncodeToString(gzSum[:]),
Size: int64(len(gzBytes)),
Path: relBase + "Packages.gz",
},
}, nil
}