Building a PXE boot server

This post is the first in a series on building/setting up/improving a PXE boot server with CentOS. By the end of this series we’ll have a fully configured PXE boot server and be able to perform unattended installations of CentOS 7.
Inside my homelab, I build and tear down VM’s often, and wanted a way to relatively quickly build a new machine without having to go through the process manually. After some research, I realised that this entirely possible with a handful of packages and some setup/configuration.

I’m going to assume you’ve already got a VM setup for this, but for convenience I’m going to set this up on a new VM, with the details/specs as follows:

Server IP: 10.176.40.10
Hostname: b-pxe.ad.goatfarm.co.uk
OS: CentOS 7
CPU: 2 cores
RAM: 2GB
Hard disk: 100GB (thin provisioned)
Firewalld is disabled, and SELinux is set to Permissive

The VM will be setup with a standard CentOS Minimal install using the CentOS-7-x86_64-DVD-2003 ISO obtained from the CentOS Mirror List; boot the ISO, run through the setup, select Minimal Install, login at the console once the install has completed and set an IP and hostname. Reboot the VM again for good measure and then we can SSH into it.

Install required packages

Now the VM is back up and running, we can start the setup. You’re going to want to install the following packages/run the following command:

yum -y install tftp-server vsftpd xinetd syslinux pykickstart wget nano 

Note that we’re not installing DNSMASQ server on our VM as lots of other guides will tell you to do – that’s handled on our router/firewall.


Populate SYSLINUX bootloaders

At this point, we’re ready to start copying files and editing configs. To kick things off, we’re going to copy all of the SYSLINUX bootloader files into our tftpboot folder. We can quickly do this with the following commands to copy and then verify the copy was successful:

