|  | 
 
| ``` #!/bin/sh
 # shellcheck shell=dash
 set -eu
 err() {
 printf "\n错误: %s.\n" "$1" 1>&2
 exit 1
 }
 warn() {
 printf "\警告: %s.\n继续使用默认值...\n" "$1" 1>&2
 sleep 5
 }
 command_exists() {
 command -v "$1" >/dev/null 2>&1
 }
 in_target_script=
 in_target() {
 local command=
 for argument in "$@"; do
 command="$command $argument"
 done
 [ -n "$command" ] && {
 [ -z "$in_target_script" ] && in_target_script='true'
 in_target_script="$in_target_script;$command"
 }
 }
 in_target_backup() {
 in_target "if [ ! -e \"$1.backup\" ]; then cp \"$1\" \"$1.backup\"; fi"
 }
 configure_sshd() {
 [ -z "${sshd_config_backup+1s}" ] && in_target_backup /etc/ssh/sshd_config
 sshd_config_backup=
 in_target sed -Ei \""s/^#?$1 .+/$1 $2/"\" /etc/ssh/sshd_config
 }
 prompt_password() {
 local prompt=
 [ $# -gt 0 ] && prompt=$1
 [ "$username" = root ] && prompt="为root用户选择一个密码: " || prompt="为用户 $username 选择一个密码: "
 stty -echo
 trap 'stty echo' EXIT
 while [ -z "$password" ]; do
 echo -n "$prompt" >/dev/tty
 read -r password /dev/tty
 done
 stty echo
 trap - EXIT
 }
 download() {
 [ -n "$mirror_proxy" ] && {
 [ -z "${http_proxy+1s}" ] && [ -z "${https_proxy+1s}" ] && [ -z "${ftp_proxy+1s}" ] && {
 export http_proxy="$mirror_proxy"
 export https_proxy="$mirror_proxy"
 export ftp_proxy="$mirror_proxy"
 }
 }
 if command_exists wget; then
 wget -O "$2" "$1"
 elif command_exists curl; then
 curl -fL "$1" -o "$2"
 elif command_exists busybox && busybox wget --help >/dev/null 2>&1; then
 busybox wget -O "$2" "$1"
 else
 err '无法找到"wget"、"curl"或"busybox wget"来下载文件'
 fi
 }
 set_mirror_proxy() {
 [ -n "$mirror_proxy" ] && return
 case $mirror_protocol in
 http)
 [ -n "${http_proxy+1s}" ] && mirror_proxy="$http_proxy"
 ;;
 https)
 [ -n "${https_proxy+1s}" ] && mirror_proxy="$https_proxy"
 ;;
 ftp)
 [ -n "${ftp_proxy+1s}" ] && mirror_proxy="$ftp_proxy"
 ;;
 *)
 err "不支持的协议: $mirror_protocol"
 esac
 }
 set_security_archive() {
 case $suite in
 buster|oldoldstable)
 security_archive="$suite/updates"
 ;;
 bullseye|oldstable|bookworm|stable|trixie|testing)
 security_archive="$suite-security"
 ;;
 sid|unstable)
 security_archive=''
 ;;
 *)
 err "不支持的版本: $suite"
 esac
 }
 set_daily_d_i() {
 case $suite in
 buster|oldoldstable|bullseye|oldstable|bookworm|stable)
 daily_d_i=false
 ;;
 trixie|testing|sid|unstable)
 daily_d_i=true
 ;;
 *)
 err "不支持的版本: $suite"
 esac
 }
 set_suite() {
 suite=$1
 set_daily_d_i
 set_security_archive
 }
 set_debian_version() {
 case $1 in
 10|buster|oldoldstable)
 set_suite buster
 ;;
 11|bullseye|oldstable)
 set_suite bullseye
 ;;
 12|bookworm|stable)
 set_suite bookworm
 ;;
 13|trixie|testing)
 set_suite bookworm
 ;;
 sid|unstable)
 set_suite sid
 ;;
 *)
 err "不支持的版本: $1"
 esac
 }
 has_cloud_kernel() {
 case $suite in
 buster|oldoldstable)
 [ "$architecture" = amd64 ] && return
 [ "$architecture" = arm64 ] && [ "$bpo_kernel" = true ] && return
 ;;
 bullseye|oldstable|bookworm|stable|trixie|testing|sid|unstable)
 [ "$architecture" = amd64 ] || [ "$architecture" = arm64 ] && return
 esac
 local tmp; tmp=''; [ "$bpo_kernel" = true ] && tmp='-backports'
 warn "没有可用于 $architecture/$suite$tmp 的云内核"
 return 1
 }
 has_backports() {
 case $suite in
 buster|oldoldstable|bullseye|oldstable|bookworm|stable|trixie|testing) return
 esac
 warn "没有可用于 $suite 的后端内核"
 return 1
 }
 interface=auto
 ip=
 netmask=
 gateway=
 dns='8.8.8.8 8.8.4.4'
 dns6='2001:4860:4860::8888 2001:4860:4860::8844'
 hostname=
 network_console=false
 set_debian_version 12
 mirror_protocol=https
 mirror_host=deb.debian.org
 mirror_directory=/debian
 mirror_proxy=
 security_repository=mirror
 account_setup=true
 username=debian
 password=
 authorized_keys_url=
 sudo_with_password=false
 timezone=Asia/Shanghai
 ntp=time.google.com
 disk_partitioning=true
 disk="/dev/$(lsblk -no PKNAME "$(df /boot | grep -Eo '/dev/[a-z0-9]+')")"
 force_gpt=true
 efi=
 esp=106
 filesystem=ext4
 kernel=
 cloud_kernel=false
 bpo_kernel=false
 install_recommends=true
 install='sudo wget curl ntp lsb-release net-tools gnupg git socat cron jq bash-completion'
 upgrade=
 kernel_params=
 force_lowmem=
 bbr=false
 ssh_port=
 hold=false
 power_off=false
 architecture=
 firmware=false
 force_efi_extra_removable=true
 grub_timeout=5
 dry_run=false
 apt_non_free_firmware=true
 apt_non_free=false
 apt_contrib=false
 apt_src=true
 apt_backports=true
 cidata=
 while [ $# -gt 0 ]; do
 case $1 in
 --cdn)
 ;;
 --aws)
 mirror_host=cdn-aws.deb.debian.org
 ntp=time.aws.com
 ;;
 --cloudflare)
 dns='1.1.1.1 1.0.0.1'
 dns6='2606:4700:4700::1111 2606:4700:4700::1001'
 ntp=time.cloudflare.com
 ;;
 --aliyun)
 dns='223.5.5.5 223.6.6.6'
 dns6='2400:3200::1 2400:3200:baba::1'
 mirror_host=mirrors.aliyun.com
 ntp=time.amazonaws.cn
 ;;
 --ustc|--china)
 dns='119.29.29.29'
 dns6='2402:4e00::'
 mirror_host=mirrors.ustc.edu.cn
 ntp=time.amazonaws.cn
 ;;
 --tuna)
 dns='119.29.29.29'
 dns6='2402:4e00::'
 mirror_host=mirrors.tuna.tsinghua.edu.cn
 ntp=time.amazonaws.cn
 ;;
 --interface)
 interface=$2
 shift
 ;;
 --ip)
 ip=$2
 shift
 ;;
 --netmask)
 netmask=$2
 shift
 ;;
 --gateway)
 gateway=$2
 shift
 ;;
 --dns)
 dns=$2
 shift
 ;;
 --dns6)
 dns6=$2
 shift
 ;;
 --hostname)
 hostname=$2
 shift
 ;;
 --network-console)
 network_console=true
 ;;
 --version)
 set_debian_version "$2"
 shift
 ;;
 --suite)
 set_suite "$2"
 shift
 ;;
 --release-d-i)
 daily_d_i=false
 ;;
 --daily-d-i)
 daily_d_i=true
 ;;
 --mirror-protocol)
 mirror_protocol=$2
 shift
 ;;
 --https)
 mirror_protocol=https
 ;;
 --mirror-host)
 mirror_host=$2
 shift
 ;;
 --mirror-directory)
 mirror_directory=${2%/}
 shift
 ;;
 --mirror-proxy|--proxy)
 mirror_proxy=$2
 shift
 ;;
 --reuse-proxy)
 set_mirror_proxy
 ;;
 --security-repository)
 security_repository=$2
 shift
 ;;
 --no-user|--no-account-setup)
 account_setup=false
 ;;
 --user|--username)
 username=$2
 shift
 ;;
 --password)
 password=$2
 shift
 ;;
 --authorized-keys-url)
 authorized_keys_url=$2
 shift
 ;;
 --sudo-with-password)
 sudo_with_password=true
 ;;
 --timezone)
 timezone=$2
 shift
 ;;
 --ntp)
 ntp=$2
 shift
 ;;
 --no-part|--no-disk-partitioning)
 disk_partitioning=false
 ;;
 --force-lowmem)
 [ "$2" != 0 ] && [ "$2" != 1 ] && [ "$2" != 2 ] && err '低内存级别只能是0、1或2'
 force_lowmem=$2
 shift
 ;;
 --disk)
 disk=$2
 shift
 ;;
 --no-force-gpt)
 force_gpt=false
 ;;
 --bios)
 efi=false
 ;;
 --efi)
 efi=true
 ;;
 --esp)
 esp=$2
 shift
 ;;
 --filesystem)
 filesystem=$2
 shift
 ;;
 --kernel)
 kernel=$2
 shift
 ;;
 --cloud-kernel)
 cloud_kernel=true
 ;;
 --bpo-kernel)
 bpo_kernel=true
 ;;
 --apt-non-free-firmware)
 apt_non_free_firmware=true
 ;;
 --apt-non-free)
 apt_non_free=true
 apt_contrib=true
 ;;
 --apt-contrib)
 apt_contrib=true
 ;;
 --apt-src)
 apt_src=true
 ;;
 --apt-backports)
 apt_backports=true
 ;;
 --no-apt-non-free-firmware)
 apt_non_free_firmware=false
 ;;
 --no-apt-non-free)
 apt_non_free=false
 ;;
 --no-apt-contrib)
 apt_contrib=false
 apt_non_free=false
 ;;
 --no-apt-src)
 apt_src=false
 ;;
 --no-apt-backports)
 apt_backports=false
 ;;
 --no-install-recommends)
 install_recommends=false
 ;;
 --install)
 install=$2
 shift
 ;;
 --no-upgrade)
 upgrade=none
 ;;
 --safe-upgrade)
 upgrade=safe-upgrade
 ;;
 --full-upgrade)
 upgrade=full-upgrade
 ;;
 --ethx)
 kernel_params="$kernel_params net.ifnames=0 biosdevname=0"
 ;;
 --bbr)
 bbr=true
 ;;
 --ssh-port)
 ssh_port=$2
 shift
 ;;
 --hold)
 hold=true
 ;;
 --power-off)
 power_off=true
 ;;
 --architecture)
 architecture=$2
 shift
 ;;
 --firmware)
 firmware=true
 ;;
 --no-force-efi-extra-removable)
 force_efi_extra_removable=false
 ;;
 --grub-timeout)
 grub_timeout=$2
 shift
 ;;
 --dry-run)
 dry_run=true
 ;;
 --cidata)
 cidata=$(realpath "$2")
 [ ! -f "$cidata/meta-data" ] && err '在cloud-init目录中找不到"meta-data"文件'
 [ ! -f "$cidata/user-data" ] && err '在cloud-init目录中找不到"user-data"文件'
 shift
 ;;
 *)
 err "未知选项: \"$1\""
 esac
 shift
 done
 [ -z "$architecture" ] && {
 architecture=$(dpkg --print-architecture 2>/dev/null) || {
 case $(uname -m) in
 x86_64)
 architecture=amd64
 ;;
 aarch64)
 architecture=arm64
 ;;
 i386)
 architecture=i386
 ;;
 *)
 err '未指定"--architecture"'
 esac
 }
 }
 [ -z "$kernel" ] && {
 kernel="linux-image-$architecture"
 [ "$cloud_kernel" = true ] && has_cloud_kernel && kernel="linux-image-cloud-$architecture"
 [ "$bpo_kernel" = true ] && has_backports && install="$kernel/$suite-backports $install"
 }
 [ -n "$authorized_keys_url" ] && ! download "$authorized_keys_url" /dev/null &&
 err "无法从 \"$authorized_keys_url\" 下载SSH授权的公钥"
 non_free_firmware_available=false
 case $suite in
 bookworm|stable|trixie|testing|sid|unstable)
 non_free_firmware_available=true
 ;;
 *)
 apt_non_free_firmware=false
 esac
 apt_components=main
 [ "$apt_contrib" = true ] && apt_components="$apt_components contrib"
 [ "$apt_non_free" = true ] && apt_components="$apt_components non-free"
 [ "$apt_non_free_firmware" = true ] && apt_components="$apt_components non-free-firmware"
 apt_services=updates
 [ "$apt_backports" = true ] && apt_services="$apt_services, backports"
 installer_directory="/boot/debian-$suite"
 save_preseed='cat'
 [ "$dry_run" = false ] && {
 [ "$(id -u)" -ne 0 ] && err '需要root权限'
 rm -rf "$installer_directory"
 mkdir -p "$installer_directory"
 cd "$installer_directory"
 save_preseed='tee -a preseed.cfg'
 }
 if [ "$account_setup" = true ]; then
 prompt_password
 elif [ "$network_console" = true ] && [ -z "$authorized_keys_url" ]; then
 prompt_password "为SSH网络控制台的安装程序用户选择一个密码: "
 fi
 $save_preseed /dev/null) ||
 password_hash=$(openssl passwd -5 "$password" 2>/dev/null) ||
 password_hash=$(busybox mkpasswd -m sha256 "$password" 2>/dev/null) || {
 for python in python3 python python2; do
 password_hash=$("$python" -c 'import crypt, sys; print(crypt.crypt(sys.argv[1], crypt.mksalt(crypt.METHOD_SHA256)))' "$password" 2>/dev/null) && break
 done
 }
 $save_preseed > ~root/.ssh/authorized_keys"
 $save_preseed  \"/etc/sudoers.d/90-user-$username\""
 $save_preseed  /etc/sysctl.d/bbr.conf'
 [ -n "$cidata" ] && in_target 'echo "{ datasource_list: [ NoCloud ], datasource: { NoCloud: { fs_label: ~ } } }" > /etc/cloud/cloud.cfg.d/99_debi.cfg'
 late_command='true'
 [ -n "$in_target_script" ] && late_command="$late_command; in-target sh -c '$in_target_script'"
 [ -n "$cidata" ] && late_command="$late_command; mkdir -p /target/var/lib/cloud/seed/nocloud; cp -r /cidata/. /target/var/lib/cloud/seed/nocloud/"
 echo "d-i preseed/late_command string $late_command" | $save_preseed
 [ "$power_off" = true ] && echo 'd-i debian-installer/exit/poweroff boolean true' | $save_preseed
 save_grub_cfg='cat'
 [ "$dry_run" = false ] && {
 base_url="$mirror_protocol://$mirror_host$mirror_directory/dists/$suite/main/installer-$architecture/current/images/netboot/debian-installer/$architecture"
 [ "$suite" = stretch ] && [ "$efi" = true ] && base_url="$mirror_protocol://$mirror_host$mirror_directory/dists/buster/main/installer-$architecture/current/images/netboot/debian-installer/$architecture"
 [ "$daily_d_i" = true ] && base_url="https://d-i.debian.org/daily-images/$architecture/daily/netboot/debian-installer/$architecture"
 firmware_url="https://cdimage.debian.org/cdimage/unofficial/non-free/firmware/$suite/current/firmware.cpio.gz"
 download "$base_url/linux" linux
 download "$base_url/initrd.gz" initrd.gz
 [ "$firmware" = true ] && download "$firmware_url" firmware.cpio.gz
 gzip -d initrd.gz
 echo preseed.cfg | cpio -o -H newc -A -F initrd
 if [ -n "$cidata" ]; then
 cp -r "$cidata" cidata
 find cidata | cpio -o -H newc -A -F initrd
 fi
 gzip -1 initrd
 mkdir -p /etc/default/grub.d
 tee /etc/default/grub.d/zz-debi.cfg 1>&2  "$tmp"
 cat "$tmp" > /etc/default/grub
 rm "$tmp"
 echo 'zz_debi=/etc/default/grub.d/zz-debi.cfg; if [ -f "$zz_debi" ]; then . "$zz_debi"; fi' >> /etc/default/grub
 grub_cfg=/boot/grub2/grub.cfg
 [ -d /sys/firmware/efi ] && grub_cfg=/boot/efi/EFI/*/grub.cfg
 grub2-mkconfig -o "$grub_cfg"
 elif command_exists grub-mkconfig; then
 tmp=$(mktemp)
 grep -vF zz_debi /etc/default/grub > "$tmp"
 cat "$tmp" > /etc/default/grub
 rm "$tmp"
 echo 'zz_debi=/etc/default/grub.d/zz-debi.cfg; if [ -f "$zz_debi" ]; then . "$zz_debi"; fi' >> /etc/default/grub
 grub_cfg=/boot/grub/grub.cfg
 grub-mkconfig -o "$grub_cfg"
 else
 err '无法找到 "update-grub" 或 "grub2-mkconfig" 或 "grub-mkconfig" 命令'
 fi
 save_grub_cfg="tee -a $grub_cfg"
 }
 mkrelpath=$installer_directory
 [ "$dry_run" = true ] && mkrelpath=/boot
 installer_directory=$(grub-mkrelpath "$mkrelpath" 2>/dev/null) ||
 installer_directory=$(grub2-mkrelpath "$mkrelpath" 2>/dev/null) || {
 err '无法找到 "grub-mkrelpath" 或 "grub2-mkrelpath" 命令'
 }
 [ "$dry_run" = true ] && installer_directory="$installer_directory/debian-$suite"
 kernel_params="$kernel_params lowmem/low=1"
 [ -n "$force_lowmem" ] && kernel_params="$kernel_params lowmem=+$force_lowmem"
 initrd="$installer_directory/initrd.gz"
 [ "$firmware" = true ] && initrd="$initrd $installer_directory/firmware.cpio.gz"
 $save_grub_cfg 1>&2 << EOF
 menuentry 'Debian Installer' --id debi {
 insmod part_msdos
 insmod part_gpt
 insmod ext2
 insmod xfs
 insmod btrfs
 linux $installer_directory/linux$kernel_params
 initrd $initrd
 }
 EOF
 ```
 | 
 |