Build Windows qcow2 #12
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 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" |