Last active
April 4, 2021 23:23
-
-
Save jsamr/b55ed800eea22019bea5881fca000851 to your computer and use it in GitHub Desktop.
A bash script to very easily create a bootable USB device from one ISO file. Just curl it, chmod it and go!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Author: jules randolph <[email protected]> https://github.com/jsamr | |
# License: MIT | |
# | |
# Usage: [<options>] <file.iso> | |
# | |
# Create a bootable FAT32 USB device from a linux-GNU/unix ISO. | |
# | |
# Options | |
# -n, --no-bootloader Do not add syslinux bootloader. | |
# -d, --device <device> Select <device> as USB device. | |
# Device should be in the form /dev/sXX or /dev/hXX. | |
# You will be prompted to select a device if you don't use this option. | |
# -f, --force-iso Disable mime-type check on ISO file. | |
# | |
# How it works | |
# | |
# The script walks through the following steps: | |
# 0. Check dependencies and prompt user to install any missing. | |
# 1. Check provided ISO exists and has the expected application/x-iso9660-image mime-type. | |
# 2. If given with -d option, check that the selected device exists and is not a partition. Otherwise, prompt the user to select a device. | |
# 3. Prompt the user for confirmation that data might be lost for selected device if he goes to next step. | |
# 4. Unmount the USB if mounted, blank it and delete existing partitions. | |
# 5. Create a FAT32 partition on the USB device. | |
# 6. Create a temporary dir to mount the ISO file and mount it. | |
# 7. Create a temporary dir to mount the USB device and mount it. | |
# 8. Copy files from ISO to USB device. | |
# 9. If option --no-bootloader is not selected, install a bootloader with syslinux in slow mode. | |
# 10. Unmount devices and remove temporary folders. | |
dependencies=('lsblk' 'grep' 'file' 'sfdisk' 'mkfs.vfat' 'syslinux' 'mtools' 'rsync') | |
scriptName=$(basename "$0") | |
selectedDrive='' | |
selectedPartition='' | |
selectedIsoFile='' | |
isoMountPoint='' | |
usbMountPoint='' | |
# options | |
addSyslinuxBootloader=true | |
disableMimeCheck=false | |
help_message="\ | |
Usage: $scriptName [<options>] <file.iso> | |
Create a bootable FAT32 USB from a linux-GNU/unix ISO. | |
Options | |
-n, --no-bootloader Do not add syslinux bootloader. | |
-d, --device <device> Select <device> as USB device. | |
Device should be in the form /dev/sXX or /dev/hXX. | |
You will be prompted to select a device if you don't use this option. | |
-f, --force-iso Disable mime-type check on iso file. | |
" | |
display_help() { | |
echo "$help_message" | |
} | |
echoerr() { | |
>&2 echo -e "\\033[0;31m$1\\033[0m" | |
} | |
echowarn() { | |
echo -e "\\033[1;33m$1\\033[0m" | |
} | |
failAndExit() { | |
echoerr "$1\\nExiting $scriptName..." | |
exit 1 | |
} | |
initPckgManager() { | |
if [ ! -z "$(which apt-get)" ]; then # Debian | |
pkgmgr="apt-get -y install" | |
return 0 | |
fi | |
if [ ! -z "$(which yum)" ]; then # Fedora | |
pkgmgr="yum -y install" | |
return 0 | |
fi | |
if [ ! -z "$(which dnf)" ]; then # Fedora | |
pkgmgr="dnf -y install" | |
return 0 | |
fi | |
if [ ! -z "$(which pacman)" ]; then # Arch | |
pkgmgr="pacman -S" | |
return 0 | |
fi | |
if [ ! -z "$(which zypper)" ]; then # OpenSuse | |
pkgmgr="zypper install" | |
return 0 | |
fi | |
if [ ! -z "$(which emerge)" ]; then # Gentoo | |
pkgmgr="emerge" | |
return 0 | |
fi | |
} | |
init() { | |
if ((EUID != 0)); then | |
if [[ -t 1 ]]; then | |
sudo "$0" "$@" | |
else | |
exec 1>output_file | |
gksu "$0" "$@" | |
fi | |
exit | |
fi | |
initPckgManager | |
} | |
parseOptions() { | |
typeset key | |
while [[ $# -gt 0 ]]; do | |
key="$1" | |
case $key in | |
-n|--no-bootloader) | |
addSyslinuxBootloader=false | |
shift | |
;; | |
-d|--device) | |
selectedDrive="$2" | |
shift | |
shift | |
;; | |
-f|--force-iso) | |
disableMimeCheck=true | |
shift | |
;; | |
-*) | |
if [ ! -f "$key" ]; then | |
failAndExit "Unknown option \`$key'." | |
fi | |
selectedIsoFile=$1 | |
;; | |
*) | |
selectedIsoFile=$1 | |
shift | |
;; | |
esac | |
done | |
if [ "$selectedIsoFile" == '' ]; then | |
failAndExit "Missing argument \`iso-file'." | |
fi | |
if [ ! -e "$selectedIsoFile" ]; then | |
failAndExit "Provided iso file \`$selectedIsoFile' does not exists." | |
fi | |
typeset mimetype=$(file --mime-type -b "$selectedIsoFile") | |
if [ ! "$mimetype" == "application/x-iso9660-image" ] && [ "$disableMimeCheck" == 'false' ]; then | |
failAndExit "Provided file \`$selectedIsoFile' doesn't seem to be an iso file (wrong mime type: $mimetype)." | |
fi | |
} | |
checkPackages() ( | |
checkpkg() { | |
if [[ $(which "$1") == "" ]]; then | |
echo -n "Package '$1' not found!" | |
if [[ ! $pkgmgr == "" ]]; then | |
read -r -n1 -p "Attempt installation? (y/n)>" answer | |
echo | |
case $answer in | |
y) $pkgmgr "$1" | |
;; | |
n) | |
read -r -n1 -p "Proceed anyway? (y/n)>" answer2 | |
echo | |
if [[ "$answer2" == "n" ]] ; then exit | |
fi | |
;; | |
esac | |
else | |
failAndExit "Missing dependency \`$1'." | |
fi | |
fi | |
} | |
for pkg in "${dependencies[@]}"; do | |
checkpkg "$pkg" | |
done | |
) | |
# print the name of the new folder if operation succeeded, fails otherwise | |
# arg1 : template name | |
createTempFolder() { | |
typeset tmpFileTemplate="/tmp/$1.XXX" | |
mktemp -d "$tmpFileTemplate" | |
typeset status=$? | |
if [ ! $status -eq 0 ]; then | |
failAndExit "Failed to create temporary folder" | |
fi | |
} | |
mountIsoFile() { | |
isoMountPoint=$(createTempFolder iso) || exit 1 | |
echo "Created iso mount point at \`$isoMountPoint'" | |
if ! mount -o loop "$selectedIsoFile" "$isoMountPoint"; then | |
failAndExit "Could not mount iso file." | |
fi | |
} | |
selectDrive() { | |
typeset drives=() | |
typeset _selectedDrive | |
containsElement () { | |
local e match="$1" | |
shift | |
for e; do [[ "$e" == "$match" ]] && return 0; done | |
return 1 | |
} | |
listDrives() { | |
lsblk | grep --color=never -oe "^[sh][a-zA-Z]*" | |
} | |
chooseDrive() { | |
echo "Select the device corresponding to the USB device you want to make bootable (${drives[*]}). Type exit to quit." | |
read -r -p "Select device name>" _selectedDrive | |
if containsElement "$_selectedDrive" "${drives[@]}"; then | |
selectedDrive="/dev/$_selectedDrive" | |
echo "Selected device is \`$selectedDrive'" | |
else | |
if [ "$_selectedDrive" == 'exit' ]; then | |
echo "Exiting on user request." | |
exit 0 | |
else | |
failAndExit "The drive $_selectedDrive does not exist." | |
fi | |
fi | |
} | |
if [ "$selectedDrive" == '' ]; then | |
mapfile -t drives < <(listDrives) | |
# List all hard disk drives | |
echo "Listing available drives in your system:" | |
lsblk | grep --color=never -e '^[sh]' | |
chooseDrive | |
else | |
if [[ "$selectedDrive" =~ "/dev" ]]; then | |
if [ -e "$selectedDrive" ]; then | |
if [[ "$selectedDrive" =~ /dev/[a-zA-Z]+[0-9]+ ]]; then | |
failAndExit "\`$selectedDrive' is a partition. Select a drive instead." | |
else | |
echo "Selected device is: $selectedDrive" | |
fi | |
else | |
failAndExit "The device $selectedDrive does not exists" | |
fi | |
else | |
failAndExit "The selected device is not a valid posix device file (should start with \`/dev')" | |
fi | |
fi | |
} | |
hasDeviceMountedPartitions() { | |
typeset result=$(mount | grep -e "^$selectedDrive") | |
if [ "$result" == "" ]; then | |
return 1 | |
else | |
return 0 | |
fi | |
} | |
partitionUSB() { | |
typeset answer | |
echowarn "$scriptName is about to wipe out the content of device $selectedDrive." | |
selectedPartition="${selectedDrive}1" | |
read -r -n1 -p "Are you sure you want to proceed? (y/n)>" answer | |
if [ "$answer" == 'y' ]; then | |
echo "Erasing contents of $selectedDrive:" | |
# unmount | |
if hasDeviceMountedPartitions; then | |
if ! umount "$selectedDrive"?*; then | |
failAndExit "$scriptName failed to unmount paritions from drive $selectedDrive" | |
fi | |
fi | |
# erase drive | |
dd if=/dev/zero of="$selectedDrive" bs=512 count=1 conv=notrunc || failAndExit "Failed to erase USB device." | |
# set partition | |
echo "$selectedPartition : start=2048, type=b, bootable" | sfdisk "$selectedDrive" || failAndExit "Failed to write USB device partition table." | |
# format | |
echo "Creating fat32 partition..." | |
mkfs.vfat "$selectedPartition" > /dev/null || failAndExit "Failed to create fat32 partition on USB device." | |
else | |
failAndExit "Discarding operation." | |
fi | |
} | |
mountUSB() { | |
usbMountPoint=$(createTempFolder usb) || exit 1 | |
echo "Created USB device mount point at \`$usbMountPoint'" | |
if ! mount -t vfat "$selectedPartition" "$usbMountPoint"; then | |
failAndExit "Could not mount USB device." | |
fi | |
} | |
createBootableUSB() { | |
echo "Copying files from iso to USB..." | |
rsync --info=progress2 --recursive --executability "$isoMountPoint"/. "$usbMountPoint" | |
if [ "$addSyslinuxBootloader" == 'true' ]; then | |
echo "Installing syslinux bootloader..." | |
syslinux --stupid "$selectedPartition" | |
if [ -d "$usbMountPoint/isolinux" ]; then | |
mv "$usbMountPoint/isolinux" "$usbMountPoint"/syslinux | |
fi | |
if [ -e "$usbMountPoint/syslinux/isolinux.cfg" ]; then | |
mv "$usbMountPoint/syslinux/isolinux.cfg" "$usbMountPoint"/syslinux/syslinux.cfg | |
fi | |
fi | |
} | |
main() { | |
init "$@" | |
parseOptions "$@" | |
checkPackages | |
selectDrive | |
mountIsoFile | |
partitionUSB | |
mountUSB | |
createBootableUSB | |
} | |
cleanup() { | |
if [ -d "$isoMountPoint" ]; then | |
echo "Unmounting iso..." | |
if umount "$isoMountPoint"; then | |
rmdir "$isoMountPoint" | |
fi | |
fi | |
if [ -d "$usbMountPoint" ]; then | |
echo "Unmounting USB..." | |
if umount "$usbMountPoint"; then | |
rmdir "$usbMountPoint" | |
fi | |
fi | |
} | |
trap cleanup EXIT INT TERM | |
main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment