#!/bin/sh set -u host= postinst_pkgs= postinst_script= dev= target= err() { local fmt="${1}" shift 1 printf "Error: ${fmt}\n" "${@}" 1>&3 } info() { local fmt="${1}" shift 1 printf "${fmt}\n" "${@}" 1>&3 } in_target() { chroot "${target}" "${@}" || return 1 return 0 } do_() { step="${1}" shift 1 do_${step} 1>&4 2>&4 } do_fdisk() { local script= local line= info 'Making partition table' script= while read line; do script="$(printf '%s\n' "${script}" "${line}")" done <<-EOF ${part_script} EOF printf '%s\n' "${script}" | sfdisk "${dev}" # Allow time to detect the new partition(s). sleep 5 return 0 } do_mkfs() { local fs= local mp= local type= local options= local dump= local pass= info 'Making file systems' while read fs mp type options dump pass; do case "${type}" in ''|'swap') continue;; esac case "${fs}" in '@DEV'*'@') fs="${fs%@}" fs="${fs#@DEV}" fs="${dev}${fs}" ;; *) continue ;; esac mkfs -t "${type}" "${fs}" || return 1 done <<-EOF ${fstab} EOF return 0 } do_mount() { local fs= local mp= local type= local options= local dump= local pass= info 'Mounting file systems' target="$(mktemp -d)" while read fs mp type options dump pass; do case "${type}" in ''|'swap') continue;; esac case "${fs}" in '@DEV'*'@') fs="${fs%@}" fs="${fs#@DEV}" fs="${dev}${fs}" ;; *) continue ;; esac mkdir -p "${target}${mp}" || return 1 mount -t "${type}" ${options:+-o "${options}"} \ "${fs}" "${target}${mp}" || return 1 done <<-EOF ${fstab} EOF return 0 } do_check_target() { if [ -e "${target}/bin/sh" ]; then err 'Unclean target' return 1 fi return 0 } do_debootstrap() { local include= info 'Installing base system' case "${extra_pkgs}" in ?*) include="--include=$(printf '%s,' ${extra_pkgs})" include="${include%,}" ;; esac debootstrap --arch="${arch}" "${include}" \ "${suite}" "${target}" "${mirror}" || return 1 return 0 } do_chroot_setup() { info 'Setting up system' case "$(uname -s)" in 'Linux') mount -t proc proc "${target}/proc" || return 1 mount -t sysfs sysfs "${target}/sys" || return 1 mount --bind /dev "${target}/dev" || return 1 mkdir -p "${target}/dev/pts" || return 1 mount -t devpts -o noexec,nosuid,gid=5,mode=620 \ devpts "${target}/dev/pts" || return 1 mount --bind /run "${target}/run" || return 1 ;; esac cat >"${target}/usr/sbin/policy-rc.d" <<-EOF #!/bin/sh exit 101 EOF chmod a+rx "${target}/usr/sbin/policy-rc.d" || return 1 if [ -e "${target}/sbin/start-stop-daemon" ]; then in_target dpkg-divert --quiet --add \ --divert '/sbin/start-stop-daemon.REAL' \ --rename '/sbin/start-stop-daemon' || return 1 fi cat >"${target}/sbin/start-stop-daemon" <<-EOF #!/bin/sh exit 0 EOF chmod a+rx "${target}/sbin/start-stop-daemon" || return 1 printf '%s\n' "${host}" >"${target}/etc/hostname" cat >"${target}/etc/hosts" <<-EOF 127.0.0.1 localhost 127.0.0.1 ${host} # The following lines are desirable for IPv6 capable hosts ::1 ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters EOF mkdir -p "${target}/etc/network" || return 1 cat >"${target}/etc/network/interfaces" <<-EOF # This file describes the network interfaces available on your system # and how to activate them. For more information, see interfaces(5). # The loopback network interface auto lo iface lo inet loopback EOF for file in /etc/networks /etc/resolv.conf; do if ! [ -f "${file}" ]; then continue fi if [ -f "${target}${file}" ]; then mv "${target}${file}" "${target}${file}.REAL" fi mkdir -p "${target}${file%/*}" cp "${file}" "${target}${file}" done export LANG="${locale}" export PERL_BADLANG=0 export DEBCONF_ADMIN_EMAIL='' export IT_LANG_OVERRIDE='C' export APT_LISTCHANGES_FRONTEND='none' export SUDO_FORCE_REMOVE='yes' return 0 } do_fstab_setup() { local fs= local mp= local type= local options= local dump= local pass= info 'Making file system table' case "$(uname -s)" in 'Linux') printf '%-15s %-15s %-7s %-15s %-7s %s\n' \ 'proc' '/proc' 'proc' 'defaults' '0' '0' \ >"${target}/etc/fstab" ;; esac while read fs mp type options dump pass; do case "${type}" in '') continue;; esac case "${fs}" in '@DEV'*'@') fs="${fs%@}" fs="${fs#@DEV}" fs="${dev}${fs}" fs="UUID=$(blkid -o value -s UUID "${fs}")" ;; esac printf '%-15s %-15s %-7s %-15s %-7s %s\n' \ "${fs}" "${mp}" "${type}" "${options}" \ "${dump}" "${pass}" >>"${target}/etc/fstab" done <<-EOF ${fstab} EOF } do_apt_update() { info 'Downloading package lists' sed 's/^\t*//;' >"${target}/etc/apt/sources.list" <<-EOF ${apt_sources} EOF case "${apt_preferences+set}" in 'set') sed 's/^\t*//;' >"${target}/etc/apt/preferences" <<-EOF ${apt_preferences} EOF esac in_target apt-get update || return 1 in_target apt-get -q -y dist-upgrade || return 1 in_target apt-get clean || return 1 return 0 } do_locale_setup() { local l= local gen= info 'Setting locale' if ! [ "x${locale}" = 'xC' ] || ! [ "x${supported_locales}" = 'x' ] then in_target apt-get -q -y install locales in_target apt-get clean || return 1 gen=false for l in $(printf '%s\n' "${locale}" ${supported_locales} | \ sort -u); do if ! LANG=C in_target validlocale "${l}" \ >>"${target}/etc/locale.gen" 2>/dev/null then gen=true fi done if ${gen}; then in_target locale-gen --keep-existing >/dev/null || \ return 1 fi fi printf 'LANG=%s\n' "${locale}" >"${target}/etc/default/locale" } do_tz_setup() { info 'Setting time zone' if [ "x${time_zone}" = 'x' ] || ! [ -e \ "${target}/usr/share/zoneinfo/${time_zone}" ]; then err '%s: Invalid time zone' "${time_zone}" return 1 fi printf '%s\n' "${time_zone}" >"${target}/etc/timezone" cp -f "${target}/usr/share/zoneinfo/${time_zone}" \ "${target}/etc/localtime" } do_user_setup() { local group= local f= info 'Setting users and passwords' in_target shadowconfig "${passwd_shadow}" || return 1 case "${root_passwd_crypted}" in ?*) in_target usermod --password="${root_passwd_crypted}" 'root' \ || return 1 ;; esac if ${user_make}; then in_target adduser --disabled-password \ --gecos "${user_full_name}" \ "${user_name}" || return 1 in_target usermod --password="${user_passwd_crypted}" \ "${user_name}" || return 1 in_target chown "${user_name}:${user_name}" \ "/home/${user_name}" || return 1 if ! ${root_login}; then in_target apt-get -q -y install sudo || return 1 in_target apt-get clean || return 1 case " ${user_groups} " in *' sudo '*);; *) user_groups="${user_groups} sudo" ;; esac if in_target update-alternatives \ --display libgksu-gconf-defaults \ 1>/dev/null 2>&1; then f='/usr/share/libgksu/debian' f="${f}/gconf-defaults.libgksu-sudo" in_target update-alternatives \ --set libgksu-gconf-defaults "${f}" || \ return 1 in_target update-gconf-defaults fi printf 'Aptitude::Get-Root-Command "%s";' \ 'sudo:/usr/bin/sudo' \ >"${target}/etc/apt/apt.conf.d/00aptitude" fi for group in ${user_groups}; do in_target adduser "${user_name}" "${group}" || return 1 done fi } do_debconf_setup() { local owner= local name= local type= local value= info 'Preseeding debconf database' printf '%s' "${debconf_selections}" | while read owner name type value do printf '%s %s %s %s\n' "${owner}" "${name}" "${type}" "${value}" done | in_target debconf-set-selections } do_install_extra() { local i= info 'Installing extra packages' i=0 while [ ${i} -lt 3 ]; do in_target apt-get -q -y install ${postinst_pkgs} && return 0 in_target apt-get clean || return 1 i=$((${i} + 1)) done return 1 } do_postinst() { . "${postinst_script}" } do_chroot_cleanup() { local mp= info 'Cleaning up system' for file in /etc/networks /etc/resolv.conf; do if [ -f "${target}${file}.REAL" ]; then mv "${target}${file}.REAL" "${target}${file}" fi done rm -f "${target}/usr/sbin/policy-rc.d" rm -f "${target}/sbin/start-stop-daemon" in_target dpkg-divert --quiet --remove \ --rename '/sbin/start-stop-daemon' || return 1 case "$(uname -s)" in 'Linux') for mp in 'run' 'dev/pts' 'dev' 'sys' 'proc'; do while ! umount "${target}/${mp}"; do sleep 1 done done ;; esac return 0 } do_umount() { local fs= local mp= local type= local options= local dump= local pass= info 'Unmounting file systems' while read fs mp type options dump pass; do case "${type}" in ''|'swap') continue;; esac case "${fs}" in '@DEV'*'@') ;; *) continue ;; esac while ! umount "${target}${mp}"; do sleep 1 done rmdir "${target}${mp}" done <<-EOF ${fstab} EOF return 0 } usage() { printf 'Usage: %s host device\n' "${0}" } main() { local host_dir= local log= exec 3>&1 if [ ${#} -ne 2 ]; then usage >&2 return 1 fi host="${1}" host_dir="$(dirname "${0}")/hosts/${host}" if ! [ -d "${host_dir}" ]; then err '%s: Unknown host' "${host}" return 1 fi dev="${2}" . "${host_dir}/conf" postinst_pkgs="$(sed 's/#.*$//;' "${host_dir}/pkgs")" postinst_script="${host_dir}/postinst" log="$(mktemp)" exec 4>"${log}" do_ fdisk || { return 1; } do_ mkfs || { return 1; } do_ mount || { return 1; } do_ check_target || { do_ umount; return 1; } do_ debootstrap || { do_ umount; return 1; } do_ chroot_setup || { do_ umount; return 1; } do_ fstab_setup || { do_ chroot_cleanup; do_ umount; return 1; } do_ apt_update || { do_ chroot_cleanup; do_ umount; return 1; } do_ locale_setup || { do_ chroot_cleanup; do_ umount; return 1; } do_ tz_setup || { do_ chroot_cleanup; do_ umount; return 1; } do_ debconf_setup || { do_ chroot_cleanup; do_ umount; return 1; } do_ install_extra || { do_ chroot_cleanup; do_ umount; return 1; } do_ user_setup || { do_ chroot_cleanup; do_ umount; return 1; } do_ postinst || { do_ chroot_cleanup; do_ umount; return 1; } do_ chroot_cleanup || { do_ umount; return 1; } exec 4>&- mkdir -p "${target}/var/log/installer" mv "${log}" "${target}/var/log/installer/log" do_umount || return 1 exec 3>&- return 0 } main "${@}"