PXE (with grub2)
This page goes over setting up a pxeboot system using tftp on Enterprise Linux or Fedora
Requirements¶
Here are the list of requirements below.
- Enterprise Linux 8, 9, or Fedora
- A DHCP server setup that allows you to setup the
next_server
directive or setup the tftp server location - Optionally if you are using a local mirror,
httpd
ornginx
installed. (This guide assumeshttpd
)
Tutorial Preface, Notes, and Recommendations¶
In some environments, it may be better (or easier, depending on your perspective) to setup a PXE server and roll out systems in a lab or otherwise in that fashion. It's one of the most straight forward ways to build out systems easily and consistently. The difference between a typical PXE setup and this is we're using grub2 menus, rather than the classic menu style. This makes it simpler to keep all configurations consistent between classic boot and EFI boot.
If you plan on using supporting other architectures, it will be easier to use that architecture to run the grub2-mknetdir command and brings those to your tftp server.
Cobbler¶
While cobbler is a perfectly viable solution to setting up a pxeboot system for various distros and configurations, it is out of scope for this article. It is unknown if it sets up or directly supports grub2.
Server Setup¶
This section goes over the server setup portion for the tftp server.
TFTP¶
Let's install the tftpserver package plus some additional grub packages. If you are wanting other architectures, you can obtain the other grub2 module packages from your distribution's BaseOS or equivalent repository for that architecture and install it manually.
# x86_64
% dnf install \
grub2-efi-x64-modules \
grub2-tools-extra \
grub2-pc-modules \
shim-ia32 \ # this does not exist on el9+
tftp-server
# aarch64
% dnf install \
grub2-efi-aa64-modules \
grub2-tools-extra \
tftp-server
Let's make our initial net directories and ensure the selinux contexts are correct.
Secure Boot
Secure Boot does not work with the default files provided. You will
need to obtain the shimx64.efi
or shim.efi
file from your
distribution's shim-{x64,aa64} package.
% grub2-mknetdir --net-directory /var/lib/tftpboot/
Netboot directory for i386-pc created. Configure your DHCP server to point to /srv/tftp/boot/grub2/i386-pc/core.0
Netboot directory for x86_64-efi created. Configure your DHCP server to point to /srv/tftp/boot/grub2/x86_64-efi/core.efi
% restorecon -R /var/lib/tftpboot
Now you'll need to enable the tftp socket and open the port. Traditionally, you would use xinetd. It's no longer required for the tftp service.
# Note: This is port 69 with the UDP protocol
% firewall-cmd --add-service=tftp --permanent
% systemctl enable tftp.socket --now
DHCP (ISC)¶
On your DHCP server configuration (typically /etc/dhcp/dhcpd.conf if running on Fedora or EL), you should set the following options:
option pxe-system-type code 93 = unsigned integer 16;
option rfc3442-classless-static-routes code 121 = array of integer 8;
option ms-classless-static-routes code 249 = array of integer 8;
option space pxelinux;
option pxelinux.magic code 208 = string;
option pxelinux.configfile code 209 = text;
option pxelinux.pathprefix code 210 = text;
option pxelinux.reboottime code 211 = unsigned integer 32;
option architecture-type code 93 = unsigned integer 16;
option pxelinux.mtftp-ip code 1 = ip-address;
option pxelinux.mtftp-cport code 2 = unsigned integer 16;
option pxelinux.mtftp-sport code 3 = unsigned integer 16;
option pxelinux.mtftp-tmout code 4 = unsigned integer 8;
option pxelinux.mtftp-delay code 5 = unsigned integer 8;
Whether this section is within a subnet block or not, it is needed to ensure the right bootloader is called. Note that we're only loading x86. If you are loading armhfp, use 00:0a. If you are loading aarch64, use 00:0b.
class "pxeclients" {
match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
# x86_64 EFI
if option pxe-system-type = 00:07 {
filename "boot/grub2/x86_64-efi/core.efi";
} else if option pxe-system-type = 00:08 {
filename "boot/grub2/x86_64-efi/core.efi";
} else if option pxe-system-type = 00:09 {
filename "boot/grub2/x86_64-efi/core.efi";
} else {
# BIOS boot only
filename "boot/grub2/i386-pc/core.0";
}
}
Note that in your subnet blocks, you should also mention next_server
,
which should point to your TFTP server. The DHCP and TFTP server can be
on the same machine and there's nothing stopping you from doing that;
next_server
needs to be set regardless here. See an example below of a
full work dhcpd.conf.
ddns-update-style interim;
allow booting;
allow bootp;
authoritative;
log-facility local6;
ignore client-updates;
set vendorclass = option vendor-class-identifier;
## Allowing EFI Clients
option pxe-system-type code 93 = unsigned integer 16;
option rfc3442-classless-static-routes code 121 = array of integer 8;
option ms-classless-static-routes code 249 = array of integer 8;
option space pxelinux;
option pxelinux.magic code 208 = string;
option pxelinux.configfile code 209 = text;
option pxelinux.pathprefix code 210 = text;
option pxelinux.reboottime code 211 = unsigned integer 32;
option architecture-type code 93 = unsigned integer 16;
option pxelinux.mtftp-ip code 1 = ip-address;
option pxelinux.mtftp-cport code 2 = unsigned integer 16;
option pxelinux.mtftp-sport code 3 = unsigned integer 16;
option pxelinux.mtftp-tmout code 4 = unsigned integer 8;
option pxelinux.mtftp-delay code 5 = unsigned integer 8;
subnet 10.100.0.0 netmask 255.255.255.0 {
interface br1000;
option routers 10.100.0.1;
option domain-name-servers 10.100.0.1, 10.100.0.231;
option domain-name "angelsofclockwork.net";
option subnet-mask 255.255.255.0;
range 10.100.0.110 10.100.0.199;
## EFI Client Catch
class "pxeclients" {
match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
if option pxe-system-type = 00:07 {
filename "boot/grub2/x86_64-efi/core.efi";
} else if option pxe-system-type = 00:08 {
filename "boot/grub2/x86_64-efi/core.efi";
} else if option pxe-system-type = 00:09 {
filename "boot/grub2/x86_64-efi/core.efi";
} else if option pxe-system-type = 00:0a {
filename "boot/grub2/armv7a-efi/core.efi";
} else if option pxe-system-type = 00:0b {
filename "boot/grub2/aarch64-efi/core.efi";
} else {
filename "boot/grub2/i386-pc/core.0";
}
}
default-lease-time 21600;
max-lease-time 43200;
next-server 10.100.0.1;
}
Ensure that the dhcpd service is restarted after making the necessary changes.
DHCP (Kea)¶
Kea is a different configuration style from ISC. Some of the configuration is the same and also frustratingly different.
You'll need to turn on some options, similar to dhcpd. In the Dhcp4
section,
we'll set them.
...
"option-def": [
{
"space": "dhcp4",
"name": "rfc3442-classless-static-routes",
"code": 121,
"array": true,
"type": "int8"
},
{
"space": "dhcp4",
"name": "ms-classless-static-routes",
"code": 249,
"array": true,
"type": "int8"
},
{
"space": "pxelinux",
"name": "magic",
"code": 208,
"type": "string"
},
{
"space": "pxelinux",
"name": "configfile",
"code": 209,
"type": "string"
},
{
"space": "pxelinux",
"name": "pathprefix",
"code": 210,
"type": "string"
},
{
"space": "pxelinux",
"name": "reboottime",
"code": 211,
"type": "uint32"
},
{
"space": "pxelinux",
"name": "mtftp-ip",
"code": 1,
"type": "ipv4-address"
},
{
"space": "pxelinux",
"name": "mtftp-cport",
"code": 2,
"type": "uint16"
},
{
"space": "pxelinux",
"name": "mtftp-sport",
"code": 3,
"type": "uint16"
},
{
"space": "pxelinux",
"name": "mtftp-tmout",
"code": 4,
"type": "uint8"
},
{
"space": "pxelinux",
"name": "mtftp-delay",
"code": 5,
"type": "uint8"
},
{
"space": "dhcp4",
"name": "iscsi-initiator-iqn",
"code": 203,
"type": "string"
},
{
"name": "PXEDiscoveryControl",
"code": 6,
"space": "vendor-encapsulated-options-space",
"type": "uint8",
"array": false
},
{
"name": "PXEMenuPrompt",
"code": 10,
"space": "vendor-encapsulated-options-space",
"type": "record",
"array": false,
"record-types": "uint8,string"
},
{
"name": "PXEBootMenu",
"code": 9,
"space": "vendor-encapsulated-options-space",
"type": "record",
"array": false,
"record-types": "uint16,uint8,string"
}
],
...
Unlike dhcpd, we'll see the class information also in the Dhcp4
section. This
will make it effectively work in all subnets. The below also enables x86, ARM,
and POWER systems.
...
"client-classes": [
{ "name": "PXEClient-x86_64-1", "test": "substring(option[60].hex,0,20) == 'PXEClient:Arch:00007'", "boot-file-name": "boot/grub2/x86_64-efi/core.efi" },
{ "name": "PXEClient-x86_64-2", "test": "substring(option[60].hex,0,20) == 'PXEClient:Arch:00008'", "boot-file-name": "boot/grub2/x86_64-efi/core.efi" },
{ "name": "PXEClient-x86_64-3", "test": "substring(option[60].hex,0,20) == 'PXEClient:Arch:00009'", "boot-file-name": "boot/grub2/x86_64-efi/core.efi" },
{ "name": "PXEClient-aarch64-1", "test": "substring(option[60].hex,0,20) == 'PXEClient:Arch:0000b'", "boot-file-name": "boot/grub2/arm64-efi/core.efi" },
{ "name": "PXEClient-ppc64le-1", "test": "substring(option[60].hex,0,20) == 'PXEClient:Arch:0000e'", "boot-file-name": "boot/grub2/powerpc-ieee1275/core.elf" },
/// these are whatever
{ "name": "PXEClient-i386-1", "test": "substring(option[60].hex,0,20) == 'PXEClient:Arch:00006'", "boot-file-name": "boot/grub2/i386-pc/core.0" },
{ "name": "PXEClient-i386-3", "test": "substring(option[60].hex,0,20) == 'PXEClient:Arch:00000'", "boot-file-name": "boot/grub2/i386-pc/core.0" },
{ "name": "PXEClient-i386-2", "test": "substring(option[60].hex,0,20) == 'PXEClient:Arch:00002'", "boot-file-name": "elilo.efi" }
],
...
Subnet blocks are straight forward. They also will sit in Dhcp4
. Note that
each subnet block will need a unique id
number. Ensure next_server
is
setup correctly also.
...
"subnet4": [
{
"id": 1,
"subnet": "10.100.0.0/24",
"interface": "br1000",
"option-data": [
{
"space": "dhcp4",
"name": "routers",
"code": 3,
"data": "10.100.0.1"
},
{
"space": "dhcp4",
"name": "domain-name-servers",
"code": 6,
"data": "10.100.0.1, 10.100.0.231"
},
{
"space": "dhcp4",
"name": "domain-name",
"code": 15,
"data": "angelsofclockwork.net"
},
{
"space": "dhcp4",
"name": "subnet-mask",
"code": 1,
"data": "255.255.255.0"
}
],
"pools": [
{
"pool": "10.100.0.110 - 10.100.0.199"
}
],
"valid-lifetime": 21600,
"max-valid-lifetime": 43200,
"next-server": "10.100.0.1",
"reservations": []
},
...
Web Server (httpd)¶
If we plan on hosting the installation mirror in your environment, it's recommended to stand up a simple web server. It does not require any kind of special configuration. We'll use the default /var/www/html/ path. If you wish to use another such as /srv/www, you will need to setup a virtual host (this is outside the scope of this page).
% dnf install httpd -y
% systemctl enable httpd --now
% firewall-cmd --add-service=http --permanent
% firewall-cmd --complete-reload
# create the directories for our distributions
% mkdir -p /var/www/html/os/{fedora,centos,rocky}
Setting up Grub¶
When you run grub2-mknetdir, it created a core.* set of files. An accompanying grub.cfg must sit next to them. To prevent a duplication of work, it can be simplified by making all grub configurations at /var/lib/tftpboot and then symlink them next to each directory containing core.*. Let's make a very, very simple one.
set default=0
set timeout=60
menuentry 'EFI Firmware System Setup' $menuentry_id_option 'uefi-firmware' {
fwsetup
}
menuentry 'Reboot' {
reboot
}
menuentry 'Shutdown' {
halt
}
Now let's just symlink it.
% cd /var/lib/tftpboot/boot/grub2/x86_64-efi
% ln -s ../../../grub.cfg
% cd /var/lib/tftpboot/boot/grub2/i386-pc
% ln -s ../../../grub.cfg
This should produce a grub menu for both EFI and BIOS systems that contain three bootable options.
Adding Distributions¶
Now that grub is sort of setup, we should add a distribution to it at least. Below are a couple examples using Fedora, Rocky Linux, and CentOS Stream.
Note
When setting up for UEFI, if linux
and initrd
do not work for you,
you may need to use linuxefi
and initrdefi
instead. This should be
a rare case.
Rocky Linux¶
Setting up Rocky Linux (or any other Enterprise Linux distribution) should be straight forward. We'll download both Rocky Linux 8 and Rocky Linux 9 and setup the menus.
Note
If you plan on not hosting a mirror of the base repositories, ensure that your inst.repo/inst.stage2 commands are accurate to a mirror of your choice.
The below assumes we are hosting a mirror of the downloaded ISO, which will make installations quicker as it'll be confined to your network.
% cd /var/tmp
# Rocky Linux 8
% wget https://dl.rockylinux.org/pub/rocky/8/isos/x86_64/Rocky-8-latest-x86_64-dvd.iso
# Rocky Linux 9
% wget https://dl.rockylinux.org/pub/rocky/9/isos/x86_64/Rocky-9-latest-x86_64-dvd.iso
# Optionally, if you plan on supporting ARM...
% wget https://dl.rockylinux.org/pub/rocky/8/isos/aarch64/Rocky-8-latest-aarch64-dvd.iso
% wget https://dl.rockylinux.org/pub/rocky/9/isos/aarch64/Rocky-9-latest-aarch64-dvd.iso
Here we'll copy the data we want into the necessary directories. Any pxeboot related images will go to /var/lib/tftpboot/rocky-X-ARCH (X being the major version, ARCH being the architecture). If we are keeping a local mirror of the DVD, we'll put it into /var/www/html/os/rocky/X/ARCH. Below is for x86_64, but the same steps can be repeated for aarch64 without any issues. Just replace x86_64 with aarch64.
## Rocky 8
% mount -o loop Rocky-8-latest-x86_64-dvd.iso /mnt
% mkdir /var/lib/tftpboot/rocky-8-x86_64
% cp /mnt/images/pxeboot/* /var/lib/tftpboot/rocky-8-x86_64
% mkdir -p /var/www/html/os/rocky/8/x86_64
% rsync -vrlptDSH --delete /mnt/ /var/www/html/os/rocky/8/x86_64
% umount /mnt
## Rocky 9
% mount -o loop Rocky-9-latest-x86_64-dvd.iso /mnt
% mkdir /var/lib/tftpboot/rocky-9-x86_64
% cp /mnt/images/pxeboot/* /var/lib/tftpboot/rocky-9-x86_64
% mkdir -p /var/www/html/os/rocky/9/x86_64
% rsync -vrlptDSH --delete /mnt/ /var/www/html/os/rocky/9/x86_64
% umount /mnt
# Copy the appropriate files over for the kernels
% mkdir -p /var/lib/tftpboot/rocky-{8,9}-x86_64
% cp /var/www/html/os/rocky/8/x86_64/images/pxeboot/* /var/lib/tftpboot/rocky-8-x86_64
% cp /var/www/html/os/rocky/9/x86_64/images/pxeboot/* /var/lib/tftpboot/rocky-9-x86_64
% restorecon -R /var/www/html/os/rocky
% restorecon -R /var/lib/tftpboot
At this point, we'll need to setup the grub menus. We'll setup non-kickstart examples for BIOS and UEFI.
. . .
# Rocky 8
menuentry 'Install Rocky Linux 8 (No KS) (UEFI)' --class fedora --class gnu-linux --class gnu --class os {
echo "Loading Rocky Linux 8 kernel..."
linux rocky-8-x86_64/vmlinuz inst.repo=http://10.100.0.1/os/rocky/8/x86_64 inst.stage2=http://10.100.0.1/os/rocky/8/x86_64 ip=dhcp
initrd rocky-8-x86_64/initrd.img
}
menuentry 'Install Rocky Linux 8 (No KS) (BIOS)' --class fedora --class gnu-linux --class gnu --class os {
echo "Loading Rocky Linux 8 kernel..."
linux16 rocky-8-x86_64/vmlinuz inst.repo=http://10.100.0.1/os/rocky/8/x86_64 inst.stage2=http://10.100.0.1/os/rocky/8/x86_64 ip=dhcp
initrd16 rocky-8-x86_64/initrd.img
}
# if you are setting up arm...
menuentry 'Install Rocky Linux 8 (No KS) (aarch64)' --class fedora --class gnu-linux --class gnu --class os {
echo "Loading Rocky Linux 8 kernel..."
linux rocky-9-aarch64/vmlinuz inst.repo=http://10.100.0.1/os/rocky/8/aarch64 inst.stage2=http://10.100.0.1/os/rocky/8/aarch64 ip=dhcp
initrd rocky-9-aarch64/initrd.img
}
. . .
# Rocky 9
menuentry 'Install Rocky Linux 9 (No KS) (UEFI)' --class fedora --class gnu-linux --class gnu --class os {
echo "Loading Rocky Linux 9 kernel..."
linux rocky-9-x86_64/vmlinuz inst.repo=http://10.100.0.1/os/rocky/9/x86_64 inst.stage2=http://10.100.0.1/os/rocky/9/x86_64 ip=dhcp
initrd rocky-9-x86_64/initrd.img
}
menuentry 'Install Rocky Linux 9 (No KS) (BIOS)' --class fedora --class gnu-linux --class gnu --class os {
echo "Loading Rocky Linux 9 kernel..."
linux16 rocky-9-x86_64/vmlinuz inst.repo=http://10.100.0.1/os/rocky/9/x86_64 inst.stage2=http://10.100.0.1/os/rocky/9/x86_64 ip=dhcp
initrd16 rocky-9-x86_64/initrd.img
}
# if you are setting up arm...
menuentry 'Install Rocky Linux 9 (No KS) (aarch64)' --class fedora --class gnu-linux --class gnu --class os {
echo "Loading Rocky Linux 9 kernel..."
linux rocky-9-aarch64/vmlinuz inst.repo=http://10.100.0.1/os/rocky/9/aarch64 inst.stage2=http://10.100.0.1/os/rocky/9/aarch64 ip=dhcp
initrd rocky-9-aarch64/initrd.img
}
The Rocky Linux installation should now be bootable.
CentOS Stream¶
Much like Rocky Linux (or other derivatives), the path is the same for setting it up.
Using upstream mirror path
If you plan on not hosting a mirror of the base repositories, ensure that your inst.repo/inst.stage2 commands are accurate to a mirror of your choice.
% cd /var/tmp
# CentOS Stream 9
% wget -O CentOS-Stream-9-latest-x86_64-dvd1.iso \
'https://mirrors.centos.org/mirrorlist?path=/9-stream/BaseOS/x86_64/iso/CentOS-Stream-9-latest-x86_64-dvd1.iso&redirect=1&protocol=https'
# Optionally, if you plan on supporting ARM...
% wget -O CentOS-Stream-9-latest-aarch64-dvd1.iso \
'https://mirrors.centos.org/mirrorlist?path=/9-stream/BaseOS/aarch64/iso/CentOS-Stream-9-latest-aarch64-dvd1.iso&redirect=1&protocol=https'
Here we'll copy the data we want into the necessary directories. Any pxeboot related images will go to /var/lib/tftpboot/rocky-X-ARCH (X being the major version, ARCH being the architecture). If we are keeping a local mirror of the DVD, we'll put it into /var/www/html/os/rocky/X/ARCH. Below is for x86_64, but the same steps can be repeated for aarch64 without any issues. Just replace x86_64 with aarch64.
## CentOS Stream 9
% mount -o loop CentOS-Stream-9-latest-x86_64-dvd1.iso /mnt
% mkdir /var/lib/tftpboot/centos-9-x86_64
% cp /mnt/images/pxeboot/* /var/lib/tftpboot/centos-9-x86_64
% mkdir -p /var/www/html/os/centos/9/x86_64
% rsync -vrlptDSH --delete /mnt/ /var/www/html/os/centos/9/x86_64
% umount /mnt
% mkdir -p /var/lib/tftpboot/centos-9-x86_64
% cp /var/www/html/os/centos/9/x86_64/images/pxeboot/* /var/lib/tftpboot/centos-9-x86_64
% restorecon -R /var/www/html/os/centos/9
% restorecon -R /var/lib/tftpboot
At this point, we'll need to setup the grub menus. We'll setup non-kickstart examples for BIOS and UEFI.
. . .
# CentOS Stream 9
menuentry 'Install CentOS Stream 9 (No KS) (UEFI)' --class fedora --class gnu-linux --class gnu --class os {
echo "Loading CentOS Stream 9 kernel..."
linux centos-9-x86_64/vmlinuz inst.repo=http://10.100.0.1/os/centos/9/x86_64 inst.stage2=http://10.100.0.1/os/centos/9/x86_64 ip=dhcp
initrd centos-9-x86_64/initrd.img
}
menuentry 'Install CentOS Stream 9 (No KS) (BIOS)' --class fedora --class gnu-linux --class gnu --class os {
echo "Loading CentOS Stream 9 kernel..."
linux16 centos-9-x86_64/vmlinuz inst.repo=http://10.100.0.1/os/centos/9/x86_64 inst.stage2=http://10.100.0.1/os/centos/9/x86_64 ip=dhcp
initrd16 centos-9-x86_64/initrd.img
}
# if you are setting up arm...
menuentry 'Install CentOS Stream 9 (No KS) (aarch64)' --class fedora --class gnu-linux --class gnu --class os {
echo "Loading CentOS Stream 9 kernel..."
linux centos-9-aarch64/vmlinuz inst.repo=http://10.100.0.1/os/centos/9/aarch64 inst.stage2=http://10.100.0.1/os/centos/9/aarch64 ip=dhcp
initrd centos-9-aarch64/initrd.img
}
The CentOS Stream installation should now be bootable.
Fedora¶
Let's put up a regular installer with no kickstart for Fedora. This does not involve pulling down any ISO's and will rely entirely on using upstream repositories.
% cd /var/lib/tftpboot
% mkdir fedora-x86_64
% cd fedora-x86_64
# Replace XX with the current fedora version
% wget https://dl.fedoraproject.org/pub/fedora/linux/releases/XX/Everything/x86_64/os/images/pxeboot/initrd.img
% wget https://dl.fedoraproject.org/pub/fedora/linux/releases/XX/Everything/x86_64/os/images/pxeboot/vmlinuz
# If you want arm systems... aarch64
% cd ..
% mkdir fedora-aarch64
# Replace XX with the current fedora version
% wget https://dl.fedoraproject.org/pub/fedora/linux/releases/XX/Everything/aarch64/os/images/pxeboot/initrd.img
% wget https://dl.fedoraproject.org/pub/fedora/linux/releases/XX/Everything/aarch64/os/images/pxeboot/vmlinuz
Now we can add a couple menu entry items for Fedora. I'm making both EFI and Classic entries to ensure we can boot both EFI and BIOS systems from the same menu.
. . .
menuentry 'Install Fedora Linux (EFI)' --class fedora --class gnu-linux --class gnu --class os {
linux fedora-x86_64/vmlinuz inst.repo=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os inst.stage2=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os ip=dhcp
initrd fedora-x86_64/initrd.img
}
menuentry 'Install Fedora Linux (Classic)' --class fedora --class gnu-linux --class gnu --class os {
linux16 fedora-x86_64/vmlinuz inst.repo=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os/ inst.stage2=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os/ ip=dhcp
initrd16 fedora-x86_64/initrd.img
}
# Add the below for ARM systems
menuentry 'Install Fedora Linux (ARM)' --class fedora --class gnu-linux --class gnu --class os {
linux fedora-aarch64/vmlinuz inst.repo=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/aarch64/os/ inst.stage2=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/aarch64/os/ ip=dhcp
initrd fedora-aarch64/initrd.img
}
Now the Fedora installation should be bootable.
Customizing Grub¶
Grub is customizable. It is possible to setup background images, choose menu colors, setup themes, and so on.
Colors and Backgrounds¶
Colors and backgrounds are perfectly possible, even in an EFI setup. We'll need to load a few modules and then set the colors and background we want. Note that if you're using a background, it should live in /var/lib/tftpboot to make things easier.
. . .
insmod all_video
insmod gfxterm
insmod gfxterm_menu
insmod gfxmenu
insmod gfxterm_background
insmod png
terminal_output gfxterm
background_image -m stretch /bg.png
set menu_color_highlight=cyan/black
set menu_color_normal=white/black
set color_normal=white/black
. . .
The background would be /var/lib/tftpboot/bg.png in this example. Selected items will appear to be cyan and the typical gray selection box is now transparent, which is done by setting it to black. Everything else should appear as white text with a transparent background. Example below.
Special Submenus¶
Submenus are easily created using submenu in the grub configuration. For example:
submenu 'Fedora Linux' --class fedora --class gnu-linux --class gnu --class os {
set menu_color_highlight=black/light-cyan
set menu_color_normal=white/black
set color_normal=white/black
menuentry 'Install Fedora Linux (EFI)' --class fedora --class gnu-linux --class gnu --class os {
linux fedora-x86_64/vmlinuz inst.repo=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os inst.stage2=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os ip=dhcp
initrd fedora-x86_64/initrd.img
}
menuentry 'Install Fedora Linux (Classic)' --class fedora --class gnu-linux --class gnu --class os {
linux16 fedora-x86_64/vmlinuz inst.repo=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os/ inst.stage2=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os/ ip=dhcp
initrd16 fedora-x86_64/initrd.img
}
menuentry 'Install Fedora Linux (ARM)' --class fedora --class gnu-linux --class gnu --class os {
linux fedora-aarch64/vmlinuz inst.repo=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/aarch64/os/ inst.stage2=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/aarch64/os/ ip=dhcp
initrd fedora-aarch64/initrd.img
}
}
This now means "Fedora Linux" will show up as a menu option and it will take you to a brand new menu with the two listed items, and another color scheme. Note that we created color items because submenus will reset the theme options. Example of how it looks is below.
It is also possible to place everything into separate source-able files. Note that when you do this, you will need to symlink those files just like you did with grub.cfg.
submenu 'Fedora Linux' --class fedora --class gnu-linux --class gnu --class os {
set menu_color_highlight=black/light-cyan
set menu_color_normal=white/black
set color_normal=white/black
source fedora.cfg
}
menuentry 'Install Fedora Linux (EFI)' --class fedora --class gnu-linux --class gnu --class os {
linux fedora-x86_64/vmlinuz inst.repo=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os inst.stage2=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os ip=dhcp
initrd fedora-x86_64/initrd.img
}
menuentry 'Install Fedora Linux (Classic)' --class fedora --class gnu-linux --class gnu --class os {
linux16 fedora-x86_64/vmlinuz inst.repo=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os/ inst.stage2=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os/ ip=dhcp
initrd16 fedora-x86_64/initrd.img
}
menuentry 'Install Fedora Linux (ARM)' --class fedora --class gnu-linux --class gnu --class os {
linux fedora-aarch64/vmlinuz inst.repo=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/aarch64/os/ inst.stage2=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/aarch64/os/ ip=dhcp
initrd fedora-aarch64/initrd.img
}
Submenus can be nested too. Here's a deeper, working example of my own setup using Fedora 40.
# grub.cfg
set default=0
set timeout=60
insmod all_video
insmod gfxterm
insmod gfxterm_menu
insmod gfxmenu
insmod gfxterm_background
insmod png
terminal_output gfxterm
loadfont /unicode.pf2
background_image -m stretch /bg.png
set menu_color_highlight=cyan/black
set menu_color_normal=white/black
set color_normal=white/black
submenu 'Fedora Linux' --class fedora --class gnu-linux --class gnu --class os {
set menu_color_highlight=black/light-cyan
set menu_color_normal=white/black
set color_normal=white/black
source fedora.cfg
}
menuentry 'EFI System Setup' $menuentry_id_option 'uefi-firmware' {
fwsetup
}
menuentry 'Reboot' {
reboot
}
menuentry 'Shutdown' {
halt
}
# fedora.cfg
submenu 'Fedora Linux (latest stable)' --class fedora --class gnu-linux --class gnu --class os {
set menu_color_highlight=black/light-cyan
set menu_color_normal=white/black
set color_normal=white/black
# EFI Only
submenu 'EFI Mode' --class fedora --class gnu-linux --class gnu --class os {
set menu_color_highlight=black/light-cyan
set menu_color_normal=white/black
set color_normal=white/black
menuentry 'Install Fedora Linux (No KS)' --class fedora --class gnu-linux --class gnu --class os {
linux fedora-x86_64/vmlinuz inst.repo=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os inst.stage2=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os ip=dhcp
initrd fedora-x86_64/initrd.img
}
menuentry 'Install Fedora Linux' --class fedora --class gnu-linux --class gnu --class os {
linux fedora-x86_64/vmlinuz inst.repo=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os inst.stage2=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os ip=dhcp
initrd fedora-x86_64/initrd.img
}
menuentry 'Fedora Linux (Rescue Mode)' --class fedora --class gnu-linux --class gnu --class os {
linux fedora-x86_64/vmlinuz inst.rescue inst.stage2=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os
initrd fedora-x86_64/initrd.img
}
}
# Classic Only
submenu 'Classic Mode' --class fedora --class gnu-linux --class gnu --class os {
set menu_color_highlight=black/light-cyan
set menu_color_normal=white/black
set color_normal=white/black
menuentry 'Install Fedora Linux (No KS)' --class fedora --class gnu-linux --class gnu --class os {
linux16 fedora-x86_64/vmlinuz inst.repo=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os/ inst.stage2=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os/ ip=dhcp
initrd16 fedora-x86_64/initrd.img
}
menuentry 'Install Fedora Linux' --class fedora --class gnu-linux --class gnu --class os {
linux16 fedora-x86_64/vmlinuz inst.repo=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os/ inst.stage2=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os/ ip=dhcp
initrd16 fedora-x86_64/initrd.img
}
menuentry 'Fedora Linux (Rescue Mode)' --class fedora --class gnu-linux --class gnu --class os {
linux16 fedora-x86_64/vmlinuz inst.rescue inst.stage2=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/x86_64/os/
initrd16 fedora-x86_64/initrd.img
}
}
# EFI mode for ARM
submenu 'EFI Mode (aarch64)' --class fedora --class gnu-linux --class gnu --class os {
set menu_color_highlight=black/light-cyan
set menu_color_normal=white/black
set color_normal=white/black
menuentry 'Install Fedora Linux (No KS)' --class fedora --class gnu-linux --class gnu --class os {
linux fedora-aarch64/vmlinuz inst.repo=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/aarch64/os inst.stage2=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/aarch64/os ip=dhcp
initrd fedora-aarch64/initrd.img
}
menuentry 'Install Fedora Linux' --class fedora --class gnu-linux --class gnu --class os {
linux fedora-aarch64/vmlinuz inst.repo=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/aarch64/os inst.stage2=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/aarch64/os ip=dhcp
initrd fedora-aarch64/initrd.img
}
menuentry 'Fedora Linux (Rescue Mode)' --class fedora --class gnu-linux --class gnu --class os {
linux fedora-aarch64/vmlinuz inst.rescue inst.stage2=http://dl.fedoraproject.org/pub/fedora/linux/releases/40/Everything/aarch64/os
initrd fedora-aarch64/initrd.img
}
}
}
Secure Boot¶
Using the above setup, it is perfectly possible to have Secure Boot working. Instead of using core.efi
, you would use a shimx64.efi
file from the shim package. The grub2-mknetdir
command does not provide this file, so we need to extract it from a running system or download and unpack it.
% dnf download shim-x64
% rpm2cpio shim-x64-15.8-2.el9.x86_64.rpm | cpio -idmv
% ls -l boot/efi/EFI/rocky
total 3656
-rwx------. 1 root root 104 Apr 4 14:23 BOOTX64.CSV
-rwx------. 1 root root 857352 Apr 4 14:23 mmx64.efi
-rwx------. 1 root root 959224 Apr 4 14:23 shim.efi
-rwx------. 1 root root 959224 Apr 4 14:23 shimx64.efi
-rwx------. 1 root root 952016 Apr 4 14:23 shimx64-rocky.efi
Note that both shim.efi
and shimx64.efi
should be the same file. Copying
shimx64.efi
is sufficient enough.
Ensure that your DHCP configuration is now pointing to this file and the
permissions are set to 755
.
Font Issues¶
If you notice your grub2 menu is missing characters (e.g. you see blocks), you
may need to get at least the unicode.pf2 file from the same system you obtained
the shimx64.efi
file. It can be found in /usr/share/grub
. However, this may
not work, and may require you to use the grub2-mkfont
tool.