Skip to content

Build Utile OS 26 ISO #23

Build Utile OS 26 ISO

Build Utile OS 26 ISO #23

Workflow file for this run

name: Build Codeswallop Linux 26.04 ISO
on:
push:
branches: [ "main" ]
workflow_dispatch:
jobs:
build-custom-iso:
runs-on: ubuntu-latest
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#codeswallop-linux-}
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
- 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/
- name: Inspect and Prune Extended Installation
run: |
echo "--- ORIGINAL install-sources.yaml ---"
cat custom-iso/casper/install-sources.yaml
echo "--- END OF ORIGINAL install-sources.yaml ---"
# 1. Delete the standard squashfs and its associated manifests
echo "Pruning minimal.standard files to save ~600MB..."
sudo rm -fv custom-iso/casper/minimal.standard.*
# 2. Remove the "Extended Installation" profile from the installer UI
# yq is pre-installed on ubuntu-latest runners
sudo yq eval 'del(.sources[] | select(.id == "ubuntu-desktop"))' -i custom-iso/casper/install-sources.yaml
echo "--- UPDATED install-sources.yaml ---"
cat custom-iso/casper/install-sources.yaml
- 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 Disk Metadata
run: |
# Matches: Disk Name and Release
echo 'Codeswallop 26.04 amd64 "Resolute Raccoon" - Release amd64 (20260430)' | sudo tee custom-iso/.disk/info
# Matches: Release URL
echo 'https://github.com/Proman4713/Codeswallop-Linux/releases/tag/${{ env.TAG_NAME }}' | sudo tee custom-iso/.disk/release_notes_url
# Set the Version marker
echo '2026.04.30' | sudo tee custom-iso/.disk/codeswallop_version
- 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 "Codeswallop 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 Custom ISO to GitHub Releases
uses: softprops/action-gh-release@v3.0.0
with:
tag_name: ${{ env.TAG_NAME }}
name: Codeswallop Linux ${{ env.TAG_NAME }}
files: |
${{ env.ISO_NAME }}
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') }}