Initial project setup: automated PXE server provisioning
Reorganized from OneDrive export into a clean project structure: - autoinstall/: cloud-init user-data and meta-data for Ubuntu 24.04 autoinstall - playbook/: Ansible playbook for PXE server config (dnsmasq, Apache, Samba, iPXE) - unattend/: Windows unattend.xml sample for image deployment - build-usb.sh: builds a bootable USB with Ubuntu installer + CIDATA partition - download-packages.sh: downloads all offline .deb dependencies via Docker Key improvements over original: - Fully air-gapped: all packages bundled offline, no WiFi needed - Hardware-agnostic network config (wildcard NIC matching) - Removed plaintext WiFi credentials - Single USB build process (was 15+ manual steps) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
196
build-usb.sh
Executable file
196
build-usb.sh
Executable file
@@ -0,0 +1,196 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# build-usb.sh — Build a bootable PXE-server installer USB
|
||||
#
|
||||
# Creates a two-partition USB:
|
||||
# Partition 1: Ubuntu Server 24.04 installer (ISO contents)
|
||||
# Partition 2: CIDATA volume (autoinstall config, .debs, playbook)
|
||||
#
|
||||
# The target machine boots from this USB, Ubuntu auto-installs with
|
||||
# cloud-init (user-data/meta-data from CIDATA), installs offline .debs,
|
||||
# and on first boot runs the Ansible playbook to configure PXE services.
|
||||
#
|
||||
# Usage:
|
||||
# sudo ./build-usb.sh /dev/sdX /path/to/ubuntu-24.04-live-server-amd64.iso
|
||||
#
|
||||
# WARNING: This will ERASE the target USB device.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
AUTOINSTALL_DIR="$SCRIPT_DIR/autoinstall"
|
||||
PLAYBOOK_DIR="$SCRIPT_DIR/playbook"
|
||||
OFFLINE_PKG_DIR="$SCRIPT_DIR/offline-packages"
|
||||
|
||||
# --- Validate arguments ---
|
||||
if [ $# -ne 2 ]; then
|
||||
echo "Usage: sudo $0 /dev/sdX /path/to/ubuntu-24.04.iso"
|
||||
echo ""
|
||||
echo "Available removable devices:"
|
||||
lsblk -d -o NAME,SIZE,TRAN,RM | grep -E '^\S+\s+\S+\s+(usb)\s+1'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
USB_DEV="$1"
|
||||
ISO_PATH="$2"
|
||||
|
||||
# Safety checks
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "ERROR: Must run as root (sudo)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -b "$USB_DEV" ]; then
|
||||
echo "ERROR: $USB_DEV is not a block device."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$ISO_PATH" ]; then
|
||||
echo "ERROR: ISO not found at $ISO_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify it's a removable device (safety against wiping system disks)
|
||||
REMOVABLE=$(lsblk -nd -o RM "$USB_DEV" 2>/dev/null || echo "0")
|
||||
if [ "$REMOVABLE" != "1" ]; then
|
||||
echo "WARNING: $USB_DEV does not appear to be a removable device."
|
||||
read -rp "Are you SURE you want to erase $USB_DEV? (type YES): " CONFIRM
|
||||
if [ "$CONFIRM" != "YES" ]; then
|
||||
echo "Aborted."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify required source files exist
|
||||
if [ ! -f "$AUTOINSTALL_DIR/user-data" ]; then
|
||||
echo "ERROR: user-data not found at $AUTOINSTALL_DIR/user-data"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$AUTOINSTALL_DIR/meta-data" ]; then
|
||||
echo "ERROR: meta-data not found at $AUTOINSTALL_DIR/meta-data"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$PLAYBOOK_DIR/pxe_server_setup.yml" ]; then
|
||||
echo "ERROR: pxe_server_setup.yml not found at $PLAYBOOK_DIR/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "============================================"
|
||||
echo "PXE Server USB Builder"
|
||||
echo "============================================"
|
||||
echo "USB Device : $USB_DEV"
|
||||
echo "ISO : $ISO_PATH"
|
||||
echo "Source Dir : $SCRIPT_DIR"
|
||||
echo ""
|
||||
echo "This will ERASE all data on $USB_DEV."
|
||||
read -rp "Continue? (y/N): " PROCEED
|
||||
if [[ ! "$PROCEED" =~ ^[Yy]$ ]]; then
|
||||
echo "Aborted."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Unmount any existing partitions ---
|
||||
echo ""
|
||||
echo "[1/6] Unmounting existing partitions on $USB_DEV..."
|
||||
for part in "${USB_DEV}"*; do
|
||||
umount "$part" 2>/dev/null || true
|
||||
done
|
||||
|
||||
# --- Write ISO to USB ---
|
||||
echo "[2/6] Writing Ubuntu ISO to $USB_DEV (this may take several minutes)..."
|
||||
dd if="$ISO_PATH" of="$USB_DEV" bs=4M status=progress oflag=sync
|
||||
sync
|
||||
|
||||
# --- Find the end of the ISO to create CIDATA partition ---
|
||||
echo "[3/6] Creating CIDATA partition after ISO data..."
|
||||
|
||||
# Get ISO size in bytes and calculate the start sector for the new partition
|
||||
ISO_SIZE=$(stat -c%s "$ISO_PATH")
|
||||
SECTOR_SIZE=512
|
||||
# Start the CIDATA partition 1MB after the ISO ends (alignment)
|
||||
START_SECTOR=$(( (ISO_SIZE / SECTOR_SIZE) + 2048 ))
|
||||
|
||||
# Use sfdisk to append a new partition
|
||||
echo " ISO size: $((ISO_SIZE / 1024 / 1024)) MB"
|
||||
echo " CIDATA partition starts at sector $START_SECTOR"
|
||||
|
||||
# Add a new partition using sfdisk --append
|
||||
echo "${START_SECTOR},+,L" | sfdisk --append "$USB_DEV" --no-reread 2>/dev/null || true
|
||||
partprobe "$USB_DEV"
|
||||
sleep 2
|
||||
|
||||
# Determine the new partition name (could be sdX3, sdX4, etc.)
|
||||
CIDATA_PART=""
|
||||
for part in "${USB_DEV}"[0-9]*; do
|
||||
# Find the partition that starts at or after our start sector
|
||||
PART_START=$(sfdisk -d "$USB_DEV" 2>/dev/null | grep "$part" | grep -o 'start=[[:space:]]*[0-9]*' | grep -o '[0-9]*')
|
||||
if [ -n "$PART_START" ] && [ "$PART_START" -ge "$START_SECTOR" ]; then
|
||||
CIDATA_PART="$part"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Fallback: use the last partition
|
||||
if [ -z "$CIDATA_PART" ]; then
|
||||
CIDATA_PART=$(lsblk -ln -o NAME "$USB_DEV" | tail -1)
|
||||
CIDATA_PART="/dev/$CIDATA_PART"
|
||||
fi
|
||||
|
||||
echo " CIDATA partition: $CIDATA_PART"
|
||||
|
||||
# --- Format CIDATA partition ---
|
||||
echo "[4/6] Formatting $CIDATA_PART as FAT32 (label: CIDATA)..."
|
||||
mkfs.vfat -F 32 -n CIDATA "$CIDATA_PART"
|
||||
|
||||
# --- Mount and copy files ---
|
||||
echo "[5/6] Copying autoinstall config, packages, and playbook to CIDATA..."
|
||||
MOUNT_POINT=$(mktemp -d)
|
||||
mount "$CIDATA_PART" "$MOUNT_POINT"
|
||||
|
||||
# Copy cloud-init files
|
||||
cp "$AUTOINSTALL_DIR/user-data" "$MOUNT_POINT/"
|
||||
cp "$AUTOINSTALL_DIR/meta-data" "$MOUNT_POINT/"
|
||||
|
||||
# Copy offline .deb packages into packages/ subdirectory
|
||||
mkdir -p "$MOUNT_POINT/packages"
|
||||
DEB_COUNT=0
|
||||
|
||||
if [ -d "$OFFLINE_PKG_DIR" ]; then
|
||||
for deb in "$OFFLINE_PKG_DIR"/*.deb; do
|
||||
if [ -f "$deb" ]; then
|
||||
cp "$deb" "$MOUNT_POINT/packages/"
|
||||
DEB_COUNT=$((DEB_COUNT + 1))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
echo " Copied $DEB_COUNT .deb packages to packages/"
|
||||
|
||||
# Copy playbook directory
|
||||
cp -r "$PLAYBOOK_DIR" "$MOUNT_POINT/playbook"
|
||||
echo " Copied playbook/"
|
||||
|
||||
# List what's on CIDATA
|
||||
echo ""
|
||||
echo " CIDATA contents:"
|
||||
ls -lh "$MOUNT_POINT/" | sed 's/^/ /'
|
||||
|
||||
# --- Cleanup ---
|
||||
echo ""
|
||||
echo "[6/6] Syncing and unmounting..."
|
||||
sync
|
||||
umount "$MOUNT_POINT"
|
||||
rmdir "$MOUNT_POINT"
|
||||
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo "USB build complete!"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Insert USB into target machine"
|
||||
echo " 2. Boot from USB (F12 / boot menu)"
|
||||
echo " 3. Ubuntu will auto-install and configure the PXE server"
|
||||
echo " 4. After reboot, move the NIC to the isolated PXE network"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user