Indroduction
Every once in a while, disaster strikes, and you need to recover from it, or maybe even, Murphy help you, re-install your operating system. Got backups?
You may already have appropriate ISO images on CD-ROM, DVD, or on a USB thumb drive. But you don't always have those to hand.
What we will do here is show you how to create a small partition, gather one or more ISO images, put them on that small partition, and create the stanzas so you can use GRUB to boot to those ISO images. We also provide a script, mk.iso.part.sh
, to pull all this together. The hope is that, Murphy willing, a small partition, not mounted, and enough of GRUB to boot it will survive whatever catastrophe hits so that you can then use them to rescue yourself.
I use three different ISOs:
-
A recent Debian netinst installer, which you can also use as a rescue system.
-
Finnix, an excellent command line rescue distribution.
-
The GParted live CD, so I can have a graphical partition editor.
How To
The Partition
The first thing is to create a small partition on your hard drive. This should be large enough to hold the ISO images you want to use, plus a few smaller files. More on those shortly. The partition should have the label CDPART
. The script will use this to detect the correct partition. If you don't like that, change it, and the script. I currently use a partition of roughly 2500 megabytes (2500M for the Debian installer). I lay down an ext2 (not ext4) file system, but GRUB can work with other file systems, not least FAT.
Gathering the ISOs
Put the ISO images in a directory where the installer script can find them. This is the value for the variable isoSource
in the script.
How the Script Works
The first thing the script does is locate the desired partition. It searches for a partition with the label cdpart
. You can override this with the command line option -n
.
There are a few command line options. -d
to install Debian, -f
for Finnix, and -g
for GPated. Or -a
for all three.
-z
will remove existing files related to a given image. So, for example, -zd
will zap existing files for the Debian image, and install new ones. Without this option the script installs files not already on the target partition, or updates newer versions.
-n
to specify a partition number. Use this to over-ride the search for a partition, or in case you want to use a partition with some other label. Give only the partition number, not the device name. E.g.: -n 23
Note: There is minimal error checking here.
-s
to specify the file system. The script supports only FAT, specified with -s msdos
, or the default, ext2. The script does not format the partition, but it does need to know the file system in order to specify the file system for /etc/fstab
and the file system module GRUB should load. Note: there is currently no error checking here.
If the ISOs partition does not already have an entry in /etc/fstab
, the script adds one and makes a suitable mount point under /media
. Under qemu, virtual disks show up as device vdX, rather than the usual sdX. We adjust the fstab
entry appropriately for virtual machines.
Then the code handles the three ISO images.
The Debian image comes with some extra goodies. In addition to the ISO image, we also copy in two checksum files, for sha256 and sha512 checksums.
We also copy in two preseed files. Preseeding is described in the Debian Installation Manual Appendix B. I use two preseed files. One, a.cfg
, contains information common to all my installations, e.g. my user name and log-in password (suitably hashed, of course). The other, ${HOSTNAME}.cfg
, contains information specific to a given machine, such as partitioning information, local NTP servers (if any), WiFi information for those machines that use WiFi, etc..
The preseed files are optional, but if you don't use them you might want to take the first menu entry out of the GRUB initialization file, 50_netinst
.
The GRUB file for the Debian netinst image is 50_netinst
. It creates a submenu for GRUB with three entries. One is for a preseeded installation, one with the standard netinst menu so you can install as you see fit, and one that boots directly into the rescue option.
All three use the GUI. If the graphical setup fails, take out the /gtk
in the linux and initrd lines in 50_netinst
, and re-run update-grub
.
The Finnix and GParted sections of the code are similar, but without the preseed and checksum files.
Almost the last thing we do is to update GRUB. Check the output for any errors!
The Results
Theme and Variations
You don't have to use a separate partition. You could put them in /root
. But that runs the risk that the ISOs will get clobbered along with the rest of the system.
The checksum and preseed files for the Debian image are optional; feel free to comment out or delete relevant code from the script and the GRUB configuration file.
GRUB has limited support for LVM. I did not investigate LVM for this purpose because I believe it would make the partition more fragile, more easily corrupted.
The Files
mk.iso.part.sh
The script.
#! /bin/bash
# A shell script to set up iso(s) on their own partition for rescue
# work.
# https://www.linuxjournal.com/content/grub-boot-iso See also:
# https://help.ubuntu.com/community/Grub2/ISOBoot/Examples
# https://www.linuxbabe.com/desktop-linux/boot-from-iso-files-using-grub2-boot-loader
# https://gparted.org/livehd.php
# https://gist.github.com/Pysis868/27203177bdef15fbb70c
doDebian=0
doFinnix=0
doGparted=0
zap=0
# Now find the partition for the CD images. This assumes that it has
# the label CDPART. The user can over-ride this with option -n.
partNo=$(blkid | grep CDPART | cut -d':' -f1 | cut -b9-)
# The file system on that partition. Your choices are msdos or ext2.
fileSys=ext2
help () {
# shellcheck disable=SC2046
printf "%s: Install isos and updated grub for them.\n" $(basename "$0");
printf "Options: -a: all; -d: Debian netinst; -f: Finnix;\n";
printf " -g: Gparted; -n: Partition number; s: file System.\n";
printf " -z: zap existing files first.\n";
exit 0;
}
if [ -z "$*" ] ; then
help;
fi
while getopts "adfgn:s:z" opt; do
case $opt in
a ) doDebian=1;doFinnix=1;doGparted=1;;
d ) doDebian=1;;
f ) doFinnix=1;;
g ) doGparted=1;;
n ) partNo=$OPTARG;;
s ) fileSys=$OPTARG;;
z ) zap=1;;
* ) help;;
esac
done
if [ "${doDebian}" = 0 ] && [ "${doFinnix}" = 0 ] && [ "${doGparted}" = 0 ] ; then
help
fi
if [ -z "${partNo}" ] ; then
printf 'CD partition not detected!\n';
help
fi
isos=/media/isos
grubd=/etc/grub.d
# If we haven't already, set up fstab to use the iso partition.
if ! grep "${isos}" /etc/fstab >> /dev/null ; then
echo updating fstab.
if [ "${fileSys}" != 'msdos' ] ; then
cat >> /etc/fstab << FSTAB
# CDROM images
/dev/sda5 /media/isos ext4 user,noauto 0 0
FSTAB
else
cat >> /etc/fstab << FSTAB
# CDROM images
/dev/sda5 /media/isos vfat user,noauto 0 0
FSTAB
fi
mkdir -p "${isos}" || { printf "Quiting! Can't make directory %s.\n" "${isos}" ; exit 1 ; }
case $HOSTNAME in
# virtual machines, e.g. debian.11
debian.* | cdtest | testing )
sed -i -e "s/\/dev\/sda5/\/dev\/vda${partNo}/g" /etc/fstab;;
*)
# N.B.: untested!
sed -i -e "s/\/dev\/sda5/\/dev\/sda${partNo}/g" /etc/fstab;;
esac
systemctl daemon-reload
fi
if ! mount "${isos}" ; then
printf "Couldn't mount %s! Exiting.\n" "${isos}"
exit 2;
fi
mtpt=$(pwd)
isoSource=/home/charles/samba/debian.installations/isos
if [ ! -d "${isoSource}" ] ; then
printf "IsoSource directory %s not mounted.\n" ${isoSource};
exit 1;
fi
cd "${isoSource}" || { printf "Could not cd to directory %s.\n" "${isoSource}"; exit 1;}
# shellcheck disable=SC2046
finnixIso=$(basename $(ls finnix*.iso))
# shellcheck disable=SC2046
gpartedIso=$(basename $(ls gparted*.iso))
# shellcheck disable=SC2046
isoBase=$(basename $(ls debian*.iso) .iso)
# shellcheck disable=SC2210
if lsb_release -a 2>&1 | grep -i 'Debian GNU/Linux.*bookworm' >> /dev/null ; then
release=bookworm
elif lsb_release -a 2>&1 | grep -i 'Debian GNU/Linux 11 (bullseye)' >> /dev/null ; then
release=bullseye
elif lsb_release -a 2>&1 | grep -i 'Debian GNU/Linux .*trixie' >> /dev/null ; then
release=trixie
else
printf "We don't support this release.\n"
exit
fi
machine=$(uname -m)
if [ "$machine" != "i686" ] && [ "$machine" != "x86_64" ] ; then
printf "We don't support this machine type, %s.\n" "$machine"
exit
fi
machineRelease="$machine$release"
printf "Release is %s. Machine is %s.\n" "$release" "$machine"
printf "isoBase is \"%s\".\nfinnixIso is \"%s\".\ngpartedIso is \"%s\".\n" \
"${isoBase}" "${finnixIso}" "${gpartedIso}"
# select the netinst iso to install. Make sure these are current and
# present.
case "$machineRelease" in
i686bullseye )
# bullseye i386 machines.
installer=install.386
;;
x86_64bullseye )
# bullseye AMD64
installer=install.amd64
;;
i686bookworm )
# bookworm i386
installer=install.386
;;
x86_64bookworm )
# bookworm AMD64
installer=install.amd64
;;
x86_64trixie )
# trixie AMD64
installer=install.amd
;;
* )
printf "We don't support this release or this machine type.\n"
exit
esac
if [ "${doDebian}" -gt 0 ] ; then
iso=${isoBase}.iso
checksums256=${isoBase}.sha256sums
checksums512=${isoBase}.sha512sums
if [ ! -e "${iso}" ] ; then
printf "File %s does not exist here!" "${iso}"
exit
fi
if [ ! -e "${checksums256}" ] ; then
printf "File %s does not exist here!" "${checksums256}"
exit
fi
if [ ! -e "${checksums512}" ] ; then
printf "File %s does not exist here!" "${checksums512}"
exit
fi
if [ "${zap}" != 0 ] ; then
if [ -e "${grubd}"/50_netinst ] ; then
rm "${grubd}"/50_netinst
fi
if [ -e "${isos}/${iso}" ] ; then
rm "${isos}/${iso}"
fi
if [ -e "${isos}/${checksums256}" ] ; then
rm "${isos}/${checksums256}"
fi
if [ -e "${isos}/${checksums512}" ] ; then
rm "${isos}/${checksums512}"
fi
fi
# netinst grub stanza
if [ "${mtpt}/isos/50_netinst" -nt "${grubd}"/50_netinst ] ; then
printf "Copying in 50_netinst.\n"
cp -rp "${mtpt}/isos/50_netinst" "${grubd}"/
chown root:root "${grubd}"/50_netinst
chmod u+x "${grubd}"/50_netinst
sed -i -e"s/set isofile=.*/set isofile=\/${iso}/g" "${grubd}"/50_netinst
sed -i -e"s/install.amd/${installer}/g" "${grubd}"/50_netinst
sed -i -e"s/preseed.cfg/${HOSTNAME}.cfg/g" "${grubd}"/50_netinst
# partition number
if [ "${partNo}" != 5 ] ; then
sed -i -e"s/set root='hd0,[[:digit:]]*'/set root='hd0,$partNo'/g" "${grubd}"/50_netinst
fi
# file system
if [ "${fileSys}" != 'msdos' ] ; then
sed -i -e"s/insmod part_msdos/insmod ext2/g" "${grubd}"/50_netinst
fi
# bullseye or bookworm?
if lsb_release -a | grep -i 'Debian GNU/Linux bookworm' >> /dev/null ; then
sed -i -e 's/Debian 13 OS/Debian 12 OS/g;' "${grubd}"/50_netinst
fi
fi
if [ ! -e "${isos}/${iso}" ] || [ "${iso}" -ot "${isos}/${iso}" ] ; then
printf "Copying in netinst iso.\n"
cp -rp "${iso}" "${checksums256}" "${checksums512}" "${isos}"
chown root:root "${isos}/${isoBase}".*
if [ ! -e "${isos}/${HOSTNAME}.cfg" ] ||
[ "${mtpt}/${HOSTNAME}.cfg" -ot "${isos}/${HOSTNAME}.cfg" ] ; then
printf "Copying in preseed files.\n"
cp -rp "${mtpt}/${HOSTNAME}.cfg" "${mtpt}/a.cfg" "${isos}"
fi
fi
fi
if [ "${doFinnix}" -gt 0 ] ; then
if [ "${zap}" != 0 ] ; then
if [ -e "${grubd}"/52_finnix ] ; then
rm "${grubd}"/52_finnix
fi
if [ -e "${isos}/${finnixIso}" ] ; then
rm "${isos}/${finnixIso}"
fi
fi
# finnix grub stanza
if [ "${mtpt}/isos/52_finnix" -nt "${grubd}"/52_finnix ] ; then
printf "Copying in 52_finnix.\n"
cp -rp "${mtpt}/isos/52_finnix" "${grubd}"/
sed -i -e"s/set isofile=.*/set isofile=\/${finnixIso}/g" "${grubd}"/52_finnix
chown root:root "${grubd}"/52_finnix
chmod u+x "${grubd}"/52_finnix
# partition number
if [ "${partNo}" != 5 ] ; then
sed -i -e"s/set root='hd0,[[:digit:]]*'/set root='hd0,$partNo'/g" "${grubd}"/52_finnix
fi
# file system
if [ "${fileSys}" != 'msdos' ] ; then
sed -i -e"s/insmod part_msdos/insmod ext2/g" "${grubd}"/52_finnix
fi
fi
if [ ! -e "${isos}/${finnixIso}" ] || [ "${finnixIso}" -ot "${isos}/${finnixIso}" ] ; then
printf "Copying in finnix iso.\n"
cp -rp "${finnixIso}" "${isos}"
chown root:root "${isos}/${finnixIso}"
fi
fi
if [ "${doGparted}" -gt 0 ] ; then
if [ "${zap}" != 0 ] ; then
if [ -e "${grubd}"/51_gparted ] ; then
rm "${grubd}"/51_gparted
fi
if [ -e "${isos}/${gpartedIso}" ] ; then
rm "${isos}/${gpartedIso}"
fi
fi
# gparted grub stanza
if [ "${mtpt}/isos/51_gparted" -nt "${grubd}"/51_gparted ] ; then
printf "Copying in 51_gparted.\n"
cp -rp "${mtpt}/isos/51_gparted" "${grubd}"/
sed -i -e"s/set isofile=.*/set isofile=\/${gpartedIso}/g" "${grubd}"/51_gparted
chown root:root "${grubd}"/51_gparted
chmod u+x "${grubd}"/51_gparted
# partition number
if [ "${partNo}" != 5 ] ; then
sed -i -e"s/set root='hd0,[[:digit:]]*'/set root='hd0,$partNo'/g" "${grubd}"/51_gparted
fi
# file system
if [ "${fileSys}" != 'msdos' ] ; then
sed -i -e"s/insmod part_msdos/insmod ext2/g" "${grubd}"/51_gparted
fi
fi
if [ ! -e "${isos}/${gpartedIso}" ] || [ "${gpartedIso}" -ot "${isos}/${gpartedIso}" ] ; then
printf "Copying in gparted iso.\n"
cp -rp "${gpartedIso}" "${isos}"
chown root:root "${isos}/${gpartedIso}"
fi
fi
printf "\nUpdating grub:\n"
update-grub
printf "\nBe sure to umount the ISO partition to protect it.\n";
printf 'Like so: "umount %s".\n' "${isos}"
50_netinst
The GRUB initialization file for Debian.
#!/bin/sh
exec tail -n +3 $0
# This file provides an easy way to add custom menu entries. Simply type the
# menu entries you want to add after this comment. Be careful not to change
# the 'exec tail' line above.
# If the graphical setup fails, take out the /gtk in the linux and
# initrd lines below.
submenu "Debian Netinst Recovery or Installer" --class linux --class gnu-linux --class gnu --class os {
menuentry "Install Debian 13 OS (Preseeded)" {
set root='hd0,5'
set isofile="/iso.file.here"
insmod part_msdos
insmod loopback
loopback loop (hd0,5)$isofile
linux (loop)/install.amd/gtk/vmlinuz auto=true file=/media/preseed.cfg
initrd (loop)/install.amd/gtk/initrd.gz
}
menuentry "Install Debian 13 OS (Expert)" {
set root='hd0,5'
set isofile="/iso.file.here"
insmod part_msdos
insmod loopback
loopback loop (hd0,5)$isofile
linux (loop)/install.amd/gtk/vmlinuz
initrd (loop)/install.amd/gtk/initrd.gz
}
menuentry "Install Debian 13 OS (Rescue)" {
set root='hd0,5'
set isofile="/iso.file.here"
insmod part_msdos
insmod loopback
loopback loop (hd0,5)$isofile
linux (loop)/install.amd/gtk/vmlinuz rescue/enable=true
initrd (loop)/install.amd/gtk/initrd.gz
}
}
51_gparted
The GRUB initialization file for GParted.
#!/bin/sh
exec tail -n +3 $0
# This file provides an easy way to add custom menu entries. Simply type the
# menu entries you want to add after this comment. Be careful not to change
# the 'exec tail' line above.
menuentry "Gparted live 1.6" {
set root='hd0,5'
set isofile="/iso.file.here"
insmod part_msdos
insmod loopback
loopback loop $isofile
linux (loop)/live/vmlinuz boot=live config union=overlay username=user components noswap noeject vga=788 ip= net.ifnames=0 toram=filesystem.squashfs findiso=$isofile
initrd (loop)/live/initrd.img
}
52_finnix
The GRUB initialization file for Finnix.
#!/bin/sh
exec tail -n +3 $0
# This file provides an easy way to add custom menu entries. Simply type the
# menu entries you want to add after this comment. Be careful not to change
# the 'exec tail' line above.
menuentry "finnix " {
set root='hd0,5'
set isofile="/iso.file.here"
insmod part_msdos
insmod loopback
loopback loop (hd0,5)$isofile
linux (loop)/live/vmlinuz findiso=$isofile vga=791 nomodeset --
initrd (loop)/live/initrd.img
}
Credits
Among the web sites I looked at while working on this:
The kernel command line for the GParted GRUB stanza came from the GParted web site.
GRUB Boot from ISO, Linux Journal, March 14, 2017
Grub2/ISOBoot/Examples has some ideas for other ISO images you might want to use.
How to Boot ISO Files From GRUB2 Boot Loader
Pysis868/grub.cfg A sample grub.cfg
: more ideas for customizing the GRUB menu