Repository rebrand #38
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 Utile OS 26.04 ISO | |
| on: | |
| push: | |
| branches: [ "main" ] | |
| workflow_dispatch: | |
| jobs: | |
| build-custom-iso: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout Repository | |
| uses: actions/checkout@v6.0.2 | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6.4.0 | |
| with: | |
| node-version: '24' | |
| cache: 'pnpm' | |
| - name: Install OS Dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y squashfs-tools xorriso mtools fdisk rsync | |
| - name: Compile Bash Script (Node.js) | |
| env: | |
| # If this was triggered by a tag (release), set to release. Otherwise, nightly will automatically be used. | |
| BUILD_TYPE: ${{ startsWith(github.ref, 'refs/tags/') && 'release' || '' }} | |
| run: | | |
| # Install any dependencies your node script needs | |
| pnpm install | |
| # Run the compilation script | |
| SH_NAME=$(node . | tail -n 1) | |
| TEMP_NAME=${SH_NAME#utile-os-} | |
| TAG_NAME=${TEMP_NAME%.sh} | |
| ISO_NAME="${SH_NAME%.sh}.iso" | |
| echo "SH_NAME=$SH_NAME" >> $GITHUB_ENV | |
| echo "ISO_NAME=$ISO_NAME" >> $GITHUB_ENV | |
| echo "TAG_NAME=$TAG_NAME" >> $GITHUB_ENV | |
| # Verify the output exists | |
| ls -la "dist/$SH_NAME" | |
| - name: Download Base Ubuntu 26.04 ISO | |
| run: | | |
| wget -q -O base-ubuntu-26.04.iso https://releases.ubuntu.com/resolute/ubuntu-26.04-desktop-amd64.iso | |
| - name: Extract Base ISO | |
| run: | | |
| mkdir -p source-iso custom-iso squashfs-root | |
| # Mount the original ISO and copy its contents | |
| sudo mount -o loop base-ubuntu-26.04.iso source-iso | |
| rsync -a source-iso/ custom-iso/ | |
| sudo umount source-iso | |
| # TODO: Use CUBIC's trick to merge the squashfs | |
| - name: Extract Root Filesystem (SquashFS) | |
| run: | | |
| # Find the largest .squashfs file in the casper directory dynamically | |
| SQUASHFS_FILE=$(ls -S custom-iso/casper/*.squashfs | head -n 1) | |
| echo "Found root filesystem: $SQUASHFS_FILE" | |
| # Save the exact path to GitHub ENV so the re-compress step knows what to overwrite | |
| echo "SQUASHFS_PATH=$SQUASHFS_FILE" >> $GITHUB_ENV | |
| # Extract it | |
| sudo unsquashfs -f -d squashfs-root "$SQUASHFS_FILE" | |
| - name: Find available squashed filesystems | |
| run: | | |
| ls -la custom-iso/casper/ | |
| # Symlink trick borrowed from CUBIC (https://github.com/PJ-Singh-001/Cubic) | |
| - name: Inspect and Prune Extended Installation | |
| run: | | |
| echo "Replacing the standard squashfs with a symlink to save space without breaking the installer..." | |
| PRIMARY_BASENAME=$(basename "${{ env.SQUASHFS_PATH }}") | |
| STANDARD_FILE=$(ls custom-iso/casper/*standard.squashfs 2>/dev/null | head -n 1) | |
| if [ -n "$STANDARD_FILE" ]; then | |
| STANDARD_BASENAME=$(basename "$STANDARD_FILE") | |
| echo "Found standard squashfs: $STANDARD_BASENAME" | |
| # 1. Delete the heavy extended payload | |
| sudo rm -f "custom-iso/casper/$STANDARD_BASENAME" | |
| # 2. Delete the old size and manifest files associated with it | |
| sudo rm -f "custom-iso/casper/${STANDARD_BASENAME%.squashfs}.size" | |
| sudo rm -f "custom-iso/casper/${STANDARD_BASENAME%.squashfs}.manifest" | |
| # 3. Create the symlinks directly inside the casper directory | |
| cd custom-iso/casper/ | |
| # Symlink the squashfs | |
| sudo ln -s "$PRIMARY_BASENAME" "$STANDARD_BASENAME" | |
| # Symlink the size file so the installer's disk space check doesn't fail or show wrong numbers | |
| sudo ln -s "${PRIMARY_BASENAME%.squashfs}.size" "${STANDARD_BASENAME%.squashfs}.size" | |
| cd ../../ | |
| echo "Successfully symlinked $STANDARD_BASENAME to $PRIMARY_BASENAME" | |
| ls -la custom-iso/casper/*standard* | |
| else | |
| echo "No standard squashfs found, skipping symlink trick." | |
| fi | |
| - name: Inject GRUB theme into ISO and installed system | |
| run: | | |
| # Installed System | |
| sudo mkdir -p squashfs-root/usr/share/grub/themes/utile | |
| sudo cp grub/theme.txt squashfs-root/usr/share/grub/themes/utile/ | |
| sudo mkdir -p squashfs-root/usr/share/grub/themes/utile/icons | |
| sudo cp -r grub/icons/* squashfs-root/usr/share/grub/themes/utile/icons 2>/dev/null || true | |
| sudo mkdir -p squashfs-root/usr/share/grub/themes/utile/fonts | |
| sudo cp -r grub/*.pf2 squashfs-root/usr/share/grub/themes/utile/fonts 2>/dev/null || true | |
| # Live USB | |
| sudo mkdir -p custom-iso/boot/grub/themes/utile | |
| sudo cp -r grub/theme.txt custom-iso/boot/grub/themes/utile/ | |
| sudo mkdir -p custom-iso/boot/grub/themes/utile/icons | |
| sudo cp -r grub/icons/* custom-iso/boot/grub/themes/utile/icons 2>/dev/null || true | |
| sudo mkdir -p custom-iso/boot/grub/fonts | |
| sudo cp -r grub/*.pf2 custom-iso/boot/grub/fonts/ 2>/dev/null || true | |
| # Replace Live USB grub config | |
| sudo rm -f custom-iso/boot/grub/grub.cfg 2>/dev/null || true | |
| sudo cp grub/grub.installer.cfg custom-iso/boot/grub/grub.cfg | |
| - name: Setup Chroot Environment & Run Custom Script | |
| run: | | |
| # Copy script | |
| sudo cp dist/${{ env.SH_NAME }} squashfs-root/opt/ | |
| sudo chmod +x squashfs-root/opt/${{ env.SH_NAME }} | |
| # Bind mounts | |
| sudo mount --bind /dev squashfs-root/dev | |
| sudo mount --bind /run squashfs-root/run | |
| sudo mount -t proc /proc squashfs-root/proc | |
| sudo mount -t sysfs /sys squashfs-root/sys | |
| # Fix for the "same file" error: Remove and copy with dereference | |
| sudo rm -f squashfs-root/etc/resolv.conf | |
| sudo cp -L /etc/resolv.conf squashfs-root/etc/resolv.conf | |
| # Execute in chroot | |
| sudo chroot squashfs-root /bin/bash -xlc "/opt/${{ env.SH_NAME }}" | |
| # Proper Cleanup (Unmount in reverse order) | |
| sudo umount squashfs-root/sys | |
| sudo umount squashfs-root/proc | |
| sudo umount squashfs-root/run | |
| sudo umount squashfs-root/dev | |
| - name: Apply Custom System Metadata | |
| run: | | |
| # Matches: Disk Name and Release | |
| echo 'Utile OS 26.04 "Abstract Assembly" - Release amd64 (${{ env.TAG_NAME }})' | sudo tee custom-iso/.disk/info | |
| # Matches: Release URL | |
| echo 'https://github.com/Proman4713/Utile-OS/releases/tag/${{ env.TAG_NAME }}' | sudo tee custom-iso/.disk/release_notes_url | |
| - name: Re-compress Root Filesystem | |
| run: | | |
| # Remove the old base filesystem using the path we found earlier | |
| sudo rm "${{ env.SQUASHFS_PATH }}" | |
| # Compress the modified filesystem back to the exact same file name | |
| sudo mksquashfs squashfs-root "${{ env.SQUASHFS_PATH }}" -comp zstd -b 1M -noappend | |
| # Update the matching filesystem size manifest (.size file) | |
| SIZE_FILE="${{ env.SQUASHFS_PATH }}" | |
| SIZE_FILE="${SIZE_FILE%.squashfs}.size" | |
| printf $(sudo du -sx --block-size=1 squashfs-root | cut -f1) | sudo tee "$SIZE_FILE" | |
| - name: Update MD5 Checksums | |
| run: | | |
| cd custom-iso | |
| sudo rm -f md5sum.txt | |
| # Calculate new checksums for integrity verification during boot | |
| sudo find -type f -print0 | sudo xargs -0 md5sum | grep -v isolinux/boot.cat | sudo tee md5sum.txt | |
| cd .. | |
| - name: Generate Final ISO | |
| run: | | |
| # 1. Extract the Master Boot Record (MBR) | |
| dd if=base-ubuntu-26.04.iso bs=512 count=1 of=isohdpfx.bin | |
| # 2. Extract the hidden EFI partition | |
| # Using a more robust selector for the EFI partition type | |
| EFI_START=$(sfdisk -J base-ubuntu-26.04.iso | jq -r '.partitiontable.partitions[] | select(.type=="C12A7328-F81F-11D2-BA4B-00A0C93EC93B" or .type=="ef") | .start' | head -n 1) | |
| EFI_SIZE=$(sfdisk -J base-ubuntu-26.04.iso | jq -r '.partitiontable.partitions[] | select(.type=="C12A7328-F81F-11D2-BA4B-00A0C93EC93B" or .type=="ef") | .size' | head -n 1) | |
| dd if=base-ubuntu-26.04.iso bs=512 skip=$EFI_START count=$EFI_SIZE of=efi.img | |
| mkdir -p custom-iso/boot/grub | |
| sudo mv efi.img custom-iso/boot/grub/efi.img | |
| # 3. Generate the ISO with Large File Support (-iso-level 3) | |
| sudo xorriso -as mkisofs \ | |
| -iso-level 3 \ | |
| -full-iso9660-filenames \ | |
| -volid "Utile OS 26.04 amd64" \ | |
| -eltorito-boot boot/grub/i386-pc/eltorito.img \ | |
| -no-emul-boot -boot-load-size 4 -boot-info-table \ | |
| --eltorito-catalog boot.catalog \ | |
| -eltorito-alt-boot \ | |
| -e boot/grub/efi.img \ | |
| -no-emul-boot \ | |
| -isohybrid-mbr isohdpfx.bin \ | |
| -isohybrid-gpt-basdat \ | |
| -append_partition 2 0xef custom-iso/boot/grub/efi.img \ | |
| -output ${{ env.ISO_NAME }} \ | |
| -graft-points \ | |
| "/=$(pwd)/custom-iso" | |
| - name: Upload script to GitHub Releases | |
| uses: softprops/action-gh-release@v3.0.0 | |
| with: | |
| tag_name: ${{ env.TAG_NAME }} | |
| name: Utile OS ${{ env.TAG_NAME }} | |
| files: | | |
| dist/${{ env.SH_NAME }} | |
| # Automatically mark as pre-release if it's a nightly or beta | |
| prerelease: ${{ contains(env.TAG_NAME, 'nightly') || contains(env.TAG_NAME, 'beta') }} | |
| - name: Upload ISO to Gofile.io | |
| env: | |
| GOFILE_KEY: ${{ secrets.GOFILE_KEY }} | |
| run: | | |
| curl --progress-bar --retry 3 -X POST -F "file=@${{ env.ISO_NAME }}" -F "folderId=d96e59a5-9121-4b18-9bb4-4a6228e66bcd" -H "Authorization: Bearer ${{ env.GOFILE_KEY }}" "https://upload.gofile.io/contents/uploadfile" | |
| # calculate sha256 checksum | |
| sha256sum ${{ env.ISO_NAME }} > ${{ env.ISO_NAME }}.sha256sum | |
| echo "sha256sum: $(cat ${{ env.ISO_NAME }}.sha256sum)" | |
| # Upload checksum to Gofile | |
| curl --progress-bar --retry 3 -X POST -F "file=@${{ env.ISO_NAME }}.sha256sum" -F "folderId=d96e59a5-9121-4b18-9bb4-4a6228e66bcd" -H "Authorization: Bearer ${{ env.GOFILE_KEY }}" "https://upload.gofile.io/contents/uploadfile" | |