Compare commits

..

24 commits

Author SHA1 Message Date
9a9543238b
release 0.9.0 2022-05-27 08:01:22 +02:00
aaab077c2d
wip 2022-05-26 07:36:22 +02:00
a238d90774
hooks/merged-usr: workaround not necessary anymore since debootstrap 1.0.125 2022-05-26 07:36:22 +02:00
790294ddca
hooks/merged-usr: acquire native architecture from apt-config
`dpkg --print-architecture` will be wrong when creating a foreign
architecture chroot and we cannot chroot() as the chroot directory is
still empty.
2022-05-26 07:36:22 +02:00
cffd47e087
drop /usr/sbin prefixes from executables 2022-05-26 07:36:22 +02:00
c6c9c27969
use DPkg::Path as default value for PATH 2022-05-26 07:36:22 +02:00
27926c75f9
unify checking if tools exist by running them with --version 2022-05-26 07:36:22 +02:00
0f9c6543c4
improve qemu-user
- rephrase info message to be less misleading
 - do not require qemu-$arch-static binary
 - check if /proc/sys/fs/binfmt_misc/qemu-$arch exists before reading it
2022-05-26 07:36:22 +02:00
b99f1d53d5
add file-mirror-automount hook-dir 2022-05-26 07:36:21 +02:00
cc3150ef04
Rework download stage to allow file:// mirrors
- factor out package downloading function
 - replace -oApt::Get::Download-Only=true by -oDebug::pkgDpkgPm=1
 - remove guessing of package names in /var/cache/apt/archives/
 - drop edsp parsing with proxysolver/mmdebstrap-dump-solution to obtain
   downloaded filenames in favour of -oDpkg::Pre-Install-Pkgs::=cat
 - /var/cache/apt/archives/ is now allowed to contain packages
 - drop --skip=download/empty
 - file:// mirrors are now supported if their path is available inside
   the chroot
2022-05-26 07:36:21 +02:00
c8835a6149
coverage.sh: make sure archives we copied into /var/cache/apt/archives are not deleted 2022-05-24 04:16:11 +02:00
dc8b09ed50
fix pod formatting typo 2022-05-24 04:15:25 +02:00
21b23ebb9f
set MMDEBSTRAP_VERBOSITY in hooks 2022-05-24 04:14:08 +02:00
0664792cd5
manually push option arguments to array instead of using s@
By mixing s@ for --$foo-hook options and manual pushing in --hook-dir,
it can happen that options get lost. Consider the following test:

use Getopt::Long;
my $arr = [];
GetOptions(
    'A=s@' => \$arr,
    'B=s' => sub { push @{$arr}, $_[1]; }
);
foreach my $hook (@{$arr}) { print "hook: $hook\n"; }

This works fine:

    perl test.pl --A=a1 --B=b1 --A=a2 --B=b2
    hook: a1
    hook: b1
    hook: a2
    hook: b2

This misses b1:

    perl test.pl --B=b1 --A=a2 --B=b2
    hook: a2
    hook: b2
2022-05-24 04:09:41 +02:00
26af846d0a
fix that cached debs were not returned if there was nothing to download 2022-05-23 23:30:29 +02:00
df6900ec4a
hooks/eatmydata/extract.sh: fix regex to also work on s390x 2022-05-22 07:52:06 +02:00
5c5f7de898
more documentation for TMPDIR 2022-05-22 02:57:42 +02:00
29b23bbcbc
document how to build on top of an existing tarball 2022-05-22 02:57:01 +02:00
d10f320f5d
document how to build an sbuild unshare chroot mode tarball 2022-05-22 02:56:21 +02:00
ce23e702e2
fixup comparison with debootstrap 2022-05-22 02:54:41 +02:00
2c155f7cc9
coverage.sh: only skip foreign arch if RUN_MA_SAME_TESTS==no and mode==fakechroot 2022-05-22 02:54:01 +02:00
d7b39b6c97
coverage.sh: enable building variant=standard 2022-05-22 02:53:17 +02:00
6ec09c27ca
coverage.sh: mount tmpfs as workaround for #1010957 2022-05-22 02:51:58 +02:00
454121acb1
run_qemu.sh: use -cpu host as a workaround for #1011003 and because it's faster 2022-05-22 02:51:10 +02:00
109 changed files with 3135 additions and 3875 deletions

View file

@ -1,3 +1,12 @@
0.9.0 (2022-05-26)
------------------
- allow file:// mirrors
- /var/cache/apt/archives/ is now allowed to contain packages
- add file-mirror-automount hook-dir
- set $MMDEBSTRAP_VERBOSITY in hooks
- rewrite coverage with multiple individual and skippable shell scripts
0.8.6 (2022-03-25)
------------------

191
coverage.py Executable file
View file

@ -0,0 +1,191 @@
#!/usr/bin/env python3
from debian.deb822 import Deb822
import os
import sys
import shutil
import subprocess
import argparse
have_qemu = os.getenv("HAVE_QEMU", "yes") == "yes"
have_unshare = os.getenv("HAVE_UNSHARE", "yes") == "yes"
have_binfmt = os.getenv("HAVE_BINFMT", "yes") == "yes"
run_ma_same_tests = os.getenv("RUN_MA_SAME_TESTS", "yes") == "yes"
default_dist = os.getenv("DEFAULT_DIST", "unstable")
all_dists = ["oldstable", "stable", "testing", "unstable"]
default_mode = "unshare" if have_unshare else "root"
all_modes = ["auto", "root", "unshare", "fakechroot", "chrootless"]
default_variant = "apt"
all_variants = [
"extract",
"custom",
"essential",
"apt",
"minbase",
"buildd",
"-",
"standard",
]
default_format = "auto"
all_formats = ["auto", "directory", "tar", "squashfs", "ext2", "null"]
only_dists = []
mirror = os.getenv("mirror", "http://127.0.0.1/debian")
hostarch = subprocess.check_output(["dpkg", "--print-architecture"]).decode().strip()
def skip(condition, dist, mode, variant, fmt):
if not condition:
return False
toskip = False
for line in condition.splitlines():
if not line:
continue
if eval(line):
toskip = True
break
return toskip
def main():
parser = argparse.ArgumentParser()
parser.add_argument("test", nargs="*", help="only run these tests")
parser.add_argument(
"-x",
"--exitfirst",
action="store_const",
dest="maxfail",
const=1,
help="exit instantly on first error or failed test.",
)
parser.add_argument(
"--maxfail",
metavar="num",
action="store",
type=int,
dest="maxfail",
default=0,
help="exit after first num failures or errors.",
)
args = parser.parse_args()
onlyrun = None
if len(sys.argv) > 1:
onlyrun = sys.argv[1]
tests = []
with open("coverage.txt") as f:
for test in Deb822.iter_paragraphs(f):
name = test["Test"]
if args.test and name not in args.text:
continue
tt = None
if have_qemu:
tt = "qemu"
elif test.get("Needs-QEMU", "false") == "true":
tt = "skip"
elif test.get("Needs-Root", "false") == "true":
tt = "sudo"
elif have_unshare:
tt = "null"
else:
tt = "sudo"
dists = test.get("Dists", default_dist)
if dists == "any":
dists = all_dists
elif dists == "default":
dists = [default_dist]
else:
dists = dists.split()
modes = test.get("Modes", default_mode)
if modes == "any":
modes = all_modes
elif modes == "default":
modes = [default_mode]
else:
modes = modes.split()
variants = test.get("Variants", default_variant)
if variants == "any":
variants = all_variants
elif variants == "default":
variants = [default_variant]
else:
variants = variants.split()
formats = test.get("Formats", default_format)
if formats == "any":
formats = all_formats
elif formats == "default":
formats = [default_format]
else:
formats = formats.split()
for dist in dists:
if only_dists and dist not in only_dists:
continue
for mode in modes:
for variant in variants:
for fmt in formats:
if skip(test.get("Skip-If"), dist, mode, variant, fmt):
tt = "skip"
tests.append((tt, name, dist, mode, variant, fmt))
skipped = []
failed = []
for i, (test, name, dist, mode, variant, fmt) in enumerate(tests):
print(
"------------------------------------------------------------------------------"
)
print("(%d/%d) %s" % (i + 1, len(tests), name))
print("dist: %s" % dist)
print("mode: %s" % mode)
print("variant: %s" % variant)
print("format: %s" % fmt)
print(
"------------------------------------------------------------------------------"
)
with open("tests/" + name) as fin, open("shared/test.sh", "w") as fout:
for line in fin:
for e in ["CMD", "SOURCE_DATE_EPOCH"]:
line = line.replace("{{ " + e + " }}", os.getenv(e))
line = line.replace("{{ DIST }}", dist)
line = line.replace("{{ MIRROR }}", mirror)
line = line.replace("{{ MODE }}", mode)
line = line.replace("{{ VARIANT }}", variant)
line = line.replace("{{ FORMAT }}", fmt)
line = line.replace("{{ HOSTARCH }}", hostarch)
fout.write(line)
argv = None
match test:
case "qemu":
argv = ["./run_qemu.sh"]
case "sudo":
argv = ["./run_null.sh", "SUDO"]
case "null":
argv = ["./run_null.sh"]
case "skip":
skipped.append((name, dist, mode, variant, fmt))
continue
proc = subprocess.Popen(argv)
try:
proc.wait()
except KeyboardInterrupt:
proc.kill()
break
if proc.returncode != 0:
failed.append((name, dist, mode, variant, fmt))
if args.maxfail and len(failed) >= args.maxfail:
break
if skipped:
print("skipped:")
for t in skipped:
print(t)
if failed:
print("failed:")
for t in failed:
print(t)
exit(1)
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load diff

312
coverage.txt Normal file
View file

@ -0,0 +1,312 @@
Test: check-against-debootstrap-dist
Dists: any
Variants: minbase buildd -
Test: as-debootstrap-unshare-wrapper
Test: help
Test: man
Test: version
Test: create-directory
Needs-Root: true
Test: unshare-as-root-user
Needs-Root: true
Test: dist-using-codename
Dists: any
Test: fail-without-etc-subuid
Needs-QEMU: true
Test: fail-without-username-in-etc-subuid
Needs-QEMU: true
Test: unshare-as-root-user-inside-chroot
Needs-Root: true
Test: root-mode-inside-chroot
Needs-Root: true
Test: root-mode-inside-unshare-chroot
Needs-QEMU: true
Test: root-without-cap-sys-admin
Needs-Root: true
Test: mount-is-missing
Needs-QEMU: true
Test: check-for-bit-by-bit-identical-format-output
Needs-QEMU: true
Formats: tar squashfs ext2
Variants: essential apt minbase buildd important standard
Skip-If:
variant == "standard" and dist in ["oldstable", "stable"] # #864082, #1004557, #1004558
variant == "important" and dist == "oldstable" # /var/lib/systemd/catalog/database differs
fmt == "squashfs" and dist == "oldstable" # squashfs-tools-ng is not available
fmt == "ext2" and dist == "oldstable" # genext2fs does not support SOURCE_DATE_EPOCH
Test: taridshift-utility
Needs-QEMU: true
Skip-If: dist == "oldstable" # python3 tarfile module does not preserve xattrs
Test: progress-bars-on-fake-tty
Test: debug-output-on-fake-tty
Test: existing-empty-directory
Needs-Root: true
Test: existing-directory-with-lost-found
Needs-Root: true
Test: fail-installing-to-non-empty-lost-found
Test: fail-installing-to-non-empty-target-directory
Test: missing-device-nodes-outside-the-chroot
Needs-QEMU: true
Test: missing-dev-sys-proc-inside-the-chroot
Needs-QEMU: true
Test: chroot-directory-not-accessible-by-apt-user
Needs-Root: true
Test: cwd-directory-not-accessible-by-unshared-user
Needs-QEMU: true
Test: create-gzip-compressed-tarball
Needs-QEMU: true
Test: custom-tmpdir
Needs-QEMU: true
Test: xz-compressed-tarball
Test: directory-ending-in-tar
Modes: root
Needs-Root: true
Test: auto-mode-without-unshare-capabilities
Needs-QEMU: true
Test: fail-with-missing-lz4
Test: fail-with-path-with-quotes
Test: create-tarball-with-tmp-mounted-nodev
Needs-QEMU: true
Test: read-from-stdin-write-to-stdout
Test: supply-components-manually
Modes: root
Needs-Root: true
Test: stable-default-mirror
Needs-QEMU: true
Test: pass-distribution-but-implicitly-write-to-stdout
Needs-QEMU: true
Test: aspcud-apt-solver
Test: mirror-is-stdin
Test: copy-mirror
Needs-QEMU: true
Test: file-mirror
Needs-QEMU: true
Test: file-mirror-automount-hook
Needs-QEMU: true
Test: mirror-is-deb
Test: mirror-is-real-file
Test: deb822-1-2
Modes: root
Needs-Root: true
Test: deb822-2-2
Modes: root
Needs-Root: true
Test: automatic-mirror-from-suite
Needs-QEMU: true
Test: invalid-mirror
Test: fail-installing-to-root
Modes: root
Needs-Root: true
Test: fail-installing-to-existing-file
Modes: root
Needs-Root: true
Test: arm64-without-qemu-support
Needs-QEMU: true
Skip-If: hostarch != "amd64"
Test: i386-which-can-be-executed-without-qemu
Needs-QEMU: true
Skip-If:
hostarch != "amd64"
not run_ma_same_tests
Test: include-libmagic-mgc-arm64
Needs-Root: true
Skip-If:
hostarch != "amd64"
not run_ma_same_tests
Test: include-libmagic-mgc-arm64-with-multiple-arch-options
Needs-Root: true
Skip-If:
hostarch != "amd64"
not run_ma_same_tests
Test: aptopt
Needs-Root: true
Test: keyring
Needs-QEMU: true
Test: keyring-overwrites
Needs-Root: true
Test: signed-by-without-host-keys
Needs-QEMU: true
Test: ascii-armored-keys
Needs-QEMU: true
Test: signed-by-with-host-keys
Needs-Root: true
Test: dpkgopt
Needs-Root: true
Test: include
Needs-Root: true
Test: multiple-include
Needs-Root: true
Test: include-with-multiple-apt-sources
Needs-Root: true
Test: merged-usr-via-setup-hook
Needs-Root: true
Test: essential-hook
Needs-Root: true
Test: customize-hook
Needs-Root: true
Test: failing-customize-hook
Needs-Root: true
Test: sigint-during-customize-hook
Needs-Root: true
Test: hook-directory
Needs-Root: true
Test: eatmydata-via-hook-dir
Needs-Root: true
Test: special-hooks-using-helpers
Needs-Root: true
Test: special-hooks-using-helpers-and-env-vars
Needs-Root: true
Test: special-hooks-with-mode-mode
Modes: root unshare fakechroot
Needs-QEMU: true
Test: debootstrap-no-op-options
Needs-Root: true
Test: verbose
Needs-Root: true
Test: debug
Needs-Root: true
Test: quiet
Needs-Root: true
Test: logfile
Needs-Root: true
Test: without-etc-resolv-conf-and-etc-hostname
Needs-QEMU: true
Test: preserve-mode-of-etc-resolv-conf-and-etc-hostname
Modes: root
Needs-QEMU: true
Test: not-having-to-install-apt-in-include-because-a-hook-did-it-before
Test: remove-start-stop-daemon-and-policy-rc-d-in-hook
Test: compare-output-with-pre-seeded-var-cache-apt-archives
Needs-QEMU: true
Variants: any
Skip-If:
variant == "standard" and dist in ["oldstable", "stable"] # #864082, #1004557, #1004558
variant == "important" and dist == "oldstable" # /var/lib/systemd/catalog/database differs
Test: create-tarball-dry-run
Variants: any
Modes: any
Test: unpack-doc-debian
Needs-QEMU: true
Modes: any
Variants: extract
Test: install-doc-debian
Modes: chrootless
Variants: custom
Test: install-known-good-from-essential-yes
Variants: custom
Modes: chrootless
Skip-If:
True # #1006692
dist in ["oldstable", "stable"]
Test: install-doc-debian-and-output-tarball
Variants: custom
Modes: chrootless
Test: install-doc-debian-and-test-hooks
Variants: custom
Modes: chrootless
Test: install-libmagic-mgc-on-arm64
Skip-If:
hostarch != "amd64"
not have_binfmt
Test: install-busybox-based-sub-essential-system
Needs-Root: true
Test: create-arm64-tarball
Modes: root unshare fakechroot
Skip-If:
hostarch != "amd64"
mode == "fakechroot" and not run_ma_same_tests
not have_binfmt

View file

@ -1,6 +1,10 @@
#!/bin/sh
set -exu
set -eu
if [ "$MMDEBSTRAP_VERBOSITY" -ge 3 ]; then
set -x
fi
rootdir="$1"

View file

@ -1,6 +1,10 @@
#!/bin/sh
set -exu
set -eu
if [ "$MMDEBSTRAP_VERBOSITY" -ge 3 ]; then
set -x
fi
rootdir="$1"

View file

@ -1,6 +1,10 @@
#!/bin/sh
set -exu
set -eu
if [ "$MMDEBSTRAP_VERBOSITY" -ge 3 ]; then
set -x
fi
rootdir="$1"

View file

@ -1,6 +1,10 @@
#!/bin/sh
set -exu
set -eu
if [ "$MMDEBSTRAP_VERBOSITY" -ge 3 ]; then
set -x
fi
rootdir="$1"
@ -25,7 +29,7 @@ END
# 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" \
| sed -ne "s/^'\([^']\+\)'\s\+\(\S\+\)\s\+\([0-9]\+\)\s\+\(SHA256:[a-f0-9]\+\)$/\1 \2 \3 \4/p" \
| while read uri fname size hash; do
echo "processing $fname" >&2
if [ -e "$tmpdir/$fname" ]; then

View file

