Files
linux-scripts/install-pxe-server.sh
T
chiefgeek a1a17e81a1 Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist,
same-site referer bypass, Blackbox-Exporter allowed bot, and all
new exporters, cheat sheets, and automation scripts.
2026-05-25 03:31:08 +02:00

935 lines
29 KiB
Bash

#!/usr/bin/env bash
# ============================================================================
# install-pxe-server.sh
# Automated PXE boot server setup — installs dnsmasq, TFTP, and nginx,
# configures PXE boot menus, generates Kickstart and Preseed templates
#
# Author: Phil Connor
# Contact: contact@mylinux.work
# License: MIT
# Version: 1.0.0
# ============================================================================
set -uo pipefail
# ============================================================================
# Defaults
# ============================================================================
INTERFACE=""
DHCP_RANGE="10.0.0.100,10.0.0.200"
TFTP_ROOT="/srv/tftp"
HTTP_ROOT="/var/www/pxe"
DISTROS="rocky9"
SERVER_IP=""
OS_FAMILY=""
OS_ID=""
OS_VERSION=""
# ============================================================================
# Colour output
# ============================================================================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
log_info() { echo -e "${CYAN}[INFO]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
log_success() { echo -e "${GREEN}[OK]${NC} $*"; }
# ============================================================================
# Usage
# ============================================================================
usage() {
cat <<EOF
Usage: $(basename "$0") [OPTIONS]
Automated PXE boot server setup. Installs and configures dnsmasq (DHCP + TFTP),
nginx (HTTP), generates PXE boot menus, Kickstart and Preseed templates.
Options:
--interface <iface> Network interface for DHCP/TFTP binding
(default: auto-detected from default route)
--dhcp-range <start,end> DHCP range as start,end
(default: 10.0.0.100,10.0.0.200)
--tftp-root <path> TFTP root directory
(default: /srv/tftp)
--http-root <path> HTTP document root for install media
(default: /var/www/pxe)
--distros <list> Comma-separated list of distros to configure
(default: rocky9)
--help Print this help and exit
Supported distros:
rocky9, rocky8, rhel9, rhel8, alma9,
ubuntu2404, ubuntu2204, debian12, debian11
Examples:
sudo $(basename "$0")
sudo $(basename "$0") --interface eth0 --dhcp-range 10.0.0.100,10.0.0.200
sudo $(basename "$0") --distros rocky9,ubuntu2404,debian12
sudo $(basename "$0") --interface ens192 --tftp-root /data/tftp --distros rocky9
EOF
exit 0
}
# ============================================================================
# Parse arguments
# ============================================================================
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--interface)
INTERFACE="$2"
shift 2
;;
--dhcp-range)
DHCP_RANGE="$2"
shift 2
;;
--tftp-root)
TFTP_ROOT="$2"
shift 2
;;
--http-root)
HTTP_ROOT="$2"
shift 2
;;
--distros)
DISTROS="$2"
shift 2
;;
--help)
usage
;;
*)
log_error "Unknown option: $1"
usage
;;
esac
done
}
# ============================================================================
# Detect OS
# ============================================================================
detect_os() {
log_info "Detecting operating system..."
if [[ ! -f /etc/os-release ]]; then
log_error "Cannot detect OS — /etc/os-release not found"
exit 1
fi
# shellcheck disable=SC1091
source /etc/os-release
OS_ID="${ID}"
OS_VERSION="${VERSION_ID}"
case "${OS_ID}" in
debian|ubuntu)
OS_FAMILY="debian"
;;
rocky|rhel|almalinux|centos)
OS_FAMILY="rhel"
;;
*)
log_error "Unsupported OS: ${OS_ID} ${OS_VERSION}"
log_error "Supported: Debian 11+, Ubuntu 22.04+, RHEL/Rocky/Alma 8+"
exit 1
;;
esac
log_success "Detected ${OS_ID} ${OS_VERSION} (${OS_FAMILY} family)"
}
# ============================================================================
# Detect network interface
# ============================================================================
detect_interface() {
if [[ -n "${INTERFACE}" ]]; then
if ! ip link show "${INTERFACE}" &>/dev/null; then
log_error "Interface ${INTERFACE} does not exist"
echo "Available interfaces:"
ip -o link show | awk -F': ' '{print " " $2}'
exit 1
fi
log_info "Using specified interface: ${INTERFACE}"
else
log_info "Auto-detecting network interface..."
INTERFACE=$(ip route show default 2>/dev/null | awk '{print $5; exit}')
if [[ -z "${INTERFACE}" ]]; then
log_error "Cannot detect default network interface"
log_error "Specify one with --interface"
exit 1
fi
log_success "Detected interface: ${INTERFACE}"
fi
SERVER_IP=$(ip -4 addr show "${INTERFACE}" | awk '/inet / {split($2,a,"/"); print a[1]; exit}')
if [[ -z "${SERVER_IP}" ]]; then
log_error "Cannot determine IP address for interface ${INTERFACE}"
exit 1
fi
log_success "Server IP: ${SERVER_IP}"
}
# ============================================================================
# Validate distro list
# ============================================================================
validate_distros() {
local valid_distros="rocky9 rocky8 rhel9 rhel8 alma9 ubuntu2404 ubuntu2204 debian12 debian11"
IFS=',' read -ra DISTRO_LIST <<< "${DISTROS}"
for distro in "${DISTRO_LIST[@]}"; do
local found=false
for valid in ${valid_distros}; do
if [[ "${distro}" == "${valid}" ]]; then
found=true
break
fi
done
if [[ "${found}" == "false" ]]; then
log_error "Invalid distro: ${distro}"
log_error "Valid options: ${valid_distros}"
exit 1
fi
done
log_success "Distros to configure: ${DISTROS}"
}
# ============================================================================
# Install packages
# ============================================================================
install_packages() {
log_info "Installing packages..."
if [[ "${OS_FAMILY}" == "debian" ]]; then
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq \
dnsmasq \
tftpd-hpa \
syslinux-common \
pxelinux \
nginx \
wget \
curl \
>/dev/null 2>&1
if [[ $? -ne 0 ]]; then
log_error "Package installation failed"
exit 1
fi
elif [[ "${OS_FAMILY}" == "rhel" ]]; then
dnf install -y -q \
dnsmasq \
tftp-server \
syslinux \
nginx \
wget \
curl \
>/dev/null 2>&1
if [[ $? -ne 0 ]]; then
log_error "Package installation failed"
exit 1
fi
fi
log_success "Packages installed"
}
# ============================================================================
# Configure dnsmasq
# ============================================================================
configure_dnsmasq() {
log_info "Configuring dnsmasq..."
local dhcp_start dhcp_end
dhcp_start=$(echo "${DHCP_RANGE}" | cut -d',' -f1)
dhcp_end=$(echo "${DHCP_RANGE}" | cut -d',' -f2)
# Disable default dnsmasq DNS to avoid conflicts
if [[ -f /etc/dnsmasq.conf ]]; then
cp /etc/dnsmasq.conf /etc/dnsmasq.conf.bak
fi
mkdir -p /etc/dnsmasq.d
cat > /etc/dnsmasq.d/pxe.conf <<EOF
# PXE Boot Server Configuration
# Generated by install-pxe-server.sh
# Interface binding
interface=${INTERFACE}
bind-interfaces
# Disable DNS (use dnsmasq only for DHCP + TFTP)
port=0
# DHCP range — 1 hour lease
dhcp-range=${dhcp_start},${dhcp_end},255.255.255.0,1h
# PXE boot options — BIOS
dhcp-boot=pxelinux.0,,${SERVER_IP}
# UEFI detection — serve GRUB for UEFI clients
dhcp-match=set:efi-x86_64,option:client-arch,7
dhcp-match=set:efi-x86_64,option:client-arch,9
dhcp-boot=tag:efi-x86_64,grubx64.efi,,${SERVER_IP}
# TFTP server
enable-tftp
tftp-root=${TFTP_ROOT}
# Set next-server for PXE
dhcp-option=66,${SERVER_IP}
# Logging
log-dhcp
log-queries
log-facility=/var/log/dnsmasq-pxe.log
EOF
# Ensure main config includes the .d directory
if [[ -f /etc/dnsmasq.conf ]]; then
if ! grep -q "^conf-dir=/etc/dnsmasq.d" /etc/dnsmasq.conf; then
echo "conf-dir=/etc/dnsmasq.d/,*.conf" >> /etc/dnsmasq.conf
fi
fi
log_success "dnsmasq configured at /etc/dnsmasq.d/pxe.conf"
}
# ============================================================================
# Setup TFTP directory
# ============================================================================
setup_tftp() {
log_info "Setting up TFTP directory..."
mkdir -p "${TFTP_ROOT}/pxelinux.cfg"
mkdir -p "${TFTP_ROOT}/grub"
# Copy syslinux/pxelinux files
if [[ "${OS_FAMILY}" == "debian" ]]; then
local pxe_src="/usr/lib/PXELINUX"
local sys_src="/usr/lib/syslinux/modules/bios"
if [[ -f "${pxe_src}/pxelinux.0" ]]; then
cp "${pxe_src}/pxelinux.0" "${TFTP_ROOT}/"
else
log_warn "pxelinux.0 not found at ${pxe_src}/pxelinux.0"
fi
for file in ldlinux.c32 menu.c32 libmenu.c32 libutil.c32; do
if [[ -f "${sys_src}/${file}" ]]; then
cp "${sys_src}/${file}" "${TFTP_ROOT}/"
else
log_warn "${file} not found at ${sys_src}/${file}"
fi
done
elif [[ "${OS_FAMILY}" == "rhel" ]]; then
local sys_src="/usr/share/syslinux"
for file in pxelinux.0 ldlinux.c32 menu.c32 libmenu.c32 libutil.c32; do
if [[ -f "${sys_src}/${file}" ]]; then
cp "${sys_src}/${file}" "${TFTP_ROOT}/"
else
log_warn "${file} not found at ${sys_src}/${file}"
fi
done
fi
# Create distro directories in TFTP root
IFS=',' read -ra DISTRO_LIST <<< "${DISTROS}"
for distro in "${DISTRO_LIST[@]}"; do
mkdir -p "${TFTP_ROOT}/${distro}"
done
# Set permissions
chmod -R 755 "${TFTP_ROOT}"
if [[ "${OS_FAMILY}" == "debian" ]]; then
chown -R tftp:tftp "${TFTP_ROOT}"
# Configure tftpd-hpa
cat > /etc/default/tftpd-hpa <<EOF
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="${TFTP_ROOT}"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="--secure --verbose"
EOF
elif [[ "${OS_FAMILY}" == "rhel" ]]; then
chown -R nobody:nobody "${TFTP_ROOT}"
fi
log_success "TFTP directory structure created at ${TFTP_ROOT}"
}
# ============================================================================
# Create PXE boot menu
# ============================================================================
create_pxe_menu() {
log_info "Generating PXE boot menu..."
local menu_file="${TFTP_ROOT}/pxelinux.cfg/default"
cat > "${menu_file}" <<'MENU_HEADER'
DEFAULT menu.c32
PROMPT 0
TIMEOUT 300
ONTIMEOUT local
MENU TITLE ===== PXE Boot Server =====
MENU COLOR border 30;44 #40ffffff #a0000000 std
MENU COLOR title 1;36;44 #9033cccc #a0000000 std
MENU COLOR sel 7;37;40 #e0ffffff #20ffffff all
MENU COLOR unsel 37;44 #50ffffff #a0000000 std
MENU COLOR help 37;40 #c0ffffff #a0000000 std
MENU COLOR timeout_msg 37;40 #80ffffff #00000000 std
MENU COLOR timeout 1;37;40 #c0ffffff #00000000 std
LABEL local
MENU LABEL Boot from local disk
MENU DEFAULT
LOCALBOOT 0
MENU_HEADER
IFS=',' read -ra DISTRO_LIST <<< "${DISTROS}"
for distro in "${DISTRO_LIST[@]}"; do
case "${distro}" in
rocky9)
cat >> "${menu_file}" <<EOF
LABEL rocky9
MENU LABEL Install Rocky Linux 9
KERNEL rocky9/vmlinuz
APPEND initrd=rocky9/initrd.img inst.ks=http://${SERVER_IP}/ks/ks-rocky9.cfg ip=dhcp
EOF
;;
rocky8)
cat >> "${menu_file}" <<EOF
LABEL rocky8
MENU LABEL Install Rocky Linux 8
KERNEL rocky8/vmlinuz
APPEND initrd=rocky8/initrd.img inst.ks=http://${SERVER_IP}/ks/ks-rocky8.cfg ip=dhcp
EOF
;;
rhel9)
cat >> "${menu_file}" <<EOF
LABEL rhel9
MENU LABEL Install RHEL 9
KERNEL rhel9/vmlinuz
APPEND initrd=rhel9/initrd.img inst.ks=http://${SERVER_IP}/ks/ks-rhel9.cfg ip=dhcp
EOF
;;
rhel8)
cat >> "${menu_file}" <<EOF
LABEL rhel8
MENU LABEL Install RHEL 8
KERNEL rhel8/vmlinuz
APPEND initrd=rhel8/initrd.img inst.ks=http://${SERVER_IP}/ks/ks-rhel8.cfg ip=dhcp
EOF
;;
alma9)
cat >> "${menu_file}" <<EOF
LABEL alma9
MENU LABEL Install AlmaLinux 9
KERNEL alma9/vmlinuz
APPEND initrd=alma9/initrd.img inst.ks=http://${SERVER_IP}/ks/ks-alma9.cfg ip=dhcp
EOF
;;
ubuntu2404)
cat >> "${menu_file}" <<EOF
LABEL ubuntu2404
MENU LABEL Install Ubuntu 24.04 LTS
KERNEL ubuntu2404/linux
APPEND initrd=ubuntu2404/initrd.gz auto=true priority=critical url=http://${SERVER_IP}/preseed/preseed-ubuntu2404.cfg interface=auto
EOF
;;
ubuntu2204)
cat >> "${menu_file}" <<EOF
LABEL ubuntu2204
MENU LABEL Install Ubuntu 22.04 LTS
KERNEL ubuntu2204/linux
APPEND initrd=ubuntu2204/initrd.gz auto=true priority=critical url=http://${SERVER_IP}/preseed/preseed-ubuntu2204.cfg interface=auto
EOF
;;
debian12)
cat >> "${menu_file}" <<EOF
LABEL debian12
MENU LABEL Install Debian 12 (Bookworm)
KERNEL debian12/linux
APPEND initrd=debian12/initrd.gz auto=true priority=critical url=http://${SERVER_IP}/preseed/preseed-debian12.cfg interface=auto
EOF
;;
debian11)
cat >> "${menu_file}" <<EOF
LABEL debian11
MENU LABEL Install Debian 11 (Bullseye)
KERNEL debian11/linux
APPEND initrd=debian11/initrd.gz auto=true priority=critical url=http://${SERVER_IP}/preseed/preseed-debian11.cfg interface=auto
EOF
;;
esac
done
log_success "PXE boot menu created at ${menu_file}"
}
# ============================================================================
# Generate Kickstart template
# ============================================================================
generate_kickstart() {
local distro="$1"
local ks_dir="${HTTP_ROOT}/ks"
local ks_file="${ks_dir}/ks-${distro}.cfg"
mkdir -p "${ks_dir}"
log_info "Generating Kickstart template: ${ks_file}"
cat > "${ks_file}" <<EOF
# Kickstart configuration for ${distro}
# Generated by install-pxe-server.sh
# Edit this file to match your environment
# Install method — update URL to match your repo path
url --url="http://${SERVER_IP}/${distro}/"
text
reboot
# Locale
lang en_US.UTF-8
keyboard us
timezone UTC --utc
# Network
network --bootproto=dhcp --device=link --activate --onboot=yes
network --hostname=${distro}-host
# Authentication
# Generate hash: python3 -c "import crypt; print(crypt.crypt('password', crypt.mksalt(crypt.METHOD_SHA512)))"
rootpw --iscrypted \$6\$rounds=4096\$CHANGEME\$CHANGEME_HASH_HERE
# Create admin user
user --name=admin --groups=wheel --iscrypted --password=\$6\$rounds=4096\$CHANGEME\$CHANGEME_HASH_HERE
# Security
selinux --enforcing
firewall --enabled --service=ssh
# Disk partitioning — auto with LVM
ignoredisk --only-use=sda
clearpart --all --initlabel --drives=sda
autopart --type=lvm --fstype=xfs
# Bootloader
bootloader --append="crashkernel=auto" --location=mbr --boot-drive=sda
# Package selection
%packages
@^minimal-environment
@standard
bash-completion
vim-enhanced
tmux
curl
wget
net-tools
bind-utils
chrony
rsync
%end
# Post-install script
%post --log=/root/ks-post.log
#!/bin/bash
# Enable SSH
systemctl enable --now sshd
# Update packages
dnf update -y
# Configure chrony
systemctl enable --now chronyd
# Set up automatic security updates
dnf install -y dnf-automatic
sed -i 's/apply_updates = no/apply_updates = yes/' /etc/dnf/automatic.conf
systemctl enable --now dnf-automatic-install.timer
# Disable root SSH login
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
echo "Post-install completed: \$(date)" >> /root/ks-post.log
%end
EOF
log_success "Kickstart template created: ${ks_file}"
}
# ============================================================================
# Generate Preseed template
# ============================================================================
generate_preseed() {
local distro="$1"
local preseed_dir="${HTTP_ROOT}/preseed"
local preseed_file="${preseed_dir}/preseed-${distro}.cfg"
mkdir -p "${preseed_dir}"
log_info "Generating Preseed template: ${preseed_file}"
local mirror_host="deb.debian.org"
local mirror_dir="/debian"
if [[ "${distro}" == ubuntu* ]]; then
mirror_host="archive.ubuntu.com"
mirror_dir="/ubuntu"
fi
cat > "${preseed_file}" <<EOF
# Preseed configuration for ${distro}
# Generated by install-pxe-server.sh
# Edit this file to match your environment
### Locale and keyboard
d-i debian-installer/locale string en_US.UTF-8
d-i keyboard-configuration/xkb-keymap select us
d-i console-setup/ask_detect boolean false
### Network
d-i netcfg/choose_interface select auto
d-i netcfg/get_hostname string ${distro}-host
d-i netcfg/get_domain string lab.local
d-i netcfg/hostname string ${distro}-host
### Mirror
d-i mirror/country string manual
d-i mirror/http/hostname string ${mirror_host}
d-i mirror/http/directory string ${mirror_dir}
d-i mirror/http/proxy string
### Clock and timezone
d-i clock-setup/utc boolean true
d-i time/zone string UTC
d-i clock-setup/ntp boolean true
### Partitioning — guided LVM entire disk
d-i partman-auto/method string lvm
d-i partman-auto-lvm/guided_size string max
d-i partman-lvm/device_remove_lvm boolean true
d-i partman-md/device_remove_md boolean true
d-i partman-lvm/confirm boolean true
d-i partman-lvm/confirm_nooverwrite boolean true
d-i partman-auto/choose_recipe select atomic
d-i partman-partitioning/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
### User account
# Generate hash: mkpasswd -m sha-512 'password'
d-i passwd/root-login boolean false
d-i passwd/user-fullname string Admin User
d-i passwd/username string admin
d-i passwd/user-password-crypted password \$6\$rounds=4096\$CHANGEME\$CHANGEME_HASH_HERE
### Package selection
tasksel tasksel/first multiselect standard, ssh-server
d-i pkgsel/include string bash-completion vim tmux curl wget net-tools rsync
d-i pkgsel/upgrade select full-upgrade
popularity-contest popularity-contest/participate boolean false
### GRUB bootloader
d-i grub-installer/only_debian boolean true
d-i grub-installer/bootdev string default
### Post-install commands
d-i preseed/late_command string \\
in-target systemctl enable ssh ; \\
echo "Post-install completed: \$(date)" >> /target/root/preseed-post.log
### Reboot after install
d-i finish-install/reboot_in_progress note
d-i debian-installer/exit/poweroff boolean false
EOF
log_success "Preseed template created: ${preseed_file}"
}
# ============================================================================
# Configure nginx
# ============================================================================
configure_nginx() {
log_info "Configuring nginx..."
mkdir -p "${HTTP_ROOT}"/{ks,preseed}
# Create distro directories in HTTP root
IFS=',' read -ra DISTRO_LIST <<< "${DISTROS}"
for distro in "${DISTRO_LIST[@]}"; do
mkdir -p "${HTTP_ROOT}/${distro}"
done
# Remove default site if present
rm -f /etc/nginx/sites-enabled/default 2>/dev/null
rm -f /etc/nginx/conf.d/default.conf 2>/dev/null
cat > /etc/nginx/conf.d/pxe.conf <<EOF
# PXE Boot HTTP Server
# Generated by install-pxe-server.sh
server {
listen 80;
server_name _;
root ${HTTP_ROOT};
location / {
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;
}
location /ks/ {
alias ${HTTP_ROOT}/ks/;
autoindex on;
}
location /preseed/ {
alias ${HTTP_ROOT}/preseed/;
autoindex on;
}
}
EOF
# Test nginx config
if nginx -t &>/dev/null; then
log_success "nginx configured at /etc/nginx/conf.d/pxe.conf"
else
log_error "nginx configuration test failed"
nginx -t
exit 1
fi
}
# ============================================================================
# Configure firewall
# ============================================================================
configure_firewall() {
log_info "Configuring firewall..."
if command -v firewall-cmd &>/dev/null && systemctl is-active --quiet firewalld; then
log_info "Detected firewalld"
firewall-cmd --permanent --add-port=67/udp # DHCP
firewall-cmd --permanent --add-port=68/udp # DHCP client
firewall-cmd --permanent --add-port=69/udp # TFTP
firewall-cmd --permanent --add-port=80/tcp # HTTP
firewall-cmd --permanent --add-port=4011/udp # ProxyDHCP
firewall-cmd --reload
log_success "Firewall ports opened (firewalld)"
elif command -v ufw &>/dev/null && ufw status | grep -q "Status: active"; then
log_info "Detected ufw"
ufw allow 67/udp comment "DHCP server"
ufw allow 68/udp comment "DHCP client"
ufw allow 69/udp comment "TFTP"
ufw allow 80/tcp comment "HTTP"
ufw allow 4011/udp comment "ProxyDHCP"
log_success "Firewall ports opened (ufw)"
else
log_warn "No active firewall detected — skipping firewall configuration"
log_warn "Manually open ports: 67/udp, 68/udp, 69/udp, 80/tcp, 4011/udp"
fi
}
# ============================================================================
# Enable services
# ============================================================================
enable_services() {
log_info "Enabling and starting services..."
# dnsmasq
systemctl enable dnsmasq
systemctl restart dnsmasq
if systemctl is-active --quiet dnsmasq; then
log_success "dnsmasq is running"
else
log_error "dnsmasq failed to start"
journalctl -u dnsmasq --no-pager -n 10
fi
# TFTP
if [[ "${OS_FAMILY}" == "debian" ]]; then
systemctl enable tftpd-hpa
systemctl restart tftpd-hpa
if systemctl is-active --quiet tftpd-hpa; then
log_success "tftpd-hpa is running"
else
log_error "tftpd-hpa failed to start"
journalctl -u tftpd-hpa --no-pager -n 10
fi
elif [[ "${OS_FAMILY}" == "rhel" ]]; then
systemctl enable tftp.socket
systemctl restart tftp.socket
if systemctl is-active --quiet tftp.socket; then
log_success "tftp.socket is running"
else
log_error "tftp.socket failed to start"
journalctl -u tftp.socket --no-pager -n 10
fi
fi
# nginx
systemctl enable nginx
systemctl restart nginx
if systemctl is-active --quiet nginx; then
log_success "nginx is running"
else
log_error "nginx failed to start"
journalctl -u nginx --no-pager -n 10
fi
}
# ============================================================================
# Print summary
# ============================================================================
print_summary() {
local dhcp_start dhcp_end
dhcp_start=$(echo "${DHCP_RANGE}" | cut -d',' -f1)
dhcp_end=$(echo "${DHCP_RANGE}" | cut -d',' -f2)
echo ""
echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ PXE Boot Server — Setup Complete ║"
echo "╠══════════════════════════════════════════════════════════════╣"
echo "║ ║"
printf "║ Server IP: %-41s║\n" "${SERVER_IP}"
printf "║ Interface: %-41s║\n" "${INTERFACE}"
printf "║ DHCP Range: %-41s║\n" "${dhcp_start}${dhcp_end}"
printf "║ TFTP Root: %-41s║\n" "${TFTP_ROOT}"
printf "║ HTTP Root: %-41s║\n" "${HTTP_ROOT}"
printf "║ Distros: %-41s║\n" "${DISTROS}"
echo "║ ║"
echo "╠══════════════════════════════════════════════════════════════╣"
echo "║ Configuration Files ║"
echo "╠══════════════════════════════════════════════════════════════╣"
printf "║ dnsmasq: %-41s║\n" "/etc/dnsmasq.d/pxe.conf"
printf "║ PXE menu: %-41s║\n" "${TFTP_ROOT}/pxelinux.cfg/default"
printf "║ nginx: %-41s║\n" "/etc/nginx/conf.d/pxe.conf"
echo "║ ║"
IFS=',' read -ra DISTRO_LIST <<< "${DISTROS}"
for distro in "${DISTRO_LIST[@]}"; do
case "${distro}" in
rocky*|rhel*|alma*)
printf "║ Kickstart: %-41s║\n" "http://${SERVER_IP}/ks/ks-${distro}.cfg"
;;
ubuntu*|debian*)
printf "║ Preseed: %-41s║\n" "http://${SERVER_IP}/preseed/preseed-${distro}.cfg"
;;
esac
done
echo "║ ║"
echo "╠══════════════════════════════════════════════════════════════╣"
echo "║ Next Steps ║"
echo "╠══════════════════════════════════════════════════════════════╣"
echo "║ ║"
echo "║ 1. Mount or extract distro ISOs into the HTTP root: ║"
for distro in "${DISTRO_LIST[@]}"; do
printf "║ mount -o loop,ro <iso> %-33s║\n" "${HTTP_ROOT}/${distro}/"
done
echo "║ ║"
echo "║ 2. Copy kernel + initrd to TFTP root: ║"
for distro in "${DISTRO_LIST[@]}"; do
printf "║ %s -> %-43s║\n" "vmlinuz/initrd" "${TFTP_ROOT}/${distro}/"
done
echo "║ ║"
echo "║ 3. Edit Kickstart/Preseed templates: ║"
echo "║ - Set root/user password hashes ║"
echo "║ - Adjust partitioning layout ║"
echo "║ - Add site-specific packages ║"
echo "║ ║"
echo "║ 4. PXE boot a target machine and select a distro ║"
echo "║ ║"
echo "╚══════════════════════════════════════════════════════════════╝"
echo ""
}
# ============================================================================
# Main
# ============================================================================
main() {
echo ""
echo "================================================"
echo " PXE Boot Server — Automated Setup"
echo " Version 1.0.0"
echo "================================================"
echo ""
# Check root
if [[ "${EUID}" -ne 0 ]]; then
log_error "This script must be run as root"
exit 1
fi
parse_args "$@"
detect_os
detect_interface
validate_distros
install_packages
configure_dnsmasq
setup_tftp
create_pxe_menu
# Generate answer file templates based on selected distros
IFS=',' read -ra DISTRO_LIST <<< "${DISTROS}"
for distro in "${DISTRO_LIST[@]}"; do
case "${distro}" in
rocky*|rhel*|alma*)
generate_kickstart "${distro}"
;;
ubuntu*|debian*)
generate_preseed "${distro}"
;;
esac
done
configure_nginx
configure_firewall
enable_services
print_summary
log_success "PXE boot server setup complete"
}
main "$@"