Ubuntu 20.04 NFS PXE autoinstall automation
-
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)