initial commit
This commit is contained in:
commit
9ed4c65e35
4 changed files with 1855 additions and 0 deletions
103
README.md
Normal file
103
README.md
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
mmdebstrap
|
||||||
|
==========
|
||||||
|
|
||||||
|
An alternative to debootstrap which uses apt internally and is thus able to use
|
||||||
|
more than one mirror and resolve more complex dependencies.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
Use like debootstrap:
|
||||||
|
|
||||||
|
sudo mmdebstrap unstable ./unstable-chroot
|
||||||
|
|
||||||
|
Without superuser privileges:
|
||||||
|
|
||||||
|
mmdebstrap unstable unstable-chroot.tar
|
||||||
|
|
||||||
|
With complex apt options:
|
||||||
|
|
||||||
|
cat /etc/apt/sources.list | mmdebstrap > unstable-chroot.tar
|
||||||
|
|
||||||
|
The sales pitch in comparison to debootstrap
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
Summary:
|
||||||
|
|
||||||
|
- more than one mirror possible
|
||||||
|
- security and updates mirror included for Debian stable chroots
|
||||||
|
- 2-3 times faster
|
||||||
|
- chroot with apt in 11 seconds
|
||||||
|
- gzipped tarball with apt is 27M small
|
||||||
|
- bit-by-bit reproducible output
|
||||||
|
- unprivileged operation using Linux user namespaces, fakechroot or proot
|
||||||
|
- can operate on filesystems mounted with nodev
|
||||||
|
- foreign architecture chroots with qemu-user
|
||||||
|
|
||||||
|
The author believes that a chroot of a Debian stable release should include the
|
||||||
|
latest packages including security fixes by default. This has been a wontfix
|
||||||
|
with debootstrap since 2009 (See #543819 and #762222). Since mmdebstrap uses
|
||||||
|
apt internally, support for multiple mirrors comes for free and stable or
|
||||||
|
oldstable **chroots will include security and updates mirrors**.
|
||||||
|
|
||||||
|
A side-effect of using apt is being **2-3 times faster** than debootstrap. The
|
||||||
|
timings were carried out on a laptop with an Intel Core i5-5200U.
|
||||||
|
|
||||||
|
| variant | mmdebstrap | debootstrap |
|
||||||
|
| ------- | ---------- | ------------ |
|
||||||
|
| minbase | 25.25 s | 51.47 s |
|
||||||
|
| buildd | 30.99 s | 59.38 s |
|
||||||
|
| - | 29.85 s | 127.18 s |
|
||||||
|
|
||||||
|
Apt considers itself an `Essential: yes` package. This feature allows one to
|
||||||
|
create a chroot containing just the `Essential: yes` packages and apt (and
|
||||||
|
their hard dependencies) in **just 11 seconds**.
|
||||||
|
|
||||||
|
If desired, a most minimal chroot with just the `Essential: yes` packages and
|
||||||
|
their hard dependencies can be created with a gzipped tarball size of just 34M.
|
||||||
|
By using dpkg's `--path-exclude` option to exclude documentation, even smaller
|
||||||
|
gzipped tarballs of 21M in size are possible. If apt is included, the result is
|
||||||
|
a **gzipped tarball of only 27M**.
|
||||||
|
|
||||||
|
These small sizes are also achieved because apt caches and other cruft is
|
||||||
|
stripped from the chroot. This also makes the result **bit-by-bit
|
||||||
|
reproducible** if the `$SOURCE_DATE_EPOCH` environment variable is set.
|
||||||
|
|
||||||
|
The author believes, that it should not be necessary to have superuser
|
||||||
|
privileges to create a file (the chroot tarball) in one's home directory. If
|
||||||
|
mmdebstrap is run by an unprivileged user, either Linux user namespaces,
|
||||||
|
fakechroot or proot are used to create a chroot tarball. Debootstrap supports
|
||||||
|
fakechroot but will not create a tarball with the right permissions by itself.
|
||||||
|
Support for Linux user namespaces and proot is missing (see bugs #829134 and
|
||||||
|
#698347, respectively).
|
||||||
|
|
||||||
|
When creating a chroot tarball with debootstrap, the temporary chroot directory
|
||||||
|
cannot be on a filesystem that has been mounted with nodev. In unprivileged
|
||||||
|
mode, mknod is never used, which means that /tmp can be used as a temporary
|
||||||
|
directory location even if if it's mounted with nodev as a security measure.
|
||||||
|
|
||||||
|
If the chroot architecture cannot be executed by the current machine, qemu-user
|
||||||
|
is used to allow one to create a foreign architecture chroot.
|
||||||
|
|
||||||
|
Limitations in comparison to debootstrap
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
Debootstrap supports creating a Debian chroot on non-Debian systems but
|
||||||
|
mmdebstrap requires apt.
|
||||||
|
|
||||||
|
There is no `SCRIPT` argument.
|
||||||
|
|
||||||
|
There is no `--second-stage` option.
|
||||||
|
|
||||||
|
Tests
|
||||||
|
=====
|
||||||
|
|
||||||
|
The script `test.sh` compares the output of mmdebstrap with debootstrap in
|
||||||
|
several scenarios. Since debootstrap needs superuser privileges, `test.sh`
|
||||||
|
needs `sudo` to run.
|
||||||
|
|
||||||
|
Bugs
|
||||||
|
====
|
||||||
|
|
||||||
|
mmdebstrap has bugs. Report them here:
|
||||||
|
https://gitlab.mister-muffin.de/josch/mmdebstrap/issues
|
99
make_mirror.sh
Executable file
99
make_mirror.sh
Executable file
|
@ -0,0 +1,99 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
mirrordir="./mirror"
|
||||||
|
|
||||||
|
mirror="http://deb.debian.org/debian"
|
||||||
|
nativearch=$(dpkg --print-architecture)
|
||||||
|
components=main
|
||||||
|
|
||||||
|
if [ -e "$mirrordir/dists/unstable/Release" ]; then
|
||||||
|
http_code=$(curl --output /dev/null --silent --location --head --time-cond "$mirrordir/dists/unstable/Release" --write-out '%{http_code}' "$mirror/dists/unstable/Release")
|
||||||
|
case "$http_code" in
|
||||||
|
200) ;; # need update
|
||||||
|
304) echo up-to-date; exit 0;;
|
||||||
|
*) echo unexpected status: $http_code; exit 1;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
for dist in stable testing unstable; do
|
||||||
|
rootdir=$(mktemp --directory)
|
||||||
|
|
||||||
|
for p in /etc/apt/apt.conf.d /etc/apt/sources.list.d /etc/apt/preferences.d /var/cache/apt /var/lib/apt/lists/partial /var/lib/dpkg; do
|
||||||
|
mkdir -p "$rootdir/$p"
|
||||||
|
done
|
||||||
|
|
||||||
|
cat << END > "$rootdir/etc/apt/apt.conf"
|
||||||
|
Apt::Architecture "$nativearch";
|
||||||
|
Dir::Etc "$rootdir/etc/apt";
|
||||||
|
Dir::State "$rootdir/var/lib/apt";
|
||||||
|
Dir::Cache "$rootdir/var/cache/apt";
|
||||||
|
Apt::Install-Recommends false;
|
||||||
|
Apt::Get::Download-Only true;
|
||||||
|
Dir::Etc::Trusted "/etc/apt/trusted.gpg";
|
||||||
|
Dir::Etc::TrustedParts "/etc/apt/trusted.gpg.d";
|
||||||
|
END
|
||||||
|
|
||||||
|
> "$rootdir/var/lib/dpkg/status"
|
||||||
|
|
||||||
|
cat << END > "$rootdir/etc/apt/sources.list"
|
||||||
|
deb [arch=$nativearch] $mirror $dist $components
|
||||||
|
END
|
||||||
|
|
||||||
|
|
||||||
|
APT_CONFIG="$rootdir/etc/apt/apt.conf" apt-get update
|
||||||
|
|
||||||
|
pkgs=$(APT_CONFIG="$rootdir/etc/apt/apt.conf" apt-get indextargets \
|
||||||
|
--format '$(FILENAME)' 'Created-By: Packages' "Architecture: $nativearch" \
|
||||||
|
| xargs --delimiter='\n' /usr/lib/apt/apt-helper cat-file \
|
||||||
|
| grep-dctrl --no-field-names --show-field=Package --exact-match \
|
||||||
|
\( --field=Essential yes --or --field=Priority required \
|
||||||
|
--or --field=Priority important --or --field=Priority standard \))
|
||||||
|
|
||||||
|
pkgs="$(echo $pkgs) build-essential"
|
||||||
|
|
||||||
|
APT_CONFIG="$rootdir/etc/apt/apt.conf" apt-get --yes install $pkgs
|
||||||
|
|
||||||
|
# to be able to also test gpg verification, we need to create a mirror
|
||||||
|
mkdir -p "$mirrordir/dists/$dist/" "$mirrordir/dists/$dist/main/binary-amd64/"
|
||||||
|
curl --location "$mirror/dists/$dist/Release" > "$mirrordir/dists/$dist/Release"
|
||||||
|
curl --location "$mirror/dists/$dist/Release.gpg" > "$mirrordir/dists/$dist/Release.gpg"
|
||||||
|
curl --location "$mirror/dists/$dist/main/binary-amd64/Packages.gz" > "$mirrordir/dists/$dist/main/binary-amd64/Packages.gz"
|
||||||
|
|
||||||
|
# the deb files downloaded by apt must be moved to their right locations in the
|
||||||
|
# pool directory
|
||||||
|
#
|
||||||
|
# Instead of parsing the Packages file, we could also attempt to move the deb
|
||||||
|
# files ourselves to the appropriate pool directories. But that approach
|
||||||
|
# requires re-creating the heuristic by which the directory is chosen, requires
|
||||||
|
# stripping the epoch from the filename and will break once mirrors change.
|
||||||
|
# This way, it doesn't matter where the mirror ends up storing the package.
|
||||||
|
gzip -dc "$mirrordir/dists/$dist/main/binary-amd64/Packages.gz" \
|
||||||
|
| grep-dctrl --no-field-names --show-field=Package,Version,Architecture,Filename,MD5sum '' \
|
||||||
|
| paste -sd " \n" \
|
||||||
|
| while read name ver arch fname md5; do
|
||||||
|
dir="${fname%/*}"
|
||||||
|
basename="${fname##*/}"
|
||||||
|
# apt stores deb files with the colon encoded as %3a while
|
||||||
|
# mirrors do not contain the epoch at all #645895
|
||||||
|
case "$ver" in *:*) ver="${ver%%:*}%3a${ver#*:}";; esac
|
||||||
|
aptname="$rootdir/var/cache/apt/archives/${name}_${ver}_${arch}.deb"
|
||||||
|
if [ -e "$aptname" ]; then
|
||||||
|
# make sure that we found the right file by checking its hash
|
||||||
|
echo "$md5 $aptname" | md5sum --check
|
||||||
|
mkdir -p "$mirrordir/$dir"
|
||||||
|
mv "$aptname" "$mirrordir/$fname"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
rm "$rootdir/var/cache/apt/archives/lock"
|
||||||
|
rmdir "$rootdir/var/cache/apt/archives/partial"
|
||||||
|
# now the apt cache should be empty
|
||||||
|
if [ ! -z "$(ls -1qA "$rootdir/var/cache/apt/archives/")" ]; then
|
||||||
|
echo "/var/cache/apt/archives not empty"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -r "$rootdir"
|
||||||
|
done
|
1478
mmdebstrap
Executable file
1478
mmdebstrap
Executable file
File diff suppressed because it is too large
Load diff
175
test.sh
Executable file
175
test.sh
Executable file
|
@ -0,0 +1,175 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
mirrordir="./mirror"
|
||||||
|
|
||||||
|
mirror="http://deb.debian.org/debian"
|
||||||
|
rootdir=$(mktemp --directory)
|
||||||
|
nativearch=$(dpkg --print-architecture)
|
||||||
|
components=main
|
||||||
|
|
||||||
|
abort=no
|
||||||
|
for dist in stable testing unstable; do
|
||||||
|
if [ -e debian-$dist-mm ]; then
|
||||||
|
echo "debian-$dist-mm exists"
|
||||||
|
abort=yes
|
||||||
|
fi
|
||||||
|
if [ -e debian-$dist-debootstrap ]; then
|
||||||
|
echo "debian-$dist-debootstrap exists"
|
||||||
|
abort=yes
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ $abort = yes ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
./make_mirror.sh
|
||||||
|
|
||||||
|
trap 'kill $pid' INT QUIT TERM EXIT
|
||||||
|
|
||||||
|
cd mirror
|
||||||
|
python3 -m http.server 8000 & pid=$!
|
||||||
|
cd -
|
||||||
|
|
||||||
|
# wait for the server to start
|
||||||
|
sleep 1
|
||||||
|
echo "running http server with pid $pid"
|
||||||
|
|
||||||
|
export SOURCE_DATE_EPOCH=$(date +%s)
|
||||||
|
|
||||||
|
for dist in stable testing unstable; do
|
||||||
|
> timings
|
||||||
|
> sizes
|
||||||
|
for variant in minbase buildd -; do
|
||||||
|
# skip because of different userids for apt/systemd
|
||||||
|
if [ "$dist" = 'stable' -a "$variant" = '-' ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo =========================================================
|
||||||
|
echo $dist $variant
|
||||||
|
echo =========================================================
|
||||||
|
|
||||||
|
/usr/bin/time --output=timings --append --format=%e ./mmdebstrap --variant=$variant --mode=unshare $dist debian-$dist-mm.tar "http://localhost:8000"
|
||||||
|
|
||||||
|
stat --format=%s debian-$dist-mm.tar >> sizes
|
||||||
|
mkdir ./debian-$dist-mm
|
||||||
|
cd ./debian-$dist-mm
|
||||||
|
sudo tar -xf ../debian-$dist-mm.tar
|
||||||
|
cd -
|
||||||
|
|
||||||
|
/usr/bin/time --output=timings --append --format=%e sudo debootstrap --merged-usr --variant=$variant $dist ./debian-$dist-debootstrap "http://localhost:8000/"
|
||||||
|
sudo tar --sort=name --mtime=@$SOURCE_DATE_EPOCH --clamp-mtime --numeric-owner --one-file-system -C ./debian-$dist-debootstrap -cf debian-$dist-debootstrap.tar .
|
||||||
|
sudo rm -r ./debian-$dist-debootstrap
|
||||||
|
|
||||||
|
stat --format=%s debian-$dist-debootstrap.tar >> sizes
|
||||||
|
mkdir ./debian-$dist-debootstrap
|
||||||
|
cd ./debian-$dist-debootstrap
|
||||||
|
sudo tar -xf ../debian-$dist-debootstrap.tar
|
||||||
|
cd -
|
||||||
|
|
||||||
|
# diff cannot compare device nodes, so we use tar to do that for us and then
|
||||||
|
# delete the directory
|
||||||
|
tar -C ./debian-$dist-debootstrap -cf dev1.tar ./dev
|
||||||
|
tar -C ./debian-$dist-mm -cf dev2.tar ./dev
|
||||||
|
cmp dev1.tar dev2.tar
|
||||||
|
rm dev1.tar dev2.tar
|
||||||
|
sudo rm -r ./debian-$dist-debootstrap/dev ./debian-$dist-mm/dev
|
||||||
|
|
||||||
|
# remove downloaded deb packages
|
||||||
|
sudo rm debian-$dist-debootstrap/var/cache/apt/archives/*.deb
|
||||||
|
# remove aux-cache
|
||||||
|
sudo rm debian-$dist-debootstrap/var/cache/ldconfig/aux-cache
|
||||||
|
# remove logs
|
||||||
|
sudo rm debian-$dist-debootstrap/var/log/dpkg.log \
|
||||||
|
debian-$dist-debootstrap/var/log/bootstrap.log \
|
||||||
|
debian-$dist-mm/var/log/apt/eipp.log.xz \
|
||||||
|
debian-$dist-debootstrap/var/log/alternatives.log
|
||||||
|
# remove *-old files
|
||||||
|
sudo rm debian-$dist-debootstrap/var/cache/debconf/config.dat-old \
|
||||||
|
debian-$dist-mm/var/cache/debconf/config.dat-old
|
||||||
|
sudo rm debian-$dist-debootstrap/var/cache/debconf/templates.dat-old \
|
||||||
|
debian-$dist-mm/var/cache/debconf/templates.dat-old
|
||||||
|
sudo rm debian-$dist-debootstrap/var/lib/dpkg/status-old \
|
||||||
|
debian-$dist-mm/var/lib/dpkg/status-old
|
||||||
|
# remove dpkg files
|
||||||
|
sudo rm debian-$dist-debootstrap/var/lib/dpkg/available \
|
||||||
|
debian-$dist-debootstrap/var/lib/dpkg/cmethopt
|
||||||
|
# since we installed packages directly from the .deb files, Priorities differ
|
||||||
|
# this we first check for equality and then remove the files
|
||||||
|
sudo chroot debian-$dist-debootstrap dpkg --list > dpkg1
|
||||||
|
sudo chroot debian-$dist-mm dpkg --list > dpkg2
|
||||||
|
diff -u dpkg1 dpkg2
|
||||||
|
rm dpkg1 dpkg2
|
||||||
|
grep -v '^Priority: ' debian-$dist-debootstrap/var/lib/dpkg/status > status1
|
||||||
|
grep -v '^Priority: ' debian-$dist-mm/var/lib/dpkg/status > status2
|
||||||
|
diff -u status1 status2
|
||||||
|
rm status1 status2
|
||||||
|
sudo rm debian-$dist-debootstrap/var/lib/dpkg/status debian-$dist-mm/var/lib/dpkg/status
|
||||||
|
# since we installed using apt, we have to remove some leftovers
|
||||||
|
sudo rm debian-$dist-mm/var/cache/apt/archives/lock \
|
||||||
|
debian-$dist-mm/var/lib/apt/extended_states \
|
||||||
|
debian-$dist-mm/var/lib/apt/lists/lock
|
||||||
|
sudo rmdir debian-$dist-mm/var/lib/apt/lists/auxfiles
|
||||||
|
# debootstrap exposes the hosts's kernel version
|
||||||
|
sudo rm debian-$dist-debootstrap/etc/apt/apt.conf.d/01autoremove-kernels \
|
||||||
|
debian-$dist-mm/etc/apt/apt.conf.d/01autoremove-kernels
|
||||||
|
# who creates /run/mount?
|
||||||
|
sudo rm -f debian-$dist-debootstrap/run/mount/utab
|
||||||
|
sudo rmdir debian-$dist-debootstrap/run/mount
|
||||||
|
# debootstrap doesn't clean apt
|
||||||
|
sudo rm debian-$dist-debootstrap/var/lib/apt/lists/localhost:8000_dists_${dist}_main_binary-amd64_Packages \
|
||||||
|
debian-$dist-debootstrap/var/lib/apt/lists/localhost:8000_dists_${dist}_Release \
|
||||||
|
debian-$dist-debootstrap/var/lib/apt/lists/localhost:8000_dists_${dist}_Release.gpg
|
||||||
|
|
||||||
|
if [ "$variant" = "-" ]; then
|
||||||
|
sudo rm debian-$dist-debootstrap/etc/machine-id
|
||||||
|
sudo rm debian-$dist-mm/etc/machine-id
|
||||||
|
sudo rm debian-$dist-debootstrap/var/lib/systemd/catalog/database
|
||||||
|
sudo rm debian-$dist-mm/var/lib/systemd/catalog/database
|
||||||
|
fi
|
||||||
|
|
||||||
|
# check if the file content differs
|
||||||
|
sudo diff --no-dereference --brief --recursive debian-$dist-debootstrap debian-$dist-mm
|
||||||
|
|
||||||
|
sudo rm -rf ./debian-$dist-debootstrap ./debian-$dist-mm \
|
||||||
|
./debian-$dist-debootstrap.tar ./debian-$dist-mm.tar
|
||||||
|
done
|
||||||
|
|
||||||
|
eval $(awk '{print "var"NR"="$1}' timings)
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "timings"
|
||||||
|
echo "======="
|
||||||
|
echo
|
||||||
|
echo "variant | mmdebstrap | debootstrap"
|
||||||
|
echo "--------+------------+------------"
|
||||||
|
echo "minbase | $var1 | $var2"
|
||||||
|
echo "buildd | $var3 | $var4"
|
||||||
|
if [ "$dist" != 'stable' ]; then
|
||||||
|
echo "- | $var5 | $var6"
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval $(awk '{print "var"NR"="$1}' sizes)
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "sizes"
|
||||||
|
echo "======="
|
||||||
|
echo
|
||||||
|
echo "variant | mmdebstrap | debootstrap"
|
||||||
|
echo "--------+------------+------------"
|
||||||
|
echo "minbase | $var1 | $var2"
|
||||||
|
echo "buildd | $var3 | $var4"
|
||||||
|
if [ "$dist" != 'stable' ]; then
|
||||||
|
echo "- | $var5 | $var6"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm timings sizes
|
||||||
|
done
|
||||||
|
|
||||||
|
kill $pid
|
||||||
|
|
||||||
|
wait $pid || true
|
||||||
|
|
||||||
|
trap - INT QUIT TERM EXIT
|
||||||
|
|
Loading…
Reference in a new issue