The golden-image Dockerfile + catalog pipeline replaces the entire
manual rootfs-build stack. With that shipped, the per-distro shell
flows are dead code.
Removed:
- scripts/customize.sh, scripts/interactive.sh, scripts/verify.sh
- scripts/make-rootfs{,-void,-alpine}.sh
- scripts/register-{void,alpine}-image.sh
- scripts/make-{void,alpine}-kernel.sh
- internal/imagepreset/ (only consumer was `banger internal packages`,
which fed customize.sh)
- examples/{void,alpine}.config.toml
- Makefile targets: rootfs, rootfs-void, rootfs-alpine, void-kernel,
alpine-kernel, void-register, alpine-register, void-vm, alpine-vm,
verify-void, verify-alpine, plus the ALPINE_RELEASE / *_IMAGE_NAME
/ *_VM_NAME variables
The void-6.12 kernel catalog entry is also gone — golden image pairs
with generic-6.12 and nothing else in the catalog depended on it.
Consolidated: imagemgr now holds the small DebianBasePackages list +
package-hash helper inline, so the `image build --from-image` flow
(still supported) no longer pulls from a separate imagepreset package.
Net: 3,815 lines deleted, 59 added. No runtime functionality removed
beyond the `banger internal packages` subcommand (hidden, used only
by the deleted customize.sh).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
150 lines
5.4 KiB
Go
150 lines
5.4 KiB
Go
// Package imagemgr contains the pure helpers of the banger image subsystem:
|
|
// path validators, artifact staging, managed-image metadata, and the guest
|
|
// provisioning script generator used by image build.
|
|
//
|
|
// The orchestrator methods (BuildImage, RegisterImage, PromoteImage,
|
|
// DeleteImage) still live in the daemon package and compose these helpers.
|
|
package imagemgr
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"banger/internal/system"
|
|
)
|
|
|
|
// debianBasePackages is the apt package list applied by
|
|
// `image build --from-image` to Debian-based managed rootfses. Small
|
|
// curated set: most of the developer tooling the golden image ships
|
|
// lives in the Dockerfile, not here.
|
|
var debianBasePackages = []string{
|
|
"make",
|
|
"git",
|
|
"less",
|
|
"tree",
|
|
"ca-certificates",
|
|
"curl",
|
|
"wget",
|
|
"iproute2",
|
|
"vim",
|
|
"tmux",
|
|
}
|
|
|
|
// DebianBasePackages returns a copy of the base package set.
|
|
func DebianBasePackages() []string {
|
|
return append([]string(nil), debianBasePackages...)
|
|
}
|
|
|
|
// hashPackages returns the hex sha256 of the package list, used as
|
|
// drift-detection metadata alongside a built rootfs.
|
|
func hashPackages(lines []string) string {
|
|
sum := sha256.Sum256([]byte(strings.Join(lines, "\n") + "\n"))
|
|
return fmt.Sprintf("%x", sum)
|
|
}
|
|
|
|
// ValidateRegisterPaths checks that rootfs + kernel exist and that optional
|
|
// artifacts, when provided, also exist.
|
|
func ValidateRegisterPaths(rootfsPath, workSeedPath, kernelPath, initrdPath, modulesDir string) error {
|
|
checks := system.NewPreflight()
|
|
checks.RequireFile(rootfsPath, "rootfs image", `pass --rootfs <path>`)
|
|
if workSeedPath != "" {
|
|
checks.RequireFile(workSeedPath, "work-seed image", `pass --work-seed <path> or rebuild the image with a work seed`)
|
|
}
|
|
addKernelChecks(checks, kernelPath, initrdPath, modulesDir)
|
|
return checks.Err("image register failed")
|
|
}
|
|
|
|
// ValidateKernelPaths checks the kernel triple alone, used by flows
|
|
// (e.g. image pull) that produce the rootfs themselves.
|
|
func ValidateKernelPaths(kernelPath, initrdPath, modulesDir string) error {
|
|
checks := system.NewPreflight()
|
|
addKernelChecks(checks, kernelPath, initrdPath, modulesDir)
|
|
return checks.Err("kernel preflight failed")
|
|
}
|
|
|
|
func addKernelChecks(checks *system.Preflight, kernelPath, initrdPath, modulesDir string) {
|
|
checks.RequireFile(kernelPath, "kernel image", `pass --kernel <path>`)
|
|
if initrdPath != "" {
|
|
checks.RequireFile(initrdPath, "initrd image", `pass --initrd <path>`)
|
|
}
|
|
if modulesDir != "" {
|
|
checks.RequireDir(modulesDir, "kernel modules dir", `pass --modules <dir>`)
|
|
}
|
|
}
|
|
|
|
// ValidatePromotePaths checks that an existing registered image's artifacts
|
|
// are still present before promoting it to daemon-owned storage.
|
|
func ValidatePromotePaths(rootfsPath, kernelPath, initrdPath, modulesDir string) error {
|
|
checks := system.NewPreflight()
|
|
checks.RequireFile(rootfsPath, "rootfs image", `re-register the image with a valid rootfs`)
|
|
checks.RequireFile(kernelPath, "kernel image", `re-register the image with a valid kernel`)
|
|
if initrdPath != "" {
|
|
checks.RequireFile(initrdPath, "initrd image", `re-register the image with a valid initrd`)
|
|
}
|
|
if modulesDir != "" {
|
|
checks.RequireDir(modulesDir, "kernel modules dir", `re-register the image with a valid modules dir`)
|
|
}
|
|
return checks.Err("image promote failed")
|
|
}
|
|
|
|
// StageBootArtifacts copies kernel/initrd/modules into artifactDir and
|
|
// returns the staged paths. initrd and modules are optional; an empty source
|
|
// returns an empty staged path.
|
|
func StageBootArtifacts(ctx context.Context, runner system.CommandRunner, artifactDir, kernelSource, initrdSource, modulesSource string) (string, string, string, error) {
|
|
kernelPath := filepath.Join(artifactDir, "kernel")
|
|
if err := system.CopyFilePreferClone(kernelSource, kernelPath); err != nil {
|
|
return "", "", "", err
|
|
}
|
|
initrdPath := ""
|
|
if strings.TrimSpace(initrdSource) != "" {
|
|
initrdPath = filepath.Join(artifactDir, "initrd.img")
|
|
if err := system.CopyFilePreferClone(initrdSource, initrdPath); err != nil {
|
|
return "", "", "", err
|
|
}
|
|
}
|
|
modulesDir := ""
|
|
if strings.TrimSpace(modulesSource) != "" {
|
|
modulesDir = filepath.Join(artifactDir, "modules")
|
|
if err := os.MkdirAll(modulesDir, 0o755); err != nil {
|
|
return "", "", "", err
|
|
}
|
|
if err := system.CopyDirContents(ctx, runner, modulesSource, modulesDir, false); err != nil {
|
|
return "", "", "", err
|
|
}
|
|
}
|
|
return kernelPath, initrdPath, modulesDir, nil
|
|
}
|
|
|
|
// StageOptionalArtifactPath returns the destination path for an optional
|
|
// artifact in artifactDir, or "" when stagedPath is empty (artifact absent).
|
|
func StageOptionalArtifactPath(artifactDir, stagedPath, name string) string {
|
|
if strings.TrimSpace(stagedPath) == "" {
|
|
return ""
|
|
}
|
|
return filepath.Join(artifactDir, name)
|
|
}
|
|
|
|
// BuildMetadataPackages returns the canonical package set recorded for a
|
|
// managed image build. The #feature:docker sentinel is appended when
|
|
// docker is requested.
|
|
func BuildMetadataPackages(docker bool) []string {
|
|
packages := DebianBasePackages()
|
|
if docker {
|
|
packages = append(packages, "#feature:docker")
|
|
}
|
|
return packages
|
|
}
|
|
|
|
// WritePackagesMetadata writes the hash of packages next to rootfsPath so
|
|
// future builds can detect drift. Empty packages or rootfsPath is a no-op.
|
|
func WritePackagesMetadata(rootfsPath string, packages []string) error {
|
|
if rootfsPath == "" || len(packages) == 0 {
|
|
return nil
|
|
}
|
|
metadataPath := rootfsPath + ".packages.sha256"
|
|
return os.WriteFile(metadataPath, []byte(hashPackages(packages)+"\n"), 0o644)
|
|
}
|