ansible/iso-builder/build-iso.sh
2026-03-30 15:19:20 +02:00

301 lines
9.5 KiB
Bash
Executable file

#!/bin/bash
set -e
# ============================================================
# Debian Unattended ISO Builder
# Baut eine Custom Debian ISO mit eingebettetem Preseed
# ============================================================
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
WORK_DIR="/tmp/iso-builder-$$"
DEBIAN_VERSION="${DEBIAN_VERSION:-13}"
DEBIAN_CODENAME="${DEBIAN_CODENAME:-trixie}"
ARCH="amd64"
# --- Defaults ---
IP=""
NETMASK="255.255.255.0"
GATEWAY=""
DNS="1.1.1.1"
HOSTNAME="debian"
USER="sascha"
PASSWORD=""
SSH_KEY_FILE="$HOME/.ssh/id_ed25519.pub"
OUTPUT_DIR="$SCRIPT_DIR/output"
NODE=""
VMID=""
CREATE_VM=false
VM_CORES=8
VM_MEMORY=16384
VM_DISK=64
usage() {
cat <<EOF
Usage: $0 [OPTIONS]
Required (either --node or --ip + --gateway):
--node N Proxmox node number (1-7), auto-sets gateway and uploads ISO
--ip IP Static IP address
--gateway GW Gateway address
Optional:
--vmid ID Create VM with this ID (optional, auto-assigns next free if omitted)
--create-vm Create VM on the node (uses --vmid or next free ID)
--cores N CPU cores (default: 8)
--memory MB RAM in MB (default: 16384)
--disk GB Disk size in GB (default: 64)
--netmask MASK Netmask (default: 255.255.255.0)
--dns DNS DNS server (default: 1.1.1.1)
--hostname NAME Hostname (default: debian)
--user USER Username (default: sascha)
--password PASS Password (will be prompted if not set)
--ssh-key FILE SSH public key file (default: ~/.ssh/id_ed25519.pub)
--output DIR Output directory (default: ./output)
--debian-version VER Debian version (default: 13)
Node mapping:
--node 1 → gateway 10.1.1.1 (node1 / 10.5.85.11)
--node 2 → gateway 10.2.1.1 (node2 / 10.5.85.12)
--node 3 → gateway 10.3.1.1 (node3 / 10.5.85.13)
--node 4 → gateway 10.4.1.1 (node4 / 10.5.85.14)
--node 5 → gateway 10.5.1.1 (node5 / 10.5.85.15)
--node 6 → gateway 10.6.1.1 (node6 / 10.5.85.16)
--node 7 → gateway 10.7.1.1 (node7 / 10.5.85.17)
Examples:
$0 --node 4 --ip 10.4.1.120 --hostname neue-vm
$0 --node 4 --ip 10.4.1.120 --hostname neue-vm --vmid 120
$0 --node 7 --ip 10.7.1.110 --hostname chris-vm --user chris --vmid 710
EOF
exit 1
}
# --- Parse Args ---
while [[ $# -gt 0 ]]; do
case $1 in
--ip) IP="$2"; shift 2;;
--netmask) NETMASK="$2"; shift 2;;
--gateway) GATEWAY="$2"; shift 2;;
--dns) DNS="$2"; shift 2;;
--hostname) HOSTNAME="$2"; shift 2;;
--user) USER="$2"; shift 2;;
--password) PASSWORD="$2"; shift 2;;
--ssh-key) SSH_KEY_FILE="$2"; shift 2;;
--output) OUTPUT_DIR="$2"; shift 2;;
--debian-version) DEBIAN_VERSION="$2"; shift 2;;
--node) NODE="$2"; shift 2;;
--vmid) VMID="$2"; CREATE_VM=true; shift 2;;
--create-vm) CREATE_VM=true; shift;;
--cores) VM_CORES="$2"; shift 2;;
--memory) VM_MEMORY="$2"; shift 2;;
--disk) VM_DISK="$2"; shift 2;;
*) echo "Unknown option: $1"; usage;;
esac
done
# --- Node mapping ---
if [[ -n "$NODE" ]]; then
declare -A NODE_GW=(
[1]="10.1.1.1" [2]="10.2.1.1" [3]="10.3.1.1" [4]="10.4.1.1"
[5]="10.5.1.1" [6]="10.6.1.1" [7]="10.7.1.1"
)
declare -A NODE_IP=(
[1]="10.5.85.11" [2]="10.5.85.12" [3]="10.5.85.13" [4]="10.5.85.14"
[5]="10.5.85.15" [6]="10.5.85.16" [7]="10.5.85.17"
)
[[ -z "${NODE_GW[$NODE]}" ]] && echo "Error: Invalid node $NODE (1-7)" && exit 1
GATEWAY="${NODE_GW[$NODE]}"
PVE_HOST="${NODE_IP[$NODE]}"
fi
[[ -z "$IP" ]] && echo "Error: --ip is required" && usage
[[ -z "$GATEWAY" ]] && echo "Error: --gateway or --node is required" && usage
# --- Dependencies ---
for cmd in xorriso cpio gzip genisoimage; do
if ! command -v $cmd &>/dev/null; then
echo "Installing missing dependency: $cmd"
sudo apt-get install -y xorriso cpio gzip genisoimage 2>/dev/null || true
fi
done
# --- Password ---
if [[ -z "$PASSWORD" ]]; then
read -sp "Password for user $USER: " PASSWORD
echo
fi
PASSWORD_HASH=$(echo "$PASSWORD" | openssl passwd -6 -stdin)
# --- SSH Key ---
if [[ -f "$SSH_KEY_FILE" ]]; then
SSH_KEY=$(cat "$SSH_KEY_FILE")
else
echo "Warning: SSH key file not found: $SSH_KEY_FILE"
SSH_KEY=""
fi
# --- Download Debian ISO ---
ISO_URL="https://cdimage.debian.org/debian-cd/current/${ARCH}/iso-cd/debian-${DEBIAN_VERSION}.*-${ARCH}-netinst.iso"
ISO_FILE="$SCRIPT_DIR/debian-${DEBIAN_VERSION}-${ARCH}-netinst.iso"
if [[ ! -f "$ISO_FILE" ]]; then
echo "Downloading Debian ${DEBIAN_VERSION} netinstall ISO..."
# Get exact filename from listing
EXACT_URL=$(curl -sL "https://cdimage.debian.org/debian-cd/current/${ARCH}/iso-cd/" | \
grep -oP "debian-${DEBIAN_VERSION}\.[0-9]+-${ARCH}-netinst\.iso" | head -1)
wget -q --show-progress -O "$ISO_FILE" \
"https://cdimage.debian.org/debian-cd/current/${ARCH}/iso-cd/${EXACT_URL}"
fi
echo "Building ISO for: ${HOSTNAME} (${IP})"
# --- Extract ISO ---
cleanup() { rm -rf "$WORK_DIR"; }
trap cleanup EXIT
mkdir -p "$WORK_DIR"/{iso,custom}
xorriso -osirrox on -indev "$ISO_FILE" -extract / "$WORK_DIR/iso" 2>/dev/null
chmod -R u+w "$WORK_DIR/iso"
# --- Generate Preseed ---
PRESEED="$WORK_DIR/iso/preseed.cfg"
sed \
-e "s|{{IP}}|${IP}|g" \
-e "s|{{NETMASK}}|${NETMASK}|g" \
-e "s|{{GATEWAY}}|${GATEWAY}|g" \
-e "s|{{DNS}}|${DNS}|g" \
-e "s|{{HOSTNAME}}|${HOSTNAME}|g" \
-e "s|{{USER}}|${USER}|g" \
-e "s|{{PASSWORD_HASH}}|${PASSWORD_HASH}|g" \
-e "s|{{SSH_KEY}}|${SSH_KEY}|g" \
"$SCRIPT_DIR/preseed.cfg.tpl" > "$PRESEED"
# --- Patch GRUB (UEFI) - komplett ersetzen ---
GRUB_CFG="$WORK_DIR/iso/boot/grub/grub.cfg"
if [[ -f "$GRUB_CFG" ]]; then
cat > "$GRUB_CFG" << GRUBEOF
set default=0
set timeout=0
menuentry "Debian Auto Install - ${HOSTNAME}" {
linux /install.amd/vmlinuz auto=true priority=critical preseed/file=/cdrom/preseed.cfg ---
initrd /install.amd/initrd.gz
}
GRUBEOF
fi
# --- Patch isolinux (BIOS) - komplett ersetzen ---
TXT_CFG="$WORK_DIR/iso/isolinux/txt.cfg"
if [[ -f "$TXT_CFG" ]]; then
cat > "$TXT_CFG" << TXTEOF
default auto
label auto
menu label Auto Install - ${HOSTNAME}
kernel /install.amd/vmlinuz
append auto=true priority=critical preseed/file=/cdrom/preseed.cfg initrd=/install.amd/initrd.gz ---
TXTEOF
fi
ISOLINUX_CFG="$WORK_DIR/iso/isolinux/isolinux.cfg"
if [[ -f "$ISOLINUX_CFG" ]]; then
sed -i 's/timeout .*/timeout 1/' "$ISOLINUX_CFG"
fi
# --- Rebuild ISO ---
mkdir -p "$OUTPUT_DIR"
OUTPUT_ISO="$OUTPUT_DIR/debian-${DEBIAN_VERSION}-${HOSTNAME}-${IP}.iso"
cd "$WORK_DIR/iso"
# Fix MD5
find . -type f ! -name 'md5sum.txt' -exec md5sum {} \; > md5sum.txt 2>/dev/null || true
xorriso -as mkisofs \
-r -J \
-b isolinux/isolinux.bin \
-c isolinux/boot.cat \
-no-emul-boot \
-boot-load-size 4 \
-boot-info-table \
-eltorito-alt-boot \
-e boot/grub/efi.img \
-no-emul-boot \
-isohybrid-gpt-basdat \
-o "$OUTPUT_ISO" \
. 2>/dev/null
echo ""
echo "============================================"
echo "ISO created: $OUTPUT_ISO"
echo "============================================"
echo "Host: $HOSTNAME"
echo "IP: $IP"
echo "Gateway: $GATEWAY"
echo "User: $USER"
echo "SSH Key: $(echo $SSH_KEY | cut -c1-40)..."
echo "============================================"
echo ""
if [[ -n "$PVE_HOST" ]]; then
echo "Uploading to node${NODE} (${PVE_HOST})..."
scp -o StrictHostKeyChecking=no "$OUTPUT_ISO" "root@${PVE_HOST}:/var/lib/vz/template/iso/" && \
echo "✅ ISO uploaded to node${NODE}" || \
{ echo "❌ Upload failed"; exit 1; }
if [[ "$CREATE_VM" == true ]]; then
ISO_NAME="$(basename "$OUTPUT_ISO")"
STORAGE="powerstore-node${NODE}"
PVE_NODE="node${NODE}"
# Auto-assign next free VMID if not specified
if [[ -z "$VMID" ]]; then
VMID=$(ssh -o StrictHostKeyChecking=no "root@${PVE_HOST}" "pvesh get /cluster/nextid" 2>/dev/null)
echo "Auto-assigned VMID: ${VMID}"
fi
echo ""
echo "Creating VM ${VMID} on ${PVE_NODE}..."
ssh -o StrictHostKeyChecking=no "root@${PVE_HOST}" "
# Create VM: q35, UEFI, virtio-scsi
pvesh create /nodes/${PVE_NODE}/qemu \
--vmid ${VMID} \
--name ${HOSTNAME} \
--machine q35 \
--bios ovmf \
--efidisk0 ${STORAGE}:1,efitype=4m,pre-enrolled-keys=0 \
--scsihw virtio-scsi-pci \
--scsi0 ${STORAGE}:${VM_DISK},cache=writeback \
--ide2 local:iso/${ISO_NAME},media=cdrom \
--net0 virtio,bridge=vmbr0 \
--cores ${VM_CORES} \
--memory ${VM_MEMORY} \
--cpu cputype=host \
--agent enabled=1 \
--boot order='scsi0;ide2' \
--ostype l26 \
--onboot 1 \
--numa 1 \
--balloon 0 \
--serial0 socket
" && echo "✅ VM ${VMID} created" || { echo "❌ VM creation failed"; exit 1; }
echo "Starting VM ${VMID}..."
ssh -o StrictHostKeyChecking=no "root@${PVE_HOST}" \
"pvesh create /nodes/${PVE_NODE}/qemu/${VMID}/status/start" && \
echo "✅ VM ${VMID} started - installing Debian..." || echo "❌ VM start failed"
echo ""
echo "============================================"
echo "Boot order: scsi0 → ide2 (disk first, ISO fallback)"
echo "1st boot: disk empty → boots ISO → installs Debian"
echo "2nd boot: disk has GRUB → boots from disk → done!"
echo ""
echo "After installation (~5 min):"
echo " ssh ${USER}@${IP}"
echo " ansible-playbook site.yml -l ${HOSTNAME}"
echo "============================================"
fi
else
echo "Upload to Proxmox:"
echo " scp $OUTPUT_ISO root@<proxmox-node>:/var/lib/vz/template/iso/"
fi