Use mmdebstrap to install Debian (and more)
This is a follow up to my
Debian Deboostrap Install
guide posted a few years back. I will not go through the whole install
process again here, you can simply follow the guide as written and replace
the debootstrap
command with mmdebstrap
when you've reached that step.
mmdebstrap - multi-mirror Debian chroot creation
The debootstrap
utility is a fantastic tool that allows you to create
custom Debian installs as big or as small as you want. It's a bit limited,
though. For exmaple, it can only use a single mirror to fetch packages
when creating the system (which can be a security risk, among other things).
This is where mmdebstrap
comes in. It fixes the security concern through
the use of multiple mirrors (including Debian's security mirror) and offers
much more customizing options, especially thanks to 'hooks'.
Make no mistake, debootstrap
can be as powerful through a good dose of
scripting, but mmdebstrap
's hooks, and other built in options, offer a
much more straightforward way of doing it.
The bread and butter
First, install mmdebstrap:
# apt-get install mmdebstrap -y
If all you want is to create a minimal Debian chroot as in the debootstrap
guide linked above, substituting the debootstrap
command with mmdebstrap,
then the following will suffice:
# /usr/bin/mmdebstrap --variant=minbase \
--components="main non-free-firmware" \
--include="usrmerge vim" \
--skip=check/empty \
$FLAVOUR $TARGET
Replace $FLAVOUR
with the right Debian version (currently, bookworm
)
and $TARGET
with the target directory, usually /mnt
when performing
a chroot-style install.
--include="usrmerge vim"
: Debian now has merged usr, and this package
will be fetched regardless, but I have encountered situations where not
including it caused some issues. Vim is there because having an editor
inside the chroot is convenient, but if you prefer something else, go
for it. You can add any other packages to --include
, but most packages
(especially things like the kernel and init system) should be installed
either in the chroot or when booted into a new install.
--skip=check/empty
: normally, mmdebstrap
will fail if the target dir
is not empty. This is a problem when doing a chroot install since you will
most likely have /home
and probably /boot
already created and mounted.
This flag skips the check, so the command will succeed. You may remove this
flag if you are creating a chroot, rather than performing an install.
Hooks
These are commands that get executed by mmdebstrap
at various stages
of the chroot creation. They are two things, really:
- Individual commands passed as options to
mmdebstrap
, or - Scripts containing multiple commands.
Types of hook:
setup
hook: executes commands after directory creation and configuration of apt and dpkg, but before packages are downloaded and installed.extract
hook: executes commands after essential packages were extracted but before they are installed.essential
hook: executes commands after the essential packages were installed, but before installing all other packages.customize
hook: executes commands after the chroot was created but before finalizing. This is the hook that gets used the most.
Hooks as individual commands
Passing a single command as a hook to mmdebstrap
is rather simple:
# /usr/bin/mmdebstrap --variant=minbase \
--components="main non-free-firmware" \
--include="usrmerge vim" \
--setup-hook='cp mydir "$1"/etc/'
--essential-hook='echo tzdata tzdata/Areas select America | chroot "$1" debconf-set-selections'
--essential-hook='echo tzdata tzdata/Zones/America select Toronto | chroot "$1" debconf-set-selections'
--customize-hook='echo myhostname > "$1"/etc/hosts' \
--skip=check/empty \
$FLAVOUR $TARGET
The --setup-hook
would copy a directory (mydir) to the chroot, for example
if it contained anything needed during setup.
The two --essential-hook
would set the timezone to America/Toronto.
The --customize-hook
would set the chroot's hostname.
Note that "$1"
represents the $TARGET
, always use "$1" whether you're
performing a command in the chroot (eg, chroot "$1" somecommand) or copying
something to the chroot (eg cp somefile "$1"/etc/skel/). This ensures that
the commands will work no matter what $TARGET
is.
Hook scripts
Passing commands to mmdebstrap
in the above manner suffices when there
aren't many, but for extensive customization, using hook scripts become
a lot more useful.
First, create a hook directory:
$ mkdir hooks
Next, simply create your scripts, and name them after the specific stage
they should be performed in, eg customize01.sh
. You can have multiple
scripts for each stages, and they would be executed alphabetically,
hence the 01
. A second script would be named customize02.sh
, and so
on.
Hook scripts can be in any scripting language as long as its dependencies are available in the chroot, at the stage they are to be executed.
Example customize01.sh script in POSIX sh:
#!/bin/sh
set -e
# Set the default debconf frontend to Readline
echo 'debconf debconf/frontend select Readline' | chroot "$1" debconf-set-selections
# Enable the wheel group.
sed -i '15 s/^# //' "$1"/etc/pam.d/su
chroot "$1" addgroup --system wheel
# Set the system's hostname.
echo "myhostname" > "$1"/etc/hostname
# Set the timezone
echo "tzdata tzdata/Areas select America" \
| chroot "$1" debconf-set-selections
echo "tzdata tzdata/Zones/America select New_York" \
| chroot "$1" debconf-set-selections
echo 'tzdata tzdata/Zones/Etc select UTC' \
| chroot "$1" debconf-set-selections
# This has to be done or else dpkg-reconfigure insists on using Etc
# as the default timezone for whatever stupid reason.
echo "America/New_York" > "$1"/etc/timezone
chroot "$1" ln -sf /usr/share/zoneinfo/America/New_York" /etc/localtime
chroot "$1" dpkg-reconfigure -f noninteractive tzdata
# Set locale
echo "locales locales/default_environment_locale select en_US.UTF-8" \
| chroot "$1" debconf-set-selections
echo "locales locales/locales_to_be_generated multiselect en_US.UTF-8 UTF-8" \
| chroot "$1" debconf-set-selections
chroot "$1" apt-get install locales -y
Then chmod +x
customize01.sh to make it executable.
Using the script with --hook-directory
:
# /usr/bin/mmdebstrap --variant=minbase \
--components="main non-free-firmware" \
--include="usrmerge vim" \
--hook-directory=hooks \
--skip=check/empty \
$FLAVOUR $TARGET
There you go. Hooks can be used to setup the whole system the same way you would when performing a chroot-style install.
Creating tarballs
Another useful built-in fuction of mmdebstrap
is its ability to create
an archive of the chroot automatically. This is especially useful if you
want custom chroots that can be used anywhere, or a custom Debian system
that can be installed simply by extracting the tarball to /mnt (for example).
To create an archive, simply change the $TARGET
from a directory to a
tarball, eg:
# /usr/bin/mmdebstrap --variant=minbase \
--components="main non-free-firmware" \
--include="usrmerge vim" \
--hook-directory=hooks \
$FLAVOUR custom-chroot.tgz
When extracting the chroot, you will need to use the following command in order to make sure the proper permissions are retained:
# tar xzpvf custom-chroot.tgz --xattrs --xattrs-include='*' --numeric-owner -C $TARGET
$TARGET
can either be any directory, or /mnt in the case of an install.
Bonus cleanup script
Whether you intend to share your chroot tarball, host it online or simply use it everywhere, it is good practice to clean it up a little.
customize02.sh
#!/bin/sh
set -e
chroot "$1" apt clean
rm -rf "$1"/var/lib/apt/lists/*
rm "$1"/var/log/apt/eipp.log.xz
rm "$1"/var/log/apt/history.log
rm "$1"/var/log/apt/term.log
rm "$1"/var/log/alternatives.log
rm "$1"/var/log/dpkg.log
rm "$1"/etc/resolv.conf
rm "$1"/tmp/*
for _file in /etc/machine-id /var/lib/dbus/machine-id; do
if [ -f "${1}/${_file}" ]; then
rm "${1}/${_file}"
fi
done