Linux for Radio Astronomy

Combining two Ubuntu ISO disc images so that the resulting image contains all the software packages from both.

Yes ✅ — it is possible to combine two Ubuntu ISO images so that the resulting image contains all the software packages from both.

However, this isn’t done by simply “merging” ISOs directly. Instead, the reliable approach is to:

  1. Extract one ISO as a base
  2. Chroot (or mount) into it
  3. Copy or install the software from the second ISO
  4. Rebuild a new bootable ISO

Here’s a practical outline of how you can do it:


🧰 What you’ll need

  • A working Linux system (Ubuntu works fine)
  • Both ISO files (e.g. ubuntuA.iso and ubuntuB.iso)
  • At least 10 GB free disk space
  • Packages:
·         sudo apt install squashfs-tools genisoimage xorriso rsync chroot

🪄 Step 1: Extract the base ISO

mkdir ~/iso-base ~/iso-edit
sudo mount -o loop ubuntuA.iso ~/iso-base
rsync -a ~/iso-base/ ~/iso-edit/
sudo umount ~/iso-base

🧩 Step 2: Extract and prepare the filesystem

cd ~/iso-edit
mkdir squashfs
sudo unsquashfs casper/filesystem.squashfs
sudo mv squashfs-root edit

🛠️ Step 3: Chroot into the filesystem

sudo mount --bind /dev/ edit/dev
sudo chroot edit

Inside the chroot:

mount -t proc none /proc
mount -t sysfs none /sys
mount -t devpts none /dev/pts
export HOME=/root
export LC_ALL=C

📦 Step 4: Add software from the second image

Mount the second ISO:

exit # leave chroot temporarily
mkdir ~/iso-second
sudo mount -o loop ubuntuB.iso ~/iso-second

You have two main choices now:

  • Option A: Copy .deb packages or additional files from ubuntuB into the edit chroot and install with dpkg -i.
  • Option B: Add the same repositories or PPAs used in ubuntuB and install the packages with apt install.

Re-enter chroot to install:

