Photo by Jainath Ponnala on Unsplash
Streamlining Raspberry Pi Deployment: A Guide to Automated Netbooting
In this blog, we'll delve into automating the netbooting process using a bash script (pxeService.sh
) and an address list (addresslist.txt
) to enhance the deployment and management of Raspberry Pi devices. If you haven't already, you can catch up on the initial steps and concepts discussed here, where we laid the groundwork for this automation project.
Bash Script Overview
The pxeService.sh
script is designed to automate the setup for each PXE client (Raspberry Pi device). You can take a look at the complete pxeService.sh
script here. Here's a breakdown of the script:
Constants and Log Paths
In this code snippet, we define constants for the script, such as the UDP port to listen on (PORT=69)
and the path for logging (pxe_service_log_path="/var/log/pxeService.log")
. It's important to ensure that the pxeService.log file is created in the /var/log
directory before running the script. This log file will store the script's output and any logged messages during its execution.
#!/bin/bash
PORT=69
pxe_service_log_path="/var/log/pxeService.log"
Handling Error and Listening to Requests
One of the key components of the script is the use of tcpdump
. This tool plays a critical role in real-time network monitoring. It allows the script to capture and analyze network traffic, specifically UDP requests on a specified port ($PORT
). This capability is fundamental as it enables the script to detect and respond to incoming requests from PXE clients promptly.
# Function to handle errors
handle_error() {
local line_number="$1"
echo "An error occurred at line $line_number" >> "$pxe_service_log_path"
exit 1
}
# Function to process UDP packets
process_udp_packets() {
tcpdump -l -i any -e udp port "$PORT" | while read -r line; do
MAC=$(echo "$line" | awk '{print $6}' | tr ':' '-')
TIME=$(date +"%Y-%m-%d %H:%M:%S")
if grep -q "$MAC" /opt/addresslist.txt; then
echo "$MAC already exists" >> "$pxe_service_log_path"
else
echo "************Executing macscript.sh for $MAC***************" >> "$pxe_service_log_path"
echo "$MAC" >> /opt/addresslist.txt
FILE=$(echo "$line" | grep -o -P '(?<=RRQ ")[^"]+')
FILE=$(awk -F'/' '{print $NF}' <<< "$FILE")
if [ "$FILE" = "start4.elf" ]; then
echo "Time: $TIME, MAC address: $MAC, Requested file: $FILE" >> "$pxe_service_log_path"
configure_pxe_services "$MAC" &
echo "**********macscript execution finished for MAC $MAC**********" >> "$pxe_service_log_path"
else
echo "Not found" >> "$pxe_service_log_path"
fi
fi
done
}
# Trap for handling errors
trap 'handle_error $LINENO' ERR
# Start processing UDP packets
process_udp_packets
Configure dnsmasq.conf
Another vital aspect is configuring dnsmasq
specifically for the TFTP (Trivial File Transfer Protocol) server functionality. In this context, dnsmasq
serves as the TFTP server, which is essential for transferring boot files to PXE clients during the netbooting process. The enable-tftp
option in the configuration enables TFTP support within dnsmasq
, ensuring seamless file transfer operations. Additionally, the tftp-root
parameter specifies the root directory for TFTP, providing a structured environment for storing and accessing boot files required by PXE clients.
# Function to configure dnsmasq service
configure_dnsmasq_service() {
local service_name="$1"
local dnsmasq_conf="/etc/dnsmasq.conf"
if [ "$service_name" == "dnsmasq" ] && systemctl is-active --quiet "dnsmasq"; then
echo "Configuring $dnsmasq_conf..."
read -p "Enter the interface name (e.g., bond0): " interface_name
# Create or append to dnsmasq configuration
cat <<EOF | sudo tee -a "$dnsmasq_conf" > /dev/null
interface=$interface_name
no-hosts
enable-tftp
tftp-root=/srv/tftp
tftp-unique-root=mac
log-facility=/var/log/dnsmasq
port=5353
EOF
echo "Configuration has been added to $dnsmasq_conf" >> "$pxe_service_log_path"
fi
}
Install Necessary Services
The ensure_service_installed function
checks if a specific service is already installed and active. If the service is not active, it attempts to install it using apt and then starts the service. If the installation and start are successful, it logs the outcome; otherwise, it logs a failure message.
# Function to ensure service is installed
ensure_service_installed() {
local service_name="$1"
if systemctl is-active --quiet "$service_name"; then
echo "$service_name is already installed and active." >> "$pxe_service_log_path"
else
echo "Installing and starting $service_name..." >> "$pxe_service_log_path"
sudo apt update
sudo apt install -y "$service_name"
if systemctl is-active --quiet "$service_name"; then
echo "$service_name installed and started successfully." >> "$pxe_service_log_path"
configure_dnsmasq_service "$service_name"
else
echo "Failed to install or start $service_name. Please check and install it manually." >> "$pxe_service_log_path"
fi
fi
}
Copying Boot, Root Files and Exporting the Entries to Exports
The configure_pxe_services
function is responsible for setting up the necessary directories, copying boot and root files, configuring cmdline.txt and fstab files, and adding entries to the exports of the Raspberry Pi server. It performs these actions to ensure the PXE services are properly configured for a specific MAC address, facilitating the netbooting process effectively.
# Function to configure PXE services
configure_pxe_services() {
local mac_address="$1"
local nfs_root="/srv/nfs/$mac_address"
local tftp_root="/srv/tftp/$mac_address"
local nfs_exports="/etc/exports"
# Log start of script execution
echo "**********Configuring PXE services for $mac_address**********" >> "$pxe_service_log_path"
# Ensure required services are installed
ensure_service_installed "nfs-kernel-server"
ensure_service_installed "dnsmasq"
# Create directories for TFTP and NFS
echo "Creating directories for $mac_address in TFTP and NFS" >> "$pxe_service_log_path"
sudo mkdir -p "$tftp_root" "$nfs_root"
# Copy necessary files to NFS and TFTP directories
echo "Copying root files to NFS directory" >> "$pxe_service_log_path"
sudo cp -a "/home/pi/userfiles/"* "$nfs_root"
echo "Finished copying root files" >> "$pxe_service_log_path"
echo "Copying boot files to TFTP directory" >> "$pxe_service_log_path"
sudo cp -r "/home/pi/bootfiles/"* "$tftp_root"
echo "Finished copying boot files" >> "$pxe_service_log_path"
# Configure cmdline.txt for boot parameters
echo "Configuring boot parameters in cmdline.txt" >> "$pxe_service_log_path"
echo -e "console=serial0,115200 console=tty1 root=/dev/nfs nfsroot=192.168.XX.XX:$nfs_root rw rootwait" | sudo tee "$tftp_root/cmdline.txt" > /dev/null
# Update fstab with NFS mount entry
local new_fstab_content="proc /proc proc defaults 0 0\n\
PARTUUID=4e639091-02 / ext4 defaults,noatime 0 1\n\
192.168.XX.XX:$tftp_root /boot nfs defaults 0 0"
echo "Updating fstab with NFS mount entry" >> "$pxe_service_log_path"
echo -e "$new_fstab_content" | sudo tee "$nfs_root/etc/fstab" > /dev/null
# Add NFS and TFTP entries to exports file
echo "Adding NFS export in $nfs_exports" >> "$pxe_service_log_path"
echo "$nfs_root *(rw,sync,no_subtree_check,no_root_squash)" | sudo tee -a "$nfs_exports" > /dev/null
echo "Adding TFTP export in $nfs_exports" >> "$pxe_service_log_path"
echo "$tftp_root *(rw,sync,no_subtree_check,no_root_squash)" | sudo tee -a "$nfs_exports" > /dev/null
# Reload NFS exports
echo "Reloading NFS exports" >> "$pxe_service_log_path"
sudo exportfs -r
# Log completion of script execution
echo "**********Successfully configured PXE services for $mac_address**********" >> "$pxe_service_log_path"
}
Address List Management
The addresslist.txt
file serves as a record of processed MAC addresses. Before executing the setup for a new MAC address, the script checks this list to ensure it hasn't already been configured. This prevents unnecessary duplication of efforts. Make sure to create addresslist.txt
in /opt
directory. Here's an example of how the addresslist.txt
file might look:
00-11-22-33-44-55
aa-bb-cc-dd-ee-ff
Running the Script as a Daemon
To ensure continuous monitoring and execution of the pxeService.sh
script, we can set it up as a daemon process using a systemd service unit file (pxeService.service
). Here's how you can create and configure the service:
Copy the
pxeService.sh
file to/usr/bin
directory.Create a new file named
pxeService.service
in the/etc/systemd/system/
directory.Add the following content to
pxeService.service
:
[Unit]
Description=PXE Service Daemon
After=network.target
[Service]
Type=simple
ExecStart=/bin/bash /usr/bin/pxeService.sh
Restart=always
[Install]
WantedBy=multi-user.target
Save the file and exit the editor, then reload systemd to read the new service unit:
sudo systemctl daemon-reload
Enable and start the pxeService.service
sudo systemctl enable pxeService.service
sudo systemctl start pxeService.service
Now, the pxeService.sh
script will run as a daemon, continuously monitoring and automating the netbooting process for new Raspberry Pi devices.
Conclusion
In this guide, we've automated the booting process using bash scripting. By leveraging automation and creating a daemon service, you can significantly streamline the provisioning and maintenance of Raspberry Pi devices in your network.