From 1fc23d6d26a25557b5c5ef1a204ef573421f2706 Mon Sep 17 00:00:00 2001 From: Andrew Cady Date: Wed, 27 May 2020 21:44:13 -0400 Subject: add missing dependency "selfstrep" (from sami.git) --- Makefile | 4 +- selfstrap | 453 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 456 insertions(+), 1 deletion(-) create mode 100755 selfstrap diff --git a/Makefile b/Makefile index 1baa086..fd6aa0b 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,8 @@ sudo := $(shell [ "$(id -u)" = 0 ] || echo sudo) build: stack build +executables = ~/.local/bin/$(binary) ./selfstrap + install: build stack install - $(sudo) install ~/.local/bin/$(binary) /usr/local/bin + $(sudo) install $(executables) /usr/local/bin diff --git a/selfstrap b/selfstrap new file mode 100755 index 0000000..6766c0c --- /dev/null +++ b/selfstrap @@ -0,0 +1,453 @@ +#!/bin/bash +debian_mirror=http://httpredir.debian.org/debian +debian_security_mirror=http://security.debian.org +EXTRA_PACKAGES='apt debian-archive-keyring locales' + +die() { printf 'Error: %s\n' "$*"; exit 1; } + +usage() +{ + echo "Usage: $0 -t [options] [packages]" >&2 + cat < Use prog for chroot command + --packages= Text file contains names of packages to install + --real-apt Use 'apt-get install' to install packages + --skip-update Do not run 'apt-get update' at startup +EOF + exit ${1:-1} +} + +GETOPT=$(getopt -n "${0##*/}" -o t:hv --long help,target:,verbose,unpack,chroot:,packages:,real-apt,skip-update -- "$@") || exit +eval set -- "$GETOPT" +while [ $# -gt 0 ]; do + case "$1" in + -t|--target) TARGET=$2; shift ;; + -h|--help) usage 0 ;; + -v|--verbose) VERBOSE=y ;; + --skip-update) SKIP_UPDATE=y ;; + --unpack) UNPACK_ONLY=y ;; + --packages) x=$(cat < "$2") || die "could not read file '$2'" + EXTRA_PACKAGES="$EXTRA_PACKAGES $x" + shift ;; + --chroot) CHROOT_PROG=$2; shift ;; + --real-apt) REAL_APT_INSTALL=y ;; + --) shift; break ;; + *) usage 1 ;; + esac + shift +done + +[ "$TARGET" ] || usage + +! [ "$REAL_APT_INSTALL" -a "$UNPACK_ONLY" ] || die "option --real-apt is incompatible with option --unpack" + +EXTRA_PACKAGES="$EXTRA_PACKAGES $*" + +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' + TARGET=$(realpath "$TARGET") || die 'realpath failed' + [ "$TARGET" ] || die 'no $TARGET' + [ -d "$TARGET" ] || mkdir "$TARGET" || die 'could not mkdir($TARGET)' +} + +generate_apt_config() +{ + APT_CONFIG=$(mktemp) || exit + chmod 644 "$APT_CONFIG" + cat > "$APT_CONFIG" <&2 + fi + + set -- "$@" -o Apt::Install-Recommends=false + set -- "$@" -o Apt::Architecture="$target_arch" + set -- "$@" -o Apt::Default-Release="$target_release" + + # Set default paths to within the created system. + set -- "$@" -o Dir="$TARGET" + set -- "$@" -o Dir::State::Status="$TARGET"/var/lib/dpkg/status + + # We must also set dpkg to use the created system. + # Dpkg::options requires undocumented apt CLI magic. + if [ "$DEBUG_DPKG" ]; then + [ "$DEBUG_DPKG" -gt 0 ] 2>/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="$TARGET" + set -- "$@" -o DPkg::options::arg2=--force-unsafe-io + + # Use the calling system for these. This is an optimization. + #set -- "$@" -o Dir::Etc::sourcelist=/etc/apt/sources.list + #set -- "$@" -o Dir::Etc::sourceparts=/etc/apt/sources.list.d + 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 + set -- "$@" -o Dir::Cache::archives=/var/cache/apt/archives + + # Avoid deleting lists on the calling system. + set -- "$@" -o APT::Get::List-Cleanup=false + + apt-"${apt_cmd}" "$@" +} +apt_get() { apt_ get "$@"; } +apt_cache() { apt_ cache "$@"; } + +idem() { if [ ! -e "${!#}" ]; then "$@"; fi; } +idem_mknod() { if [ ! -e "$3" ]; then mknod "$@"; fi; } + +# This function is copied (with modifications) from debootstrap. +install_devices() +{ + [ "$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() +{ + [ "$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() +{ + [ "$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() +{ + [ "$TARGET" ] || die 'no $TARGET' + set -B + mkdir -p \ + "$TARGET"/etc/apt/{preferences.d,apt.conf.d,trusted.gpg.d,sources.list.d} \ + "$TARGET"/var/{lib/{apt/lists/partial,dpkg/{info,parts,triggers,alternatives,updates}},cache/apt} \ + "$TARGET"/var/log/apt + + touch "$TARGET"/var/lib/dpkg/status + + [ -d "$TARGET"/usr/bin ] || mkdir -p "$TARGET"/usr/bin + [ -e "$TARGET/usr/bin/awk" -o -L "$TARGET/usr/bin/awk" ] || ln -s mawk "$TARGET/usr/bin/awk" + + write_lines_once "$TARGET"/var/lib/dpkg/arch "$target_arch" + write_sources_list + install_devices +} + +write_sources_list() +{ + local dest="$TARGET"/etc/apt/sources.list + [ -e "$dest" ] && return + printf '%s\n' \ + "deb ${debian_mirror} ${target_release} main contrib non-free" \ + "deb ${debian_mirror} ${target_release}-backports main contrib non-free" \ + "deb ${debian_security_mirror} ${target_release}/updates main contrib non-free" | + column -t > "$dest" +} + +parse_apt_simul_line() +{ + set -- $* + action=$1 + package=$2 + # Third word might either be "(version" or "[version]" depending on which + # apt is producing the output. Newer apt versions apparently add an extra + # "[version]" parameter, making "(version" the fourth argument. + version=${3#\(} + version=${version#\[} + version=${version%]} + shift 3 + + # Find the last word, which is inside (a group of words that are inside + # parentheses). + while [ "$1" ]; do + case "$1" in + *\)) + arch=${1%\)} + arch=${arch#\[} + arch=${arch%\]} + return + ;; + *) shift ;; + esac + done + return 1 +} + +dpkg_unpack() +{ + [ "$TARGET" -a -d "$TARGET" ] || die 'no $TARGET' + local deb="$1" multiarch="$2" SET_STATUS="$3" command PKG + PKG=${deb##*/} + PKG=${PKG%%_*} + PKG=$PKG$multiarch + + extract_tmp_ci "$deb" + install_metadata_from_tmp_ci # uses TARGET PKG SET_STATUS + remove_tmp_ci + + dpkg --fsys-tarfile "$deb" | + (cd "$TARGET" && tar -xv) | + sed 's?^\.??; s?^/$?/.?; s?/$??' > "$TARGET/var/lib/dpkg/info/$PKG.list" +} + +install_metadata_from_tmp_ci() # uses TARGET PKG SET_STATUS +{ + (cd "$TARGET"/var/lib/dpkg/tmp.ci || die "cannot cd to /var/lib/dpkg/tmp.ci" +# PKG=$(sed -n 's/^Package: *//p' control) + for f in *; do + [ "$f" = postinst ] && SET_STATUS=${SET_STATUS:+installed} || true + [ "$f" = control ] || mv "$f" "$TARGET"/var/lib/dpkg/info/"$PKG"."$f" + done + if [ "$SET_STATUS" -a -e control ]; then + (sed "/^Package:/a Status: install ok $SET_STATUS"; echo) < control >> "$TARGET"/var/lib/dpkg/status + fi) +} + +verbosely() +{ + if [ "$VERBOSE" ]; then + (set -x; "$@") + else + "$@" + fi +} + +remove_tmp_ci() +{ + [ ! -e "$TARGET"/var/lib/dpkg/tmp.ci ] || rm -r "$TARGET"/var/lib/dpkg/tmp.ci +} + +# /var/lib/dpkg/tmp.ci/control is read by debconf to determine the owner package +# See `grep -A3 control /usr/share/debconf/frontend` +extract_tmp_ci() +{ + local deb="$1" + remove_tmp_ci + mkdir "$TARGET"/var/lib/dpkg/tmp.ci + dpkg --ctrl-tarfile "$deb" | tar -C "$TARGET"/var/lib/dpkg/tmp.ci -x +} + +dpkg_configure_from_apt_actions() +{ + while read line; do + parse_apt_simul_line "$line" || die "parse_apt_simul_line: 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 "$TARGET"/"$preinst" ]; then + extract_tmp_ci "$deb" + verbosely ${CHROOT_PROG:-chroot} "$TARGET" "$preinst" install + remove_tmp_ci + fi + ;; + Conf) + export DPKG_MAINTSCRIPT_NAME=postinst + postinst=/var/lib/dpkg/info/${package}${multiarch}.postinst + if [ -x "$TARGET"/"$postinst" ]; then + extract_tmp_ci "$deb" + verbosely ${CHROOT_PROG:-chroot} "$TARGET" "$postinst" configure + remove_tmp_ci + fi + ;; + Remv) ;; + *) die "dpkg_configure_from_apt_actions: unknown apt simul action: $action" ;; + esac + done +} + +apt_extract() +{ + apt_get -d -yqq install "$@" + actions=$(mktemp) || die 'mktemp failed' + apt_get -s -yqq install "$@" > "$actions" || die 'apt-get failed' + + dpkg_unpack_from_apt_actions < "$actions" || die 'dpkg unpack (using internal dpkg) failed' + + if [ ! "$UNPACK_ONLY" ]; then + install_etc_passwd + dpkg_configure_from_apt_actions < "$actions" || die 'dpkg configure (using internal dpkg) failed' + fi + + rm "$actions" +} + +dpkg_unpack_from_apt_actions() +{ + while read line; do + + parse_apt_simul_line "$line" || die "parse_apt_simul_line: 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 "deb not found: $deb" + } + case "$action" in + Inst) + printf 'Unpacking %s\n' "${deb##*/}" >&2 + if [ "$REAL_APT_INSTALL" ]; then + dpkg --extract "$deb" "$TARGET" || die 'dpkg' + else + is_multiarch_same "$package" && multiarch=":$arch" || multiarch= + [ "$UNPACK_ONLY" ] && set_status=unpacked || set_status=installed + dpkg_unpack "$deb" "$multiarch" "$set_status" || die "dpkg_unpack" + fi + ;; + Conf) ;; + Remv) ;; + *) die "dpkg_unpack_from_apt_actions: unknown apt simul action: $action" ;; + esac + done +} + +install_etc_passwd() +{ + [ -e "$TARGET"/etc/passwd ] || cp "$TARGET"/usr/share/base-passwd/passwd.master "$TARGET"/etc/passwd + [ -e "$TARGET"/etc/group ] || cp "$TARGET"/usr/share/base-passwd/group.master "$TARGET"/etc/group +} + +required_packages() +{ + apt_cache dumpavail | + perl -00 -ne '/^Priority: required/m || next; /^Package: (.*)$/m && print "$1\n"' | + 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() +{ + apt_cache dumpavail | + perl -00 -ne '/^Multi-Arch: same/mi || next; /^Package: (.*)$/m && print "$1\n"' | + sort -u +} + +is_multiarch_same() +{ + multicheck + [ "${is_multiarch_same[$1]}" ] +} + +apt_update_stamp=~/.selfstrap/apt-update-stamp + +apt_get_update() +{ + if [ ! -e "$apt_update_stamp" ] || [ $(( $(date +%s) - $(stat -c %Y "$apt_update_stamp") )) -gt $(( 60 * 60 * 24 )) ] + then + apt_get update + mkdir -p ~/.selfstrap + touch "$apt_update_stamp" + fi +} + +set -e + +target_arch=$(dpkg-architecture -q DEB_HOST_ARCH) || die 'dpkg-architecture failed' +target_release=$(current_debian_codename) && [ "$target_release" ] || die 'could not determine Debian release name' + +# Set things up so apt-get update works. +sanity_checks +generate_apt_config +populate_rootfs +[ "$SKIP_UPDATE" ] || apt_get_update + +required_packages=$(required_packages) && [ "$required_packages" ] || die 'failed to determine list of required packages' + +export LC_ALL=C +export DEBIAN_FRONTEND=noninteractive + +if [ "$REAL_APT_INSTALL" ]; then + # 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 + + # Unpack required packages. Handles (1) + # Note: populate_rootfs() already created a necessary symlink /usr/bin/awk -> /usr/bin/mawk + apt_extract $required_packages + + # This handles (2). + # An alternative (used by debootstrap) is to configure base-passwd + install_etc_passwd + + apt_get install -y $required_packages $EXTRA_PACKAGES +else + apt_extract $required_packages + apt_extract $EXTRA_PACKAGES +fi -- cgit v1.2.3