Skip to content

Instantly share code, notes, and snippets.

@jeffangelion
Last active October 17, 2025 13:49
Show Gist options
  • Save jeffangelion/f666fef66b2cf5a6c0b88915964addcd to your computer and use it in GitHub Desktop.
Save jeffangelion/f666fef66b2cf5a6c0b88915964addcd to your computer and use it in GitHub Desktop.
WIP: automated script for including custom packages in Debian ISO without network mirror/preseed
#!/usr/bin/env bash
set -euo pipefail
# Sources:
# https://wiki.debian.org/DebianInstaller/Modify/CD
# https://wiki.debian.org/RepackBootableISO
# Variables
needed_packages='ifenslave sudo'
mirror_url='http://[FQDN]/debian'
mirror_components='main'
#orig_iso="$HOME"/debian-12.12.0-amd64-netinst.iso
#new_files="$HOME"/bookworm-modified
#iso_label='Debian 12.12.0 amd64 n'
apt_cache_dir="$HOME"/apt-cache
config_deb="$HOME"/config-deb
indices_dir="$HOME"/indices
#debian_release=bookworm
#new_iso="$HOME"/debian-12.12.0-amd64-modified.iso
mbr_template="$HOME"/isohdpfx.bin
# Split needed_packages string by whitespaces with 'read -a' and here-string (<<<)
read -ra needed_packages <<< "$needed_packages"
# Extract MBR template file to disk
printf '\033[0;33mExtracting MBR from %s\033[0m\n' "$orig_iso"
dd if="$orig_iso" bs=432 count=1 of="$mbr_template" &>/dev/null
# Extract orig_iso files to new_files
# TODO: better cleanup before extracting
printf '\033[0;33mExtracting files from %s to %s\033[0m\n' "$orig_iso" "$new_files"
mkdir -p "$new_files"
rm -rf "${new_files:?}"/* &>/dev/null || true
bsdtar -C "$new_files" -xf "$orig_iso"
# TODO: get iso_label from "$new_files"/.disk/mkisofs
# Make new_files content writable
chmod -R +w "$new_files"
# Create APT cache directory
printf '\033[0;33mCreating temp APT cache directory: %s\033[0m\n' "$apt_cache_dir"
mkdir -p "$apt_cache_dir"
rm "${apt_cache_dir:?}"/* &>/dev/null || true
# Create apt.conf
cat >"$apt_cache_dir"/apt.conf << EOF
pkgCacheGen::Essential="none";
Dir::Etc::sourcelist="$apt_cache_dir/sources.list";
Dir::Cache::archives="$apt_cache_dir";
Dir::Cache="$apt_cache_dir";
EOF
# Create custom APT sources.list for needed_packages
# TODO: add DEB882 support
cat >"$apt_cache_dir"/sources.list << EOF
deb $mirror_url $debian_release $mirror_components
EOF
# Populate APT cache
printf '\033[0;33mPopulating APT cache\033[0m\n'
apt update \
-o pkgCacheGen::Essential=none \
-o Dir::Etc::sourcelist="$apt_cache_dir"/sources.list \
-o Dir::Cache::archives="$apt_cache_dir" \
-o Dir::Cache="$apt_cache_dir" &>/dev/null
# Download needed_packages
# TODO: option for specifying network mirror?
for i in "${needed_packages[@]}"; do
printf '\033[0;33mDownloading custom package: %s\033[0m\n' "$i"
# -c "$apt_cache_dir"/apt.conf
apt install --download-only --reinstall -y "$i" \
-o pkgCacheGen::Essential=none \
-o Dir::Etc::sourcelist="$apt_cache_dir"/sources.list \
-o Dir::Cache::archives="$apt_cache_dir" \
-o Dir::Cache="$apt_cache_dir" &>/dev/null
done
#Repopulate APT cache after 'poisoning' it
printf '\033[0;33mRepopulating APT cache\033[0m\n'
apt update &>/dev/null
# Create folder for needed_packages
# TODO: place needed_packages to same paths as in network mirror?
printf '\033[0;33mCreating pool directory for custom packages: %s\033[0m\n' "$new_files"/pool/main/custom
mkdir -p "$new_files"/pool/main/custom
# Move needed_packages to new_files
printf '\033[0;33mMoving custom packages to pool\033[0m\n'
mv "$apt_cache_dir"/*.deb "$new_files"/pool/main/custom
# Generate override
# By setting package priority to 'required', it will be installed by default
# More: https://wiki.debian.org/FtpMaster/Override
printf '\033[0;33mGenerating override file: %s\033[0m\n' "$indices_dir"/override
mkdir -p "$indices_dir"
rm -rf "${indices_dir:?}"/*
for i in "${needed_packages[@]}"; do
cat >>"$indices_dir"/override << EOF
$i required admin
EOF
done
# Generate config-deb
printf '\033[0;33mGenerating config-deb: %s\033[0m\n' "$config_deb"
cat >"$config_deb" << EOF
Dir {
ArchiveDir "$new_files";
OverrideDir "$indices_dir";
CacheDir "$indices_dir";
};
TreeDefault {
Directory "pool/";
};
BinDirectory "pool/main" {
Packages "dists/$debian_release/main/binary-amd64/Packages";
BinOverride "override";
};
Default {
Packages {
Extensions ".deb";
};
};
EOF
# Generate Packages.gz file
printf '\033[0;33mGenerating Packages.gz file\033[0m\n'
apt-ftparchive generate "$config_deb" &>/dev/null
# Generate Release file
printf '\033[0;33mCalculating new checksums\033[0m\n'
sed -i '/MD5Sum:/,$d' "$new_files"/dists/"$debian_release"/Release
apt-ftparchive release "$new_files"/dists/"$debian_release" >> "$new_files"/dists/"$debian_release"/Release
printf '\033[0;33mNOTE: "File system loop detected" is not an error\033[0m\n'
# shellcheck disable=SC2046
md5sum $(find "$new_files" ! -name "md5sum.txt" ! -path "./isolinux/*" -follow -type f) > md5sum.txt
# Make new_files content read-only
chmod -R -w "$new_files"
# Create the new ISO image
printf '\033[0;33mCreating modified ISO: %s\033[0m\n' "$new_iso"
xorriso -as mkisofs \
-r -V "$iso_label" \
-o "$new_iso" \
-J -joliet-long -cache-inodes \
-isohybrid-mbr "$mbr_template" \
-b isolinux/isolinux.bin \
-c isolinux/boot.cat \
-boot-load-size 4 -boot-info-table -no-emul-boot \
-eltorito-alt-boot \
-e boot/grub/efi.img \
-no-emul-boot -isohybrid-gpt-basdat -isohybrid-apm-hfsplus \
"$new_files" &>/dev/null
printf '\033[0;32mModified ISO successfully created: %s\033[0m\n' "$new_iso"
# Cleanup
rm -rf "$new_files"
rm -rf "$apt_cache_dir"
rm "$config_deb"
rm -rf "$indices_dir"
rm "$mbr_template"
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment