12-21-2018, 01:47 PM
How to Build a Netboot Server, Part 3
<div style="margin: 5px 5% 10px 5%;"><img src="http://www.sickgaming.net/blog/wp-content/uploads/2018/12/how-to-build-a-netboot-server-part-3.png" width="1024" height="641" title="" alt="" /></div><div><p>The <a href="https://fedoramagazine.org/how-to-build-a-netboot-server-part-1/" target="_blank" rel="noopener">How to Build a Netboot Server, Part 1</a> article provided a minimal <a href="https://ipxe.org/" target="_blank" rel="noopener">iPXE</a> boot script for your netboot image. Many users probably have a local operating system that they want to use in addition to the netboot image. But switching bootloaders using the typical workstation’s BIOS can be cumbersome. This part of the series shows how to set up some more complex iPXE configurations. These allow the end user to easily choose which operating system they want to boot. They also let the system administrator manage the boot menus from a central server.<span id="more-23341"></span></p>
<h2>An interactive iPXE boot menu</h2>
<p>The commands below redefine the netboot image’s <em>boot.cfg</em> as an interactive iPXE boot menu with a 5 second countdown timer:</p>
<pre>$ MY_FVER=29 $ MY_KRNL=$(ls -c /fc$MY_FVER/lib/modules | head -n 1) $ MY_DNS1=192.0.2.91 $ MY_DNS2=192.0.2.92 $ MY_NAME=server-01.example.edu $ MY_EMAN=$(echo $MY_NAME | tr '.' "\n" | tac | tr "\n" '.' | cut -b -${#MY_NAME}) $ MY_ADDR=$(host -t A $MY_NAME | awk '{print $4}') $ cat << END > $HOME/esp/linux/boot.cfg #!ipxe set timeout 5000 :menu menu iPXE Boot Menu item --key 1 lcl 1. Microsoft Windows 10 item --key 2 f$MY_FVER 2. RedHat Fedora $MY_FVER choose --timeout \${timeout} --default lcl selected || goto shell set timeout 0 goto \${selected} :failed echo boot failed, dropping to shell... goto shell :shell echo type 'exit' to get the back to the menu set timeout 0 shell goto menu :lcl exit :f$MY_FVER kernel --name kernel.efi \${prefix}/vmlinuz-$MY_KRNL initrd=initrd.img ro ip=dhcp rd.peerdns=0 nameserver=$MY_DNS1 nameserver=$MY_DNS2 root=/dev/disk/by-path/ip-$MY_ADDR:3260-iscsi-iqn.$MY_EMAN:fc$MY_FVER-lun-1 netroot=iscsi:$MY_ADDR::::iqn.$MY_EMAN:fc$MY_FVER console=tty0 console=ttyS0,115200n8 audit=0 selinux=0 quiet initrd --name initrd.img \${prefix}/initramfs-$MY_KRNL.img boot || goto failed END</pre>
<p>The above menu has five sections:</p>
<ul>
<li><strong>menu</strong> defines the actual menu that will be shown on the screen.</li>
<li><strong>failed</strong> notifies the user that something went wrong and drops the user to a shell so they can troubleshot the problem.</li>
<li><strong>shell</strong> provides an interactive command prompt. You can reach it either by pressing the <strong>Esc</strong> key while at the boot menu or if the “boot” command returns with a failure code.</li>
<li><strong>lcl</strong> contains a single command that tells iPXE to <em>exit</em> and return control back to the BIOS. Whatever you want to boot by default (e.g. the workstation’s local hard drive) <strong>must</strong> be listed as the next boot item right after iPXE in your workstation’s BIOS.</li>
<li><strong>f29</strong> contains the same netboot code used earlier but with the final <em>exit</em> replaced with <em>goto failed</em>.</li>
</ul>
<p>Copy the updated <em>boot.cfg</em> from your <em>$HOME/esp/linux</em> directory out to the ESPs of all your client systems. If all goes well, you should see results similar to the image below:</p>
<p><img class="alignnone size-large wp-image-23652" src="http://www.sickgaming.net/blog/wp-content/uploads/2018/12/how-to-build-a-netboot-server-part-3.png" alt="" width="616" height="386" /></p>
<h2>A server hosted boot menu</h2>
<p>Another feature you can add to the netboot server is the ability to manage all the client boot menus from one central location. This feature can be especially useful when rolling out a new version of the OS. It lets you perform a sort of <a href="https://en.wikipedia.org/wiki/Atomicity_(database_systems)" target="_blank" rel="noopener">atomic transaction</a> to switch all clients over to the new OS after you’ve copied the new kernel and initramfs out to the ESPs of all the clients.</p>
<p>Install Mojolicious:</p>
<pre>$ sudo -i # dnf install -y perl-Mojolicious</pre>
<p>Define the “bootmenu” app:</p>
<pre># mkdir /opt/bootmenu # cat << END > /opt/bootmenu/bootmenu.pl #!/usr/bin/env perl use Mojolicious::Lite; use Mojolicious::Plugins; plugin 'Config'; get '/menu'; app->start; END # chmod 755 /opt/bootmenu/bootmenu.pl</pre>
<p>Define the configuration file for the bootmenu app:</p>
<pre># cat << END > /opt/bootmenu/bootmenu.conf { hypnotoad => { listen => ['http://*:80'], pid_file => '/run/bootmenu/bootmenu.pid', } } END</pre>
<p>This is an extremely simple Mojolicious application that listens on port 80 and only answers to <em>/menu</em> requests. If you want a quick introduction to what Mojolicious can do, run <em>man Mojolicious::Guides::Growing</em> to view the manual. Use the <strong>Q</strong> key to quit the manual.</p>
<p>Move <em>boot.cfg</em> over to our netboot app as a template named <em>menu.html.ep:</em></p>
<pre># mkdir /opt/bootmenu/templates # mv $HOME/esp/linux/boot.cfg /opt/bootmenu/templates/menu.html.ep</pre>
<p>Define a systemd service to manage the bootmenu app:</p>
<pre># cat << END > /etc/systemd/system/bootmenu.service [Unit] Description=Serves iPXE Menus over HTTP After=network-online.target [Service] Type=forking DynamicUser=true RuntimeDirectory=bootmenu PIDFile=/run/bootmenu/bootmenu.pid ExecStart=/usr/bin/hypnotoad /opt/bootmenu/bootmenu.pl ExecReload=/usr/bin/hypnotoad /opt/bootmenu/bootmenu.pl AmbientCapabilities=CAP_NET_BIND_SERVICE KillMode=process [Install] WantedBy=multi-user.target END</pre>
<p>Add an exception for the HTTP service to the local firewall and start the bootmenu service:</p>
<pre># firewall-cmd --add-service http # firewall-cmd --runtime-to-permanent # systemctl enable bootmenu.service # systemctl start bootmenu.service</pre>
<p>Test it with <em>wget</em>:</p>
<pre>$ sudo dnf install -y wget $ MY_BOOTMENU_SERVER=server-01.example.edu $ wget -q -O - http://$MY_BOOTMENU_SERVER/menu</pre>
<p>The above command should output something similar to the following:</p>
<pre>#!ipxe set timeout 5000 :menu menu iPXE Boot Menu item --key 1 lcl 1. Microsoft Windows 10 item --key 2 f29 2. RedHat Fedora 29 choose --timeout ${timeout} --default lcl selected || goto shell set timeout 0 goto ${selected} :failed echo boot failed, dropping to shell... goto shell :shell echo type 'exit' to get the back to the menu set timeout 0 shell goto menu :lcl exit :f29 kernel --name kernel.efi ${prefix}/vmlinuz-4.19.4-300.fc29.x86_64 initrd=initrd.img ro ip=dhcp rd.peerdns=0 nameserver=192.0.2.91 nameserver=192.0.2.92 root=/dev/disk/by-path/ip-192.0.2.158:3260-iscsi-iqn.edu.example.server-01:fc29-lun-1 netroot=iscsi:192.0.2.158::::iqn.edu.example.server-01:fc29 console=tty0 console=ttyS0,115200n8 audit=0 selinux=0 quiet initrd --name initrd.img ${prefix}/initramfs-4.19.4-300.fc29.x86_64.img boot || goto failed</pre>
<p>Now that the boot menu server is working, rebuild the <em>ipxe.efi</em> bootloader with an init script that points to it.</p>
<p>First, update the <em>init.ipxe</em> script created in part one of this series:</p>
<pre>$ MY_BOOTMENU_SERVER=server-01.example.edu $ cat << END > $HOME/ipxe/init.ipxe #!ipxe dhcp || exit set prefix file:///linux chain http://$MY_BOOTMENU_SERVER/menu || exit END</pre>
<p>Now, rebuild the boot loader:</p>
<pre>$ cd $HOME/ipxe/src $ make clean $ make bin-x86_64-efi/ipxe.efi EMBED=../init.ipxe</pre>
<p>Copy the updated bootloader to your ESP:</p>
<pre>$ cp $HOME/ipxe/src/bin-x86_64-efi/ipxe.efi $HOME/esp/efi/boot/bootx64.efi</pre>
<p>After you’ve copied the updated bootloader to all your clients, you can make future updates to the boot menu simply by editing <em>/opt/bootmenu/templates/menu.html.ep</em> and running:</p>
<pre>$ sudo systemctl restart bootmenu.service</pre>
<h2>Making further changes</h2>
<p>If the boot menu server is working properly, you’ll longer need the the <em>boot.cfg</em> file on your client systems.</p>
<p>For example, re-add the Fedora 28 image to the boot menu:</p>
<pre>$ sudo -i # MY_FVER=28 # MY_KRNL=$(ls -c /fc$MY_FVER/lib/modules | head -n 1) # MY_DNS1=192.0.2.91 # MY_DNS2=192.0.2.92 # MY_NAME=$(</etc/hostname) # MY_EMAN=$(echo $MY_NAME | tr '.' "\n" | tac | tr "\n" '.' | cut -b -${#MY_NAME}) # MY_ADDR=$(host -t A $MY_NAME | awk '{print $4}') # cat << END >> /opt/bootmenu/templates/menu.html.ep :f$MY_FVER kernel --name kernel.efi \${prefix}/vmlinuz-$MY_KRNL initrd=initrd.img ro ip=dhcp rd.peerdns=0 nameserver=$MY_DNS1 nameserver=$MY_DNS2 root=/dev/disk/by-path/ip-$MY_ADDR:3260-iscsi-iqn.$MY_EMAN:fc$MY_FVER-lun-1 netroot=iscsi:$MY_ADDR::::iqn.$MY_EMAN:fc$MY_FVER console=tty0 console=ttyS0,115200n8 audit=0 selinux=0 quiet initrd --name initrd.img \${prefix}/initramfs-$MY_KRNL.img boot || goto failed END # sed -i "/item --key 2/a item --key 3 f$MY_FVER 3. RedHat Fedora $MY_FVER" /opt/bootmenu/templates/menu.html.ep # systemctl restart bootmenu.service</pre>
<p>If all goes well, your clients should see results similar to the image below the next time they boot:</p>
<p><img class="alignnone size-large wp-image-23660" src="http://www.sickgaming.net/blog/wp-content/uploads/2018/12/how-to-build-a-netboot-server-part-3-1.png" alt="" width="616" height="386" /></p>
</div>
<div style="margin: 5px 5% 10px 5%;"><img src="http://www.sickgaming.net/blog/wp-content/uploads/2018/12/how-to-build-a-netboot-server-part-3.png" width="1024" height="641" title="" alt="" /></div><div><p>The <a href="https://fedoramagazine.org/how-to-build-a-netboot-server-part-1/" target="_blank" rel="noopener">How to Build a Netboot Server, Part 1</a> article provided a minimal <a href="https://ipxe.org/" target="_blank" rel="noopener">iPXE</a> boot script for your netboot image. Many users probably have a local operating system that they want to use in addition to the netboot image. But switching bootloaders using the typical workstation’s BIOS can be cumbersome. This part of the series shows how to set up some more complex iPXE configurations. These allow the end user to easily choose which operating system they want to boot. They also let the system administrator manage the boot menus from a central server.<span id="more-23341"></span></p>
<h2>An interactive iPXE boot menu</h2>
<p>The commands below redefine the netboot image’s <em>boot.cfg</em> as an interactive iPXE boot menu with a 5 second countdown timer:</p>
<pre>$ MY_FVER=29 $ MY_KRNL=$(ls -c /fc$MY_FVER/lib/modules | head -n 1) $ MY_DNS1=192.0.2.91 $ MY_DNS2=192.0.2.92 $ MY_NAME=server-01.example.edu $ MY_EMAN=$(echo $MY_NAME | tr '.' "\n" | tac | tr "\n" '.' | cut -b -${#MY_NAME}) $ MY_ADDR=$(host -t A $MY_NAME | awk '{print $4}') $ cat << END > $HOME/esp/linux/boot.cfg #!ipxe set timeout 5000 :menu menu iPXE Boot Menu item --key 1 lcl 1. Microsoft Windows 10 item --key 2 f$MY_FVER 2. RedHat Fedora $MY_FVER choose --timeout \${timeout} --default lcl selected || goto shell set timeout 0 goto \${selected} :failed echo boot failed, dropping to shell... goto shell :shell echo type 'exit' to get the back to the menu set timeout 0 shell goto menu :lcl exit :f$MY_FVER kernel --name kernel.efi \${prefix}/vmlinuz-$MY_KRNL initrd=initrd.img ro ip=dhcp rd.peerdns=0 nameserver=$MY_DNS1 nameserver=$MY_DNS2 root=/dev/disk/by-path/ip-$MY_ADDR:3260-iscsi-iqn.$MY_EMAN:fc$MY_FVER-lun-1 netroot=iscsi:$MY_ADDR::::iqn.$MY_EMAN:fc$MY_FVER console=tty0 console=ttyS0,115200n8 audit=0 selinux=0 quiet initrd --name initrd.img \${prefix}/initramfs-$MY_KRNL.img boot || goto failed END</pre>
<p>The above menu has five sections:</p>
<ul>
<li><strong>menu</strong> defines the actual menu that will be shown on the screen.</li>
<li><strong>failed</strong> notifies the user that something went wrong and drops the user to a shell so they can troubleshot the problem.</li>
<li><strong>shell</strong> provides an interactive command prompt. You can reach it either by pressing the <strong>Esc</strong> key while at the boot menu or if the “boot” command returns with a failure code.</li>
<li><strong>lcl</strong> contains a single command that tells iPXE to <em>exit</em> and return control back to the BIOS. Whatever you want to boot by default (e.g. the workstation’s local hard drive) <strong>must</strong> be listed as the next boot item right after iPXE in your workstation’s BIOS.</li>
<li><strong>f29</strong> contains the same netboot code used earlier but with the final <em>exit</em> replaced with <em>goto failed</em>.</li>
</ul>
<p>Copy the updated <em>boot.cfg</em> from your <em>$HOME/esp/linux</em> directory out to the ESPs of all your client systems. If all goes well, you should see results similar to the image below:</p>
<p><img class="alignnone size-large wp-image-23652" src="http://www.sickgaming.net/blog/wp-content/uploads/2018/12/how-to-build-a-netboot-server-part-3.png" alt="" width="616" height="386" /></p>
<h2>A server hosted boot menu</h2>
<p>Another feature you can add to the netboot server is the ability to manage all the client boot menus from one central location. This feature can be especially useful when rolling out a new version of the OS. It lets you perform a sort of <a href="https://en.wikipedia.org/wiki/Atomicity_(database_systems)" target="_blank" rel="noopener">atomic transaction</a> to switch all clients over to the new OS after you’ve copied the new kernel and initramfs out to the ESPs of all the clients.</p>
<p>Install Mojolicious:</p>
<pre>$ sudo -i # dnf install -y perl-Mojolicious</pre>
<p>Define the “bootmenu” app:</p>
<pre># mkdir /opt/bootmenu # cat << END > /opt/bootmenu/bootmenu.pl #!/usr/bin/env perl use Mojolicious::Lite; use Mojolicious::Plugins; plugin 'Config'; get '/menu'; app->start; END # chmod 755 /opt/bootmenu/bootmenu.pl</pre>
<p>Define the configuration file for the bootmenu app:</p>
<pre># cat << END > /opt/bootmenu/bootmenu.conf { hypnotoad => { listen => ['http://*:80'], pid_file => '/run/bootmenu/bootmenu.pid', } } END</pre>
<p>This is an extremely simple Mojolicious application that listens on port 80 and only answers to <em>/menu</em> requests. If you want a quick introduction to what Mojolicious can do, run <em>man Mojolicious::Guides::Growing</em> to view the manual. Use the <strong>Q</strong> key to quit the manual.</p>
<p>Move <em>boot.cfg</em> over to our netboot app as a template named <em>menu.html.ep:</em></p>
<pre># mkdir /opt/bootmenu/templates # mv $HOME/esp/linux/boot.cfg /opt/bootmenu/templates/menu.html.ep</pre>
<p>Define a systemd service to manage the bootmenu app:</p>
<pre># cat << END > /etc/systemd/system/bootmenu.service [Unit] Description=Serves iPXE Menus over HTTP After=network-online.target [Service] Type=forking DynamicUser=true RuntimeDirectory=bootmenu PIDFile=/run/bootmenu/bootmenu.pid ExecStart=/usr/bin/hypnotoad /opt/bootmenu/bootmenu.pl ExecReload=/usr/bin/hypnotoad /opt/bootmenu/bootmenu.pl AmbientCapabilities=CAP_NET_BIND_SERVICE KillMode=process [Install] WantedBy=multi-user.target END</pre>
<p>Add an exception for the HTTP service to the local firewall and start the bootmenu service:</p>
<pre># firewall-cmd --add-service http # firewall-cmd --runtime-to-permanent # systemctl enable bootmenu.service # systemctl start bootmenu.service</pre>
<p>Test it with <em>wget</em>:</p>
<pre>$ sudo dnf install -y wget $ MY_BOOTMENU_SERVER=server-01.example.edu $ wget -q -O - http://$MY_BOOTMENU_SERVER/menu</pre>
<p>The above command should output something similar to the following:</p>
<pre>#!ipxe set timeout 5000 :menu menu iPXE Boot Menu item --key 1 lcl 1. Microsoft Windows 10 item --key 2 f29 2. RedHat Fedora 29 choose --timeout ${timeout} --default lcl selected || goto shell set timeout 0 goto ${selected} :failed echo boot failed, dropping to shell... goto shell :shell echo type 'exit' to get the back to the menu set timeout 0 shell goto menu :lcl exit :f29 kernel --name kernel.efi ${prefix}/vmlinuz-4.19.4-300.fc29.x86_64 initrd=initrd.img ro ip=dhcp rd.peerdns=0 nameserver=192.0.2.91 nameserver=192.0.2.92 root=/dev/disk/by-path/ip-192.0.2.158:3260-iscsi-iqn.edu.example.server-01:fc29-lun-1 netroot=iscsi:192.0.2.158::::iqn.edu.example.server-01:fc29 console=tty0 console=ttyS0,115200n8 audit=0 selinux=0 quiet initrd --name initrd.img ${prefix}/initramfs-4.19.4-300.fc29.x86_64.img boot || goto failed</pre>
<p>Now that the boot menu server is working, rebuild the <em>ipxe.efi</em> bootloader with an init script that points to it.</p>
<p>First, update the <em>init.ipxe</em> script created in part one of this series:</p>
<pre>$ MY_BOOTMENU_SERVER=server-01.example.edu $ cat << END > $HOME/ipxe/init.ipxe #!ipxe dhcp || exit set prefix file:///linux chain http://$MY_BOOTMENU_SERVER/menu || exit END</pre>
<p>Now, rebuild the boot loader:</p>
<pre>$ cd $HOME/ipxe/src $ make clean $ make bin-x86_64-efi/ipxe.efi EMBED=../init.ipxe</pre>
<p>Copy the updated bootloader to your ESP:</p>
<pre>$ cp $HOME/ipxe/src/bin-x86_64-efi/ipxe.efi $HOME/esp/efi/boot/bootx64.efi</pre>
<p>After you’ve copied the updated bootloader to all your clients, you can make future updates to the boot menu simply by editing <em>/opt/bootmenu/templates/menu.html.ep</em> and running:</p>
<pre>$ sudo systemctl restart bootmenu.service</pre>
<h2>Making further changes</h2>
<p>If the boot menu server is working properly, you’ll longer need the the <em>boot.cfg</em> file on your client systems.</p>
<p>For example, re-add the Fedora 28 image to the boot menu:</p>
<pre>$ sudo -i # MY_FVER=28 # MY_KRNL=$(ls -c /fc$MY_FVER/lib/modules | head -n 1) # MY_DNS1=192.0.2.91 # MY_DNS2=192.0.2.92 # MY_NAME=$(</etc/hostname) # MY_EMAN=$(echo $MY_NAME | tr '.' "\n" | tac | tr "\n" '.' | cut -b -${#MY_NAME}) # MY_ADDR=$(host -t A $MY_NAME | awk '{print $4}') # cat << END >> /opt/bootmenu/templates/menu.html.ep :f$MY_FVER kernel --name kernel.efi \${prefix}/vmlinuz-$MY_KRNL initrd=initrd.img ro ip=dhcp rd.peerdns=0 nameserver=$MY_DNS1 nameserver=$MY_DNS2 root=/dev/disk/by-path/ip-$MY_ADDR:3260-iscsi-iqn.$MY_EMAN:fc$MY_FVER-lun-1 netroot=iscsi:$MY_ADDR::::iqn.$MY_EMAN:fc$MY_FVER console=tty0 console=ttyS0,115200n8 audit=0 selinux=0 quiet initrd --name initrd.img \${prefix}/initramfs-$MY_KRNL.img boot || goto failed END # sed -i "/item --key 2/a item --key 3 f$MY_FVER 3. RedHat Fedora $MY_FVER" /opt/bootmenu/templates/menu.html.ep # systemctl restart bootmenu.service</pre>
<p>If all goes well, your clients should see results similar to the image below the next time they boot:</p>
<p><img class="alignnone size-large wp-image-23660" src="http://www.sickgaming.net/blog/wp-content/uploads/2018/12/how-to-build-a-netboot-server-part-3-1.png" alt="" width="616" height="386" /></p>
</div>