// 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" "os" "path/filepath" "strings" "banger/internal/imagepreset" "banger/internal/system" ) // 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 `) if workSeedPath != "" { checks.RequireFile(workSeedPath, "work-seed image", `pass --work-seed 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 `) if initrdPath != "" { checks.RequireFile(initrdPath, "initrd image", `pass --initrd `) } if modulesDir != "" { checks.RequireDir(modulesDir, "kernel modules dir", `pass --modules `) } } // 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 := imagepreset.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(imagepreset.Hash(packages)+"\n"), 0o644) }