diff options
Diffstat (limited to 'hooks')
-rwxr-xr-x | hooks/busybox/extract00.sh | 14 | ||||
-rwxr-xr-x | hooks/busybox/setup00.sh | 17 | ||||
-rwxr-xr-x | hooks/copy-host-apt-sources-and-preferences/customize00.pl | 44 | ||||
-rwxr-xr-x | hooks/copy-host-apt-sources-and-preferences/setup00.sh | 46 | ||||
-rw-r--r-- | hooks/eatmydata/README.txt | 5 | ||||
-rwxr-xr-x | hooks/eatmydata/customize.sh | 30 | ||||
-rwxr-xr-x | hooks/eatmydata/extract.sh | 75 | ||||
-rwxr-xr-x | hooks/file-mirror-automount/customize00.sh | 41 | ||||
-rwxr-xr-x | hooks/file-mirror-automount/setup00.sh | 73 | ||||
-rwxr-xr-x | hooks/jessie-or-older/extract00.sh | 16 | ||||
-rwxr-xr-x | hooks/jessie-or-older/extract01.sh | 47 | ||||
-rwxr-xr-x | hooks/maybe-jessie-or-older/extract00.sh | 37 | ||||
-rwxr-xr-x | hooks/maybe-jessie-or-older/extract01.sh | 57 | ||||
-rwxr-xr-x | hooks/maybe-merged-usr/essential00.sh | 36 | ||||
-rwxr-xr-x | hooks/maybe-merged-usr/extract00.sh | 27 | ||||
-rwxr-xr-x | hooks/maybe-merged-usr/setup00.sh | 27 | ||||
-rwxr-xr-x | hooks/merged-usr/essential00.sh | 28 | ||||
-rwxr-xr-x | hooks/merged-usr/extract00.sh | 85 | ||||
-rwxr-xr-x | hooks/merged-usr/setup00.sh | 79 | ||||
l--------- | hooks/no-merged-usr/essential00.sh | 1 | ||||
-rwxr-xr-x | hooks/no-merged-usr/setup00.sh | 54 |
21 files changed, 839 insertions, 0 deletions
diff --git a/hooks/busybox/extract00.sh b/hooks/busybox/extract00.sh new file mode 100755 index 0000000..7d9b6ec --- /dev/null +++ b/hooks/busybox/extract00.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +set -eu + +if [ "${MMDEBSTRAP_VERBOSITY:-1}" -ge 3 ]; then + set -x +fi + +rootdir="$1" + +# Run busybox using an absolute path so that this script also works in case +# /proc is not mounted. Busybox uses /proc/self/exe to figure out the path +# to its executable. +chroot "$rootdir" /bin/busybox --install -s diff --git a/hooks/busybox/setup00.sh b/hooks/busybox/setup00.sh new file mode 100755 index 0000000..fc65e12 --- /dev/null +++ b/hooks/busybox/setup00.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +set -eu + +if [ "${MMDEBSTRAP_VERBOSITY:-1}" -ge 3 ]; then + set -x +fi + +rootdir="$1" + +mkdir -p "$rootdir/bin" +echo root:x:0:0:root:/root:/bin/sh > "$rootdir/etc/passwd" +cat << END > "$rootdir/etc/group" +root:x:0: +mail:x:8: +utmp:x:43: +END diff --git a/hooks/copy-host-apt-sources-and-preferences/customize00.pl b/hooks/copy-host-apt-sources-and-preferences/customize00.pl new file mode 100755 index 0000000..53f6059 --- /dev/null +++ b/hooks/copy-host-apt-sources-and-preferences/customize00.pl @@ -0,0 +1,44 @@ +#!/usr/bin/perl +# +# This script makes sure that all packages that are installed both locally as +# well as inside the chroot have the same version. +# +# It is implemented in Perl because there are no associative arrays in POSIX +# shell. + +use strict; +use warnings; + +sub get_pkgs { + my $root = shift; + my %pkgs = (); + open(my $fh, '-|', 'dpkg-query', "--root=$root", '--showformat', + '${binary:Package}=${Version}\n', '--show') + // die "cannot exec dpkg-query"; + while (my $line = <$fh>) { + my ($pkg, $ver) = split(/=/, $line, 2); + $pkgs{$pkg} = $ver; + } + close $fh; + if ($? != 0) { die "failed to run dpkg-query" } + return %pkgs; +} + +my %pkgs_local = get_pkgs('/'); +my %pkgs_chroot = get_pkgs($ARGV[0]); + +my @diff = (); +foreach my $pkg (keys %pkgs_chroot) { + next unless exists $pkgs_local{$pkg}; + if ($pkgs_local{$pkg} ne $pkgs_chroot{$pkg}) { + push @diff, $pkg; + } +} + +if (scalar @diff > 0) { + print STDERR "E: packages from the host and the chroot differ:\n"; + foreach my $pkg (@diff) { + print STDERR "E: $pkg $pkgs_local{$pkg} $pkgs_chroot{$pkg}\n"; + } + exit 1; +} diff --git a/hooks/copy-host-apt-sources-and-preferences/setup00.sh b/hooks/copy-host-apt-sources-and-preferences/setup00.sh new file mode 100755 index 0000000..07caa78 --- /dev/null +++ b/hooks/copy-host-apt-sources-and-preferences/setup00.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +set -eu + +if [ "${MMDEBSTRAP_VERBOSITY:-1}" -ge 3 ]; then + set -x +fi + +if [ -n "${MMDEBSTRAP_SUITE:-}" ]; then + if [ "${MMDEBSTRAP_VERBOSITY:-1}" -ge 1 ]; then + echo "W: using a non-empty suite name $MMDEBSTRAP_SUITE does not make sense with this hook and might select the wrong Essential:yes package set" >&2 + fi +fi + +rootdir="$1" + +SOURCELIST="/etc/apt/sources.list" +eval "$(apt-config shell SOURCELIST Dir::Etc::SourceList/f)" +SOURCEPARTS="/etc/apt/sources.d/" +eval "$(apt-config shell SOURCEPARTS Dir::Etc::SourceParts/d)" +PREFERENCES="/etc/apt/preferences" +eval "$(apt-config shell PREFERENCES Dir::Etc::Preferences/f)" +PREFERENCESPARTS="/etc/apt/preferences.d/" +eval "$(apt-config shell PREFERENCESPARTS Dir::Etc::PreferencesParts/d)" + +for f in "$SOURCELIST" \ + "$SOURCEPARTS"/*.list \ + "$SOURCEPARTS"/*.sources \ + "$PREFERENCES" \ + "$PREFERENCESPARTS"/*; do + [ -e "$f" ] || continue + mkdir --parents "$(dirname "$rootdir/$f")" + if [ -e "$rootdir/$f" ]; then + if [ "${MMDEBSTRAP_VERBOSITY:-1}" -ge 2 ]; then + echo "I: $f already exists in chroot, appending..." >&2 + fi + # Add extra newline between old content and new content. + # This is required in case of deb822 files. + echo >> "$rootdir/$f" + fi + cat "$f" >> "$rootdir/$f" + if [ "${MMDEBSTRAP_VERBOSITY:-1}" -ge 3 ]; then + echo "D: contents of $f inside the chroot:" >&2 + cat "$rootdir/$f" >&2 + fi +done diff --git a/hooks/eatmydata/README.txt b/hooks/eatmydata/README.txt new file mode 100644 index 0000000..84659e9 --- /dev/null +++ b/hooks/eatmydata/README.txt @@ -0,0 +1,5 @@ +Adding this directory with --hook-directory will result in mmdebstrap using +dpkg inside an eatmydata wrapper script. This will result in spead-ups on +systems where sync() takes some time. Using --dpkgopt=force-unsafe-io will have +a lesser effect compared to eatmydata. See: +https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=613428 diff --git a/hooks/eatmydata/customize.sh b/hooks/eatmydata/customize.sh new file mode 100755 index 0000000..c675848 --- /dev/null +++ b/hooks/eatmydata/customize.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +set -eu + +if [ "${MMDEBSTRAP_VERBOSITY:-1}" -ge 3 ]; then + set -x +fi + +rootdir="$1" + +if [ -e "$rootdir/var/lib/dpkg/arch" ]; then + chrootarch=$(head -1 "$rootdir/var/lib/dpkg/arch") +else + chrootarch=$(dpkg --print-architecture) +fi +libdir="/usr/lib/$(dpkg-architecture -a "$chrootarch" -q DEB_HOST_MULTIARCH)" + +# if eatmydata was actually installed properly, then we are not removing +# anything here +if ! chroot "$rootdir" dpkg-query --show eatmydata; then + rm "$rootdir/usr/bin/eatmydata" +fi +if ! chroot "$rootdir" dpkg-query --show libeatmydata1; then + rm "$rootdir$libdir"/libeatmydata.so* +fi + +rm "$rootdir/usr/bin/dpkg" +chroot "$rootdir" dpkg-divert --local --rename --remove /usr/bin/dpkg + +sync diff --git a/hooks/eatmydata/extract.sh b/hooks/eatmydata/extract.sh new file mode 100755 index 0000000..7ffe8e5 --- /dev/null +++ b/hooks/eatmydata/extract.sh @@ -0,0 +1,75 @@ +#!/bin/sh + +set -eu + +if [ "${MMDEBSTRAP_VERBOSITY:-1}" -ge 3 ]; then + set -x +fi + +rootdir="$1" + +if [ -e "$rootdir/var/lib/dpkg/arch" ]; then + chrootarch=$(head -1 "$rootdir/var/lib/dpkg/arch") +else + chrootarch=$(dpkg --print-architecture) +fi + +trusted= +eval "$(apt-config shell trusted Dir::Etc::trusted/f)" +trustedparts= +eval "$(apt-config shell trustedparts Dir::Etc::trustedparts/d)" +tmpfile=$(mktemp --tmpdir="$rootdir/tmp") +cat << END > "$tmpfile" +Apt::Architecture "$chrootarch"; +Apt::Architectures "$chrootarch"; +Dir "$rootdir"; +Dir::Etc::Trusted "$trusted"; +Dir::Etc::TrustedParts "$trustedparts"; +END +# we run "apt-get download --print-uris" in a temporary directory, to make sure +# that the packages do not already exist in the current directory, or otherwise +# nothing will be printed for them +tmpdir=$(mktemp --directory --tmpdir="$rootdir/tmp") +env --chdir="$tmpdir" APT_CONFIG="$tmpfile" apt-get download --print-uris eatmydata libeatmydata1 \ + | sed -ne "s/^'\([^']\+\)'\s\+\(\S\+\)\s\+\([0-9]\+\)\s\+\(SHA256:[a-f0-9]\+\)$/\1 \2 \3 \4/p" \ + | while read -r uri fname size hash; do + echo "processing $fname" >&2 + if [ -e "$tmpdir/$fname" ]; then + echo "$tmpdir/$fname already exists" >&2 + exit 1 + fi + [ -z "$hash" ] && hash="Checksum-FileSize:$size" + env --chdir="$tmpdir" APT_CONFIG="$tmpfile" /usr/lib/apt/apt-helper download-file "$uri" "$fname" "$hash" + case "$fname" in + eatmydata_*_all.deb) + mkdir -p "$rootdir/usr/bin" + dpkg-deb --fsys-tarfile "$tmpdir/$fname" \ + | tar --directory="$rootdir/usr/bin" --strip-components=3 --extract --verbose ./usr/bin/eatmydata + ;; + libeatmydata1_*_$chrootarch.deb) + libdir="/usr/lib/$(dpkg-architecture -a "$chrootarch" -q DEB_HOST_MULTIARCH)" + mkdir -p "$rootdir$libdir" + dpkg-deb --fsys-tarfile "$tmpdir/$fname" \ + | tar --directory="$rootdir$libdir" --strip-components=4 --extract --verbose --wildcards ".$libdir/libeatmydata.so*" + ;; + *) + echo "unexpected filename: $fname" >&2 + exit 1 + ;; + esac + rm "$tmpdir/$fname" +done +rm "$tmpfile" +rmdir "$tmpdir" + +mv "$rootdir/usr/bin/dpkg" "$rootdir/usr/bin/dpkg.distrib" +cat << END > "$rootdir/usr/bin/dpkg" +#!/bin/sh +exec /usr/bin/eatmydata /usr/bin/dpkg.distrib "\$@" +END +chmod +x "$rootdir/usr/bin/dpkg" +cat << END >> "$rootdir/var/lib/dpkg/diversions" +/usr/bin/dpkg +/usr/bin/dpkg.distrib +: +END diff --git a/hooks/file-mirror-automount/customize00.sh b/hooks/file-mirror-automount/customize00.sh new file mode 100755 index 0000000..b6b9b46 --- /dev/null +++ b/hooks/file-mirror-automount/customize00.sh @@ -0,0 +1,41 @@ +#!/bin/sh +# +# shellcheck disable=SC2086 + +set -eu + +if [ "${MMDEBSTRAP_VERBOSITY:-1}" -ge 3 ]; then + set -x +fi + +rootdir="$1" + +if [ ! -e "$rootdir/run/mmdebstrap/file-mirror-automount" ]; then + exit 0 +fi + +xargsopts="--null --no-run-if-empty -I {} --max-args=1" + +case $MMDEBSTRAP_MODE in + root|unshare) + echo "unmounting the following mountpoints:" >&2 ;; + *) + echo "removing the following directories:" >&2 ;; +esac + +< "$rootdir/run/mmdebstrap/file-mirror-automount" \ + xargs $xargsopts echo " $rootdir/{}" + +case $MMDEBSTRAP_MODE in + root|unshare) + < "$rootdir/run/mmdebstrap/file-mirror-automount" \ + xargs $xargsopts umount "$rootdir/{}" + ;; + *) + < "$rootdir/run/mmdebstrap/file-mirror-automount" \ + xargs $xargsopts rm -r "$rootdir/{}" + ;; +esac + +rm "$rootdir/run/mmdebstrap/file-mirror-automount" +rmdir --ignore-fail-on-non-empty "$rootdir/run/mmdebstrap" diff --git a/hooks/file-mirror-automount/setup00.sh b/hooks/file-mirror-automount/setup00.sh new file mode 100755 index 0000000..61f60f2 --- /dev/null +++ b/hooks/file-mirror-automount/setup00.sh @@ -0,0 +1,73 @@ +#!/bin/sh + +set -eu + +if [ "${MMDEBSTRAP_VERBOSITY:-1}" -ge 3 ]; then + set -x +fi + +rootdir="$1" + +# process all configured apt repositories +env APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-get indextargets --no-release-info --format '$(REPO_URI)' \ + | sed -ne 's/^file:\/\+//p' \ + | sort -u \ + | while read -r path; do + mkdir -p "$rootdir/run/mmdebstrap" + if [ ! -d "/$path" ]; then + echo "/$path is not an existing directory" >&2 + continue + fi + case $MMDEBSTRAP_MODE in + root|unshare) + echo "bind-mounting /$path into the chroot" >&2 + mkdir -p "$rootdir/$path" + mount -o ro,bind "/$path" "$rootdir/$path" + ;; + *) + echo "copying /$path into the chroot" >&2 + mkdir -p "$rootdir/$path" + "$MMDEBSTRAP_ARGV0" --hook-helper "$rootdir" "$MMDEBSTRAP_MODE" "$MMDEBSTRAP_HOOK" env "$MMDEBSTRAP_VERBOSITY" sync-in "/$path" "/$path" <&"$MMDEBSTRAP_HOOKSOCK" >&"$MMDEBSTRAP_HOOKSOCK" + ;; + esac + printf '/%s\0' "$path" >> "$rootdir/run/mmdebstrap/file-mirror-automount" + done + +# process all files given via --include +set -f # turn off pathname expansion +IFS=',' # split by comma +for pkg in $MMDEBSTRAP_INCLUDE; do + set +f; unset IFS + case $pkg in + ./*|../*|/*) : ;; # we are interested in this case + *) continue ;; # not a file + esac + # undo escaping + pkg="$(printf '%s' "$pkg" | sed 's/%2C/,/g; s/%25/%/g')" + # check for existance + if [ ! -f "$pkg" ]; then + echo "$pkg does not exist" >&2 + continue + fi + # make path absolute + pkg="$(realpath "$pkg")" + case "$pkg" in + /*) : ;; + *) echo "path for $pkg is not absolute" >&2; continue;; + esac + mkdir -p "$rootdir/run/mmdebstrap" + mkdir -p "$rootdir/$(dirname "$pkg")" + case $MMDEBSTRAP_MODE in + root|unshare) + echo "bind-mounting $pkg into the chroot" >&2 + touch "$rootdir/$pkg" + mount -o bind "$pkg" "$rootdir/$pkg" + ;; + *) + echo "copying $pkg into the chroot" >&2 + "$MMDEBSTRAP_ARGV0" --hook-helper "$rootdir" "$MMDEBSTRAP_MODE" "$MMDEBSTRAP_HOOK" env "$MMDEBSTRAP_VERBOSITY" upload "$pkg" "$pkg" <&"$MMDEBSTRAP_HOOKSOCK" >&"$MMDEBSTRAP_HOOKSOCK" + ;; + esac + printf '/%s\0' "$pkg" >> "$rootdir/run/mmdebstrap/file-mirror-automount" +done +set +f; unset IFS diff --git a/hooks/jessie-or-older/extract00.sh b/hooks/jessie-or-older/extract00.sh new file mode 100755 index 0000000..f327052 --- /dev/null +++ b/hooks/jessie-or-older/extract00.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +set -eu + +if [ "${MMDEBSTRAP_VERBOSITY:-1}" -ge 3 ]; then + set -x +fi + +TARGET="$1" + +# not needed since dpkg 1.17.11 +for f in available diversions cmethopt; do + if [ ! -e "$TARGET/var/lib/dpkg/$f" ]; then + touch "$TARGET/var/lib/dpkg/$f" + fi +done diff --git a/hooks/jessie-or-older/extract01.sh b/hooks/jessie-or-older/extract01.sh new file mode 100755 index 0000000..43f1540 --- /dev/null +++ b/hooks/jessie-or-older/extract01.sh @@ -0,0 +1,47 @@ +#!/bin/sh +# +# needed until init 1.33 which pre-depends on systemd-sysv +# starting with init 1.34, init is not Essential:yes anymore +# +# jessie has init 1.22 + +set -eu + +if [ "${MMDEBSTRAP_VERBOSITY:-1}" -ge 3 ]; then + set -x +fi + +TARGET="$1" + +if [ -z "${MMDEBSTRAP_ESSENTIAL+x}" ]; then + MMDEBSTRAP_ESSENTIAL= + for f in "$TARGET/var/cache/apt/archives/"*.deb; do + [ -f "$f" ] || continue + f="${f#"$TARGET"}" + MMDEBSTRAP_ESSENTIAL="$MMDEBSTRAP_ESSENTIAL $f" + done +fi + +fname_base_passwd= +fname_base_files= +fname_dpkg= +for pkg in $MMDEBSTRAP_ESSENTIAL; do + pkgname=$(dpkg-deb --show --showformat='${Package}' "$TARGET/$pkg") + # shellcheck disable=SC2034 + case $pkgname in + base-passwd) fname_base_passwd=$pkg;; + base-files) fname_base_files=$pkg;; + dpkg) fname_dpkg=$pkg;; + esac +done + +for var in base_passwd base_files dpkg; do + eval 'val=$fname_'"$var" + [ -z "$val" ] && continue + chroot "$TARGET" dpkg --install --force-depends "$val" +done + +# shellcheck disable=SC2086 +chroot "$TARGET" dpkg --unpack --force-depends $MMDEBSTRAP_ESSENTIAL + +chroot "$TARGET" dpkg --configure --pending diff --git a/hooks/maybe-jessie-or-older/extract00.sh b/hooks/maybe-jessie-or-older/extract00.sh new file mode 100755 index 0000000..9a82fbf --- /dev/null +++ b/hooks/maybe-jessie-or-older/extract00.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +set -eu + +# we need to check the version of dpkg +# since at this point packages are just extracted but not installed, we cannot use dpkg-query +# since we want to support chrootless, we cannot run dpkg --version inside the chroot +# to avoid this hook depending on dpkg-dev being installed, we do not parse the extracted changelog with dpkg-parsechangelog +# we also want to avoid parsing the changelog because /usr/share/doc might've been added to dpkg --path-exclude +# instead, we just ask apt about the latest version of dpkg it knows of +# this should only fail in situations where there are multiple versions of dpkg in different suites +ver=$(env --chdir="$1" APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-cache show --no-all-versions dpkg 2>/dev/null | sed -ne 's/^Version: \(.*\)$/\1/p' || printf '') +if [ -z "$ver" ]; then + echo "no package called dpkg can be installed -- not running jessie-or-older extract00 hook" >&2 + exit 0 +fi + +if dpkg --compare-versions "$ver" ge 1.17.11; then + echo "dpkg version $ver is >= 1.17.11 -- not running jessie-or-older extract00 hook" >&2 + exit 0 +else + echo "dpkg version $ver is << 1.17.11 -- running jessie-or-older extract00 hook" >&2 +fi + +# resolve the script path using several methods in order: +# 1. using dirname -- "$0" +# 2. using ./hooks +# 3. using /usr/share/mmdebstrap/hooks/ +for p in "$(dirname -- "$0")/.." ./hooks /usr/share/mmdebstrap/hooks; do + if [ -x "$p/jessie-or-older/extract00.sh" ] && [ -x "$p/jessie-or-older/extract01.sh" ]; then + "$p/jessie-or-older/extract00.sh" "$1" + exit 0 + fi +done + +echo "cannot find jessie-or-older hook anywhere" >&2 +exit 1 diff --git a/hooks/maybe-jessie-or-older/extract01.sh b/hooks/maybe-jessie-or-older/extract01.sh new file mode 100755 index 0000000..9a92d6d --- /dev/null +++ b/hooks/maybe-jessie-or-older/extract01.sh @@ -0,0 +1,57 @@ +#!/bin/sh + +set -eu + +# The jessie-or-older extract01 hook has to be run up to the point where the +# Essential:yes field was removed from the init package (with +# init-system-helpers 1.34). Since the essential packages have only been +# extracted but not installed, we cannot use dpkg-query to find out its +# version. Since /usr/share/doc might be missing due to dpkg --path-exclude, we +# also cannot check whether /usr/share/doc/init/copyright exists. There also +# was a time (before init-system-helpers 1.20) where there was no init package +# at all where we also want to apply this hook. So we just ask apt about the +# candidate version for init-system-helpers. This should only fail in +# situations where there are multiple versions of init-system-helpers in +# different suites. +ver=$(env --chdir="$1" APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-cache show --no-all-versions init-system-helpers 2>/dev/null | sed -ne 's/^Version: \(.*\)$/\1/p' || printf '') +if [ -z "$ver" ]; then + # there is no package called init-system-helpers, so either: + # - this is so old that init-system-helpers didn't exist yet + # - we are in a future where init-system-helpers doesn't exist anymore + # - something strange is going on + # we should only call the hook in the first case + ver=$(env --chdir="$1" APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-cache show --no-all-versions base-files 2>/dev/null | sed -ne 's/^Version: \(.*\)$/\1/p' || printf '') + if [ -z "$ver" ]; then + echo "neither init-system-helpers nor base-files can be installed -- not running jessie-or-older extract01 hook" >&2 + exit 0 + fi + + # Jessie is Debian 8 + if dpkg --compare-versions "$ver" ge 8; then + echo "there is no init-system-helpers but base-files version $ver is >= 8 -- not running jessie-or-older extract01 hook" >&2 + exit 0 + else + echo "there is no init-system-helpers but base-files version $ver is << 8 -- running jessie-or-older extract01 hook" >&2 + fi +else + if dpkg --compare-versions "$ver" ge 1.34; then + echo "init-system-helpers version $ver is >= 1.34 -- not running jessie-or-older extract01 hook" >&2 + exit 0 + else + echo "init-system-helpers version $ver is << 1.34 -- running jessie-or-older extract01 hook" >&2 + fi +fi + +# resolve the script path using several methods in order: +# 1. using dirname -- "$0" +# 2. using ./hooks +# 3. using /usr/share/mmdebstrap/hooks/ +for p in "$(dirname -- "$0")/.." ./hooks /usr/share/mmdebstrap/hooks; do + if [ -x "$p/jessie-or-older/extract00.sh" ] && [ -x "$p/jessie-or-older/extract01.sh" ]; then + "$p/jessie-or-older/extract01.sh" "$1" + exit 0 + fi +done + +echo "cannot find jessie-or-older hook anywhere" >&2 +exit 1 diff --git a/hooks/maybe-merged-usr/essential00.sh b/hooks/maybe-merged-usr/essential00.sh new file mode 100755 index 0000000..a23f2f7 --- /dev/null +++ b/hooks/maybe-merged-usr/essential00.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +set -eu + +ver=$(dpkg-query --root="$1" -f '${db:Status-Status} ${Source} ${Version}' --show usr-is-merged 2>/dev/null || printf '') +case "$ver" in + '') + echo "no package called usr-is-merged is installed -- not running merged-usr essential hook" >&2 + exit 0 + ;; + 'installed mmdebstrap-dummy-usr-is-merged 1') + echo "dummy usr-is-merged package installed -- running merged-usr essential hook" >&2 + ;; + 'installed usrmerge '*) + echo "usr-is-merged package from src:usrmerge installed -- not running merged-usr essential hook" >&2 + exit 0 + ;; + *) + echo "unexpected situation for package usr-is-merged: $ver" >&2 + exit 1 + ;; +esac + +# resolve the script path using several methods in order: +# 1. using dirname -- "$0" +# 2. using ./hooks +# 3. using /usr/share/mmdebstrap/hooks/ +for p in "$(dirname -- "$0")/.." ./hooks /usr/share/mmdebstrap/hooks; do + if [ -x "$p/merged-usr/setup00.sh" ] && [ -x "$p/merged-usr/extract00.sh" ] && [ -x "$p/merged-usr/essential00.sh" ]; then + "$p/merged-usr/essential00.sh" "$1" + exit 0 + fi +done + +echo "cannot find merged-usr hook anywhere" >&2 +exit 1 diff --git a/hooks/maybe-merged-usr/extract00.sh b/hooks/maybe-merged-usr/extract00.sh new file mode 100755 index 0000000..dc88450 --- /dev/null +++ b/hooks/maybe-merged-usr/extract00.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +set -eu + +env --chdir="$1" APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-get update --error-on=any + +# if the usr-is-merged package cannot be installed with apt, do nothing +if ! env --chdir="$1" APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-cache show --no-all-versions usr-is-merged > /dev/null 2>&1; then + echo "no package called usr-is-merged found -- not running merged-usr extract hook" >&2 + exit 0 +else + echo "package usr-is-merged found -- running merged-usr extract hook" >&2 +fi + +# resolve the script path using several methods in order: +# 1. using dirname -- "$0" +# 2. using ./hooks +# 3. using /usr/share/mmdebstrap/hooks/ +for p in "$(dirname -- "$0")/.." ./hooks /usr/share/mmdebstrap/hooks; do + if [ -x "$p/merged-usr/setup00.sh" ] && [ -x "$p/merged-usr/extract00.sh" ] && [ -x "$p/merged-usr/essential00.sh" ]; then + "$p/merged-usr/extract00.sh" "$1" + exit 0 + fi +done + +echo "cannot find merged-usr hook anywhere" >&2 +exit 1 diff --git a/hooks/maybe-merged-usr/setup00.sh b/hooks/maybe-merged-usr/setup00.sh new file mode 100755 index 0000000..a6bd712 --- /dev/null +++ b/hooks/maybe-merged-usr/setup00.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +set -eu + +env --chdir="$1" APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-get update --error-on=any + +# if the usr-is-merged package cannot be installed with apt, do nothing +if ! env --chdir="$1" APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-cache show --no-all-versions usr-is-merged > /dev/null 2>&1; then + echo "no package called usr-is-merged found -- not running merged-usr setup hook" >&2 + exit 0 +else + echo "package usr-is-merged found -- running merged-usr setup hook" >&2 +fi + +# resolve the script path using several methods in order: +# 1. using dirname -- "$0" +# 2. using ./hooks +# 3. using /usr/share/mmdebstrap/hooks/ +for p in "$(dirname -- "$0")/.." ./hooks /usr/share/mmdebstrap/hooks; do + if [ -x "$p/merged-usr/setup00.sh" ] && [ -x "$p/merged-usr/extract00.sh" ] && [ -x "$p/merged-usr/essential00.sh" ]; then + "$p/merged-usr/setup00.sh" "$1" + exit 0 + fi +done + +echo "cannot find merged-usr hook anywhere" >&2 +exit 1 diff --git a/hooks/merged-usr/essential00.sh b/hooks/merged-usr/essential00.sh new file mode 100755 index 0000000..d9a8130 --- /dev/null +++ b/hooks/merged-usr/essential00.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +set -eu + +if [ "${MMDEBSTRAP_VERBOSITY:-1}" -ge 3 ]; then + set -x +fi + +TARGET="$1" + +if [ "${MMDEBSTRAP_MODE:-}" = "chrootless" ]; then + APT_CONFIG=$MMDEBSTRAP_APT_CONFIG apt-get --yes install \ + -oDPkg::Chroot-Directory= \ + -oDPkg::Options::=--force-not-root \ + -oDPkg::Options::=--force-script-chrootless \ + -oDPkg::Options::=--root="$TARGET" \ + -oDPkg::Options::=--log="$TARGET/var/log/dpkg.log" \ + usr-is-merged + export DPKG_ROOT="$TARGET" + dpkg-query --showformat '${db:Status-Status}\n' --show usr-is-merged | grep -q '^installed$' + dpkg-query --showformat '${Source}\n' --show usr-is-merged | grep -q '^usrmerge$' + dpkg --compare-versions "1" "lt" "$(dpkg-query --showformat '${Version}\n' --show usr-is-merged)" +else + APT_CONFIG=$MMDEBSTRAP_APT_CONFIG apt-get --yes install usr-is-merged + chroot "$TARGET" dpkg-query --showformat '${db:Status-Status}\n' --show usr-is-merged | grep -q '^installed$' + chroot "$TARGET" dpkg-query --showformat '${Source}\n' --show usr-is-merged | grep -q '^usrmerge$' + dpkg --compare-versions "1" "lt" "$(chroot "$TARGET" dpkg-query --showformat '${Version}\n' --show usr-is-merged)" +fi diff --git a/hooks/merged-usr/extract00.sh b/hooks/merged-usr/extract00.sh new file mode 100755 index 0000000..7334191 --- /dev/null +++ b/hooks/merged-usr/extract00.sh @@ -0,0 +1,85 @@ +#!/bin/sh + +set -eu + +if [ "${MMDEBSTRAP_VERBOSITY:-1}" -ge 3 ]; then + set -x +fi + +TARGET="$1" + +# can_usrmerge_symlink() and can_usrmerge_symlink() are +# Copyright 2023 Helmut Grohne <helmut@subdivi.de> +# and part of the debootstrap source in /usr/share/debootstrap/functions +# https://salsa.debian.org/installer-team/debootstrap/-/merge_requests/96 +# https://bugs.debian.org/104989 +can_usrmerge_symlink() { + # Absolute symlinks can be relocated without problems. + test "${2#/}" = "$2" || return 0 + while :; do + if test "${2#/}" != "$2"; then + # Handle double-slashes. + set -- "$1" "${2#/}" + elif test "${2#./}" != "$2"; then + # Handle ./ inside a link target. + set -- "$1" "${2#./}" + elif test "$2" = ..; then + # A parent directory symlink is ok if it does not + # cross the top level directory. + test "${1%/*/*}" != "$1" -a -n "${1%/*/*}" + return $? + elif test "${2#../}" != "$2"; then + # Symbolic link crossing / cannot be moved safely. + # This is prohibited by Debian Policy 10.5. + test "${1%/*/*}" = "$1" -o -z "${1%/*/*}" && return 1 + set -- "${1%/*}" "${2#../}" + else + # Consider the symlink ok if its target does not + # contain a parent directory. When we fail here, + # the link target is non-minimal and doesn't happen + # in the archive. + test "${2#*/../}" = "$2" + return $? + fi + done +} + +merge_usr_entry() { + # shellcheck disable=SC3043 + local entry canon + canon="$TARGET/usr/${1#"$TARGET/"}" + test -h "$canon" && + error 1 USRMERGEFAIL "cannot move %s as its destination exists as a symlink" "${1#"$TARGET"}" + if ! test -e "$canon"; then + mv "$1" "$canon" + return 0 + fi + test -d "$1" || + error 1 USRMERGEFAIL "cannot move non-directory %s as its destination exists" "${1#"$TARGET"}" + test -d "$canon" || + error 1 USRMERGEFAIL "cannot move directory %s as its destination is not a directory" "${1#"$TARGET"}" + for entry in "$1/"* "$1/."*; do + # Some shells return . and .. on dot globs. + test "${entry%/.}" != "${entry%/..}" && continue + if test -h "$entry" && ! can_usrmerge_symlink "${entry#"$TARGET"}" "$(readlink "$entry")"; then + error 1 USRMERGEFAIL "cannot move relative symlink crossing top-level directory" "${entry#"$TARGET"}" + fi + # Ignore glob match failures + if test "${entry%'/*'}" != "${entry%'/.*'}" && ! test -e "$entry"; then + continue + fi + merge_usr_entry "$entry" + done + rmdir "$1" +} + +# This is list includes all possible multilib directories. It must be +# updated when new multilib directories are being added. Hopefully, +# all new architectures use multiarch instead, so we never get to +# update this. +for dir in bin lib lib32 lib64 libo32 libx32 sbin; do + test -h "$TARGET/$dir" && continue + test -e "$TARGET/$dir" || continue + merge_usr_entry "$TARGET/$dir" + ln -s "usr/$dir" "$TARGET/$dir" +done diff --git a/hooks/merged-usr/setup00.sh b/hooks/merged-usr/setup00.sh new file mode 100755 index 0000000..a6b08d2 --- /dev/null +++ b/hooks/merged-usr/setup00.sh @@ -0,0 +1,79 @@ +#!/bin/sh +# +# mmdebstrap does have a --merged-usr option but only as a no-op for +# debootstrap compatibility +# +# Using this hook script, you can emulate what debootstrap does to set up +# merged /usr via directory symlinks, even using the exact same shell function +# that debootstrap uses by running mmdebstrap with: +# +# --setup-hook=/usr/share/mmdebstrap/hooks/merged-usr/setup00.sh +# +# Alternatively, you can setup merged-/usr by installing the usrmerge package: +# +# --include=usrmerge +# +# mmdebstrap will not include this functionality via a --merged-usr option +# because there are many reasons against implementing merged-/usr that way: +# +# https://wiki.debian.org/Teams/Dpkg/MergedUsr +# https://wiki.debian.org/Teams/Dpkg/FAQ#Q:_Does_dpkg_support_merged-.2Fusr-via-aliased-dirs.3F +# https://lists.debian.org/20190219044924.GB21901@gaara.hadrons.org +# https://lists.debian.org/YAkLOMIocggdprSQ@thunder.hadrons.org +# https://lists.debian.org/20181223030614.GA8788@gaara.hadrons.org +# +# In addition, the merged-/usr-via-aliased-dirs approach violates an important +# principle of component based software engineering one of the core design +# ideas/goals of mmdebstrap: All the information to create a chroot of a Debian +# based distribution should be included in its packages and their metadata. +# Using directory symlinks as used by debootstrap contradicts this principle. +# The information whether a distribution uses this approach to merged-/usr or +# not is not anymore contained in its packages but in a tool from the outside. +# +# Example real world problem: I'm using debbisect to bisect Debian unstable +# between 2015 and today. For which snapshot.d.o timestamp should a merged-/usr +# chroot be created and for which ones not? +# +# The problem is not the idea of merged-/usr but the problem is the way how it +# got implemented in debootstrap via directory symlinks. That way of rolling +# out merged-/usr is bad from the dpkg point-of-view and completely opposite of +# the vision with which in mind I wrote mmdebstrap. + +set -eu + +if [ "${MMDEBSTRAP_VERBOSITY:-1}" -ge 3 ]; then + set -x +fi + +TARGET="$1" + +# now install an empty "usr-is-merged" package to avoid installing the +# usrmerge package on this system even after init-system-helpers starts +# depending on "usrmerge | usr-is-merged". +# +# This package will not end up in the final chroot because the essential +# hook replaces it with the actual usr-is-merged package from src:usrmerge. + +tmpdir=$(mktemp --directory --tmpdir="$TARGET/tmp") +mkdir -p "$tmpdir/usr-is-merged/DEBIAN" + +cat << END > "$tmpdir/usr-is-merged/DEBIAN/control" +Package: usr-is-merged +Priority: optional +Section: oldlibs +Maintainer: Johannes Schauer Marin Rodrigues <josch@debian.org> +Architecture: all +Multi-Arch: foreign +Source: mmdebstrap-dummy-usr-is-merged +Version: 1 +Description: dummy package created by mmdebstrap merged-usr setup hook + This package was generated and installed by the mmdebstrap merged-usr + setup hook at /usr/share/mmdebstrap/hooks/merged-usr. + . + If this package is installed in the final chroot, then this is a bug + in mmdebstrap. Please report: https://gitlab.mister-muffin.de/josch/mmdebstrap +END +dpkg-deb --build "$tmpdir/usr-is-merged" "$tmpdir/usr-is-merged.deb" +dpkg --root="$TARGET" --log="$TARGET/var/log/dpkg.log" --install "$tmpdir/usr-is-merged.deb" +rm "$tmpdir/usr-is-merged.deb" "$tmpdir/usr-is-merged/DEBIAN/control" +rmdir "$tmpdir/usr-is-merged/DEBIAN" "$tmpdir/usr-is-merged" "$tmpdir" diff --git a/hooks/no-merged-usr/essential00.sh b/hooks/no-merged-usr/essential00.sh new file mode 120000 index 0000000..5360c8f --- /dev/null +++ b/hooks/no-merged-usr/essential00.sh @@ -0,0 +1 @@ +../merged-usr/essential00.sh
\ No newline at end of file diff --git a/hooks/no-merged-usr/setup00.sh b/hooks/no-merged-usr/setup00.sh new file mode 100755 index 0000000..df50499 --- /dev/null +++ b/hooks/no-merged-usr/setup00.sh @@ -0,0 +1,54 @@ +#!/bin/sh +# +# mmdebstrap does have a --no-merged-usr option but only as a no-op for +# debootstrap compatibility +# +# Using this hook script, you can emulate what debootstrap does to set up +# a system without merged-/usr even after the essential init-system-helpers +# package added a dependency on "usrmerge | usr-is-merged". By installing +# a dummy usr-is-merged package, it avoids pulling in the dependencies of +# the usrmerge package. + +set -eu + +if [ "${MMDEBSTRAP_VERBOSITY:-1}" -ge 3 ]; then + set -x +fi + +TARGET="$1" + +echo "Warning: starting with Debian 12 (Bookworm), systems without merged-/usr are not supported anymore" >&2 +echo "Warning: starting with Debian 13 (Trixie), merged-/usr symlinks are shipped by packages in the essential-set making this hook ineffective" >&2 + +echo "this system will not be supported in the future" > "$TARGET/etc/unsupported-skip-usrmerge-conversion" + +# now install an empty "usr-is-merged" package to avoid installing the +# usrmerge package on this system even after init-system-helpers starts +# depending on "usrmerge | usr-is-merged". +# +# This package will not end up in the final chroot because the essential +# hook replaces it with the actual usr-is-merged package from src:usrmerge. + +tmpdir=$(mktemp --directory --tmpdir="$TARGET/tmp") +mkdir -p "$tmpdir/usr-is-merged/DEBIAN" + +cat << END > "$tmpdir/usr-is-merged/DEBIAN/control" +Package: usr-is-merged +Priority: optional +Section: oldlibs +Maintainer: Johannes Schauer Marin Rodrigues <josch@debian.org> +Architecture: all +Multi-Arch: foreign +Source: mmdebstrap-dummy-usr-is-merged +Version: 1 +Description: dummy package created by mmdebstrap no-merged-usr setup hook + This package was generated and installed by the mmdebstrap no-merged-usr + setup hook at /usr/share/mmdebstrap/hooks/no-merged-usr. + . + If this package is installed in the final chroot, then this is a bug + in mmdebstrap. Please report: https://gitlab.mister-muffin.de/josch/mmdebstrap +END +dpkg-deb --build "$tmpdir/usr-is-merged" "$tmpdir/usr-is-merged.deb" +dpkg --root="$TARGET" --log="$TARGET/var/log/dpkg.log" --install "$tmpdir/usr-is-merged.deb" +rm "$tmpdir/usr-is-merged.deb" "$tmpdir/usr-is-merged/DEBIAN/control" +rmdir "$tmpdir/usr-is-merged/DEBIAN" "$tmpdir/usr-is-merged" "$tmpdir" |