Build Talos UFS #46
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Talos UFS | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| talos_version: | |
| description: 'Talos version to build (e.g., v1.12.2)' | |
| required: true | |
| type: string | |
| concurrency: | |
| group: build-${{ github.workflow }} | |
| cancel-in-progress: true | |
| env: | |
| KERNEL_IMAGE: ghcr.io/${{ github.repository_owner }}/talos-ufs-kernel | |
| IMAGER_IMAGE: ghcr.io/${{ github.repository_owner }}/talos-ufs-imager | |
| INSTALLER_IMAGE: ghcr.io/${{ github.repository_owner }}/talos-ufs-installer | |
| jobs: | |
| build-kernel: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 360 | |
| permissions: | |
| contents: read | |
| packages: write | |
| outputs: | |
| pkgs_ref: ${{ steps.resolve-pkgs.outputs.pkgs_ref }} | |
| kernel_image: ${{ steps.meta.outputs.kernel_image }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Clone siderolabs/talos | |
| run: | | |
| git clone --depth 1 --branch "${{ inputs.talos_version }}" \ | |
| https://github.com/siderolabs/talos.git /tmp/talos | |
| - name: Resolve pkgs version from Talos Makefile | |
| id: resolve-pkgs | |
| run: | | |
| PKGS_REF=$(grep -E '^PKGS \?=' /tmp/talos/Makefile | sed 's/PKGS ?= //') | |
| echo "pkgs_ref=${PKGS_REF}" >> "$GITHUB_OUTPUT" | |
| echo "Resolved pkgs ref: ${PKGS_REF}" | |
| # Extract git commit hash from describe format (e.g., v1.12.0-32-g4f8efaf -> 4f8efaf) | |
| if [[ "${PKGS_REF}" =~ -g([0-9a-f]+)$ ]]; then | |
| PKGS_COMMIT="${BASH_REMATCH[1]}" | |
| echo "pkgs_commit=${PKGS_COMMIT}" >> "$GITHUB_OUTPUT" | |
| echo "Resolved pkgs commit: ${PKGS_COMMIT}" | |
| else | |
| echo "pkgs_commit=${PKGS_REF}" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Clone siderolabs/pkgs | |
| run: | | |
| git clone https://github.com/siderolabs/pkgs.git /tmp/pkgs | |
| cd /tmp/pkgs | |
| git checkout "${{ steps.resolve-pkgs.outputs.pkgs_commit }}" | |
| - name: Apply kernel config patch | |
| run: | | |
| git -C /tmp/pkgs apply --3way "${{ github.workspace }}/patches/kernel-config.patch" | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set kernel image tag | |
| id: meta | |
| run: | | |
| TAG="${{ inputs.talos_version }}" | |
| echo "kernel_image=${KERNEL_IMAGE}:${TAG}" >> "$GITHUB_OUTPUT" | |
| - name: Check if kernel image already exists | |
| id: check-kernel | |
| run: | | |
| if skopeo inspect "docker://${{ steps.meta.outputs.kernel_image }}" &>/dev/null; then | |
| echo "exists=true" >> "$GITHUB_OUTPUT" | |
| echo "Kernel image already exists, skipping build." | |
| else | |
| echo "exists=false" >> "$GITHUB_OUTPUT" | |
| echo "Kernel image not found, building." | |
| fi | |
| - name: Build and push kernel | |
| if: steps.check-kernel.outputs.exists == 'false' | |
| working-directory: /tmp/pkgs | |
| run: | | |
| docker buildx build \ | |
| --no-cache \ | |
| --file=Pkgfile \ | |
| --platform=linux/amd64 \ | |
| --target=kernel \ | |
| --tag="${{ steps.meta.outputs.kernel_image }}" \ | |
| --push \ | |
| . | |
| build-talos: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 120 | |
| needs: build-kernel | |
| permissions: | |
| contents: read | |
| packages: write | |
| outputs: | |
| imager_image: ${{ steps.meta.outputs.imager_image }} | |
| installer_image: ${{ steps.meta.outputs.installer_image }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Set image tags | |
| id: meta | |
| run: | | |
| TAG="${{ inputs.talos_version }}" | |
| echo "imager_image=${IMAGER_IMAGE}:${TAG}" >> "$GITHUB_OUTPUT" | |
| echo "installer_image=${INSTALLER_IMAGE}:${TAG}" >> "$GITHUB_OUTPUT" | |
| - name: Check if talos images already exist | |
| id: check-talos | |
| run: | | |
| if skopeo inspect "docker://${IMAGER_IMAGE}:${{ inputs.talos_version }}" &>/dev/null && \ | |
| skopeo inspect "docker://${INSTALLER_IMAGE}:${{ inputs.talos_version }}" &>/dev/null; then | |
| echo "exists=true" >> "$GITHUB_OUTPUT" | |
| echo "Imager and installer images already exist, skipping build." | |
| else | |
| echo "exists=false" >> "$GITHUB_OUTPUT" | |
| echo "Images not found, building." | |
| fi | |
| - name: Clone siderolabs/talos | |
| if: steps.check-talos.outputs.exists == 'false' | |
| run: | | |
| git clone --depth 1 --branch "${{ inputs.talos_version }}" \ | |
| https://github.com/siderolabs/talos.git /tmp/talos | |
| - name: Apply EFI partition size patch | |
| if: steps.check-talos.outputs.exists == 'false' | |
| run: | | |
| git -C /tmp/talos apply --3way "${{ github.workspace }}/patches/efi-partition-size.patch" | |
| - name: Install crane | |
| if: steps.check-talos.outputs.exists == 'false' | |
| run: | | |
| VERSION=$(curl -s https://api.github.com/repos/google/go-containerregistry/releases/latest | jq -r .tag_name) | |
| curl -fsSL "https://github.com/google/go-containerregistry/releases/download/${VERSION}/go-containerregistry_Linux_x86_64.tar.gz" | sudo tar -xz -C /usr/local/bin crane | |
| - name: Start local registry | |
| if: steps.check-talos.outputs.exists == 'false' | |
| run: | | |
| docker run -d -p 5000:5000 --name registry registry:2 | |
| - name: Set up Docker Buildx (with insecure local registry) | |
| if: steps.check-talos.outputs.exists == 'false' | |
| uses: docker/setup-buildx-action@v4 | |
| with: | |
| buildkitd-config-inline: | | |
| [registry."localhost:5000"] | |
| http = true | |
| insecure = true | |
| driver-opts: network=host | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build imager to local registry | |
| if: steps.check-talos.outputs.exists == 'false' | |
| working-directory: /tmp/talos | |
| run: | | |
| mkdir -p _out | |
| make imager \ | |
| PKG_KERNEL="${{ needs.build-kernel.outputs.kernel_image }}" \ | |
| PLATFORM=linux/amd64 \ | |
| REGISTRY=localhost:5000 \ | |
| PUSH=true \ | |
| INSTALLER_ARCH=amd64 | |
| - name: Build installer-base to local registry | |
| if: steps.check-talos.outputs.exists == 'false' | |
| working-directory: /tmp/talos | |
| run: | | |
| mkdir -p _out | |
| make installer-base \ | |
| PKG_KERNEL="${{ needs.build-kernel.outputs.kernel_image }}" \ | |
| PLATFORM=linux/amd64 \ | |
| REGISTRY=localhost:5000 \ | |
| PUSH=true \ | |
| INSTALLER_ARCH=amd64 | |
| - name: Build installer to local registry | |
| if: steps.check-talos.outputs.exists == 'false' | |
| working-directory: /tmp/talos | |
| env: | |
| CRANE_INSECURE: "true" | |
| run: | | |
| # Talos v1.13+ runs imager as host user (--user $(id -u):$(id -g)). | |
| # Pre-create _out so the docker volume mount doesn't auto-create it as root. | |
| mkdir -p _out | |
| make installer \ | |
| PKG_KERNEL="${{ needs.build-kernel.outputs.kernel_image }}" \ | |
| PLATFORM=linux/amd64 \ | |
| REGISTRY=localhost:5000 \ | |
| PUSH=true \ | |
| INSTALLER_ARCH=amd64 | |
| - name: Determine local image tag | |
| if: steps.check-talos.outputs.exists == 'false' | |
| id: local-tag | |
| run: | | |
| # Talos appends -dirty when source is modified | |
| cd /tmp/talos | |
| TAG=$(git describe --tag --always --dirty 2>/dev/null || echo "${{ inputs.talos_version }}-dirty") | |
| echo "tag=${TAG}" >> "$GITHUB_OUTPUT" | |
| echo "Local image tag: ${TAG}" | |
| - name: Push images to GHCR with project naming | |
| if: steps.check-talos.outputs.exists == 'false' | |
| run: | | |
| LOCAL_TAG="${{ steps.local-tag.outputs.tag }}" | |
| VERSION="${{ inputs.talos_version }}" | |
| echo "${{ secrets.GITHUB_TOKEN }}" | skopeo login ghcr.io -u "${{ github.actor }}" --password-stdin | |
| echo "Copying imager: localhost:5000 -> ${IMAGER_IMAGE}:${VERSION}" | |
| skopeo copy --all --src-tls-verify=false \ | |
| "docker://localhost:5000/siderolabs/imager:${LOCAL_TAG}" \ | |
| "docker://${IMAGER_IMAGE}:${VERSION}" | |
| echo "Copying installer: localhost:5000 -> ${INSTALLER_IMAGE}:${VERSION}" | |
| skopeo copy --all --src-tls-verify=false \ | |
| "docker://localhost:5000/siderolabs/installer:${LOCAL_TAG}" \ | |
| "docker://${INSTALLER_IMAGE}:${VERSION}" | |
| generate-iso: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| needs: [build-kernel, build-talos] | |
| permissions: | |
| contents: write | |
| packages: read | |
| issues: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Generate ISO | |
| run: | | |
| mkdir -p output | |
| docker run --rm --platform linux/amd64 \ | |
| -v "$(pwd)/output:/out" \ | |
| --privileged \ | |
| "${{ needs.build-talos.outputs.imager_image }}" \ | |
| iso --arch amd64 | |
| - name: Generate checksum | |
| run: | | |
| cd output | |
| sha256sum metal-amd64.iso > metal-amd64.iso.sha256 | |
| - name: Create GitHub Release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| TAG="${{ inputs.talos_version }}-ufs" | |
| OWNER="${{ github.repository_owner }}" | |
| gh release create "${TAG}" \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --title "Talos Linux ${{ inputs.talos_version }} with UFS Support" \ | |
| --notes "$(cat <<'NOTES_EOF' | |
| **Upstream Release Notes:** https://github.com/siderolabs/talos/releases/tag/${{ inputs.talos_version }} | |
| ## Downloads | |
| - `metal-amd64.iso` - USB boot installation media | |
| - `metal-amd64.iso.sha256` - SHA256 checksum | |
| ## Changes from Upstream | |
| - UFS drivers (ufshcd-core, ufshcd-pci) built into kernel | |
| - EFI partition size increased to 512MiB (4096-byte sector support) | |
| ## Container Images | |
| ``` | |
| ghcr.io/OWNER_PLACEHOLDER/talos-ufs-installer:VERSION_PLACEHOLDER | |
| ghcr.io/OWNER_PLACEHOLDER/talos-ufs-imager:VERSION_PLACEHOLDER | |
| ghcr.io/OWNER_PLACEHOLDER/talos-ufs-kernel:VERSION_PLACEHOLDER | |
| ``` | |
| ## Installation | |
| 1. Write ISO to USB drive | |
| 2. Disable Secure Boot on device | |
| 3. Boot from USB | |
| 4. Specify the installer in machine config: | |
| ```yaml | |
| machine: | |
| install: | |
| image: ghcr.io/OWNER_PLACEHOLDER/talos-ufs-installer:VERSION_PLACEHOLDER | |
| ``` | |
| ## Custom ISO Generation | |
| ```bash | |
| docker run --rm -t -v /dev:/dev --privileged \ | |
| ghcr.io/OWNER_PLACEHOLDER/talos-ufs-imager:VERSION_PLACEHOLDER \ | |
| metal --system-extension-image <extension-image> | |
| ``` | |
| ## Supported Hardware | |
| All x86_64 devices with PCI-connected UFS controllers. | |
| ### Verified | |
| - MINISFORUM S100-WLP (Intel Alder Lake-N, PCI ID: 8086:54ff) | |
| NOTES_EOF | |
| )" \ | |
| output/metal-amd64.iso \ | |
| output/metal-amd64.iso.sha256 | |
| # Replace placeholders in release notes | |
| gh release edit "${TAG}" \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --notes "$(gh release view "${TAG}" --repo "${GITHUB_REPOSITORY}" --json body --jq .body | \ | |
| sed "s/OWNER_PLACEHOLDER/${OWNER}/g; s/VERSION_PLACEHOLDER/${{ inputs.talos_version }}/g")" | |
| - name: Auto-close patch failure issues | |
| if: success() | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # Close any open patch-failure issues | |
| ISSUES=$(gh issue list \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --label "patch-failure" \ | |
| --state open \ | |
| --json number \ | |
| --jq '.[].number' 2>/dev/null || true) | |
| for ISSUE_NUM in ${ISSUES}; do | |
| gh issue close "${ISSUE_NUM}" \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --comment "Automatically closed: build succeeded for ${{ inputs.talos_version }}." || true | |
| done | |
| notify-failure: | |
| runs-on: ubuntu-latest | |
| needs: [build-kernel, build-talos, generate-iso] | |
| if: failure() | |
| permissions: | |
| issues: write | |
| steps: | |
| - name: Create failure issue | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VERSION="${{ inputs.talos_version }}" | |
| TITLE="Build failed for Talos ${VERSION}" | |
| # Ensure patch-failure label exists | |
| gh label create "patch-failure" \ | |
| --description "Automated build failure" \ | |
| --color "d93f0b" \ | |
| --repo "${GITHUB_REPOSITORY}" 2>/dev/null || true | |
| # Check if an open issue already exists for this version | |
| EXISTING=$(gh issue list \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --label "patch-failure" \ | |
| --state open \ | |
| --search "in:title ${TITLE}" \ | |
| --json number \ | |
| --jq 'length') | |
| if [ "${EXISTING}" -gt 0 ]; then | |
| echo "Issue already exists for ${VERSION}, skipping creation." | |
| exit 0 | |
| fi | |
| gh issue create \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --title "${TITLE}" \ | |
| --label "patch-failure" \ | |
| --body "$(cat <<EOF | |
| The automated build for Talos **${VERSION}** has failed. | |
| **Workflow run:** ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID} | |
| This usually means one of: | |
| 1. A patch failed to apply due to upstream changes | |
| 2. The kernel build failed | |
| 3. The Talos imager/installer build failed | |
| Please check the workflow logs and update the patches if needed. | |
| EOF | |
| )" |