sudo chroot edit
dpkg -i /path/to/debs/*.deb   # or apt install package names
apt -f install               # fix dependencies if needed

Then clean up:

apt clean
rm -rf /tmp/*
umount /proc /sys /dev/pts
exit

🧼 Step 5: Rebuild the squashfs filesystem

sudo mksquashfs edit casper/filesystem.squashfs -b 1048576 -comp xz

Update the manifest:

sudo chroot edit dpkg-query -W --showformat='${Package} ${Version}\n' | sudo tee casper/filesystem.manifest
cp casper/filesystem.manifest casper/filesystem.manifest-desktop

📀 Step 6: Rebuild the ISO

cd ~/iso-edit
sudo mkisofs -D -r -V "Custom_Ubuntu" -cache-inodes -J -l \
  -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot \
  -boot-load-size 4 -boot-info-table -o ../custom-ubuntu.iso .

Alternatively (more modern):

xorriso -as mkisofs -r -V "Custom_Ubuntu" -J -l -b isolinux/isolinux.bin \
-c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table \
-isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin -o ../custom-ubuntu.iso .

🧪 Step 7: Test your ISO

You can test it without burning:

qemu-system-x86_64 -cdrom ../custom-ubuntu.iso -m 4096

Result: a single custom Ubuntu ISO with all the software from both originals.


👉 Alternative (easier) option if you don’t need to distribute a bootable ISO:
Install one ISO, then use:

dpkg --get-selections > packages.list

on the second system, copy that file to the first, then:

sudo dpkg --set-selections < packages.list
sudo apt-get dselect-upgrade

This reproduces the installed software set without rebuilding the ISO.


Would you like me to give you a minimal automated script to merge the ISOs into one custom ISO (to avoid typing all these steps manually)?

Excellent 👍

Below is a bash script that will automatically:

  • Mount the first ISO as a base
  • Extract and prepare the filesystem
  • Mount the second ISO so you can grab .deb packages or run a script inside it
  • Drop you into a chroot environment to install software from ISO #2
  • Rebuild the filesystem and ISO

🧰 Requirements (install once)

sudo apt update
sudo apt install -y squashfs-tools genisoimage xorriso rsync chroot

Make sure your ISO filenames are like:

ubuntuA.iso   # base image
ubuntuB.iso   # second image with extra software

📜 merge-ubuntu-iso.sh

#!/usr/bin/env bash
set -e
 
BASE_ISO="ubuntuA.iso"
SECOND_ISO="ubuntuB.iso"
WORKDIR="$HOME/ubuntu-iso-work"
EDITDIR="$WORKDIR/iso-edit"
MOUNTBASE="$WORKDIR/iso-base"
MOUNTSECOND="$WORKDIR/iso-second"
 
echo "=== Setting up work directories ==="
mkdir -p "$WORKDIR" "$EDITDIR" "$MOUNTBASE" "$MOUNTSECOND"
 
echo "=== Mounting base ISO ==="
sudo mount -o loop "$BASE_ISO" "$MOUNTBASE"
rsync -a "$MOUNTBASE/" "$EDITDIR/"
sudo umount "$MOUNTBASE"
 
echo "=== Extracting squashfs filesystem ==="
cd "$EDITDIR"
mkdir -p squashfs
sudo unsquashfs casper/filesystem.squashfs
sudo mv squashfs-root edit
 
echo "=== Mounting second ISO (for extra software) ==="
sudo mount -o loop "$SECOND_ISO" "$MOUNTSECOND"
echo "Second ISO mounted at $MOUNTSECOND"
echo "You can copy .deb packages from here later."
 
echo "=== Binding system directories for chroot ==="
sudo mount --bind /dev edit/dev
sudo chroot edit /bin/bash -c "
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devpts none /dev/pts
export HOME=/root
export LC_ALL=C
 
echo
echo '*** You are now inside the chroot environment ***'
echo 'You can install packages from the second ISO, e.g.:'
echo '  dpkg -i /mnt/path/to/debs/*.deb'
echo 'or use apt to install anything else.'
echo 'When done, type:  exit'
echo
 
/bin/bash
umount /proc /sys /dev/pts
"
 
echo "=== Unmounting chroot bind mounts ==="
sudo umount edit/dev
sudo umount "$MOUNTSECOND"
 
echo "=== Rebuilding squashfs filesystem ==="
sudo rm casper/filesystem.squashfs
sudo mksquashfs edit casper/filesystem.squashfs -b 1048576 -comp xz
 
echo "=== Updating package manifest ==="
sudo chroot edit dpkg-query -W --showformat='${Package} ${Version}\n' | sudo tee casper/filesystem.manifest
cp casper/filesystem.manifest casper/filesystem.manifest-desktop
 
echo "=== Building new ISO ==="
cd "$EDITDIR"
ISO_OUT="$WORKDIR/custom-ubuntu.iso"
 
sudo xorriso -as mkisofs -r -V "Custom_Ubuntu" -J -l \
  -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot \
  -boot-load-size 4 -boot-info-table \
  -isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin \
  -o "$ISO_OUT" .
 
echo "✅ ISO build complete: $ISO_OUT"
echo "You can test it with: qemu-system-x86_64 -cdrom $ISO_OUT -m 4096"

🏃 How to run it

  1. Save as merge-ubuntu-iso.sh
  2. Make executable:
3.  chmod +x merge-ubuntu-iso.sh
  1. Run it:
5.  ./merge-ubuntu-iso.sh
  1. When the script drops you into the chroot prompt:
    • Install the software from the second ISO, e.g.:
o    cp /mnt/path/to/debs/*.deb /tmp/
o    dpkg -i /tmp/*.deb
o    apt -f install
  1. When done, type exit.

The script will then rebuild the filesystem and produce:

~/ubuntu-iso-work/custom-ubuntu.iso

👉 Tip: If your second ISO doesn’t contain .deb files but just a pre-installed system, you can extract the list of packages from it with:

dpkg --get-selections > packages.list

and then in the chroot of the base image:

dpkg --set-selections < /path/to/packages.list
apt-get dselect-upgrade

Would you like me to modify the script so it automatically installs the packages from the second ISO (if .deb files are present) rather than dropping you into the chroot manually? (That would fully automate the merge.)

Perfect 👌

Here’s the fully automated version of the script.
This will:

  • Mount both ISOs
  • Extract the base filesystem
  • Automatically copy any .deb packages found on the second ISO
  • Install them inside the chroot automatically
  • Clean up
  • Rebuild the filesystem and generate the final ISO

📜 merge-ubuntu-iso-auto.sh

#!/usr/bin/env bash
set -e
 
BASE_ISO="ubuntuA.iso"
SECOND_ISO="ubuntuB.iso"
WORKDIR="$HOME/ubuntu-iso-work"
EDITDIR="$WORKDIR/iso-edit"
MOUNTBASE="$WORKDIR/iso-base"
MOUNTSECOND="$WORKDIR/iso-second"
DEBDIR="$WORKDIR/debs"
 
echo "=== 🧰 Setting up work directories ==="
mkdir -p "$WORKDIR" "$EDITDIR" "$MOUNTBASE" "$MOUNTSECOND" "$DEBDIR"
 
echo "=== 💿 Mounting base ISO ==="
sudo mount -o loop "$BASE_ISO" "$MOUNTBASE"
rsync -a "$MOUNTBASE/" "$EDITDIR/"
sudo umount "$MOUNTBASE"
 
echo "=== 📦 Extracting squashfs filesystem ==="
cd "$EDITDIR"
mkdir -p squashfs
sudo unsquashfs casper/filesystem.squashfs
sudo mv squashfs-root edit
 
echo "=== 💿 Mounting second ISO ==="
sudo mount -o loop "$SECOND_ISO" "$MOUNTSECOND"
 
echo "=== 📁 Collecting .deb packages from second ISO ==="
find "$MOUNTSECOND" -type f -name "*.deb" -exec cp {} "$DEBDIR" \;
 
if [ -z "$(ls -A $DEBDIR)" ]; then
    echo "⚠️ No .deb packages found on second ISO. Skipping auto-install."
else
    echo "✅ Found $(ls -1 $DEBDIR | wc -l) .deb packages."
fi
 
echo "=== 🛠️ Binding system directories for chroot ==="
sudo mount --bind /dev edit/dev
sudo chroot edit /bin/bash -c "
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devpts none /dev/pts
export HOME=/root
export LC_ALL=C
 
if [ -d /debs ]; then
    echo '=== 📦 Installing packages from /debs ==='
    dpkg -i /debs/*.deb || true
    apt-get -f install -y
    echo '=== 🧹 Cleaning up apt cache ==='
    apt-get clean
fi
 
umount /proc /sys /dev/pts
exit
"
 
echo "=== 🧽 Cleaning up chroot mounts ==="
sudo umount edit/dev
sudo umount "$MOUNTSECOND"
 
# Copy debs into chroot before the install step
echo "=== 📂 Copying .deb packages into chroot ==="
sudo mkdir -p edit/debs
sudo cp $DEBDIR/*.deb edit/debs 2>/dev/null || true
 
echo "=== 🧱 Rebuilding squashfs filesystem ==="
sudo rm casper/filesystem.squashfs
sudo mksquashfs edit casper/filesystem.squashfs -b 1048576 -comp xz
 
echo "=== 📝 Updating package manifest ==="
sudo chroot edit dpkg-query -W --showformat='${Package} ${Version}\n' | sudo tee casper/filesystem.manifest
cp casper/filesystem.manifest casper/filesystem.manifest-desktop
 
echo "=== 🔥 Building final ISO ==="
cd "$EDITDIR"
ISO_OUT="$WORKDIR/custom-ubuntu.iso"
 
sudo xorriso -as mkisofs -r -V "Custom_Ubuntu" -J -l \
  -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot \
  -boot-load-size 4 -boot-info-table \
  -isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin \
  -o "$ISO_OUT" .
 
echo "✅ All done!"
echo "Final ISO: $ISO_OUT"
echo "💻 Test with: qemu-system-x86_64 -cdrom $ISO_OUT -m 4096"

🪛 Usage instructions

  1. Save the script:
2.  nano merge-ubuntu-iso-auto.sh

(Paste the script above)

  1. Make it executable:
4.  chmod +x merge-ubuntu-iso-auto.sh
  1. Run it:
6.  ./merge-ubuntu-iso-auto.sh
  1. It will:
    • Mount ubuntuA.iso and use it as the base
    • Mount ubuntuB.iso and look for .deb packages
    • Copy and install them automatically
    • Rebuild the ISO
    • Save it as:
o    ~/ubuntu-iso-work/custom-ubuntu.iso

Tip:
If your second ISO doesn’t contain .deb files but a full pre-installed system, you can:

  • Boot it in a VM,
  • Run:
·         dpkg --get-selections > packages.list
  • Then use that packages.list in the script to install via apt-get.
    I can modify the script to support that too if you like.

Would you like me to add optional support for packages.list as a fallback if no .deb files are found? (useful for customized systems) 🧠💿

Excellent — adding a packages.list fallback is a smart move 💡

This will make the script handle both cases automatically:

  • ✅ If .deb packages are found on the second ISO → install them
  • 📜 If no .deb packages are found but a packages.list file is present → install those packages via apt-get
  • 💤 If neither is found → it just rebuilds the base ISO unchanged.

Here’s the final version of the script:


📜 merge-ubuntu-iso-auto-full.sh

#!/usr/bin/env bash
set -e
 
# --- CONFIG ---
BASE_ISO="ubuntuA.iso"
SECOND_ISO="ubuntuB.iso"
WORKDIR="$HOME/ubuntu-iso-work"
EDITDIR="$WORKDIR/iso-edit"
MOUNTBASE="$WORKDIR/iso-base"
MOUNTSECOND="$WORKDIR/iso-second"
DEBDIR="$WORKDIR/debs"
 
# --- PREPARE WORKSPACE ---
echo "=== 🧰 Setting up work directories ==="
mkdir -p "$WORKDIR" "$EDITDIR" "$MOUNTBASE" "$MOUNTSECOND" "$DEBDIR"
 
# --- MOUNT & COPY BASE ISO ---
echo "=== 💿 Mounting base ISO ==="
sudo mount -o loop "$BASE_ISO" "$MOUNTBASE"
rsync -a "$MOUNTBASE/" "$EDITDIR/"
sudo umount "$MOUNTBASE"
 
# --- EXTRACT ROOT FILESYSTEM ---
echo "=== 📦 Extracting squashfs filesystem ==="
cd "$EDITDIR"
mkdir -p squashfs
sudo unsquashfs casper/filesystem.squashfs
sudo mv squashfs-root edit
 
# --- MOUNT SECOND ISO ---
echo "=== 💿 Mounting second ISO ==="
sudo mount -o loop "$SECOND_ISO" "$MOUNTSECOND"
 
# --- COLLECT DEB PACKAGES ---
echo "=== 📁 Searching second ISO for .deb packages ==="
find "$MOUNTSECOND" -type f -name "*.deb" -exec cp {} "$DEBDIR" \; || true
DEB_COUNT=$(ls -1 "$DEBDIR" 2>/dev/null | wc -l)
 
# --- CHECK FOR PACKAGES.LIST ---
echo "=== 📜 Checking for packages.list ==="
if [ -f "$MOUNTSECOND/packages.list" ]; then
    cp "$MOUNTSECOND/packages.list" "$WORKDIR/"
    LIST_FOUND=1
else
    LIST_FOUND=0
fi
 
echo "=== 🧱 Preparing chroot environment ==="
sudo mkdir -p edit/debs
if [ "$DEB_COUNT" -gt 0 ]; then
    sudo cp $DEBDIR/*.deb edit/debs
fi
if [ "$LIST_FOUND" -eq 1 ]; then
    sudo cp "$WORKDIR/packages.list" edit/
fi
 
sudo mount --bind /dev edit/dev
 
# --- CHROOT SCRIPT ---
echo "=== 🧠 Entering chroot to install extra software ==="
sudo chroot edit /bin/bash -c "
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devpts none /dev/pts
export HOME=/root
export LC_ALL=C
 
# If .deb files are present, install them
if [ -d /debs ] && [ \"\$(ls -A /debs 2>/dev/null)\" ]; then
    echo '=== 📦 Installing local .deb packages ==='
    dpkg -i /debs/*.deb || true
    apt-get -f install -y
fi
 
# If packages.list is present, use it to install packages
if [ -f /packages.list ]; then
    echo '=== 📜 Installing packages from packages.list ==='
    apt-get update
    dpkg --set-selections < /packages.list
    apt-get dselect-upgrade -y
fi
 
apt-get clean
rm -rf /debs /packages.list
umount /proc /sys /dev/pts
exit
"
 
# --- CLEANUP ---
echo "=== 🧽 Cleaning up mounts ==="
sudo umount edit/dev
sudo umount "$MOUNTSECOND"
 
# --- REBUILD FILESYSTEM ---
echo "=== 🧱 Rebuilding squashfs filesystem ==="
sudo rm casper/filesystem.squashfs
sudo mksquashfs edit casper/filesystem.squashfs -b 1048576 -comp xz
 
# --- UPDATE MANIFEST ---
echo "=== 📝 Updating package manifest ==="
sudo chroot edit dpkg-query -W --showformat='${Package} ${Version}\n' | sudo tee casper/filesystem.manifest
cp casper/filesystem.manifest casper/filesystem.manifest-desktop
 
# --- REBUILD ISO ---
echo "=== 🔥 Building final ISO ==="
cd "$EDITDIR"
ISO_OUT="$WORKDIR/custom-ubuntu.iso"
 
sudo xorriso -as mkisofs -r -V "Custom_Ubuntu" -J -l \
  -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot \
  -boot-load-size 4 -boot-info-table \
  -isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin \
  -o "$ISO_OUT" .
 
echo
echo "✅ All done!"
echo "Final ISO: $ISO_OUT"
echo "💻 Test with: qemu-system-x86_64 -cdrom $ISO_OUT -m 4096"
echo
if [ "$DEB_COUNT" -gt 0 ]; then
    echo "📦 Installed $DEB_COUNT local .deb packages from second ISO."
fi
if [ "$LIST_FOUND" -eq 1 ]; then
    echo "📜 Installed additional packages from packages.list."
fi

🪛 How to use this script

  1. Save the file:
2.  nano merge-ubuntu-iso-auto-full.sh

Paste the script above.

  1. Make it executable:
4.  chmod +x merge-ubuntu-iso-auto-full.sh
  1. Ensure:
    • ubuntuA.iso is your base Ubuntu image
    • ubuntuB.iso is the second image containing either:
      • .deb packages (in any folder), or
      • a file named packages.list at the root of the ISO
  2. Run:
7.  ./merge-ubuntu-iso-auto-full.sh
  1. Wait — when finished you’ll have:
9.  ~/ubuntu-iso-work/custom-ubuntu.iso

🧪 Optional: Generating packages.list yourself

If your second ISO is a pre-installed system rather than a package collection, boot it in a VM and run:

dpkg --get-selections > /home/user/packages.list

Then copy packages.list into the root of the ISO or a mounted folder.
The script will pick it up automatically.


Result:
You now have a fully automated Ubuntu ISO merging tool that can handle both .deb package sets and installed package lists.

Would you like me to also make a reverse function — i.e. extract packages.list from an ISO that already contains an installed system (for cloning later)?

Excellent — this will give you a full two-way toolset 💿🔁💿

You’ll then be able to:

  • Merge two Ubuntu ISOs into one (with .deb or packages.list support)
  • 📜 Extract a packages.list file from any existing ISO to replicate its software set later

Here’s the companion script:


📜 extract-packages-list-from-iso.sh

This script:

  • Mounts an Ubuntu ISO
  • Extracts its filesystem.squashfs
  • Enters it in a minimal chroot
  • Dumps all installed package selections to packages.list
  • Saves it in your working directory
#!/usr/bin/env bash
set -e
 
ISO_FILE="${1:-ubuntuA.iso}"
WORKDIR="$HOME/ubuntu-extract-work"
MOUNTDIR="$WORKDIR/iso-mount"
EDITDIR="$WORKDIR/edit"
LISTFILE="$WORKDIR/packages.list"
 
echo "=== 🧰 Creating work directories ==="
mkdir -p "$WORKDIR" "$MOUNTDIR"
 
echo "=== 💿 Mounting ISO: $ISO_FILE ==="
sudo mount -o loop "$ISO_FILE" "$MOUNTDIR"
 
echo "=== 📦 Extracting filesystem.squashfs ==="
mkdir -p "$WORKDIR/squashfs"
sudo unsquashfs -d "$EDITDIR" "$MOUNTDIR/casper/filesystem.squashfs"
 
echo "=== 🛠️ Entering chroot to list packages ==="
sudo mount --bind /dev "$EDITDIR/dev"
 
sudo chroot "$EDITDIR" /bin/bash -c "
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devpts none /dev/pts
export HOME=/root
export LC_ALL=C
 
echo '=== 📜 Dumping package list ==='
dpkg --get-selections > /packages.list
 
umount /proc /sys /dev/pts
exit
"
 
echo "=== 💾 Copying packages.list to host ==="
sudo cp "$EDITDIR/packages.list" "$LISTFILE"
 
echo "=== 🧽 Cleaning up ==="
sudo umount "$EDITDIR/dev"
sudo umount "$MOUNTDIR"
sudo rm -rf "$EDITDIR"
 
echo "✅ Done!"
echo "Package list saved at: $LISTFILE"
echo "You can embed this file in another ISO to replicate the same software set."

🪛 Usage

  1. Save as:
2.  nano extract-packages-list-from-iso.sh
  1. Make executable:
4.  chmod +x extract-packages-list-from-iso.sh
  1. Run with your ISO:
6.  ./extract-packages-list-from-iso.sh ubuntuB.iso

This will generate:

~/ubuntu-extract-work/packages.list

You can now drop this packages.list file into the second ISO (or alongside it) and use the merge script to replicate its software.


🧠 Bonus tip (optional automation)

You can pair the scripts:

./extract-packages-list-from-iso.sh ubuntuB.iso
cp ~/ubuntu-extract-work/packages.list .
./merge-ubuntu-iso-auto-full.sh

✅ This lets you:

  • Clone the package set of one ISO
  • Add it to another ISO
  • Build a new one that has everything pre-installed

Would you like me to combine both scripts into a single master script with a menu (e.g. “1 = extract packages, 2 = merge ISOs, 3 = exit”)? 🖥️✨

Excellent — let’s make this elegant and easy to use 😎💿

Here’s a single master script with a simple menu system.
It lets you:

  1. 📜 Extract a packages.list from any Ubuntu ISO
  2. 🧪 Merge two Ubuntu ISOs (with automatic .deb and packages.list handling)
  3. 🚪 Exit

This means you can now replicate and combine customized Ubuntu systems with minimal typing.


📜 ubuntu-iso-tool.sh

#!/usr/bin/env bash
set -e
 
# ============================================================
# 🐧 Ubuntu ISO Tool - Extract & Merge Custom Ubuntu ISOs
# Author: Andrew + GPT-5 Assistant
# ============================================================
 
WORKDIR_BASE="$HOME/ubuntu-iso-tools"
EXTRACT_WORK="$WORKDIR_BASE/extract"
MERGE_WORK="$WORKDIR_BASE/merge"
 
# Make sure required packages are installed
check_requirements() {
    echo "=== 🔍 Checking required packages ==="
    sudo apt-get update -qq
    sudo apt-get install -y squashfs-tools genisoimage xorriso rsync chroot
}
 
# ============================================================
# 📜 Function: Extract package list from ISO
# ============================================================
extract_packages_list() {
    ISO_FILE="$1"
    WORKDIR="$EXTRACT_WORK"
    MOUNTDIR="$WORKDIR/iso-mount"
    EDITDIR="$WORKDIR/edit"
    LISTFILE="$WORKDIR/packages.list"
 
    echo "=== 🧰 Preparing workspace for extraction ==="
    rm -rf "$WORKDIR"
    mkdir -p "$MOUNTDIR"
 
    echo "=== 💿 Mounting ISO: $ISO_FILE ==="
    sudo mount -o loop "$ISO_FILE" "$MOUNTDIR"
 
    echo "=== 📦 Extracting filesystem.squashfs ==="
    mkdir -p "$EDITDIR"
    sudo unsquashfs -d "$EDITDIR" "$MOUNTDIR/casper/filesystem.squashfs"
 
    echo "=== 🛠️ Entering chroot to list packages ==="
    sudo mount --bind /dev "$EDITDIR/dev"
 
    sudo chroot "$EDITDIR" /bin/bash -c "
    mount -t proc none /proc
    mount -t sysfs none /sys
    mount -t devpts none /dev/pts
    export HOME=/root
    export LC_ALL=C
    dpkg --get-selections > /packages.list
    umount /proc /sys /dev/pts
    exit
    "
 
    echo "=== 💾 Copying packages.list ==="
    sudo cp "$EDITDIR/packages.list" "$LISTFILE"
 
    echo "=== 🧽 Cleaning up ==="
    sudo umount "$EDITDIR/dev"
    sudo umount "$MOUNTDIR"
    sudo rm -rf "$EDITDIR"
 
    echo "✅ Package list saved at: $LISTFILE"
}
 
# ============================================================
# 💿 Function: Merge ISOs
# ============================================================
merge_isos() {
    BASE_ISO="$1"
    SECOND_ISO="$2"
 
    WORKDIR="$MERGE_WORK"
    EDITDIR="$WORKDIR/iso-edit"
    MOUNTBASE="$WORKDIR/iso-base"
    MOUNTSECOND="$WORKDIR/iso-second"
    DEBDIR="$WORKDIR/debs"
 
    rm -rf "$WORKDIR"
    mkdir -p "$WORKDIR" "$EDITDIR" "$MOUNTBASE" "$MOUNTSECOND" "$DEBDIR"
 
    echo "=== 💿 Mounting base ISO ==="
    sudo mount -o loop "$BASE_ISO" "$MOUNTBASE"
    rsync -a "$MOUNTBASE/" "$EDITDIR/"
    sudo umount "$MOUNTBASE"
 
    echo "=== 📦 Extracting squashfs filesystem ==="
    cd "$EDITDIR"
    mkdir -p squashfs
    sudo unsquashfs casper/filesystem.squashfs
    sudo mv squashfs-root edit
 
    echo "=== 💿 Mounting second ISO ==="
    sudo mount -o loop "$SECOND_ISO" "$MOUNTSECOND"
 
    echo "=== 📁 Searching for .deb packages ==="
    find "$MOUNTSECOND" -type f -name "*.deb" -exec cp {} "$DEBDIR" \; || true
    DEB_COUNT=$(ls -1 "$DEBDIR" 2>/dev/null | wc -l)
 
    echo "=== 📜 Checking for packages.list ==="
    if [ -f "$MOUNTSECOND/packages.list" ]; then
        cp "$MOUNTSECOND/packages.list" "$WORKDIR/"
        LIST_FOUND=1
    else
        LIST_FOUND=0
    fi
 
    echo "=== 🧱 Preparing chroot ==="
    sudo mkdir -p edit/debs
    if [ "$DEB_COUNT" -gt 0 ]; then
        sudo cp $DEBDIR/*.deb edit/debs
    fi
    if [ "$LIST_FOUND" -eq 1 ]; then
        sudo cp "$WORKDIR/packages.list" edit/
    fi
 
    sudo mount --bind /dev edit/dev
    echo "=== 🧠 Installing software in chroot ==="
    sudo chroot edit /bin/bash -c "
    mount -t proc none /proc
    mount -t sysfs none /sys
    mount -t devpts none /dev/pts
    export HOME=/root
    export LC_ALL=C
 
    if [ -d /debs ] && [ \"\$(ls -A /debs 2>/dev/null)\" ]; then
        echo '📦 Installing local .deb packages'
        dpkg -i /debs/*.deb || true
        apt-get -f install -y
    fi
 
    if [ -f /packages.list ]; then
        echo '📜 Installing packages from packages.list'
        apt-get update
        dpkg --set-selections < /packages.list
        apt-get dselect-upgrade -y
    fi
 
    apt-get clean
    rm -rf /debs /packages.list
    umount /proc /sys /dev/pts
    exit
    "
 
    echo "=== 🧽 Cleaning up chroot mounts ==="
    sudo umount edit/dev
    sudo umount "$MOUNTSECOND"
 
    echo "=== 🧱 Rebuilding squashfs filesystem ==="
    sudo rm casper/filesystem.squashfs
    sudo mksquashfs edit casper/filesystem.squashfs -b 1048576 -comp xz
 
    echo "=== 📝 Updating package manifest ==="
    sudo chroot edit dpkg-query -W --showformat='${Package} ${Version}\n' | sudo tee casper/filesystem.manifest
    cp casper/filesystem.manifest casper/filesystem.manifest-desktop
 
    echo "=== 🔥 Building final ISO ==="
    ISO_OUT="$WORKDIR/custom-ubuntu.iso"
    sudo xorriso -as mkisofs -r -V "Custom_Ubuntu" -J -l \
        -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot \
        -boot-load-size 4 -boot-info-table \
        -isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin \
        -o "$ISO_OUT" .
 
    echo
    echo "✅ Final ISO created: $ISO_OUT"
    if [ "$DEB_COUNT" -gt 0 ]; then
        echo "📦 Installed $DEB_COUNT .deb packages from second ISO."
    fi
    if [ "$LIST_FOUND" -eq 1 ]; then
        echo "📜 Installed additional packages from packages.list."
    fi
    echo "💻 Test with: qemu-system-x86_64 -cdrom $ISO_OUT -m 4096"
}
 
# ============================================================
# 🖥️ Menu
# ============================================================
main_menu() {
    while true; do
        echo
        echo "=============================="
        echo " 🐧 Ubuntu ISO Tool"
        echo "=============================="
        echo "1) 📜 Extract packages.list from ISO"
        echo "2) 🧪 Merge two Ubuntu ISOs"
        echo "3) 🚪 Exit"
        echo "=============================="
        read -rp "Select option [1-3]: " CHOICE
 
        case "$CHOICE" in
            1)
                read -rp "Enter ISO filename to extract from: " ISO
                extract_packages_list "$ISO"
                ;;
            2)
                read -rp "Enter base ISO filename: " BASE
                read -rp "Enter second ISO filename: " SECOND
                merge_isos "$BASE" "$SECOND"
                ;;
            3)
                echo "👋 Goodbye."
                exit 0
                ;;
            *)
                echo "❌ Invalid option."
                ;;
        esac
    done
}
 
check_requirements
main_menu

🪛 Setup

nano ubuntu-iso-tool.sh

Paste the script above.

Then:

chmod +x ubuntu-iso-tool.sh

Run:

./ubuntu-iso-tool.sh

🧭 Usage

  • Option 1 — Extract packages.list
·         📜 → from any Ubuntu ISO, saved to ~/ubuntu-iso-tools/extract/packages.list
  • Option 2 — Merge ISOs
·         🧪 → uses .deb and/or packages.list automatically
  • Option 3 — Exit

Advantages of this unified tool:

  • Single file, no manual chrooting needed
  • Handles both .deb and packages.list automatically
  • Cleans up its own temp directories
  • Easily reusable for making multiple custom Ubuntu spins
  • Works great with ISO-to-VM or cloning workflows

Would you like me to also add a “3rd merge mode” that allows merging a base ISO + local folder of .deb packages or a packages.list (without needing a second ISO)? That would let you maintain your own “package bundle” locally 💼✨

Perfect 👌 — this next step will make the tool truly versatile.

You’ll then have three merge modes:

  1. 🧪 Base ISO + Second ISO (with .deb and/or packages.list)
  2. 🐧 Base ISO + Local folder of .deb packages
  3. 📜 Base ISO + Local packages.list file

This is ideal if you maintain your own package sets separately without needing to build a “source ISO” every time.


📜 Final Master Script: ubuntu-iso-tool.sh

#!/usr/bin/env bash
set -e
 
# ============================================================
# 🐧 Ubuntu ISO Tool - Extract & Merge Custom Ubuntu ISOs
# Author: Andrew + GPT-5 Assistant
# ============================================================
 
WORKDIR_BASE="$HOME/ubuntu-iso-tools"
EXTRACT_WORK="$WORKDIR_BASE/extract"
MERGE_WORK="$WORKDIR_BASE/merge"
 
# --- Check dependencies ---
check_requirements() {
    echo "=== 🔍 Checking required packages ==="
    sudo apt-get update -qq
    sudo apt-get install -y squashfs-tools genisoimage xorriso rsync chroot
}
 
# ============================================================
# 📜 Extract package list from ISO
# ============================================================
extract_packages_list() {
    ISO_FILE="$1"
    WORKDIR="$EXTRACT_WORK"
    MOUNTDIR="$WORKDIR/iso-mount"
    EDITDIR="$WORKDIR/edit"
    LISTFILE="$WORKDIR/packages.list"
 
    echo "=== 🧰 Preparing workspace for extraction ==="
    rm -rf "$WORKDIR"
    mkdir -p "$MOUNTDIR"
 
    echo "=== 💿 Mounting ISO: $ISO_FILE ==="
    sudo mount -o loop "$ISO_FILE" "$MOUNTDIR"
 
    echo "=== 📦 Extracting filesystem.squashfs ==="
    mkdir -p "$EDITDIR"
    sudo unsquashfs -d "$EDITDIR" "$MOUNTDIR/casper/filesystem.squashfs"
 
    echo "=== 🛠️ Entering chroot to list packages ==="
    sudo mount --bind /dev "$EDITDIR/dev"
    sudo chroot "$EDITDIR" /bin/bash -c "
    mount -t proc none /proc
    mount -t sysfs none /sys
    mount -t devpts none /dev/pts
    export HOME=/root
    export LC_ALL=C
    dpkg --get-selections > /packages.list
    umount /proc /sys /dev/pts
    exit
    "
 
    echo "=== 💾 Copying packages.list ==="
    sudo cp "$EDITDIR/packages.list" "$LISTFILE"
 
    echo "=== 🧽 Cleaning up ==="
    sudo umount "$EDITDIR/dev"
    sudo umount "$MOUNTDIR"
    sudo rm -rf "$EDITDIR"
 
    echo "✅ Package list saved at: $LISTFILE"
}
 
# ============================================================
# 🧱 Core function: prepare chroot, install packages, rebuild ISO
# ============================================================
do_merge_common() {
    BASE_ISO="$1"
    DEB_SOURCE="$2"
    LIST_SOURCE="$3"
    WORKDIR="$MERGE_WORK"
    EDITDIR="$WORKDIR/iso-edit"
    MOUNTBASE="$WORKDIR/iso-base"
    DEBDIR="$WORKDIR/debs"
 
    rm -rf "$WORKDIR"
    mkdir -p "$WORKDIR" "$EDITDIR" "$MOUNTBASE" "$DEBDIR"
 
    echo "=== 💿 Mounting base ISO ==="
    sudo mount -o loop "$BASE_ISO" "$MOUNTBASE"
    rsync -a "$MOUNTBASE/" "$EDITDIR/"
    sudo umount "$MOUNTBASE"
 
    echo "=== 📦 Extracting squashfs filesystem ==="
    cd "$EDITDIR"
    mkdir -p squashfs
    sudo unsquashfs casper/filesystem.squashfs
    sudo mv squashfs-root edit
 
    # Handle deb source
    if [ -n "$DEB_SOURCE" ]; then
        echo "=== 📁 Copying .deb packages from $DEB_SOURCE ==="
        find "$DEB_SOURCE" -type f -name "*.deb" -exec cp {} "$DEBDIR" \; || true
    fi
 
    DEB_COUNT=$(ls -1 "$DEBDIR" 2>/dev/null | wc -l)
 
    # Handle packages.list source
    if [ -n "$LIST_SOURCE" ]; then
        echo "=== 📜 Copying packages.list from $LIST_SOURCE ==="
        cp "$LIST_SOURCE" "$WORKDIR/packages.list"
        LIST_FOUND=1
    else
        LIST_FOUND=0
    fi
 
    echo "=== 🧱 Preparing chroot ==="
    sudo mkdir -p edit/debs
    if [ "$DEB_COUNT" -gt 0 ]; then
        sudo cp $DEBDIR/*.deb edit/debs
    fi
    if [ "$LIST_FOUND" -eq 1 ]; then
        sudo cp "$WORKDIR/packages.list" edit/
    fi
 
    sudo mount --bind /dev edit/dev
    sudo chroot edit /bin/bash -c "
    mount -t proc none /proc
    mount -t sysfs none /sys
    mount -t devpts none /dev/pts
    export HOME=/root
    export LC_ALL=C
 
    if [ -d /debs ] && [ \"\$(ls -A /debs 2>/dev/null)\" ]; then
        echo '📦 Installing local .deb packages'
        dpkg -i /debs/*.deb || true
        apt-get -f install -y
    fi
 
    if [ -f /packages.list ]; then
        echo '📜 Installing packages from packages.list'
        apt-get update
        dpkg --set-selections < /packages.list
        apt-get dselect-upgrade -y
    fi
 
    apt-get clean
    rm -rf /debs /packages.list
    umount /proc /sys /dev/pts
    exit
    "
    sudo umount edit/dev
 
    echo "=== 🧱 Rebuilding squashfs filesystem ==="
    sudo rm casper/filesystem.squashfs
    sudo mksquashfs edit casper/filesystem.squashfs -b 1048576 -comp xz
 
    echo "=== 📝 Updating package manifest ==="
    sudo chroot edit dpkg-query -W --showformat='${Package} ${Version}\n' | sudo tee casper/filesystem.manifest
    cp casper/filesystem.manifest casper/filesystem.manifest-desktop
 
    echo "=== 🔥 Building final ISO ==="
    ISO_OUT="$WORKDIR/custom-ubuntu.iso"
    sudo xorriso -as mkisofs -r -V "Custom_Ubuntu" -J -l \
        -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot \
        -boot-load-size 4 -boot-info-table \
        -isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin \
        -o "$ISO_OUT" .
 
    echo
    echo "✅ Final ISO created: $ISO_OUT"
    if [ "$DEB_COUNT" -gt 0 ]; then
        echo "📦 Installed $DEB_COUNT .deb packages."
    fi
    if [ "$LIST_FOUND" -eq 1 ]; then
        echo "📜 Installed packages from packages.list."
    fi
    echo "💻 Test with: qemu-system-x86_64 -cdrom $ISO_OUT -m 4096"
}
 
# ============================================================
# 🧪 Merge modes
# ============================================================
merge_iso_to_iso() {
    BASE_ISO="$1"
    SECOND_ISO="$2"
 
    TMPDEB="$MERGE_WORK/tmp-debs"
    TMPLIST=""
    mkdir -p "$TMPDEB"
 
    sudo mount -o loop "$SECOND_ISO" "$MERGE_WORK/second-mount"
    find "$MERGE_WORK/second-mount" -type f -name "*.deb" -exec cp {} "$TMPDEB" \; || true
    if [ -f "$MERGE_WORK/second-mount/packages.list" ]; then
        TMPLIST="$MERGE_WORK/second-mount/packages.list"
    fi
 
    do_merge_common "$BASE_ISO" "$TMPDEB" "$TMPLIST"
    sudo umount "$MERGE_WORK/second-mount"
}
 
merge_iso_with_local_debs() {
    BASE_ISO="$1"
    DEB_FOLDER="$2"
    do_merge_common "$BASE_ISO" "$DEB_FOLDER" ""
}
 
merge_iso_with_local_list() {
    BASE_ISO="$1"
    LIST_FILE="$2"
    do_merge_common "$BASE_ISO" "" "$LIST_FILE"
}
 
# ============================================================
# 🖥️ Menu
# ============================================================
main_menu() {
    while true; do
        echo
        echo "=============================="
        echo " 🐧 Ubuntu ISO Tool"
        echo "=============================="
        echo "1) 📜 Extract packages.list from ISO"
        echo "2) 🧪 Merge Base ISO + Second ISO"
        echo "3) 📦 Merge Base ISO + Local .deb folder"
        echo "4) 📜 Merge Base ISO + Local packages.list"
        echo "5) 🚪 Exit"
        echo "=============================="
        read -rp "Select option [1-5]: " CHOICE
 
        case "$CHOICE" in
            1)
                read -rp "Enter ISO filename to extract from: " ISO
                extract_packages_list "$ISO"
                ;;
            2)
                read -rp "Enter base ISO filename: " BASE
                read -rp "Enter second ISO filename: " SECOND
                merge_iso_to_iso "$BASE" "$SECOND"
                ;;
            3)
                read -rp "Enter base ISO filename: " BASE
                read -rp "Enter local folder containing .deb packages: " DEB_DIR
                merge_iso_with_local_debs "$BASE" "$DEB_DIR"
                ;;
            4)
                read -rp "Enter base ISO filename: " BASE
                read -rp "Enter local packages.list filename: " LIST_FILE
                merge_iso_with_local_list "$BASE" "$LIST_FILE"
                ;;
            5)
                echo "👋 Goodbye."
                exit 0
                ;;
            *)
                echo "❌ Invalid option."
                ;;
        esac
    done
}
 
check_requirements
main_menu

🪛 How to use

nano ubuntu-iso-tool.sh
chmod +x ubuntu-iso-tool.sh
./ubuntu-iso-tool.sh

Menu options:

  1. 📜 Extract packages.list from ISO
    → Creates ~/ubuntu-iso-tools/extract/packages.list
  2. 🧪 Merge Base ISO + Second ISO
    → Automatically uses .deb and/or packages.list inside second ISO.
  3. 📦 Merge Base ISO + Local .deb folder
    → Use your own collection of .deb packages.
  4. 📜 Merge Base ISO + Local packages.list
    → Rebuild a customized Ubuntu ISO without a second ISO.
  5. 🚪 Exit

🧠 Example workflows

Clone a customized system:

# Extract package list from your custom ISO
./ubuntu-iso-tool.sh (option 1)
 
# Merge it with a fresh Ubuntu base ISO
./ubuntu-iso-tool.sh (option 4)

Maintain your own “software pack”:

/home/you/my-debs/
  ├─ vlc.deb
  ├─ gimp.deb
  ├─ ...

Then:

./ubuntu-iso-tool.sh (option 3)

Features Recap:

  • Single file, menu-driven
  • Supports ISO-to-ISO, ISO+local DEBs, ISO+packages.list
  • Automatically handles chroot, package installation, ISO rebuild
  • Minimal cleanup needed
  • Ideal for building custom live or installer ISOs

Would you like me to add an optional automatic naming system so the final ISO includes the merge date and source name (e.g. custom-ubuntu-2025-10-10.iso)? 🏷️✨

Excellent choice 👌

Adding automatic ISO naming makes it much easier to manage multiple builds — especially when testing or maintaining different software bundles.

We’ll generate filenames like:

custom-ubuntu-2025-10-10.iso
custom-ubuntu-2025-10-10-from-localdebs.iso
custom-ubuntu-2025-10-10-from-MyCustom.iso

This includes:

  • 📅 Current date
  • 🏷️ Source name (either second ISO name or localdebs / packageslist)
  • ✅ Automatically saved in the ~/ubuntu-iso-tools/merge/ directory

Below is the final updated script with auto-naming built in.


📜 ubuntu-iso-tool.sh (final with auto naming)

#!/usr/bin/env bash
set -e
 
# ============================================================
# 🐧 Ubuntu ISO Tool - Extract & Merge Custom Ubuntu ISOs
# Author: Andrew + GPT-5 Assistant
# ============================================================
 
WORKDIR_BASE="$HOME/ubuntu-iso-tools"
EXTRACT_WORK="$WORKDIR_BASE/extract"
MERGE_WORK="$WORKDIR_BASE/merge"
 
DATESTAMP=$(date +%Y-%m-%d)
 
# --- Check dependencies ---
check_requirements() {
    echo "=== 🔍 Checking required packages ==="
    sudo apt-get update -qq
    sudo apt-get install -y squashfs-tools genisoimage xorriso rsync chroot
}
 
# ============================================================
# 📜 Extract package list from ISO
# ============================================================
extract_packages_list() {
    ISO_FILE="$1"
    WORKDIR="$EXTRACT_WORK"
    MOUNTDIR="$WORKDIR/iso-mount"
    EDITDIR="$WORKDIR/edit"
    LISTFILE="$WORKDIR/packages.list"
 
    echo "=== 🧰 Preparing workspace for extraction ==="
    rm -rf "$WORKDIR"
    mkdir -p "$MOUNTDIR"
 
    echo "=== 💿 Mounting ISO: $ISO_FILE ==="
    sudo mount -o loop "$ISO_FILE" "$MOUNTDIR"
 
    echo "=== 📦 Extracting filesystem.squashfs ==="
    mkdir -p "$EDITDIR"
    sudo unsquashfs -d "$EDITDIR" "$MOUNTDIR/casper/filesystem.squashfs"
 
    echo "=== 🛠️ Entering chroot to list packages ==="
    sudo mount --bind /dev "$EDITDIR/dev"
    sudo chroot "$EDITDIR" /bin/bash -c "
    mount -t proc none /proc
    mount -t sysfs none /sys
    mount -t devpts none /dev/pts
    export HOME=/root
    export LC_ALL=C
    dpkg --get-selections > /packages.list
    umount /proc /sys /dev/pts
    exit
    "
 
    echo "=== 💾 Copying packages.list ==="
    sudo cp "$EDITDIR/packages.list" "$LISTFILE"
 
    echo "=== 🧽 Cleaning up ==="
    sudo umount "$EDITDIR/dev"
    sudo umount "$MOUNTDIR"
    sudo rm -rf "$EDITDIR"
 
    echo "✅ Package list saved at: $LISTFILE"
}
 
# ============================================================
# 🧱 Core merge and build logic with auto naming
# ============================================================
do_merge_common() {
    BASE_ISO="$1"
    DEB_SOURCE="$2"
    LIST_SOURCE="$3"
    LABEL="$4"
 
    WORKDIR="$MERGE_WORK"
    EDITDIR="$WORKDIR/iso-edit"
    MOUNTBASE="$WORKDIR/iso-base"
    DEBDIR="$WORKDIR/debs"
 
    rm -rf "$WORKDIR"
    mkdir -p "$WORKDIR" "$EDITDIR" "$MOUNTBASE" "$DEBDIR"
 
    echo "=== 💿 Mounting base ISO ==="
    sudo mount -o loop "$BASE_ISO" "$MOUNTBASE"
    rsync -a "$MOUNTBASE/" "$EDITDIR/"
    sudo umount "$MOUNTBASE"
 
    echo "=== 📦 Extracting squashfs filesystem ==="
    cd "$EDITDIR"
    mkdir -p squashfs
    sudo unsquashfs casper/filesystem.squashfs
    sudo mv squashfs-root edit
 
    # Handle deb source
    if [ -n "$DEB_SOURCE" ]; then
        echo "=== 📁 Copying .deb packages from $DEB_SOURCE ==="
        find "$DEB_SOURCE" -type f -name "*.deb" -exec cp {} "$DEBDIR" \; || true
    fi
 
    DEB_COUNT=$(ls -1 "$DEBDIR" 2>/dev/null | wc -l)
 
    # Handle packages.list source
    if [ -n "$LIST_SOURCE" ]; then
        echo "=== 📜 Copying packages.list from $LIST_SOURCE ==="
        cp "$LIST_SOURCE" "$WORKDIR/packages.list"
        LIST_FOUND=1
    else
        LIST_FOUND=0
    fi
 
    echo "=== 🧱 Preparing chroot ==="
    sudo mkdir -p edit/debs
    if [ "$DEB_COUNT" -gt 0 ]; then
        sudo cp $DEBDIR/*.deb edit/debs
    fi
    if [ "$LIST_FOUND" -eq 1 ]; then
        sudo cp "$WORKDIR/packages.list" edit/
    fi
 
    sudo mount --bind /dev edit/dev
    sudo chroot edit /bin/bash -c "
    mount -t proc none /proc
    mount -t sysfs none /sys
    mount -t devpts none /dev/pts
    export HOME=/root
    export LC_ALL=C
 
    if [ -d /debs ] && [ \"\$(ls -A /debs 2>/dev/null)\" ]; then
        echo '📦 Installing local .deb packages'
        dpkg -i /debs/*.deb || true
        apt-get -f install -y
    fi
 
    if [ -f /packages.list ]; then
        echo '📜 Installing packages from packages.list'
        apt-get update
        dpkg --set-selections < /packages.list
        apt-get dselect-upgrade -y
    fi
 
    apt-get clean
    rm -rf /debs /packages.list
    umount /proc /sys /dev/pts
    exit
    "
    sudo umount edit/dev
 
    echo "=== 🧱 Rebuilding squashfs filesystem ==="
    sudo rm casper/filesystem.squashfs
    sudo mksquashfs edit casper/filesystem.squashfs -b 1048576 -comp xz
 
    echo "=== 📝 Updating package manifest ==="
    sudo chroot edit dpkg-query -W --showformat='${Package} ${Version}\n' | sudo tee casper/filesystem.manifest
    cp casper/filesystem.manifest casper/filesystem.manifest-desktop
 
    ISO_OUT="$WORKDIR/custom-ubuntu-${DATESTAMP}-${LABEL}.iso"
    echo "=== 🔥 Building final ISO: $ISO_OUT ==="
    sudo xorriso -as mkisofs -r -V "Custom_Ubuntu" -J -l \
        -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot \
        -boot-load-size 4 -boot-info-table \
        -isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin \
        -o "$ISO_OUT" .
 
    echo
    echo "✅ Final ISO created: $ISO_OUT"
    if [ "$DEB_COUNT" -gt 0 ]; then
        echo "📦 Installed $DEB_COUNT .deb packages."
    fi
    if [ "$LIST_FOUND" -eq 1 ]; then
        echo "📜 Installed packages from packages.list."
    fi
    echo "💻 Test with: qemu-system-x86_64 -cdrom $ISO_OUT -m 4096"
}
 
# ============================================================
# 🧪 Merge modes
# ============================================================
merge_iso_to_iso() {
    BASE_ISO="$1"
    SECOND_ISO="$2"
 
    TMPDEB="$MERGE_WORK/tmp-debs"
    TMPLIST=""
    mkdir -p "$TMPDEB"
 
    sudo mount -o loop "$SECOND_ISO" "$MERGE_WORK/second-mount"
    find "$MERGE_WORK/second-mount" -type f -name "*.deb" -exec cp {} "$TMPDEB" \; || true
    if [ -f "$MERGE_WORK/second-mount/packages.list" ]; then
        TMPLIST="$MERGE_WORK/second-mount/packages.list"
    fi
 
    # derive label from ISO name
    LABEL="$(basename "$SECOND_ISO" .iso)"
    do_merge_common "$BASE_ISO" "$TMPDEB" "$TMPLIST" "$LABEL"
    sudo umount "$MERGE_WORK/second-mount"
}
 
merge_iso_with_local_debs() {
    BASE_ISO="$1"
    DEB_FOLDER="$2"
    LABEL="from-localdebs"
    do_merge_common "$BASE_ISO" "$DEB_FOLDER" "" "$LABEL"
}
 
merge_iso_with_local_list() {
    BASE_ISO="$1"
    LIST_FILE="$2"
    LABEL="from-packageslist"
    do_merge_common "$BASE_ISO" "" "$LIST_FILE" "$LABEL"
}
 
# ============================================================
# 🖥️ Menu
# ============================================================
main_menu() {
    while true; do
        echo
        echo "=============================="
        echo " 🐧 Ubuntu ISO Tool"
        echo "=============================="
        echo "1) 📜 Extract packages.list from ISO"
        echo "2) 🧪 Merge Base ISO + Second ISO"
        echo "3) 📦 Merge Base ISO + Local .deb folder"
        echo "4) 📜 Merge Base ISO + Local packages.list"
        echo "5) 🚪 Exit"
        echo "=============================="
        read -rp "Select option [1-5]: " CHOICE
 
        case "$CHOICE" in
            1)
                read -rp "Enter ISO filename to extract from: " ISO
                extract_packages_list "$ISO"
                ;;
            2)
                read -rp "Enter base ISO filename: " BASE
                read -rp "Enter second ISO filename: " SECOND
                merge_iso_to_iso "$BASE" "$SECOND"
                ;;
            3)
                read -rp "Enter base ISO filename: " BASE
                read -rp "Enter local folder containing .deb packages: " DEB_DIR
                merge_iso_with_local_debs "$BASE" "$DEB_DIR"
                ;;
            4)
                read -rp "Enter base ISO filename: " BASE
                read -rp "Enter local packages.list filename: " LIST_FILE
                merge_iso_with_local_list "$BASE" "$LIST_FILE"
                ;;
            5)
                echo "👋 Goodbye."
                exit 0
                ;;
            *)
                echo "❌ Invalid option."
                ;;
        esac
    done
}
 
check_requirements
main_menu

🪛 Key Upgrade: Auto-naming

  • Final ISO name now automatically includes the current date and source:
    • custom-ubuntu-2025-10-10-MyCustom.iso (from another ISO)
    • custom-ubuntu-2025-10-10-from-localdebs.iso (from local DEBs)
    • custom-ubuntu-2025-10-10-from-packageslist.iso (from list)

👉 This helps keep builds organized when testing multiple variations.


🧭 Example usage

# Merge two ISOs
./ubuntu-iso-tool.sh     # option 2
# Result: ~/ubuntu-iso-tools/merge/custom-ubuntu-2025-10-10-SECONDNAME.iso
 
# Merge base ISO + local DEBs
./ubuntu-iso-tool.sh     # option 3
# Result: ~/ubuntu-iso-tools/merge/custom-ubuntu-2025-10-10-from-localdebs.iso
 
# Merge base ISO + packages.list
./ubuntu-iso-tool.sh     # option 4
# Result: ~/ubuntu-iso-tools/merge/custom-ubuntu-2025-10-10-from-packageslist.iso

Final Features Recap

  • 📜 Extract packages.list from any ISO
  • 🧪 Merge Base ISO + Second ISO automatically
  • 📦 Merge Base ISO + local .deb folder
  • 📜 Merge Base ISO + local packages.list
  • 🏷️ Auto-named output ISOs with date/source
  • 🔄 Fully automated chroot + ISO rebuild process

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.