From 0e1ac3e61ba5669c1489a6eebeb078efb6b1f72d Mon Sep 17 00:00:00 2001 From: Maximilian Moehl Date: Tue, 24 Feb 2026 12:28:04 +0100 Subject: [PATCH 1/3] Update README with new image names --- README.md | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/README.md b/README.md index 6177962..89ec051 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,58 @@ ## Overview The `os-images` repository specializes in automating the release and publication of Operating System (OS) image artifacts as OCI (Open Container Initiative) images. This project streamlines the process of pushing these OS images to the project's GitHub Container Registry (`ghcr.io`) via GitHub Actions. +### Published Images + +> [!WARNING] +> +> We are in the process of re-structuring the images we publish. The pattern +> described is the desired state which may not be fully implemented yet. Images +> not following this pattern will eventually be deleted, if you require some of +> them to be retained, please open an issue. + +Images in this repository are published according to the following scheme: + +``` +ghcr.io/ironcore-dev/os-images/$distro[/$flavor]:$version[-$variant-$arch][-manual] +``` + +* `$distro`: The linux distribution from which this image is built. Currently, + only [gardenlinux](https://gardenlinux.io) is supported. +* `$flavor`: Additional packages on top of the base image for the given purpose. + E.g. the CAPI flavor contains packages necessary to deploy CAPI nodes. +* `$version`: The version of the base image, distribution specific. +* `$variant`: The platform this image runs on, currently `kvm` and `metal` are + supported. +* `$arch`: Either `amd64` or `arch64`. +* `-manual`: Set for images built and published by hand. + +The architecture and variant can also automatically be selected by fetching the +tag without them to get a manifest listing all permutations. Note that, if you +specify the variant or the architecture you also need to specify the other. +We automatically publish the following images: + +``` +ghcr.io/ironcore-dev/os-images +└── /gardenlinux + ├── :$version + ├── :$version-kvm-amd64 + ├── :$version-kvm-arm64 + ├── :$version-metal-amd64 + ├── :$version-metal-arm64 +    ├── /gardener + │ ├── :$version + │ ├── :$version-kvm-amd64 + │ ├── :$version-kvm-arm64 + │ ├── :$version-metal-amd64 + │ └── :$version-metal-arm64 +    └── /capi + ├── :$version + ├── :$version-kvm-amd64 + ├── :$version-kvm-arm64 + ├── :$version-metal-amd64 + └── :$version-metal-arm64 +``` + ## Workflow The GitHub Actions workflow triggers on pushes to the `main` branch and performs the following tasks: - **Download and Extract OS Artifact**: Automates the process of downloading and extracting specified OS artifacts. From 5912d8ab30db25c8e8b59729ee4822280f515a7f Mon Sep 17 00:00:00 2001 From: Maximilian Moehl Date: Tue, 24 Feb 2026 17:10:51 +0100 Subject: [PATCH 2/3] Adjust workflow to new image names --- .github/os_image_artifacts.yml | 6 - .../workflows/publish-gardenlinux-dev.yaml | 76 ----- .../publish-gardenlinux-ironcore.yml | 137 --------- .../publish-gardenlinux-virtualization.yml | 94 ------ .github/workflows/publish-gardenlinux.yml | 289 +++++++++++------- README.md | 46 +-- 6 files changed, 210 insertions(+), 438 deletions(-) delete mode 100644 .github/os_image_artifacts.yml delete mode 100644 .github/workflows/publish-gardenlinux-dev.yaml delete mode 100644 .github/workflows/publish-gardenlinux-ironcore.yml delete mode 100644 .github/workflows/publish-gardenlinux-virtualization.yml diff --git a/.github/os_image_artifacts.yml b/.github/os_image_artifacts.yml deleted file mode 100644 index cac0212..0000000 --- a/.github/os_image_artifacts.yml +++ /dev/null @@ -1,6 +0,0 @@ -amd64: - gardenlinux_kvm_artifact_url: https://github.com/gardenlinux/gardenlinux/releases/download/1443.10/kvm-gardener_prod-amd64-1443.10-8d098305.tar.xz - gardenlinux_metal_artifact_url: https://github.com/gardenlinux/gardenlinux/releases/download/1443.10/metal-gardener_prod_pxe-amd64-1443.10-8d098305.tar.xz -arm64: - gardenlinux_kvm_artifact_url: https://github.com/gardenlinux/gardenlinux/releases/download/1443.10/kvm-gardener_prod-arm64-1443.10-8d098305.tar.xz - gardenlinux_metal_artifact_url: https://github.com/gardenlinux/gardenlinux/releases/download/1443.10/metal-gardener_prod_pxe-arm64-1443.10-8d098305.tar.xz \ No newline at end of file diff --git a/.github/workflows/publish-gardenlinux-dev.yaml b/.github/workflows/publish-gardenlinux-dev.yaml deleted file mode 100644 index 0f2b210..0000000 --- a/.github/workflows/publish-gardenlinux-dev.yaml +++ /dev/null @@ -1,76 +0,0 @@ -name: Publish GardenLinux Dev Image - -on: - push: - branches: - - main - workflow_dispatch: - -jobs: - build: - permissions: - contents: read - packages: write - runs-on: ubuntu-latest - - steps: - - name: Install Podman - run: | - sudo apt-get update -qq - sudo apt-get -qq -y install podman - curl -Lo ./crun https://github.com/containers/crun/releases/download/1.14.3/crun-1.14.3-linux-amd64 - chmod +x ./crun - sudo mv ./crun /usr/bin/crun - - - name: Setup ORAS - uses: oras-project/setup-oras@v1 - - - name: Clone gardenlinux repository - uses: actions/checkout@v2 - with: - repository: gardenlinux/gardenlinux - fetch-depth: 0 - - - name: Checkout latest tag - run: | - git fetch --all --tags - latestTag=$(git describe --tags `git rev-list --tags --max-count=1`) - git checkout $latestTag - echo "{\"commandLine\": \"\", \"os-release\": \"$latestTag\"}" > config.json - - - name: Overwrite features/ssh/info.yaml - run: | - echo 'description: "OpenSSH server"' > features/ssh/info.yaml - echo 'type: element' >> features/ssh/info.yaml - - - name: Build with Podman - run: | - ./build kvm - - - name: Create dummy files using dd in the current directory - run: | - dd if=/dev/zero of=initrd bs=2M count=1 - dd if=/dev/zero of=vmlinuz bs=3M count=1 - - - name: List files in the current directory - run: | - ls -alh - - - name: Login to GitHub Container Registry - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Push Artifact using ORAS - run: | - KVM_RAW_FILE=$(ls .build/*.raw | head -n 1) - INITRD_FILE=initrd - VMLINUZ_FILE=vmlinuz - echo "Pushing files to ORAS..." - oras push ghcr.io/${{ github.repository_owner }}/os-images/gardenlinux-dev:latest \ - $KVM_RAW_FILE:application/vnd.ironcore.image.rootfs.v1alpha1.rootfs \ - $INITRD_FILE:application/vnd.ironcore.image.initramfs.v1alpha1.initramfs \ - $VMLINUZ_FILE:application/vnd.ironcore.image.vmlinuz.v1alpha1.vmlinuz \ - --config config.json:application/vnd.ironcore.image.config.v1alpha1+json diff --git a/.github/workflows/publish-gardenlinux-ironcore.yml b/.github/workflows/publish-gardenlinux-ironcore.yml deleted file mode 100644 index 975c29b..0000000 --- a/.github/workflows/publish-gardenlinux-ironcore.yml +++ /dev/null @@ -1,137 +0,0 @@ -name: Publish GardenLinux New OCI Image with UKI - -on: - workflow_dispatch: - inputs: - version: - description: "Specify the GardenLinux version (e.g., 1877.0)" - required: true - -jobs: - publish: - permissions: - contents: read - packages: write - runs-on: ubuntu-latest - - env: - VERSION: ${{ github.event.inputs.version }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Dependencies and Build ukify + stub - run: | - sudo apt-get update -qq - sudo apt-get install -y \ - jq curl git make meson ninja-build gperf \ - python3-pip python3-pyelftools \ - libssl-dev liblz4-dev libzstd-dev libacl1-dev \ - libblkid-dev libkmod-dev libmount-dev libpam0g-dev \ - libcryptsetup-dev libaudit-dev libmicrohttpd-dev \ - libcap-dev pkg-config uuid-dev \ - libefivar-dev gnu-efi - - sudo pip3 install pefile - git clone --depth=1 --branch v256 https://github.com/systemd/systemd.git - cd systemd - meson setup build - ninja -C build - sudo cp build/ukify /usr/local/bin/ukify - sudo mkdir -p /usr/lib/systemd/boot/efi - sudo cp build/src/boot/efi/linuxx64.efi.stub /usr/lib/systemd/boot/efi/ - - - name: Setup ORAS - uses: oras-project/setup-oras@v1 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ env.GITHUB_TOKEN }} - - - name: Clone Ironcore Image Repo - run: | - git clone https://x-access-token:${GITHUB_TOKEN}@github.com/ironcore-dev/ironcore-image.git - - - name: Build and Push OCI Images (Vanilla/Gardener/CAPI) - run: | - set -euo pipefail - cd ironcore-image - make build - - for VARIANT in vanilla gardener capi; do - echo "Starting build for variant: $VARIANT" - - case "$VARIANT" in - vanilla) - IMAGE_NAME="ghcr.io/${{ github.repository_owner }}/os-images/gardenlinux" - CNAME_PREFIX="metal_pxe" - ;; - gardener) - IMAGE_NAME="ghcr.io/${{ github.repository_owner }}/os-images/gardener/gardenlinux" - CNAME_PREFIX="metal-gardener_pxe" - ;; - capi) - IMAGE_NAME="ghcr.io/${{ github.repository_owner }}/os-images/capi/gardenlinux" - CNAME_PREFIX="metal-capi" - ;; - esac - - mkdir -p ../binaries/amd64 ../binaries/arm64 - - for ARCH in amd64 arm64; do - echo "Fetching layer for $VARIANT $ARCH" - - INDEX_JSON=$(oras manifest fetch ghcr.io/gardenlinux/gardenlinux:$VERSION) - echo "$INDEX_JSON" | jq . > index-${ARCH}.json - - DIGEST=$(jq -r --arg arch "$ARCH" --arg prefix "$CNAME_PREFIX" \ - '.manifests[] | select(.platform.architecture == $arch and ((.annotations.cname? // "") | tostring | startswith($prefix))) | .digest' index-${ARCH}.json) - - echo "Found digest: $DIGEST" - oras manifest fetch ghcr.io/gardenlinux/gardenlinux@$DIGEST > manifest-${ARCH}.json - - for BIN in initrd vmlinuz root.squashfs; do - DIGEST_BIN=$(jq -r --arg bin "$BIN" '.layers[] | select(.annotations."org.opencontainers.image.title" == $bin).digest' manifest-${ARCH}.json) - oras blob fetch ghcr.io/gardenlinux/gardenlinux@$DIGEST_BIN -o ../binaries/$ARCH/$BIN - done - done - - echo "First build (without UKI)" - ./bin/ironcore-image build \ - --tag $IMAGE_NAME:$VERSION \ - --config arch=amd64,squashfs=../binaries/amd64/root.squashfs,initramfs=../binaries/amd64/initrd,kernel=../binaries/amd64/vmlinuz \ - --config arch=arm64,squashfs=../binaries/arm64/root.squashfs,initramfs=../binaries/arm64/initrd,kernel=../binaries/arm64/vmlinuz - - echo "Inspecting squashfs digest for cmdline" - for ARCH in amd64 arm64; do - REF_TAG="${VERSION}-${ARCH}" - DIGEST=$(./bin/ironcore-image inspect $IMAGE_NAME:$REF_TAG \ - | jq -r '.manifest.layers[] | select(.mediaType == "application/vnd.ironcore.image.squashfs").digest') - - CMDLINE="initrd=initrd gl.ovl=/:tmpfs gl.live=1 ip=any console=ttyS0,115200 console=tty0 earlyprintk=ttyS0,115200 consoleblank=0 ignition.firstboot=1 ignition.config.url=http://boot.onmetal.de:8083/ignition ignition.config.url.append.uuid=true ignition.platform.id=metal gl.url=http://boot.onmetal.de:8083/image?imageName=$IMAGE_NAME&version=${VERSION}&layerDigest=${DIGEST}" - - echo "Building UKI for $ARCH with squashfs digest $DIGEST" - ukify build \ - --linux ../binaries/$ARCH/vmlinuz \ - --initrd ../binaries/$ARCH/initrd \ - --stub /usr/lib/systemd/boot/efi/linuxx64.efi.stub \ - --cmdline "$CMDLINE" \ - --output ../binaries/$ARCH/uki.img - done - - echo "Final image build with UKI for $VARIANT" - ./bin/ironcore-image build \ - --tag $IMAGE_NAME:$VERSION \ - --config arch=amd64,squashfs=../binaries/amd64/root.squashfs,initramfs=../binaries/amd64/initrd,kernel=../binaries/amd64/vmlinuz,uki=../binaries/amd64/uki.img \ - --config arch=arm64,squashfs=../binaries/arm64/root.squashfs,initramfs=../binaries/arm64/initrd,kernel=../binaries/arm64/vmlinuz,uki=../binaries/arm64/uki.img - - echo "Pushing final image with UKI: $IMAGE_NAME:$VERSION" - ./bin/ironcore-image push $IMAGE_NAME:$VERSION --push-sub-manifests - - echo "Finished $VARIANT" - done diff --git a/.github/workflows/publish-gardenlinux-virtualization.yml b/.github/workflows/publish-gardenlinux-virtualization.yml deleted file mode 100644 index e23235d..0000000 --- a/.github/workflows/publish-gardenlinux-virtualization.yml +++ /dev/null @@ -1,94 +0,0 @@ -name: Publish GardenLinux Virtualization Image - -on: - push: - branches: - - main - workflow_dispatch: - -jobs: - build: - permissions: - contents: read - packages: write - - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@v5 - - - name: Install yq (YAML Processor) - run: sudo snap install yq - - - name: Read Config Files and Extract OS Version - id: read-config - run: | - #AMD64 - GARDENLINUX_AMD64_KVM_ARTIFACT_URL=$(yq e .amd64.gardenlinux_kvm_artifact_url .github/os_image_artifacts.yml) - echo "GARDENLINUX_AMD64_KVM_ARTIFACT_URL=$GARDENLINUX_AMD64_KVM_ARTIFACT_URL" >> $GITHUB_ENV - - AMD64_ARTIFACT_FOLDER=$(basename $GARDENLINUX_AMD64_KVM_ARTIFACT_URL | sed 's/.tar.xz//') - echo "AMD64_ARTIFACT_FOLDER=$AMD64_ARTIFACT_FOLDER" >> $GITHUB_ENV - - #ARM64 - GARDENLINUX_ARM64_KVM_ARTIFACT_URL=$(yq e .arm64.gardenlinux_kvm_artifact_url .github/os_image_artifacts.yml) - echo "GARDENLINUX_ARM64_KVM_ARTIFACT_URL=$GARDENLINUX_ARM64_KVM_ARTIFACT_URL" >> $GITHUB_ENV - - ARM64_ARTIFACT_FOLDER=$(basename $GARDENLINUX_ARM64_KVM_ARTIFACT_URL | sed 's/.tar.xz//') - echo "ARM64_ARTIFACT_FOLDER=$ARM64_ARTIFACT_FOLDER" >> $GITHUB_ENV - - OS_VERSION=$(echo $GARDENLINUX_AMD64_KVM_ARTIFACT_URL | cut -d '/' -f 8) - echo "OS_VERSION=$OS_VERSION" >> $GITHUB_ENV - - name: Download and Extract Gardenlinux KVM Artifact - run: | - curl -L ${{ env.GARDENLINUX_AMD64_KVM_ARTIFACT_URL }} -o gardenlinux_amd.tar.xz - tar -xf gardenlinux_amd.tar.xz - KVM_AMD64_RAW_FILE=$(ls ${{ env.AMD64_ARTIFACT_FOLDER }}/*.raw) - echo "KVM_AMD64_RAW_FILE=$KVM_AMD64_RAW_FILE" >> $GITHUB_ENV - - curl -L ${{ env.GARDENLINUX_ARM64_KVM_ARTIFACT_URL }} -o gardenlinux_arm.tar.xz - tar -xf gardenlinux_arm.tar.xz - KVM_ARM64_RAW_FILE=$(ls ${{ env.ARM64_ARTIFACT_FOLDER }}/*.raw) - echo "KVM_ARM64_RAW_FILE=$KVM_ARM64_RAW_FILE" >> $GITHUB_ENV - - - name: Create Config JSON - run: | - echo "gl.url=/dev/disk/by-id/virtio-machineboot gl.live=1 gl.ovl=/:tmpfs console=tty0 console=ttyAMA0,115200 earlyprintk=ttyAMA0,115200 consoleblank=0 cgroup_enable=memory swapaccount=1 ignition.firstboot=1 ignition.platform.id=qemu" > arm64.cmdline.json - echo "gl.url=/dev/disk/by-id/virtio-machineboot gl.live=1 gl.ovl=/:tmpfs console=tty0 console=ttyS0,115200 earlyprintk=ttyS0,115200 consoleblank=0 cgroup_enable=memory swapaccount=1 ignition.firstboot=1 ignition.platform.id=qemu" > amd64.cmdline.json - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Setup go - uses: actions/setup-go@v6 - with: - go-version: 'stable' - - - name: Install ironcore-image - run: | - go install github.com/ironcore-dev/ironcore-image/cmd@main - mv $(go env GOPATH)/bin/cmd $(go env GOPATH)/bin/ironcore-image - echo "$(go env GOPATH)/bin" >> $GITHUB_PATH - - - name: Push Image (Version Tag) - run: | - ironcore-image build \ - --tag ghcr.io/${{ github.repository_owner }}/os-images/virtualization/gardenlinux:$OS_VERSION \ - --config arch=amd64,rootfs=${{ env.KVM_AMD64_RAW_FILE }},cmdline=./amd64.cmdline.json \ - --config arch=arm64,rootfs=${{ env.KVM_ARM64_RAW_FILE }},cmdline=./arm64.cmdline.json - - ironcore-image push ghcr.io/${{ github.repository_owner }}/os-images/virtualization/gardenlinux:$OS_VERSION --push-sub-manifests - - - name: Push Image (Latest Tag) - run: | - ironcore-image build \ - --tag ghcr.io/${{ github.repository_owner }}/os-images/virtualization/gardenlinux:latest \ - --config arch=amd64,rootfs=${{ env.KVM_AMD64_RAW_FILE }},cmdline=./amd64.cmdline.json \ - --config arch=arm64,rootfs=${{ env.KVM_ARM64_RAW_FILE }},cmdline=./arm64.cmdline.json - - ironcore-image push ghcr.io/${{ github.repository_owner }}/os-images/virtualization/gardenlinux:latest --push-sub-manifests diff --git a/.github/workflows/publish-gardenlinux.yml b/.github/workflows/publish-gardenlinux.yml index dc0fc6d..2d8ad5f 100644 --- a/.github/workflows/publish-gardenlinux.yml +++ b/.github/workflows/publish-gardenlinux.yml @@ -1,119 +1,193 @@ -name: Publish GardenLinux Image +name: Publish GardenLinux Images on: schedule: - # Run weekly on Sundays at 00:00 UTC - cron: '0 0 * * 0' workflow_dispatch: + inputs: + version: + description: 'GardenLinux version (leave empty for latest release)' + required: false + type: string jobs: - check-release: + resolve-version: runs-on: ubuntu-latest outputs: - latest_version: ${{ steps.get-latest.outputs.latest_version }} - should_publish: ${{ steps.check-published.outputs.should_publish }} + version: ${{ steps.resolve.outputs.version }} + should_publish: ${{ steps.resolve.outputs.should_publish }} steps: - - name: Get Latest GardenLinux Release - id: get-latest + - name: Resolve version + id: resolve run: | - LATEST_RELEASE=$(curl -s https://api.github.com/repos/gardenlinux/gardenlinux/releases/latest | jq -r '.tag_name') - echo "Latest GardenLinux release: $LATEST_RELEASE" - echo "latest_version=$LATEST_RELEASE" >> $GITHUB_OUTPUT + if [ -n "${{ inputs.version }}" ]; then + VERSION="${{ inputs.version }}" + echo "Using manually specified version: $VERSION" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "should_publish=true" >> "$GITHUB_OUTPUT" + exit 0 + fi - - name: Check if Version Already Published - id: check-published - run: | - LATEST_VERSION="${{ steps.get-latest.outputs.latest_version }}" - # Check if the image already exists in ghcr.io by attempting to pull the manifest - if docker manifest inspect ghcr.io/${{ github.repository_owner }}/os-images/gardenlinux-amd64:$LATEST_VERSION > /dev/null 2>&1; then - echo "Version $LATEST_VERSION already published, skipping" - echo "should_publish=false" >> $GITHUB_OUTPUT - else - echo "New version $LATEST_VERSION found, will publish" - echo "should_publish=true" >> $GITHUB_OUTPUT + VERSION=$(curl -s https://api.github.com/repos/gardenlinux/gardenlinux/releases/latest | jq -r '.tag_name') + if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then + echo "::error::Failed to resolve latest GardenLinux release" + exit 1 + fi + echo "Resolved latest GardenLinux release: $VERSION" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + # Only check for duplicates on scheduled runs, manual dispatch always publishes. + if [ "${{ github.event_name }}" = "schedule" ]; then + if docker manifest inspect "ghcr.io/${{ github.repository_owner }}/gardenlinux/gardener:${VERSION}-kvm-amd64" > /dev/null 2>&1; then + echo "Version $VERSION already published, skipping" + echo "should_publish=false" >> "$GITHUB_OUTPUT" + exit 0 + fi fi + echo "should_publish=true" >> "$GITHUB_OUTPUT" + publish: - needs: check-release - if: needs.check-release.outputs.should_publish == 'true' + needs: resolve-version + if: needs.resolve-version.outputs.should_publish == 'true' permissions: contents: read packages: write runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - arch: [ "arm64", "amd64" ] + include: + - variant: metal + image_name: gardenlinux + asset_pattern: baremetal_pxe + - variant: kvm + image_name: gardenlinux/gardener + asset_pattern: kvm-gardener_prod + - variant: metal + image_name: gardenlinux/gardener + asset_pattern: baremetal-gardener_pxe + - variant: metal + image_name: gardenlinux/capi + asset_pattern: baremetal-capi + + env: + VERSION: ${{ needs.resolve-version.outputs.version }} + IMAGE: ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }} steps: - - name: Checkout Repository + - name: Checkout uses: actions/checkout@v4 - - name: Setup ORAS - uses: oras-project/setup-oras@v1 - - - name: Set Version and Artifact URLs - id: read-config + - name: Fetch artifacts from GitHub release + env: + GH_TOKEN: ${{ github.token }} run: | - OS_VERSION="${{ needs.check-release.outputs.latest_version }}" - echo "OS_VERSION=$OS_VERSION" >> $GITHUB_ENV - - # Fetch release assets to find the correct artifact filenames - ASSETS=$(curl -s "https://api.github.com/repos/gardenlinux/gardenlinux/releases/tags/$OS_VERSION" | jq -r '.assets[].name') - - # Find KVM artifact for this architecture - KVM_ASSET=$(echo "$ASSETS" | grep -E "^kvm-gardener_prod-${{ matrix.arch }}-.*\.tar\.xz$" | head -1) - if [ -z "$KVM_ASSET" ]; then - echo "Error: Could not find KVM artifact for ${{ matrix.arch }}" - exit 1 - fi - GARDENLINUX_KVM_ARTIFACT_URL="https://github.com/gardenlinux/gardenlinux/releases/download/$OS_VERSION/$KVM_ASSET" - echo "GARDENLINUX_KVM_ARTIFACT_URL=$GARDENLINUX_KVM_ARTIFACT_URL" >> $GITHUB_ENV - - # Find Metal PXE artifact for this architecture (optional - may not exist for arm64) - METAL_ASSET=$(echo "$ASSETS" | grep -E "^metal-gardener_prod_pxe-${{ matrix.arch }}-.*\.tar\.xz$" | head -1) - if [ -z "$METAL_ASSET" ]; then - echo "Warning: Could not find Metal artifact for ${{ matrix.arch }}, skipping Metal artifacts" - echo "HAS_METAL_ARTIFACT=false" >> $GITHUB_ENV - else - GARDENLINUX_METAL_ARTIFACT_URL="https://github.com/gardenlinux/gardenlinux/releases/download/$OS_VERSION/$METAL_ASSET" - echo "GARDENLINUX_METAL_ARTIFACT_URL=$GARDENLINUX_METAL_ARTIFACT_URL" >> $GITHUB_ENV - echo "HAS_METAL_ARTIFACT=true" >> $GITHUB_ENV - fi - - echo "KVM URL: $GARDENLINUX_KVM_ARTIFACT_URL" - if [ -n "$METAL_ASSET" ]; then - echo "Metal URL: $GARDENLINUX_METAL_ARTIFACT_URL" - else - echo "Metal URL: (not available for ${{ matrix.arch }})" - fi - - - name: Download and Extract Gardenlinux KVM Artifact + set -euo pipefail + + ASSETS=$(curl -sH "Authorization: token ${GH_TOKEN}" \ + "https://api.github.com/repos/gardenlinux/gardenlinux/releases/tags/$VERSION" \ + | jq -r '.assets[].name') + + for ARCH in amd64 arm64; do + ASSET=$(echo "$ASSETS" | grep -E "^${{ matrix.asset_pattern }}-${ARCH}-.*\.tar\.xz$" | head -1) + if [ -z "$ASSET" ]; then + echo "::error::Could not find asset matching ${{ matrix.asset_pattern }}-${ARCH}-*.tar.xz" + exit 1 + fi + + # The asset base name (without .tar.xz) is the prefix for all + # files inside the archive. + ASSET_BASE="${ASSET%.tar.xz}" + + echo "Downloading $ASSET" + mkdir -p "artifacts/${ARCH}" + curl -sL "https://github.com/gardenlinux/gardenlinux/releases/download/${VERSION}/${ASSET}" \ + -o "artifacts/${ARCH}/${ASSET}" + + echo "Extracting $ASSET" + tar -xf "artifacts/${ARCH}/${ASSET}" -C "artifacts/${ARCH}" + rm "artifacts/${ARCH}/${ASSET}" + + if [ "${{ matrix.variant }}" = "kvm" ]; then + RAW_FILE="artifacts/${ARCH}/${ASSET_BASE}.raw" + if [ ! -f "$RAW_FILE" ]; then + echo "::error::Expected .raw file not found at ${RAW_FILE}" + exit 1 + fi + echo "KVM_${ARCH^^}_RAW=${RAW_FILE}" >> "$GITHUB_ENV" + else + # Metal: extract the nested PXE tarball to get initrd, vmlinuz, root.squashfs. + PXE_TARBALL="artifacts/${ARCH}/${ASSET_BASE}.pxe.tar.gz" + if [ ! -f "$PXE_TARBALL" ]; then + echo "::error::Could not find nested PXE tarball at ${PXE_TARBALL}" + exit 1 + fi + tar -xzf "$PXE_TARBALL" -C "artifacts/${ARCH}" + + for BIN in initrd vmlinuz root.squashfs; do + if [ ! -f "artifacts/${ARCH}/${BIN}" ]; then + echo "::error::Could not find ${BIN} for ${ARCH}" + exit 1 + fi + done + + echo "METAL_${ARCH^^}_DIR=artifacts/${ARCH}" >> "$GITHUB_ENV" + echo "METAL_${ARCH^^}_ASSET_BASE=${ASSET_BASE}" >> "$GITHUB_ENV" + fi + done + + - name: Create cmdline files (KVM) + if: matrix.variant == 'kvm' run: | - curl -L ${{ env.GARDENLINUX_KVM_ARTIFACT_URL }} -o gardenlinux.tar.xz - tar -xf gardenlinux.tar.xz - echo "Extracted contents:" - ls -la + echo -n "gl.url=/dev/disk/by-id/virtio-machineboot gl.live=1 gl.ovl=/:tmpfs cgroup_enable=memory swapaccount=1 ignition.firstboot=1 ignition.platform.id=qemu consoleblank=0 console=tty0 console=ttyS0,115200 earlyprintk=ttyS0,115200" > amd64.cmdline + echo -n "gl.url=/dev/disk/by-id/virtio-machineboot gl.live=1 gl.ovl=/:tmpfs cgroup_enable=memory swapaccount=1 ignition.firstboot=1 ignition.platform.id=qemu consoleblank=0 console=tty0 console=ttyAMA0,115200 earlyprintk=ttyAMA0,115200" > arm64.cmdline - - name: Download and Extract Gardenlinux Metal Artifact - if: env.HAS_METAL_ARTIFACT == 'true' + - name: Install systemd-ukify (Metal) + if: matrix.variant == 'metal' run: | - # Download the outer tarball - curl -L ${{ env.GARDENLINUX_METAL_ARTIFACT_URL }} -o gardenlinux-metal.tar.xz - tar -xf gardenlinux-metal.tar.xz - - # Extract the nested tarball to get the initrd, vmlinuz, and root.squashfs files - NESTED_TARBALL=$(find . -name "*pxe.tar.gz") - tar -xzf $NESTED_TARBALL + sudo apt-get update -qq + sudo apt-get install -y systemd-ukify - - name: Create Config JSON + - name: Build UKI (Metal) + if: matrix.variant == 'metal' run: | - if [ "${{ matrix.arch }}" == "arm64" ]; then - echo "{\"commandLine\": \"gl.url=/dev/disk/by-id/virtio-machineboot gl.live=1 gl.ovl=/:tmpfs console=tty0 console=ttyAMA0,115200 earlyprintk=ttyAMA0,115200 consoleblank=0 cgroup_enable=memory swapaccount=1 ignition.firstboot=1 ignition.platform.id=qemu\", \"os-release\": \"$OS_VERSION\", \"arch\": \"${{ matrix.arch }}\"}" > config.json - fi - if [ "${{ matrix.arch }}" == "amd64" ]; then - echo "{\"commandLine\": \"gl.url=/dev/disk/by-id/virtio-machineboot gl.live=1 gl.ovl=/:tmpfs console=tty0 console=ttyS0,115200 earlyprintk=ttyS0,115200 consoleblank=0 cgroup_enable=memory swapaccount=1 ignition.firstboot=1 ignition.platform.id=qemu\", \"os-release\": \"$OS_VERSION\", \"arch\": \"${{ matrix.arch }}\"}" > config.json - fi + set -euo pipefail + + for ARCH in amd64 arm64; do + ARCH_UPPER="${ARCH^^}" + DIR_VAR="METAL_${ARCH_UPPER}_DIR" + DIR="${!DIR_VAR}" + BASE_VAR="METAL_${ARCH_UPPER}_ASSET_BASE" + ASSET_BASE="${!BASE_VAR}" + + # Extract the arch-correct EFI stub from the rootfs tar. + if [ "$ARCH" = "amd64" ]; then + STUB_NAME="linuxx64.efi.stub" + else + STUB_NAME="linuxaa64.efi.stub" + fi + STUB_PATH="usr/lib/systemd/boot/efi/${STUB_NAME}" + tar -xf "${DIR}/${ASSET_BASE}.tar" -C "${DIR}" "${STUB_PATH}" + EFI_STUB="${DIR}/${STUB_PATH}" + + # Build initrd-uki by embedding squashfs into initrd + cp "${DIR}/initrd" "${DIR}/initrd-uki" + (cd "${DIR}" && echo root.squashfs | cpio -H newc -o | xz --check=crc32) >> "${DIR}/initrd-uki" + + CMDLINE="initrd=initrd gl.ovl=/:tmpfs gl.live=1 ip=any console=ttyS0,115200 console=tty0 earlyprintk=ttyS0,115200 consoleblank=0 ignition.firstboot=1 ignition.config.url=http://boot.onmetal.de:8083/ignition ignition.config.url.append.uuid=true ignition.platform.id=metal" + + ukify build \ + --linux "${DIR}/vmlinuz" \ + --initrd "${DIR}/initrd-uki" \ + --stub "$EFI_STUB" \ + --cmdline "$CMDLINE" \ + --output "${DIR}/uki.img" + + echo "Built UKI for ${ARCH}" + done + - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: @@ -121,30 +195,33 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Push Image with ORAS (Version Tag) - run: | - # Find the .raw file dynamically (handles varying folder structures) - KVM_RAW_FILE=$(find . -name "*.raw" -type f | head -1) - if [ -z "$KVM_RAW_FILE" ]; then - echo "Error: Could not find .raw file after extraction" - exit 1 - fi - echo "Found KVM raw file: $KVM_RAW_FILE" + - name: Setup Go + uses: actions/setup-go@v6 + with: + go-version: 'stable' - # Build the oras push command with required KVM artifact - ORAS_ARGS="$KVM_RAW_FILE:application/vnd.ironcore.image.rootfs.v1alpha1.rootfs" + - name: Install ironcore-image + run: | + go install github.com/ironcore-dev/ironcore-image/cmd@latest + mv "$(go env GOPATH)/bin/cmd" "$(go env GOPATH)/bin/ironcore-image" + echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" - # Add Metal artifacts if available - if [ "${{ env.HAS_METAL_ARTIFACT }}" == "true" ]; then - ORAS_ARGS="$ORAS_ARGS root.squashfs:application/vnd.ironcore.image.squashfs.v1alpha1.squashfs" - ORAS_ARGS="$ORAS_ARGS initrd:application/vnd.ironcore.image.initramfs.v1alpha1.initramfs" - ORAS_ARGS="$ORAS_ARGS vmlinuz:application/vnd.ironcore.image.vmlinuz.v1alpha1.vmlinuz" - fi + - name: Build image (KVM) + if: matrix.variant == 'kvm' + run: | + ironcore-image build \ + --tag "${IMAGE}:${VERSION}-kvm" \ + --config "arch=amd64,rootfs=${KVM_AMD64_RAW},cmdline=./amd64.cmdline" \ + --config "arch=arm64,rootfs=${KVM_ARM64_RAW},cmdline=./arm64.cmdline" - oras push ghcr.io/${{ github.repository_owner }}/os-images/gardenlinux-${{ matrix.arch }}:$OS_VERSION \ - $ORAS_ARGS \ - --config config.json:application/vnd.ironcore.image.config.v1alpha1+json + - name: Build image (Metal) + if: matrix.variant == 'metal' + run: | + ironcore-image build \ + --tag "${IMAGE}:${VERSION}-metal" \ + --config "arch=amd64,squashfs=${METAL_AMD64_DIR}/root.squashfs,initramfs=${METAL_AMD64_DIR}/initrd,kernel=${METAL_AMD64_DIR}/vmlinuz,uki=${METAL_AMD64_DIR}/uki.img" \ + --config "arch=arm64,squashfs=${METAL_ARM64_DIR}/root.squashfs,initramfs=${METAL_ARM64_DIR}/initrd,kernel=${METAL_ARM64_DIR}/vmlinuz,uki=${METAL_ARM64_DIR}/uki.img" - - name: Push Image with ORAS (Latest Tag) + - name: Push image run: | - oras tag ghcr.io/${{ github.repository_owner }}/os-images/gardenlinux-${{ matrix.arch }}:$OS_VERSION latest + ironcore-image push "${IMAGE}:${VERSION}-${{ matrix.variant }}" --push-sub-manifests diff --git a/README.md b/README.md index 89ec051..26a7e4c 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,6 @@ We automatically publish the following images: ghcr.io/ironcore-dev/os-images └── /gardenlinux ├── :$version - ├── :$version-kvm-amd64 - ├── :$version-kvm-arm64 ├── :$version-metal-amd64 ├── :$version-metal-arm64    ├── /gardener @@ -53,33 +51,43 @@ ghcr.io/ironcore-dev/os-images │ └── :$version-metal-arm64    └── /capi ├── :$version - ├── :$version-kvm-amd64 - ├── :$version-kvm-arm64 ├── :$version-metal-amd64 └── :$version-metal-arm64 ``` +Since we are currently only re-publishing the gardenlinux images, we can not +publish the full matrix as gardenlinux builds only some of the combinations we'd +like to support. + ## Workflow -The GitHub Actions workflow triggers on pushes to the `main` branch and performs the following tasks: -- **Download and Extract OS Artifact**: Automates the process of downloading and extracting specified OS artifacts. -- **Prepare and Push Images with ORAS**: Pushes the prepared OS images to `ghcr.io`, tagging each with its specific version and also as `latest`. -## Configuration -The configuration for OS artifacts is managed through the `os_image_artifacts.yml` file located in the `.github` directory. Currently only `amd64` and `arm64` platforms are supported. +The main publishing workflow (`publish-gardenlinux.yml`) runs weekly on Sundays +and can also be triggered manually via `workflow_dispatch`. It: + +1. **Resolves the version** to publish: uses the manually provided version or + discovers the latest Garden Linux release from the GitHub API. Scheduled runs + skip publishing if the version has already been published. +2. **Downloads artifacts** from the Garden Linux GitHub release for both `amd64` + and `arm64` architectures. +3. **Builds multi-arch OCI images** using + [`ironcore-image`](https://github.com/ironcore-dev/ironcore-image) for each + flavor and variant combination. +4. **Pushes images** to `ghcr.io` with per-variant and per-arch sub-manifest + tags. + +For metal images, the workflow also builds a Unified Kernel Image (UKI) using +`ukify` with the EFI stub extracted from the Garden Linux artifacts. + +> [!NOTE] +> +> The per-variant tags (e.g. `:$version-metal-amd64`) are available today. +> The combined `:$version` index manifest listing all variant-arch permutations +> is not yet produced and will require extending `ironcore-image` with variant +> support. ## Documentation - Manual build (Garden Linux -> `ironcore-image`): `docs/manual-oci-image-build.md` -### Example Configuration -```yaml -amd64: - gardenlinux_kvm_artifact_url: https://github.com/gardenlinux/gardenlinux/releases/download/1592.3/kvm-gardener_prod-amd64-1592.3-f64e280f.tar.xz - gardenlinux_metal_artifact_url: https://github.com/gardenlinux/gardenlinux/releases/download/1592.3/metal-gardener_prod_pxe-amd64-1592.3-f64e280f.tar.xz -arm64: - gardenlinux_kvm_artifact_url: https://github.com/gardenlinux/gardenlinux/releases/download/1592.3/kvm-gardener_prod-arm64-1592.3-f64e280f.tar.xz - gardenlinux_metal_artifact_url: https://github.com/gardenlinux/gardenlinux/releases/download/1592.3/metal-gardener_prod_pxe-arm64-1592.3-f64e280f.tar.xz -``` - ## Contributing Contributions to enhance and broaden the scope of the os-images project are encouraged. Please ensure all changes are well-tested before submission. From 95c113765a565d08ffde08a0222d43a65d2faa87 Mon Sep 17 00:00:00 2001 From: Maximilian Moehl Date: Wed, 25 Feb 2026 17:03:25 +0100 Subject: [PATCH 3/3] Add hack script for manual images --- README.md | 6 +- REUSE.toml | 1 + docs/README.md | 4 - docs/manual-oci-image-build.md | 177 --------------------------------- hack/README.md | 26 +++++ hack/build-metal-dev.sh | 116 +++++++++++++++++++++ 6 files changed, 146 insertions(+), 184 deletions(-) delete mode 100644 docs/README.md delete mode 100644 docs/manual-oci-image-build.md create mode 100644 hack/README.md create mode 100755 hack/build-metal-dev.sh diff --git a/README.md b/README.md index 26a7e4c..113272d 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ The `os-images` repository specializes in automating the release and publication Images in this repository are published according to the following scheme: ``` -ghcr.io/ironcore-dev/os-images/$distro[/$flavor]:$version[-$variant-$arch][-manual] +ghcr.io/ironcore-dev/$distro[/$flavor]:$version[-$variant-$arch][-manual] ``` * `$distro`: The linux distribution from which this image is built. Currently, @@ -38,7 +38,7 @@ specify the variant or the architecture you also need to specify the other. We automatically publish the following images: ``` -ghcr.io/ironcore-dev/os-images +ghcr.io/ironcore-dev └── /gardenlinux ├── :$version ├── :$version-metal-amd64 @@ -86,7 +86,7 @@ For metal images, the workflow also builds a Unified Kernel Image (UKI) using > support. ## Documentation -- Manual build (Garden Linux -> `ironcore-image`): `docs/manual-oci-image-build.md` +- Manual build (Garden Linux -> `ironcore-image`): `hack/` ## Contributing Contributions to enhance and broaden the scope of the os-images project are encouraged. Please ensure all changes are well-tested before submission. diff --git a/REUSE.toml b/REUSE.toml index e526851..ba4bd39 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -10,6 +10,7 @@ path = [ "CODEOWNERS", "REUSE.toml", "docs/**", + "hack/**", ] precedence = "aggregate" SPDX-FileCopyrightText = "2024 SAP SE or an SAP affiliate company and IronCore contributors" diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 499745c..0000000 --- a/docs/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Documentation - -- `manual-oci-image-build.md`: Manual workflow to build Garden Linux artifacts and package/push them as OCI images using `ironcore-image` (metal-api focused). - diff --git a/docs/manual-oci-image-build.md b/docs/manual-oci-image-build.md deleted file mode 100644 index d5da13a..0000000 --- a/docs/manual-oci-image-build.md +++ /dev/null @@ -1,177 +0,0 @@ -# Manual OCI Image Build (Garden Linux -> ironcore-image) for metal-api machinery - -This document describes a **manual** workflow to build Garden Linux artifacts and package them as an OCI image using `ironcore-image`. - -The resulting OCI image format is what the metal-api machinery expects: kernel (`vmlinuz`), initramfs (`initrd`), optional `root.squashfs`, and optionally a UKI (`.uki`). - -## Prerequisites - -- A Linux build host (the commands below assume standard Linux tooling). -- Tools: `git`, `tar`, `cpio`, `xz`, and `ukify` (from `systemd` / `systemd-ukify`, depending on your distro). -- `ironcore-image` binary built from the `ironcore-image` repository (`make build` produces `./bin/ironcore-image`). - -## 1) Build Garden Linux artifacts - -Clone Garden Linux (this repo contains the `./build` scripts that produce the PXE boot artifacts): - -```bash -git clone https://github.com/gardenlinux/gardenlinux -cd gardenlinux -``` - -Build one of the metal targets (choose the one that fits your use case). Each command runs a build pipeline and writes outputs under `.build/`: - -```bash -# "vanilla" metal PXE artifact -./build metal_pxe - -# CAPI-focused metal artifact -./build metal-capi - -# Gardener-focused metal PXE artifact -./build metal-gardener_pxe -``` - -What to expect after `./build ...`: - -- A `.build/` directory gets created. -- Inside `.build/` you will find one or more target-specific archives (tarballs). Those archives contain the actual boot artifacts used by the metal-api machinery, typically including: - - `vmlinuz` (Linux kernel) - - `initrd` (initramfs/initramd) - - `root.squashfs` (root filesystem image) - -### Extract the PXE artifact - -In `.build/`, pick the tarball that matches your architecture and Garden Linux version and extract it: - -```bash -cd .build -tar -xvzf metal_pxe-amd64--local.pxe.tar.gz -``` - -Notes: - -- Use `tar -xvzf` for `.tar.gz` archives (gzip). -- If your archive ends with `.tar.xz`, use `tar -xvJf .tar.xz` instead. - -After extraction, locate `vmlinuz`, `initrd`, and `root.squashfs` in the extracted directory tree (the exact paths depend on the target and Garden Linux version). - -## 2) Optional: Append `root.squashfs` to `initrd` - -In some setups it is useful to embed the squashfs into the initramfs by appending an additional xz-compressed cpio archive to the existing `initrd`. This can simplify artifact handling (you ship one initrd blob that already contains the squashfs). - -Run the following in the directory where `initrd` and `root.squashfs` are located: - -```bash -cp initrd initrd.orig -echo root.squashfs | cpio -H newc -o | xz --check=crc32 >> initrd -``` - -What this does: - -- `echo root.squashfs | cpio -H newc -o` creates a tiny cpio archive (newc format) that contains the file `root.squashfs` (as a payload file). -- `xz --check=crc32` compresses that cpio archive. -- `>> initrd` appends the compressed cpio stream to the end of the existing `initrd` (initramfs). - -If you do not want this behavior, keep using the original `initrd` (or restore from `initrd.orig`) and ship `root.squashfs` separately. - -## 3) Prepare the kernel command line (`cmdline`) - -Create a file named `cmdline` next to your boot artifacts: - -```text -initrd=initrd gl.ovl=/:tmpfs gl.live=1 ip=any console=ttyS0,115200 console=tty0 earlyprintk=ttyS0,115200 consoleblank=0 ignition.firstboot=1 ignition.config.url=http:///ignition ignition.config.url.append.uuid=true ignition.platform.id=metal rd.break SYSTEMD_SULOGIN_FORCE=1 -``` - -Notes: - -- This file becomes the kernel command line used by both PXE flows and the UKI build step below. -- Replace `http:///ignition` with your actual Ignition endpoint. -- `rd.break` and `SYSTEMD_SULOGIN_FORCE=1` are typically used for debugging / emergency access. For production images, remove them unless you explicitly need them. - -## 4) Build a UKI (Unified Kernel Image) with `ukify` - -From the directory containing `vmlinuz`, `initrd`, and `cmdline`: - -```bash -ukify build \ - --linux vmlinuz \ - --initrd initrd \ - --stub "/usr/lib/systemd/boot/efi/linuxx64.efi.stub" \ - --cmdline "@cmdline" \ - --output today.uki -``` - -What this does: - -- Produces a single `.uki` artifact that bundles the kernel, initrd, and command line for UEFI-based boot flows. -- The stub path is distro-dependent; adjust `--stub` if your system stores the EFI stub elsewhere. - -## 5) Build and push the OCI image with `ironcore-image` - -### Build `ironcore-image` - -In the `ironcore-image` repository: - -```bash -make build -``` - -This should produce `./bin/ironcore-image`. - -### Prepare a standard image reference (placeholders) - -Use a versioned tag that encodes the upstream Garden Linux version and the metal flavor/variant. A simple convention that works well in practice is: - -```text -///gardenlinux:-metal--manual -``` - -Examples (replace placeholders with real values): - -- `ghcr.io/ironcore-dev/os-images/gardenlinux:1877.12-metal-manual` -- `ghcr.io/ironcore-dev/os-images/gardenlinux/capi:1877.12-metal-manual` -- `ghcr.io/ironcore-dev/os-images/gardenlinux/gardener:1877.12-metal-manual` - -Suggested `` values to keep naming consistent with the Garden Linux build target you used: - -- `vanilla` for `./build metal_pxe` -- `capi` for `./build metal-capi` -- `gardener` for `./build metal-gardener_pxe` - -### Build the image - -Copy the artifacts into the `ironcore-image` repo (or adjust paths accordingly). The example below assumes: - -- `bin/vmlinuz` -- `bin/initrd` -- `bin/root.squashfs` -- `bin/today.uki` - -Build the OCI image: - -```bash -./bin/ironcore-image build \ - --tag ///gardenlinux:-metal--manual \ - --config arch=amd64,squashfs=bin/root.squashfs,initramfs=bin/initrd,kernel=bin/vmlinuz,uki=bin/today.uki -``` - -What the `--config` values mean: - -- `arch=amd64`: target architecture for the image artifacts. -- `kernel=...`: points to the `vmlinuz` you extracted/built. -- `initramfs=...`: points to the `initrd` you extracted/built (optionally with appended squashfs). -- `squashfs=...`: points to the `root.squashfs` root filesystem image. If you fully embedded `root.squashfs` into `initrd` and do not want to ship it as a separate artifact, omit `squashfs=...`. -- `uki=...`: points to the UKI built via `ukify` (optional, but included here to support UKI boot flows). - -### Push the image - -Push the built image reference to GHCR: - -```bash -./bin/ironcore-image push \ - ///gardenlinux:-metal--manual \ - --push-sub-manifests -``` - -`--push-sub-manifests` is commonly used when publishing multi-platform images/manifests; keep it enabled if your workflow expects the sub-manifests to be pushed as well. diff --git a/hack/README.md b/hack/README.md new file mode 100644 index 0000000..1a304a8 --- /dev/null +++ b/hack/README.md @@ -0,0 +1,26 @@ +# Hack Scripts + +## build-metal-dev.sh + +If you need to build a custom image (for example to set custom kernel parameters +via the kernel cmdline) use this. First download the baremetal PXE release asset +from gardenlinux, then run the script with it as the only argument. Example: + +``` +curl -sL "https://github.com/gardenlinux/gardenlinux/releases/download/2150.0.0/baremetal_pxe-amd64-2150.0.0-eb8696b9.tar.xz" -o "baremetal_pxe-amd64-2150.0.0-eb8696b9.tar.xz" +./build-metal-dev.sh ./baremetal_pxe-amd64-2150.0.0-eb8696b9.tar.xz +``` + +If you require a custom image that is not available as a pre-built artifact you +have to build the `_pxe` release artifact yourself. + +Make sure to modify the script to your needs before doing this. It will push the +image with the `-manual` tag suffix and a randomly generated string to avoid +conflicts. + +Requirements: +* Tools: ironcore-image, ukify, cpio, xz, xxd +* Logged in to ghcr.io + +This script will inevitably drift from the workflow, have your LLM check for any +drift before using it. diff --git a/hack/build-metal-dev.sh b/hack/build-metal-dev.sh new file mode 100755 index 0000000..ff692c9 --- /dev/null +++ b/hack/build-metal-dev.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +set -euo pipefail + +REGISTRY="ghcr.io/ironcore-dev" + +usage() { + echo "Usage: $0 " + echo + echo "Builds a metal OCI image with UKI from a Garden Linux baremetal PXE" + echo "release asset and pushes it to the registry." + echo + echo "The image name, version, and architecture are derived from the asset" + echo "filename. A random suffix is appended to the tag." + echo + echo "Example:" + echo " $0 baremetal-gardener_pxe-amd64-2150.0.0-eb8696b9.tar.xz" + echo " -> ${REGISTRY}/gardenlinux/gardener:2150.0.0-metal-amd64-manual-1a2b3c4d" + echo + echo "Prerequisites: ironcore-image, ukify, cpio, xz, xxd" + exit 1 +} + +if [ $# -ne 1 ]; then + usage +fi + +ASSET="$1" + +if [ ! -f "$ASSET" ]; then + echo "Error: asset not found: $ASSET" + exit 1 +fi + +ASSET_FILENAME="$(basename "$ASSET")" +ASSET_BASE="${ASSET_FILENAME%.tar.xz}" + +# Detect architecture from the filename. +if [[ "$ASSET_FILENAME" == *-amd64-* ]]; then + ARCH="amd64" + STUB_NAME="linuxx64.efi.stub" +elif [[ "$ASSET_FILENAME" == *-arm64-* ]]; then + ARCH="arm64" + STUB_NAME="linuxaa64.efi.stub" +else + echo "Error: could not detect architecture from filename: $ASSET_FILENAME" + exit 1 +fi + +# Extract the asset pattern (everything before -$ARCH-) and map to image name. +ASSET_PATTERN="${ASSET_FILENAME%%-${ARCH}-*}" +case "$ASSET_PATTERN" in + baremetal_pxe) IMAGE_NAME="gardenlinux" ;; + baremetal-gardener_pxe) IMAGE_NAME="gardenlinux/gardener" ;; + baremetal-capi) IMAGE_NAME="gardenlinux/capi" ;; + *) + echo "Error: unknown asset pattern: $ASSET_PATTERN" + exit 1 + ;; +esac + +# Extract version: strip pattern and arch prefix, then drop the trailing hash. +# e.g. "baremetal-gardener_pxe-amd64-2150.0.0-eb8696b9" -> "2150.0.0-eb8696b9" -> "2150.0.0" +VERSION_AND_HASH="${ASSET_BASE#${ASSET_PATTERN}-${ARCH}-}" +VERSION="${VERSION_AND_HASH%-*}" + +RANDOM_SUFFIX="$(head -c4 /dev/urandom | xxd -p)" +IMAGE_TAG="${REGISTRY}/${IMAGE_NAME}:${VERSION}-metal-manual-${RANDOM_SUFFIX}" + +echo "Architecture: $ARCH" +echo "Version: $VERSION" +echo "Image: $IMAGE_TAG" + +TMPDIR="$(mktemp -d)" +trap 'rm -rf "$TMPDIR"' EXIT + +echo "Extracting $ASSET_FILENAME" +tar -xf "$ASSET" -C "$TMPDIR" + +echo "Extracting PXE tarball" +tar -xzf "$TMPDIR/${ASSET_BASE}.pxe.tar.gz" -C "$TMPDIR" + +for BIN in initrd vmlinuz root.squashfs; do + if [ ! -f "$TMPDIR/$BIN" ]; then + echo "Error: $BIN not found after extraction" + exit 1 + fi +done + +echo "Extracting EFI stub" +STUB_PATH="usr/lib/systemd/boot/efi/${STUB_NAME}" +tar -xf "$TMPDIR/${ASSET_BASE}.tar" -C "$TMPDIR" "$STUB_PATH" +EFI_STUB="$TMPDIR/$STUB_PATH" + +echo "Building initrd-uki (embedding squashfs into initrd)" +cp "$TMPDIR/initrd" "$TMPDIR/initrd-uki" +(cd "$TMPDIR" && echo root.squashfs | cpio -H newc -o | xz --check=crc32) >> "$TMPDIR/initrd-uki" + +CMDLINE="initrd=initrd gl.ovl=/:tmpfs gl.live=1 ip=any console=ttyS0,115200 console=tty0 earlyprintk=ttyS0,115200 consoleblank=0 ignition.firstboot=1 ignition.config.url=http://boot.onmetal.de:8083/ignition ignition.config.url.append.uuid=true ignition.platform.id=metal" + +echo "Building UKI" +ukify build \ + --linux "$TMPDIR/vmlinuz" \ + --initrd "$TMPDIR/initrd-uki" \ + --stub "$EFI_STUB" \ + --cmdline "$CMDLINE" \ + --output "$TMPDIR/uki.img" + +echo "Building image" +ironcore-image build \ + --tag "$IMAGE_TAG" \ + --config "arch=${ARCH},squashfs=$TMPDIR/root.squashfs,initramfs=$TMPDIR/initrd,kernel=$TMPDIR/vmlinuz,uki=$TMPDIR/uki.img" + +echo "Pushing image" +ironcore-image push --push-sub-manifests "$IMAGE_TAG" + +echo "Done: $IMAGE_TAG"