diff --git a/internal/api/types.go b/internal/api/types.go index 8a043be..d73fac8 100644 --- a/internal/api/types.go +++ b/internal/api/types.go @@ -300,6 +300,13 @@ type KernelShowResult struct { Entry KernelEntry `json:"entry"` } +type KernelImportParams struct { + Name string `json:"name"` + FromDir string `json:"from_dir"` + Distro string `json:"distro,omitempty"` + Arch string `json:"arch,omitempty"` +} + type SudoStatus struct { Available bool `json:"available"` Command string `json:"command,omitempty"` diff --git a/internal/cli/banger.go b/internal/cli/banger.go index e37a5d5..678772a 100644 --- a/internal/cli/banger.go +++ b/internal/cli/banger.go @@ -1599,10 +1599,45 @@ func newKernelCommand() *cobra.Command { newKernelListCommand(), newKernelShowCommand(), newKernelRmCommand(), + newKernelImportCommand(), ) return cmd } +func newKernelImportCommand() *cobra.Command { + var params api.KernelImportParams + cmd := &cobra.Command{ + Use: "import ", + Short: "Import a kernel bundle produced by scripts/make-*-kernel.sh", + Long: "Copy the kernel, optional initrd, and optional modules directory from into the local kernel catalog keyed by . is usually build/manual/void-kernel or build/manual/alpine-kernel.", + Args: exactArgsUsage(1, "usage: banger kernel import --from "), + RunE: func(cmd *cobra.Command, args []string) error { + params.Name = args[0] + if strings.TrimSpace(params.FromDir) == "" { + return errors.New("--from is required") + } + abs, err := filepath.Abs(params.FromDir) + if err != nil { + return err + } + params.FromDir = abs + layout, _, err := ensureDaemon(cmd.Context()) + if err != nil { + return err + } + result, err := rpc.Call[api.KernelShowResult](cmd.Context(), layout.SocketPath, "kernel.import", params) + if err != nil { + return err + } + return printJSON(cmd.OutOrStdout(), result.Entry) + }, + } + cmd.Flags().StringVar(¶ms.FromDir, "from", "", "directory produced by make-*-kernel.sh (e.g. build/manual/void-kernel)") + cmd.Flags().StringVar(¶ms.Distro, "distro", "", "distribution label stored in the manifest (e.g. void, alpine)") + cmd.Flags().StringVar(¶ms.Arch, "arch", "", "architecture label stored in the manifest (e.g. x86_64)") + return cmd +} + func newKernelListCommand() *cobra.Command { return &cobra.Command{ Use: "list", diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index 6a53b07..4ca164c 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -75,7 +75,7 @@ func TestKernelCommandExposesSubcommands(t *testing.T) { for _, sub := range kernel.Commands() { names = append(names, sub.Name()) } - want := []string{"list", "rm", "show"} + want := []string{"import", "list", "rm", "show"} if !reflect.DeepEqual(names, want) { t.Fatalf("kernel subcommands = %v, want %v", names, want) } diff --git a/internal/daemon/daemon.go b/internal/daemon/daemon.go index 5c6ca6f..d4768be 100644 --- a/internal/daemon/daemon.go +++ b/internal/daemon/daemon.go @@ -543,6 +543,13 @@ func (d *Daemon) dispatch(ctx context.Context, req rpc.Request) rpc.Response { } err = d.KernelDelete(ctx, params.Name) return marshalResultOrError(api.Empty{}, err) + case "kernel.import": + params, err := rpc.DecodeParams[api.KernelImportParams](req) + if err != nil { + return rpc.NewError("bad_request", err.Error()) + } + entry, err := d.KernelImport(ctx, params) + return marshalResultOrError(api.KernelShowResult{Entry: entry}, err) default: return rpc.NewError("unknown_method", req.Method) } diff --git a/internal/daemon/kernels.go b/internal/daemon/kernels.go index 1436fd0..e3de641 100644 --- a/internal/daemon/kernels.go +++ b/internal/daemon/kernels.go @@ -2,12 +2,16 @@ package daemon import ( "context" + "errors" "fmt" "os" + "path/filepath" + "strings" "time" "banger/internal/api" "banger/internal/kernelcat" + "banger/internal/system" ) func (d *Daemon) KernelList(_ context.Context) (api.KernelListResult, error) { @@ -37,6 +41,97 @@ func (d *Daemon) KernelDelete(_ context.Context, name string) error { return kernelcat.DeleteLocal(d.layout.KernelsDir, name) } +// KernelImport copies the kernel / initrd / modules artifacts produced by +// scripts/make-*-kernel.sh (under params.FromDir) into the local catalog +// under params.Name and writes the manifest. It is the primary bridge from +// "I built a kernel with the helper scripts" to "banger kernel list shows +// it and image register --kernel-ref works." +func (d *Daemon) KernelImport(ctx context.Context, params api.KernelImportParams) (api.KernelEntry, error) { + name := strings.TrimSpace(params.Name) + if err := kernelcat.ValidateName(name); err != nil { + return api.KernelEntry{}, err + } + fromDir := strings.TrimSpace(params.FromDir) + if fromDir == "" { + return api.KernelEntry{}, errors.New("--from is required") + } + + discovered, err := kernelcat.DiscoverPaths(fromDir) + if err != nil { + return api.KernelEntry{}, fmt.Errorf("discover artifacts under %s: %w", fromDir, err) + } + + targetDir := kernelcat.EntryDir(d.layout.KernelsDir, name) + // Overwrite-by-default: clear any prior entry so a re-import is clean. + if err := kernelcat.DeleteLocal(d.layout.KernelsDir, name); err != nil { + return api.KernelEntry{}, fmt.Errorf("clear prior catalog entry %q: %w", name, err) + } + if err := os.MkdirAll(targetDir, 0o755); err != nil { + return api.KernelEntry{}, err + } + + kernelTarget := filepath.Join(targetDir, "vmlinux") + if err := system.CopyFilePreferClone(discovered.KernelPath, kernelTarget); err != nil { + return api.KernelEntry{}, fmt.Errorf("copy kernel: %w", err) + } + if discovered.InitrdPath != "" { + initrdTarget := filepath.Join(targetDir, "initrd.img") + if err := system.CopyFilePreferClone(discovered.InitrdPath, initrdTarget); err != nil { + return api.KernelEntry{}, fmt.Errorf("copy initrd: %w", err) + } + } + if discovered.ModulesDir != "" { + modulesTarget := filepath.Join(targetDir, "modules") + if err := os.MkdirAll(modulesTarget, 0o755); err != nil { + return api.KernelEntry{}, err + } + if err := system.CopyDirContents(ctx, d.runner, discovered.ModulesDir, modulesTarget, false); err != nil { + return api.KernelEntry{}, fmt.Errorf("copy modules: %w", err) + } + } + + sum, err := kernelcat.SumFile(kernelTarget) + if err != nil { + return api.KernelEntry{}, fmt.Errorf("sha256 kernel: %w", err) + } + + entry := kernelcat.Entry{ + Name: name, + Distro: strings.TrimSpace(params.Distro), + Arch: strings.TrimSpace(params.Arch), + KernelVersion: inferKernelVersion(discovered.KernelPath, discovered.ModulesDir), + SHA256: sum, + Source: "import:" + fromDir, + ImportedAt: time.Now().UTC(), + } + if err := kernelcat.WriteLocal(d.layout.KernelsDir, entry); err != nil { + return api.KernelEntry{}, fmt.Errorf("write manifest: %w", err) + } + stored, err := kernelcat.ReadLocal(d.layout.KernelsDir, name) + if err != nil { + return api.KernelEntry{}, err + } + return kernelEntryToAPI(stored), nil +} + +// inferKernelVersion makes a best-effort guess at the kernel version from +// the source filename (e.g. "vmlinux-6.12.79_1") or falls back to the +// modules directory basename. Returns "" if nothing looks useful. +func inferKernelVersion(kernelPath, modulesDir string) string { + if modulesDir != "" { + if base := filepath.Base(modulesDir); base != "." && base != string(filepath.Separator) { + return base + } + } + base := filepath.Base(kernelPath) + for _, prefix := range []string{"vmlinux-", "vmlinuz-"} { + if strings.HasPrefix(base, prefix) { + return strings.TrimPrefix(base, prefix) + } + } + return "" +} + func kernelEntryToAPI(entry kernelcat.Entry) api.KernelEntry { importedAt := "" if !entry.ImportedAt.IsZero() { diff --git a/internal/daemon/kernels_test.go b/internal/daemon/kernels_test.go index 369afef..e747c1f 100644 --- a/internal/daemon/kernels_test.go +++ b/internal/daemon/kernels_test.go @@ -12,6 +12,7 @@ import ( "banger/internal/kernelcat" "banger/internal/paths" "banger/internal/rpc" + "banger/internal/system" ) func seedKernelEntry(t *testing.T, kernelsDir, name string) { @@ -149,6 +150,72 @@ func TestRegisterImageRejectsKernelRefAndPath(t *testing.T) { } } +func TestKernelImportCopiesArtifactsAndWritesManifest(t *testing.T) { + src := t.TempDir() + if err := os.MkdirAll(filepath.Join(src, "boot"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(src, "boot", "vmlinux-6.12.79_1"), []byte("kernel-bytes"), 0o644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(src, "boot", "initramfs-6.12.79_1"), []byte("initrd-bytes"), 0o644); err != nil { + t.Fatal(err) + } + modulesSource := filepath.Join(src, "lib", "modules", "6.12.79_1") + if err := os.MkdirAll(modulesSource, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(modulesSource, "modules.dep"), []byte(""), 0o644); err != nil { + t.Fatal(err) + } + + kernelsDir := t.TempDir() + d := &Daemon{ + layout: paths.Layout{KernelsDir: kernelsDir}, + runner: system.NewRunner(), + } + + entry, err := d.KernelImport(context.Background(), api.KernelImportParams{ + Name: "void-6.12", + FromDir: src, + Distro: "void", + Arch: "x86_64", + }) + if err != nil { + t.Fatalf("KernelImport: %v", err) + } + if entry.Name != "void-6.12" || entry.Distro != "void" || entry.Arch != "x86_64" { + t.Fatalf("entry metadata = %+v", entry) + } + if entry.KernelVersion != "6.12.79_1" { + t.Errorf("KernelVersion = %q, want 6.12.79_1 (from modules dir)", entry.KernelVersion) + } + if entry.SHA256 == "" { + t.Errorf("SHA256 not populated") + } + + if _, err := os.Stat(filepath.Join(kernelsDir, "void-6.12", "vmlinux")); err != nil { + t.Errorf("kernel not copied: %v", err) + } + if _, err := os.Stat(filepath.Join(kernelsDir, "void-6.12", "initrd.img")); err != nil { + t.Errorf("initrd not copied: %v", err) + } + if _, err := os.Stat(filepath.Join(kernelsDir, "void-6.12", "modules", "modules.dep")); err != nil { + t.Errorf("modules not copied: %v", err) + } +} + +func TestKernelImportRejectsMissingFromDir(t *testing.T) { + d := &Daemon{ + layout: paths.Layout{KernelsDir: t.TempDir()}, + runner: system.NewRunner(), + } + _, err := d.KernelImport(context.Background(), api.KernelImportParams{Name: "x"}) + if err == nil || !strings.Contains(err.Error(), "--from") { + t.Fatalf("KernelImport without --from: err=%v", err) + } +} + func TestRegisterImageMissingKernelRef(t *testing.T) { rootfs := filepath.Join(t.TempDir(), "rootfs.ext4") if err := os.WriteFile(rootfs, []byte("rootfs"), 0o644); err != nil { diff --git a/internal/kernelcat/import.go b/internal/kernelcat/import.go new file mode 100644 index 0000000..c6cea0e --- /dev/null +++ b/internal/kernelcat/import.go @@ -0,0 +1,168 @@ +package kernelcat + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "sort" +) + +// DiscoveredArtifacts is what DiscoverPaths returns: absolute paths to a +// kernel, an optional initrd, and an optional modules directory located +// under the staged output of make-*-kernel.sh (or an equivalent layout). +type DiscoveredArtifacts struct { + KernelPath string + InitrdPath string + ModulesDir string +} + +// metadataFile is the JSON dropped by scripts/make-void-kernel.sh alongside +// its staged output. We read it when present to avoid guessing at filenames. +type metadataFile struct { + KernelPath string `json:"kernel_path"` + InitrdPath string `json:"initrd_path"` + ModulesDir string `json:"modules_dir"` +} + +// DiscoverPaths locates kernel / initrd / modules artifacts under fromDir. +// It prefers a metadata.json emitted by make-*-kernel.sh; otherwise it +// falls back to globbing boot/vmlinux-*, boot/vmlinuz-* (for Alpine), +// boot/initramfs-*, and the newest subdir under lib/modules/. +func DiscoverPaths(fromDir string) (DiscoveredArtifacts, error) { + info, err := os.Stat(fromDir) + if err != nil { + return DiscoveredArtifacts{}, err + } + if !info.IsDir() { + return DiscoveredArtifacts{}, fmt.Errorf("%s is not a directory", fromDir) + } + + if paths, ok, err := discoverFromMetadata(fromDir); err != nil { + return DiscoveredArtifacts{}, err + } else if ok { + return paths, nil + } + + bootDir := filepath.Join(fromDir, "boot") + kernel, err := latestMatch(bootDir, []string{"vmlinux-*", "vmlinuz-*"}) + if err != nil { + return DiscoveredArtifacts{}, fmt.Errorf("locate kernel under %s: %w", bootDir, err) + } + initrd, err := latestMatch(bootDir, []string{"initramfs-*"}) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return DiscoveredArtifacts{}, fmt.Errorf("locate initrd under %s: %w", bootDir, err) + } + modules, err := latestSubdir(filepath.Join(fromDir, "lib", "modules")) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return DiscoveredArtifacts{}, fmt.Errorf("locate modules under %s: %w", fromDir, err) + } + return DiscoveredArtifacts{ + KernelPath: kernel, + InitrdPath: initrd, + ModulesDir: modules, + }, nil +} + +func discoverFromMetadata(fromDir string) (DiscoveredArtifacts, bool, error) { + data, err := os.ReadFile(filepath.Join(fromDir, "metadata.json")) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return DiscoveredArtifacts{}, false, nil + } + return DiscoveredArtifacts{}, false, err + } + var meta metadataFile + if err := json.Unmarshal(data, &meta); err != nil { + return DiscoveredArtifacts{}, false, fmt.Errorf("parse metadata.json in %s: %w", fromDir, err) + } + kernel := absoluteOrAnchored(fromDir, meta.KernelPath) + if kernel == "" { + return DiscoveredArtifacts{}, false, nil + } + if _, err := os.Stat(kernel); err != nil { + return DiscoveredArtifacts{}, false, fmt.Errorf("metadata.json references missing kernel %s: %w", kernel, err) + } + out := DiscoveredArtifacts{KernelPath: kernel} + if meta.InitrdPath != "" { + candidate := absoluteOrAnchored(fromDir, meta.InitrdPath) + if _, err := os.Stat(candidate); err == nil { + out.InitrdPath = candidate + } + } + if meta.ModulesDir != "" { + candidate := absoluteOrAnchored(fromDir, meta.ModulesDir) + if info, err := os.Stat(candidate); err == nil && info.IsDir() { + out.ModulesDir = candidate + } + } + return out, true, nil +} + +// absoluteOrAnchored returns path as-is if absolute; otherwise joins it to +// anchor. Empty input returns "". +func absoluteOrAnchored(anchor, path string) string { + path = filepath.Clean(path) + if path == "" || path == "." { + return "" + } + if filepath.IsAbs(path) { + return path + } + return filepath.Join(anchor, path) +} + +// latestMatch returns the lexicographically latest file in dir matching any +// of patterns (filename globs, not full paths). Returns os.ErrNotExist if no +// match. +func latestMatch(dir string, patterns []string) (string, error) { + if _, err := os.Stat(dir); err != nil { + return "", err + } + entries, err := os.ReadDir(dir) + if err != nil { + return "", err + } + var matches []string + for _, entry := range entries { + if entry.IsDir() { + continue + } + for _, pattern := range patterns { + ok, _ := filepath.Match(pattern, entry.Name()) + if ok { + matches = append(matches, entry.Name()) + break + } + } + } + if len(matches) == 0 { + return "", os.ErrNotExist + } + sort.Strings(matches) + return filepath.Join(dir, matches[len(matches)-1]), nil +} + +// latestSubdir returns the lexicographically latest subdirectory of root. +// Returns os.ErrNotExist if root is missing or has no subdirs. +func latestSubdir(root string) (string, error) { + if _, err := os.Stat(root); err != nil { + return "", err + } + entries, err := os.ReadDir(root) + if err != nil { + return "", err + } + var dirs []string + for _, entry := range entries { + if entry.IsDir() { + dirs = append(dirs, entry.Name()) + } + } + if len(dirs) == 0 { + return "", os.ErrNotExist + } + sort.Strings(dirs) + return filepath.Join(root, dirs[len(dirs)-1]), nil +} diff --git a/internal/kernelcat/import_test.go b/internal/kernelcat/import_test.go new file mode 100644 index 0000000..5147f6d --- /dev/null +++ b/internal/kernelcat/import_test.go @@ -0,0 +1,133 @@ +package kernelcat + +import ( + "errors" + "os" + "path/filepath" + "testing" +) + +func writeFile(t *testing.T, path string, data string) { + t.Helper() + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, []byte(data), 0o644); err != nil { + t.Fatal(err) + } +} + +func TestDiscoverPathsPrefersMetadataJSON(t *testing.T) { + t.Parallel() + dir := t.TempDir() + writeFile(t, filepath.Join(dir, "boot", "vmlinux-custom"), "ignored") + writeFile(t, filepath.Join(dir, "boot", "initramfs-custom"), "ignored") + writeFile(t, filepath.Join(dir, "boot", "vmlinux-pick-me"), "kernel") + writeFile(t, filepath.Join(dir, "boot", "initramfs-pick-me"), "initrd") + if err := os.MkdirAll(filepath.Join(dir, "lib", "modules", "6.12.79_1"), 0o755); err != nil { + t.Fatal(err) + } + metadata := `{ +"kernel_path": "boot/vmlinux-pick-me", +"initrd_path": "boot/initramfs-pick-me", +"modules_dir": "lib/modules/6.12.79_1" +}` + writeFile(t, filepath.Join(dir, "metadata.json"), metadata) + + got, err := DiscoverPaths(dir) + if err != nil { + t.Fatalf("DiscoverPaths: %v", err) + } + if got.KernelPath != filepath.Join(dir, "boot", "vmlinux-pick-me") { + t.Errorf("KernelPath = %q", got.KernelPath) + } + if got.InitrdPath != filepath.Join(dir, "boot", "initramfs-pick-me") { + t.Errorf("InitrdPath = %q", got.InitrdPath) + } + if got.ModulesDir != filepath.Join(dir, "lib", "modules", "6.12.79_1") { + t.Errorf("ModulesDir = %q", got.ModulesDir) + } +} + +func TestDiscoverPathsFallsBackToGlobbing(t *testing.T) { + t.Parallel() + dir := t.TempDir() + writeFile(t, filepath.Join(dir, "boot", "vmlinux-6.12.0"), "k") + writeFile(t, filepath.Join(dir, "boot", "vmlinux-6.12.1"), "newer") + writeFile(t, filepath.Join(dir, "boot", "initramfs-6.12.1"), "i") + if err := os.MkdirAll(filepath.Join(dir, "lib", "modules", "6.12.0"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.MkdirAll(filepath.Join(dir, "lib", "modules", "6.12.1"), 0o755); err != nil { + t.Fatal(err) + } + + got, err := DiscoverPaths(dir) + if err != nil { + t.Fatalf("DiscoverPaths: %v", err) + } + if got.KernelPath != filepath.Join(dir, "boot", "vmlinux-6.12.1") { + t.Errorf("KernelPath = %q, want latest", got.KernelPath) + } + if got.InitrdPath != filepath.Join(dir, "boot", "initramfs-6.12.1") { + t.Errorf("InitrdPath = %q", got.InitrdPath) + } + if got.ModulesDir != filepath.Join(dir, "lib", "modules", "6.12.1") { + t.Errorf("ModulesDir = %q, want latest subdir", got.ModulesDir) + } +} + +func TestDiscoverPathsAlpineVmlinuzFallback(t *testing.T) { + t.Parallel() + dir := t.TempDir() + // Alpine older layouts may only ship vmlinuz-virt. + writeFile(t, filepath.Join(dir, "boot", "vmlinuz-virt"), "k") + writeFile(t, filepath.Join(dir, "boot", "initramfs-virt"), "i") + + got, err := DiscoverPaths(dir) + if err != nil { + t.Fatalf("DiscoverPaths: %v", err) + } + if got.KernelPath != filepath.Join(dir, "boot", "vmlinuz-virt") { + t.Errorf("KernelPath = %q, want vmlinuz-virt fallback", got.KernelPath) + } +} + +func TestDiscoverPathsMissingKernelIsError(t *testing.T) { + t.Parallel() + dir := t.TempDir() + // boot/ exists but contains no kernel + if err := os.MkdirAll(filepath.Join(dir, "boot"), 0o755); err != nil { + t.Fatal(err) + } + _, err := DiscoverPaths(dir) + if err == nil { + t.Fatal("expected error when no kernel present") + } + if !errors.Is(err, os.ErrNotExist) && !containsErr(err, "locate kernel") { + t.Fatalf("error shape: %v", err) + } +} + +func TestDiscoverPathsNotADirectory(t *testing.T) { + t.Parallel() + path := filepath.Join(t.TempDir(), "file") + writeFile(t, path, "") + _, err := DiscoverPaths(path) + if err == nil { + t.Fatal("expected error when fromDir is a file") + } +} + +func containsErr(err error, substr string) bool { + return err != nil && (err.Error() == substr || len(err.Error()) >= len(substr) && errContains(err.Error(), substr)) +} + +func errContains(s, substr string) bool { + for i := 0; i+len(substr) <= len(s); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false +} diff --git a/internal/system/system.go b/internal/system/system.go index 6368ed6..0f89449 100644 --- a/internal/system/system.go +++ b/internal/system/system.go @@ -288,7 +288,10 @@ func lastJSONLine(data []byte) []byte { } func CopyDirContents(ctx context.Context, runner CommandRunner, sourceDir, targetDir string, useSudo bool) error { - args := []string{"-a", filepath.Join(sourceDir, "."), targetDir + "/"} + // Trailing "/." on the source tells cp -a to copy the directory's + // contents rather than the directory itself. filepath.Join would + // strip the dot, hence the manual concat. + args := []string{"-a", strings.TrimRight(sourceDir, "/") + "/.", targetDir + "/"} var err error if useSudo { _, err = runner.RunSudo(ctx, append([]string{"cp"}, args...)...) diff --git a/scripts/register-alpine-image.sh b/scripts/register-alpine-image.sh index f72e7bc..b70e95b 100755 --- a/scripts/register-alpine-image.sh +++ b/scripts/register-alpine-image.sh @@ -5,23 +5,6 @@ log() { printf '[register-alpine-image] %s\n' "$*" >&2 } -find_latest_matching() { - local dir="$1" - local pattern="$2" - if [[ ! -d "$dir" ]]; then - return 1 - fi - find "$dir" -maxdepth 1 -type f -name "$pattern" | sort | tail -n 1 -} - -find_latest_module_dir() { - local root="$1" - if [[ ! -d "$root" ]]; then - return 1 - fi - find "$root" -mindepth 1 -maxdepth 1 -type d | sort | tail -n 1 -} - resolve_banger_bin() { if [[ -n "${BANGER_BIN:-}" ]]; then printf '%s\n' "$BANGER_BIN" @@ -47,6 +30,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" RUNTIME_DIR="${BANGER_MANUAL_DIR:-$REPO_ROOT/build/manual}" IMAGE_NAME="${ALPINE_IMAGE_NAME:-alpine}" +KERNEL_REF="${ALPINE_KERNEL_REF:-$IMAGE_NAME}" BANGER_BIN="$(resolve_banger_bin)" ROOTFS="$RUNTIME_DIR/rootfs-alpine.ext4" WORK_SEED="$RUNTIME_DIR/rootfs-alpine.work-seed.ext4" @@ -59,34 +43,22 @@ if [[ ! -f "$WORK_SEED" ]]; then log "missing Alpine work-seed: $WORK_SEED" exit 1 fi - -args=( - image register - --name "$IMAGE_NAME" - --rootfs "$ROOTFS" - --work-seed "$WORK_SEED" - --docker -) - if [[ ! -d "$RUNTIME_DIR/alpine-kernel" ]]; then log "missing staged Alpine kernel artifacts: $RUNTIME_DIR/alpine-kernel" log "run 'make alpine-kernel' before registering $IMAGE_NAME" exit 1 fi -kernel="$(find_latest_matching "$RUNTIME_DIR/alpine-kernel/boot" 'vmlinux-*' || true)" -if [[ -z "$kernel" ]]; then - kernel="$(find_latest_matching "$RUNTIME_DIR/alpine-kernel/boot" 'vmlinuz-*' || true)" -fi -initrd="$(find_latest_matching "$RUNTIME_DIR/alpine-kernel/boot" 'initramfs-*' || true)" -modules="$(find_latest_module_dir "$RUNTIME_DIR/alpine-kernel/lib/modules" || true)" +log "importing Alpine kernel from $RUNTIME_DIR/alpine-kernel as $KERNEL_REF" +"$BANGER_BIN" kernel import "$KERNEL_REF" \ + --from "$RUNTIME_DIR/alpine-kernel" \ + --distro alpine \ + --arch x86_64 -if [[ -z "$kernel" || -z "$initrd" || -z "$modules" ]]; then - log "staged Alpine kernel is incomplete; expected kernel, initramfs, and modules under $RUNTIME_DIR/alpine-kernel" - exit 1 -fi - -log "using staged Alpine kernel artifacts from $RUNTIME_DIR/alpine-kernel" -args+=(--kernel "$kernel" --initrd "$initrd" --modules "$modules") - -"$BANGER_BIN" "${args[@]}" +log "registering image $IMAGE_NAME with kernel-ref $KERNEL_REF" +"$BANGER_BIN" image register \ + --name "$IMAGE_NAME" \ + --rootfs "$ROOTFS" \ + --work-seed "$WORK_SEED" \ + --docker \ + --kernel-ref "$KERNEL_REF" diff --git a/scripts/register-void-image.sh b/scripts/register-void-image.sh index 0cc0719..64de6d7 100755 --- a/scripts/register-void-image.sh +++ b/scripts/register-void-image.sh @@ -5,23 +5,6 @@ log() { printf '[register-void-image] %s\n' "$*" >&2 } -find_latest_matching() { - local dir="$1" - local pattern="$2" - if [[ ! -d "$dir" ]]; then - return 1 - fi - find "$dir" -maxdepth 1 -type f -name "$pattern" | sort | tail -n 1 -} - -find_latest_module_dir() { - local root="$1" - if [[ ! -d "$root" ]]; then - return 1 - fi - find "$root" -mindepth 1 -maxdepth 1 -type d | sort | tail -n 1 -} - resolve_banger_bin() { if [[ -n "${BANGER_BIN:-}" ]]; then printf '%s\n' "$BANGER_BIN" @@ -47,6 +30,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" RUNTIME_DIR="${BANGER_MANUAL_DIR:-$REPO_ROOT/build/manual}" IMAGE_NAME="${VOID_IMAGE_NAME:-void}" +KERNEL_REF="${VOID_KERNEL_REF:-$IMAGE_NAME}" BANGER_BIN="$(resolve_banger_bin)" ROOTFS="$RUNTIME_DIR/rootfs-void.ext4" WORK_SEED="$RUNTIME_DIR/rootfs-void.work-seed.ext4" @@ -59,30 +43,21 @@ if [[ ! -f "$WORK_SEED" ]]; then log "missing Void work-seed: $WORK_SEED" exit 1 fi - -args=( - image register - --name "$IMAGE_NAME" - --rootfs "$ROOTFS" - --work-seed "$WORK_SEED" -) - if [[ ! -d "$RUNTIME_DIR/void-kernel" ]]; then log "missing staged Void kernel artifacts: $RUNTIME_DIR/void-kernel" log "run 'make void-kernel' before registering $IMAGE_NAME" exit 1 fi -kernel="$(find_latest_matching "$RUNTIME_DIR/void-kernel/boot" 'vmlinux-*' || true)" -initrd="$(find_latest_matching "$RUNTIME_DIR/void-kernel/boot" 'initramfs-*' || true)" -modules="$(find_latest_module_dir "$RUNTIME_DIR/void-kernel/lib/modules" || true)" +log "importing Void kernel from $RUNTIME_DIR/void-kernel as $KERNEL_REF" +"$BANGER_BIN" kernel import "$KERNEL_REF" \ + --from "$RUNTIME_DIR/void-kernel" \ + --distro void \ + --arch x86_64 -if [[ -z "$kernel" || -z "$initrd" || -z "$modules" ]]; then - log "staged Void kernel is incomplete; expected vmlinux, initramfs, and modules under $RUNTIME_DIR/void-kernel" - exit 1 -fi - -log "using staged Void kernel artifacts from $RUNTIME_DIR/void-kernel" -args+=(--kernel "$kernel" --initrd "$initrd" --modules "$modules") - -"$BANGER_BIN" "${args[@]}" +log "registering image $IMAGE_NAME with kernel-ref $KERNEL_REF" +"$BANGER_BIN" image register \ + --name "$IMAGE_NAME" \ + --rootfs "$ROOTFS" \ + --work-seed "$WORK_SEED" \ + --kernel-ref "$KERNEL_REF"