@ -0,0 +1,26 @@
#!/bin/sh
set -eu
if [ "$MMDEBSTRAP_VERBOSITY" -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"
echo "unmounting the following mountpoints:" >&2
cat "$rootdir/run/mmdebstrap/file-mirror-automount" \
| xargs $xargsopts echo " $rootdir/{}"
cat "$rootdir/run/mmdebstrap/file-mirror-automount" \
| xargs $xargsopts umount "$rootdir/{}"
rm "$rootdir/run/mmdebstrap/file-mirror-automount"
rmdir --ignore-fail-on-non-empty "$rootdir/run/mmdebstrap"

View file

@ -0,0 +1,20 @@
#!/bin/sh
set -eu
if [ "$MMDEBSTRAP_VERBOSITY" -ge 3 ]; then
set -x
fi
rootdir="$1"
env APT_CONFIG=$MMDEBSTRAP_APT_CONFIG apt-get indextargets --no-release-info \
| sed -ne 's/^Repo-URI: file:\/\+//p' \
| sort -u \
| while read path; do
echo "bind-mounting /$path into the chroot" >&2
mkdir -p "$rootdir/run/mmdebstrap"
mkdir -p "$rootdir/$path"
mount -o ro,bind "/$path" "$rootdir/$path"
printf '/%s\0' "$path" >> "$rootdir/run/mmdebstrap/file-mirror-automount"
done

View file

@ -39,22 +39,21 @@
# 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 -exu
set -eu
if [ "$MMDEBSTRAP_VERBOSITY" -ge 3 ]; then
set -x
fi
TARGET="$1"
if [ -e "$TARGET/var/lib/dpkg/arch" ]; then
ARCH=$(head -1 "$TARGET/var/lib/dpkg/arch")
else
ARCH=$(dpkg --print-architecture)
fi
eval "$(APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-config shell ARCH Apt::Architecture)"
if [ -e /usr/share/debootstrap/functions ]; then
. /usr/share/debootstrap/functions
doing_variant () { [ $1 != "buildd" ]; }
MERGED_USR="yes"
# until https://salsa.debian.org/installer-team/debootstrap/-/merge_requests/48 gets merged
link_dir=""
setup_merged_usr
else
link_dir=""

View file

@ -23,7 +23,7 @@
use strict;
use warnings;
our $VERSION = '0.8.6';
our $VERSION = '0.9.0';
use English;
use Getopt::Long;
@ -206,6 +206,26 @@ sub minor {
return $right->bior($left);
}
sub can_execute {
my $tool = shift;
my $pid = open my $fh, '-|' // return 0;
if ($pid == 0) {
open(STDERR, '>&', STDOUT) or die;
exec {$tool} $tool, '--version' or die;
}
chomp(
my $content = do { local $/; <$fh> }
);
close $fh;
if ($? != 0) {
return 0;
}
if (length $content == 0) {
return 0;
}
return 1;
}
# check whether a directory is mounted by comparing the device number of the
# directory itself with its parent
sub is_mountpoint {
@ -874,28 +894,9 @@ sub run_dpkg_progress {
sub run_apt_progress {
my $options = shift;
my @debs = @{ $options->{PKGS} // [] };
my $tmpedsp;
if (exists $options->{EDSP_RES}) {
(undef, $tmpedsp) = tempfile(
"mmdebstrap.edsp.XXXXXXXXXXXX",
OPEN => 0,
TMPDIR => 1
);
}
my $get_exec = sub {
my @prefix = ();
my @opts = ();
if (exists $options->{EDSP_RES}) {
push @prefix, 'env', "APT_EDSP_DUMP_FILENAME=$tmpedsp";
if (-e "./proxysolver") {
# for development purposes, use the current directory if it
# contains a file called proxysolver
push @opts, ("-oDir::Bin::solvers=" . getcwd()),
'--solver=proxysolver';
} else {
push @opts, '--solver=mmdebstrap-dump-solution';
}
}
return (
@prefix,
@{ $options->{ARGV} },
@ -950,38 +951,79 @@ sub run_apt_progress {
}
};
run_progress $get_exec, $line_handler, $line_has_error, $options->{CHDIR};
if (exists $options->{EDSP_RES}) {
info "parsing EDSP results...";
open my $fh, '<', $tmpedsp
or error "failed to open $tmpedsp for reading: $!";
my $inst = 0;
my $pkg;
my $ver;
while (my $line = <$fh>) {
chomp $line;
if ($line ne "") {
if ($line =~ /^Install: \d+/) {
$inst = 1;
} elsif ($line =~ /^Package: (.*)/) {
$pkg = $1;
} elsif ($line =~ /^Version: (.*)/) {
$ver = $1;
}
next;
}
if ($inst == 1 && defined $pkg && defined $ver) {
push @{ $options->{EDSP_RES} }, [$pkg, $ver];
}
$inst = 0;
undef $pkg;
undef $ver;
}
close $fh;
unlink $tmpedsp;
}
return;
}
sub run_apt_download_progress {
my $options = shift;
if ($options->{dryrun}) {
info "simulate downloading packages with apt...";
} else {
info "downloading packages with apt...";
}
pipe my $rfh, my $wfh;
my $pid = open my $fh, '-|' // error "fork() failed: $!";
if ($pid == 0) {
close $wfh;
# read until parent process closes $wfh
my $content = do { local $/; <$rfh> };
close $rfh;
# the parent is done -- pass what we read back to it
print $content;
exit 0;
}
close $rfh;
# Unset the close-on-exec flag, so that the file descriptor does not
# get closed when we exec
my $flags = fcntl($wfh, F_GETFD, 0) or error "fcntl F_GETFD: $!";
fcntl($wfh, F_SETFD, $flags & ~FD_CLOEXEC) or error "fcntl F_SETFD: $!";
my $fd = fileno $wfh;
# 2022-05-02, #debian-apt on OFTC, times in UTC+2
# 16:57 < josch> DonKult: how is -oDebug::pkgDpkgPm=1 -oDir::Log=/dev/null
# a "fancy no-op"?
# 11:52 < DonKult> josch: "fancy no-op" in sofar as it does nothing to the
# system even through its not in a special mode ala
# simulation or download-only. It does all the things it
# normally does, except that it just prints the dpkg calls
# instead of execv() them which in practice amounts means
# it does nothing (the Dir::Log just prevents libapt from
# creating the /var/log/apt directories. As the code
# creates them even if no logs will be placed there…). As
# said, midterm an apt --print-install-packages or
# something would be nice to avoid running everything.
run_apt_progress({
ARGV => [
'apt-get',
'--yes',
'-oDebug::pkgDpkgPm=1',
'-oDir::Log=/dev/null',
$options->{dryrun}
? '-oAPT::Get::Simulate=true'
: (
"-oAPT::Keep-Fds::=$fd",
"-oDPkg::Tools::options::'cat >&$fd'::InfoFD=$fd",
"-oDpkg::Pre-Install-Pkgs::=cat >&$fd",
# no need to lock the database if we are just downloading
"-oDebug::NoLocking=1",
# no need for pty magic if we write no log
"-oDpkg::Use-Pty=0",
),
@{ $options->{APT_ARGV} },
],
});
# signal the child process that we are done
close $wfh;
# and then read from it what it got
my @listofdebs = <$fh>;
close $fh;
if ($? != 0) {
error "status child failed";
}
# remove trailing newlines
chomp @listofdebs;
return @listofdebs;
}
sub run_chroot {
my $cmd = shift;
my $options = shift;
@ -1424,6 +1466,9 @@ sub run_hooks {
# This is the file descriptor of the socket that the mmdebstrap
# --hook-helper can write to and read from to communicate with the outside.
push @env_opts, ("MMDEBSTRAP_HOOKSOCK=" . fileno($options->{hooksock}));
# Store the verbosity of mmdebstrap so that hooks can be just as verbose
# as the mmdebstrap invocation that called them.
push @env_opts, ("MMDEBSTRAP_VERBOSITY=" . $verbosity_level);
my $runner = sub {
foreach my $script (@{ $options->{"${name}_hook"} }) {
@ -2034,24 +2079,12 @@ sub run_update() {
sub run_download() {
my $options = shift;
# We use /var/cache/apt/archives/ to figure out which packages apt chooses
# to install. That's why the directory must be empty if:
# - /var/cache/apt/archives exists, and
# - no simulation run is done, and
# - the variant is not extract or custom or the number to be
# installed packages not zero
#
# We could also unconditionally use the proxysolver and then "apt-get
# download" any missing packages but using the proxysolver requires
# /usr/lib/apt/solvers/apt from the apt-utils package and we want to avoid
# that dependency.
#
# In the future we want to replace downloading packages with "apt-get
# install --download-only" and installing them with dpkg by just installing
# the essential packages with apt from the outside with
# DPkg::Chroot-Directory. We are not doing that because then the preinst
# script of base-passwd will not be called early enough and packages will
# fail to install because they are missing /etc/passwd.
# install" and installing them with dpkg by just installing the essential
# packages with apt from the outside with DPkg::Chroot-Directory.
# We are not doing that because then the preinst script of base-passwd will
# not be called early enough and packages will fail to install because they
# are missing /etc/passwd.
my @cached_debs = ();
my @dl_debs = ();
if (
@ -2073,14 +2106,6 @@ sub run_download() {
push @cached_debs, $deb;
}
closedir $dh;
if (scalar @cached_debs > 0) {
if (any { $_ eq 'download/empty' } @{ $options->{skip} }) {
info "skipping download/empty as requested";
} else {
error("/var/cache/apt/archives/ inside the chroot contains: "
. (join ', ', (sort @cached_debs)));
}
}
}
# To figure out the right package set for the apt variant we can use:
@ -2092,9 +2117,9 @@ sub run_download() {
if (any { $_ eq $options->{variant} } ('extract', 'custom')) {
if (scalar @{ $options->{include} } == 0) {
info "nothing to download -- skipping...";
return ([], []);
return ([], \@cached_debs);
}
my %pkgs_to_install;
my @apt_argv = ('install');
for my $incl (@{ $options->{include} }) {
for my $pkg (split /[,\s]+/, $incl) {
# strip leading and trailing whitespace
@ -2103,32 +2128,15 @@ sub run_download() {
if ($pkg eq '') {
next;
}
$pkgs_to_install{$pkg} = ();
push @apt_argv, $pkg;
}
}
my %result = ();
if ($options->{dryrun}) {
info "simulate downloading packages with apt...";
} else {
# if there are already packages in /var/cache/apt/archives/, we
# need to use our proxysolver to obtain the solution chosen by apt
if (scalar @cached_debs > 0) {
$result{EDSP_RES} = \@dl_debs;
}
info "downloading packages with apt...";
}
run_apt_progress({
ARGV => [
'apt-get',
'--yes',
'-oApt::Get::Download-Only=true',
$options->{dryrun} ? '-oAPT::Get::Simulate=true' : (),
'install'
],
PKGS => [keys %pkgs_to_install],
%result
});
@dl_debs = run_apt_download_progress({
APT_ARGV => [@apt_argv],
dryrun => $options->{dryrun},
},
);
} elsif ($options->{variant} eq 'apt') {
# if we just want to install Essential:yes packages, apt and their
# dependencies then we can make use of libapt treating apt as
@ -2143,27 +2151,11 @@ sub run_download() {
# remind me in 5+ years that I said that after I wrote
# in the bugreport: "Are you crazy?!? Nobody in his
# right mind would even suggest depending on it!")
my %result = ();
if ($options->{dryrun}) {
info "simulate downloading packages with apt...";
} else {
# if there are already packages in /var/cache/apt/archives/, we
# need to use our proxysolver to obtain the solution chosen by apt
if (scalar @cached_debs > 0) {
$result{EDSP_RES} = \@dl_debs;
}
info "downloading packages with apt...";
}
run_apt_progress({
ARGV => [
'apt-get',
'--yes',
'-oApt::Get::Download-Only=true',
$options->{dryrun} ? '-oAPT::Get::Simulate=true' : (),
'dist-upgrade'
],
%result
});
@dl_debs = run_apt_download_progress({
APT_ARGV => ['dist-upgrade'],
dryrun => $options->{dryrun},
},
);
} elsif (
any { $_ eq $options->{variant} }
('essential', 'standard', 'important', 'required', 'buildd')
@ -2172,23 +2164,8 @@ sub run_download() {
# 17:27 < DonKult> (?essential includes 'apt' through)
# 17:30 < josch> DonKult: no, because pkgCacheGen::ForceEssential ",";
# 17:32 < DonKult> touché
my %result = ();
if ($options->{dryrun}) {
info "simulate downloading packages with apt...";
} else {
# if there are already packages in /var/cache/apt/archives/, we
# need to use our proxysolver to obtain the solution chosen by apt
if (scalar @cached_debs > 0) {
$result{EDSP_RES} = \@dl_debs;
}
info "downloading packages with apt...";
}
run_apt_progress({
ARGV => [
'apt-get',
'--yes',
'-oApt::Get::Download-Only=true',
$options->{dryrun} ? '-oAPT::Get::Simulate=true' : (),
@dl_debs = run_apt_download_progress({
APT_ARGV => [
'install',
'?narrow('
. (
@ -2203,76 +2180,37 @@ sub run_download() {
. $options->{nativearch}
. '),?essential)'
],
%result
});
dryrun => $options->{dryrun},
},
);
} else {
error "unknown variant: $options->{variant}";
}
my @essential_pkgs;
if (scalar @cached_debs > 0 && scalar @dl_debs > 0) {
my $archives = "/var/cache/apt/archives/";
# for each package in @dl_debs, check if it's in
# /var/cache/apt/archives/ and add it to @essential_pkgs
foreach my $p (@dl_debs) {
my ($pkg, $ver_epoch) = @{$p};
# apt appends the architecture at the end of the package name
($pkg, my $arch) = split ':', $pkg, 2;
# apt replaces the colon by its percent encoding %3a
my $ver = $ver_epoch;
$ver =~ s/:/%3a/;
# the architecture returned by apt is the native architecture.
# Since we don't know whether the package is architecture
# independent or not, we first try with the native arch and then
# with "all" and only error out if neither exists.
if (-e "$options->{root}/$archives/${pkg}_${ver}_$arch.deb") {
push @essential_pkgs, "$archives/${pkg}_${ver}_$arch.deb";
} elsif (-e "$options->{root}/$archives/${pkg}_${ver}_all.deb") {
push @essential_pkgs, "$archives/${pkg}_${ver}_all.deb";
} else {
error( "cannot find package for $pkg:$arch (= $ver_epoch) "
. "in /var/cache/apt/archives/");
}
}
} else {
# collect the .deb files that were downloaded by apt from the content
# of /var/cache/apt/archives/
if (!$options->{dryrun}) {
my $apt_archives = "/var/cache/apt/archives/";
opendir my $dh, "$options->{root}/$apt_archives"
or error "cannot read $apt_archives";
while (my $deb = readdir $dh) {
if ($deb !~ /\.deb$/) {
next;
}
$deb = "$apt_archives/$deb";
if (!-f "$options->{root}/$deb") {
next;
# strip the chroot directory from the filenames
foreach my $deb (@dl_debs) {
# if filename does not start with chroot directory then the user
# might've used a file:// mirror and we check whether the path is
# accessible inside the chroot
if (rindex $deb, $options->{root}, 0) {
if (!-e "$options->{root}/$deb") {
error "package file $deb not accessible from chroot directory"
. " -- use copy:// instead of file:// or a bind-mount. You"
. " can also try using --hook-dir=/usr/share/mmdebstrap/"
. "hooks/file-mirror-automount to automatically create"
. " the necessary bind-mounts.";
}
push @essential_pkgs, $deb;
next;
}
closedir $dh;
if (scalar @essential_pkgs == 0) {
# check if a file:// URI was used
open(my $pipe_apt, '-|', 'apt-get', 'indextargets', '--format',
'$(URI)', 'Created-By: Packages')
or error "cannot start apt-get indextargets: $!";
while (my $uri = <$pipe_apt>) {
if ($uri =~ /^file:\/\//) {
error
"nothing got downloaded -- use copy:// instead of"
. " file://";
# filename starts with chroot directory, strip it off
# this is the normal case
if (!-e $deb) {
error "cannot find package file $deb";
}
push @essential_pkgs, substr($deb, length($options->{root}));
}
error "nothing got downloaded";
}
}
}
# Unpack order matters. Since we create this list using two different
# methods but we want both methods to have the same result, we sort the
# list before returning it.
@essential_pkgs = sort @essential_pkgs;
return (\@essential_pkgs, \@cached_debs);
}
@ -2499,7 +2437,7 @@ sub run_prepare {
any { $_ eq $options->{mode} }
('root', 'unshare', 'fakechroot')
) {
push @chrootcmd, ('/usr/sbin/chroot', $options->{root});
push @chrootcmd, ('chroot', $options->{root});
} else {
error "unknown mode: $options->{mode}";
}
@ -2561,6 +2499,7 @@ sub run_prepare {
# binary at configuration time instead of lazily at startup
# time. If the flag is set, then the qemu-static binary is not
# required inside the chroot.
if (-e "/proc/sys/fs/binfmt_misc/qemu-$options->{qemu}") {
open my $fh, '<',
"/proc/sys/fs/binfmt_misc/qemu-$options->{qemu}";
while (my $line = <$fh>) {
@ -2571,6 +2510,7 @@ sub run_prepare {
}
}
close $fh;
}
};
if ($require_qemu_static) {
# other modes require a static qemu-user binary
@ -2729,6 +2669,10 @@ sub run_essential() {
# before the download phase
next
if any { "/var/cache/apt/archives/$_" eq $deb } @{$cached_debs};
# do not unlink those packages that were not in
# /var/cache/apt/archive (for example because they were provided by
# a file:// mirror)
next if $deb !~ /\/var\/cache\/apt\/archives\//;
unlink "$options->{root}/$deb"
or error "cannot unlink $deb: $!";
}
@ -3107,7 +3051,7 @@ sub hookhelper {
# Fakechroot requires tar to run inside the chroot or
# otherwise absolute symlinks will include the path to the
# root directory
push @cmdprefix, '/usr/sbin/chroot', $root;
push @cmdprefix, 'chroot', $root;
} elsif ($mode eq 'proot') {
# proot requires tar to run inside proot or otherwise
# permissions will be completely off
@ -4284,10 +4228,18 @@ sub main() {
sub { push @{ $options->{noop} }, 'no-merged-usr'; },
'force-check-gpg' =>
sub { push @{ $options->{noop} }, 'force-check-gpg'; },
'setup-hook=s@' => \$options->{setup_hook},
'extract-hook=s@' => \$options->{extract_hook},
'essential-hook=s@' => \$options->{essential_hook},
'customize-hook=s@' => \$options->{customize_hook},
'setup-hook=s' => sub {
push @{ $options->{setup_hook} }, $_[1];
},
'extract-hook=s' => sub {
push @{ $options->{extract_hook} }, $_[1];
},
'essential-hook=s' => sub {
push @{ $options->{essential_hook} }, $_[1];
},
'customize-hook=s' => sub {
push @{ $options->{customize_hook} }, $_[1];
},
'hook-directory=s' => sub {
my ($opt_name, $opt_value) = @_;
if (!-e $opt_value) {
@ -4401,12 +4353,13 @@ sub main() {
}
# setting PATH for chroot, ldconfig, start-stop-daemon...
my $defaultpath = `eval \$(apt-config shell v DPkg::Path); printf \$v`;
if (length $ENV{PATH}) {
## no critic (Variables::RequireLocalizedPunctuationVars)
$ENV{PATH} = "$ENV{PATH}:/usr/sbin:/usr/bin:/sbin:/bin";
$ENV{PATH} = "$ENV{PATH}:$defaultpath";
} else {
## no critic (Variables::RequireLocalizedPunctuationVars)
$ENV{PATH} = "/usr/sbin:/usr/bin:/sbin:/bin";
$ENV{PATH} = $defaultpath;
}
foreach my $tool (
@ -4414,14 +4367,7 @@ sub main() {
'apt-config', 'tar', 'rm', 'find',
'env'
) {
my $found = 0;
foreach my $path (split /:/, $ENV{PATH}) {
if (-f "$path/$tool" && -x _ ) {
$found = 1;
last;
}
}
if (!$found) {
if (!can_execute $tool) {
error "cannot find $tool";
}
}
@ -4503,7 +4449,7 @@ sub main() {
# if we are not root, unshare mode is our best option if
# test_unshare_userns() succeeds
$options->{mode} = 'unshare';
} elsif (system('fakechroot --version>/dev/null') == 0) {
} elsif (can_execute 'fakechroot') {
# the next fallback is fakechroot
# exec ourselves again but within fakechroot
my @prefix = ();
@ -4511,7 +4457,7 @@ sub main() {
@prefix = ($EXECUTABLE_NAME, '-MDevel::Cover=-silent,-nogcov');
}
exec 'fakechroot', 'fakeroot', @prefix, $PROGRAM_NAME, @ARGVORIG;
} elsif (system('proot --version>/dev/null') == 0) {
} elsif (can_execute 'proot') {
# and lastly, proot
$options->{mode} = 'proot';
} else {
@ -4523,13 +4469,13 @@ sub main() {
error "need to be root";
}
} elsif ($options->{mode} eq 'proot') {
if (system('proot --version>/dev/null') != 0) {
if (!can_execute 'proot') {
error "need working proot binary";
}
} elsif ($options->{mode} eq 'fakechroot') {
if (&{$check_fakechroot_running}()) {
# fakechroot is already running
} elsif (system('fakechroot --version>/dev/null') != 0) {
} elsif (!can_execute 'fakechroot') {
error "need working fakechroot binary";
} else {
# exec ourselves again but within fakechroot
@ -4629,7 +4575,7 @@ sub main() {
}
if (any { $_ eq $options->{mode} } ('root', 'unshare')) {
if (system('mount --version>/dev/null') != 0) {
if (!can_execute 'mount') {
warning "cannot execute mount";
$options->{canmount} = 0;
}
@ -4707,7 +4653,7 @@ sub main() {
} elsif ($options->{variant} eq "extract") {
info "skipping emulation check for extract variant";
} elsif ($hostarch ne $options->{nativearch}) {
if (system('arch-test --version>/dev/null') != 0) {
if (!can_execute 'arch-test') {
error "install arch-test for foreign architecture support";
}
my $withemu = 0;
@ -4798,15 +4744,12 @@ sub main() {
if (!exists $deb2qemu->{ $options->{nativearch} }) {
warning "no mapping from $options->{nativearch} to"
. " qemu-user binary";
} elsif (
system('/usr/sbin/update-binfmts --version>/dev/null')
!= 0) {
warning "cannot find /usr/sbin/update-binfmts";
} elsif (!can_execute 'update-binfmts') {
warning "cannot find update-binfmts";
} else {
my $binfmt_identifier
= 'qemu-' . $deb2qemu->{ $options->{nativearch} };
open my $fh, '-|', '/usr/sbin/update-binfmts',
'--display',
open my $fh, '-|', 'update-binfmts', '--display',
$binfmt_identifier // error "failed to fork(): $!";
chomp(
my $binfmts = do { local $/; <$fh> }
@ -4823,19 +4766,13 @@ sub main() {
} elsif ($withemu == 0 and $noemu == 1) {
error "arch-test succeeded without emu but not with emu";
} elsif ($withemu == 1 and $noemu == 0) {
info "$options->{nativearch} cannot be executed, falling back"
. " to qemu-user";
info "$options->{nativearch} cannot be executed natively, but"
. " transparently using qemu-user binfmt emulation";
if (!exists $deb2qemu->{ $options->{nativearch} }) {
error "no mapping from $options->{nativearch} to qemu-user"
. " binary";
}
$options->{qemu} = $deb2qemu->{ $options->{nativearch} };
if (any { $_ eq $options->{mode} } ('root', 'unshare')) {
my $qemubin = "/usr/bin/qemu-$options->{qemu}-static";
if (!-e $qemubin) {
error "cannot find $qemubin";
}
}
} elsif ($withemu == 1 and $noemu == 1) {
info "$options->{nativearch} is different from $hostarch but"
. " can be executed natively";
@ -5623,7 +5560,7 @@ sub main() {
# Fakechroot requires tar to run inside the chroot or
# otherwise absolute symlinks will include the path to the
# root directory
0 == system('/usr/sbin/chroot', $options->{root}, 'tar',
0 == system('chroot', $options->{root}, 'tar',
@taropts, '-C', '/', '.')
or error "tar failed: $?";
} elsif ($options->{mode} eq 'proot') {
@ -6276,6 +6213,11 @@ architecture specific symlinks
--hook-dir=/usr/share/mmdebstrap/hooks/merged-usr
Example 4: Automatically mount all directories referenced by C<file://> mirrors
into the chroot
--hook-dir=/usr/share/mmdebstrap/hooks/file-mirror-automount
=item B<--skip>=I<stage>[,I<stage>,...]
B<mmdebstrap> tries hard to implement sensible defaults and will try to stop
@ -6566,15 +6508,22 @@ hook. Otherwise, if I<command> is an existing executable file from C<$PATH> or
if I<command> does not contain any shell metacharacters, then I<command> is
directly exec-ed with the path to the chroot directory passed as the first
argument. Otherwise, I<command> is executed under I<sh> and the chroot
directory can be accessed via I<$1>. All environment variables set by
B<mmdebstrap> (like C<APT_CONFIG>, C<DEBIAN_FRONTEND>, C<LC_ALL> and C<PATH>)
are preserved. All environment variables set by the user are preserved, except
for C<TMPDIR> which is cleared. See section B<TMPDIR>.
directory can be accessed via I<$1>. Most environment variables set by
B<mmdebstrap> (like C<DEBIAN_FRONTEND>, C<LC_ALL> and C<PATH>) are preserved.
Most notably, C<APT_CONFIG> is being unset. If you need the path to
C<APT_CONFIG> as written by mmdebstrap it can be found in the
C<MMDEBSTRAP_APT_CONFIG> environment variable. All environment variables set by
the user are preserved, except for C<TMPDIR> which is cleared. See section
B<TMPDIR>. Furthermore, C<MMDEBSTRAP_MODE> will store the mode set by
B<--mode>, C<MMDEBSTRAP_HOOK> stores which hook is currently run (setup,
extract, essential, customize) and C<MMDEBSTRAP_VERBOSITY> stores the numerical
verbosity level (0 for no output, 1 for normal, 2 for verbose and 3 for debug
output).
The paths inside the chroot are relative to the root directory of the chroot.
The path on the outside is relative to current directory of the original
B<mmdebstrap> invocation. The path inside the chroot must already exist. Paths
outside the chroot are created as necessary.
In special hooks, the paths inside the chroot are relative to the root
directory of the chroot. The path on the outside is relative to current
directory of the original B<mmdebstrap> invocation. The path inside the chroot
must already exist. Paths outside the chroot are created as necessary.
In B<fakechroot> and B<proot> mode, C<tar>, or C<sh> and C<cat> have to be run
inside the chroot or otherwise, symlinks will be wrongly resolved and/or
@ -6703,15 +6652,13 @@ the B<setup> step. This can be disabled using B<--skip=update>.
=item B<download>
Checks whether F</var/cache/apt/archives/> is empty. This can be disabled with
B<--skip=download/empty>. In the B<extract> and B<custom> variants, C<apt-get
--download-only install> is used to download all the packages requested via the
B<--include> option. The B<apt> variant uses the fact that libapt treats the
C<apt> packages as implicitly essential to download only all C<Essential:yes>
packages plus apt using C<apt-get --download-only dist-upgrade>. In the
remaining variants, all Packages files downloaded by the B<update> step are
inspected to find the C<Essential:yes> package set as well as all packages of
the required priority.
In the B<extract> and B<custom> variants, C<apt-get install> is used to
download all the packages requested via the B<--include> option. The B<apt>
variant uses the fact that libapt treats the C<apt> packages as implicitly
essential to download only all C<Essential:yes> packages plus apt using
C<apt-get dist-upgrade>. In the remaining variants, all Packages files
downloaded by the B<update> step are inspected to find the C<Essential:yes>
package set as well as all packages of the required priority.
=item B<extract>
@ -6727,7 +6674,7 @@ In B<fakechroot> mode, environment variables C<LD_LIBRARY_PATH> will be set up
correctly. If the chroot requires the qemu-user-static binary it will be copied
in. For foreign B<fakechroot> environments, C<LD_LIBRARY_PATH> and
C<QEMU_LD_PREFIX> are set up accordingly. This step is not carried out in
>B<extract> mode and neither for the B<chrootless> variant.
B<extract> mode and neither for the B<chrootless> variant.
=item B<essential>
@ -6815,6 +6762,10 @@ create device nodes:
$ mmdebstrap unstable | tar --delete ./dev > unstable-chroot.tar
Create a tarball for use with C<sbuild --chroot-mode=unshare>:
$ mmdebstrap --variant=buildd unstable ~/.cache/sbuild/unstable-amd64.tar
Instead of a tarball, a squashfs image can be created:
$ mmdebstrap unstable unstable-chroot.squashfs
@ -6957,12 +6908,30 @@ apt-cacher-ng, you can use the B<sync-in> and B<sync-out> special hooks to
synchronize a directory outside the chroot with F</var/cache/apt/archives>
inside the chroot.
$ mmdebstrap --variant=apt --skip=download/empty --skip=essential/unlink \
$ mmdebstrap --variant=apt --skip=essential/unlink \
--setup-hook='mkdir -p ./cache "$1"/var/cache/apt/archives/' \
--setup-hook='sync-in ./cache /var/cache/apt/archives/' \
--customize-hook='sync-out /var/cache/apt/archives ./cache' \
unstable /dev/null
Instead of copying potentially large amounts of data with B<sync-in> you can
also use a bind-mount in combination with a C<file://> mirror to make packages
from the outside available inside the chroot:
$ mmdebstrap --variant=apt --skip=essential/unlink \
--setup-hook='mkdir "$1/tmp/mirror"' \
--setup-hook='mount -o ro,bind /tmp/mirror "$1/tmp/mirror"' \
--customize-hook='sync-out /var/cache/apt/archives ./cache' \
--customize-hook='umount "$1/tmp/mirror"; rmdir "$1/tmp/mirror";' \
unstable /dev/null file:///tmp/mirror http://deb.debian.org/debian
To automatically mount all directories referenced by C<file://> mirrors
into the chroot you can use a hook:
$ mmdebstrap --variant=apt \
--hook-dir=/usr/share/mmdebstrap/hooks/file-mirror-automount \
unstable /dev/null file:///tmp/mirror1 file:///tmp/mirror2
Create a system that can be used with docker:
$ mmdebstrap unstable | sudo docker import - debian
@ -6990,6 +6959,17 @@ As a docker/podman replacement:
root
$ rm chroot.tar
You can re-use a chroot tarball created with mmdebstrap for further refinement.
Say you want to create a minimal chroot and a chroot with more packages
installed, then instead of downloading and installing the essential packages
twice you can instead build on top of the already present minimal chroot:
$ mmdebstrap --variant=apt unstable chroot.tar
$ mmdebstrap --variant=custom --setup-hook \
'mmtarfilter "--path-exclude=/dev/*" < chroot.tar | tar -C "$1" -x' \
--customize-hook='chroot "$1" apt-get install --yes pkg1 pkg2' \
'' chroot-full.tar
=head1 ENVIRONMENT VARIABLES
=over 8
@ -7017,7 +6997,12 @@ or fill them with reproducible content:
When creating a tarball, a temporary directory is populated with the rootfs
before the tarball is packed. The location of that temporary directory will be
in F</tmp> or the location pointed to by C<TMPDIR> if that environment variable
is set.
is set. Setting C<TMPDIR> to a different directory than F</tmp> is useful if
you have F</tmp> on a tmpfs that is too small for your rootfs.
If you set C<TMPDIR> in B<unshare> mode, then the unshared user must be able to
access the directory. This means that the directory itself must be
world-writable and all its ancestors must be at least world-executable.
Since C<TMPDIR> is only valid outside the chroot, the variable is being unset
when running hook scripts. If you need a valid temporary directory in a hook,
@ -7037,7 +7022,7 @@ This section lists some differences to debootstrap.
=item * Multiple ways to operate as non-root: fakechroot, proot, unshare
=item * 3-6 times faster
=item * twice as fast
=item * Can create a chroot with only C<Essential:yes> packages and their deps
@ -7057,7 +7042,7 @@ Limitations in comparison to debootstrap:
=item * Only runs on systems with apt installed (Debian and derivatives)
=item * No I<SCRIPT> argument
=item * No I<SCRIPT> argument (use hooks instead)
=item * Some debootstrap options don't exist, namely:
@ -7111,12 +7096,6 @@ as the non-root user, then as a workaround you could run C<chmod 600
/etc/dpkg/dpkg.cfg.d/*> so that the config files are only accessible by the
root user. See Debian bug #808203.
The C<file://> URI type cannot be used to install the essential packages. This
is because B<mmdebstrap> uses dpkg to install the packages that apt places into
F</var/cache/apt/archives> but with C<file://> apt will not copy the files even
with C<--download-only>. Use C<copy://> instead, which is equivalent to
C<file://> but copies the archives into F</var/cache/apt/archives>.
With apt versions before 2.1.16, setting C<[trusted=yes]> or
C<Acquire::AllowInsecureRepositories "1"> to allow signed archives without a
known public key or unsigned archives will fail because of a gpg warning in the

View file

@ -32,8 +32,9 @@ qemu-img create -f qcow2 -b "$(realpath $cachedir)/debian-$DEFAULT_DIST.qcow" -F
# minicom -D 'unix#/tmp/ttyS0'
ret=0
timeout 20m qemu-system-x86_64 \
-cpu host \
-no-user-config \
-M accel=kvm:tcg -m 1G -nographic \
-M accel=kvm:tcg -m 4G -nographic \
-object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-pci,rng=rng0 \
-monitor unix:/tmp/monitor,server,nowait \
-serial unix:/tmp/ttyS0,server,nowait \

9
tests/aptopt Normal file
View file

@ -0,0 +1,9 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
echo 'Acquire::Languages "none";' > /tmp/config
{{ CMD }} --mode=root --variant=apt --aptopt='Acquire::Check-Valid-Until "false"' --aptopt=/tmp/config {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
printf 'Acquire::Check-Valid-Until "false";\nAcquire::Languages "none";\n' | cmp /tmp/debian-chroot/etc/apt/apt.conf.d/99mmdebstrap -
rm /tmp/debian-chroot/etc/apt/apt.conf.d/99mmdebstrap
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot /tmp/config

View file

@ -0,0 +1,14 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
apt-get remove --yes qemu-user-static binfmt-support qemu-user
ret=0
{{ CMD }} --mode={{ MODE }} --variant=apt --architectures=arm64 {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }} || ret=$?
if [ "$ret" = 0 ]; then
echo expected failure but got exit $ret >&2
exit 1
fi

View file

@ -0,0 +1,71 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
export SOURCE_DATE_EPOCH={{ SOURCE_DATE_EPOCH }}
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
sysctl -w kernel.unprivileged_userns_clone=1
adduser --gecos user --disabled-password user
runuser -u user -- {{ CMD }} --variant=custom --mode=unshare --setup-hook='env container=lxc debootstrap --no-merged-usr unstable "$1" {{ MIRROR }}' - /tmp/debian-mm.tar {{ MIRROR }}
mkdir /tmp/debian-mm
tar --xattrs --xattrs-include='*' -C /tmp/debian-mm -xf /tmp/debian-mm.tar
mkdir /tmp/debian-debootstrap
tar --xattrs --xattrs-include='*' -C /tmp/debian-debootstrap -xf "cache/debian-unstable--.tar"
# diff cannot compare device nodes, so we use tar to do that for us and then
# delete the directory
tar -C /tmp/debian-debootstrap -cf dev1.tar ./dev
tar -C /tmp/debian-mm -cf dev2.tar ./dev
cmp dev1.tar dev2.tar
rm dev1.tar dev2.tar
rm -r /tmp/debian-debootstrap/dev /tmp/debian-mm/dev
# remove downloaded deb packages
rm /tmp/debian-debootstrap/var/cache/apt/archives/*.deb
# remove aux-cache
rm /tmp/debian-debootstrap/var/cache/ldconfig/aux-cache
# remove logs
rm /tmp/debian-debootstrap/var/log/dpkg.log \
/tmp/debian-debootstrap/var/log/bootstrap.log \
/tmp/debian-debootstrap/var/log/alternatives.log \
/tmp/debian-mm/var/log/bootstrap.log
# debootstrap doesn't clean apt
rm /tmp/debian-debootstrap/var/lib/apt/lists/127.0.0.1_debian_dists_unstable_main_binary-{{ HOSTARCH }}_Packages \
/tmp/debian-debootstrap/var/lib/apt/lists/127.0.0.1_debian_dists_unstable_Release \
/tmp/debian-debootstrap/var/lib/apt/lists/127.0.0.1_debian_dists_unstable_Release.gpg
rm /tmp/debian-debootstrap/etc/machine-id /tmp/debian-mm/etc/machine-id
rm /tmp/debian-mm/var/cache/apt/archives/lock
rm /tmp/debian-mm/var/lib/apt/lists/lock
# check if the file content differs
diff --no-dereference --recursive /tmp/debian-debootstrap /tmp/debian-mm
# check permissions, ownership, symlink targets, modification times using tar
# mtimes of directories created by mmdebstrap will differ, thus we equalize them first
for d in etc/apt/preferences.d/ etc/apt/sources.list.d/ etc/dpkg/dpkg.cfg.d/ var/log/apt/; do
touch --date="@{{ SOURCE_DATE_EPOCH }}" /tmp/debian-debootstrap/$d /tmp/debian-mm/$d
done
# debootstrap never ran apt -- fixing permissions
for d in ./var/lib/apt/lists/partial ./var/cache/apt/archives/partial; do
chroot /tmp/debian-debootstrap chmod 0700 $d
chroot /tmp/debian-debootstrap chown _apt:root $d
done
tar -C /tmp/debian-debootstrap --numeric-owner --xattrs --xattrs-include='*' --sort=name --clamp-mtime --mtime=$(date --utc --date=@{{ SOURCE_DATE_EPOCH }} --iso-8601=seconds) -cf /tmp/root1.tar .
tar -C /tmp/debian-mm --numeric-owner --xattrs --xattrs-include='*' --sort=name --clamp-mtime --mtime=$(date --utc --date=@{{ SOURCE_DATE_EPOCH }} --iso-8601=seconds) -cf /tmp/root2.tar .
tar --full-time --verbose -tf /tmp/root1.tar > /tmp/root1.tar.list
tar --full-time --verbose -tf /tmp/root2.tar > /tmp/root2.tar.list
# despite SOURCE_DATE_EPOCH and --clamp-mtime, the timestamps in the tarball
# will slightly differ from each other in the sub-second precision (last
# decimals) so the tarballs will not be identical, so we use diff to compare
# content and tar to compare attributes
diff -u /tmp/root1.tar.list /tmp/root2.tar.list
rm /tmp/root1.tar /tmp/root2.tar /tmp/root1.tar.list /tmp/root2.tar.list
rm /tmp/debian-mm.tar
rm -r /tmp/debian-debootstrap /tmp/debian-mm

18
tests/ascii-armored-keys Normal file
View file

@ -0,0 +1,18 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
for f in /usr/share/keyrings/*.gpg; do
name=$(basename "$f" .gpg)
gpg --enarmor < /usr/share/keyrings/$name.gpg \
| sed 's/ PGP ARMORED FILE/ PGP PUBLIC KEY BLOCK/;/^Comment: /d' \
> /etc/apt/trusted.gpg.d/$name.asc
done
rm /etc/apt/trusted.gpg.d/*.gpg
rm /usr/share/keyrings/*.gpg
{{ CMD }} --mode=root --variant=apt {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot.tar

11
tests/aspcud-apt-solver Normal file
View file

@ -0,0 +1,11 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
{{ CMD }} --mode={{ MODE }} --variant=custom \
--include $(cat pkglist.txt | tr '\n' ',') \
--aptopt='APT::Solver "aspcud"' \
{{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
tar -tf /tmp/debian-chroot.tar | sort \
| grep -v '^./etc/apt/apt.conf.d/99mmdebstrap$' \
| diff -u tar1.txt -
rm /tmp/debian-chroot.tar

View file

@ -0,0 +1,12 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
adduser --gecos user --disabled-password user
sysctl -w kernel.unprivileged_userns_clone=0
runuser -u user -- {{ CMD }} --mode=auto --variant=apt {{ DIST }} /tmp/debian-chroot.tar.gz {{ MIRROR }}
tar -tf /tmp/debian-chroot.tar.gz | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar.gz

View file

@ -0,0 +1,14 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
cat << HOSTS >> /etc/hosts
127.0.0.1 deb.debian.org
127.0.0.1 security.debian.org
HOSTS
{{ CMD }} --mode={{ MODE }} --variant=apt {{ DIST }} /tmp/debian-chroot.tar
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar

View file

@ -0,0 +1,196 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
export SOURCE_DATE_EPOCH={{ SOURCE_DATE_EPOCH }}
# we create the apt user ourselves or otherwise its uid/gid will differ
# compared to the one chosen in debootstrap because of different installation
# order in comparison to the systemd users
# https://bugs.debian.org/969631
# we cannot use useradd because passwd is not Essential:yes
{{ CMD }} --variant={{ VARIANT }} --mode={{ MODE }} \
--essential-hook='if [ {{ VARIANT }} = - ]; then echo _apt:*:100:65534::/nonexistent:/usr/sbin/nologin >> "$1"/etc/passwd; fi' \
{{ DIST }} /tmp/debian-{{ DIST }}-mm.tar {{ MIRROR }}
mkdir /tmp/debian-{{ DIST }}-mm
tar --xattrs --xattrs-include='*' -C /tmp/debian-{{ DIST }}-mm -xf /tmp/debian-{{ DIST }}-mm.tar
rm /tmp/debian-{{ DIST }}-mm.tar
mkdir /tmp/debian-{{ DIST }}-debootstrap
tar --xattrs --xattrs-include='*' -C /tmp/debian-{{ DIST }}-debootstrap -xf "cache/debian-{{ DIST }}-{{ VARIANT }}.tar"
# diff cannot compare device nodes, so we use tar to do that for us and then
# delete the directory
tar -C /tmp/debian-{{ DIST }}-debootstrap -cf dev1.tar ./dev
tar -C /tmp/debian-{{ DIST }}-mm -cf dev2.tar ./dev
ret=0
cmp dev1.tar dev2.tar || ret=$?
if [ "$ret" -ne 0 ]; then
if type diffoscope >/dev/null; then
diffoscope dev1.tar dev2.tar
exit 1
else
echo "no diffoscope installed" >&2
fi
if type base64 >/dev/null; then
base64 dev1.tar
base64 dev2.tar
exit 1
else
echo "no base64 installed" >&2
fi
if type xxd >/dev/null; then
xxd dev1.tar
xxd dev2.tar
exit 1
else
echo "no xxd installed" >&2
fi
exit 1
fi
rm dev1.tar dev2.tar
rm -r /tmp/debian-{{ DIST }}-debootstrap/dev /tmp/debian-{{ DIST }}-mm/dev
# remove downloaded deb packages
rm /tmp/debian-{{ DIST }}-debootstrap/var/cache/apt/archives/*.deb
# remove aux-cache
rm /tmp/debian-{{ DIST }}-debootstrap/var/cache/ldconfig/aux-cache
# remove logs
rm /tmp/debian-{{ DIST }}-debootstrap/var/log/dpkg.log \
/tmp/debian-{{ DIST }}-debootstrap/var/log/bootstrap.log \
/tmp/debian-{{ DIST }}-debootstrap/var/log/alternatives.log
# remove *-old files
rm /tmp/debian-{{ DIST }}-debootstrap/var/cache/debconf/config.dat-old \
/tmp/debian-{{ DIST }}-mm/var/cache/debconf/config.dat-old
rm /tmp/debian-{{ DIST }}-debootstrap/var/cache/debconf/templates.dat-old \
/tmp/debian-{{ DIST }}-mm/var/cache/debconf/templates.dat-old
rm /tmp/debian-{{ DIST }}-debootstrap/var/lib/dpkg/status-old \
/tmp/debian-{{ DIST }}-mm/var/lib/dpkg/status-old
# remove dpkg files
rm /tmp/debian-{{ DIST }}-debootstrap/var/lib/dpkg/available
rm /tmp/debian-{{ DIST }}-debootstrap/var/lib/dpkg/cmethopt
# since we installed packages directly from the .deb files, Priorities differ
# thus we first check for equality and then remove the files
chroot /tmp/debian-{{ DIST }}-debootstrap dpkg --list > dpkg1
chroot /tmp/debian-{{ DIST }}-mm dpkg --list > dpkg2
diff -u dpkg1 dpkg2
rm dpkg1 dpkg2
grep -v '^Priority: ' /tmp/debian-{{ DIST }}-debootstrap/var/lib/dpkg/status > status1
grep -v '^Priority: ' /tmp/debian-{{ DIST }}-mm/var/lib/dpkg/status > status2
diff -u status1 status2
rm status1 status2
rm /tmp/debian-{{ DIST }}-debootstrap/var/lib/dpkg/status /tmp/debian-{{ DIST }}-mm/var/lib/dpkg/status
# debootstrap exposes the hosts's kernel version
if [ -e /tmp/debian-{{ DIST }}-debootstrap/etc/apt/apt.conf.d/01autoremove-kernels ]; then
rm /tmp/debian-{{ DIST }}-debootstrap/etc/apt/apt.conf.d/01autoremove-kernels
fi
if [ -e /tmp/debian-{{ DIST }}-mm/etc/apt/apt.conf.d/01autoremove-kernels ]; then
rm /tmp/debian-{{ DIST }}-mm/etc/apt/apt.conf.d/01autoremove-kernels
fi
# who creates /run/mount?
if [ -e "/tmp/debian-{{ DIST }}-debootstrap/run/mount/utab" ]; then
rm "/tmp/debian-{{ DIST }}-debootstrap/run/mount/utab"
fi
if [ -e "/tmp/debian-{{ DIST }}-debootstrap/run/mount" ]; then
rmdir "/tmp/debian-{{ DIST }}-debootstrap/run/mount"
fi
# debootstrap doesn't clean apt
rm /tmp/debian-{{ DIST }}-debootstrap/var/lib/apt/lists/127.0.0.1_debian_dists_{{ DIST }}_main_binary-{{ HOSTARCH }}_Packages \
/tmp/debian-{{ DIST }}-debootstrap/var/lib/apt/lists/127.0.0.1_debian_dists_{{ DIST }}_Release \
/tmp/debian-{{ DIST }}-debootstrap/var/lib/apt/lists/127.0.0.1_debian_dists_{{ DIST }}_Release.gpg
if [ "{{ VARIANT }}" = "-" ]; then
rm /tmp/debian-{{ DIST }}-debootstrap/etc/machine-id
rm /tmp/debian-{{ DIST }}-mm/etc/machine-id
rm /tmp/debian-{{ DIST }}-debootstrap/var/lib/systemd/catalog/database
rm /tmp/debian-{{ DIST }}-mm/var/lib/systemd/catalog/database
cap=$(chroot /tmp/debian-{{ DIST }}-debootstrap /sbin/getcap /bin/ping)
expected="/bin/ping cap_net_raw=ep"
if [ "{{ DIST }}" = oldstable ]; then
expected="/bin/ping = cap_net_raw+ep"
fi
if [ "$cap" != "$expected" ]; then
echo "expected bin/ping to have capabilities $expected" >&2
echo "but debootstrap produced: $cap" >&2
exit 1
fi
cap=$(chroot /tmp/debian-{{ DIST }}-mm /sbin/getcap /bin/ping)
if [ "$cap" != "$expected" ]; then
echo "expected bin/ping to have capabilities $expected" >&2
echo "but mmdebstrap produced: $cap" >&2
exit 1
fi
fi
rm /tmp/debian-{{ DIST }}-mm/var/cache/apt/archives/lock
rm /tmp/debian-{{ DIST }}-mm/var/lib/apt/extended_states
rm /tmp/debian-{{ DIST }}-mm/var/lib/apt/lists/lock
# the list of shells might be sorted wrongly
for f in "/tmp/debian-{{ DIST }}-debootstrap/etc/shells" "/tmp/debian-{{ DIST }}-mm/etc/shells"; do
sort -o "$f" "$f"
done
# Because of unreproducible uids (#969631) we created the _apt user ourselves
# and because passwd is not Essential:yes we didn't use useradd. But newer
# versions of adduser and shadow will create a different /etc/shadow
for f in shadow shadow-; do
if grep -q '^_apt:!:' /tmp/debian-{{ DIST }}-debootstrap/etc/$f; then
sed -i 's/^_apt:\*:\([^:]\+\):0:99999:7:::$/_apt:!:\1::::::/' /tmp/debian-{{ DIST }}-mm/etc/$f
fi
done
# workaround for https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=917773
if ! cmp /tmp/debian-{{ DIST }}-debootstrap/etc/shadow /tmp/debian-{{ DIST }}-mm/etc/shadow; then
echo patching /etc/shadow on {{ DIST }} {{ VARIANT }} >&2
awk -v FS=: -v OFS=: -v SDE={{ SOURCE_DATE_EPOCH }} '{ print $1,$2,int(SDE/60/60/24),$4,$5,$6,$7,$8,$9 }' < /tmp/debian-{{ DIST }}-mm/etc/shadow > /tmp/debian-{{ DIST }}-mm/etc/shadow.bak
cat /tmp/debian-{{ DIST }}-mm/etc/shadow.bak > /tmp/debian-{{ DIST }}-mm/etc/shadow
rm /tmp/debian-{{ DIST }}-mm/etc/shadow.bak
else
echo no difference for /etc/shadow on {{ DIST }} {{ VARIANT }} >&2
fi
if ! cmp /tmp/debian-{{ DIST }}-debootstrap/etc/shadow- /tmp/debian-{{ DIST }}-mm/etc/shadow-; then
echo patching /etc/shadow- on {{ DIST }} {{ VARIANT }} >&2
awk -v FS=: -v OFS=: -v SDE={{ SOURCE_DATE_EPOCH }} '{ print $1,$2,int(SDE/60/60/24),$4,$5,$6,$7,$8,$9 }' < /tmp/debian-{{ DIST }}-mm/etc/shadow- > /tmp/debian-{{ DIST }}-mm/etc/shadow-.bak
cat /tmp/debian-{{ DIST }}-mm/etc/shadow-.bak > /tmp/debian-{{ DIST }}-mm/etc/shadow-
rm /tmp/debian-{{ DIST }}-mm/etc/shadow-.bak
else
echo no difference for /etc/shadow- on {{ DIST }} {{ VARIANT }} >&2
fi
# Because of unreproducible uids (#969631) we created the _apt user ourselves
# and because passwd is not Essential:yes we didn't use useradd. But passwd
# since 1:4.11.1+dfsg1-1 will create empty mail files, so we create it too.
# https://bugs.debian.org/1004710
if [ {{ VARIANT }} = - ]; then
if [ -e /tmp/debian-{{ DIST }}-debootstrap/var/mail/_apt ]; then
touch /tmp/debian-{{ DIST }}-mm/var/mail/_apt
chmod 660 /tmp/debian-{{ DIST }}-mm/var/mail/_apt
chown 100:8 /tmp/debian-{{ DIST }}-mm/var/mail/_apt
fi
fi
# check if the file content differs
diff --unified --no-dereference --recursive /tmp/debian-{{ DIST }}-debootstrap /tmp/debian-{{ DIST }}-mm >&2
# check permissions, ownership, symlink targets, modification times using tar
# directory mtimes will differ, thus we equalize them first
find /tmp/debian-{{ DIST }}-debootstrap /tmp/debian-{{ DIST }}-mm -type d -print0 | xargs -0 touch --date="@{{ SOURCE_DATE_EPOCH }}"
# debootstrap never ran apt -- fixing permissions
for d in ./var/lib/apt/lists/partial ./var/cache/apt/archives/partial; do
chroot /tmp/debian-{{ DIST }}-debootstrap chmod 0700 $d
chroot /tmp/debian-{{ DIST }}-debootstrap chown _apt:root $d
done
tar -C /tmp/debian-{{ DIST }}-debootstrap --numeric-owner --sort=name --clamp-mtime --mtime=$(date --utc --date=@{{ SOURCE_DATE_EPOCH }} --iso-8601=seconds) -cf /tmp/root1.tar .
tar -C /tmp/debian-{{ DIST }}-mm --numeric-owner --sort=name --clamp-mtime --mtime=$(date --utc --date=@{{ SOURCE_DATE_EPOCH }} --iso-8601=seconds) -cf /tmp/root2.tar .
tar --full-time --verbose -tf /tmp/root1.tar > /tmp/root1.tar.list
tar --full-time --verbose -tf /tmp/root2.tar > /tmp/root2.tar.list
diff -u /tmp/root1.tar.list /tmp/root2.tar.list
rm /tmp/root1.tar /tmp/root2.tar /tmp/root1.tar.list /tmp/root2.tar.list
# check if file properties (permissions, ownership, symlink names, modification time) differ
#
# we cannot use this (yet) because it cannot cope with paths that have [ or @ in them
#fmtree -c -p /tmp/debian-{{ DIST }}-debootstrap -k flags,gid,link,mode,size,time,uid | sudo fmtree -p /tmp/debian-{{ DIST }}-mm
rm -r /tmp/debian-{{ DIST }}-debootstrap /tmp/debian-{{ DIST }}-mm

View file

@ -0,0 +1,35 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
adduser --gecos user --disabled-password user
sysctl -w kernel.unprivileged_userns_clone=1
export SOURCE_DATE_EPOCH={{ SOURCE_DATE_EPOCH }}
mount -o size=4G -t tmpfs tmpfs /tmp # workaround for #1010957
{{ CMD }} --mode=root --variant={{ VARIANT }} {{ DIST }} /tmp/debian-chroot-root.{{ FORMAT }} {{ MIRROR }}
if [ "{{ FORMAT }}" = tar ]; then
printf 'ustar ' | cmp --bytes=6 --ignore-initial=257:0 /tmp/debian-chroot-root.tar -
elif [ "{{ FORMAT }}" = squashfs ]; then
printf 'hsqs' | cmp --bytes=4 /tmp/debian-chroot-root.squashfs -
elif [ "{{ FORMAT }}" = ext2 ]; then
printf '\123\357' | cmp --bytes=2 --ignore-initial=1080:0 /tmp/debian-chroot-root.ext2 -
else
echo "unknown format: {{ FORMAT }}" >&2
exit 1
fi
runuser -u user -- {{ CMD }} --mode=unshare --variant={{ VARIANT }} {{ DIST }} /tmp/debian-chroot-unshare.{{ FORMAT }} {{ MIRROR }}
cmp /tmp/debian-chroot-root.{{ FORMAT }} /tmp/debian-chroot-unshare.{{ FORMAT }}
rm /tmp/debian-chroot-unshare.{{ FORMAT }}
case {{ VARIANT }} in essential|apt|minbase|buildd)
# variants important and standard differ because permissions drwxr-sr-x
# and extended attributes of ./var/log/journal/ cannot be preserved
# in fakechroot mode
runuser -u user -- {{ CMD }} --mode=fakechroot --variant={{ VARIANT }} {{ DIST }} /tmp/debian-chroot-fakechroot.{{ FORMAT }} {{ MIRROR }}
cmp /tmp/debian-chroot-root.{{ FORMAT }} /tmp/debian-chroot-fakechroot.{{ FORMAT }}
rm /tmp/debian-chroot-fakechroot.{{ FORMAT }}
;;
esac
rm /tmp/debian-chroot-root.{{ FORMAT }}

View file

@ -0,0 +1,8 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
mkdir /tmp/debian-chroot
chmod 700 /tmp/debian-chroot
{{ CMD }} --mode=root --variant=apt {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,45 @@
#!/bin/sh
#
# test that the user can drop archives into /var/cache/apt/archives as well as
# into /var/cache/apt/archives/partial
set -eu
export LC_ALL=C.UTF-8
export SOURCE_DATE_EPOCH={{ SOURCE_DATE_EPOCH }}
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test requires the cache directory to be mounted on /mnt and should only be run inside a container" >&2
exit 1
fi
include="--include=doc-debian"
if [ "{{ VARIANT }}" = "custom" ]; then
include="$include,base-files,base-passwd,coreutils,dash,diffutils,dpkg,libc-bin,sed"
fi
mount -o size=4G -t tmpfs tmpfs /tmp # workaround for #1010957
{{ CMD }} $include --mode={{ MODE }} --variant={{ VARIANT }} \
--setup-hook='mkdir -p "$1"/var/cache/apt/archives/partial' \
--setup-hook='touch "$1"/var/cache/apt/archives/lock' \
--setup-hook='chmod 0640 "$1"/var/cache/apt/archives/lock' \
{{ DIST }} - {{ MIRROR }} > orig.tar
# somehow, when trying to create a tarball from the 9p mount, tar throws the
# following error: tar: ./doc-debian_6.4_all.deb: File shrank by 132942 bytes; padding with zeros
# to reproduce, try: tar --directory /mnt/cache/debian/pool/main/d/doc-debian/ --create --file - . | tar --directory /tmp/ --extract --file -
# this will be different:
# md5sum /mnt/cache/debian/pool/main/d/doc-debian/*.deb /tmp/*.deb
# another reason to copy the files into a new directory is, that we can use shell globs
tmpdir=$(mktemp -d)
cp /mnt/cache/debian/pool/main/b/busybox/busybox_*"_{{ HOSTARCH }}.deb" /mnt/cache/debian/pool/main/a/apt/apt_*"_{{ HOSTARCH }}.deb" "$tmpdir"
{{ CMD }} $include --mode={{ MODE }} --variant={{ VARIANT }} \
--setup-hook='mkdir -p "$1"/var/cache/apt/archives/partial' \
--setup-hook='sync-in "'"$tmpdir"'" /var/cache/apt/archives/partial' \
{{ DIST }} - {{ MIRROR }} > test1.tar
cmp orig.tar test1.tar
{{ CMD }} $include --mode={{ MODE }} --variant={{ VARIANT }} \
--customize-hook='touch "$1"/var/cache/apt/archives/partial' \
--setup-hook='mkdir -p "$1"/var/cache/apt/archives/' \
--setup-hook='sync-in "'"$tmpdir"'" /var/cache/apt/archives/' \
--setup-hook='chmod 0755 "$1"/var/cache/apt/archives/' \
--customize-hook='find "'"$tmpdir"'" -type f -exec md5sum "{}" \; | sed "s|"'"$tmpdir"'"|$1/var/cache/apt/archives|" | md5sum --check' \
{{ DIST }} - {{ MIRROR }} > test2.tar
cmp orig.tar test2.tar
rm "$tmpdir"/*.deb orig.tar test1.tar test2.tar
rmdir "$tmpdir"

10
tests/copy-mirror Normal file
View file

@ -0,0 +1,10 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test requires the cache directory to be mounted on /mnt and should only be run inside a container" >&2
exit 1
fi
{{ CMD }} --mode={{ MODE }} --variant=apt {{ DIST }} /tmp/debian-chroot.tar "deb copy:///mnt/cache/debian {{ DIST }} main"
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar

View file

@ -0,0 +1,45 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ "$(id -u)" -eq 0 ] && ! id -u user > /dev/null 2>&1; then
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
adduser --gecos user --disabled-password user
fi
if [ "{{ MODE }}" = unshare ]; then
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
sysctl -w kernel.unprivileged_userns_clone=1
fi
prefix=
[ "$(id -u)" -eq 0 ] && [ "{{ MODE }}" != "root" ] && prefix="runuser -u user --"
[ "{{ MODE }}" = "fakechroot" ] && prefix="$prefix fakechroot fakeroot"
$prefix {{ CMD }} --mode={{ MODE }} --variant=apt --architectures=arm64 {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
# we ignore differences between architectures by ignoring some files
# and renaming others
# in proot mode, some extra files are put there by proot
{ tar -tf /tmp/debian-chroot.tar \
| grep -v '^\./lib/ld-linux-aarch64\.so\.1$' \
| grep -v '^\./lib/aarch64-linux-gnu/ld-linux-aarch64\.so\.1$' \
| grep -v '^\./usr/share/doc/[^/]\+/changelog\(\.Debian\)\?\.arm64\.gz$' \
| sed 's/aarch64-linux-gnu/x86_64-linux-gnu/' \
| sed 's/arm64/amd64/';
} | sort > tar2.txt
{ cat tar1.txt \
| grep -v '^\./usr/bin/i386$' \
| grep -v '^\./usr/bin/x86_64$' \
| grep -v '^\./lib64/$' \
| grep -v '^\./lib64/ld-linux-x86-64\.so\.2$' \
| grep -v '^\./lib/x86_64-linux-gnu/ld-linux-x86-64\.so\.2$' \
| grep -v '^\./lib/x86_64-linux-gnu/libmvec-2\.[0-9]\+\.so$' \
| grep -v '^\./lib/x86_64-linux-gnu/libmvec\.so\.1$' \
| grep -v '^\./usr/share/doc/[^/]\+/changelog\(\.Debian\)\?\.amd64\.gz$' \
| grep -v '^\./usr/share/man/man8/i386\.8\.gz$' \
| grep -v '^\./usr/share/man/man8/x86_64\.8\.gz$';
[ "{{ MODE }}" = "proot" ] && printf "./etc/ld.so.preload\n";
} | sort | diff -u - tar2.txt
rm /tmp/debian-chroot.tar

7
tests/create-directory Normal file
View file

@ -0,0 +1,7 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
{{ CMD }} --mode=root --variant=apt {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
chroot /tmp/debian-chroot dpkg-query --showformat '${binary:Package}\n' --show > pkglist.txt
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort > tar1.txt
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,43 @@
cat << END > shared/test.sh
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
{{ CMD }} --mode={{ MODE }} --dry-run --variant=apt --setup-hook="exit 1" --essential-hook="exit 1" --customize-hook="exit 1" {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
rm /tmp/debian-chroot/dev/console
rm /tmp/debian-chroot/dev/fd
rm /tmp/debian-chroot/dev/full
rm /tmp/debian-chroot/dev/null
rm /tmp/debian-chroot/dev/ptmx
rm /tmp/debian-chroot/dev/random
rm /tmp/debian-chroot/dev/stderr
rm /tmp/debian-chroot/dev/stdin
rm /tmp/debian-chroot/dev/stdout
rm /tmp/debian-chroot/dev/tty
rm /tmp/debian-chroot/dev/urandom
rm /tmp/debian-chroot/dev/zero
rm /tmp/debian-chroot/etc/apt/sources.list
rm /tmp/debian-chroot/etc/fstab
rm /tmp/debian-chroot/etc/hostname
rm /tmp/debian-chroot/etc/resolv.conf
rm /tmp/debian-chroot/var/lib/apt/lists/lock
rm /tmp/debian-chroot/var/lib/dpkg/status
# the rest should be empty directories that we can rmdir recursively
find /tmp/debian-chroot -depth -print0 | xargs -0 rmdir
END
if [ "$HAVE_QEMU" = "yes" ]; then
./run_qemu.sh
runtests=$((runtests+1))
elif [ "{{ MODE }}" = "root" ]; then
./run_null.sh SUDO
runtests=$((runtests+1))
else
./run_null.sh
runtests=$((runtests+1))
fi
# test all --dry-run variants
# we are testing all variants here because with 0.7.5 we had a bug:
# mmdebstrap sid /dev/null --simulate ==> E: cannot read /var/cache/apt/archives/
for variant in extract custom essential apt minbase buildd important standard; do
for mode in root unshare fakechroot proot chrootless; do

View file

@ -0,0 +1,13 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
adduser --gecos user --disabled-password user
sysctl -w kernel.unprivileged_userns_clone=1
runuser -u user -- {{ CMD }} --mode=unshare --variant=apt {{ DIST }} /tmp/debian-chroot.tar.gz {{ MIRROR }}
printf '\037\213\010' | cmp --bytes=3 /tmp/debian-chroot.tar.gz -
tar -tf /tmp/debian-chroot.tar.gz | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar.gz

View file

@ -0,0 +1,35 @@
#!/bin/sh
#
# we are testing all variants here because with 0.7.5 we had a bug:
# mmdebstrap sid /dev/null --simulate ==> E: cannot read /var/cache/apt/archives/
set -eu
export LC_ALL=C.UTF-8
prefix=
include=
if [ "$(id -u)" -eq 0 ] && [ "{{ MODE }}" != root ]; then
# this must be qemu
if ! id -u user >/dev/null 2>&1; then
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
adduser --gecos user --disabled-password user
fi
if [ "{{ MODE }}" = unshare ]; then
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
sysctl -w kernel.unprivileged_userns_clone=1
fi
prefix="runuser -u user --"
if [ "{{ MODE }}" = extract ] || [ "{{ MODE }}" = custom ]; then
include="--include=$(cat pkglist.txt | tr '\n' ',')"
fi
fi
$prefix {{ CMD }} --mode={{ MODE }} $include --dry-run --variant={{ VARIANT }} {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
if [ -e /tmp/debian-chroot.tar ]; then
echo "/tmp/debian-chroot.tar must not be created with --dry-run" >&2
exit 1
fi

View file

@ -0,0 +1,12 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
mount -t tmpfs -o nodev,nosuid,size=300M tmpfs /tmp
# use --customize-hook to exercise the mounting/unmounting code of block devices in root mode
{{ CMD }} --mode=root --variant=apt --customize-hook='mount | grep /dev/full' --customize-hook='test "$(echo foo | tee /dev/full 2>&1 1>/dev/null)" = "tee: /dev/full: No space left on device"' {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar

18
tests/custom-tmpdir Normal file
View file

@ -0,0 +1,18 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
adduser --gecos user --disabled-password user
sysctl -w kernel.unprivileged_userns_clone=1
homedir=$(runuser -u user -- sh -c 'cd && pwd')
runuser -u user -- mkdir "$homedir/tmp"
runuser -u user -- env TMPDIR="$homedir/tmp" {{ CMD }} --mode=unshare --variant=apt \
--setup-hook='case "$1" in "'"$homedir/tmp/mmdebstrap."'"??????????) exit 0;; *) exit 1;; esac' \
{{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
# use rmdir as a quick check that nothing is remaining in TMPDIR
runuser -u user -- rmdir "$homedir/tmp"
rm /tmp/debian-chroot.tar

17
tests/customize-hook Normal file
View file

@ -0,0 +1,17 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
cat << 'SCRIPT' > /tmp/customize.sh
#!/bin/sh
chroot "$1" whoami > "$1/output2"
chroot "$1" pwd >> "$1/output2"
SCRIPT
chmod +x /tmp/customize.sh
{{ CMD }} --mode=root --variant=apt --customize-hook='chroot "$1" sh -c "whoami; pwd" > "$1/output1"' --customize-hook=/tmp/customize.sh {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
printf "root\n/\n" | cmp /tmp/debian-chroot/output1
printf "root\n/\n" | cmp /tmp/debian-chroot/output2
rm /tmp/debian-chroot/output1
rm /tmp/debian-chroot/output2
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm /tmp/customize.sh
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,22 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
adduser --gecos user --disabled-password user
sysctl -w kernel.unprivileged_userns_clone=1
mkdir /tmp/debian-chroot
chmod 700 /tmp/debian-chroot
chown user:user /tmp/debian-chroot
if [ "{{ CMD }}" = "./mmdebstrap" ]; then
CMD=$(realpath --canonicalize-existing ./mmdebstrap)
elif [ "{{ CMD }}" = "perl -MDevel::Cover=-silent,-nogcov ./mmdebstrap" ]; then
CMD="perl -MDevel::Cover=-silent,-nogcov $(realpath --canonicalize-existing ./mmdebstrap)"
else
CMD="{{ CMD }}"
fi
env --chdir=/tmp/debian-chroot runuser -u user -- $CMD --mode=unshare --variant=apt {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar

46
tests/deb822-1-2 Normal file
View file

@ -0,0 +1,46 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
cat << SOURCES > /tmp/deb822.sources
Types: deb
URIs: {{ MIRROR }}1
Suites: {{ DIST }}
Components: main
SOURCES
echo "deb {{ MIRROR }}2 {{ DIST }} main" > /tmp/sources.list
echo "deb {{ MIRROR }}3 {{ DIST }} main" \
| {{ CMD }} --mode={{ MODE }} --variant=apt {{ DIST }} \
/tmp/debian-chroot \
/tmp/deb822.sources \
{{ MIRROR }}4 \
- \
"deb {{ MIRROR }}5 {{ DIST }} main" \
{{ MIRROR }}6 \
/tmp/sources.list
test ! -e /tmp/debian-chroot/etc/apt/sources.list
cat << SOURCES | cmp /tmp/debian-chroot/etc/apt/sources.list.d/0000deb822.sources -
Types: deb
URIs: {{ MIRROR }}1
Suites: {{ DIST }}
Components: main
SOURCES
cat << SOURCES | cmp /tmp/debian-chroot/etc/apt/sources.list.d/0001main.list -
deb {{ MIRROR }}4 {{ DIST }} main
deb {{ MIRROR }}3 {{ DIST }} main
deb {{ MIRROR }}5 {{ DIST }} main
deb {{ MIRROR }}6 {{ DIST }} main
SOURCES
echo "deb {{ MIRROR }}2 {{ DIST }} main" | cmp /tmp/debian-chroot/etc/apt/sources.list.d/0002sources.list -
tar -C /tmp/debian-chroot --one-file-system -c . \
| {
tar -t \
| grep -v "^./etc/apt/sources.list.d/0000deb822.sources$" \
| grep -v "^./etc/apt/sources.list.d/0001main.list$" \
| grep -v "^./etc/apt/sources.list.d/0002sources.list";
printf "./etc/apt/sources.list\n";
} | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot
rm /tmp/sources.list /tmp/deb822.sources

45
tests/deb822-2-2 Normal file
View file

@ -0,0 +1,45 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
cat << SOURCES > /tmp/deb822
Types: deb
URIs: {{ MIRROR }}1
Suites: {{ DIST }}
Components: main
SOURCES
echo "deb {{ MIRROR }}2 {{ DIST }} main" > /tmp/sources
cat << SOURCES | {{ CMD }} --mode={{ MODE }} --variant=apt {{ DIST }} \
/tmp/debian-chroot \
/tmp/deb822 \
- \
/tmp/sources
Types: deb
URIs: {{ MIRROR }}3
Suites: {{ DIST }}
Components: main
SOURCES
test ! -e /tmp/debian-chroot/etc/apt/sources.list
ls -lha /tmp/debian-chroot/etc/apt/sources.list.d/
cat << SOURCES | cmp /tmp/debian-chroot/etc/apt/sources.list.d/0000deb822.sources -
Types: deb
URIs: {{ MIRROR }}1
Suites: {{ DIST }}
Components: main
SOURCES
cat << SOURCES | cmp /tmp/debian-chroot/etc/apt/sources.list.d/0001main.sources -
Types: deb
URIs: {{ MIRROR }}3
Suites: {{ DIST }}
Components: main
SOURCES
echo "deb {{ MIRROR }}2 {{ DIST }} main" | cmp /tmp/debian-chroot/etc/apt/sources.list.d/0002sources.list -
tar -C /tmp/debian-chroot --one-file-system -c . \
| {
tar -t \
| grep -v "^./etc/apt/sources.list.d/0000deb822.sources$" \
| grep -v "^./etc/apt/sources.list.d/0001main.sources$" \
| grep -v "^./etc/apt/sources.list.d/0002sources.list$";
printf "./etc/apt/sources.list\n";
} | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot
rm /tmp/sources /tmp/deb822

View file

@ -0,0 +1,6 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
{{ CMD }} --mode=root --variant=apt --resolve-deps --merged-usr --no-merged-usr --force-check-gpg {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot

6
tests/debug Normal file
View file

@ -0,0 +1,6 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
{{ CMD }} --mode=root --variant=apt --debug {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,6 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
script -qfc "{{ CMD }} --mode={{ MODE }} --debug --variant=apt {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}" /dev/null
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar

View file

@ -0,0 +1,12 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
[ "$(whoami)" = "root" ]
{{ CMD }} --mode={{ MODE }} --variant=apt --format=directory {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
ftype=$(stat -c %F /tmp/debian-chroot.tar)
if [ "$ftype" != directory ]; then
echo "expected directory but got: $ftype" >&2
exit 1
fi
tar -C /tmp/debian-chroot.tar --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot.tar

12
tests/dist-using-codename Normal file
View file

@ -0,0 +1,12 @@
#!/bin/sh
#
# make sure that using codenames works https://bugs.debian.org/1003191
set -eu
export LC_ALL=C.UTF-8
/usr/lib/apt/apt-helper download-file "{{ MIRROR }}/dists/{{ DIST }}/Release" Release
codename=$(awk '/^Codename: / { print $2; }' Release)
rm Release
{{ CMD }} --mode={{ MODE }} --variant=apt $codename /tmp/debian-chroot {{ MIRROR }}
echo "deb {{ MIRROR }} $codename main" | diff -u - /tmp/debian-chroot/etc/apt/sources.list
rm -rf /tmp/debian-chroot

10
tests/dpkgopt Normal file
View file

@ -0,0 +1,10 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
echo no-pager > /tmp/config
{{ CMD }} --mode=root --variant=apt --dpkgopt="path-exclude=/usr/share/doc/*" --dpkgopt=/tmp/config --dpkgopt="path-include=/usr/share/doc/dpkg/copyright" {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
printf 'path-exclude=/usr/share/doc/*\nno-pager\npath-include=/usr/share/doc/dpkg/copyright\n' | cmp /tmp/debian-chroot/etc/dpkg/dpkg.cfg.d/99mmdebstrap -
rm /tmp/debian-chroot/etc/dpkg/dpkg.cfg.d/99mmdebstrap
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort > tar2.txt
{ grep -v '^./usr/share/doc/.' tar1.txt; echo ./usr/share/doc/dpkg/; echo ./usr/share/doc/dpkg/copyright; } | sort | diff -u - tar2.txt
rm -r /tmp/debian-chroot /tmp/config

View file

@ -0,0 +1,39 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
cat << SCRIPT > /tmp/checkeatmydata.sh
#!/bin/sh
set -exu
cat << EOF | diff - "\$1"/usr/bin/dpkg
#!/bin/sh
exec /usr/bin/eatmydata /usr/bin/dpkg.distrib "\\\$@"
EOF
[ -e "\$1"/usr/bin/eatmydata ]
SCRIPT
chmod +x /tmp/checkeatmydata.sh
# first four bytes: magic
elfheader="\\177ELF"
# fifth byte: bits
case "$(dpkg-architecture -qDEB_HOST_ARCH_BITS)" in
32) elfheader="$elfheader\\001";;
64) elfheader="$elfheader\\002";;
*) echo "bits not supported"; exit 1;;
esac
# sixth byte: endian
case "$(dpkg-architecture -qDEB_HOST_ARCH_ENDIAN)" in
little) elfheader="$elfheader\\001";;
big) elfheader="$elfheader\\002";;
*) echo "endian not supported"; exit 1;;
esac
# seventh and eigth byte: elf version (1) and abi (unset)
elfheader="$elfheader\\001\\000"
{{ CMD }} --mode=root --variant=apt \
--customize-hook=/tmp/checkeatmydata.sh \
--essential-hook=/tmp/checkeatmydata.sh \
--extract-hook='printf "'"$elfheader"'" | cmp --bytes=8 - "$1"/usr/bin/dpkg' \
--hook-dir=./hooks/eatmydata \
--customize-hook='printf "'"$elfheader"'" | cmp --bytes=8 - "$1"/usr/bin/dpkg' \
{{ DIST }} /tmp/debian-chroot {{ MIRROR }}
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm /tmp/checkeatmydata.sh
rm -r /tmp/debian-chroot

21
tests/essential-hook Normal file
View file

@ -0,0 +1,21 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
cat << 'SCRIPT' > /tmp/essential.sh
#!/bin/sh
echo tzdata tzdata/Zones/Europe select Berlin | chroot "$1" debconf-set-selections
SCRIPT
chmod +x /tmp/essential.sh
{{ CMD }} --mode=root --variant=apt --include=tzdata --essential-hook='echo tzdata tzdata/Areas select Europe | chroot "$1" debconf-set-selections' --essential-hook=/tmp/essential.sh {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
echo Europe/Berlin | cmp /tmp/debian-chroot/etc/timezone
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort \
| grep -v '^./etc/localtime' \
| grep -v '^./etc/timezone' \
| grep -v '^./usr/sbin/tzconfig' \
| grep -v '^./usr/share/doc/tzdata' \
| grep -v '^./usr/share/zoneinfo' \
| grep -v '^./var/lib/dpkg/info/tzdata.' \
| grep -v '^./var/lib/apt/extended_states$' \
| diff -u tar1.txt -
rm /tmp/essential.sh
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,9 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
mkdir /tmp/debian-chroot
mkdir /tmp/debian-chroot/lost+found
{{ CMD }} --mode=root --variant=apt {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
rmdir /tmp/debian-chroot/lost+found
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,7 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
mkdir /tmp/debian-chroot
{{ CMD }} --mode=root --variant=apt {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,10 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
touch /tmp/exists
ret=0
{{ CMD }} --mode={{ MODE }} --variant=apt {{ DIST }} /tmp/exists {{ MIRROR }} || ret=$?
if [ "$ret" = 0 ]; then
echo expected failure but got exit $ret >&2
exit 1
fi

View file

@ -0,0 +1,15 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
mkdir /tmp/debian-chroot
mkdir /tmp/debian-chroot/lost+found
touch /tmp/debian-chroot/lost+found/exists
ret=0
{{ CMD }} --mode={{ MODE }} --variant=apt {{ DIST }} /tmp/debian-chroot {{ MIRROR }} || ret=$?
rm /tmp/debian-chroot/lost+found/exists
rmdir /tmp/debian-chroot/lost+found
rmdir /tmp/debian-chroot
if [ "$ret" = 0 ]; then
echo expected failure but got exit $ret >&2
exit 1
fi

View file

@ -0,0 +1,15 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
mkdir /tmp/debian-chroot
mkdir /tmp/debian-chroot/lost+found
touch /tmp/debian-chroot/exists
ret=0
{{ CMD }} --mode={{ MODE }} --variant=apt {{ DIST }} /tmp/debian-chroot {{ MIRROR }} || ret=$?
rmdir /tmp/debian-chroot/lost+found
rm /tmp/debian-chroot/exists
rmdir /tmp/debian-chroot
if [ "$ret" = 0 ]; then
echo expected failure but got exit $ret >&2
exit 1
fi

View file

@ -0,0 +1,9 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
ret=0
{{ CMD }} --mode={{ MODE }} --variant=apt {{ DIST }} / {{ MIRROR }} || ret=$?
if [ "$ret" = 0 ]; then
echo expected failure but got exit $ret >&2
exit 1
fi

View file

@ -0,0 +1,9 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
ret=0
{{ CMD }} --mode={{ MODE }} --variant=apt {{ DIST }} /tmp/debian-chroot.tar.lz4 {{ MIRROR }} || ret=$?
if [ "$ret" = 0 ]; then
echo expected failure but got exit $ret >&2
exit 1
fi

View file

@ -0,0 +1,9 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
ret=0
{{ CMD }} --mode={{ MODE }} --variant=apt {{ DIST }} /tmp/quoted\"path {{ MIRROR }} || ret=$?
if [ "$ret" = 0 ]; then
echo expected failure but got exit $ret >&2
exit 1
fi

View file

@ -0,0 +1,17 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
adduser --gecos user --disabled-password user
sysctl -w kernel.unprivileged_userns_clone=1
rm /etc/subuid
ret=0
runuser -u user -- {{ CMD }} --mode=unshare --variant=apt {{ DIST }} /tmp/debian-chroot {{ MIRROR }} || ret=$?
if [ "$ret" = 0 ]; then
echo expected failure but got exit $ret >&2
exit 1
fi
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,18 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
adduser --gecos user --disabled-password user
sysctl -w kernel.unprivileged_userns_clone=1
awk -F: '$1!="user"' /etc/subuid > /etc/subuid.tmp
mv /etc/subuid.tmp /etc/subuid
ret=0
runuser -u user -- {{ CMD }} --mode=unshare --variant=apt {{ DIST }} /tmp/debian-chroot {{ MIRROR }} || ret=$?
if [ "$ret" = 0 ]; then
echo expected failure but got exit $ret >&2
exit 1
fi
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,10 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
ret=0
{{ CMD }} --mode=root --variant=apt --customize-hook='chroot "$1" sh -c "exit 1"' {{ DIST }} /tmp/debian-chroot {{ MIRROR }} || ret=$?
rm -r /tmp/debian-chroot
if [ "$ret" = 0 ]; then
echo expected failure but got exit $ret >&2
exit 1
fi

10
tests/file-mirror Normal file
View file

@ -0,0 +1,10 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test requires the cache directory to be mounted on /mnt and should only be run inside a container" >&2
exit 1
fi
{{ CMD }} --mode={{ MODE }} --variant=apt --setup-hook='mkdir -p "$1"/mnt/cache/debian; mount -o ro,bind /mnt/cache/debian "$1"/mnt/cache/debian' --customize-hook='umount "$1"/mnt/cache/debian; rmdir "$1"/mnt/cache/debian "$1"/mnt/cache' {{ DIST }} /tmp/debian-chroot.tar "deb file:///mnt/cache/debian {{ DIST }} main"
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar

View file

@ -0,0 +1,10 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test requires the cache directory to be mounted on /mnt and should only be run inside a container" >&2
exit 1
fi
{{ CMD }} --mode={{ MODE }} --variant=apt --hook-dir=./hooks/file-mirror-automount --customize-hook='rmdir "$1"/mnt/cache/debian/ "$1"/mnt/cache' {{ DIST }} /tmp/debian-chroot.tar "deb file:///mnt/cache/debian {{ DIST }} main"
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar

6
tests/help Normal file
View file

@ -0,0 +1,6 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
# we redirect to /dev/null instead of using --quiet to not cause a broken pipe
# when grep exits before mmdebstrap was able to write all its output
{{ CMD }} --help | grep --fixed-strings 'mmdebstrap [OPTION...] [SUITE [TARGET [MIRROR...]]]' >/dev/null

49
tests/hook-directory Normal file
View file

@ -0,0 +1,49 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
for h in hookA hookB; do
mkdir /tmp/$h
for s in setup extract essential customize; do
cat << SCRIPT > /tmp/$h/${s}00.sh
#!/bin/sh
echo $h/${s}00 >> "\$1/$s"
SCRIPT
chmod +x /tmp/$h/${s}00.sh
cat << SCRIPT > /tmp/$h/${s}01.sh
echo $h/${s}01 >> "\$1/$s"
SCRIPT
chmod +x /tmp/$h/${s}01.sh
done
done
{{ CMD }} --mode=root --variant=apt \
--setup-hook='echo cliA/setup >> "$1"/setup' \
--extract-hook='echo cliA/extract >> "$1"/extract' \
--essential-hook='echo cliA/essential >> "$1"/essential' \
--customize-hook='echo cliA/customize >> "$1"/customize' \
--hook-dir=/tmp/hookA \
--setup-hook='echo cliB/setup >> "$1"/setup' \
--extract-hook='echo cliB/extract >> "$1"/extract' \
--essential-hook='echo cliB/essential >> "$1"/essential' \
--customize-hook='echo cliB/customize >> "$1"/customize' \
--hook-dir=/tmp/hookB \
--setup-hook='echo cliC/setup >> "$1"/setup' \
--extract-hook='echo cliC/extract >> "$1"/extract' \
--essential-hook='echo cliC/essential >> "$1"/essential' \
--customize-hook='echo cliC/customize >> "$1"/customize' \
{{ DIST }} /tmp/debian-chroot {{ MIRROR }}
printf "cliA/setup\nhookA/setup00\nhookA/setup01\ncliB/setup\nhookB/setup00\nhookB/setup01\ncliC/setup\n" | diff -u - /tmp/debian-chroot/setup
printf "cliA/extract\nhookA/extract00\nhookA/extract01\ncliB/extract\nhookB/extract00\nhookB/extract01\ncliC/extract\n" | diff -u - /tmp/debian-chroot/extract
printf "cliA/essential\nhookA/essential00\nhookA/essential01\ncliB/essential\nhookB/essential00\nhookB/essential01\ncliC/essential\n" | diff -u - /tmp/debian-chroot/essential
printf "cliA/customize\nhookA/customize00\nhookA/customize01\ncliB/customize\nhookB/customize00\nhookB/customize01\ncliC/customize\n" | diff -u - /tmp/debian-chroot/customize
for s in setup extract essential customize; do
rm /tmp/debian-chroot/$s
done
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
for h in hookA hookB; do
for s in setup extract essential customize; do
rm /tmp/$h/${s}00.sh
rm /tmp/$h/${s}01.sh
done
rmdir /tmp/$h
done
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,38 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
# remove qemu just to be sure
apt-get remove --yes qemu-user-static binfmt-support qemu-user
{{ CMD }} --mode={{ MODE }} --variant=apt --architectures=i386 {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
# we ignore differences between architectures by ignoring some files
# and renaming others
{ tar -tf /tmp/debian-chroot.tar \
| grep -v '^\./usr/bin/i386$' \
| grep -v '^\./lib/ld-linux\.so\.2$' \
| grep -v '^\./lib/i386-linux-gnu/ld-linux\.so\.2$' \
| grep -v '^\./usr/lib/gcc/i686-linux-gnu/$' \
| grep -v '^\./usr/lib/gcc/i686-linux-gnu/[0-9]\+/$' \
| grep -v '^\./usr/share/man/man8/i386\.8\.gz$' \
| grep -v '^\./usr/share/doc/[^/]\+/changelog\(\.Debian\)\?\.i386\.gz$' \
| sed 's/i386-linux-gnu/x86_64-linux-gnu/' \
| sed 's/i386/amd64/';
} | sort > tar2.txt
{ cat tar1.txt \
| grep -v '^\./usr/bin/i386$' \
| grep -v '^\./usr/bin/x86_64$' \
| grep -v '^\./lib64/$' \
| grep -v '^\./lib64/ld-linux-x86-64\.so\.2$' \
| grep -v '^\./usr/lib/gcc/x86_64-linux-gnu/$' \
| grep -v '^\./usr/lib/gcc/x86_64-linux-gnu/[0-9]\+/$' \
| grep -v '^\./lib/x86_64-linux-gnu/ld-linux-x86-64\.so\.2$' \
| grep -v '^\./lib/x86_64-linux-gnu/libmvec-2\.[0-9]\+\.so$' \
| grep -v '^\./lib/x86_64-linux-gnu/libmvec\.so\.1$' \
| grep -v '^\./usr/share/doc/[^/]\+/changelog\(\.Debian\)\?\.amd64\.gz$' \
| grep -v '^\./usr/share/man/man8/i386\.8\.gz$' \
| grep -v '^\./usr/share/man/man8/x86_64\.8\.gz$';
} | sort | diff -u - tar2.txt
rm /tmp/debian-chroot.tar

12
tests/include Normal file
View file

@ -0,0 +1,12 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
{{ CMD }} --mode=root --variant=apt --include=doc-debian {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
rm /tmp/debian-chroot/usr/share/doc-base/debian-*
rm -r /tmp/debian-chroot/usr/share/doc/debian
rm -r /tmp/debian-chroot/usr/share/doc/doc-debian
rm /tmp/debian-chroot/var/lib/apt/extended_states
rm /tmp/debian-chroot/var/lib/dpkg/info/doc-debian.list
rm /tmp/debian-chroot/var/lib/dpkg/info/doc-debian.md5sums
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,29 @@
#!/bin/sh
#
# to test foreign architecture package installation we choose a package which
# - is not part of the native installation set
# - does not have any dependencies
# - installs only few files
# - doesn't change its name regularly (like gcc-*-base)
set -eu
export LC_ALL=C.UTF-8
{{ CMD }} --mode=root --variant=apt --architectures=amd64,arm64 --include=libmagic-mgc:arm64 {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
{ echo "amd64"; echo "arm64"; } | cmp /tmp/debian-chroot/var/lib/dpkg/arch -
rm /tmp/debian-chroot/var/lib/dpkg/arch
rm /tmp/debian-chroot/var/lib/apt/extended_states
rm /tmp/debian-chroot/var/lib/dpkg/info/libmagic-mgc.list
rm /tmp/debian-chroot/var/lib/dpkg/info/libmagic-mgc.md5sums
rm /tmp/debian-chroot/usr/lib/file/magic.mgc
rm /tmp/debian-chroot/usr/share/doc/libmagic-mgc/README.Debian
rm /tmp/debian-chroot/usr/share/doc/libmagic-mgc/changelog.Debian.gz
rm /tmp/debian-chroot/usr/share/doc/libmagic-mgc/changelog.gz
rm /tmp/debian-chroot/usr/share/doc/libmagic-mgc/copyright
rm /tmp/debian-chroot/usr/share/file/magic.mgc
rm /tmp/debian-chroot/usr/share/misc/magic.mgc
rmdir /tmp/debian-chroot/usr/share/doc/libmagic-mgc/
rmdir /tmp/debian-chroot/usr/share/file/magic/
rmdir /tmp/debian-chroot/usr/share/file/
rmdir /tmp/debian-chroot/usr/lib/file/
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,22 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
{{ CMD }} --mode=root --variant=apt --architectures=amd64 --architectures=arm64 --include=libmagic-mgc:arm64 {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
{ echo "amd64"; echo "arm64"; } | cmp /tmp/debian-chroot/var/lib/dpkg/arch -
rm /tmp/debian-chroot/var/lib/dpkg/arch
rm /tmp/debian-chroot/var/lib/apt/extended_states
rm /tmp/debian-chroot/var/lib/dpkg/info/libmagic-mgc.list
rm /tmp/debian-chroot/var/lib/dpkg/info/libmagic-mgc.md5sums
rm /tmp/debian-chroot/usr/lib/file/magic.mgc
rm /tmp/debian-chroot/usr/share/doc/libmagic-mgc/README.Debian
rm /tmp/debian-chroot/usr/share/doc/libmagic-mgc/changelog.Debian.gz
rm /tmp/debian-chroot/usr/share/doc/libmagic-mgc/changelog.gz
rm /tmp/debian-chroot/usr/share/doc/libmagic-mgc/copyright
rm /tmp/debian-chroot/usr/share/file/magic.mgc
rm /tmp/debian-chroot/usr/share/misc/magic.mgc
rmdir /tmp/debian-chroot/usr/share/doc/libmagic-mgc/
rmdir /tmp/debian-chroot/usr/share/file/magic/
rmdir /tmp/debian-chroot/usr/share/file/
rmdir /tmp/debian-chroot/usr/lib/file/
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,10 @@
#!/bin/sh
#
# This checks for https://bugs.debian.org/976166
# Since $DEFAULT_DIST varies, we hardcode stable and unstable.
set -eu
export LC_ALL=C.UTF-8
{{ CMD }} --mode=root --variant=minbase --include=doc-debian unstable /tmp/debian-chroot "deb {{ MIRROR }} unstable main" "deb {{ MIRROR }} stable main"
chroot /tmp/debian-chroot dpkg-query --show doc-debian
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,33 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
pkgs=base-files,base-passwd,busybox,debianutils,dpkg,libc-bin,mawk,tar
# busybox --install -s will install symbolic links into the rootfs, leaving
# existing files untouched. It has to run after extraction (otherwise there is
# no busybox binary) and before first configuration
{{ CMD }} --mode=root --variant=custom \
--include=$pkgs \
--setup-hook='mkdir -p "$1/bin"' \
--setup-hook='echo root:x:0:0:root:/root:/bin/sh > "$1/etc/passwd"' \
--setup-hook='printf "root:x:0:\nmail:x:8:\nutmp:x:43:\n" > "$1/etc/group"' \
--extract-hook='chroot "$1" busybox --install -s' \
{{ DIST }} /tmp/debian-chroot {{ MIRROR }}
echo "$pkgs" | tr ',' '\n' > /tmp/expected
chroot /tmp/debian-chroot dpkg-query -f '${binary:Package}\n' -W \
| comm -12 - /tmp/expected \
| diff -u - /tmp/expected
rm /tmp/expected
for cmd in echo cat sed grep; do
test -L /tmp/debian-chroot/bin/$cmd
test "$(readlink /tmp/debian-chroot/bin/$cmd)" = "/bin/busybox"
done
for cmd in sort; do
test -L /tmp/debian-chroot/usr/bin/$cmd
test "$(readlink /tmp/debian-chroot/usr/bin/$cmd)" = "/bin/busybox"
done
chroot /tmp/debian-chroot echo foobar \
| chroot /tmp/debian-chroot cat \
| chroot /tmp/debian-chroot sort \
| chroot /tmp/debian-chroot sed 's/foobar/blubber/' \
| chroot /tmp/debian-chroot grep blubber >/dev/null
rm -r /tmp/debian-chroot

46
tests/install-doc-debian Normal file
View file

@ -0,0 +1,46 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ "$(id -u)" -eq 0 ] && ! id -u user > /dev/null 2>&1; then
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
adduser --gecos user --disabled-password user
fi
prefix=
[ "$(id -u)" -eq 0 ] && prefix="runuser -u user --"
$prefix {{ CMD }} --mode=chrootless --variant=custom --include=doc-debian {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
tar -C /tmp/debian-chroot --owner=0 --group=0 --numeric-owner --sort=name --clamp-mtime --mtime=$(date --utc --date=@{{ SOURCE_DATE_EPOCH }} --iso-8601=seconds) -cf /tmp/debian-chroot.tar .
tar tvf /tmp/debian-chroot.tar > doc-debian.tar.list
rm /tmp/debian-chroot.tar
# delete contents of doc-debian
rm /tmp/debian-chroot/usr/share/doc-base/debian-*
rm -r /tmp/debian-chroot/usr/share/doc/debian
rm -r /tmp/debian-chroot/usr/share/doc/doc-debian
# delete real files
rm /tmp/debian-chroot/etc/apt/sources.list
rm /tmp/debian-chroot/etc/fstab
rm /tmp/debian-chroot/etc/hostname
rm /tmp/debian-chroot/etc/resolv.conf
rm /tmp/debian-chroot/var/lib/dpkg/status
rm /tmp/debian-chroot/var/cache/apt/archives/lock
rm /tmp/debian-chroot/var/lib/dpkg/lock
rm /tmp/debian-chroot/var/lib/dpkg/lock-frontend
rm /tmp/debian-chroot/var/lib/apt/lists/lock
## delete merged usr symlinks
#rm /tmp/debian-chroot/libx32
#rm /tmp/debian-chroot/lib64
#rm /tmp/debian-chroot/lib32
#rm /tmp/debian-chroot/sbin
#rm /tmp/debian-chroot/bin
#rm /tmp/debian-chroot/lib
# in chrootless mode, there is more to remove
rm /tmp/debian-chroot/var/lib/dpkg/triggers/Lock
rm /tmp/debian-chroot/var/lib/dpkg/triggers/Unincorp
rm /tmp/debian-chroot/var/lib/dpkg/status-old
rm /tmp/debian-chroot/var/lib/dpkg/info/format
rm /tmp/debian-chroot/var/lib/dpkg/info/doc-debian.md5sums
rm /tmp/debian-chroot/var/lib/dpkg/info/doc-debian.list
# the rest should be empty directories that we can rmdir recursively
find /tmp/debian-chroot -depth -print0 | xargs -0 rmdir

View file

@ -0,0 +1,16 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
export SOURCE_DATE_EPOCH={{ SOURCE_DATE_EPOCH }}
if [ "$(id -u)" -eq 0 ] && ! id -u user > /dev/null 2>&1; then
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
adduser --gecos user --disabled-password user
fi
prefix=
[ "$(id -u)" -eq 0 ] && prefix="runuser -u user --"
$prefix {{ CMD }} --mode=chrootless --variant=custom --include=doc-debian {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
tar tvf /tmp/debian-chroot.tar | grep -v ' ./dev' | diff -u doc-debian.tar.list -
rm /tmp/debian-chroot.tar

View file

@ -0,0 +1,49 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
export SOURCE_DATE_EPOCH={{ SOURCE_DATE_EPOCH }}
if [ "$(id -u)" -eq 0 ] && ! id -u user > /dev/null 2>&1; then
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
adduser --gecos user --disabled-password user
fi
prefix=
[ "$(id -u)" -eq 0 ] && prefix="runuser -u user --"
$prefix {{ CMD }} --mode=chrootless --skip=cleanup/tmp --variant=custom --include=doc-debian --setup-hook='touch "$1/tmp/setup"' --customize-hook='touch "$1/tmp/customize"' {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
rm /tmp/debian-chroot/tmp/setup
rm /tmp/debian-chroot/tmp/customize
tar -C /tmp/debian-chroot --owner=0 --group=0 --numeric-owner --sort=name --clamp-mtime --mtime=$(date --utc --date=@{{ SOURCE_DATE_EPOCH }} --iso-8601=seconds) -cf /tmp/debian-chroot.tar .
tar tvf /tmp/debian-chroot.tar | grep -v ' ./dev' | diff -u doc-debian.tar.list -
rm /tmp/debian-chroot.tar
# delete contents of doc-debian
rm /tmp/debian-chroot/usr/share/doc-base/debian-*
rm -r /tmp/debian-chroot/usr/share/doc/debian
rm -r /tmp/debian-chroot/usr/share/doc/doc-debian
# delete real files
rm /tmp/debian-chroot/etc/apt/sources.list
rm /tmp/debian-chroot/etc/fstab
rm /tmp/debian-chroot/etc/hostname
rm /tmp/debian-chroot/etc/resolv.conf
rm /tmp/debian-chroot/var/lib/dpkg/status
rm /tmp/debian-chroot/var/cache/apt/archives/lock
rm /tmp/debian-chroot/var/lib/dpkg/lock
rm /tmp/debian-chroot/var/lib/dpkg/lock-frontend
rm /tmp/debian-chroot/var/lib/apt/lists/lock
## delete merged usr symlinks
#rm /tmp/debian-chroot/libx32
#rm /tmp/debian-chroot/lib64
#rm /tmp/debian-chroot/lib32
#rm /tmp/debian-chroot/sbin
#rm /tmp/debian-chroot/bin
#rm /tmp/debian-chroot/lib
# in chrootless mode, there is more to remove
rm /tmp/debian-chroot/var/lib/dpkg/triggers/Lock
rm /tmp/debian-chroot/var/lib/dpkg/triggers/Unincorp
rm /tmp/debian-chroot/var/lib/dpkg/status-old
rm /tmp/debian-chroot/var/lib/dpkg/info/format
rm /tmp/debian-chroot/var/lib/dpkg/info/doc-debian.md5sums
rm /tmp/debian-chroot/var/lib/dpkg/info/doc-debian.list
# the rest should be empty directories that we can rmdir recursively
find /tmp/debian-chroot -depth -print0 | xargs -0 rmdir

View file

@ -0,0 +1,43 @@
#!/bin/sh
#
# regularly check whether more packages work with chrootless:
# for p in $(grep-aptavail -F Essential yes -s Package -n | sort -u); do ./mmdebstrap -- mode=chrootless --variant=custom --include=bsdutils,coreutils,debianutils,diffutils,dpkg, findutils,grep,gzip,hostname,init-system-helpers,ncurses-base,ncurses-bin,perl-base,sed, sysvinit-utils,tar,$p unstable /dev/null; done
#
# see https://bugs.debian.org/cgi-bin/pkgreport.cgi?users=debian-dpkg@lists.debian.org;tag=dpkg- root-support
#
# base-files: #824594
# base-passwd: debconf
# bash: depends base-files
# bsdutils: ok
# coreutils: ok
# dash: debconf
# debianutils: ok
# diffutils: ok
# dpkg: ok
# findutils: ok
# grep: ok
# gzip: ok
# hostname: ok
# init-system-helpers: ok
# libc-bin: #983412
# login: debconf
# ncurses-base: ok
# ncurses-bin: ok
# perl-base: ok
# sed: ok
# sysvinit-utils: ok
# tar: ok
# util-linux: debconf
set -eu
export LC_ALL=C.UTF-8
if [ "$(id -u)" -eq 0 ] && ! id -u user > /dev/null 2>&1; then
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
adduser --gecos user --disabled-password user
fi
prefix=
[ "$(id -u)" -eq 0 ] && prefix="runuser -u user --"
$prefix {{ CMD }} --mode=chrootless --variant=custom --include=bsdutils,coreutils,debianutils,diffutils,dpkg,findutils,grep,gzip,hostname,init-system-helpers,ncurses-base,ncurses-bin,perl-base,sed,sysvinit-utils,tar {{ DIST }} /dev/null {{ MIRROR }}

View file

@ -0,0 +1,48 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ "$(id -u)" -eq 0 ] && ! id -u user > /dev/null 2>&1; then
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
adduser --gecos user --disabled-password user
fi
prefix=
[ "$(id -u)" -eq 0 ] && prefix="runuser -u user --"
$prefix {{ CMD }} --mode=chrootless --variant=custom --architectures=arm64 --include=libmagic-mgc {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
# delete contents of libmagic-mgc
rm /tmp/debian-chroot/usr/lib/file/magic.mgc
rm /tmp/debian-chroot/usr/share/doc/libmagic-mgc/README.Debian
rm /tmp/debian-chroot/usr/share/doc/libmagic-mgc/changelog.Debian.gz
rm /tmp/debian-chroot/usr/share/doc/libmagic-mgc/changelog.gz
rm /tmp/debian-chroot/usr/share/doc/libmagic-mgc/copyright
rm /tmp/debian-chroot/usr/share/file/magic.mgc
rm /tmp/debian-chroot/usr/share/misc/magic.mgc
# delete real files
rm /tmp/debian-chroot/etc/apt/sources.list
rm /tmp/debian-chroot/etc/fstab
rm /tmp/debian-chroot/etc/hostname
rm /tmp/debian-chroot/etc/resolv.conf
rm /tmp/debian-chroot/var/lib/dpkg/status
rm /tmp/debian-chroot/var/cache/apt/archives/lock
rm /tmp/debian-chroot/var/lib/dpkg/lock
rm /tmp/debian-chroot/var/lib/dpkg/lock-frontend
rm /tmp/debian-chroot/var/lib/apt/lists/lock
## delete merged usr symlinks
#rm /tmp/debian-chroot/libx32
#rm /tmp/debian-chroot/lib64
#rm /tmp/debian-chroot/lib32
#rm /tmp/debian-chroot/sbin
#rm /tmp/debian-chroot/bin
#rm /tmp/debian-chroot/lib
# in chrootless mode, there is more to remove
rm /tmp/debian-chroot/var/lib/dpkg/arch
rm /tmp/debian-chroot/var/lib/dpkg/triggers/Lock
rm /tmp/debian-chroot/var/lib/dpkg/triggers/Unincorp
rm /tmp/debian-chroot/var/lib/dpkg/status-old
rm /tmp/debian-chroot/var/lib/dpkg/info/format
rm /tmp/debian-chroot/var/lib/dpkg/info/libmagic-mgc.md5sums
rm /tmp/debian-chroot/var/lib/dpkg/info/libmagic-mgc.list
# the rest should be empty directories that we can rmdir recursively
find /tmp/debian-chroot -depth -print0 | xargs -0 rmdir

10
tests/invalid-mirror Normal file
View file

@ -0,0 +1,10 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
ret=0
{{ CMD }} --mode={{ MODE }} --variant=apt {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}/invalid || ret=$?
rm /tmp/debian-chroot.tar
if [ "$ret" = 0 ]; then
echo expected failure but got exit $ret >&2
exit 1
fi

13
tests/keyring Normal file
View file

@ -0,0 +1,13 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
rm /etc/apt/trusted.gpg.d/*.gpg
{{ CMD }} --mode=root --variant=apt --keyring=/usr/share/keyrings/debian-archive-keyring.gpg --keyring=/usr/share/keyrings/ {{ DIST }} /tmp/debian-chroot "deb {{ MIRROR }} {{ DIST }} main"
# make sure that no [signedby=...] managed to make it into the sources.list
echo "deb {{ MIRROR }} {{ DIST }} main" | cmp /tmp/debian-chroot/etc/apt/sources.list -
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot

17
tests/keyring-overwrites Normal file
View file

@ -0,0 +1,17 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
mkdir -p /tmp/emptydir
touch /tmp/emptyfile
# this overwrites the apt keyring options and should fail
ret=0
{{ CMD }} --mode=root --variant=apt --keyring=/tmp/emptydir --keyring=/tmp/emptyfile {{ DIST }} /tmp/debian-chroot "deb {{ MIRROR }} {{ DIST }} main" || ret=$?
# make sure that no [signedby=...] managed to make it into the sources.list
echo "deb {{ MIRROR }} {{ DIST }} main" | cmp /tmp/debian-chroot/etc/apt/sources.list -
rm -r /tmp/debian-chroot
rmdir /tmp/emptydir
rm /tmp/emptyfile
if [ "$ret" = 0 ]; then
echo expected failure but got exit $ret >&2
exit 1
fi

20
tests/logfile Normal file
View file

@ -0,0 +1,20 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
# we check the full log to also prevent debug printfs to accidentally make it into a commit
{{ CMD }} --mode=root --variant=apt --logfile=/tmp/log {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
# omit the last line which should contain the runtime
head --lines=-1 /tmp/log > /tmp/trimmed
cat << LOG | diff -u - /tmp/trimmed
I: chroot architecture {{ HOSTARCH }} is equal to the host's architecture
I: automatically chosen format: directory
I: running apt-get update...
I: downloading packages with apt...
I: extracting archives...
I: installing essential packages...
I: cleaning package lists and apt cache...
LOG
tail --lines=1 /tmp/log | grep '^I: success in .* seconds$'
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot
rm /tmp/log /tmp/trimmed

7
tests/man Normal file
View file

@ -0,0 +1,7 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
# we redirect to /dev/null instead of using --quiet to not cause a broken pipe
# when grep exits before mmdebstrap was able to write all its output
{{ CMD }} --man | grep --fixed-strings 'mmdebstrap [OPTION...] [*SUITE* [*TARGET* [*MIRROR*...]]]' >/dev/null

View file

@ -0,0 +1,43 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
{{ CMD }} --mode=root --variant=apt \
--setup-hook=./hooks/merged-usr/setup00.sh \
--customize-hook='[ -L "$1"/bin -a -L "$1"/sbin -a -L "$1"/lib ]' \
{{ DIST }} /tmp/debian-chroot {{ MIRROR }}
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort > tar2.txt
{
sed -e 's/^\.\/bin\//.\/usr\/bin\//;s/^\.\/lib\//.\/usr\/lib\//;s/^\.\/sbin\//.\/usr\/sbin\//;' tar1.txt | {
case {{ HOSTARCH }} in
amd64) sed -e 's/^\.\/lib32\//.\/usr\/lib32\//;s/^\.\/lib64\//.\/usr\/lib64\//;s/^\.\/libx32\//.\/usr\/libx32\//;';;
ppc64el) sed -e 's/^\.\/lib64\//.\/usr\/lib64\//;';;
*) cat;;
esac
};
echo ./bin;
echo ./lib;
echo ./sbin;
case {{ HOSTARCH }} in
amd64)
echo ./lib32;
echo ./lib64;
echo ./libx32;
echo ./usr/lib32/;
echo ./usr/libx32/;
;;
i386)
echo ./lib64;
echo ./libx32;
echo ./usr/lib64/;
echo ./usr/libx32/;
;;
ppc64el)
echo ./lib64;
;;
s390x)
echo ./lib32;
echo ./usr/lib32/;
;;
esac
} | sort -u | diff -u - tar2.txt
rm -r /tmp/debian-chroot

6
tests/mirror-is-deb Normal file
View file

@ -0,0 +1,6 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
{{ CMD }} --mode={{ MODE }} --variant=apt {{ DIST }} /tmp/debian-chroot.tar "deb {{ MIRROR }} {{ DIST }} main"
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar

View file

@ -0,0 +1,9 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
echo "deb {{ MIRROR }} {{ DIST }} main" > /tmp/sources.list
{{ CMD }} --mode={{ MODE }} --variant=apt {{ DIST }} /tmp/debian-chroot.tar /tmp/sources.list
tar -tf /tmp/debian-chroot.tar \
| sed 's#^./etc/apt/sources.list.d/0000sources.list$#./etc/apt/sources.list#' \
| sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar /tmp/sources.list

6
tests/mirror-is-stdin Normal file
View file

@ -0,0 +1,6 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
echo "deb {{ MIRROR }} {{ DIST }} main" | {{ CMD }} --mode={{ MODE }} --variant=apt {{ DIST }} /tmp/debian-chroot.tar -
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar

View file

@ -0,0 +1,10 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
adduser --gecos user --disabled-password user
sysctl -w kernel.unprivileged_userns_clone=1
runuser -u user -- {{ CMD }} --mode=unshare --variant=custom --include=dpkg,dash,diffutils,coreutils,libc-bin,sed {{ DIST }} /dev/null {{ MIRROR }}

View file

@ -0,0 +1,13 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
rm /dev/console
adduser --gecos user --disabled-password user
sysctl -w kernel.unprivileged_userns_clone=1
runuser -u user -- {{ CMD }} --mode=unshare --variant=apt {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar

13
tests/mount-is-missing Normal file
View file

@ -0,0 +1,13 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
for p in /bin /usr/bin /sbin /usr/sbin; do
rm -f "$p/mount"
done
{{ CMD }} --mode=root --variant=apt {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar

23
tests/multiple-include Normal file
View file

@ -0,0 +1,23 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
{{ CMD }} --mode=root --variant=apt --include=doc-debian --include=tzdata {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
rm /tmp/debian-chroot/usr/share/doc-base/debian-*
rm -r /tmp/debian-chroot/usr/share/doc/debian
rm -r /tmp/debian-chroot/usr/share/doc/doc-debian
rm /tmp/debian-chroot/etc/localtime
rm /tmp/debian-chroot/etc/timezone
rm /tmp/debian-chroot/usr/sbin/tzconfig
rm -r /tmp/debian-chroot/usr/share/doc/tzdata
rm -r /tmp/debian-chroot/usr/share/zoneinfo
rm /tmp/debian-chroot/var/lib/apt/extended_states
rm /tmp/debian-chroot/var/lib/dpkg/info/doc-debian.list
rm /tmp/debian-chroot/var/lib/dpkg/info/doc-debian.md5sums
rm /tmp/debian-chroot/var/lib/dpkg/info/tzdata.list
rm /tmp/debian-chroot/var/lib/dpkg/info/tzdata.md5sums
rm /tmp/debian-chroot/var/lib/dpkg/info/tzdata.config
rm /tmp/debian-chroot/var/lib/dpkg/info/tzdata.postinst
rm /tmp/debian-chroot/var/lib/dpkg/info/tzdata.postrm
rm /tmp/debian-chroot/var/lib/dpkg/info/tzdata.templates
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,9 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
{{ CMD }} --mode={{ MODE }} --variant=essential --include=apt \
--essential-hook='APT_CONFIG=$MMDEBSTRAP_APT_CONFIG apt-get update' \
--essential-hook='APT_CONFIG=$MMDEBSTRAP_APT_CONFIG apt-get --yes install -oDPkg::Chroot-Directory="$1" apt' \
{{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
tar -tf /tmp/debian-chroot.tar | sort | grep -v ./var/lib/apt/extended_states | diff -u tar1.txt -
rm /tmp/debian-chroot.tar

View file

@ -0,0 +1,14 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
cat << HOSTS >> /etc/hosts
127.0.0.1 deb.debian.org
127.0.0.1 security.debian.org
HOSTS
{{ CMD }} --mode={{ MODE }} --variant=apt {{ DIST }} > /tmp/debian-chroot.tar
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar

View file

@ -0,0 +1,99 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
for f in /etc/resolv.conf /etc/hostname; do
# preserve original content
cat "$f" > "$f.bak"
# in case $f is a symlink, we replace it by a real file
if [ -L "$f" ]; then
rm "$f"
cp "$f.bak" "$f"
fi
chmod 644 "$f"
[ "$(stat --format=%A "$f")" = "-rw-r--r--" ]
done
{{ CMD }} --variant=custom --mode={{ MODE }} {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
for f in /etc/resolv.conf /etc/hostname; do
[ "$(stat --format=%A "/tmp/debian-chroot/$f")" = "-rw-r--r--" ]
done
rm /tmp/debian-chroot/dev/console
rm /tmp/debian-chroot/dev/fd
rm /tmp/debian-chroot/dev/full
rm /tmp/debian-chroot/dev/null
rm /tmp/debian-chroot/dev/ptmx
rm /tmp/debian-chroot/dev/random
rm /tmp/debian-chroot/dev/stderr
rm /tmp/debian-chroot/dev/stdin
rm /tmp/debian-chroot/dev/stdout
rm /tmp/debian-chroot/dev/tty
rm /tmp/debian-chroot/dev/urandom
rm /tmp/debian-chroot/dev/zero
rm /tmp/debian-chroot/etc/apt/sources.list
rm /tmp/debian-chroot/etc/fstab
rm /tmp/debian-chroot/etc/hostname
rm /tmp/debian-chroot/etc/resolv.conf
rm /tmp/debian-chroot/var/lib/apt/lists/lock
rm /tmp/debian-chroot/var/lib/dpkg/status
# the rest should be empty directories that we can rmdir recursively
find /tmp/debian-chroot -depth -print0 | xargs -0 rmdir
for f in /etc/resolv.conf /etc/hostname; do
chmod 755 "$f"
[ "$(stat --format=%A "$f")" = "-rwxr-xr-x" ]
done
{{ CMD }} --variant=custom --mode={{ MODE }} {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
for f in /etc/resolv.conf /etc/hostname; do
[ "$(stat --format=%A "/tmp/debian-chroot/$f")" = "-rwxr-xr-x" ]
done
rm /tmp/debian-chroot/dev/console
rm /tmp/debian-chroot/dev/fd
rm /tmp/debian-chroot/dev/full
rm /tmp/debian-chroot/dev/null
rm /tmp/debian-chroot/dev/ptmx
rm /tmp/debian-chroot/dev/random
rm /tmp/debian-chroot/dev/stderr
rm /tmp/debian-chroot/dev/stdin
rm /tmp/debian-chroot/dev/stdout
rm /tmp/debian-chroot/dev/tty
rm /tmp/debian-chroot/dev/urandom
rm /tmp/debian-chroot/dev/zero
rm /tmp/debian-chroot/etc/apt/sources.list
rm /tmp/debian-chroot/etc/fstab
rm /tmp/debian-chroot/etc/hostname
rm /tmp/debian-chroot/etc/resolv.conf
rm /tmp/debian-chroot/var/lib/apt/lists/lock
rm /tmp/debian-chroot/var/lib/dpkg/status
# the rest should be empty directories that we can rmdir recursively
find /tmp/debian-chroot -depth -print0 | xargs -0 rmdir
for f in /etc/resolv.conf /etc/hostname; do
rm "$f"
ln -s "$f.bak" "$f"
[ "$(stat --format=%A "$f")" = "lrwxrwxrwx" ]
done
{{ CMD }} --variant=custom --mode={{ MODE }} {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
for f in /etc/resolv.conf /etc/hostname; do
[ "$(stat --format=%A "/tmp/debian-chroot/$f")" = "-rw-r--r--" ]
done
rm /tmp/debian-chroot/dev/console
rm /tmp/debian-chroot/dev/fd
rm /tmp/debian-chroot/dev/full
rm /tmp/debian-chroot/dev/null
rm /tmp/debian-chroot/dev/ptmx
rm /tmp/debian-chroot/dev/random
rm /tmp/debian-chroot/dev/stderr
rm /tmp/debian-chroot/dev/stdin
rm /tmp/debian-chroot/dev/stdout
rm /tmp/debian-chroot/dev/tty
rm /tmp/debian-chroot/dev/urandom
rm /tmp/debian-chroot/dev/zero
rm /tmp/debian-chroot/etc/apt/sources.list
rm /tmp/debian-chroot/etc/fstab
rm /tmp/debian-chroot/etc/hostname
rm /tmp/debian-chroot/etc/resolv.conf
rm /tmp/debian-chroot/var/lib/apt/lists/lock
rm /tmp/debian-chroot/var/lib/dpkg/status
# the rest should be empty directories that we can rmdir recursively
find /tmp/debian-chroot -depth -print0 | xargs -0 rmdir

View file

@ -0,0 +1,6 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
script -qfc "{{ CMD }} --mode={{ MODE }} --variant=apt {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}" /dev/null
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar

6
tests/quiet Normal file
View file

@ -0,0 +1,6 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
{{ CMD }} --mode=root --variant=apt --quiet {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,6 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
echo "deb {{ MIRROR }} {{ DIST }} main" | {{ CMD }} --mode={{ MODE }} --variant=apt > /tmp/debian-chroot.tar
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar

View file

@ -0,0 +1,6 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
{{ CMD }} --mode={{ MODE }} --variant=apt --customize-hook='rm "$1/usr/sbin/policy-rc.d"; rm "$1/sbin/start-stop-daemon"' {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar

View file

@ -0,0 +1,26 @@
#!/bin/sh
#
# Same as unshare-as-root-user-inside-chroot but this time we run mmdebstrap in
# root mode from inside a chroot
set -eu
export LC_ALL=C.UTF-8
[ "$(whoami)" = "root" ]
cat << 'SCRIPT' > script.sh
#!/bin/sh
set -eu
rootfs="$1"
mkdir -p "$rootfs/mnt"
[ -e /usr/bin/mmdebstrap ] && cp -aT /usr/bin/mmdebstrap "$rootfs/usr/bin/mmdebstrap"
[ -e ./mmdebstrap ] && cp -aT ./mmdebstrap "$rootfs/mnt/mmdebstrap"
chroot "$rootfs" env --chdir=/mnt \
{{ CMD }} --mode=root --variant=apt \
{{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
SCRIPT
chmod +x script.sh
{{ CMD }} --mode=root --variant=apt --include=perl,mount \
--customize-hook=./script.sh \
--customize-hook="download /tmp/debian-chroot.tar /tmp/debian-chroot.tar" \
{{ DIST }} /dev/null {{ MIRROR }}
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar script.sh

View file

@ -0,0 +1,32 @@
#!/bin/sh
#
# Same as root-mode-inside-chroot but this time we run mmdebstrap in root mode
# from inside an unshare chroot.
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
[ "$(whoami)" = "root" ]
adduser --gecos user --disabled-password user
sysctl -w kernel.unprivileged_userns_clone=1
cat << 'SCRIPT' > script.sh
#!/bin/sh
set -eu
rootfs="$1"
mkdir -p "$rootfs/mnt"
[ -e /usr/bin/mmdebstrap ] && cp -aT /usr/bin/mmdebstrap "$rootfs/usr/bin/mmdebstrap"
[ -e ./mmdebstrap ] && cp -aT ./mmdebstrap "$rootfs/mnt/mmdebstrap"
chroot "$rootfs" env --chdir=/mnt \
{{ CMD }} --mode=root --variant=apt \
{{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
SCRIPT
chmod +x script.sh
runuser -u user -- {{ CMD }} --mode=unshare --variant=apt --include=perl,mount \
--customize-hook=./script.sh \
--customize-hook="download /tmp/debian-chroot.tar /tmp/debian-chroot.tar" \
{{ DIST }} /dev/null {{ MIRROR }}
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar script.sh

View file

@ -0,0 +1,17 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
[ "$(whoami)" = "root" ]
if grep --null-data --quiet --no-messages '^container=lxc$' /proc/1/environ; then
# see https://stackoverflow.com/questions/65748254/
echo "cannot run under lxc -- Skipping test..." >&2
exit 0
fi
capsh --drop=cap_sys_admin -- -c 'exec "$@"' exec \
{{ CMD }} --mode=root --variant=apt \
--customize-hook='chroot "$1" sh -c "test ! -e /proc/self/fd"' \
{{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar

View file

@ -0,0 +1,21 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
setsid --wait {{ CMD }} --mode=root --variant=apt --customize-hook='touch done && sleep 10 && touch fail' {{ DIST }} /tmp/debian-chroot {{ MIRROR }} &
pid=$!
while sleep 1; do [ -e done ] && break; done
rm done
pgid=$(echo $(ps -p $pid -o pgid=))
/bin/kill --signal INT -- -$pgid
ret=0
wait $pid || ret=$?
rm -r /tmp/debian-chroot
if [ -e fail ]; then
echo customize hook was not interrupted >&2
rm fail
exit 1
fi
if [ "$ret" = 0 ]; then
echo expected failure but got exit $ret >&2
exit 1
fi

View file

@ -0,0 +1,7 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
{{ CMD }} --mode=root --variant=apt {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
printf 'deb {{ MIRROR }} {{ DIST }} main\n' | cmp /tmp/debian-chroot/etc/apt/sources.list -
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,12 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
rm /etc/apt/trusted.gpg.d/*.gpg
{{ CMD }} --mode=root --variant=apt {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
printf 'deb [signed-by="/usr/share/keyrings/debian-archive-keyring.gpg"] {{ MIRROR }} {{ DIST }} main\n' | cmp /tmp/debian-chroot/etc/apt/sources.list -
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,27 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
mkfifo /tmp/myfifo
mkdir /tmp/root
ln -s /real /tmp/root/link
mkdir /tmp/root/real
run_testA() {
echo content > /tmp/foo
{ { { {{ CMD }} --hook-helper /tmp/root root setup env 1 upload /tmp/foo $1 < /tmp/myfifo 3>&-; echo $? >&3; printf "\\000\\000adios";
} | {{ CMD }} --hook-listener 1 3>&- >/tmp/myfifo; echo $?; } 3>&1;
} | { read xs1; [ "$xs1" -eq 0 ]; read xs2; [ "$xs2" -eq 0 ]; }
echo content | diff -u - /tmp/root/real/foo
rm /tmp/foo
rm /tmp/root/real/foo
}
run_testA link/foo
run_testA /link/foo
run_testA ///link///foo///
run_testA /././link/././foo/././
run_testA /link/../link/foo
run_testA /link/../../link/foo
run_testA /../../link/foo
rmdir /tmp/root/real
rm /tmp/root/link
rmdir /tmp/root
rm /tmp/myfifo

View file

@ -0,0 +1,31 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
cat << 'SCRIPT' > /tmp/script.sh
#!/bin/sh
set -eu
echo "MMDEBSTRAP_APT_CONFIG $MMDEBSTRAP_APT_CONFIG"
echo "$MMDEBSTRAP_HOOK" >> /tmp/hooks
[ "$MMDEBSTRAP_MODE" = "root" ]
echo test-content $MMDEBSTRAP_HOOK > test
{{ CMD }} --hook-helper "$1" "$MMDEBSTRAP_MODE" "$MMDEBSTRAP_HOOK" env 1 upload test /test <&$MMDEBSTRAP_HOOKSOCK >&$MMDEBSTRAP_HOOKSOCK
rm test
echo "content inside chroot:"
cat "$1/test"
[ "test-content $MMDEBSTRAP_HOOK" = "$(cat "$1/test")" ]
{{ CMD }} --hook-helper "$1" "$MMDEBSTRAP_MODE" "$MMDEBSTRAP_HOOK" env 1 download /test test <&$MMDEBSTRAP_HOOKSOCK >&$MMDEBSTRAP_HOOKSOCK
echo "content outside chroot:"
cat test
[ "test-content $MMDEBSTRAP_HOOK" = "$(cat test)" ]
rm test
SCRIPT
chmod +x /tmp/script.sh
{{ CMD }} --mode=root --variant=apt \
--setup-hook=/tmp/script.sh \
--extract-hook=/tmp/script.sh \
--essential-hook=/tmp/script.sh \
--customize-hook=/tmp/script.sh \
{{ DIST }} /tmp/debian-chroot {{ MIRROR }}
printf "setup\nextract\nessential\ncustomize\n" | diff -u - /tmp/hooks
rm /tmp/script.sh /tmp/hooks
rm -r /tmp/debian-chroot

View file

@ -0,0 +1,151 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ "$(id -u)" -eq 0 ] && ! id -u user > /dev/null 2>&1; then
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
adduser --gecos user --disabled-password user
fi
if [ "{{ MODE }}" = unshare ]; then
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
sysctl -w kernel.unprivileged_userns_clone=1
fi
prefix=
[ "$(id -u)" -eq 0 ] && [ "{{ MODE }}" != "root" ] && prefix="runuser -u user --"
[ "{{ MODE }}" = "fakechroot" ] && prefix="$prefix fakechroot fakeroot"
symlinktarget=/real
case {{ MODE }} in fakechroot|proot) symlinktarget='$1/real';; esac
echo copy-in-setup > /tmp/copy-in-setup
echo copy-in-essential > /tmp/copy-in-essential
echo copy-in-customize > /tmp/copy-in-customize
echo tar-in-setup > /tmp/tar-in-setup
echo tar-in-essential > /tmp/tar-in-essential
echo tar-in-customize > /tmp/tar-in-customize
tar --numeric-owner --format=pax --pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime -C /tmp -cf /tmp/tar-in-setup.tar tar-in-setup
tar --numeric-owner --format=pax --pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime -C /tmp -cf /tmp/tar-in-essential.tar tar-in-essential
tar --numeric-owner --format=pax --pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime -C /tmp -cf /tmp/tar-in-customize.tar tar-in-customize
rm /tmp/tar-in-setup
rm /tmp/tar-in-essential
rm /tmp/tar-in-customize
echo upload-setup > /tmp/upload-setup
echo upload-essential > /tmp/upload-essential
echo upload-customize > /tmp/upload-customize
mkdir /tmp/sync-in-setup
mkdir /tmp/sync-in-essential
mkdir /tmp/sync-in-customize
echo sync-in-setup > /tmp/sync-in-setup/file
echo sync-in-essential > /tmp/sync-in-essential/file
echo sync-in-customize > /tmp/sync-in-customize/file
$prefix {{ CMD }} --mode={{ MODE }} --variant=apt \
--setup-hook='mkdir "$1/real"' \
--setup-hook='copy-in /tmp/copy-in-setup /real' \
--setup-hook='echo copy-in-setup | cmp "$1/real/copy-in-setup" -' \
--setup-hook='rm "$1/real/copy-in-setup"' \
--setup-hook='echo copy-out-setup > "$1/real/copy-out-setup"' \
--setup-hook='copy-out /real/copy-out-setup /tmp' \
--setup-hook='rm "$1/real/copy-out-setup"' \
--setup-hook='tar-in /tmp/tar-in-setup.tar /real' \
--setup-hook='echo tar-in-setup | cmp "$1/real/tar-in-setup" -' \
--setup-hook='tar-out /real/tar-in-setup /tmp/tar-out-setup.tar' \
--setup-hook='rm "$1"/real/tar-in-setup' \
--setup-hook='upload /tmp/upload-setup /real/upload' \
--setup-hook='echo upload-setup | cmp "$1/real/upload" -' \
--setup-hook='download /real/upload /tmp/download-setup' \
--setup-hook='rm "$1/real/upload"' \
--setup-hook='sync-in /tmp/sync-in-setup /real' \
--setup-hook='echo sync-in-setup | cmp "$1/real/file" -' \
--setup-hook='sync-out /real /tmp/sync-out-setup' \
--setup-hook='rm "$1/real/file"' \
--essential-hook='ln -s "'"$symlinktarget"'" "$1/symlink"' \
--essential-hook='copy-in /tmp/copy-in-essential /symlink' \
--essential-hook='echo copy-in-essential | cmp "$1/real/copy-in-essential" -' \
--essential-hook='rm "$1/real/copy-in-essential"' \
--essential-hook='echo copy-out-essential > "$1/real/copy-out-essential"' \
--essential-hook='copy-out /symlink/copy-out-essential /tmp' \
--essential-hook='rm "$1/real/copy-out-essential"' \
--essential-hook='tar-in /tmp/tar-in-essential.tar /symlink' \
--essential-hook='echo tar-in-essential | cmp "$1/real/tar-in-essential" -' \
--essential-hook='tar-out /symlink/tar-in-essential /tmp/tar-out-essential.tar' \
--essential-hook='rm "$1"/real/tar-in-essential' \
--essential-hook='upload /tmp/upload-essential /symlink/upload' \
--essential-hook='echo upload-essential | cmp "$1/real/upload" -' \
--essential-hook='download /symlink/upload /tmp/download-essential' \
--essential-hook='rm "$1/real/upload"' \
--essential-hook='sync-in /tmp/sync-in-essential /symlink' \
--essential-hook='echo sync-in-essential | cmp "$1/real/file" -' \
--essential-hook='sync-out /real /tmp/sync-out-essential' \
--essential-hook='rm "$1/real/file"' \
--customize-hook='copy-in /tmp/copy-in-customize /symlink' \
--customize-hook='echo copy-in-customize | cmp "$1/real/copy-in-customize" -' \
--customize-hook='rm "$1/real/copy-in-customize"' \
--customize-hook='echo copy-out-customize > "$1/real/copy-out-customize"' \
--customize-hook='copy-out /symlink/copy-out-customize /tmp' \
--customize-hook='rm "$1/real/copy-out-customize"' \
--customize-hook='tar-in /tmp/tar-in-customize.tar /symlink' \
--customize-hook='echo tar-in-customize | cmp "$1/real/tar-in-customize" -' \
--customize-hook='tar-out /symlink/tar-in-customize /tmp/tar-out-customize.tar' \
--customize-hook='rm "$1"/real/tar-in-customize' \
--customize-hook='upload /tmp/upload-customize /symlink/upload' \
--customize-hook='echo upload-customize | cmp "$1/real/upload" -' \
--customize-hook='download /symlink/upload /tmp/download-customize' \
--customize-hook='rm "$1/real/upload"' \
--customize-hook='sync-in /tmp/sync-in-customize /symlink' \
--customize-hook='echo sync-in-customize | cmp "$1/real/file" -' \
--customize-hook='sync-out /real /tmp/sync-out-customize' \
--customize-hook='rm "$1/real/file"' \
--customize-hook='rmdir "$1/real"' \
--customize-hook='rm "$1/symlink"' \
{{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
for n in setup essential customize; do
ret=0
cmp /tmp/tar-in-$n.tar /tmp/tar-out-$n.tar || ret=$?
if [ "$ret" -ne 0 ]; then
if type diffoscope >/dev/null; then
diffoscope /tmp/tar-in-$n.tar /tmp/tar-out-$n.tar
exit 1
else
echo "no diffoscope installed" >&2
fi
if type base64 >/dev/null; then
base64 /tmp/tar-in-$n.tar
base64 /tmp/tar-out-$n.tar
exit 1
else
echo "no base64 installed" >&2
fi
if type xxd >/dev/null; then
xxd /tmp/tar-in-$n.tar
xxd /tmp/tar-out-$n.tar
exit 1
else
echo "no xxd installed" >&2
fi
exit 1
fi
done
echo copy-out-setup | cmp /tmp/copy-out-setup -
echo copy-out-essential | cmp /tmp/copy-out-essential -
echo copy-out-customize | cmp /tmp/copy-out-customize -
echo upload-setup | cmp /tmp/download-setup -
echo upload-essential | cmp /tmp/download-essential -
echo upload-customize | cmp /tmp/download-customize -
echo sync-in-setup | cmp /tmp/sync-out-setup/file -
echo sync-in-essential | cmp /tmp/sync-out-essential/file -
echo sync-in-customize | cmp /tmp/sync-out-customize/file -
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar \
/tmp/copy-in-setup /tmp/copy-in-essential /tmp/copy-in-customize \
/tmp/copy-out-setup /tmp/copy-out-essential /tmp/copy-out-customize \
/tmp/tar-in-setup.tar /tmp/tar-in-essential.tar /tmp/tar-in-customize.tar \
/tmp/tar-out-setup.tar /tmp/tar-out-essential.tar /tmp/tar-out-customize.tar \
/tmp/upload-setup /tmp/upload-essential /tmp/upload-customize \
/tmp/download-setup /tmp/download-essential /tmp/download-customize \
/tmp/sync-in-setup/file /tmp/sync-in-essential/file /tmp/sync-in-customize/file \
/tmp/sync-out-setup/file /tmp/sync-out-essential/file /tmp/sync-out-customize/file
rmdir /tmp/sync-in-setup /tmp/sync-in-essential /tmp/sync-in-customize \
/tmp/sync-out-setup /tmp/sync-out-essential /tmp/sync-out-customize

View file

@ -0,0 +1,20 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
cat << HOSTS >> /etc/hosts
127.0.0.1 deb.debian.org
127.0.0.1 security.debian.org
HOSTS
apt-cache policy
cat /etc/apt/sources.list
{{ CMD }} --mode=root --variant=apt stable /tmp/debian-chroot
cat << SOURCES | cmp /tmp/debian-chroot/etc/apt/sources.list
deb http://deb.debian.org/debian stable main
deb http://deb.debian.org/debian stable-updates main
deb http://security.debian.org/debian-security stable-security main
SOURCES
rm -r /tmp/debian-chroot

Some files were not shown because too many files have changed in this diff Show more