a1a17e81a1
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.
935 lines
29 KiB
Bash
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 "$@"
|