Skip to content

Commit b476ee0

Browse files
committed
major refactor. core/service/gateway layers
1 parent 4dc3623 commit b476ee0

36 files changed

+471
-383
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2024 João Pinto
1+
Copyright (c) 2024-2025 João Pinto
22

33
Permission is hereby granted, free of charge, to any person obtaining a copy
44
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 62 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,76 @@
11
# kman (Linux Kernel Manager)
22

3-
kman aims to automate the Linux Kernel installation from source process, allowing for a
3+
kman aims to automate and unify the Linux Kernel installation from source process, allowing for a
44
repeatable way of installing a Linux kernel, generating a initramfs image and updating
5-
the bootloader configuration, based on the tools and configurations available of your system.
5+
the bootloader configuration, based on the tools and configurations available of your system,
6+
offering a sane and powerful way to manage kernels on any Linux distribution.
67

78
## Features
89

9-
- [x] No External Library dependencies
10-
- [x] Automated download of Kernel versions
11-
- [x] Cross-distro compatibility
12-
- [ ] Configuration file
13-
- [ ] Incremental updates via patching
14-
- [ ] Support multiple bootloaders configuration
10+
11+
- [x] Minimal external libraries
12+
- [x] Cross‑distro compatibility
13+
- [x] Automated download and verification of kernel versions
14+
- [ ] Accelerated & cached downloads, incremental updates
15+
- [ ] Embedded tar.gz/tar.xz multi-threaded decompression
16+
- [ ] Embedded key signature verification
17+
- [ ] Ephemeral container build environments
18+
- [ ] Configuration file support
19+
- [ ] Support for multiple bootloaders
1520
- [ ] GNU grub
1621
- [ ] systemd-boot
1722
- [ ] limine
1823
- [ ] rEFInd
19-
- [ ] Support multiple initramfs generators
24+
- [ ] Support for multiple initramfs generators
2025
- [ ] dracut
2126
- [ ] mkinitcpio
2227
- [ ] initramfs-tools
2328
- [ ] booster
24-
- [ ] distcc support for distributed compilation
25-
- [ ] modprobed-db support to reduce compile time
26-
- [ ] Unified kernel image
27-
28-
## Phases
29-
30-
Phases follow this order, all of them are optional and
31-
defined either with command flags or with a configuration file.
32-
33-
TODO: ensure all of this phases are correct and in right order
34-
35-
- Linux
36-
- list
37-
- download
38-
- verify
39-
- extract
40-
- configure
41-
- patch
42-
- compile
43-
- install
44-
- Initramfs
45-
- generate
46-
- Bootloader
47-
- configure
48-
49-
### Design Patterns
50-
51-
It is adopted some level of project maintainability by using some
52-
of the following design patterns:
53-
54-
- Strategy Pattern: The strategy lets the algorithm vary independently from clients
55-
that use it. Used for handling multiple bootloaders (e.g., GRUB, LILO, systemd-boot)
56-
and multiple initramfs tools (e.g., Dracut, mkinitcpio, booster) depending on the system.
57-
- Pipeline Pattern: Encapsulates each step (downloading, configuring, etc)
58-
into a set of steps (pipeline), allowing for easy execution and management. Ensures commands
59-
are ran in the correct order.
60-
- Builder Pattern: Provides a flexible way to construct and add kernel configuration parameters
61-
step-by-step.
62-
- Facade Pattern: Manages the kernel context and encapsulates the flow of commands into one component.
29+
- [ ] distcc, ccache, modprobed-db, and unified kernel image support
30+
31+
## Pipeline Steps
32+
33+
Each step in the pipeline can be executed indidually, but requires some parameter/s.
34+
35+
- Download (url)
36+
- Verify (archive)
37+
- Extract (archive)
38+
- Patch (optional: directory)
39+
- Configure (optional: .config file, directory, njobs)
40+
- Compile (optional: directory)
41+
- Install (optional: directory)
42+
- Initramfs (optional: initramfs)
43+
- Bootloader (optional: bootloader)
44+
45+
### Architecture
46+
47+
Some level of software architecture is adopted to make the project maintainability and evolution easier, such
48+
as separation of concerns (e.g. validation of data separated from execution of code) and responsabilities
49+
by layer (gateway, service, core); the use of design patterns for step dependency resolution
50+
and interaction with multiple kinds of outside tools.
51+
52+
As a rule of thumb I like to keep my '.go' files as small as possible, have a component
53+
based design and have as little external dependencies as possible, even in outside layers.
54+
55+
The final program must work as a unified cohesive experience.
56+
57+
#### Layers
58+
59+
- Gateway: UI (CLI/TUI/GUI), I/O, Interaction with 3rd party programs.
60+
- Service: Execution layer, use cases, orchestration logic.
61+
- Core: Definition of entities, validation, data structures; Does not depend on anything.
62+
63+
Ideally dependencies should flow inward, in practice this is achieved by extensive use of interfaces.
64+
Interfaces add overhead and they are not always needed. Usually I will have injection of raw implementation
65+
(without interface) because of that.
66+
67+
#### Design Patterns
68+
69+
- Strategy Pattern: Used to handle multiple bootloader and initramfs tools. The Service layer
70+
chooses the right strategy at runtime based on system capabilities and configuration.
71+
- Pipeline Pattern: Each step (list, download, compile, etc.) is encapsulated as a modular
72+
component. The Pipeline Manager in the Service layer coordinates step dependencies and execution order.
73+
- Builder Pattern: A flexible way to assemble the kernel build configuration step-by‑step,
74+
culminating in a unified kernel build process.
75+
- Facade Pattern: Provides a simplified interface (a unified kernel context) to coordinate the
76+
flow of commands across multiple steps.