cp -r /usr/share/syslinux/* /var/lib/tftpboot
ls /var/lib/tftpboot

Enable and start services

Before we can continue, we need to start and enable the services we’ve just installed:

systemctl start xinetd vsftpd
systemctl enable xinetd vsftpd

Now we’ve copied the bootloader files, we also need to edit the TFTP server config file, which can be found at /etc/xinetd.d/tftp.
We just need to change a single option within the file; disable = yes to disabled = no. Open the file with a text editor (nano, vi, etc) change, save and quit.
Another quick way to do this would be to use the sed command as follows:

sed -i '14 s/yes/no/' /etc/xinetd.d/tftp

Copy CentOS installation files

Now we’re going to want to copy our installation files from our CentOS DVD image to the FTP directory. Before we do this, we’re going to need to create a directory for the files to go into within the FTP directory:

mkdir /var/ftp/centos7


Assuming the DVD is still in the VM’s virtual DVD drive, we quickly mount it and verify that it’s mounted correctly:

mount -o loop /dev/cdrom /mnt
ls /mnt

Next, we can copy the files with and then verify they copied successfully:

cp -av /mnt/* /var/ftp/centos7/
ls /var/ftp/centos7

Finally, we need to copy the CentOS bootable kernel and initrd files from the DVD to the tftpboot folder, and unmount the installation DVD:

mkdir /var/lib/tftpboot/centos7
cp /mnt/images/pxeboot/vmlinuz /var/lib/tftpboot/centos7
cp /mnt/images/pxeboot/initrd.img /var/lib/tftpboot/centos7
umount /mnt

Note that we’re putting everything related to CentOS 7 within it’s own folders – this is so we can differentiate between each OS if we decide to add another OS to this PXE server at a later date.

Creating Kickstart file

At this point, we’re almost ready to create our basic kickstart file for an unattended installation.
We’re going to create this file in the same location we put out CentOS 7 installation files; /var/ftp/centos7/.
Before we do this, we need to generate ourselves an encrypted root password with a quick Python one-liner – we’ll use this when we create the kickstart file. Run the following command and enter a password when prompted, and the output will be an encrypted password:

python -c 'import crypt,getpass; print(crypt.crypt(getpass.getpass(), crypt.mksalt(crypt.METHOD_SHA512)))'

Take note of the output string, in my case $6$knygtSMiA2ehNpE9$e8k2WyNkZPXozT9Tiw0YMf63k.9YIJNeF7QsxPNA8sboI84fHuw38lyjYbzpNxUfzb/eyjT6LWxM1nxnOkNRy1 – we’ll need this in a moment.
Note that we’re using SHA256 and our authconfig below is also set to sha256.
If out authconfig was set to enablemd5 we could use openssl to generate the string.

Below is an example kickstart file to perform an unattended installation of CentOS 7. The file is relatively self-explanatory – note that this WILL format and erase any drive when used.

You’ll need to replace the FTP IP address on line 5 with the IP of your own server, and change the rootpw value on line 25 to the output string of your password as generated above. Any additional repos or packages can be added on lines 46 and 52 respectively. If you don’t want the VM to update after the install completes, just comment out line 66.

This kickstart file has been saved to the following location: /var/ftp/centos7/anaconda-ks.cfg

#version=DEVEL
# System authorization information
auth --enableshadow --passalgo=sha512
# Use network installation
url --url="ftp://10.176.40.10/centos7"
# Use text install
text
# Run the Setup Agent on first boot
firstboot --enable
ignoredisk --only-use=sda
# Keyboard layouts
keyboard --vckeymap=gb --xlayouts='gb'
# System language
lang en_GB.UTF-8
# Reboot after install
reboot

# Set network information
network --bootproto=dhcp --device=ens192 --nameserver=1.1.1.1 --ipv6=auto --activate
# Disable Firewall/Selinux
firewall --disabled
selinux --disabled

# Root password
rootpw --iscrypted $6$knygtSMiA2ehNpE9$e8k2WyNkZPXozT9Tiw0YMf63k.9YIJNeF7QsxPNA8sboI84fHuw38lyjYbzpNxUfzb/eyjT6LWxM1nxnOkNRy1

# System services
services --enabled="chronyd"
# System timezone
timezone Europe/London --isUtc

################################### partitions
# Partition clearing information
clearpart --all --initlabel --drives=sda
zerombr

# Disk partitioning information
autopart --type=plain --fstype=ext4

# Repos
repo --name=base --baseurl=http://mirror.centos.org/centos/7.3.1611/os/x86_64/
repo --name=epel-release --baseurl=http://anorien.csc.warwick.ac.uk/mirrors/epel/7/x86_64/

%packages
@base
@core
epel-release
bzip2
wget
curl
nano
htop
%end

%addon com_redhat_kdump --enable --reserve-mb='auto'

%end

%anaconda
pwpolicy root --minlen=6 --minquality=1 --notstrict --nochanges --notempty
pwpolicy user --minlen=6 --minquality=1 --notstrict --nochanges --emptyok
pwpolicy luks --minlen=6 --minquality=1 --notstrict --nochanges --notempty
%end

%post
yum update -y

%end

To verify our kickstart is correctly understood and is usable, we can use the ksvalidator command from the pykickstart package we installed earlier. If there are any problems, you’ll receive an error message telling you what’s wrong.

Creating PXE boot menu file

Next we’re going to create our PXE boot menu. This is where we can select between different options to install different OS’s. For the moment, our PXE menu is going to contain a couple of options for installing CentOS 7 with/without a kickstart, and with/without our FTP network repo we setup above.
To create the directory and default menu file, we’ll need to run the following commands:

mkdir /var/lib/tftpboot/pxelinux.cfg 
touch /var/lib/tftpboot/pxelinux.cfg/default

Below is an example of a boot menu – this allows you to boot the CentOS installation
– Without using a kickstart file
– Using our local repo and kickstart file
– Using an external repo and our own kickstart file
– To the local drive and skip the installation

you’ll need to replace the IP address on lines 11, 16 and 21 with the IP of your own server.
Note that the kernel lines are point to files relative to itself within the pxelinux.cfg directory – these are the bootloader files we coped over earlier.

This boot menu file has been saved to the following location: /var/lib/tftpboot/pxelinux.cfg/default

default menu.c32
prompt 0
timeout 300
ONTIMEOUT local

menu title ########## PXE Boot Menu ##########

label 1
menu label ^1) Install clean CentOS 7 x64 with Network Repo
kernel centos7/vmlinuz
append initrd=centos7/initrd.img method=ftp://10.176.40.10/centos7 devfs=nomount

label 2
menu label ^2) Install Kickstarted CentOS 7 x64 with Network Repo
kernel centos7/vmlinuz
append initrd=centos7/initrd.img method=ftp://10.176.40.10/centos7 devfs=nomount inst.ks=ftp://10.176.40.10/centos7/anaconda-ks.cfg

label 3
menu label ^3) Install Kickstarted CentOS 7 x64 with http://mirror.centos.org Repo
kernel centos7/vmlinuz
append initrd=centos7/initrd.img method=http://mirror.centos.org/centos/7/os/x86_64/ inst.ks=ftp://10.176.40.10/centos7/anaconda-ks.cfg

label 4
menu label ^4) Boot from local drive
LOCALBOOT 0

Enable and start services

Now we’re ready to enable and start our services.

systemctl enable vsftpd
systemctl enable xinetd
systemctl start vsftpd
systemctl start xinetd

Configure DHCP/firewall to allow PXE booting

The final thing we need to configure before we can attempt to PXE boot a client is our router/firewall. Within the DHCP settings of our router/firewall we need to edit/enable the following settings:
– TFTP Server
– Next Server
– Default file name
If we’re unable to configure them specific settings, we can also specify DHCP options 66 and 67.

In my examples, I’m going to be configuring these settings on PFSense and also a Unifi UDM Pro.

On our PFSense, we’re going to want to navigate to Services > DHCP Server > Network name.
Scroll to the bottom of the page and find TFTP. Click Display Advanced and enter the IP of your PXE server – in my case, 10.176.40.10.
Next, scroll down to Network Booting and click Display Advanced. Tick the checkbox for Enables network booting and enter the IP of your PXE server in the Next Server box.
In the Default BIOS file name box we’re going to enter the following: pxelinux.0 – This is one of the files we copied right at the beginning.

After doing this, fire up a test machine and try to PXE boot. If you get the correct boot menu/etc then we’re good to go!

For the second example on our Unifi UDM Pro, we’re going to want to navigate to Settings from the dashboard. From Settings, navigate to Networks on the left-hand side, select your network on the right-hand side, and click Edit.
Scroll down the page and find Advanced DHCP Options. Tick the Enable network boot checkbox, and enter the IP of your PXE server – in my case, 10.176.40.10. In the Filename box, enter pxelinux.0. The last box we need to enter details in is the DHCP TFTP Server – enter the IP of your PXE server.

Scroll down, hit the Save button, and then try to PXE boot a client.

If everything has worked successfully, we should be able to select the second boot option, and after a few moments, the automated installation should begin.

Once the VM has completed the installation and rebooted, you’ll be landed at a localhost login prompt, and the VM will have installed as per the kickstart file we created – you’ll be able to login as root with the password we encrypted, and use the packages we specified in the kickstart file (wget,curl,nano,htop). The VM will have a DHCP IP so you’ll need to configure it with a static IP if required, and also set a hostname.

… Aaaaaand that concludes this post!
In another post, I’m going to go through the process of modifying our kickstart file to prompt for a hostname/IP address, and also explain how we can automate some configuration of packages through the kickstart file and post-scripts. Stay tuned!

Danny Written by: