Here is a script to setup PXE boot for Ubuntu 20.04 server iso which can be preseed to automate server/desktop installation using cloud-init. It also automatically update fog pxe menu entry
Since ubuntu 20, canonical deprecated debian-installer. Debian-installer is still working but I ran into issues with some statements and find out that using debian-installer successor cloud-init a bit easier to perform autoinstall.
Ubuntu provide daily builds, we can use them to keep our iso updated by setting up a daily cronjob for this script and then add the user-data file needed as cloud-init input (inspired myself from this script which help greatly testing user-data file by wrapping it up into an ubuntu-server iso which can then be directly use either on vm or physical machine).
#!/bin/bash
DIST="ubuntu"
RELEASE="focal"
URL="https://cdimage.ubuntu.com/ubuntu-server/${RELEASE}/daily-live/current/${RELEASE}-live-server-amd64.iso"
CHECKSUM_URL="https://cdimage.ubuntu.com/ubuntu-server/${RELEASE}/daily-live/current/SHA256SUMS"
IMAGE_NAME="${DIST}/${RELEASE}"
GIT_URL="https://some.git.repo.url/containing-user-data-file"
# you need to setup an nfs server serving the NFS_DIR and a tftp server serving TFTP_DIR
DOWNLOAD_DIR="/data/iso"
TFTP_DIR="/tftpboot/os"
NFS_DIR="/data/nfs"
MOUNT_DIR="/media/tmp_image"
NFS_SERVER_IP="your_fog_nfs_server_ip"
# only define this if you have several vmlinuz or initrd inside the iso
VMLINUZ=""
INITRD=""
# pxe parameters use in fog pxe menu entry
PXE_PARAMS="kernel tftp://\$\{fog-ip\}/os/${DIST}/${RELEASE}/vmlinuz
initrd tftp://\$\{fog-ip\}/os/${DIST}/${RELEASE}/initrd
imgargs boot=casper ip=dhcp netboot=nfs nfsroot=\$\{fog-ip\}:${NFS_DIR}/${IMAGE_NAME} rw"
mkdir -p ${DOWNLOAD_DIR}/${DIST}/${RELEASE}
image="${DOWNLOAD_DIR}/${DIST}/${RELEASE}/${RELEASE}-live-server-amd64.iso"
function cleanup() {
trap - SIGINT SIGTERM ERR EXIT
if [ -n "${tmpdir+x}" ]; then
rm -rf "$tmpdir"
echo "🚽 Deleted temporary working directory $tmpdir"
fi
}
trap cleanup SIGINT SIGTERM ERR EXIT
testdir() {
if [ ! -d "$1" ]; then
echo "Directory $1 doesn't exist" 1>&2
usage
exit 1
fi
}
checkIso() {
if [ ! -f "${tmpdir}/SHA256SUMS-${today}" ]; then
curl -NsSL ${CHECKSUM_URL} -o "${tmpdir}/SHA256SUMS-${today}"
fi
digest=$(sha256sum "${image}" | cut -f1 -d ' ')
set +e
grep -Fq "$digest" "${tmpdir}/SHA256SUMS-${today}"
if [ $? -eq 0 ]; then
echo "👍 Verification succeeded."
set -e
return 0
else
echo "👿 Verification of ISO digest failed." 1>&2;
return 1
fi
}
downloadIso() {
if [ ! -f "${image}" ]; then
echo "File $image doesn't exist, downloading iso"
wget -P ${DOWNLOAD_DIR}/${DIST}/${RELEASE} ${URL}
fi
checkIso
if [ $? -eq 1 ]; then
rm ${image}
downloadIso
fi
# TODO eventually check iso signature
}
# updating fog PXEMenu
updatePXEMenu() {
echo "param $1 $2"
pxeID=$(mysql --batch --skip-column-names --execute="select pxeID from fog.pxeMenu where pxeName=\"${DIST}_${RELEASE}_$1\"" | head -n 1)
if [ -z "$pxeID" ]; then
echo "No entry for ${DIST} ${RELEASE} $1, generating a new one"
mysql --batch --skip-column-names --execute="INSERT INTO fog.pxeMenu (pxeName, pxeDesc, pxeParams, pxeRegOnly, pxeArgs, pxeDefault, pxeHotKeyEnable, pxeKeySequence) VALUES (\"${DIST}_${RELEASE}_$1\", \"${DIST} ${RELEASE} $1 image\", '$2', 2, \"\", 0, \"0\", \"\");"
else
echo "Entry found for ${DIST} ${RELEASE}, updating"
mysql --batch --skip-column-names --execute="UPDATE fog.pxeMenu SET pxeName=\"${DIST}_${RELEASE}_$1\", pxeDesc=\"${DIST} ${RELEASE} $1 image\", pxeParams='$2' where pxeID=${pxeID};"
fi
}
# clone the git repo containing user-data files
# it will search for user data store in /user-data/${DIST}/${RELEASE}/
# you can them define as many user-data file as you want by creating storing
# them this way inside the repo :
# user-data/${DIST}/${RELEASE}/desktop-minimal/user-data
# user-data/${DIST}/${RELEASE}/desktop-full/user-data
addUserData() {
git clone --quiet ${GIT_URL} ${tmpdir}/cloud-init
mkdir -p ${NFS_DIR}/${IMAGE_NAME}/nocloud
cp -r ${tmpdir}/cloud-init/user-data/${DIST}/${RELEASE}/* ${NFS_DIR}/${IMAGE_NAME}/nocloud/
for item in ${NFS_DIR}/${IMAGE_NAME}/nocloud/*; do
item="$(echo $item | sed 's#.*/##')"
tmp="${PXE_PARAMS} autoinstall ds=nocloud;s=/cdrom/nocloud/${item}"
updatePXEMenu "autoinstall_${item}" "${tmp}"
done
}
echo "PXE image script"
if [ "$EUID" -ne 0 ]; then
echo "Please run as root"
exit
fi
tmpdir=$(mktemp -d)
if [[ ! "$tmpdir" || ! -d "$tmpdir" ]]; then
echo "💥 Could not create temporary working directory." 1>&2; exit 1;
else
echo "📁 Created temporary working directory $tmpdir"
fi
downloadIso
mkdir -p ${MOUNT_DIR}
echo "Trying to mount ${image} to ${MOUNT_DIR}..."
mount -ro loop ${image} ${MOUNT_DIR}
vmlinuzs=( $(find ${MOUNT_DIR} -name "vmlinuz*") )
index=0
nbelem=${#vmlinuzs[@]}
if [ ${nbelem} -ne 1 ]; then
echo "absent or multiple vmlinuz file, please run interactive script" 1>&2
exit 1
fi
VMLINUZ=${vmlinuzs[${index}]}
echo "The file ${VMLINUZ} will be used"
initrds=( $(find ${MOUNT_DIR} -name "initrd*") )
index=0
nbelem=${#initrds[@]}
if [ ${nbelem} -ne 1 ]; then
echo "absent or multiple initrd file, please run interactive script" 1>&2
exit 1
fi
INITRD=${initrds[${index}]}
echo "The file ${INITRD} will be used"
mkdir -vp ${TFTP_DIR}/${IMAGE_NAME}
mkdir -vp ${NFS_DIR}/${IMAGE_NAME}
echo "Copying the image file to NFS Server"
cp -Rf ${MOUNT_DIR}/* ${NFS_DIR}/${IMAGE_NAME}
# match hidden files too (store UUID needed to use NFS as it perform a check)
cp -Rf ${MOUNT_DIR}/.[!.]* ${NFS_DIR}/${IMAGE_NAME}
echo "Copying the vmlinuz & initrd files to TFTP Server"
cp -f ${VMLINUZ} ${TFTP_DIR}/${IMAGE_NAME}/
cp -f ${INITRD} ${TFTP_DIR}/${IMAGE_NAME}/
umount ${MOUNT_DIR}
echo "Editing TFTP config file"
# setting up an entry with no autoinstall
updatePXEMenu "server" "${PXE_PARAMS}"
# setting up one entry per user-data stored in your git repo
addUserData
echo "Restarting TFTP Server"
service tftpd-hpa restart
echo "Finished"
To setup manually this instead of using this script, you can follow this kind of instruction but perform the following command after this one cp -R /mnt/loop/* /images/os/mint/19.1
cp -R /mnt/loop/.[!.]* /images/os/mint/19.1
This script can be adapted to setup other distro iso (without automation as I think only ubuntu will work with cloud init)