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/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) } } 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"`