diff --git a/.github/workflows/base-images.yaml b/.github/workflows/base-images.yaml index 2af0979d..53f8a652 100644 --- a/.github/workflows/base-images.yaml +++ b/.github/workflows/base-images.yaml @@ -2,11 +2,186 @@ name: Build Kairos Init Base Images on: workflow_dispatch: + inputs: + base_os_image: + description: "Base OS Image" + required: false + type: string + default: "" + kairos_init_image: + description: "Kairos Init Image" + required: false + type: string + default: "quay.io/kairos/kairos-init:v0.5.28" + arch: + description: "Architecture" + required: false + type: choice + options: + - amd64 + - arm64 + default: "amd64" + model: + description: "Model" + required: false + type: string + default: "generic" + kairos_version: + description: "Kairos Version" + required: false + type: string + default: "v3.5.9" + trusted_boot: + description: "Trusted Boot" + required: false + type: boolean + default: false + registry_prefix: + description: "Registry prefix for output images" + required: false + type: string + default: "us-east1-docker.pkg.dev/spectro-images/dev/pe-6616/edge" jobs: generate-matrix: - runs-on: ubuntu-latest + runs-on: Luet-BigRunner + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - - name: Generate Matrix + - name: Generate build matrix + id: set-matrix run: | - echo "Hello" + python3 << 'EOF' + import json + import os + + base_os_image = "${{ github.event.inputs.base_os_image }}" + registry_prefix = "${{ github.event.inputs.registry_prefix }}" + arch = "${{ github.event.inputs.arch }}" + model = "${{ github.event.inputs.model }}" + kairos_version = "${{ github.event.inputs.kairos_version }}" + trusted_boot = "${{ github.event.inputs.trusted_boot }}" == "true" + + matrix = [] + + if base_os_image: + # Custom base OS image + simple_name = base_os_image.split('/')[-1].replace(':', '-') + tag = f"{registry_prefix}/kairos-custom:{simple_name}-core-{arch}-{model}-{kairos_version}" + if trusted_boot: + tag += "-uki" + + matrix.append({ + "base_os": base_os_image, + "tag": tag + }) + + elif trusted_boot: + # UKI builds - only Ubuntu 24.04 + tag = f"{registry_prefix}/kairos-ubuntu:24.04-core-{arch}-{model}-{kairos_version}-uki" + matrix.append({ + "base_os": "ubuntu:24.04", + "tag": tag + }) + + else: + # Standard builds - all combinations + combinations = [ + ("ubuntu:20.04", "kairos-ubuntu:20.04-core"), + ("ubuntu:22.04", "kairos-ubuntu:22.04-core"), + ("ubuntu:24.04", "kairos-ubuntu:24.04-core"), + ("opensuse/leap:15.6", "kairos-opensuse:leap-15.6-core") + ] + + for base_os, tag_prefix in combinations: + tag = f"{registry_prefix}/{tag_prefix}-{arch}-{model}-{kairos_version}" + matrix.append({ + "base_os": base_os, + "tag": tag + }) + + matrix_json = json.dumps(matrix) + print(f"Generated matrix: {matrix_json}") + + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + f.write(f"matrix={matrix_json}\n") + EOF + + kairosify: + needs: generate-matrix + runs-on: Luet-BigRunner + strategy: + matrix: + include: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} + fail-fast: false + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to registry + run: echo "${{ secrets.US_EAST_JSON_KEY_B64 }}" | base64 -d | docker login -u _json_key --password-stdin us-east1-docker.pkg.dev + + - name: Build and push kairosify image + uses: docker/bake-action@v6 + with: + files: docker-bake-kairosify.hcl + targets: kairosify + push: true + set: | + kairosify.platform=linux/${{ github.event.inputs.arch }} + kairosify.args.BASE_OS_IMAGE=${{ matrix.base_os }} + kairosify.args.KAIROS_INIT_IMAGE=${{ github.event.inputs.kairos_init_image }} + kairosify.args.KAIROS_VERSION=${{ github.event.inputs.kairos_version }} + kairosify.args.TRUSTED_BOOT=${{ github.event.inputs.trusted_boot }} + kairosify.args.MODEL=${{ github.event.inputs.model }} + kairosify.tags=${{ matrix.tag }} + env: + DOCKER_BUILD_SUMMARY: false + + - name: Save build result + run: | + mkdir -p /tmp/results + echo "${{ matrix.tag }}" >> /tmp/results/built_tags.txt + + - name: Upload build results + uses: actions/upload-artifact@v4 + with: + name: build-result-${{ strategy.job-index }}-${{ hashFiles('**/matrix.*') }} + path: /tmp/results/built_tags.txt + + collect-outputs: + needs: kairosify + runs-on: Luet-BigRunner + outputs: + built_tags: ${{ steps.combine-tags.outputs.tags }} + steps: + - name: Download all build results + uses: actions/download-artifact@v4 + with: + pattern: build-result-* + path: /tmp/results + + - name: Combine all tags + id: combine-tags + run: | + ALL_TAGS=$(find /tmp/results -name "*.txt" -type f -exec cat {} \; | grep -v '^$' | jq -R -s -c 'split("\n") | map(select(length > 0))') + echo "tags=$ALL_TAGS" >> $GITHUB_OUTPUT + echo "All built tags: $ALL_TAGS" + + - name: Summary + run: | + echo "## Kairosify Build Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Input Parameters:**" >> $GITHUB_STEP_SUMMARY + echo "- Base OS Image: ${{ github.event.inputs.base_os_image || 'Default combinations' }}" >> $GITHUB_STEP_SUMMARY + echo "- Kairos Init Image: ${{ github.event.inputs.kairos_init_image }}" >> $GITHUB_STEP_SUMMARY + echo "- Architecture: ${{ github.event.inputs.arch }}" >> $GITHUB_STEP_SUMMARY + echo "- Model: ${{ github.event.inputs.model }}" >> $GITHUB_STEP_SUMMARY + echo "- Kairos Version: ${{ github.event.inputs.kairos_version }}" >> $GITHUB_STEP_SUMMARY + echo "- Trusted Boot: ${{ github.event.inputs.trusted_boot }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Built Images:**" >> $GITHUB_STEP_SUMMARY + echo '${{ steps.combine-tags.outputs.tags }}' | jq -r '.[] | "- " + .' >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml deleted file mode 100644 index 72877e90..00000000 --- a/.github/workflows/pr.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# name: Pr - -# on: [pull_request] - -# concurrency: -# group: ${{ github.pr_workflow }}-${{ github.ref }} -# cancel-in-progress: true - -# permissions: -# contents: read - -# jobs: -# build: -# uses: ./.github/workflows/release.yaml -# secrets: inherit \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..87848b58 --- /dev/null +++ b/Makefile @@ -0,0 +1,301 @@ +-include .arg +export + +# ============================================================================== +# TARGETS +# ============================================================================== +.PHONY: build build-all-images iso build-provider-images iso-disk-image \ + uki-genkey secure-boot-dirs alpine-all validate-user-data \ + cloud-image raw-image aws-cloud-image iso-image-cloud cloud-image-tools \ + maas-image iso-image-maas \ + internal-slink iso-efi-size-check \ + clean clean-all clean-cloud-image clean-keys help + +.SILENT: uki-genkey validate-user-data clean-cloud-image secure-boot-dirs help cloud-image + +# ============================================================================== +# CONSTANTS +# ============================================================================== +AURORABOOT_IMAGE := quay.io/kairos/auroraboot:v0.19.0 + +# Docker Bake file configuration +COMMON_BAKE_FILE := docker-bake-common.hcl +MAIN_BAKE_FILE := docker-bake.hcl + +# Bake file arguments for main builds (includes common + main bake files) +BAKE_FILES := -f $(COMMON_BAKE_FILE) -f $(MAIN_BAKE_FILE) + +CLOUD_IMAGE_DIR := $(CURDIR)/build/cloud-image +MAAS_IMAGE_DIR := $(CURDIR)/build/maas-image +ISO_IMAGE_CLOUD := palette-installer-image-cloud:latest +ISO_IMAGE_MAAS := palette-installer-image-maas:latest + +CLOUD_IMAGE_TOOLS := us-docker.pkg.dev/palette-images/edge/canvos/cloud-image-tools:latest +MAAS_IMAGE_NAME ?= kairos-ubuntu-maas + +# ============================================================================== +# HELPER VARIABLES +# ============================================================================== +comma := , +empty := +space := $(empty) $(empty) + +# ============================================================================== +# CONFIGURATION DEFAULTS +# ============================================================================== +PUSH := $(or $(PUSH),true) +DEBUG := $(or $(DEBUG),false) +NO_CACHE := $(or $(NO_CACHE),false) +DRY_RUN := $(or $(DRY_RUN),false) +ARCH := $(or $(ARCH),amd64) + + +USER_DATA := $(or $(USER_DATA),user-data) + +# Common Configuration expressions for building targets +PUSH_ARGS = $(if $(filter true,$(PUSH)),--set *.output=type=image$(comma)push=$(PUSH),) +DRY_RUN_ARGS = $(if $(filter true,$(DRY_RUN)),--print,) +DOCKER_BUILD_OUT = $(if $(filter true,$(DEBUG)),--progress=plain --debug,) +DOCKER_NO_CACHE = $(if $(filter true,$(NO_CACHE)),--no-cache,) + +# ============================================================================== +# COMPUTED VARIABLES +# ============================================================================== +PROVIDER_TARGET_TYPE = $(if $(filter true,$(IS_UKI)),uki-provider-image,provider-image) +ISO_TARGET_TYPE = $(if $(filter true,$(IS_UKI)),build-uki-iso,build-iso) + +ALL_K8S_VERSIONS = $(if $(strip $(K8S_VERSION)),\ + $(subst $(comma),$(space),$(K8S_VERSION)),\ + $(shell jq -r --arg key "$(K8S_DISTRIBUTION)" 'if .[$$key] then .[$$key][] else empty end' k8s_version.json)) + +# Check for content directories, cluster config, and edge config +HAS_CONTENT := $(shell ls -d content-* 2>/dev/null | head -1) +HAS_CLUSTERCONFIG := $(if $(CLUSTERCONFIG),$(shell test -f "$(CLUSTERCONFIG)" && echo yes),) +HAS_EDGE_CONFIG := $(if $(EDGE_CUSTOM_CONFIG),$(shell test -f "$(EDGE_CUSTOM_CONFIG)" && echo yes),) +HAS_USER_DATA := $(shell test -f "$(USER_DATA)" && echo yes) + +# ============================================================================== +# CORE BUILD TARGET +# ============================================================================== +build: + $(if $(BAKE_ENV),env $(BAKE_ENV)) \ + docker buildx bake $(BAKE_FILES) $(DRY_RUN_ARGS) $(DOCKER_BUILD_OUT) $(DOCKER_NO_CACHE) $(TARGET) $(BAKE_ARGS) + +# ============================================================================== +# MAIN BUILD TARGETS +# ============================================================================== +build-all-images: build-provider-images iso + +iso: + $(MAKE) TARGET=$(ISO_TARGET_TYPE) build + +iso-disk-image: + $(MAKE) TARGET=iso-disk-image build BAKE_ARGS="$(PUSH_ARGS)" + +# ============================================================================== +# PROVIDER IMAGE BUILD TARGETS +# ============================================================================== +build-provider-images: .check-provider-prereqs $(addprefix .build-provider-image-,$(strip $(ALL_K8S_VERSIONS))) + @echo "All provider images built successfully" + +.build-provider-image-%: .check-provider-prereqs + @echo "Building provider image for k8s version: $*" + @$(MAKE) TARGET=$(PROVIDER_TARGET_TYPE) \ + BAKE_ENV="K8S_VERSION=$*" \ + BAKE_ARGS="--set *.args.K8S_VERSION=$* $(PUSH_ARGS)" \ + build + +.check-provider-prereqs: + @$(if $(strip $(K8S_DISTRIBUTION)),,\ + $(error K8S_DISTRIBUTION is not set. Please set K8S_DISTRIBUTION to kubeadm, kubeadm-fips, k3s, nodeadm, rke2 or canonical)) + @$(if $(strip $(ALL_K8S_VERSIONS)),,\ + $(error No versions found for K8S_DISTRIBUTION=$(K8S_DISTRIBUTION))) + + +# ------------------------------------------------------------------------------ +# Cloud Image +# ------------------------------------------------------------------------------ +# Build AWS AMI from cloud image +aws-cloud-image: cloud-image + @echo "Creating AWS AMI from cloud image" + $(MAKE) TARGET=aws-cloud-image build BAKE_ARGS="--set *.contexts.raw-image=$(CLOUD_IMAGE_DIR)" + +cloud-image: iso-image-cloud cloud-image-tools content-partition-cloud + $(MAKE) kairos-raw-image OUTPUT_DIR=$(CLOUD_IMAGE_DIR) CONTAINER_IMAGE=$(ISO_IMAGE_CLOUD) + @ls -lh $(CLOUD_IMAGE_DIR)/* 2>/dev/null || true + +iso-image-cloud: + $(MAKE) TARGET=iso-image-cloud build + +content-partition-cloud: + @if [ -n "$(HAS_CONTENT)" ] || [ -n "$(HAS_CLUSTERCONFIG)" ] || [ -n "$(HAS_EDGE_CONFIG)" ]; then \ + echo "Adding content partition to cloud image"; \ + RAW_FILE=$$(ls $(CLOUD_IMAGE_DIR)/*.raw 2>/dev/null || ls $(CLOUD_IMAGE_DIR)/*.img 2>/dev/null | head -1); \ + if [ -z "$$RAW_FILE" ]; then \ + echo "Error: No raw image found in $(CLOUD_IMAGE_DIR)"; \ + exit 1; \ + fi; \ + echo "Adding content partition to: $$RAW_FILE"; \ + docker run --rm --privileged --net host \ + -v /dev:/dev \ + -v $(CLOUD_IMAGE_DIR):/workdir \ + $(foreach dir,$(wildcard content-*),-v $(CURDIR)/$(dir):/workdir/$(dir):ro) \ + $(if $(HAS_CLUSTERCONFIG),-v $(CURDIR)/$(CLUSTERCONFIG):/workdir/spc.tgz:ro,) \ + $(if $(HAS_EDGE_CONFIG),-v $(CURDIR)/$(EDGE_CUSTOM_CONFIG):/workdir/edge_custom_config.yaml:ro,) \ + -e CLUSTERCONFIG=$(if $(HAS_CLUSTERCONFIG),/workdir/spc.tgz,) \ + -e EDGE_CUSTOM_CONFIG=$(if $(HAS_EDGE_CONFIG),/workdir/edge_custom_config.yaml,) \ + $(CLOUD_IMAGE_TOOLS) \ + -c "/scripts/add-content-partition.sh /workdir/$$(basename $$RAW_FILE)"; \ + else \ + echo "Skipped adding content partition (no content files)"; \ + fi + +# ------------------------------------------------------------------------------ +# MAAS Image +# ------------------------------------------------------------------------------ +maas-image: raw-disk-maas build-maas-composite + @echo "MAAS image build complete" + @ls -lh $(MAAS_IMAGE_DIR)/* 2>/dev/null || true + +# raw-disk-maas: iso-image-maas cloud-image-tools +raw-disk-maas: + @echo "Creating base raw disk image for MAAS" + $(MAKE) kairos-raw-image OUTPUT_DIR=$(MAAS_IMAGE_DIR) CONTAINER_IMAGE=$(ISO_IMAGE_MAAS) + +# Build MAAS composite image (adds Ubuntu rootfs + content partition) +build-maas-composite: cloud-image-tools + @RAW_FILE=$$(ls $(MAAS_IMAGE_DIR)/*.raw 2>/dev/null || ls $(MAAS_IMAGE_DIR)/*.img 2>/dev/null | head -1); \ + if [ -z "$$RAW_FILE" ]; then \ + echo "Error: No raw image found in $(MAAS_IMAGE_DIR)"; \ + exit 1; \ + fi; \ + echo "Building MAAS composite from: $$RAW_FILE"; \ + docker run --rm --privileged --net host \ + -v /dev:/dev \ + -v $(MAAS_IMAGE_DIR):/workdir \ + $(foreach dir,$(wildcard content-*),-v $(CURDIR)/$(dir):/input/$(dir):ro) \ + $(if $(HAS_CLUSTERCONFIG),-v $(CURDIR)/$(CLUSTERCONFIG):/input/spc.tgz:ro,) \ + $(if $(HAS_EDGE_CONFIG),-v $(CURDIR)/$(EDGE_CUSTOM_CONFIG):/input/edge_custom_config.yaml:ro,) \ + -e CURTIN_HOOKS_SCRIPT=/scripts/curtin-hooks \ + -e CONTENT_BASE_DIR=/input \ + -e CLUSTERCONFIG=$(if $(HAS_CLUSTERCONFIG),/input/spc.tgz,) \ + -e EDGE_CUSTOM_CONFIG=$(if $(HAS_EDGE_CONFIG),/input/edge_custom_config.yaml,) \ + -e MAAS_IMAGE_NAME=$(MAAS_IMAGE_NAME) \ + $(CLOUD_IMAGE_TOOLS) \ + -c "/scripts/build-kairos-maas.sh /workdir/$$(basename $$RAW_FILE) $(MAAS_IMAGE_NAME)" + +iso-image-maas: + $(MAKE) TARGET=iso-image-maas build + +# ============================================================================== +# INTERNAL TARGETs (for MAAS and Cloud images) +# ============================================================================== +kairos-raw-image: validate-user-data + @mkdir -p $(OUTPUT_DIR) + docker run --rm --net host --privileged \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v $(OUTPUT_DIR):/output \ + $(if $(HAS_USER_DATA),-v $(CURDIR)/$(USER_DATA):/config.yaml:ro,) \ + $(AURORABOOT_IMAGE) \ + $(if $(filter true,$(DEBUG)),--debug,) \ + --set "disable_http_server=true" \ + --set "disable_netboot=true" \ + --set "disk.efi=true" \ + --set "arch=$(ARCH)" \ + --set "container_image=$(CONTAINER_IMAGE)" \ + --set "state_dir=/output" \ + $(if $(HAS_USER_DATA),--cloud-config /config.yaml,--set "no_default_cloud_config=true") + +# ============================================================================== +# UTILITY TARGETS +# ============================================================================== +uki-genkey: + $(MAKE) TARGET=uki-genkey build + ./keys.sh secure-boot/ + +secure-boot-dirs: + mkdir -p secure-boot/enrollment secure-boot/exported-keys secure-boot/private-keys secure-boot/public-keys + find secure-boot -type d -exec chmod 0700 {} \; + find secure-boot -type f -exec chmod 0600 {} \; + echo "Created secure-boot directory structure" + +validate-user-data: + $(MAKE) TARGET=validate-user-data build + +internal-slink: + $(MAKE) TARGET=internal-slink build + +iso-efi-size-check: + $(MAKE) TARGET=iso-efi-size-check build + +alpine-all: + $(MAKE) TARGET=alpine-all BAKE_ARGS="$(PUSH_ARGS)" build + +cloud-image-tools: + $(MAKE) TARGET=cloud-image-tools BAKE_ARGS="$(PUSH_ARGS)" build + +# ============================================================================== +# CLEAN TARGETS +# ============================================================================== +clean-all: clean clean-keys + +clean: + rm -rf build + +clean-cloud-image: + rm -rf $(CLOUD_IMAGE_DIR) $(MAAS_IMAGE_DIR) + +clean-keys: + rm -rf secure-boot + +# ============================================================================== +# Colors for help output +BOLD := $(shell printf '\033[1m') +CYAN := $(shell printf '\033[36m') +GREEN := $(shell printf '\033[32m') +YELLOW := $(shell printf '\033[33m') +RESET := $(shell printf '\033[0m') + +help: + @echo "" + @echo "$(BOLD)CanvOS Build System$(RESET)" + @echo "" + @echo "$(CYAN)Build Targets:$(RESET)" + @echo " $(GREEN)build-all-images$(RESET) Build all provider images and ISO" + @echo " $(GREEN)iso$(RESET) Build ISO installer" + @echo " $(GREEN)iso-disk-image$(RESET) Build ISO disk image and push to registry" + @echo " $(GREEN)build-provider-images$(RESET) Build provider images for configured K8S versions" + @echo "" + @echo "$(CYAN)Cloud Image Targets:$(RESET) $(YELLOW)(require privileged Docker)$(RESET)" + @echo " $(GREEN)cloud-image$(RESET) Build raw cloud disk image with content partition" + @echo " $(GREEN)aws-cloud-image$(RESET) Build AWS AMI from cloud image" + @echo " $(GREEN)raw-image$(RESET) Alias for cloud-image" + @echo "" + @echo "$(CYAN)MAAS Image Targets:$(RESET) $(YELLOW)(require privileged Docker)$(RESET)" + @echo " $(GREEN)maas-image$(RESET) Build MAAS raw disk image with content partition" + @echo "" + @echo "$(CYAN)Utility Targets:$(RESET)" + @echo " $(GREEN)uki-genkey$(RESET) Generate UKI secure boot keys" + @echo " $(GREEN)secure-boot-dirs$(RESET) Create secure-boot directory structure" + @echo " $(GREEN)validate-user-data$(RESET) Validate user-data configuration" + @echo "" + @echo "$(CYAN)Clean Targets:$(RESET)" + @echo " $(GREEN)clean$(RESET) Remove build directory" + @echo " $(GREEN)clean-all$(RESET) Remove build directory and secure boot keys" + @echo " $(GREEN)clean-cloud-image$(RESET) Remove cloud/MAAS image artifacts" + @echo " $(GREEN)clean-keys$(RESET) Remove secure boot keys" + @echo "" + @echo "$(CYAN)Configuration:$(RESET) (set in .arg file or as make variables)" + @echo " PUSH=true|false Push images to registry (default: true)" + @echo " DEBUG=true|false Enable debug output (default: false)" + @echo " NO_CACHE=true|false Disable build cache (default: false)" + @echo " DRY_RUN=true|false Print commands without executing" + @echo "" + @echo "$(CYAN)Cloud/MAAS Configuration:$(RESET)" + @echo " USER_DATA= Cloud config file (default: user-data)" + @echo " CLUSTERCONFIG= Cluster config archive (spc.tgz)" + @echo " EDGE_CUSTOM_CONFIG= Edge custom config yaml" + @echo " content-*/ Content bundle directories (auto-detected)" + @echo "" + @echo "$(YELLOW)For base image builds:$(RESET) make -f Makefile.base-images help" + @echo "" \ No newline at end of file diff --git a/cloud-images/config/user-data.yaml b/cloud-images/config/user-data.yaml new file mode 100644 index 00000000..136b6fcb --- /dev/null +++ b/cloud-images/config/user-data.yaml @@ -0,0 +1,21 @@ +#cloud-config +install: + device: /dev/sda + reboot: true + poweroff: false +stylus: + debug: true + site: + paletteEndpoint: xxxx.spectrocloud.com + autoRegister: true + edgeHostToken: xxxxxxxxxxx + insecureSkipVerify: false + +stages: + initramfs: + - name: "Set user and password" + users: + kairos: + passwd: "kairos" + groups: + - "admin" \ No newline at end of file diff --git a/cloud-images/workaround/custom-post-reset.yaml b/cloud-images/workaround/custom-post-reset.yaml index d7032043..fc98f340 100644 --- a/cloud-images/workaround/custom-post-reset.yaml +++ b/cloud-images/workaround/custom-post-reset.yaml @@ -56,4 +56,4 @@ stages: - if: '[ -f "/run/cos/active_mode" ]' name: "extract content from content partition" commands: - - bash /opt/spectrocloud/scripts/cloud-content.sh \ No newline at end of file + - bash /opt/spectrocloud/scripts/cloud-content.sh diff --git a/cloudconfigs/build-kairos-maas.sh b/cloudconfigs/build-kairos-maas.sh index b785e5a2..524fdecb 100644 --- a/cloudconfigs/build-kairos-maas.sh +++ b/cloudconfigs/build-kairos-maas.sh @@ -46,12 +46,14 @@ UBUNTU_ROOT_SIZE="3G" # The size of the content partition (for Stylus content files) # Default to 2G, but will be calculated based on actual content size if content files exist CONTENT_SIZE="2G" -# Path to the curtin hooks script (relative to the original directory) -CURTIN_HOOKS_SCRIPT="$ORIG_DIR/curtin-hooks" + +CURTIN_HOOKS_SCRIPT="${CURTIN_HOOKS_SCRIPT:-$ORIG_DIR/curtin-hooks}" + +CONTENT_BASE_DIR="${CONTENT_BASE_DIR:-$ORIG_DIR}" # Path to content directory (if content files are provided) # Look for content-* directories (e.g., content-3a456a58) CONTENT_DIR="" -for dir in "$ORIG_DIR"/content-*; do +for dir in "$CONTENT_BASE_DIR"/content-*; do if [ -d "$dir" ]; then CONTENT_DIR="$dir" echo "Found content directory: $CONTENT_DIR" @@ -59,8 +61,8 @@ for dir in "$ORIG_DIR"/content-*; do fi done # Fallback to plain content directory if no content-* found -if [ -z "$CONTENT_DIR" ] && [ -d "$ORIG_DIR/content" ]; then - CONTENT_DIR="$ORIG_DIR/content" +if [ -z "$CONTENT_DIR" ] && [ -d "$CONTENT_BASE_DIR/content" ]; then + CONTENT_DIR="$CONTENT_BASE_DIR/content" echo "Found content directory: $CONTENT_DIR" fi @@ -579,8 +581,16 @@ if [ -f "$INPUT_IMG" ]; then rm -f "$INPUT_IMG" fi +# Generate SHA256 checksum +echo "Generating SHA256 checksum..." +sha256sum "$COMPRESSED_IMG" > "${COMPRESSED_IMG}.sha256" +CHECKSUM=$(cat "${COMPRESSED_IMG}.sha256" | cut -d' ' -f1) +echo "SHA256: $CHECKSUM" + echo "" echo "✅ Composite image created and compressed successfully: $COMPRESSED_IMG" +echo " Size: $COMP_SIZE" +echo " Checksum: ${COMPRESSED_IMG}.sha256" echo "You can now upload this compressed raw image to MAAS (MAAS will automatically decompress it)." # Exit with success code diff --git a/docker-bake-common.hcl b/docker-bake-common.hcl new file mode 100644 index 00000000..60adb7c8 --- /dev/null +++ b/docker-bake-common.hcl @@ -0,0 +1,65 @@ +# ============================================================================= +# Docker Bake Common Variables +# ============================================================================= +# This file contains shared variables used across all docker-bake configurations. +# Include this file with your specific bake file using multiple -f flags: +# +# Usage: +# docker buildx bake -f docker-bake-common.hcl -f docker-bake.hcl +# docker buildx bake -f docker-bake-common.hcl -f docker-bake-base-images.hcl +# +# Variables defined in later files (-f order) will override earlier ones. +# ============================================================================= + +# ----------------------------------------------------------------------------- +# Core Version Variables +# ----------------------------------------------------------------------------- + +variable "KAIROS_VERSION" { + default = "v3.5.9" + description = "Kairos framework version" +} + +variable "KAIROS_INIT_IMAGE" { + default = "quay.io/kairos/kairos-init:v0.5.28" + description = "Kairos init image for kairosification" +} + +# ----------------------------------------------------------------------------- +# Architecture and Platform +# ----------------------------------------------------------------------------- + +variable "ARCH" { + default = "amd64" + description = "Target architecture (amd64, arm64)" +} + +# ----------------------------------------------------------------------------- +# Image Registry and Tagging +# ----------------------------------------------------------------------------- + +variable "IMAGE_REGISTRY" { + default = "" + description = "Container registry to push images (e.g., ghcr.io/myorg, us-docker.pkg.dev/project)" +} + +variable "IMAGE_TAG" { + default = "latest" + description = "Tag for built images (can be overridden by specific bake files)" +} + +variable "PUSH_IMAGES" { + type = bool + default = false + description = "Whether to push images to registry" +} + +# ----------------------------------------------------------------------------- +# FIPS Configuration +# ----------------------------------------------------------------------------- + +variable "FIPS_ENABLED" { + type = bool + default = false + description = "Enable FIPS-compliant builds" +} diff --git a/docker-bake-kairosify.hcl b/docker-bake-kairosify.hcl new file mode 100644 index 00000000..7fe0b9d6 --- /dev/null +++ b/docker-bake-kairosify.hcl @@ -0,0 +1,41 @@ +variable "KAIROS_INIT_IMAGE" { + default = "quay.io/kairos/kairos-init:v0.5.28" +} + +variable "ARCH" { + default = "amd64" +} + +variable "BASE_OS_IMAGE" { + default = "ubuntu:20.04" +} + +variable "MODEL" { + default = "generic" +} + +variable "KAIROS_VERSION" { + default = "v3.5.9" +} + +variable "TRUSTED_BOOT" { + type = bool + default = false +} + +variable "TAG" { + default = "kairosify:latest" +} + +target "kairosify" { + dockerfile = "dockerfiles/kairosify/Dockerfile.kairosify" + platforms = ["linux/${ARCH}"] + args = { + BASE_OS_IMAGE = BASE_OS_IMAGE + KAIROS_INIT_IMAGE = KAIROS_INIT_IMAGE + KAIROS_VERSION = KAIROS_VERSION + TRUSTED_BOOT = TRUSTED_BOOT + MODEL = MODEL + } + tags = [TAG] +} \ No newline at end of file diff --git a/docker-bake.hcl b/docker-bake.hcl new file mode 100644 index 00000000..7af3a1ba --- /dev/null +++ b/docker-bake.hcl @@ -0,0 +1,722 @@ +# Note: FIPS_ENABLED and ARCH are defined in docker-bake-common.hcl +# Include with: docker buildx bake -f docker-bake-common.hcl -f docker-bake.hcl + +variable "SPECTRO_PUB_REPO" { + default = FIPS_ENABLED ? "us-docker.pkg.dev/palette-images-fips" : "us-docker.pkg.dev/palette-images" +} + +variable "SPECTRO_THIRD_PARTY_IMAGE" { + default = "us-east1-docker.pkg.dev/spectro-images/third-party/spectro-third-party:4.6" +} + +variable "ALPINE_TAG" { + default = "3.22" +} + +variable "ALPINE_IMG" { + default = "${SPECTRO_PUB_REPO}/edge/canvos/alpine:${ALPINE_TAG}" +} + +variable "SPECTRO_LUET_REPO" { + default = "us-docker.pkg.dev/palette-images/edge" +} + +variable "KAIROS_BASE_IMAGE_URL" { + default = "${SPECTRO_PUB_REPO}/edge" +} + +variable AURORABOOT_VERSION { + default = "v0.19.0" +} + +variable AURORABOOT_IMAGE { + default = "quay.io/kairos/auroraboot:${AURORABOOT_VERSION}" +} + +variable "PE_VERSION" { + default = "v4.8.1" +} + +# Note: KAIROS_VERSION is defined in docker-bake-common.hcl + +variable "K3S_FLAVOR_TAG" { + default = "k3s1" +} + +variable "RKE2_FLAVOR_TAG" { + default = "rke2r1" +} + +variable "K3S_PROVIDER_VERSION" { + default = "v4.7.1" +} + +variable "KUBEADM_PROVIDER_VERSION" { + default = "v4.7.3" +} + +variable "RKE2_PROVIDER_VERSION" { + default = "v4.7.1" +} + +variable "NODEADM_PROVIDER_VERSION" { + default = "v4.6.0" +} + +variable "CANONICAL_PROVIDER_VERSION" { + default = "v1.2.2" +} + + +variable OS_DISTRIBUTION {} +variable OS_VERSION {} + +variable K8S_DISTRIBUTION {} +variable K8S_VERSION {} + +variable IMAGE_REGISTRY {} +variable IMAGE_REPO { + default = OS_DISTRIBUTION +} + +variable "ISO_NAME" { + default = "installer" +} + +variable "CLUSTERCONFIG" {} + +variable EDGE_CUSTOM_CONFIG { + default = ".edge-custom-config.yaml" +} + +variable "CUSTOM_TAG" {} + +variable "DISABLE_SELINUX" { + type = bool + default = true +} + +variable "CIS_HARDENING" { + type = bool + default = false +} + +variable UBUNTU_PRO_KEY {} + +variable DRBD_VERSION { + default = "9.2.13" +} + +variable HTTP_PROXY {} +variable HTTPS_PROXY {} +variable NO_PROXY {} +variable PROXY_CERT_PATH {} + +variable http_proxy { + default = HTTP_PROXY +} +variable https_proxy { + default = HTTPS_PROXY +} +variable no_proxy { + default = NO_PROXY +} + +variable "UPDATE_KERNEL" { + type = bool + default = false +} + +variable ETCD_VERSION { + default = "v3.5.13" +} + +variable KINE_VERSION { + default = "0.11.4" +} + +variable "TWO_NODE" { + type = bool + default = false +} + +variable "IS_MAAS" { + type = bool + default = false +} + +variable "IS_UKI" { + type = bool + default = false + validation{ + condition = ((IS_UKI && (OS_VERSION == "24" || OS_VERSION == "24.04")) || IS_UKI == false) + error_message = "OS_VERSION must be 24.04 for UKI" + } +} + +variable "INCLUDE_MS_SECUREBOOT_KEYS" { + type = bool + default = true +} + +variable "AUTO_ENROLL_SECUREBOOT_KEYS" { + type = bool + default = false +} + +variable "UKI_BRING_YOUR_OWN_KEYS" { + type = bool + default = false +} + +variable "CMDLINE" { + default = "stylus.registration" +} + +variable "BRANDING" { + default = "Palette eXtended Kubernetes Edge" +} + +variable "FORCE_INTERACTIVE_INSTALL"{ + type = bool + default = false +} + +variable "MY_ORG" { + default = "ACME Corp" +} + +variable "EXPIRATION_IN_DAYS" { + default = 5475 +} + +variable "EFI_MAX_SIZE" { + default = "2048" +} + +variable "EFI_IMG_SIZE" { + default = "2200" +} + +variable "GOLANG_VERSION" { + default = "1.23" +} + +variable "IS_CLOUD_IMAGE" { + type = bool + default = false +} + + +variable "DEBUG" { + type = bool + default = false +} + +variable "BASE_IMAGE" { + default = "" +} + +variable "REGION" {} + +variable "S3_BUCKET" {} + +variable "S3_KEY" {} + +# Alpine base image provided by platform team +variable "ALPINE_BASE_IMAGE" { + default = FIPS_ENABLED ? "us-docker.pkg.dev/palette-images-fips/third-party/alpine:${ALPINE_TAG}-fips" : "us-docker.pkg.dev/palette-images/third-party/alpine:${ALPINE_TAG}" +} + +# Secrets for secure boot private keys - used by trustedboot-image and build-uki-iso +variable "SECURE_BOOT_SECRETS" { + default = [ + "id=db_key,src=secure-boot/private-keys/db.key", + "id=db_pem,src=secure-boot/public-keys/db.pem", + "id=kek_key,src=secure-boot/private-keys/KEK.key", + "id=kek_pem,src=secure-boot/public-keys/KEK.pem", + "id=pk_key,src=secure-boot/private-keys/PK.key", + "id=pk_pem,src=secure-boot/public-keys/PK.pem", + "id=tpm2_pcr_private,src=secure-boot/private-keys/tpm2-pcr-private.pem" + ] +} + +variable "IS_JETSON" { + type = bool + default = length(regexall("nvidia-jetson-agx-orin", BASE_IMAGE)) > 0 +} + +variable "STYLUS_BASE" { + default = "${SPECTRO_PUB_REPO}/edge/stylus-framework-linux-${ARCH}:${PE_VERSION}" +} + +variable "STYLUS_PACKAGE_BASE" { + default = "${SPECTRO_PUB_REPO}/edge/stylus-linux-${ARCH}:${PE_VERSION}" +} + +variable "BIN_TYPE" { + default = FIPS_ENABLED ? "vertex" : "palette" +} + +variable "CLI_IMAGE" { + default = FIPS_ENABLED ? "${SPECTRO_PUB_REPO}/edge/palette-edge-cli-fips-${ARCH}:${PE_VERSION}" : "${SPECTRO_PUB_REPO}/edge/palette-edge-cli-${ARCH}:${PE_VERSION}" +} + +variable "IMAGE_TAG" { + default = CUSTOM_TAG != "" ? "${PE_VERSION}-${CUSTOM_TAG}" : PE_VERSION +} + +variable "IMAGE_PATH" { + default = "${IMAGE_REGISTRY}/${IMAGE_REPO}:${K8S_DISTRIBUTION}-${K8S_VERSION}-${IMAGE_TAG}" +} + +function "get_ubuntu_image" { + params = [fips_enabled, spectro_pub_repo] + result = fips_enabled ? "${spectro_pub_repo}/third-party/ubuntu-fips:22.04" : "${spectro_pub_repo}/third-party/ubuntu:22.04" +} + +function "normalized_os_version" { + params = [os_distribution, os_version] + // For Ubuntu: if version is like 20, 22, 24, append .04 + result = os_distribution != "ubuntu" ? os_version : (length(regexall("\\.", os_version)) > 0 ? os_version : "${os_version}.04") +} + +function "get_base_image" { + params = [base_image, os_distribution, os_version, is_uki] + result = base_image != "" ? base_image : ( + os_distribution == "ubuntu" ? "${KAIROS_BASE_IMAGE_URL}/kairos-${os_distribution}:${os_version}-core-${ARCH}-generic-${KAIROS_VERSION}${is_uki ? "-uki" : ""}" : + + os_distribution == "opensuse-leap" && os_version == "15.6" ? + "${KAIROS_BASE_IMAGE_URL}/kairos-opensuse:leap-${os_version}-core-${ARCH}-generic-${KAIROS_VERSION}" : + + "" + ) +} + + +target "base" { + dockerfile = "Dockerfile" + args = { + BASE = get_base_image(BASE_IMAGE, OS_DISTRIBUTION, + normalized_os_version(OS_DISTRIBUTION, OS_VERSION), + IS_UKI) + OS_DISTRIBUTION = OS_DISTRIBUTION + PROXY_CERT_PATH = PROXY_CERT_PATH + HTTP_PROXY = HTTP_PROXY + HTTPS_PROXY = HTTPS_PROXY + NO_PROXY = NO_PROXY + DRBD_VERSION = DRBD_VERSION + } +} + +target "base-image" { + dockerfile = "dockerfiles/Dockerfile.base-image" + context = "." + contexts = { + base = "target:base" + } + args = { + OS_DISTRIBUTION = OS_DISTRIBUTION + OS_VERSION = normalized_os_version(OS_DISTRIBUTION, OS_VERSION) + IS_JETSON = IS_JETSON + IS_UKI = IS_UKI + UPDATE_KERNEL = UPDATE_KERNEL + CIS_HARDENING = CIS_HARDENING + KAIROS_VERSION = KAIROS_VERSION + DISABLE_SELINUX = DISABLE_SELINUX + ARCH = ARCH + FIPS_ENABLED = FIPS_ENABLED + IS_MAAS = IS_MAAS + } + secret = ["id=ubuntu_pro_key,env=UBUNTU_PRO_KEY"] +} + +variable "provider_prefix" { + default = "docker-image://${SPECTRO_PUB_REPO}/edge/kairos-io/provider" +} + +function "get_provider_base" { + params = [k8s_distribution, kubeadm_version, k3s_version, rke2_version, nodeadm_version, canonical_version] + result = ( + k8s_distribution == "kubeadm" ? "${provider_prefix}-kubeadm:${kubeadm_version}" : + k8s_distribution == "kubeadm-fips" ? "${provider_prefix}-kubeadm:${kubeadm_version}" : + k8s_distribution == "k3s" ? "${provider_prefix}-k3s:${k3s_version}" : + k8s_distribution == "rke2" ? "${provider_prefix}-rke2:${rke2_version}" : + k8s_distribution == "nodeadm" ? "${provider_prefix}-nodeadm:${nodeadm_version}" : + k8s_distribution == "canonical" ? "${provider_prefix}-canonical:${canonical_version}" : + "" + ) +} + +target "install-k8s" { + dockerfile = "dockerfiles/Dockerfile.install-k8s" + context = "." + target = "install-k8s" + platforms = ["linux/${ARCH}"] + contexts = { + alpine-certs = "target:alpine-certs" + third-party-luet = "target:third-party-luet" + } + args = { + ARCH = ARCH + K8S_DISTRIBUTION = K8S_DISTRIBUTION + K8S_VERSION = K8S_VERSION + K3S_FLAVOR_TAG = K3S_FLAVOR_TAG + RKE2_FLAVOR_TAG = RKE2_FLAVOR_TAG + LUET_REPO = ARCH == "arm64" ? "luet-repo-arm" : "luet-repo-amd" + SPECTRO_LUET_REPO = SPECTRO_LUET_REPO + } +} + +target "provider-image" { + dockerfile = "dockerfiles/Dockerfile.provider-image" + context = "." + target = "provider-image" + platforms = ["linux/${ARCH}"] + contexts = { + base-image = "target:base-image" + kairos-provider-image = get_provider_base( + K8S_DISTRIBUTION, + KUBEADM_PROVIDER_VERSION, + K3S_PROVIDER_VERSION, + RKE2_PROVIDER_VERSION, + NODEADM_PROVIDER_VERSION, + CANONICAL_PROVIDER_VERSION, + ) + stylus-image = "docker-image://${STYLUS_BASE}" + install-k8s = "target:install-k8s" + third-party-luet = "target:third-party-luet" + third-party-etcdctl = "target:third-party-etcdctl" + internal-slink = "target:internal-slink" + } + args = { + ARCH = ARCH + IMAGE_REPO = IMAGE_REPO + K8S_DISTRIBUTION = K8S_DISTRIBUTION + K8S_VERSION = K8S_VERSION + K3S_FLAVOR_TAG = K3S_FLAVOR_TAG + RKE2_FLAVOR_TAG = RKE2_FLAVOR_TAG + OS_DISTRIBUTION = OS_DISTRIBUTION + EDGE_CUSTOM_CONFIG = EDGE_CUSTOM_CONFIG + TWO_NODE = TWO_NODE + KINE_VERSION = KINE_VERSION + IMAGE_PATH = IMAGE_PATH + IS_UKI = IS_UKI + UPDATE_KERNEL = UPDATE_KERNEL + } + tags = [IMAGE_PATH] + output = ["type=image,push=true"] +} + +target "uki-provider-image" { + dockerfile = "dockerfiles/Dockerfile.uki-provider-image" + context = "." + platforms = ["linux/${ARCH}"] + contexts = { + third-party-luet = "target:third-party-luet" + install-k8s = "target:install-k8s" + trustedboot-image = "target:trustedboot-image" + stylus-image = "docker-image://${STYLUS_BASE}" + } + args = { + BASE_IMAGE = get_base_image(BASE_IMAGE, OS_DISTRIBUTION, + normalized_os_version(OS_DISTRIBUTION, OS_VERSION), + IS_UKI) + UBUNTU_IMAGE = get_ubuntu_image(FIPS_ENABLED, SPECTRO_PUB_REPO) + EDGE_CUSTOM_CONFIG = EDGE_CUSTOM_CONFIG + IMAGE_PATH = IMAGE_PATH + } + tags = [IMAGE_PATH] + output = ["type=image,push=true"] +} + +target "trustedboot-image" { + dockerfile = "dockerfiles/Dockerfile.trustedboot-image" + platforms = ["linux/${ARCH}"] + target = "output" + context = "." + contexts = { + provider-image = "target:provider-image" + } + args = { + AURORABOOT_IMAGE = AURORABOOT_IMAGE + DEBUG = DEBUG + } + secret = SECURE_BOOT_SECRETS + output = ["type=local,dest=./trusted-boot/"] +} + +target "validate-user-data" { + dockerfile = "dockerfiles/Dockerfile.validate-ud" + target = "validate-user-data" + args = { + CLI_IMAGE = CLI_IMAGE + ARCH = ARCH + } + platforms = ["linux/${ARCH}"] +} + + +target "iso-image" { + dockerfile = "dockerfiles/Dockerfile.iso-image" + context = "." + target = "iso-image" + platforms = ["linux/${ARCH}"] + contexts = { + base-image = "target:base-image" + stylus-image = "docker-image://${STYLUS_BASE}" + } + args = { + ARCH = ARCH + IS_UKI = IS_UKI + IS_CLOUD_IMAGE = IS_CLOUD_IMAGE + IS_MAAS = IS_MAAS + } + tags = ["palette-installer-image:${IMAGE_TAG}"] + output = ["type=docker"] +} + +target "build-iso" { + dockerfile = "dockerfiles/Dockerfile.build-iso" + platforms = ["linux/${ARCH}"] + target = "output" + contexts = { + validate-user-data = "target:validate-user-data" + iso-image = "target:iso-image" + } + args = { + ISO_NAME = ISO_NAME + CLUSTERCONFIG = CLUSTERCONFIG + EDGE_CUSTOM_CONFIG = EDGE_CUSTOM_CONFIG + AURORABOOT_IMAGE = AURORABOOT_IMAGE + FORCE_INTERACTIVE_INSTALL = FORCE_INTERACTIVE_INSTALL + } + output = ["type=local,dest=./build"] +} + +target "build-uki-iso" { + dockerfile = "dockerfiles/Dockerfile.build-uki-iso" + target = "output" + context = "." + platforms = ["linux/${ARCH}"] + contexts = { + validate-user-data = "target:validate-user-data" + stylus-image-pack = "target:stylus-image-pack" + third-party-luet = "target:third-party-luet" + iso-image = "target:iso-image" + } + args = { + AURORABOOT_IMAGE = AURORABOOT_IMAGE + ARCH = ARCH + ISO_NAME = ISO_NAME + CLUSTERCONFIG = CLUSTERCONFIG + EDGE_CUSTOM_CONFIG = EDGE_CUSTOM_CONFIG + AUTO_ENROLL_SECUREBOOT_KEYS = AUTO_ENROLL_SECUREBOOT_KEYS + DEBUG = DEBUG + CMDLINE = CMDLINE + BRANDING = BRANDING + } + secret = SECURE_BOOT_SECRETS + output = ["type=local,dest=./build/"] +} + +target "stylus-image-pack" { + dockerfile = "dockerfiles/Dockerfile.stylus-image-pack" + target = "output" + platforms = ["linux/${ARCH}"] + contexts = { + third-party-luet = "target:third-party-luet" + } + args = { + STYLUS_PACKAGE_BASE = STYLUS_PACKAGE_BASE + STYLUS_BASE = STYLUS_BASE + ARCH = ARCH + } + output = ["type=local,dest=./build/"] +} + +target "uki-genkey" { + dockerfile = "dockerfiles/Dockerfile.uki-genkey" + context = "." + target = UKI_BRING_YOUR_OWN_KEYS ? "output-byok" : "output-no-byok" + platforms = ["linux/${ARCH}"] + contexts = UKI_BRING_YOUR_OWN_KEYS ? { + uki-byok = "target:uki-byok" + } : {} + args = { + MY_ORG = MY_ORG + EXPIRATION_IN_DAYS = EXPIRATION_IN_DAYS + INCLUDE_MS_SECUREBOOT_KEYS = INCLUDE_MS_SECUREBOOT_KEYS + AURORABOOT_IMAGE = AURORABOOT_IMAGE + ARCH = ARCH + } + output = ["type=local,dest=./secure-boot/"] +} + +target "uki-byok" { + dockerfile = "dockerfiles/Dockerfile.uki-byok" + context = "." + platforms = ["linux/${ARCH}"] + args = { + UBUNTU_IMAGE = get_ubuntu_image(FIPS_ENABLED, SPECTRO_PUB_REPO) + INCLUDE_MS_SECUREBOOT_KEYS = INCLUDE_MS_SECUREBOOT_KEYS + } +} + +target "internal-slink" { + dockerfile = "dockerfiles/Dockerfile.slink" + context = "." + platforms = ["linux/${ARCH}"] + args = { + SPECTRO_PUB_REPO = SPECTRO_PUB_REPO + GOLANG_VERSION = GOLANG_VERSION + BIN = "slink" + SRC = "cmd/slink/slink.go" + GOOS = "linux" + GOARCH = ARCH + } + output = ["type=local,dest=build"] +} + +target "third-party-luet" { + dockerfile = "dockerfiles/Dockerfile.third-party" + target = "third-party" + contexts = { + alpine-certs = "target:alpine-certs" + } + args = { + SPECTRO_THIRD_PARTY_IMAGE = SPECTRO_THIRD_PARTY_IMAGE + binary = "luet" + BIN_TYPE = BIN_TYPE + ARCH = ARCH + TARGETPLATFORM = "linux/${ARCH}" + } +} + +target "third-party-etcdctl" { + dockerfile = "dockerfiles/Dockerfile.third-party" + target = "third-party" + contexts = { + alpine-certs = "target:alpine-certs" + } + args = { + SPECTRO_THIRD_PARTY_IMAGE = SPECTRO_THIRD_PARTY_IMAGE + binary = "etcdctl" + BIN_TYPE = BIN_TYPE + ARCH = ARCH + TARGETPLATFORM = "linux/${ARCH}" + } +} + +target "iso-disk-image" { + platforms = ["linux/${ARCH}"] + contexts = { + build-iso = IS_UKI ? "target:build-uki-iso" : "target:build-iso" + } + dockerfile-inline = <<-EOF + FROM scratch + COPY --from=build-iso /*.iso /disk/ + EOF + tags = ["${IMAGE_REGISTRY}/${IMAGE_REPO}/${ISO_NAME}:${IMAGE_TAG}"] + output = ["type=image,push=true"] +} + +target "alpine-all" { + dockerfile = "dockerfiles/Dockerfile.alpine" + target = "alpine" + platforms = ["linux/amd64", "linux/arm64"] + args = { + ALPINE_BASE_IMAGE = ALPINE_BASE_IMAGE + } + tags = [ALPINE_IMG] + output = ["type=image,push=true"] +} + +# Alpine with custom certificates from build context - used as base for install-k8s and third-party targets +target "alpine-certs" { + dockerfile = "dockerfiles/Dockerfile.alpine" + context = "." + target = "alpine-certs" + platforms = ["linux/${ARCH}"] + args = { + ALPINE_BASE_IMAGE = ALPINE_BASE_IMAGE + } +} + +target "iso-efi-size-check" { + dockerfile = "dockerfiles/Dockerfile.iso-efi-size-check" + context = "." + target = "output" + platforms = ["linux/amd64"] + args = { + UBUNTU_IMAGE = get_ubuntu_image(FIPS_ENABLED, SPECTRO_PUB_REPO) + EFI_MAX_SIZE = EFI_MAX_SIZE + EFI_IMG_SIZE = EFI_IMG_SIZE + } + output = ["type=local,dest=./build/"] +} + +variable "CLOUD_IMAGE_DIR" { + default = "./build/cloud-image" +} + +# Build installer image and load to Docker daemon +# Used by: make cloud-image (which then runs auroraboot to create raw disk) +target "iso-image-cloud" { + inherits = ["iso-image"] + args = { + IS_CLOUD_IMAGE = true + } + tags = ["palette-installer-image-cloud:latest"] + output = ["type=docker"] +} + +# Build MAAS installer image and load to Docker daemon +# Used by: make maas-image (which then runs auroraboot to create raw disk) +target "iso-image-maas" { + inherits = ["iso-image"] + args = { + IS_MAAS = true + } + tags = ["palette-installer-image-maas:latest"] + output = ["type=docker"] +} + + + + + +target "aws-cloud-image" { + dockerfile = "dockerfiles/Dockerfile.aws-cloud-image" + context = "." + platforms = ["linux/${ARCH}"] + contexts = { + raw-image = CLOUD_IMAGE_DIR + } + args = { + UBUNTU_IMAGE = get_ubuntu_image(FIPS_ENABLED, SPECTRO_PUB_REPO) + ARCH = ARCH + REGION = REGION + S3_BUCKET = S3_BUCKET + S3_KEY = S3_KEY + } + secret = [ + "id=AWS_PROFILE,env=AWS_PROFILE", + "id=AWS_ACCESS_KEY_ID,env=AWS_ACCESS_KEY_ID", + "id=AWS_SECRET_ACCESS_KEY,env=AWS_SECRET_ACCESS_KEY" + ] +} + +// Tools image for cloud/MAAS disk operations (content partition for cloud image, MAAS builder) +target "cloud-image-tools" { + dockerfile = "dockerfiles/Dockerfile.cloud-image-tools" + context = "." + platforms = ["linux/${ARCH}"] + args = { + BASE_IMAGE = ALPINE_BASE_IMAGE + } + tags = [ + "${SPECTRO_PUB_REPO}/edge/canvos/cloud-image-tools:latest" + ] + output = ["type=docker"] +} diff --git a/dockerfiles/Dockerfile.alpine b/dockerfiles/Dockerfile.alpine new file mode 100644 index 00000000..c35ceb7d --- /dev/null +++ b/dockerfiles/Dockerfile.alpine @@ -0,0 +1,22 @@ +ARG ALPINE_BASE_IMAGE + +FROM ${ALPINE_BASE_IMAGE} AS alpine-base + +RUN apk add --no-cache bash curl jq ca-certificates upx + + +FROM alpine-base AS alpine + +# In FIPS mode, this may fail due to OpenSSL FIPS constraints. +RUN update-ca-certificates || true + + +FROM alpine-base AS alpine-certs + +# Use bind mount to conditionally copy certs without adding to image layers +# Note: update-ca-certificates may fail in FIPS mode due to OpenSSL constraints +RUN --mount=type=bind,target=/build-context \ + if [ -d "/build-context/certs" ]; then \ + cp -r /build-context/certs/. /etc/ssl/certs/; \ + fi && \ + update-ca-certificates || true diff --git a/dockerfiles/Dockerfile.aws-cloud-image b/dockerfiles/Dockerfile.aws-cloud-image new file mode 100644 index 00000000..738e9cf5 --- /dev/null +++ b/dockerfiles/Dockerfile.aws-cloud-image @@ -0,0 +1,46 @@ +ARG UBUNTU_IMAGE + +FROM ${UBUNTU_IMAGE} AS builder + +ARG REGION +ARG S3_BUCKET +ARG S3_KEY +ARG ARCH + +# Install dependencies +RUN apt-get update && apt-get install -y unzip ca-certificates curl && \ + rm -rf /var/lib/apt/lists/* + +# Install AWS CLI +RUN AWS_CLI_ARCH=$(case "$ARCH" in \ + amd64) echo "x86_64" ;; \ + arm64) echo "aarch64" ;; \ + esac) && \ + curl --fail -s "https://awscli.amazonaws.com/awscli-exe-linux-${AWS_CLI_ARCH}.zip" -o "awscliv2.zip" && \ + unzip -q awscliv2.zip -d aws_install_temp && \ + ./aws_install_temp/aws/install && \ + rm -rf awscliv2.zip aws_install_temp + +WORKDIR /workdir + +# Copy the cloud image output +COPY --from=raw-image / /workdir/raw-image/ + +# Copy the AMI creation script +COPY cloud-images/scripts/create-raw-to-ami.sh /workdir/create-raw-to-ami.sh + +# Run the script to create AMI from RAW file +RUN --mount=type=secret,id=AWS_PROFILE \ + --mount=type=secret,id=AWS_ACCESS_KEY_ID \ + --mount=type=secret,id=AWS_SECRET_ACCESS_KEY \ + RAW_FILE_PATH=$(ls /workdir/raw-image/*.raw) && \ + echo "RAW_FILE_PATH: $RAW_FILE_PATH" && \ + if [ ! -f "$RAW_FILE_PATH" ]; then \ + echo "Error: RAW file not found." && \ + ls -la /workdir/ && \ + exit 1; \ + else \ + echo "RAW file '$RAW_FILE_PATH' found." && \ + echo "Proceeding with creation of AMI..."; \ + fi && \ + /workdir/create-raw-to-ami.sh "$RAW_FILE_PATH" diff --git a/dockerfiles/Dockerfile.base-image b/dockerfiles/Dockerfile.base-image new file mode 100644 index 00000000..fbd64fc0 --- /dev/null +++ b/dockerfiles/Dockerfile.base-image @@ -0,0 +1,197 @@ +FROM base + +ARG OS_DISTRIBUTION +ARG OS_VERSION + +ARG IS_JETSON=false +ARG IS_UKI=false +ARG UPDATE_KERNEL +ARG CIS_HARDENING +ARG KAIROS_VERSION +ARG DISABLE_SELINUX +ARG ARCH=amd64 +ARG FIPS_ENABLED=false +ARG IS_MAAS=false + +# Copy platform-specific OEM configurations +RUN --mount=type=bind,target=/build-context \ + if [ "$IS_JETSON" = "true" ]; then \ + cp /build-context/cloudconfigs/mount.yaml /system/oem/mount.yaml; \ + fi && \ + if [ "$IS_UKI" = "true" ]; then \ + cp /build-context/cloudconfigs/80_stylus_uki.yaml /system/oem/80_stylus_uki.yaml; \ + fi && \ + if [ "$IS_MAAS" = "true" ]; then \ + cp /build-context/cloudconfigs/80_stylus_maas.yaml /system/oem/80_stylus_maas.yaml; \ + fi + +# Use secret for UBUNTU_PRO_KEY - not cached, not stored in image layers +RUN --mount=type=secret,id=ubuntu_pro_key,target=/run/secrets/ubuntu_pro_key \ + --mount=type=bind,target=/build-context \ + if [ "$OS_DISTRIBUTION" = "ubuntu" ] && [ "$ARCH" = "amd64" ]; then \ + if [ -f /run/secrets/ubuntu_pro_key ] && [ -s /run/secrets/ubuntu_pro_key ]; then \ + sed -i '/^[[:space:]]*$/d' /etc/os-release && \ + apt-get update && \ + apt-get install -y snapd && \ + pro attach "$(cat /run/secrets/ubuntu_pro_key)"; \ + fi && \ + \ + DEBIAN_FRONTEND=noninteractive apt-get update && \ + DEBIAN_FRONTEND=noninteractive UCF_FORCE_CONFFOLD=1 apt-get install --no-install-recommends kbd zstd vim iputils-ping bridge-utils curl tcpdump ethtool rsyslog logrotate -y && \ + \ + APT_UPGRADE_FLAGS="-y" && \ + if [ "$UPDATE_KERNEL" = "false" ]; then \ + if dpkg -l "linux-image-generic-hwe-$OS_VERSION" > /dev/null; then \ + apt-mark hold "linux-image-generic-hwe-$OS_VERSION" "linux-headers-generic-hwe-$OS_VERSION" "linux-generic-hwe-$OS_VERSION"; \ + fi && \ + if dpkg -l linux-image-generic > /dev/null; then \ + apt-mark hold linux-image-generic linux-headers-generic linux-generic; \ + fi; \ + else \ + APT_UPGRADE_FLAGS="-y --with-new-pkgs" && \ + DEBIAN_FRONTEND=noninteractive apt-get update && \ + apt-get install -y linux-image-generic-hwe-$OS_VERSION; \ + fi && \ + \ + if [ "$IS_UKI" = "false" ]; then \ + DEBIAN_FRONTEND=noninteractive apt-get update && \ + apt-get upgrade $APT_UPGRADE_FLAGS && \ + DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ + util-linux \ + parted \ + cloud-guest-utils \ + gawk \ + fdisk \ + gdisk \ + e2fsprogs \ + dosfstools \ + rsync \ + cryptsetup-bin \ + udev && \ + \ + latest_kernel=$(printf '%s\n' /lib/modules/* | xargs -n1 basename | sort -V | tail -1 | awk -F '-' '{print $1"-"$2}') && \ + if [ "$FIPS_ENABLED" = "true" ]; then \ + # When FIPS is enabled, we need to remove any non-FIPS kernel packages (e.g., 5.15 HWE) to avoid conflicts. + # However, some kernel packages may be held (apt-mark hold), which causes `apt-get purge` to fail with: + # "E: Held packages were changed and -y was used without --allow-change-held-packages." + # To fix this, we first unhold all matching non-FIPS and non-latest kernel packages before purging them. + # This ensures a clean, FIPS-only environment without apt resolver errors. + for pkg in $(dpkg -l | awk '/^.i\s+linux-(image|headers|modules)/ {print $2}' \ + | grep -v "$latest_kernel" | grep -v fips); do \ + apt-mark unhold "$pkg" || true; \ + done && \ + apt-get purge -y $(dpkg -l | awk '/^.i\s+linux-(image|headers|modules)/ {print $2}' | grep -v "${latest_kernel}" | grep -v fips); \ + else \ + apt-get purge -y $(dpkg -l | awk '/^ii\s+linux-(image|headers|modules)/ {print $2}' | grep -v "${latest_kernel}"); \ + fi && \ + apt-get autoremove -y && \ + rm -rf /var/lib/apt/lists/* && \ + kernel=$(ls /boot/vmlinuz-* 2>/dev/null | tail -n1) && \ + ln -sf "${kernel#/boot/}" /boot/vmlinuz && \ + \ + if [ "$FIPS_ENABLED" = "false" ]; then \ + kernel=$(printf '%s\n' /lib/modules/* | xargs -n1 basename | sort -V | tail -1) && \ + dracut -f "/boot/initrd-${kernel}" "${kernel}" && \ + ln -sf "initrd-${kernel}" /boot/initrd; \ + fi && \ + kernel=$(printf '%s\n' /lib/modules/* | xargs -n1 basename | sort -V | tail -1) && \ + depmod -a "${kernel}" && \ + \ + if [ ! -f /usr/bin/grub2-editenv ]; then \ + ln -s /usr/sbin/grub-editenv /usr/bin/grub2-editenv; \ + fi; \ + fi && \ + \ + if [ "$CIS_HARDENING" = "true" ]; then \ + cp /build-context/cis-harden/harden.sh /tmp/harden.sh && \ + /tmp/harden.sh && rm /tmp/harden.sh; \ + fi && \ + \ + if [ -f /run/secrets/ubuntu_pro_key ] && [ -s /run/secrets/ubuntu_pro_key ]; then \ + pro detach --assume-yes; \ + fi; \ + fi + +RUN if [ "$OS_DISTRIBUTION" = "opensuse-leap" ]; then \ + if [ "$ARCH" = "amd64" ]; then \ + if [ "$UPDATE_KERNEL" = "false" ]; then \ + zypper al kernel-de*; \ + fi && \ + \ + zypper refresh && zypper update -y && \ + \ + if [ -e "/usr/bin/dracut" ]; then \ + kernel=$(printf '%s\n' /lib/modules/* | xargs -n1 basename | sort -V | tail -1) && \ + depmod -a "${kernel}" && \ + dracut -f "/boot/initrd-${kernel}" "${kernel}" && \ + ln -sf "initrd-${kernel}" /boot/initrd; \ + fi && \ + \ + zypper install -y zstd vim iputils bridge-utils curl ethtool tcpdump && \ + zypper cc && \ + zypper clean; \ + fi && \ + \ + zypper install -y apparmor-parser apparmor-profiles rsyslog logrotate && \ + zypper cc && \ + zypper clean && \ + \ + if [ ! -e /usr/bin/apparmor_parser ]; then \ + cp /sbin/apparmor_parser /usr/bin/apparmor_parser; \ + fi; \ + fi + +RUN if [ "$OS_DISTRIBUTION" = "rhel" ]; then \ + yum install -y openssl rsyslog logrotate; \ + fi + +RUN if [ "$OS_DISTRIBUTION" = "sles" ]; then \ + if [ ! -e /usr/bin/apparmor_parser ]; then \ + cp /sbin/apparmor_parser /usr/bin/apparmor_parser; \ + fi; \ + fi + +# Generate os-release using bind mount for template +RUN --mount=type=bind,target=/build-context \ + export OS_ID=${OS_DISTRIBUTION} && \ + export OS_VERSION=${OS_VERSION} && \ + export OS_LABEL=latest && \ + export VARIANT=${OS_DISTRIBUTION} && \ + export FLAVOR=${OS_DISTRIBUTION} && \ + export BUG_REPORT_URL=https://github.com/spectrocloud/CanvOS/issues && \ + export HOME_URL=https://github.com/spectrocloud/CanvOS && \ + export OS_REPO=spectrocloud/CanvOS && \ + export OS_NAME=${kairos-core-${OS_DISTRIBUTION}} && \ + export ARTIFACT=${kairos-core-${OS_DISTRIBUTION}-${OS_VERSION}} && \ + export KAIROS_RELEASE=${KAIROS_VERSION} && \ + envsubst >>/etc/os-release < /build-context/overlay/files/usr/lib/os-release.tmpl + +RUN KAIROS_IMAGE_LABEL="${OS_VERSION}-standard-${ARCH}-generic" && \ + if [ "$IS_MAAS" = "true" ]; then \ + KAIROS_IMAGE_LABEL="${KAIROS_IMAGE_LABEL}-maas";\ + fi; \ + if [ -f /etc/kairos-release ]; then \ + sed -i 's/^KAIROS_NAME=.*/KAIROS_NAME="kairos-core-'"$OS_DISTRIBUTION"'-'"$OS_VERSION"'"/' /etc/kairos-release; \ + sed -i '/^KAIROS_IMAGE_LABEL=/d' /etc/kairos-release; \ + echo 'KAIROS_IMAGE_LABEL="'"$KAIROS_IMAGE_LABEL"'"' >> /etc/kairos-release; \ + else \ + echo 'KAIROS_NAME="kairos-core-'"$OS_DISTRIBUTION"'-'"$OS_VERSION"'"' >> /etc/kairos-release; \ + echo 'KAIROS_IMAGE_LABEL="'"$KAIROS_IMAGE_LABEL"'"' >> /etc/kairos-release; \ + fi + +RUN rm -rf /var/cache/* && \ + journalctl --vacuum-size=1K && \ + rm -rf /etc/machine-id && \ + rm -rf /var/lib/dbus/machine-id && \ + touch /etc/machine-id && \ + chmod 444 /etc/machine-id && \ + rm /tmp/* -rf + +RUN if [ "$DISABLE_SELINUX" = "true" ]; then \ + if grep "security=selinux" /etc/cos/bootargs.cfg > /dev/null 2>&1; then \ + sed -i 's/security=selinux //g' /etc/cos/bootargs.cfg; \ + fi && \ + if grep "selinux=1" /etc/cos/bootargs.cfg > /dev/null 2>&1; then \ + sed -i 's/selinux=1/selinux=0/g' /etc/cos/bootargs.cfg; \ + fi; \ + fi diff --git a/dockerfiles/Dockerfile.build-iso b/dockerfiles/Dockerfile.build-iso new file mode 100644 index 00000000..3f4814ee --- /dev/null +++ b/dockerfiles/Dockerfile.build-iso @@ -0,0 +1,62 @@ +ARG AURORABOOT_IMAGE + +FROM ${AURORABOOT_IMAGE} AS builder + +ARG ISO_NAME +ARG CLUSTERCONFIG +ARG EDGE_CUSTOM_CONFIG +ARG FORCE_INTERACTIVE_INSTALL=false + +COPY overlay/files-iso/ /overlay/ + +COPY --from=validate-user-data /validated /validated + +COPY --from=iso-image / /iso-source/ + + +RUN --mount=type=bind,target=/build-context \ + mkdir -p /overlay/opt/spectrocloud/content && \ + for dir in /build-context/content-*/; do \ + [ -d "$dir" ] && cp "$dir"*.zst /overlay/opt/spectrocloud/content/ 2>/dev/null || true; \ + done && \ + if [ -n "$EDGE_CUSTOM_CONFIG" ] && [ -f /build-context/"$EDGE_CUSTOM_CONFIG" ]; then \ + cp /build-context/"$EDGE_CUSTOM_CONFIG" /overlay/.edge_custom_config.yaml; \ + fi && \ + if [ -n "$(ls /overlay/opt/spectrocloud/content/*.zst 2>/dev/null)" ]; then \ + for file in /overlay/opt/spectrocloud/content/*.zst; do \ + split --bytes=3GB --numeric-suffixes "$file" /overlay/opt/spectrocloud/content/$(basename "$file")_part; \ + done; \ + rm -f /overlay/opt/spectrocloud/content/*.zst; \ + fi && \ + if [ -n "$CLUSTERCONFIG" ] && [ -f /build-context/"$CLUSTERCONFIG" ]; then \ + mkdir -p /overlay/opt/spectrocloud/clusterconfig && \ + cp /build-context/"$CLUSTERCONFIG" /overlay/opt/spectrocloud/clusterconfig/spc.tgz; \ + fi + +# Generate grub.cfg based on FORCE_INTERACTIVE_INSTALL setting +RUN if [ "$FORCE_INTERACTIVE_INSTALL" = "true" ]; then \ + sed 's/{{DEFAULT_ENTRY}}/2/g' /overlay/boot/grub2/grub.cfg > /overlay/boot/grub2/grub.cfg.tmp && \ + mv /overlay/boot/grub2/grub.cfg.tmp /overlay/boot/grub2/grub.cfg; \ + else \ + sed 's/{{DEFAULT_ENTRY}}/0/g' /overlay/boot/grub2/grub.cfg > /overlay/boot/grub2/grub.cfg.tmp && \ + mv /overlay/boot/grub2/grub.cfg.tmp /overlay/boot/grub2/grub.cfg; \ + fi + +RUN mkdir -p /output + +RUN /usr/bin/auroraboot \ + --debug \ + build-iso \ + --output /output \ + --override-name ${ISO_NAME} \ + --overlay-iso /overlay \ + $(if [ -f /validated/config.yaml ] && [ -s /validated/config.yaml ]; then echo "--cloud-config /validated/config.yaml"; fi) \ + dir:/iso-source + +RUN mkdir -p /iso && \ + cp /output/${ISO_NAME}.iso /iso/ && \ + cd /iso && \ + sha256sum "${ISO_NAME}.iso" > "${ISO_NAME}.iso.sha256" + +FROM scratch AS output +COPY --from=builder /iso/ / \ No newline at end of file diff --git a/dockerfiles/Dockerfile.build-uki-iso b/dockerfiles/Dockerfile.build-uki-iso new file mode 100644 index 00000000..901e78a4 --- /dev/null +++ b/dockerfiles/Dockerfile.build-uki-iso @@ -0,0 +1,90 @@ +ARG AURORABOOT_IMAGE +FROM ${AURORABOOT_IMAGE} AS builder + +ARG ARCH +ARG ISO_NAME +ARG CLUSTERCONFIG +ARG EDGE_CUSTOM_CONFIG +ARG AUTO_ENROLL_SECUREBOOT_KEYS +ARG DEBUG=false +ARG CMDLINE +ARG BRANDING="Palette eXtended Kubernetes Edge" + +ENV ISO_NAME=${ISO_NAME} + +COPY overlay/files-iso/ /overlay/ + +COPY --from=validate-user-data /validated /overlay/ + +COPY --from=stylus-image-pack /stylus-image.tar /overlay/stylus-image.tar + +COPY --from=third-party-luet /WORKDIR/luet /overlay/luet + +COPY --from=iso-image / /iso-source/ + +RUN --mount=type=bind,target=/build-context \ + mkdir -p /overlay/opt/spectrocloud/content && \ + for dir in /build-context/content-*/; do \ + [ -d "$dir" ] && cp "$dir"*.zst /overlay/opt/spectrocloud/content/ 2>/dev/null || true; \ + done && \ + if [ -n "$EDGE_CUSTOM_CONFIG" ] && [ -f /build-context/"$EDGE_CUSTOM_CONFIG" ]; then \ + cp /build-context/"$EDGE_CUSTOM_CONFIG" /overlay/.edge_custom_config.yaml; \ + fi && \ + if [ -n "$(ls /overlay/opt/spectrocloud/content/*.zst 2>/dev/null)" ]; then \ + for file in /overlay/opt/spectrocloud/content/*.zst; do \ + split --bytes=3GB --numeric-suffixes "$file" /overlay/opt/spectrocloud/content/$(basename "$file")_part; \ + done; \ + rm -f /overlay/opt/spectrocloud/content/*.zst; \ + fi && \ + if [ -n "$CLUSTERCONFIG" ] && [ -f /build-context/"$CLUSTERCONFIG" ]; then \ + mkdir -p /overlay/opt/spectrocloud/clusterconfig && \ + cp /build-context/"$CLUSTERCONFIG" /overlay/opt/spectrocloud/clusterconfig/spc.tgz; \ + fi + +# For UKI: move local-ui from squashfs to overlay to reduce EFI size +RUN if [ -d /iso-source/opt/spectrocloud/local-ui ]; then \ + mkdir -p /overlay/opt/spectrocloud && \ + mv /iso-source/opt/spectrocloud/local-ui /overlay/opt/spectrocloud/ ; \ + fi + +COPY secure-boot/enrollment/ /keys/ + +RUN --mount=type=secret,id=db_key,target=/keys/db.key \ + --mount=type=secret,id=db_pem,target=/keys/db.pem \ + --mount=type=secret,id=kek_key,target=/keys/KEK.key \ + --mount=type=secret,id=kek_pem,target=/keys/KEK.pem \ + --mount=type=secret,id=pk_key,target=/keys/PK.key \ + --mount=type=secret,id=pk_pem,target=/keys/PK.pem \ + --mount=type=secret,id=tpm2_pcr_private,target=/keys/tpm2-pcr-private.pem \ + mkdir -p /iso && \ + if [ "$ARCH" = "arm64" ]; then \ + /usr/bin/auroraboot \ + $([ "$DEBUG" = "true" ] && echo "--debug") \ + build-iso \ + --output /iso \ + --override-name "${ISO_NAME}" \ + --overlay-iso /overlay \ + dir:/iso-source; \ + elif [ "$ARCH" = "amd64" ]; then \ + /usr/bin/auroraboot \ + $([ "$DEBUG" = "true" ] && echo "--debug") \ + build-uki \ + --output-type iso \ + --name "${ISO_NAME}" \ + --output-dir /iso \ + --overlay-iso /overlay \ + --public-keys /keys \ + --tpm-pcr-private-key /keys/tpm2-pcr-private.pem \ + --sb-key /keys/db.key \ + --sb-cert /keys/db.pem \ + --boot-branding "${BRANDING}" \ + $([ -n "$CMDLINE" ] && echo "--extend-cmdline $CMDLINE") \ + $([ "$AUTO_ENROLL_SECUREBOOT_KEYS" = "true" ] && echo "--secure-boot-enroll force") \ + dir:/iso-source; \ + fi + +RUN cd /iso && \ + sha256sum "${ISO_NAME}.iso" > "${ISO_NAME}.iso.sha256" + +FROM scratch AS output +COPY --from=builder /iso/ / \ No newline at end of file diff --git a/dockerfiles/Dockerfile.cloud-image-tools b/dockerfiles/Dockerfile.cloud-image-tools new file mode 100644 index 00000000..ffeea784 --- /dev/null +++ b/dockerfiles/Dockerfile.cloud-image-tools @@ -0,0 +1,49 @@ +ARG BASE_IMAGE=alpine:3.20 + +FROM ${BASE_IMAGE} + +# Install all required tools for disk operations +# Grouped by purpose for clarity +RUN apk add --no-cache \ + # Core utilities + bash \ + coreutils \ + util-linux \ + # Partitioning tools + parted \ + gptfdisk \ + multipath-tools \ + # Filesystem tools + e2fsprogs \ + dosfstools \ + # Disk image tools + qemu-img \ + # File operations + rsync \ + tar \ + gzip \ + # Docker CLI for auroraboot + docker-cli \ + # Utilities + jq \ + curl \ + wget \ + # GRUB tools (for MAAS) + grub \ + && rm -rf /var/cache/apk/* + +# Create scripts directory +RUN mkdir -p /scripts + +# Copy all disk operation scripts +COPY cloud-images/scripts/add-content-partition.sh /scripts/ +COPY cloudconfigs/build-kairos-maas.sh /scripts/ +COPY cloudconfigs/curtin-hooks /scripts/ + +# Make scripts executable +RUN chmod +x /scripts/*.sh + +# Set working directory +WORKDIR /workdir + +ENTRYPOINT ["/bin/bash"] diff --git a/dockerfiles/Dockerfile.install-k8s b/dockerfiles/Dockerfile.install-k8s new file mode 100644 index 00000000..a7caf61b --- /dev/null +++ b/dockerfiles/Dockerfile.install-k8s @@ -0,0 +1,44 @@ +ARG ARCH + +FROM --platform=linux/${ARCH} alpine-certs AS install-k8s + +ARG K8S_DISTRIBUTION +ARG K8S_VERSION +ARG K3S_FLAVOR_TAG +ARG RKE2_FLAVOR_TAG +ARG SPECTRO_LUET_REPO +ARG ARCH +ARG LUET_REPO + +COPY --from=third-party-luet /WORKDIR/luet /usr/bin/luet + +WORKDIR /output + +RUN mkdir -p /etc/luet/repos.conf.d && \ + luet repo add spectro --type docker --url $SPECTRO_LUET_REPO/$LUET_REPO --priority 1 -y + +# Use bind mount for optional luet auth config +RUN --mount=type=bind,target=/build-context \ + if [ -f /build-context/spectro-luet-auth.yaml ]; then \ + cat /build-context/spectro-luet-auth.yaml >> /etc/luet/repos.conf.d/spectro.yaml; \ + fi + +RUN luet repo update + +# Calculate BASE_K8S_VERSION based on K8S_DISTRIBUTION +RUN if [ "$K8S_DISTRIBUTION" = "kubeadm" ] || \ + [ "$K8S_DISTRIBUTION" = "kubeadm-fips" ] || \ + [ "$K8S_DISTRIBUTION" = "nodeadm" ] || \ + [ "$K8S_DISTRIBUTION" = "canonical" ]; then \ + BASE_K8S_VERSION="$K8S_VERSION"; \ + elif [ "$K8S_DISTRIBUTION" = "k3s" ]; then \ + K8S_DISTRIBUTION_TAG="$K3S_FLAVOR_TAG"; \ + BASE_K8S_VERSION="$K8S_VERSION-$K8S_DISTRIBUTION_TAG"; \ + elif [ "$K8S_DISTRIBUTION" = "rke2" ]; then \ + K8S_DISTRIBUTION_TAG="$RKE2_FLAVOR_TAG"; \ + BASE_K8S_VERSION="$K8S_VERSION-$K8S_DISTRIBUTION_TAG"; \ + fi && \ + echo "Installing k8s/$K8S_DISTRIBUTION@$BASE_K8S_VERSION for K8S_VERSION=$K8S_VERSION" && \ + luet install -y k8s/$K8S_DISTRIBUTION@$BASE_K8S_VERSION --system-target /output && luet cleanup + +RUN rm -rf /output/var/cache/* diff --git a/dockerfiles/Dockerfile.iso-efi-size-check b/dockerfiles/Dockerfile.iso-efi-size-check new file mode 100644 index 00000000..1040045a --- /dev/null +++ b/dockerfiles/Dockerfile.iso-efi-size-check @@ -0,0 +1,45 @@ +ARG UBUNTU_IMAGE +# Stage 1: Build the EFI binary +FROM rust:1.78-bookworm AS rust-deps + +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -qq autoconf autotools-dev libtool-bin clang cmake bsdmainutils + +RUN ls -lrth + +WORKDIR /build +COPY efi-size-check efi-size-check + +WORKDIR /build/efi-size-check +RUN cargo build --target x86_64-unknown-uefi + +# Stage 2: Create the ISO +FROM ${UBUNTU_IMAGE} AS iso-builder + +ARG EFI_MAX_SIZE +ARG EFI_IMG_SIZE + +RUN apt-get update && \ + apt-get install -y mtools xorriso && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /build + +COPY --from=rust-deps /build/efi-size-check/target/x86_64-unknown-uefi/debug/efi-size-check.efi /build/efi-size-check.efi + +RUN mkdir -p esp && \ + dd if=/dev/urandom of=esp/ABC bs=1M count=${EFI_MAX_SIZE} && \ + dd if=/dev/zero of=fat.img bs=1M count=${EFI_IMG_SIZE} && \ + mformat -i fat.img -F :: && \ + mmd -i fat.img ::/EFI && \ + mmd -i fat.img ::/EFI/BOOT && \ + mcopy -i fat.img efi-size-check.efi ::/EFI/BOOT/BOOTX64.EFI && \ + mcopy -i fat.img esp/ABC :: && \ + mkdir -p iso && \ + cp fat.img iso && \ + xorriso -as mkisofs -e fat.img -no-emul-boot -o efi-size-check.iso iso + +# Output stage +FROM scratch AS output +COPY --from=iso-builder /build/efi-size-check.iso / + diff --git a/dockerfiles/Dockerfile.iso-image b/dockerfiles/Dockerfile.iso-image new file mode 100644 index 00000000..a02a2245 --- /dev/null +++ b/dockerfiles/Dockerfile.iso-image @@ -0,0 +1,45 @@ +FROM base-image AS iso-image + +ARG IS_UKI=false +ARG IS_CLOUD_IMAGE=false +ARG IS_MAAS=false + +COPY --from=stylus-image / / + +RUN if [ "$IS_UKI" = "true" ]; then \ + find /opt/spectrocloud/bin/. ! -name 'agent-provider-stylus' -type f -exec rm -f {} + && \ + rm -f /usr/bin/luet; \ + fi + +COPY overlay/files/ / + +RUN if [ -f /etc/logrotate.d/stylus.conf ]; then \ + chmod 644 /etc/logrotate.d/stylus.conf; \ + fi + +RUN --mount=type=bind,target=/build-context \ + # Extract local-ui for all build types (ISO, UKI, Cloud, MAAS) + if [ -f /build-context/local-ui.tar ]; then \ + mkdir -p /opt/spectrocloud && \ + tar -xf /build-context/local-ui.tar -C /opt/spectrocloud; \ + fi && \ + # Cloud-specific configuration + if [ "$IS_CLOUD_IMAGE" = "true" ]; then \ + cp /build-context/cloud-images/workaround/grubmenu.cfg /etc/kairos/branding/grubmenu.cfg && \ + cp /build-context/cloud-images/workaround/custom-post-reset.yaml /system/oem/custom-post-reset.yaml && \ + mkdir -p /opt/spectrocloud/scripts && \ + cp /build-context/cloudconfigs/cloud-content.sh /opt/spectrocloud/scripts/cloud-content.sh && \ + chmod 755 /opt/spectrocloud/scripts/cloud-content.sh && \ + cp /build-context/cloudconfigs/cloud-extend-persistent.sh /opt/spectrocloud/scripts/cloud-extend-persistent.sh && \ + chmod 755 /opt/spectrocloud/scripts/cloud-extend-persistent.sh; \ + fi && \ + # MAAS-specific configuration + if [ "$IS_MAAS" = "true" ]; then \ + mkdir -p /opt/spectrocloud/scripts && \ + cp /build-context/cloudconfigs/maas-content.sh /opt/spectrocloud/scripts/maas-content.sh && \ + chmod 755 /opt/spectrocloud/scripts/maas-content.sh; \ + fi + +RUN rm -f /etc/ssh/ssh_host_* /etc/ssh/moduli && \ + touch /etc/machine-id && \ + chmod 444 /etc/machine-id diff --git a/dockerfiles/Dockerfile.provider-image b/dockerfiles/Dockerfile.provider-image new file mode 100644 index 00000000..c564f9d6 --- /dev/null +++ b/dockerfiles/Dockerfile.provider-image @@ -0,0 +1,137 @@ +ARG ARCH + +FROM base-image AS provider-image + +ARG IMAGE_REPO +ARG K8S_DISTRIBUTION +ARG K8S_VERSION +ARG K3S_FLAVOR_TAG +ARG RKE2_FLAVOR_TAG +ARG OS_DISTRIBUTION +ARG EDGE_CUSTOM_CONFIG +ARG TWO_NODE +ARG KINE_VERSION +ARG IMAGE_PATH +ARG IS_UKI +ARG ARCH +ARG UPDATE_KERNEL=false + +RUN if [ "$OS_DISTRIBUTION" = "ubuntu" ] && [ "$ARCH" = "amd64" ] && [ "$K8S_DISTRIBUTION" = "kubeadm" ]; then \ + kernel=$(printf '%s\n' /lib/modules/* | xargs -n1 basename | sort -V | tail -1) && \ + if ! ls /usr/src | grep linux-headers-$kernel; then apt-get update && apt-get install -y "linux-headers-${kernel}"; fi; \ + fi + +# UPDATE_KERNEL handling for different OS distributions +RUN if [ "$UPDATE_KERNEL" = "true" ]; then \ + if [ "$OS_DISTRIBUTION" = "ubuntu" ] && [ "$ARCH" = "amd64" ]; then \ + kernel=$(printf '%s\n' /lib/modules/* | xargs -n1 basename | sort -V | tail -1) && \ + if ! ls /usr/src | grep linux-headers-$kernel; then apt-get update && apt-get install -y "linux-headers-${kernel}"; fi; \ + elif [ "$OS_DISTRIBUTION" = "opensuse-leap" ] || [ "$OS_DISTRIBUTION" = "sles" ]; then \ + zypper --non-interactive ref && \ + kernel=$(printf '%s\n' /lib/modules/* | xargs -n1 basename | sort -V | tail -1) && \ + echo "kernel module: $kernel" && \ + version=$(echo $kernel | sed 's/-default$//') && \ + echo "kernel version: $version" && \ + if ! zypper --non-interactive install --no-recommends kernel-default-devel-$version; then \ + echo "Exact kernel-default-devel-$version not found, searching for closest match..."; \ + match=$(zypper se -s kernel-default-devel | awk -F'|' '/kernel-default-devel/ && $3 ~ /^ *[0-9]/ {gsub(/^ +| +$/,"",$3); if (index($3,"'"$version"'")==1) print $3}' | sort -Vr | head -n1); \ + if [ -n "$match" ]; then \ + echo "Trying to install kernel-default-devel-$match"; \ + zypper --non-interactive install --no-recommends kernel-default-devel-$match || echo "Failed to install kernel-default-devel-$match"; \ + else \ + echo "No matching kernel-default-devel package found, trying generic kernel-devel"; \ + zypper --non-interactive install --no-recommends kernel-devel || echo "kernel development packages not available, continuing without them"; \ + fi; \ + fi; \ + elif [ "$OS_DISTRIBUTION" = "rhel" ]; then \ + yum clean all && yum makecache && \ + yum-config-manager --enable ubi-8-baseos-rpms ubi-8-appstream-rpms ubi-8-codeready-builder-rpms 2>/dev/null || true && \ + yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm 2>/dev/null || echo "EPEL repo not available" && \ + yum makecache && \ + kernel=$(printf '%s\n' /lib/modules/* | xargs -n1 basename | sort -V | tail -1) && echo "kernel version: $kernel" && \ + if ! yum install -y kernel-devel-$kernel; then \ + echo "kernel-devel-$kernel not available, trying alternative packages" && \ + yum install -y kernel-devel || \ + echo "Trying to install from different source..." && \ + yum install -y gcc make || echo "kernel development packages not available, continuing without them"; \ + fi; \ + fi; \ + fi + +COPY overlay/files/etc/ /etc/ + +RUN if [ -f /etc/logrotate.d/stylus.conf ]; then \ + chmod 644 /etc/logrotate.d/stylus.conf; \ + fi + +COPY --from=kairos-provider-image / / +COPY --from=stylus-image /etc/kairos/branding /etc/kairos/branding +COPY --from=stylus-image /oem/stylus_config.yaml /etc/kairos/branding/stylus_config.yaml +COPY --from=stylus-image /etc/elemental/config.yaml /etc/elemental/config.yaml + +COPY --from=stylus-image / /tmp/stylus-image/ +RUN if [ -f /tmp/stylus-image/system/oem/80_stylus.yaml ]; then \ + mkdir -p /system/oem && \ + cp /tmp/stylus-image/system/oem/80_stylus.yaml /system/oem/80_stylus.yaml; \ + fi && \ + rm -rf /tmp/stylus-image + +# Use bind mount to conditionally copy EDGE_CUSTOM_CONFIG without adding to image layers +RUN --mount=type=bind,target=/build-context \ + if [ -n "$EDGE_CUSTOM_CONFIG" ] && [ -f /build-context/"$EDGE_CUSTOM_CONFIG" ]; then \ + mkdir -p /oem && \ + cp /build-context/"$EDGE_CUSTOM_CONFIG" /oem/.edge_custom_config.yaml; \ + fi + +COPY --from=install-k8s /output/ /k8s +COPY --from=internal-slink /slink /usr/bin/slink + +RUN if [ "$IS_UKI" = "true" ]; then \ + slink --source /k8s/ --target /opt/k8s && \ + rm -rf /k8s && \ + ln -sf /opt/spectrocloud/bin/agent-provider-stylus /usr/local/bin/agent-provider-stylus; \ + else \ + cp -r /k8s/* /; \ + fi && \ + rm -rf /k8s /usr/bin/slink + +RUN rm -f /etc/ssh/ssh_host_* /etc/ssh/moduli + +COPY --from=third-party-etcdctl /WORKDIR/etcdctl /usr/bin/ + +RUN touch /etc/machine-id && \ + chmod 444 /etc/machine-id + +RUN if [ "$OS_DISTRIBUTION" = "ubuntu" ] && [ "$K8S_DISTRIBUTION" = "nodeadm" ]; then \ + apt-get update -y && apt-get install -y gnupg && \ + /opt/nodeadmutil/bin/nodeadm install -p iam-ra $K8S_VERSION --skip validate && \ + /opt/nodeadmutil/bin/nodeadm install -p ssm $K8S_VERSION --skip validate && \ + find /opt/ssm -type f -name "amazon-ssm-agent.deb" -exec sudo dpkg -i {} \; && \ + apt-get remove gnupg -y && apt autoremove -y && \ + mv /usr/local/bin/aws-iam-authenticator /usr/bin && \ + mv /usr/local/bin/aws_signing_helper /usr/bin && \ + cp /lib/systemd/system/amazon-ssm-agent.service /etc/systemd/system/snap.amazon-ssm-agent.amazon-ssm-agent.service; \ + fi + +RUN if [ "$TWO_NODE" = "true" ]; then \ + if [ "$OS_DISTRIBUTION" = "ubuntu" ] && [ "$ARCH" = "amd64" ]; then \ + apt-get update && \ + echo "tzdata tzdata/Areas select Etc" | debconf-set-selections && \ + echo "tzdata tzdata/Zones/Etc select UTC" | debconf-set-selections && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl && \ + install -d /usr/share/postgresql-common/pgdg && \ + curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc && \ + echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list && \ + apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y postgresql-16 postgresql-contrib-16 iputils-ping; \ + elif [ "$OS_DISTRIBUTION" = "opensuse-leap" ] && [ "$ARCH" = "amd64" ]; then \ + zypper --non-interactive --quiet addrepo --refresh -p 90 http://download.opensuse.org/repositories/server:database:postgresql/openSUSE_Tumbleweed/ PostgreSQL && \ + zypper --gpg-auto-import-keys ref && \ + zypper install -y postgresql-16 postgresql-server-16 postgresql-contrib iputils; \ + fi && \ + mkdir -p /opt/spectrocloud/bin && \ + curl -L https://github.com/k3s-io/kine/releases/download/v${KINE_VERSION}/kine-amd64 | install -m 755 /dev/stdin /opt/spectrocloud/bin/kine && \ + su postgres -c 'echo "export PERL5LIB=/usr/share/perl/5.34:/etc/perl:/usr/lib/x86_64-linux-gnu/perl5/5.34:/usr/share/perl5:/usr/lib/x86_64-linux-gnu/perl/5.34:/usr/lib/x86_64-linux-gnu/perl-base" > ~/.bash_profile' && \ + sed -i 's/After=network.target/After=network-online.target/' /lib/systemd/system/postgresql@.service && \ + systemctl disable postgresql; \ + fi diff --git a/dockerfiles/Dockerfile.slink b/dockerfiles/Dockerfile.slink new file mode 100644 index 00000000..f1e80fdc --- /dev/null +++ b/dockerfiles/Dockerfile.slink @@ -0,0 +1,31 @@ +ARG SPECTRO_PUB_REPO +ARG GOLANG_VERSION + +FROM ${SPECTRO_PUB_REPO}/third-party/golang:${GOLANG_VERSION}-alpine AS go-deps + +RUN apk add libc-dev binutils-gold clang + +FROM go-deps AS build + +COPY internal /build/internal + +WORKDIR /build/internal + +ARG VERSION=dev +ARG BIN +ARG SRC +ARG GOOS +ARG GOARCH + +ENV GOOS=${GOOS} +ENV GOARCH=${GOARCH} +ENV GO_LDFLAGS="-X github.com/spectrocloud/stylus/pkg/version.Version=${VERSION} -w -s" +ENV CC=clang + +RUN go mod download + +RUN go-build-static.sh -a -o /build/bin/${BIN} ./${SRC} + +FROM scratch AS export +ARG BIN +COPY --from=build /build/bin/${BIN} /${BIN} \ No newline at end of file diff --git a/dockerfiles/Dockerfile.stylus-image-pack b/dockerfiles/Dockerfile.stylus-image-pack new file mode 100644 index 00000000..f03d3c87 --- /dev/null +++ b/dockerfiles/Dockerfile.stylus-image-pack @@ -0,0 +1,18 @@ +ARG STYLUS_PACKAGE_BASE +FROM ${STYLUS_PACKAGE_BASE} AS stylus-package + +FROM alpine AS packer + +ARG STYLUS_BASE +ARG ARCH + +COPY --from=third-party-luet /WORKDIR/luet /usr/bin/luet + +COPY --from=stylus-package / /stylus/ + +RUN cd /stylus && \ + tar -czf /stylus.tar * && \ + luet util pack "$STYLUS_BASE" /stylus.tar /stylus-image.tar + +FROM scratch AS output +COPY --from=packer /stylus-image.tar / \ No newline at end of file diff --git a/dockerfiles/Dockerfile.third-party b/dockerfiles/Dockerfile.third-party new file mode 100644 index 00000000..cdb8ec98 --- /dev/null +++ b/dockerfiles/Dockerfile.third-party @@ -0,0 +1,15 @@ +ARG SPECTRO_THIRD_PARTY_IMAGE +ARG TARGETPLATFORM + +FROM --platform=${TARGETPLATFORM} ${SPECTRO_THIRD_PARTY_IMAGE} AS source + +FROM --platform=${TARGETPLATFORM} alpine-certs AS third-party + +ARG ARCH +ARG BIN_TYPE +ARG binary + +WORKDIR /WORKDIR +COPY --from=source /binaries/${binary}/latest/${BIN_TYPE}/${ARCH}/${binary} /WORKDIR/${binary} +COPY --from=source /binaries/${binary}/latest/${BIN_TYPE}/${ARCH}/${binary}.version /WORKDIR/${binary}.version +RUN upx -1 /WORKDIR/${binary} \ No newline at end of file diff --git a/dockerfiles/Dockerfile.trustedboot-image b/dockerfiles/Dockerfile.trustedboot-image new file mode 100644 index 00000000..70c3f3ec --- /dev/null +++ b/dockerfiles/Dockerfile.trustedboot-image @@ -0,0 +1,34 @@ +ARG AURORABOOT_IMAGE +ARG DEBUG=false + +FROM provider-image AS provider-image-rootfs + +FROM ${AURORABOOT_IMAGE} AS builder + +COPY --from=provider-image-rootfs / /build/image/ + +COPY secure-boot/enrollment/ /keys/ + +RUN mkdir -p /output + +RUN --mount=type=secret,id=db_key,target=/keys/db.key \ + --mount=type=secret,id=db_pem,target=/keys/db.pem \ + --mount=type=secret,id=kek_key,target=/keys/KEK.key \ + --mount=type=secret,id=kek_pem,target=/keys/KEK.pem \ + --mount=type=secret,id=pk_key,target=/keys/PK.key \ + --mount=type=secret,id=pk_pem,target=/keys/PK.pem \ + --mount=type=secret,id=tpm2_pcr_private,target=/keys/tpm2-pcr-private.pem \ + /usr/bin/auroraboot \ + $([ "$DEBUG" = "true" ] && echo "--debug") \ + build-uki \ + --output-dir /output \ + --public-keys /keys \ + --tpm-pcr-private-key /keys/tpm2-pcr-private.pem \ + --sb-key /keys/db.key \ + --sb-cert /keys/db.pem \ + --output-type container \ + --boot-branding "Palette eXtended Kubernetes Edge" \ + dir:/build/image + +FROM scratch AS output +COPY --from=builder /output/ / \ No newline at end of file diff --git a/dockerfiles/Dockerfile.uki-byok b/dockerfiles/Dockerfile.uki-byok new file mode 100644 index 00000000..a75e328c --- /dev/null +++ b/dockerfiles/Dockerfile.uki-byok @@ -0,0 +1,86 @@ +ARG UBUNTU_IMAGE + +FROM ${UBUNTU_IMAGE} + +ARG INCLUDE_MS_SECUREBOOT_KEYS=false + + +RUN apt-get update && apt-get install -y efitools curl tar ca-certificates && \ + update-ca-certificates + +RUN curl -fsSL -o /tmp/sbctl.tar.gz https://github.com/Foxboron/sbctl/releases/download/0.13/sbctl-0.13-linux-amd64.tar.gz && \ + tar -xzf /tmp/sbctl.tar.gz -C /tmp && \ + mv /tmp/sbctl/sbctl /usr/bin/sbctl && \ + chmod +x /usr/bin/sbctl && \ + rm -rf /tmp/sbctl.tar.gz /tmp/sbctl + +RUN mkdir -p /exported-keys /keys /tmp/secure-boot /private-keys /public-keys + +COPY secure-boot/ /tmp/secure-boot + +# Copy keys from /tmp/secure-boot to their destinations +# Preserve original structure in /private-keys and /public-keys for output +RUN if [ -d "/tmp/secure-boot/private-keys" ]; then \ + cp -r /tmp/secure-boot/private-keys/* /keys/ && \ + cp -r /tmp/secure-boot/private-keys/* /private-keys/; \ + fi + +RUN if [ -d "/tmp/secure-boot/public-keys" ]; then \ + cp -r /tmp/secure-boot/public-keys/* /keys/ && \ + cp -r /tmp/secure-boot/public-keys/* /public-keys/; \ + fi + +RUN if [ -d "/tmp/secure-boot/exported-keys" ]; then \ + cp -r /tmp/secure-boot/exported-keys/* /exported-keys/; \ + fi + +RUN rm -rf /tmp/secure-boot + +WORKDIR /keys + +RUN sbctl import-keys \ + --pk-key /keys/PK.key \ + --pk-cert /keys/PK.pem \ + --kek-key /keys/KEK.key \ + --kek-cert /keys/KEK.pem \ + --db-key /keys/db.key \ + --db-cert /keys/db.pem + +RUN sbctl create-keys + +RUN if [ "$INCLUDE_MS_SECUREBOOT_KEYS" = "false" ]; then \ + sbctl enroll-keys --export esl --yes-this-might-brick-my-machine; \ + else \ + sbctl enroll-keys --export esl --yes-this-might-brick-my-machine --microsoft; \ + fi + +RUN mkdir -p /output + +RUN cp PK.esl /output/PK.esl 2>/dev/null || true && \ + cp KEK.esl /output/KEK.esl 2>/dev/null || true && \ + cp db.esl /output/db.esl 2>/dev/null || true + +RUN if [ -f dbx.esl ]; then \ + cp dbx.esl /output/dbx.esl; \ + else \ + touch /output/dbx.esl; \ + fi + +RUN [ -f /exported-keys/KEK ] && cat /exported-keys/KEK >> /output/KEK.esl || true && \ + [ -f /exported-keys/db ] && cat /exported-keys/db >> /output/db.esl || true && \ + [ -f /exported-keys/dbx ] && cat /exported-keys/dbx >> /output/dbx.esl || true + +WORKDIR /output + +RUN sign-efi-sig-list -c /keys/PK.pem -k /keys/PK.key PK PK.esl PK.auth && \ + sign-efi-sig-list -c /keys/PK.pem -k /keys/PK.key KEK KEK.esl KEK.auth && \ + sign-efi-sig-list -c /keys/KEK.pem -k /keys/KEK.key db db.esl db.auth && \ + sign-efi-sig-list -c /keys/KEK.pem -k /keys/KEK.key dbx dbx.esl dbx.auth + +RUN sig-list-to-certs 'PK.esl' 'PK' && \ + sig-list-to-certs 'KEK.esl' 'KEK' && \ + sig-list-to-certs 'db.esl' 'db' + +RUN cp PK-0.der PK.der 2>/dev/null || true && \ + cp KEK-0.der KEK.der 2>/dev/null || true && \ + cp db-0.der db.der 2>/dev/null || true \ No newline at end of file diff --git a/dockerfiles/Dockerfile.uki-genkey b/dockerfiles/Dockerfile.uki-genkey new file mode 100644 index 00000000..81ba3253 --- /dev/null +++ b/dockerfiles/Dockerfile.uki-genkey @@ -0,0 +1,56 @@ +ARG MY_ORG +ARG EXPIRATION_IN_DAYS +ARG INCLUDE_MS_SECUREBOOT_KEYS +ARG AURORABOOT_IMAGE +ARG ARCH + +# Stage 1: Generate keys (used when UKI_BRING_YOUR_OWN_KEYS=false) +FROM ${AURORABOOT_IMAGE} AS builder + +ARG MY_ORG +ARG EXPIRATION_IN_DAYS +ARG INCLUDE_MS_SECUREBOOT_KEYS + +RUN mkdir -p /custom-keys && \ + if [ -d "secure-boot/exported-keys" ]; then \ + cp -r secure-boot/exported-keys/* /custom-keys/ 2>/dev/null || true; \ + fi && \ + \ + # Build flags and message dynamically + GENKEY_FLAGS="" && \ + MSG="Generating Secure Boot keys" && \ + \ + if [ -f /custom-keys/KEK ] && [ -f /custom-keys/db ]; then \ + GENKEY_FLAGS="--custom-cert-dir /custom-keys" && \ + MSG="$MSG, including exported UEFI keys"; \ + fi && \ + \ + if [ "$INCLUDE_MS_SECUREBOOT_KEYS" != "true" ]; then \ + GENKEY_FLAGS="$GENKEY_FLAGS --skip-microsoft-certs-I-KNOW-WHAT-IM-DOING"; \ + else \ + MSG="$MSG and Microsoft keys"; \ + fi && \ + \ + echo "$MSG..." && \ + /usr/bin/auroraboot genkey \ + "$MY_ORG" \ + $GENKEY_FLAGS \ + --expiration-days $EXPIRATION_IN_DAYS \ + --output /keys && \ + \ + ls -ltrh /keys && \ + mkdir -p /private-keys /public-keys && \ + cd /keys && mv *.key tpm2-pcr-private.pem /private-keys/ 2>/dev/null || true && \ + cd /keys && mv *.pem /public-keys/ 2>/dev/null || true + +# UKI_BRING_YOUR_OWN_KEYS=true - copy all files from uki-byok context +FROM scratch AS output-byok +COPY --from=uki-byok /output/ /enrollment/ +COPY --from=uki-byok /private-keys/ /private-keys/ +COPY --from=uki-byok /public-keys/ /public-keys/ + +# UKI_BRING_YOUR_OWN_KEYS=false - use generated keys from builder +FROM scratch AS output-no-byok +COPY --from=builder /keys/ /enrollment/ +COPY --from=builder /private-keys/ /private-keys/ +COPY --from=builder /public-keys/ /public-keys/ \ No newline at end of file diff --git a/dockerfiles/Dockerfile.uki-provider-image b/dockerfiles/Dockerfile.uki-provider-image new file mode 100644 index 00000000..e6e9c6d3 --- /dev/null +++ b/dockerfiles/Dockerfile.uki-provider-image @@ -0,0 +1,44 @@ +ARG UBUNTU_IMAGE +ARG BASE_IMAGE + +FROM ${BASE_IMAGE} AS kairos-agent +RUN mkdir -p /output +RUN cp /usr/bin/kairos-agent /output/kairos-agent + +FROM ${UBUNTU_IMAGE} AS trust-boot-unpack + +COPY --from=third-party-luet /WORKDIR/luet /usr/bin/luet +COPY --from=trustedboot-image / /image/ + +RUN FILE="file:/$(find /image -type f -name "*.tar" | head -n 1)" && \ + luet util unpack $FILE /trusted-boot + +FROM ${UBUNTU_IMAGE} AS uki-provider-image + +ARG EDGE_CUSTOM_CONFIG + +RUN apt-get update && apt-get install -y rsync + +WORKDIR / +COPY overlay/files/etc/ /etc/ +RUN if [ -f /etc/logrotate.d/stylus.conf ]; then chmod 644 /etc/logrotate.d/stylus.conf; fi + +COPY --from=third-party-luet /WORKDIR/luet /usr/bin/luet +COPY --from=kairos-agent /output/kairos-agent /usr/bin/kairos-agent +COPY --from=trust-boot-unpack /trusted-boot/ /trusted-boot/ +COPY --from=install-k8s /output/ /k8s/ + +# Use bind mount to conditionally copy EDGE_CUSTOM_CONFIG without adding to image layers +RUN --mount=type=bind,target=/build-context \ + if [ -n "$EDGE_CUSTOM_CONFIG" ] && [ -f /build-context/"$EDGE_CUSTOM_CONFIG" ]; then \ + mkdir -p /oem && \ + cp /build-context/"$EDGE_CUSTOM_CONFIG" /oem/.edge_custom_config.yaml; \ + fi + +# Copy stylus-image to temp location to handle optional 80_stylus.yaml (Docker doesn't have --if-exists) +COPY --from=stylus-image / /tmp/stylus-image/ +RUN if [ -f /tmp/stylus-image/system/oem/80_stylus.yaml ]; then \ + mkdir -p /system/oem && \ + cp /tmp/stylus-image/system/oem/80_stylus.yaml /system/oem/80_stylus.yaml; \ + fi && \ + rm -rf /tmp/stylus-image diff --git a/dockerfiles/Dockerfile.validate-ud b/dockerfiles/Dockerfile.validate-ud new file mode 100644 index 00000000..a722334e --- /dev/null +++ b/dockerfiles/Dockerfile.validate-ud @@ -0,0 +1,15 @@ +ARG CLI_IMAGE +ARG ARCH +FROM --platform=linux/${ARCH} ${CLI_IMAGE} AS validate-user-data + +RUN chmod +x /usr/local/bin/palette-edge-cli + +# Use bind mount to access user-data without copying entire context +RUN --mount=type=bind,target=/build-context \ + mkdir -p /validated && \ + if [ -f /build-context/user-data ]; then \ + /usr/local/bin/palette-edge-cli validate -f /build-context/user-data && \ + cp /build-context/user-data /validated/config.yaml; \ + else \ + echo "user-data file does not exist (skipping validation)"; \ + fi \ No newline at end of file diff --git a/dockerfiles/kairosify/Dockerfile.kairosify b/dockerfiles/kairosify/Dockerfile.kairosify new file mode 100644 index 00000000..8e2afe3e --- /dev/null +++ b/dockerfiles/kairosify/Dockerfile.kairosify @@ -0,0 +1,24 @@ +ARG KAIROS_INIT_IMAGE +ARG BASE_OS_IMAGE + +FROM ${KAIROS_INIT_IMAGE} AS kairos-init +FROM ${BASE_OS_IMAGE} AS baseos + +ARG MODEL +ARG KAIROS_VERSION +ARG TRUSTED_BOOT +ARG BASE_OS_IMAGE + +COPY --from=kairos-init /kairos-init /kairos-init +RUN /kairos-init -l debug -m "${MODEL}" -t "${TRUSTED_BOOT}" --version "${KAIROS_VERSION}" && rm /kairos-init + +RUN if echo "${BASE_OS_IMAGE}" | grep -q ubuntu; then \ + apt-get update && apt-get install -y --no-install-recommends \ + dracut dracut-network isc-dhcp-common isc-dhcp-client cloud-guest-utils \ + $([ "$BASE_OS_IMAGE" != "ubuntu:20.04" ] && echo "dracut-live"); \ + fi + +RUN if echo "${BASE_OS_IMAGE}" | grep -q opensuse; then \ + zypper refresh && zypper update -y && \ + zypper install -y dracut dhcp-client squashfs; \ + fi \ No newline at end of file diff --git a/earthly.sh b/earthly.sh index 9c50e22d..4b35c9b4 100755 --- a/earthly.sh +++ b/earthly.sh @@ -179,9 +179,8 @@ if [[ "$1" == "+maas-image" ]]; then fi # Run the original build-kairos-maas.sh script locally - # The script expects curtin-hooks to be in ORIG_DIR (the directory where the script is invoked from) - # Copy curtin-hooks to the repo root (current directory) so the script can find it - cp "$CURTIN_HOOKS" ./curtin-hooks + # Export path to curtin-hooks so the build script can find it + export CURTIN_HOOKS_SCRIPT="$(readlink -f "$CURTIN_HOOKS")" # Check for files to add to content partition # The build script looks for: @@ -286,26 +285,12 @@ if [[ "$1" == "+maas-image" ]]; then exit 1 fi - # Move the compressed composite image to build directory for consistency + # Move the compressed composite image and checksum to build directory mkdir -p build mv "$COMPOSITE_IMAGE" "build/$COMPOSITE_IMAGE" + mv "${COMPOSITE_IMAGE}.sha256" "build/${COMPOSITE_IMAGE}.sha256" 2>/dev/null || true - # Generate SHA256 checksum file for the final image - echo "=== Generating SHA256 checksum ===" - FINAL_IMAGE_PATH="build/$COMPOSITE_IMAGE" - sha256sum "$FINAL_IMAGE_PATH" > "${FINAL_IMAGE_PATH}.sha256" - echo "✅ SHA256 checksum created: ${FINAL_IMAGE_PATH}.sha256" - - # Clean up temporary curtin-hooks file from repo root - rm -f ./curtin-hooks - - # Show final image size and checksum - FINAL_SIZE=$(du -h "$FINAL_IMAGE_PATH" | cut -f1) - CHECKSUM=$(cat "${FINAL_IMAGE_PATH}.sha256" | cut -d' ' -f1) - echo "✅ MAAS composite image created and compressed successfully: $FINAL_IMAGE_PATH" - echo " Final size: $FINAL_SIZE" - echo " SHA256: $CHECKSUM" - echo " MAAS will automatically decompress this image during upload." + echo "✅ MAAS image build complete: build/$COMPOSITE_IMAGE" exit 0 fi diff --git a/rhel-core-images/Dockerfile.rhel8.sat b/rhel-core-images/Dockerfile.rhel8.sat index 1d8f567f..715a40c1 100644 --- a/rhel-core-images/Dockerfile.rhel8.sat +++ b/rhel-core-images/Dockerfile.rhel8.sat @@ -1,7 +1,7 @@ ARG BASE_IMAGE=registry.access.redhat.com/ubi8/ubi-init:8.7-10 ARG KAIROS_INIT_IMAGE=quay.io/kairos/kairos-init:v0.5.28 -FROM $KAIROS_INIT_IMAGE as kairos-init +FROM $KAIROS_INIT_IMAGE AS kairos-init FROM $BASE_IMAGE diff --git a/rhel-core-images/Dockerfile.rhel9.sat b/rhel-core-images/Dockerfile.rhel9.sat index 7ccf6889..3923b2a4 100644 --- a/rhel-core-images/Dockerfile.rhel9.sat +++ b/rhel-core-images/Dockerfile.rhel9.sat @@ -1,7 +1,7 @@ ARG BASE_IMAGE=registry.access.redhat.com/ubi9-init:9.4-6 ARG KAIROS_INIT_IMAGE=quay.io/kairos/kairos-init:v0.5.28 -FROM $KAIROS_INIT_IMAGE as kairos-init +FROM $KAIROS_INIT_IMAGE AS kairos-init FROM $BASE_IMAGE diff --git a/rhel-fips/Dockerfile.rhel8 b/rhel-fips/Dockerfile.rhel8 index 3a96bc55..7ad839df 100644 --- a/rhel-fips/Dockerfile.rhel8 +++ b/rhel-fips/Dockerfile.rhel8 @@ -1,7 +1,7 @@ # Kairos init image FROM quay.io/kairos/kairos-init:v0.5.28 AS kairos-init -FROM registry.access.redhat.com/ubi8/ubi-init:8.7-10 as base +FROM registry.access.redhat.com/ubi8/ubi-init:8.7-10 AS base ARG USERNAME ARG PASSWORD diff --git a/rhel-fips/Dockerfile.rhel9 b/rhel-fips/Dockerfile.rhel9 index 29641f11..03503ad5 100644 --- a/rhel-fips/Dockerfile.rhel9 +++ b/rhel-fips/Dockerfile.rhel9 @@ -1,7 +1,7 @@ # Kairos init image FROM quay.io/kairos/kairos-init:v0.5.28 AS kairos-init -FROM registry.access.redhat.com/ubi9-init:9.4-6 as base +FROM registry.access.redhat.com/ubi9-init:9.4-6 AS base ARG USERNAME ARG PASSWORD diff --git a/sb-private-ca/howto.md b/sb-private-ca/howto.md index d9434945..9c177f8d 100644 --- a/sb-private-ca/howto.md +++ b/sb-private-ca/howto.md @@ -16,7 +16,7 @@ PK.pem KEK.pem db.pem -5. Run `./earthly.sh +secure-boot-dirs` to create the secure-boot directory structure in CanvOS. +5. Run `./earthly.sh +secure-boot-dirs` or `make secure-boot-dirs` to create the secure-boot directory structure in CanvOS. 6. Place the files in the following directory structure: ``` CanvOS/