From 7392932128db786fe915e8ef432953fbf55bc0f2 Mon Sep 17 00:00:00 2001 From: Andrew Cady Date: Wed, 30 Aug 2017 19:20:21 -0400 Subject: selfstrap: debootstrap using local /var/cache/apt/archives --- multistrap/selfstrap | 302 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100755 multistrap/selfstrap (limited to 'multistrap/selfstrap') diff --git a/multistrap/selfstrap b/multistrap/selfstrap new file mode 100755 index 0000000..265049b --- /dev/null +++ b/multistrap/selfstrap @@ -0,0 +1,302 @@ +#!/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 -- cgit v1.2.3