cmd/kman/kman.go

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,26 @@ package main
33
import (
44
"os"
55

6-
"github.com/jpnt/kman/internal/kernel"
6+
"github.com/jpnt/kman/internal/core"
7+
"github.com/jpnt/kman/internal/service"
78
"github.com/jpnt/kman/pkg/logger"
89
)
910

1011
func main() {
1112
l := logger.NewLogger(logger.InfoLevel)
12-
b := kernel.NewKernelBuilder(l)
1313

14-
// fmt.Println(args.len())
14+
ctx := core.NewKernelContext()
15+
p := core.NewPipeline(ctx)
16+
f := service.NewStepFactory()
1517

16-
// TODO: dynamic argument builder configuration
17-
// if args.len()
18-
b = b.WithDefault()
19-
// else:
20-
// b = b.WithArguments(args)
18+
b := core.NewPipelineBuilder(l, p, f)
2119

20+
b = b.WithDefault()
21+
// TODO: dynamic argument builder configuration
2222

23-
f, err := b.Build()
23+
err := p.Run()
2424
if err != nil {
2525
l.Error("Error: %s", err.Error())
26-
}
27-
28-
if f.Run() != nil {
29-
l.Error("Error: %s", err.Error())
3026
os.Exit(1)
3127
}
3228
}

internal/core/context.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package core
2+
3+
import (
4+
"errors"
5+
)
6+
7+
type IKernelContext interface {
8+
Validate(stepName string) error
9+
}
10+
11+
type KernelContext struct {
12+
TarballURL string
13+
DownloadPath string
14+
ArchivePath string
15+
Directory string
16+
SignatureURL string
17+
ConfigOptions []string
18+
OldConfigPath string
19+
NumJobs string
20+
Initramfs string
21+
Bootloader string
22+
}
23+
24+
var _ IKernelContext = (*KernelContext)(nil)
25+
26+
func NewKernelContext() IKernelContext {
27+
return &KernelContext{}
28+
}
29+
30+
func (c *KernelContext) Validate(stepName string) error {
31+
switch stepName {
32+
case "download":
33+
if c.TarballURL == "" {
34+
return errors.New("kernel tarball URL not set")
35+
}
36+
case "verify":
37+
if c.ArchivePath == "" {
38+
return errors.New("kernel archive path not set")
39+
}
40+
case "extract":
41+
if c.ArchivePath == "" {
42+
return errors.New("kernel archive path not set")
43+
}
44+
}
45+
return nil
46+
}

