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:
- Extract one ISO as a base
- Chroot (or mount) into it
- Copy or install the software from the second ISO
- 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
andubuntuB.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 fromubuntuB
into theedit
chroot and install withdpkg -i
. - Option B: Add the same repositories or PPAs used in
ubuntuB
and install the packages withapt 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
- Save as
merge-ubuntu-iso.sh
- Make executable:
3.
chmod +x merge-ubuntu-iso.sh
- Run it:
5.
./merge-ubuntu-iso.sh
- 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
- 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
- Save the script:
2.
nano merge-ubuntu-iso-auto.sh
(Paste the script above)
- Make it executable:
4.
chmod +x merge-ubuntu-iso-auto.sh
- Run it:
6.
./merge-ubuntu-iso-auto.sh
- 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:
- Mount
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 viaapt-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 apackages.list
file is present → install those packages viaapt-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
- Save the file:
2.
nano merge-ubuntu-iso-auto-full.sh
Paste the script above.
- Make it executable:
4.
chmod +x merge-ubuntu-iso-auto-full.sh
- Ensure:
ubuntuA.iso
is your base Ubuntu imageubuntuB.iso
is the second image containing either:.deb
packages (in any folder), or- a file named
packages.list
at the root of the ISO
- Run:
7.
./merge-ubuntu-iso-auto-full.sh
- 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
orpackages.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
- Save as:
2.
nano extract-packages-list-from-iso.sh
- Make executable:
4.
chmod +x extract-packages-list-from-iso.sh
- 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:
- 📜 Extract a
packages.list
from any Ubuntu ISO - 🧪 Merge two Ubuntu ISOs (with automatic
.deb
andpackages.list
handling) - 🚪 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
andpackages.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:
- 🧪 Base ISO + Second ISO (with
.deb
and/orpackages.list
) - 🐧 Base ISO + Local folder of
.deb
packages - 📜 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:
- 📜 Extract
packages.list
from ISO
→ Creates~/ubuntu-iso-tools/extract/packages.list
- 🧪 Merge Base ISO + Second ISO
→ Automatically uses.deb
and/orpackages.list
inside second ISO. - 📦 Merge Base ISO + Local
.deb
folder
→ Use your own collection of.deb
packages. - 📜 Merge Base ISO + Local
packages.list
→ Rebuild a customized Ubuntu ISO without a second ISO. - 🚪 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