#!/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=10013 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 "$@" [ "$VERBOSE" ] && printf '+ apt-get %s\n' "$visible_args" >&2 || true apt-get "$@" } idem() { if [ ! -e "${!#}" ]; then "$@"; fi; } idem_mknod() { if [ ! -e "$3" ]; then mknod "$@"; fi; } # This function is copied (with modifications) from debootstrap. install_devices() { local TARGET="$rootfs" [ "$TARGET" -a -d "$TARGET" ] || die 'no $TARGET' idem mkdir "$TARGET"/dev # The list of devices that can be created in a container comes from # src/core/cgroup.c in the systemd source tree. idem_mknod -m 666 "$TARGET"/dev/null c 1 3 idem_mknod -m 666 "$TARGET"/dev/zero c 1 5 idem_mknod -m 666 "$TARGET"/dev/full c 1 7 idem_mknod -m 666 "$TARGET"/dev/random c 1 8 idem_mknod -m 666 "$TARGET"/dev/urandom c 1 9 idem_mknod -m 666 "$TARGET"/dev/tty c 5 0 idem mkdir "$TARGET"/dev/pts/ idem mkdir "$TARGET"/dev/shm/ # Inside a container, we might not be allowed to create /dev/ptmx. # If not, do the next best thing. if ! idem_mknod -m 666 "$TARGET"/dev/ptmx c 5 2; then idem ln -s pts/ptmx "$TARGET"/dev/ptmx fi idem ln -s /proc/self/fd "$TARGET"/dev/fd idem ln -s /proc/self/fd/0 "$TARGET"/dev/stdin idem ln -s /proc/self/fd/1 "$TARGET"/dev/stdout idem ln -s /proc/self/fd/2 "$TARGET"/dev/stderr } mount_virtfs() { local TARGET="$1" [ "$TARGET" ] || die 'no $TARGET' [ -d "$TARGET"/proc ] || mkdir "$TARGET"/proc [ -d "$TARGET"/sys ] || mkdir "$TARGET"/sys mount -t proc proc "$TARGET"/proc mount -t sysfs sysfs "$TARGET"/sys } umount_virtfs() { local TARGET="$1" fail= [ "$TARGET" ] || die 'no $TARGET' umount "$TARGET"/proc || fail=y umount "$TARGET"/sys || fail=y [ ! "$fail" ] } 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" install_devices } 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 } dpkg_extract_with_info() { local deb="$1" TARGET="$2" multiarch="$3" command PKG PKG=${deb##*/} PKG=${PKG%%_*} PKG=$PKG$multiarch command=$(cat <<'EOF' if [ "$TAR_FILENAME" = ./control ]; then (sed "/^Package:/a Status: install ok installed"; echo) >> "$TARGET"/var/lib/dpkg/status else f=$TARGET/var/lib/dpkg/info/$PKG.${TAR_FILENAME#./} cat > "$f" chmod $TAR_MODE "$f" fi EOF ) (export PKG TARGET; dpkg --ctrl-tarfile "$deb" | tar -x --to-command "$command") dpkg --fsys-tarfile "$deb" | (cd "$TARGET" && tar -xv) | sed 's?^\.??; s?^/$?/.?; s?/$??' > "$TARGET/var/lib/dpkg/info/$PKG.list" } apt_run_inst() { apt_get -s -yqq install "$@" | dpkg_inst_from_apt } dpkg_inst_from_apt() { while read line; do parse_apt_noact_line "$line" || die "unexpected output from apt-get: $line" export LC_ALL=C export DEBIAN_FRONTEND=noninteractive export DPKG_MAINTSCRIPT_PACKAGE="$package" DPKG_MAINTSCRIPT_ARCH="$arch" is_multiarch_same "$package" && multiarch=":$arch" || multiarch= 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 "$@" actions=$(mktemp) || die 'mktemp failed' apt_get -s -yqq install "$@" | tee "$actions" | dpkg_extract_from_apt if [ "$EXTRACT_DPKG_INFO" ]; then install_etc_passwd dpkg_inst_from_apt < "$actions" fi rm "$actions" } dpkg_extract_from_apt() { 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 if [ "$EXTRACT_DPKG_INFO" ]; then is_multiarch_same "$package" && multiarch=":$arch" || multiarch= dpkg_extract_with_info "$deb" "$rootfs" "$multiarch" else dpkg-deb --extract "$deb" "$rootfs" 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 } declare -A is_multiarch_same multicheck() { local m if [ ! "$multichecked" ]; then for m in $(multiarch_same_packages); do is_multiarch_same[$m]=y done fi multichecked=y } multiarch_same_packages() { perl -00 -ne \ '/^Multi-Arch: same/mi || next; /^Package: (.*)$/m && print "$1\n"' \ "$(main_packages_file)" | sort -u } is_multiarch_same() { multicheck [ "${is_multiarch_same[$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' # 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. # Note: apt_extract() runs preinst and postinst scripts itself when # $EXTRACT_DPKG_INFO is true. apt_extract $packages if [ "$SKIP_INSTALL" ]; then apt_extract $extra_packages else export LC_ALL=C export DEBIAN_FRONTEND=noninteractive install_etc_passwd # This handles (2). if [ "$FIX_BROKEN" ]; then dpkg --root="$rootfs" --configure -a fi apt_get install -y $packages $extra_packages fi