internal/core/pipeline.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package core
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
type IPipeline interface {
8+
AddStep(step IStep)
9+
Steps() []IStep
10+
Ctx() IKernelContext
11+
Run() error
12+
}
13+
14+
type Pipeline struct {
15+
steps []IStep
16+
ctx IKernelContext
17+
}
18+
19+
// Ensure struct implements interface
20+
var _ IPipeline = (*Pipeline)(nil)
21+
22+
func NewPipeline(c IKernelContext) *Pipeline {
23+
return &Pipeline{ctx: c}
24+
}
25+
26+
func (pl *Pipeline) AddStep(step IStep) {
27+
pl.steps = append(pl.steps, step)
28+
}
29+
30+
func (pl *Pipeline) Steps() []IStep {
31+
return pl.steps
32+
}
33+
34+
func (pl *Pipeline) Ctx() IKernelContext {
35+
return pl.ctx
36+
}
37+
38+
func (pl *Pipeline) Run() error {
39+
if len(pl.Steps()) == 0 {
40+
return fmt.Errorf("no steps were configured")
41+
}
42+
43+
for _, step := range pl.steps {
44+
if err := pl.ctx.Validate(step.Name()); err != nil {
45+
return fmt.Errorf("validation failed for step %q: %w", step.Name(), err)
46+
}
47+
48+
if err := step.Execute(); err != nil {
49+
return fmt.Errorf("execution failed for step %q: %w", step.Name(), err)
50+
}
51+
}
52+
return nil
53+
}

internal/core/pipeline_builder.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package core
2+
3+
import (
4+
"github.com/jpnt/kman/pkg/logger"
5+
)
6+
7+
type IPipelineBuilder interface {
8+
WithStep(stepName string) IPipelineBuilder
9+
WithDefault() IPipelineBuilder
10+
}
11+
12+
type PipelineBuilder struct {
13+
logger logger.ILogger
14+
pl IPipeline
15+
factory IStepFactory
16+
}
17+
18+
// Ensure struct implements interface
19+
var _ IPipelineBuilder = (*PipelineBuilder)(nil)
20+
21+
func NewPipelineBuilder(l logger.ILogger, p IPipeline, f IStepFactory) IPipelineBuilder {
22+
return &PipelineBuilder{logger: l, pl: p, factory: f}
23+
}
24+
25+
func (b *PipelineBuilder) WithStep(stepName string) IPipelineBuilder {
26+
step, err := b.factory.CreateStep(stepName, b.logger, b.pl.Ctx())
27+
if err != nil {
28+
b.logger.Warn("Unrecognized step: %q", stepName)
29+
return b
30+
}
31+
b.pl.AddStep(step)
32+
return b
33+
}
34+
35+
func (b *PipelineBuilder) WithDefault() IPipelineBuilder {
36+
return b.
37+
WithStep("list").
38+
WithStep("download").
39+
WithStep("verify").
40+
WithStep("extract").
41+
WithStep("patch").
42+
WithStep("configure").
43+
WithStep("compile").
44+
WithStep("install").
45+
WithStep("initramfs").
46+
WithStep("bootloader")
47+
}

internal/core/step.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package core
2+
3+
type IStep interface {
4+
Name() string
5+
// CtxParamsNeeded() []string // TODO: simpler way?
6+
Execute() error
7+
// Completed() bool
8+
}

internal/core/step_factory.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package core
2+
3+
import (
4+
"github.com/jpnt/kman/pkg/logger"
5+
)
6+
7+
type IStepFactory interface {
8+
CreateStep(name string, logger logger.ILogger, ctx IKernelContext) (IStep, error)
9+
}

internal/gateway/bootloader/bootloader.go

Whitespace-only changes.
File renamed without changes.

0 commit comments

Comments
 (0)