Skip to content

Instantly share code, notes, and snippets.

@FalconNL93
Created July 17, 2025 13:25
Show Gist options
  • Save FalconNL93/b3709ac77248801fdc6b65a07bee458c to your computer and use it in GitHub Desktop.
Save FalconNL93/b3709ac77248801fdc6b65a07bee458c to your computer and use it in GitHub Desktop.
#!/bin/bash
set -euo pipefail
ISO_ORIG="$HOME/Dev/ISO/debian-12.11.0-amd64-netinst.iso"
ISO_NEW="$HOME/Dev/ISO/debian-12.11.0-amd64-unattended-cloudinit.iso"
PRESEED_FILE="$(pwd)/preseed.cfg"
WORKDIR="$HOME/Dev/ISO/Debian/iso-work"
ISO_MOUNT="$HOME/Dev/ISO/Debian/iso-mount"
REMOTE_USER=""
REMOTE_HOST=""
REMOTE_PATH="/var/lib/vz/template/iso"
# Ensure required packages are installed
ensure_packages_installed() {
local packages=(gzip cpio rsync xorriso qemu-system-x86)
local missing=()
for pkg in "${packages[@]}"; do
if ! command -v "$pkg" &>/dev/null && ! rpm -q "$pkg" &>/dev/null; then
missing+=("$pkg")
fi
done
if [ ${#missing[@]} -gt 0 ]; then
echo "[*] Installing missing packages: ${missing[*]}..."
if [ -f /etc/os-release ]; then
. /etc/os-release
ID_LIKE=${ID_LIKE:-""} # Ensure ID_LIKE is defined
if [[ "$ID" =~ ^(ubuntu|debian)$ || "$ID_LIKE" == *"debian"* ]]; then
sudo apt update && sudo apt install -y "${missing[@]}"
elif [[ "$ID" =~ ^(fedora|rhel|centos)$ || "$ID_LIKE" == *"rhel"* ]]; then
sudo dnf install -y "${missing[@]}"
elif [[ "$ID" == "arch" || "$ID_LIKE" == *"arch"* ]]; then
sudo pacman -Sy --noconfirm "${missing[@]}"
else
echo "Unsupported OS. Please install: ${missing[*]} manually."
exit 1
fi
else
echo "Unable to detect OS. Please install: ${missing[*]} manually."
exit 1
fi
else
echo "[*] All required packages are already installed."
fi
}
# Call the function at the start of the script
ensure_packages_installed
# Check required files
check_required_files() {
echo "[*] Checking required files..."
[[ -f "$ISO_ORIG" ]] || { echo "ERROR: ISO file not found: $ISO_ORIG"; exit 1; }
[[ -f "$PRESEED_FILE" ]] || { echo "ERROR: Preseed file not found in current directory."; exit 1; }
}
check_required_files
# Clean up old work directories and ISO
cleanup_old_files() {
echo "[*] Cleaning up old work dirs and ISO..."
sudo rm -rf "$WORKDIR" "$ISO_MOUNT" "$ISO_NEW"
mkdir -p "$WORKDIR" "$ISO_MOUNT"
}
cleanup_old_files
# Cleanup function for unmounting and stopping VM
cleanup() {
if mountpoint -q "$ISO_MOUNT"; then
echo "[*] Unmounting ISO..."
sudo umount "$ISO_MOUNT"
fi
if pgrep -f "debian12" > /dev/null; then
echo "[*] Stopping KVM VM 'debian12'..."
pkill -f "debian12"
sleep 2
fi
if [ -f "$WORKDIR/debian12.qcow2" ]; then
echo "[*] Removing virtual drive: $WORKDIR/debian12.qcow2..."
rm -f "$WORKDIR/debian12.qcow2"
fi
}
trap cleanup EXIT
# Mount original ISO
mount_iso() {
echo "[*] Mounting original ISO..."
sudo mount -o loop "$ISO_ORIG" "$ISO_MOUNT"
}
mount_iso
# Copy ISO contents
copy_iso_contents() {
echo "[*] Copying ISO contents..."
rsync -a -H --no-owner --no-group --exclude=TRANS.TBL \
--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r \
"$ISO_MOUNT/" "$WORKDIR/"
sudo umount "$ISO_MOUNT"
}
copy_iso_contents
# Embed preseed.cfg inside initrd.gz
embed_preseed() {
echo "[*] Embedding preseed.cfg inside initrd.gz..."
mkdir -p /tmp/initrd-extract
cd /tmp/initrd-extract
gzip -dc "$WORKDIR/install.amd/initrd.gz" | \
cpio --quiet --extract --make-directories --no-absolute-filenames --no-preserve-owner 2>/dev/null || true
cp "$PRESEED_FILE" ./preseed.cfg
(find . | cpio --quiet --create --format='newc' | gzip -9 > "$WORKDIR/install.amd/initrd.gz") || true
cd -
rm -rf /tmp/initrd-extract
}
embed_preseed
# Copy scripts to ISO work directory
copy_scripts() {
echo "[*] Copying scripts to the ISO work directory for preseed usage..."
SCRIPTS_DIR="$(pwd)/scripts"
if [ -d "$SCRIPTS_DIR" ]; then
cp -r "$SCRIPTS_DIR" "$WORKDIR/media"
else
echo "[!] Scripts directory not found: $SCRIPTS_DIR"
exit 1
fi
}
copy_scripts
# Add boot entry and set default
configure_boot_menu() {
echo "[*] Adding 'Automated Install' boot entry to txt.cfg..."
cat <<EOF >> "$WORKDIR/isolinux/txt.cfg"
label auto
menu label ^Automated Install
kernel /install.amd/vmlinuz
append vga=788 initrd=/install.amd/initrd.gz auto priority=critical preseed/file=/preseed.cfg --- quiet
EOF
echo "[*] Setting 'auto' as default boot option in isolinux.cfg..."
sed -i 's/^default .*/default auto/' "$WORKDIR/isolinux/isolinux.cfg"
}
configure_boot_menu
# Fix MD5 checksums
fix_checksums() {
echo "[*] Fixing MD5 checksums..."
pushd "$WORKDIR" > /dev/null
md5sum $(find -type f) > md5sum.txt
popd > /dev/null
}
fix_checksums
# Build new ISO using xorriso
build_iso() {
echo "[*] Building new ISO..."
xorriso -as mkisofs \
-o "$ISO_NEW" \
-r -J -joliet-long \
-V "Debian 12.11.0 amd64 n" \
-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 \
"$WORKDIR"
}
build_iso
# Create virtual drive only when running KVM test
create_virtual_drive() {
echo "[*] Creating a 20 GB virtual drive for Debian installation..."
DRIVE_FILE="$WORKDIR/debian12.qcow2"
if [ ! -f "$DRIVE_FILE" ]; then
qemu-img create -f qcow2 "$DRIVE_FILE" 20G
else
echo "[*] Virtual drive already exists: $DRIVE_FILE"
fi
}
# Start KVM test boot
start_kvm_test() {
echo "[*] Starting KVM test boot (debian12)..."
create_virtual_drive
qemu-system-x86_64 \
-name debian12 \
-m 2048 \
-smp 2 \
-cdrom "$ISO_NEW" \
-boot d \
-drive file="$WORKDIR/debian12.qcow2",format=qcow2 \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device e1000,netdev=net0 \
-display gtk \
-enable-kvm
}
# Parse command-line arguments
RUN_KVM_TEST=false
UPLOAD_TO_PROXMOX=false
for arg in "$@"; do
case $arg in
--run)
RUN_KVM_TEST=true
shift
;;
--upload)
UPLOAD_TO_PROXMOX=true
shift
;;
*)
echo "[!] Unknown argument: $arg"
exit 1
;;
esac
done
# Start KVM test boot if --run is provided
if [ "$RUN_KVM_TEST" = true ]; then
start_kvm_test
fi
# Upload to Proxmox if --upload is provided
if [ "$UPLOAD_TO_PROXMOX" = true ]; then
upload_to_proxmox
fi
echo "[+] ISO creation and setup completed."
# Locale
d-i debian-installer/locale string en_US.UTF-8
d-i debian-installer/language string en
d-i debian-installer/country string NL
d-i localechooser/supported-locales multiselect en_US.UTF-8
d-i time/zone string Europe/Amsterdam
# Keyboard
d-i console-setup/ask_detect boolean false
d-i keyboard-configuration/modelcode string pc105
d-i keyboard-configuration/layoutcode string us
d-i keyboard-configuration/variantcode string
d-i keyboard-configuration/unsupported_layout boolean true
d-i keyboard-configuration/xkb-keymap select us
# Network
d-i netcfg/choose_interface select auto
d-i netcfg/get_hostname string debian
d-i netcfg/get_domain string local
# Mirror
d-i mirror/country string manual
d-i mirror/http/hostname string deb.debian.org
d-i mirror/http/directory string /debian
d-i mirror/http/proxy string
# Root
d-i passwd/root-password password changeme
d-i passwd/root-password-again password changeme
d-i passwd/make-user boolean false
# Partitioning
d-i partman-auto/method string regular
d-i partman-auto/expert_recipe string custom :: \
1000 10000 100000000 ext4 \
$primary{ } $bootable{ } \
method{ format } format{ } \
use_filesystem{ } filesystem{ ext4 } \
mountpoint{ / } \
. \
500 10000 100000000 linux-swap \
method{ swap } format{ } \
.
# Use GPT partitioning scheme
d-i partman-partitioning/default_label string gpt
# Automatically confirm partitioning
d-i partman/confirm_write_new_label boolean true
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
# Automatically finish partitioning
d-i partman/choose_partition select finish
d-i partman/finish_partitioning boolean true
d-i partman/commit boolean true
# Package Selection
tasksel tasksel/first multiselect standard, ssh-server
d-i pkgsel/include string sudo curl git cloud-init
popularity-contest popularity-contest/participate boolean false
# Skip media scanning dialog
d-i cdrom-detect/eject boolean false
d-i apt-setup/cdrom/set-first boolean false
d-i apt-setup/disable-cdrom-entries boolean true
# GRUB Bootloader
d-i grub-installer/only_debian boolean true
d-i grub-installer/with_other_os boolean true
# Automatically install GRUB on the primary drive
d-i grub-installer/bootdev string /dev/sda
# Reboot After Install
d-i finish-install/reboot_in_progress note
d-i cdrom-detect/eject boolean false
# Debug Preseed Loading Confirmation
d-i preseed/early_command string echo '>>> Preseed loaded successfully' > /dev/console
# Late Command
d-i preseed/late_command string \
in-target systemctl enable ssh; \
in-target sed -i 's/preserve_hostname: true/preserve_hostname: false/' /etc/cloud/cloud.cfg; \
in-target cloud-init clean --logs; \
in-target cp /media/post_boot.sh /root/post_boot.sh; \
in-target chmod +x /root/post_boot.sh; \
in-target echo "@reboot root /root/post_boot.sh" >> /etc/crontab
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment