#!/bin/bash debian_mirror=http://httpredir.debian.org/debian if [ $# = 1 ]; then rootfs=$1 else echo "Usage: $0 " >&2 exit 1 fi die() { printf 'Error: %s\n' "$*"; exit 1; } am_root() { [ "$(id -u)" = 0 ]; } current_debian_codename() { # lsb_release -cs sed -ne 's/^VERSION=.* (\(.*\)).*/\1/p' /etc/os-release } sanity_checks() { am_root || die 'you are not root' rootfs=$(realpath "$rootfs") || die 'realpath failed' [ "$rootfs" ] || die 'no $rootfs' [ -d "$rootfs" ] || mkdir "$rootfs" || die 'could not mkdir($rootfs)' } generate_apt_config() { APT_CONFIG=$(mktemp) || exit chmod 644 "$APT_CONFIG" cat > "$APT_CONFIG" </dev/null || DEBUG_DPKG=013 set -- -o DPkg::options::arg0=--debug="${DEBUG_DPKG}" "$@" fi # This is the important one: set -- -o DPkg::options::arg1=--root="$rootfs" "$@" set -- -o DPkg::options::arg2=--force-unsafe-io "$@" # Use the calling system for these. This is an optimization. set -- -o Dir::Cache::archives=/var/cache/apt/archives "$@" set -- -o Dir::Etc::Trusted=/etc/apt/trusted.gpg "$@" set -- -o Dir::Etc::TrustedParts=/etc/apt/trusted.gpg.d "$@" set -- -o Dir::State::Lists=/var/lib/apt/lists "$@" # Avoid deleting lists on the calling system. set -- -o APT::Get::List-Cleanup=false "$@" apt-get "$@" } write_lines_once() { local output="$1" shift [ -e "$output" ] || printf '%s\n' "$@" > "$output" } populate_rootfs() { [ "$rootfs" ] || die 'no $rootfs' set -B mkdir -p \ "$rootfs"/etc/apt/{preferences.d,apt.conf.d,trusted.gpg.d,sources.list.d} \ "$rootfs"/var/{lib/{apt/lists/partial,dpkg/{info,parts,triggers,alternatives,updates}},cache/apt} \ "$rootfs"/var/log/apt touch "$rootfs"/var/lib/dpkg/status write_lines_once "$rootfs"/var/lib/dpkg/arch "$rootfs_arch" write_lines_once "$rootfs"/etc/apt/sources.list \ "deb $debian_mirror $release main contrib non-free" \ "deb http://security.debian.org $release/updates main contrib non-free" } parse_apt_noact_line() { set -- $* action=$1 package=$2 version=${3#\(} shift 3 while [ "$1" ]; do case "$1" in *\)) arch=${1%\)} arch=${arch#\[} arch=${arch%\]} return ;; *) shift ;; esac done return 1 } extract_dpkg_info() { local deb="$1" TARGET="$2" multiarch="$3" command PKG PKG=${deb##*/} PKG=${PKG%%_*} PKG=$PKG$multiarch command=' [ "$TAR_FILENAME" = ./control ] && exit f=$TARGET/var/lib/dpkg/info/$PKG.${TAR_FILENAME#./} cat > "$f" chmod $TAR_MODE "$f" ' (export PKG TARGET dpkg --ctrl-tarfile "$deb" | tar -x --to-command "$command") dpkg --fsys-tarfile "$deb" | tar -t | sed 's?^\.??; s?^/$?/.?; s?/$??' > "$TARGET/var/lib/dpkg/info/$PKG.list" } apt_run_inst() { apt_get -s -yqq install "$@" | while read line; do parse_apt_noact_line "$line" || die "unexpected output from apt-get: $line" export DPKG_MAINTSCRIPT_PACKAGE="$package" DPKG_MAINTSCRIPT_ARCH="$arch" if is_multiarch_same "$package"; then multiarch=":$arch" else multiarch= fi case "$action" in Inst) export DPKG_MAINTSCRIPT_NAME=preinst preinst=/var/lib/dpkg/info/${package}${multiarch}.preinst if [ -x "$rootfs"/"$preinst" ]; then (set -x; chroot "$rootfs" "$preinst" install) fi ;; Conf) export DPKG_MAINTSCRIPT_NAME=postinst postinst=/var/lib/dpkg/info/${package}${multiarch}.postinst if [ -x "$rootfs"/"$postinst" ]; then (set -x; chroot "$rootfs" "$postinst" configure) fi ;; Remv) ;; *) die "impossible" ;; esac done } apt_extract() { apt_get -d -yqq install "$@" apt_get -s -yqq install "$@" | while read line; do parse_apt_noact_line "$line" || die "unexpected output from apt-get: $line" deb=/var/cache/apt/archives/${package}_${version//:/%3a}_${arch}.deb [ -f "$deb" ] || { echo "line=$line" >&2 printf '%s\n' "$action" "$package" "$version" "$arch" >&2 die "unexpected output from apt-get" } case "$action" in Inst) printf 'Extracting %s\n' "${deb##*/}" >&2 dpkg-deb --extract "$deb" "$rootfs" if [ "$EXTRACT_DPKG_INFO" ]; then is_multiarch_same "$package" && multiarch=":$arch" || multiarch= extract_dpkg_info "$deb" "$rootfs" "$multiarch" fi ;; Conf) ;; Remv) ;; *) die "impossible" ;; esac done } install_etc_passwd() { [ -e "$rootfs"/etc/passwd ] || cp "$rootfs"//usr/share/base-passwd/passwd.master "$rootfs"/etc/passwd [ -e "$rootfs"/etc/group ] || cp "$rootfs"//usr/share/base-passwd/group.master "$rootfs"/etc/group } main_packages_file() { local uri_part="${debian_mirror#*/}" while [ "${uri_part:0:1}" = / ]; do uri_part=${uri_part#?} done uri_part=${uri_part//\//_} printf '/var/lib/apt/lists/%s_dists_%s_main_binary-%s_Packages\n' "$uri_part" "$release" "$rootfs_arch" } required_packages() { perl -00 -ne \ '/^Priority: required/m || next; /^Package: (.*)$/m && print "$1\n"' \ "$(main_packages_file)" | sort -u } multiarch_same_packages() { [ "$multiarch_same_packages" ] || multiarch_same_packages=$(multiarch_same_packages_) echo "$multiarch_same_packages" } multiarch_same_packages_() { perl -00 -ne \ '/^Multi-Arch: same/mi || next; /^Package: (.*)$/m && print "$1\n"' \ "$(main_packages_file)" | sort -u } is_multiarch_same() { local p for p in $(multiarch_same_packages); do [ "$1" = "$p" ] && return done return 1 } set -e rootfs_arch=$(dpkg-architecture -q DEB_HOST_ARCH) || die 'dpkg-architecture failed' release=$(current_debian_codename) && [ "$release" ] || die 'could not determine Debian release name' if [ "$FAST_MODE" ]; then SKIP_INSTALL=y EXTRACT_DPKG_INFO=y RUN_INST_SCRIPTS=y fi # Set things up so apt-get update works. sanity_checks generate_apt_config populate_rootfs # Initial apt-get update will determine what we shall install. [ "$SKIP_UPDATE" ] || apt_get update packages=$(required_packages) && [ "$packages" ] || die 'failed to determine list of required packages' extra_packages='apt debian-archive-keyring locales' # Extract files from downloaded packages. # # Some files need to be present before 'apt-get install' can install anything. # In particular: # # 1. binaries used by dpkg 'inst' scripts. # 2. /etc/passwd and /etc/group so that 'chown' works # Rather than fuss about (1), extract everything from all packages. apt_extract $packages # This handles (2). install_etc_passwd # Finally we are ready to run apt-get install. export LC_ALL=C export DEBIAN_FRONTEND=noninteractive if [ "$SKIP_INSTALL" ]; then apt_extract $extra_packages if [ "$RUN_INST_SCRIPTS" ]; then apt_run_inst $packages $extra_packages fi else if [ "$FIX_BROKEN" ]; then dpkg --root="$rootfs" --configure -a fi apt_get install -y $packages $extra_packages fi