From bf237f9dcb893425f75efe496ce7feca83ddd281 Mon Sep 17 00:00:00 2001 From: Dimitris Karakasilis Date: Tue, 18 Nov 2025 10:17:15 +0200 Subject: [PATCH 1/2] Allow setting the arch of the image to be pulled because when we are on amd64 host, the auroraboot image is amd64 but we may want to pull and build artifacts for arm64 (e.g. when building rpi raw images). Signed-off-by: Dimitris Karakasilis --- README.md | 17 +++++++++++++++++ deployer/steps.go | 3 ++- example.yaml | 2 ++ pkg/ops/container.go | 19 ++++++++++++++++--- pkg/ops/iso.go | 19 +++++++++++++++++++ pkg/schema/config.go | 3 +++ 6 files changed, 59 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 04fa531f..d520e88e 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,22 @@ This command will: - **Create a custom ISO with the cloud config attached to drive automated installations** - **Provision Kairos from network, with the same settings** +### Specifying architecture + +When pulling container images for a different architecture than the host (e.g., pulling ARM64 images on an AMD64 host), you need to specify the `arch` option: + +``` +docker run --rm -ti --net host quay.io/kairos/auroraboot \ + --set container_image=quay.io/kairos/alpine:3.21-standard-arm64-rpi4-v3.6.0-rc5-k3s-v1.32.9-k3s1 \ + --set arch=arm64 +``` + +Supported architectures: +- `amd64` (default, matches x86_64) +- `arm64` (matches aarch64) + +**Note**: If you don't specify `arch` when pulling images for a different architecture, AuroraBoot will default to the host architecture (`amd64` on x86_64 systems), which will fail when the image doesn't have a matching platform variant. + ### Disable Netboot To disable netboot, and allow only ISO generation (for offline usage), use `--set disable_netboot=true`: @@ -129,6 +145,7 @@ A configuration file can be for instance: artifact_version: "v2.4.2" release_version: "v2.4.2" container_image: "..." +arch: "amd64" # Optional: architecture to use when pulling container images (amd64 or arm64) flavor: "rockylinux" flavor_release: "9" repository: "kairos-io/kairos" diff --git a/deployer/steps.go b/deployer/steps.go index e6111f4a..1a40b0bf 100644 --- a/deployer/steps.go +++ b/deployer/steps.go @@ -95,9 +95,10 @@ func (d *Deployer) StepCopyCloudConfig() error { func (d *Deployer) StepDumpSource() error { // Ops to generate from container image + internal.Log.Logger.Debug().Str("arch", d.Config.Arch).Str("image", d.Artifact.ContainerImage).Msg("StepDumpSource: config arch and image") return d.Add(constants.OpDumpSource, herd.EnableIf(d.fromImage), - herd.WithDeps(constants.OpPrepareDirs), herd.WithCallback(ops.DumpSource(d.Artifact.ContainerImage, d.tmpRootFs))) + herd.WithDeps(constants.OpPrepareDirs), herd.WithCallback(ops.DumpSource(d.Artifact.ContainerImage, d.tmpRootFs, d.Config.Arch))) } func (d *Deployer) StepGenISO() error { diff --git a/example.yaml b/example.yaml index d796a819..ad2e8da1 100644 --- a/example.yaml +++ b/example.yaml @@ -1,5 +1,7 @@ artifact_version: "v2.4.2" release_version: "v2.4.2" +container_image: "" # Optional: container image to use instead of artifacts +arch: "amd64" # Optional: architecture to use when pulling container images (amd64 or arm64) flavor: "rockylinux" flavor_release: "9" repository: "kairos-io/kairos" diff --git a/pkg/ops/container.go b/pkg/ops/container.go index e56cc29e..130ef83b 100644 --- a/pkg/ops/container.go +++ b/pkg/ops/container.go @@ -13,16 +13,29 @@ import ( // or simply copies the directory to the destination. // Supports these prefixes: // https://github.com/kairos-io/kairos-agent/blob/1e81cdef38677c8a36cae50d3334559976f66481/pkg/types/v1/common.go#L30-L33 -func DumpSource(image string, dstFunc valueGetOnCall) func(ctx context.Context) error { +func DumpSource(image string, dstFunc valueGetOnCall, arch string) func(ctx context.Context) error { return func(ctx context.Context) error { dst := dstFunc() if image == "" { return fmt.Errorf("image source is empty, cannot dump to %s", dst) } - cfg := NewConfig( + internal.Log.Logger.Debug().Str("arch", arch).Msg("DumpSource: arch parameter") + opts := []GenericOptions{ WithImageExtractor(v1.OCIImageExtractor{}), WithLogger(internal.Log), - ) + } + if arch != "" { + opts = append(opts, WithArch(arch)) + } + cfg := NewConfig(opts...) + if cfg != nil { + internal.Log.Logger.Debug().Str("arch", cfg.Arch).Msg("DumpSource: config arch after NewConfig") + if cfg.Platform != nil { + internal.Log.Logger.Debug().Str("platform", cfg.Platform.String()).Msg("DumpSource: config platform after NewConfig") + } else { + internal.Log.Logger.Debug().Msg("DumpSource: config platform is nil after NewConfig") + } + } e := elemental.NewElemental(cfg) imgSource, err := v1.NewSrcFromURI(image) diff --git a/pkg/ops/iso.go b/pkg/ops/iso.go index da2a3c4c..9447a330 100644 --- a/pkg/ops/iso.go +++ b/pkg/ops/iso.go @@ -756,3 +756,22 @@ func WithImageExtractor(extractor v1types.ImageExtractor) func(r *agentconfig.Co return nil } } + +func WithArch(arch string) func(r *agentconfig.Config) error { + return func(r *agentconfig.Config) error { + if arch != "" { + convertedArch, err := utils.GolangArchToArch(arch) + if err != nil { + return fmt.Errorf("invalid architecture %s: %w", arch, err) + } + r.Arch = convertedArch + // Also set Platform since DumpSource uses Platform.String() not Arch + platform, err := v1types.NewPlatformFromArch(arch) + if err != nil { + return fmt.Errorf("invalid architecture for platform %s: %w", arch, err) + } + r.Platform = platform + } + return nil + } +} diff --git a/pkg/schema/config.go b/pkg/schema/config.go index abc398ba..14c22e29 100644 --- a/pkg/schema/config.go +++ b/pkg/schema/config.go @@ -27,6 +27,9 @@ type Config struct { ListenAddr string `yaml:"listen_addr"` + // Architecture to use for container image pulling (e.g., "amd64", "arm64") + Arch string `yaml:"arch"` + // ISO block configuration ISO ISO `yaml:"iso"` From 4f3465a8952f9883488e5a84b6e9c853ad515be6 Mon Sep 17 00:00:00 2001 From: Dimitris Karakasilis Date: Tue, 18 Nov 2025 12:38:11 +0200 Subject: [PATCH 2/2] Add debug information for when the source dir is not as expected Signed-off-by: Dimitris Karakasilis --- internal/cmd/build-uki.go | 42 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/internal/cmd/build-uki.go b/internal/cmd/build-uki.go index 70c5bf6a..1323583c 100644 --- a/internal/cmd/build-uki.go +++ b/internal/cmd/build-uki.go @@ -288,6 +288,9 @@ var BuildUKICmd = cli.Command{ e := elemental.NewElemental(config) _, err = e.DumpSource(sourceDir, imgSource) + if err != nil { + return fmt.Errorf("extracting image source: %w", err) + } defer os.RemoveAll(sourceDir) if overlayRootfs := ctx.String("overlay-rootfs"); overlayRootfs != "" { @@ -550,13 +553,46 @@ func getEfiNeededFiles(arch string) ([]string, error) { } func findKairosVersion(sourceDir string) (string, error) { + // Check if sourceDir exists + if _, err := os.Stat(sourceDir); err != nil { + return "", fmt.Errorf("source directory does not exist: %s: %w", sourceDir, err) + } + var osReleaseBytes []byte - osReleaseBytes, err := os.ReadFile(filepath.Join(sourceDir, "etc", "kairos-release")) + var err error + + // Try kairos-release first + kairosReleasePath := filepath.Join(sourceDir, "etc", "kairos-release") + osReleaseBytes, err = os.ReadFile(kairosReleasePath) if err != nil { // fallback to os-release - osReleaseBytes, err = os.ReadFile(filepath.Join(sourceDir, "etc", "os-release")) + osReleasePath := filepath.Join(sourceDir, "etc", "os-release") + osReleaseBytes, err = os.ReadFile(osReleasePath) if err != nil { - return "", fmt.Errorf("reading kairos-release file: %w", err) + // Check if etc directory exists and list its contents for debugging + etcDir := filepath.Join(sourceDir, "etc") + if etcInfo, err := os.Stat(etcDir); err == nil && etcInfo.IsDir() { + entries, listErr := os.ReadDir(etcDir) + if listErr == nil { + var files []string + for _, entry := range entries { + files = append(files, entry.Name()) + } + return "", fmt.Errorf("reading kairos-release or os-release file: %w (found files in etc/: %v)", err, files) + } + } + // If etc doesn't exist, list top-level directory contents + entries, listErr := os.ReadDir(sourceDir) + if listErr == nil { + var dirs []string + for _, entry := range entries { + if entry.IsDir() { + dirs = append(dirs, entry.Name()) + } + } + return "", fmt.Errorf("reading kairos-release or os-release file: %w (top-level directories found: %v)", err, dirs) + } + return "", fmt.Errorf("reading kairos-release or os-release file: %w", err) } }