301 lines
9.5 KiB
Bash
Executable file
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
|