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) { entries, err := kernelcat.ListLocal(d.layout.KernelsDir) if err != nil { return api.KernelListResult{}, err } result := api.KernelListResult{Entries: make([]api.KernelEntry, 0, len(entries))} for _, entry := range entries { result.Entries = append(result.Entries, kernelEntryToAPI(entry)) } return result, nil } func (d *Daemon) KernelShow(_ context.Context, name string) (api.KernelEntry, error) { entry, err := kernelcat.ReadLocal(d.layout.KernelsDir, name) if err != nil { return api.KernelEntry{}, kernelNotFoundIfMissing(name, err) } return kernelEntryToAPI(entry), nil } func (d *Daemon) KernelDelete(_ context.Context, name string) error { if err := kernelcat.ValidateName(name); err != nil { return err } 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() { importedAt = entry.ImportedAt.UTC().Format(time.RFC3339) } return api.KernelEntry{ Name: entry.Name, Distro: entry.Distro, Arch: entry.Arch, KernelVersion: entry.KernelVersion, SHA256: entry.SHA256, Source: entry.Source, ImportedAt: importedAt, KernelPath: entry.KernelPath, InitrdPath: entry.InitrdPath, ModulesDir: entry.ModulesDir, } } func kernelNotFoundIfMissing(name string, err error) error { if err == nil { return nil } if os.IsNotExist(err) { return fmt.Errorf("kernel %q not found", name) } return err }