Introduces the headline feature of the kernel catalog: pulling a kernel
bundle over HTTP without any local build step.
Catalog format (internal/kernelcat/catalog.go):
- Catalog { Version, Entries } + CatEntry { Name, Distro, Arch,
KernelVersion, TarballURL, TarballSHA256, SizeBytes, Description }.
- catalog.json is embedded via go:embed and ships with each banger
binary. It starts empty (Phase 5's CI pipeline will populate it).
- Lookup(name) returns the matching entry or os.ErrNotExist.
Fetch (internal/kernelcat/fetch.go):
- HTTP GET with streaming SHA256 over the response body.
- zstd-decode (github.com/klauspost/compress/zstd) -> tar extract into
<kernelsDir>/<name>/.
- Hardens against path-traversal tarball entries (members whose
normalised path escapes the target dir, and unsafe symlink
targets) and sha256-mismatch downloads; any failure removes the
partially-populated target dir.
- Regular files, directories, and safe symlinks are supported; other
tar types (hardlinks, devices, fifos) are silently skipped.
- After extraction, recomputes sha256 over the on-disk vmlinux and
writes the manifest with Source="pull:<url>".
Daemon methods (internal/daemon/kernels.go):
- KernelPull(ctx, {Name, Force}) - lookup in embedded catalog, refuse
overwrite unless Force, delegate to kernelcat.Fetch.
- KernelCatalog(ctx) - return the embedded catalog annotated per-entry
with whether it has been pulled locally.
RPC: kernel.pull, kernel.catalog dispatch cases.
CLI:
- `banger kernel pull <name> [--force]`.
- `banger kernel list --available` prints the catalog with a
pulled/available STATE column and a human-readable size.
Tests: fetch round-trip (extract + manifest + sha256), sha256 mismatch
rejection with cleanup, missing-vmlinux rejection, path-traversal
rejection, HTTP error propagation, catalog parsing, lookup,
pulled-status reconciliation. All 20 packages green.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
59 lines
1.6 KiB
Go
59 lines
1.6 KiB
Go
package kernelcat
|
|
|
|
import (
|
|
_ "embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
)
|
|
|
|
//go:embed catalog.json
|
|
var embeddedCatalog []byte
|
|
|
|
// Catalog is the published list of kernel bundles banger can pull. It ships
|
|
// embedded in the banger binary and is updated across releases; Phase 5
|
|
// wires CI to regenerate it.
|
|
type Catalog struct {
|
|
Version int `json:"version"`
|
|
Entries []CatEntry `json:"entries"`
|
|
}
|
|
|
|
// CatEntry describes one downloadable kernel bundle.
|
|
type CatEntry struct {
|
|
Name string `json:"name"`
|
|
Distro string `json:"distro,omitempty"`
|
|
Arch string `json:"arch,omitempty"`
|
|
KernelVersion string `json:"kernel_version,omitempty"`
|
|
TarballURL string `json:"tarball_url"`
|
|
TarballSHA256 string `json:"tarball_sha256"`
|
|
SizeBytes int64 `json:"size_bytes,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
}
|
|
|
|
// LoadEmbedded returns the catalog compiled into this banger binary.
|
|
func LoadEmbedded() (Catalog, error) {
|
|
return ParseCatalog(embeddedCatalog)
|
|
}
|
|
|
|
// ParseCatalog decodes a catalog.json payload. An empty payload is valid
|
|
// and returns a zero Catalog.
|
|
func ParseCatalog(data []byte) (Catalog, error) {
|
|
var cat Catalog
|
|
if len(data) == 0 {
|
|
return cat, nil
|
|
}
|
|
if err := json.Unmarshal(data, &cat); err != nil {
|
|
return Catalog{}, fmt.Errorf("parse catalog: %w", err)
|
|
}
|
|
return cat, nil
|
|
}
|
|
|
|
// Lookup returns the catalog entry matching name, or os.ErrNotExist.
|
|
func (c Catalog) Lookup(name string) (CatEntry, error) {
|
|
for _, e := range c.Entries {
|
|
if e.Name == name {
|
|
return e, nil
|
|
}
|
|
}
|
|
return CatEntry{}, os.ErrNotExist
|
|
}
|