// Package imagecat is the published catalog of banger image bundles // (rootfs.ext4 + manifest.json, packaged as a .tar.zst). It ships // embedded in the banger binary. Downloading a bundle is the fast // path for pulling a curated banger image — the rootfs is already // flattened, ownership-fixed, and has banger's guest agents injected // at build time. // // This package is the metadata + fetch layer. Writing to the banger // image store is done by higher layers (the daemon's PullImage // orchestrator), so imagecat has no local-storage concept of its own. package imagecat import ( _ "embed" "encoding/json" "fmt" "os" "regexp" "strings" ) //go:embed catalog.json var embeddedCatalog []byte // Catalog is the list of pullable image bundles compiled into this // banger binary. type Catalog struct { Version int `json:"version"` Entries []CatEntry `json:"entries"` } // CatEntry describes one downloadable bundle. TarballURL points at a // .tar.zst containing rootfs.ext4 and manifest.json. type CatEntry struct { Name string `json:"name"` Distro string `json:"distro,omitempty"` Arch string `json:"arch,omitempty"` KernelRef string `json:"kernel_ref,omitempty"` // kernelcat entry name to pair with 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 yields 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 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 } // namePattern accepts short filesystem-safe identifiers. Same rule as // kernelcat so `--kernel-ref` and bundle-name refs share syntax. var namePattern = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$`) // ValidateName returns an error unless name is a non-empty identifier // of alphanumerics, dots, hyphens, and underscores, starting with an // alphanumeric and at most 64 characters long. func ValidateName(name string) error { if strings.TrimSpace(name) == "" { return fmt.Errorf("image name is required") } if !namePattern.MatchString(name) { return fmt.Errorf("invalid image name %q: use alphanumerics, dots, hyphens, underscores (<=64 chars, starts with alphanumeric)", name) } return nil }