- netboot-base.nix with SSH key auth - Launch scripts for node01/02/03 - Node configuration.nix and disko.nix - Nix modules for first-boot automation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
498 lines
14 KiB
Bash
Executable file
498 lines
14 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
###############################################################################
|
|
# PXE Boot Server Setup Script
|
|
#
|
|
# This script prepares a PXE boot server for Centra Cloud bare-metal
|
|
# provisioning. It performs the following tasks:
|
|
#
|
|
# 1. Creates directory structure for boot assets
|
|
# 2. Downloads iPXE bootloaders (or provides build instructions)
|
|
# 3. Copies configuration files to appropriate locations
|
|
# 4. Validates configuration files
|
|
# 5. Tests DHCP/TFTP/HTTP services
|
|
#
|
|
# Usage:
|
|
# sudo ./setup.sh [options]
|
|
#
|
|
# Options:
|
|
# --install Install and configure services
|
|
# --download Download iPXE bootloaders
|
|
# --build-ipxe Build iPXE from source (recommended for production)
|
|
# --validate Validate configuration files
|
|
# --test Test services (DHCP, TFTP, HTTP)
|
|
# --help Show this help message
|
|
#
|
|
# Example:
|
|
# sudo ./setup.sh --install --download --validate
|
|
###############################################################################
|
|
|
|
set -euo pipefail
|
|
|
|
# Configuration
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
BOOT_ASSETS_DIR="/var/lib/pxe-boot"
|
|
IPXE_DIR="${BOOT_ASSETS_DIR}/ipxe"
|
|
NIXOS_DIR="${BOOT_ASSETS_DIR}/nixos"
|
|
TFTP_DIR="/var/lib/tftpboot"
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Logging functions
|
|
log_info() {
|
|
echo -e "${BLUE}[INFO]${NC} $*"
|
|
}
|
|
|
|
log_success() {
|
|
echo -e "${GREEN}[SUCCESS]${NC} $*"
|
|
}
|
|
|
|
log_warning() {
|
|
echo -e "${YELLOW}[WARNING]${NC} $*"
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $*"
|
|
}
|
|
|
|
# Check if running as root
|
|
check_root() {
|
|
if [[ $EUID -ne 0 ]]; then
|
|
log_error "This script must be run as root (use sudo)"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Display help
|
|
show_help() {
|
|
cat << EOF
|
|
PXE Boot Server Setup Script
|
|
|
|
Usage: sudo $0 [options]
|
|
|
|
Options:
|
|
--install Install and configure services
|
|
--download Download iPXE bootloaders from boot.ipxe.org
|
|
--build-ipxe Build iPXE from source (recommended for production)
|
|
--validate Validate configuration files
|
|
--test Test services (DHCP, TFTP, HTTP)
|
|
--clean Clean up boot assets directory
|
|
--help Show this help message
|
|
|
|
Examples:
|
|
# Full setup with pre-built bootloaders
|
|
sudo $0 --install --download --validate
|
|
|
|
# Build iPXE from source (more secure, customizable)
|
|
sudo $0 --install --build-ipxe --validate
|
|
|
|
# Validate configuration only
|
|
sudo $0 --validate
|
|
|
|
# Test services
|
|
sudo $0 --test
|
|
|
|
For more information, see README.md
|
|
EOF
|
|
}
|
|
|
|
# Create directory structure
|
|
create_directories() {
|
|
log_info "Creating directory structure..."
|
|
|
|
mkdir -p "${IPXE_DIR}"
|
|
mkdir -p "${NIXOS_DIR}"
|
|
mkdir -p "${TFTP_DIR}"
|
|
mkdir -p /var/log/dhcpd
|
|
mkdir -p /var/log/nginx
|
|
|
|
# Set permissions
|
|
chown -R nginx:nginx "${BOOT_ASSETS_DIR}" 2>/dev/null || \
|
|
log_warning "nginx user not found, skipping chown (install nginx first)"
|
|
|
|
chmod -R 755 "${BOOT_ASSETS_DIR}"
|
|
|
|
log_success "Directory structure created at ${BOOT_ASSETS_DIR}"
|
|
}
|
|
|
|
# Download iPXE bootloaders
|
|
download_ipxe() {
|
|
log_info "Downloading iPXE bootloaders from boot.ipxe.org..."
|
|
|
|
# URLs for iPXE bootloaders
|
|
IPXE_BASE_URL="https://boot.ipxe.org"
|
|
|
|
# Download BIOS bootloader (undionly.kpxe)
|
|
if [[ ! -f "${IPXE_DIR}/undionly.kpxe" ]]; then
|
|
log_info "Downloading undionly.kpxe (BIOS bootloader)..."
|
|
curl -L -o "${IPXE_DIR}/undionly.kpxe" "${IPXE_BASE_URL}/undionly.kpxe" || {
|
|
log_error "Failed to download undionly.kpxe"
|
|
return 1
|
|
}
|
|
log_success "Downloaded undionly.kpxe ($(du -h "${IPXE_DIR}/undionly.kpxe" | cut -f1))"
|
|
else
|
|
log_info "undionly.kpxe already exists, skipping download"
|
|
fi
|
|
|
|
# Download UEFI bootloader (ipxe.efi)
|
|
if [[ ! -f "${IPXE_DIR}/ipxe.efi" ]]; then
|
|
log_info "Downloading ipxe.efi (UEFI x86-64 bootloader)..."
|
|
curl -L -o "${IPXE_DIR}/ipxe.efi" "${IPXE_BASE_URL}/ipxe.efi" || {
|
|
log_error "Failed to download ipxe.efi"
|
|
return 1
|
|
}
|
|
log_success "Downloaded ipxe.efi ($(du -h "${IPXE_DIR}/ipxe.efi" | cut -f1))"
|
|
else
|
|
log_info "ipxe.efi already exists, skipping download"
|
|
fi
|
|
|
|
# Download UEFI 32-bit bootloader (optional, rare)
|
|
if [[ ! -f "${IPXE_DIR}/ipxe-i386.efi" ]]; then
|
|
log_info "Downloading ipxe-i386.efi (UEFI x86 32-bit bootloader)..."
|
|
curl -L -o "${IPXE_DIR}/ipxe-i386.efi" "${IPXE_BASE_URL}/ipxe-i386.efi" || {
|
|
log_warning "Failed to download ipxe-i386.efi (this is optional)"
|
|
}
|
|
if [[ -f "${IPXE_DIR}/ipxe-i386.efi" ]]; then
|
|
log_success "Downloaded ipxe-i386.efi ($(du -h "${IPXE_DIR}/ipxe-i386.efi" | cut -f1))"
|
|
fi
|
|
else
|
|
log_info "ipxe-i386.efi already exists, skipping download"
|
|
fi
|
|
|
|
# Set permissions
|
|
chmod 644 "${IPXE_DIR}"/*.{kpxe,efi} 2>/dev/null || true
|
|
|
|
log_success "iPXE bootloaders downloaded successfully"
|
|
}
|
|
|
|
# Build iPXE from source
|
|
build_ipxe() {
|
|
log_info "Building iPXE from source..."
|
|
|
|
# Check for required tools
|
|
if ! command -v git &> /dev/null; then
|
|
log_error "git is required to build iPXE"
|
|
return 1
|
|
fi
|
|
|
|
if ! command -v make &> /dev/null; then
|
|
log_error "make is required to build iPXE"
|
|
return 1
|
|
fi
|
|
|
|
# Create temporary build directory
|
|
BUILD_DIR=$(mktemp -d)
|
|
log_info "Build directory: ${BUILD_DIR}"
|
|
|
|
# Clone iPXE repository
|
|
log_info "Cloning iPXE repository..."
|
|
git clone https://github.com/ipxe/ipxe.git "${BUILD_DIR}/ipxe" || {
|
|
log_error "Failed to clone iPXE repository"
|
|
return 1
|
|
}
|
|
|
|
cd "${BUILD_DIR}/ipxe/src"
|
|
|
|
# Build BIOS bootloader
|
|
log_info "Building undionly.kpxe (BIOS bootloader)..."
|
|
make bin/undionly.kpxe || {
|
|
log_error "Failed to build undionly.kpxe"
|
|
return 1
|
|
}
|
|
cp bin/undionly.kpxe "${IPXE_DIR}/undionly.kpxe"
|
|
log_success "Built undionly.kpxe ($(du -h "${IPXE_DIR}/undionly.kpxe" | cut -f1))"
|
|
|
|
# Build UEFI bootloader
|
|
log_info "Building ipxe.efi (UEFI x86-64 bootloader)..."
|
|
make bin-x86_64-efi/ipxe.efi || {
|
|
log_error "Failed to build ipxe.efi"
|
|
return 1
|
|
}
|
|
cp bin-x86_64-efi/ipxe.efi "${IPXE_DIR}/ipxe.efi"
|
|
log_success "Built ipxe.efi ($(du -h "${IPXE_DIR}/ipxe.efi" | cut -f1))"
|
|
|
|
# Clean up
|
|
cd /
|
|
rm -rf "${BUILD_DIR}"
|
|
|
|
# Set permissions
|
|
chmod 644 "${IPXE_DIR}"/*.{kpxe,efi} 2>/dev/null || true
|
|
|
|
log_success "iPXE bootloaders built successfully"
|
|
}
|
|
|
|
# Install boot scripts
|
|
install_boot_scripts() {
|
|
log_info "Installing boot scripts..."
|
|
|
|
# Copy boot.ipxe
|
|
if [[ -f "${SCRIPT_DIR}/ipxe/boot.ipxe" ]]; then
|
|
cp "${SCRIPT_DIR}/ipxe/boot.ipxe" "${IPXE_DIR}/boot.ipxe"
|
|
chmod 644 "${IPXE_DIR}/boot.ipxe"
|
|
log_success "Installed boot.ipxe"
|
|
else
|
|
log_warning "boot.ipxe not found in ${SCRIPT_DIR}/ipxe/"
|
|
fi
|
|
|
|
# Copy MAC mappings documentation
|
|
if [[ -f "${SCRIPT_DIR}/ipxe/mac-mappings.txt" ]]; then
|
|
cp "${SCRIPT_DIR}/ipxe/mac-mappings.txt" "${IPXE_DIR}/mac-mappings.txt"
|
|
chmod 644 "${IPXE_DIR}/mac-mappings.txt"
|
|
log_success "Installed mac-mappings.txt"
|
|
fi
|
|
}
|
|
|
|
# Create symlinks for TFTP
|
|
create_tftp_symlinks() {
|
|
log_info "Creating TFTP symlinks..."
|
|
|
|
# Symlink bootloaders to TFTP directory
|
|
for file in undionly.kpxe ipxe.efi ipxe-i386.efi; do
|
|
if [[ -f "${IPXE_DIR}/${file}" ]]; then
|
|
ln -sf "${IPXE_DIR}/${file}" "${TFTP_DIR}/${file}"
|
|
log_success "Symlinked ${file} to TFTP directory"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Validate configuration files
|
|
validate_configs() {
|
|
log_info "Validating configuration files..."
|
|
|
|
local errors=0
|
|
|
|
# Check DHCP configuration
|
|
if [[ -f "${SCRIPT_DIR}/dhcp/dhcpd.conf" ]]; then
|
|
log_info "Checking DHCP configuration..."
|
|
if command -v dhcpd &> /dev/null; then
|
|
dhcpd -t -cf "${SCRIPT_DIR}/dhcp/dhcpd.conf" &> /dev/null && \
|
|
log_success "DHCP configuration is valid" || {
|
|
log_error "DHCP configuration is invalid"
|
|
dhcpd -t -cf "${SCRIPT_DIR}/dhcp/dhcpd.conf"
|
|
((errors++))
|
|
}
|
|
else
|
|
log_warning "dhcpd not installed, skipping DHCP validation"
|
|
fi
|
|
else
|
|
log_error "dhcpd.conf not found"
|
|
((errors++))
|
|
fi
|
|
|
|
# Check Nginx configuration
|
|
if [[ -f "${SCRIPT_DIR}/http/nginx.conf" ]]; then
|
|
log_info "Checking Nginx configuration..."
|
|
if command -v nginx &> /dev/null; then
|
|
nginx -t -c "${SCRIPT_DIR}/http/nginx.conf" &> /dev/null && \
|
|
log_success "Nginx configuration is valid" || {
|
|
log_error "Nginx configuration is invalid"
|
|
nginx -t -c "${SCRIPT_DIR}/http/nginx.conf"
|
|
((errors++))
|
|
}
|
|
else
|
|
log_warning "nginx not installed, skipping Nginx validation"
|
|
fi
|
|
else
|
|
log_error "nginx.conf not found"
|
|
((errors++))
|
|
fi
|
|
|
|
# Check iPXE boot script
|
|
if [[ -f "${SCRIPT_DIR}/ipxe/boot.ipxe" ]]; then
|
|
log_info "Checking iPXE boot script..."
|
|
# Basic syntax check (iPXE doesn't have a validation tool)
|
|
if grep -q "#!ipxe" "${SCRIPT_DIR}/ipxe/boot.ipxe"; then
|
|
log_success "iPXE boot script appears valid"
|
|
else
|
|
log_error "iPXE boot script is missing #!ipxe shebang"
|
|
((errors++))
|
|
fi
|
|
else
|
|
log_error "boot.ipxe not found"
|
|
((errors++))
|
|
fi
|
|
|
|
# Check for required bootloaders
|
|
log_info "Checking for iPXE bootloaders..."
|
|
for file in undionly.kpxe ipxe.efi; do
|
|
if [[ -f "${IPXE_DIR}/${file}" ]]; then
|
|
log_success "Found ${file} ($(du -h "${IPXE_DIR}/${file}" | cut -f1))"
|
|
else
|
|
log_warning "${file} not found (run --download or --build-ipxe)"
|
|
fi
|
|
done
|
|
|
|
if [[ $errors -eq 0 ]]; then
|
|
log_success "All configuration files are valid"
|
|
return 0
|
|
else
|
|
log_error "Found $errors configuration error(s)"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Test services
|
|
test_services() {
|
|
log_info "Testing PXE boot services..."
|
|
|
|
local errors=0
|
|
|
|
# Test TFTP server
|
|
log_info "Testing TFTP server..."
|
|
if systemctl is-active --quiet atftpd 2>/dev/null; then
|
|
log_success "TFTP server (atftpd) is running"
|
|
|
|
# Try to fetch a file via TFTP
|
|
if command -v tftp &> /dev/null; then
|
|
timeout 5 tftp localhost -c get undionly.kpxe /tmp/test-undionly.kpxe &> /dev/null && {
|
|
log_success "TFTP fetch test successful"
|
|
rm -f /tmp/test-undionly.kpxe
|
|
} || {
|
|
log_warning "TFTP fetch test failed (this may be normal if files aren't ready)"
|
|
}
|
|
fi
|
|
else
|
|
log_error "TFTP server is not running"
|
|
((errors++))
|
|
fi
|
|
|
|
# Test HTTP server
|
|
log_info "Testing HTTP server..."
|
|
if systemctl is-active --quiet nginx 2>/dev/null; then
|
|
log_success "HTTP server (nginx) is running"
|
|
|
|
# Try to fetch health endpoint
|
|
if command -v curl &> /dev/null; then
|
|
curl -f -s http://localhost/health &> /dev/null && {
|
|
log_success "HTTP health check successful"
|
|
} || {
|
|
log_warning "HTTP health check failed"
|
|
((errors++))
|
|
}
|
|
fi
|
|
else
|
|
log_error "HTTP server is not running"
|
|
((errors++))
|
|
fi
|
|
|
|
# Test DHCP server
|
|
log_info "Testing DHCP server..."
|
|
if systemctl is-active --quiet dhcpd4 2>/dev/null || \
|
|
systemctl is-active --quiet isc-dhcp-server 2>/dev/null; then
|
|
log_success "DHCP server is running"
|
|
else
|
|
log_error "DHCP server is not running"
|
|
((errors++))
|
|
fi
|
|
|
|
# Network connectivity test
|
|
log_info "Checking network interfaces..."
|
|
ip addr show | grep -q "inet " && {
|
|
log_success "Network interfaces are up"
|
|
} || {
|
|
log_error "No network interfaces with IP addresses found"
|
|
((errors++))
|
|
}
|
|
|
|
if [[ $errors -eq 0 ]]; then
|
|
log_success "All service tests passed"
|
|
return 0
|
|
else
|
|
log_error "Found $errors service error(s)"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Clean up boot assets
|
|
clean_assets() {
|
|
log_warning "Cleaning up boot assets directory..."
|
|
read -p "This will delete ${BOOT_ASSETS_DIR}. Continue? (y/N) " -n 1 -r
|
|
echo
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
rm -rf "${BOOT_ASSETS_DIR}"
|
|
rm -rf "${TFTP_DIR}"
|
|
log_success "Boot assets cleaned up"
|
|
else
|
|
log_info "Cleanup cancelled"
|
|
fi
|
|
}
|
|
|
|
# Full installation
|
|
full_install() {
|
|
log_info "Starting full PXE server installation..."
|
|
|
|
create_directories
|
|
install_boot_scripts
|
|
create_tftp_symlinks
|
|
|
|
log_success "Installation complete!"
|
|
log_info ""
|
|
log_info "Next steps:"
|
|
log_info " 1. Download or build iPXE bootloaders:"
|
|
log_info " sudo $0 --download"
|
|
log_info " OR"
|
|
log_info " sudo $0 --build-ipxe"
|
|
log_info ""
|
|
log_info " 2. Configure your network settings in:"
|
|
log_info " ${SCRIPT_DIR}/dhcp/dhcpd.conf"
|
|
log_info " ${SCRIPT_DIR}/nixos-module.nix"
|
|
log_info ""
|
|
log_info " 3. Deploy NixOS configuration or manually start services"
|
|
log_info ""
|
|
log_info " 4. Add NixOS boot images to ${NIXOS_DIR}/"
|
|
log_info " (This will be done by T032.S3 - Image Builder)"
|
|
}
|
|
|
|
# Main script
|
|
main() {
|
|
if [[ $# -eq 0 ]]; then
|
|
show_help
|
|
exit 0
|
|
fi
|
|
|
|
check_root
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--install)
|
|
full_install
|
|
shift
|
|
;;
|
|
--download)
|
|
download_ipxe
|
|
shift
|
|
;;
|
|
--build-ipxe)
|
|
build_ipxe
|
|
shift
|
|
;;
|
|
--validate)
|
|
validate_configs
|
|
shift
|
|
;;
|
|
--test)
|
|
test_services
|
|
shift
|
|
;;
|
|
--clean)
|
|
clean_assets
|
|
shift
|
|
;;
|
|
--help)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
*)
|
|
log_error "Unknown option: $1"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
main "$@"
|