Skip to content

Repository rebrand

Repository rebrand #38

Workflow file for this run

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"