Skip to content

Build Windows qcow2 #12

Build Windows qcow2

Build Windows qcow2 #12

Workflow file for this run

name: Build Windows qcow2
on:
workflow_dispatch:
inputs:
image_name:
description: "Image name under ghcr.io/cocoonstack/windows/"
required: true
default: "win11"
version_tag:
description: "Version tag for the image"
required: true
default: "25h2"
disk_size:
description: "Disk image size"
required: true
default: "40G"
env:
VIRTIO_WIN_URL: "https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.285-1/virtio-win-0.1.285.iso"
SSH_PORT: 2222
QCOW2_NAME: "windows-11-25h2.qcow2"
# Windows auto-logon password from autounattend.xml (base64(UTF16LE("C@c#on160Password")))
WIN_PASS: "C@c#on160"
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 180
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Free disk space
run: |
sudo rm -rf /usr/share/dotnet /usr/local/share/boost /opt/ghc \
/usr/local/lib/android /opt/hostedtoolcache/CodeQL
sudo apt-get purge -y '^ghc-' '^dotnet-' '^llvm-' 'php.*' 'mysql.*' \
'^postgresql.*' azure-cli google-cloud-cli firefox 2>/dev/null || true
sudo apt-get autoremove -y
sudo apt-get clean
df -h /
- name: Verify KVM
run: |
sudo apt-get update
sudo apt-get install -y cpu-checker
sudo kvm-ok
ls -la /dev/kvm
sudo chmod 666 /dev/kvm
- name: Install dependencies
run: |
sudo apt-get install -y --no-install-recommends \
qemu-system-x86 qemu-utils \
ovmf swtpm mtools xorriso \
openssh-client sshpass netcat-openbsd \
imagemagick
- name: Install oras
run: |
VERSION="1.2.0"
curl -LO "https://github.com/oras-project/oras/releases/download/v${VERSION}/oras_${VERSION}_linux_amd64.tar.gz"
mkdir -p oras-install
tar -xzf "oras_${VERSION}_linux_amd64.tar.gz" -C oras-install
sudo mv oras-install/oras /usr/local/bin/
rm -rf oras-install "oras_${VERSION}_linux_amd64.tar.gz"
oras version
- name: Build qcow2 with repo script
env:
WINDOWS_ISO_URL: ${{ secrets.WINDOWS_ISO_URL }}
run: |
test -n "$WINDOWS_ISO_URL" || { echo "::error::WINDOWS_ISO_URL secret not set"; exit 1; }
chmod +x scripts/build-qemu.sh
WORKDIR="$GITHUB_WORKSPACE/work/qemu-build" \
ARTIFACT_DIR="$GITHUB_WORKSPACE/artifacts/qemu-build" \
WINDOWS_ISO_URL="$WINDOWS_ISO_URL" \
VIRTIO_WIN_URL="$VIRTIO_WIN_URL" \
DISK_SIZE="${{ inputs.disk_size }}" \
QCOW2_NAME="$QCOW2_NAME" \
./scripts/build-qemu.sh
QCOW2_PATH="$(cat "$GITHUB_WORKSPACE/artifacts/qemu-build/qcow2.path")"
echo "QCOW2_PATH=$QCOW2_PATH" >> "$GITHUB_ENV"
echo "QCOW2_DIR=$(dirname "$QCOW2_PATH")" >> "$GITHUB_ENV"
echo "QCOW2_BASENAME=$(basename "$QCOW2_PATH")" >> "$GITHUB_ENV"
- name: Free disk before compression
run: |
rm -f \
"$GITHUB_WORKSPACE/work/qemu-build/windows.iso" \
"$GITHUB_WORKSPACE/work/qemu-build/windows-orig.iso" \
"$GITHUB_WORKSPACE/work/qemu-build/virtio-win.iso"
df -h /
- name: Compress qcow2
run: |
echo "Original: $(du -sh "$QCOW2_PATH" | cut -f1)"
qemu-img convert -O qcow2 -c "$QCOW2_PATH" "${QCOW2_PATH}.compressed"
mv "${QCOW2_PATH}.compressed" "$QCOW2_PATH"
echo "Compressed: $(du -sh "$QCOW2_PATH" | cut -f1)"
- name: Split qcow2 into OCI blob chunks
# ~1.9 GiB per chunk to stay well under any GHCR blob limit
run: |
cd "$QCOW2_DIR"
split -b 1900M -d --additional-suffix=.qcow2.part "$QCOW2_BASENAME" "${QCOW2_BASENAME}."
rm "$QCOW2_BASENAME"
ls -lh "${QCOW2_BASENAME}."*
sha256sum "${QCOW2_BASENAME}."* > SHA256SUMS
cat SHA256SUMS
- name: Push to GHCR via ORAS
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "$GITHUB_TOKEN" | oras login ghcr.io -u "${{ github.actor }}" --password-stdin
REPO_LC=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
TAG="${{ inputs.version_tag }}-$(date -u +%Y%m%d)"
REF="ghcr.io/${REPO_LC}/${{ inputs.image_name }}:${TAG}"
cd "$QCOW2_DIR"
# Assemble --file arguments for each part with media type
ARGS=()
for part in "${QCOW2_BASENAME}."*; do
ARGS+=("${part}:application/vnd.cocoonstack.disk.qcow2.part")
done
ARGS+=("SHA256SUMS:text/plain")
oras push "$REF" \
--artifact-type "application/vnd.cocoonstack.os-image.v1+json" \
--annotation "org.opencontainers.image.source=https://github.com/${{ github.repository }}" \
--annotation "org.opencontainers.image.revision=${{ github.sha }}" \
--annotation "cocoonstack.os.name=windows" \
--annotation "cocoonstack.os.version=${{ inputs.version_tag }}" \
--annotation "cocoonstack.disk.format=qcow2" \
--annotation "cocoonstack.disk.reassemble=cat ${QCOW2_BASENAME}.*.qcow2.part > ${QCOW2_BASENAME}" \
"${ARGS[@]}"
# Also push "latest" tag for the version family
oras tag "$REF" "${{ inputs.version_tag }}"
echo "Pushed: $REF"
echo "Pushed: ghcr.io/${REPO_LC}/${{ inputs.image_name }}:${{ inputs.version_tag }}"
- name: Upload debug artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: debug-${{ github.run_id }}
if-no-files-found: ignore
retention-days: 7
path: |
artifacts/qemu-build/**
- name: Cleanup
if: always()
run: |
sudo rm -rf "$GITHUB_WORKSPACE/work" "$GITHUB_WORKSPACE/artifacts"