Table of Contents

Build system as systemd-container

This should work on modern linux distributions even when running in WSL.

Naming Conventions

Prerequisites

Debootstrapping Debian machine

Base files

debootstrap --arch=amd64 --include=dbus,libpam-systemd,locales stable /var/lib/machines/openwrt-builder http://deb.debian.org/debian

set hostname

echo 'openwrt-builder' > /var/lib/machines/openwrt-builder/etc/hostname

locale

systemd-nspawn --settings=false -D "/var/lib/machines/openwrt-builder" /bin/bash -c 'sed -i "s@^# \(en_US*\)@\1@g" /etc/locale.gen'
systemd-nspawn --settings=false -D "/var/lib/machines/openwrt-builder" /bin/bash -c 'locale-gen'
echo -e 'LANG=en_US.UTF-8\nLC_ALL=en_US.UTF-8' > /var/lib/machines/openwrt-builder/etc/default/locale.conf

Install packages inside machine

# systemd-nspawn --settings=false -D "/var/lib/machines/openwrt-builder" /bin/bash -c 'apt -y install build-essential clang flex bison g++ gawk gpg gcc-multilib g++-multilib gettext git libncurses5-dev libssl-dev python3-setuptools rsync swig unzip zlib1g-dev file wget vim'
systemd-nspawn --settings=false -D "/var/lib/machines/openwrt-builder" /bin/bash -c 'apt -y install build-essential clang flex bison g++ gpg gcc-multilib g++-multilib python3-setuptools swig libncurses-dev zlib1g-dev gawk git gettext libssl-dev xsltproc rsync file wget vim unzip python3'

Create unprivileged user 'builder' inside machine

systemd-nspawn --settings=false -D "/var/lib/machines/openwrt-builder" useradd -m -s /bin/bash builder > /dev/null 2>&1 || true

Create nspawn configuration

NSPAWN_FILE="/etc/systemd/nspawn/openwrt-builder.nspawn"
cat > "$NSPAWN_FILE" <<EOF
[Exec]
Boot=true
PrivateUsers=no
Hostname=openwrt-builder
 
[Network]
Private=no
VirtualEthernet=no
 
[Files]
EOF

Start the machine

You should now be able to start (essentially it is nearly a boot) the machine with the following command:

machinectl start openwrt-builder

Shell into the machine (as user builder)

machinectl shell builder@openwrt-builder

You should now see the following output (being inside the machine):

builder@openwrt-builder:~$

You can now start building OpenWRT.

Exiting the machine

Type exit to exit/logout from the machine.

builder@openwrt-builder:~$ exit
logout
Connection to machine openwrt-builder terminated.

Shutdown the machine

Yes, the machine/container did a real boot so we have to shut it down:

To see that it is running type machinectl list:

MACHINE         CLASS     SERVICE        OS     VERSION ADDRESSES
openwrt-builder container systemd-nspawn debian 13-

1 machines listed.

To shut it down type:

machinectl stop openwrt-builder

Fully backup the machine

You can save your work by creating a full, re-importable export of your machine and a copy of the file /etc/systemd/nspawn/openwrt-builder.nspawn.

Export the machine

In the following example we will export the machine to the file /root/openwrt-builder.tar.gz

# machinectl export-tar openwrt-builder /root/openwrt-builder.tar.xz # smaller file size takes longer
machinectl export-tar openwrt-builder /root/openwrt-builder.tar.gz 

Output:

Enqueued transfer job 1. Press C-c to continue download in background.
Exporting '/var/lib/machines/openwrt-builder', saving to '/root/openwrt-builder.tar.gz' with compression 'gzip'.
Operation completed successfully.
Exiting.

Backup nspawn configuration

Make sure, to backup a copy of /etc/systemd/nspawn/openwrt-builder.nspawn

cp /etc/systemd/nspawn/openwrt-builder.nspawn /root/openwrt-builder.nspawn

Delete the machine

This command will destroy the machine!! The folder /var/lib/machines/openwrt-builder and the file /etc/systemd/nspawn/openwrt-builder.nspawn will be deleted!

machinectl remove openwrt-builder

Fully restore the machine

Import the machine

Use the archive from a previous export, e.g. openwrt-builder.tar.gz and import it. Make sure, that machine openwrt-builder2 does not exist or use a different name

machinectl import-tar /root/openwrt-builder.tar.gz openwrt-builder2

Output:

Enqueued transfer job 1. Press C-c to continue download in background.
Importing '/var/lib/machines/openwrt-builder.tar.gz', saving as 'openwrt-builder2'.
Imported 0%.
Imported 1%.
Imported 2%.
Imported 5%.
Imported 10%
...
...
Imported 99%.
Operation completed successfully.
Exiting.

Restore nspawn configuration

Copy your backup of <machine-name>.nspawn file to /etc/systemd/nspawn/<machine-name>.nspawn.

cp /root/openwrt-builder.nspawn /etc/systemd/nspawn/openwrt-builder2.nspawn

Everything should be restored!

You can now again startup the container/machine using machinectl start openwrt-builder2.

All-In-One-Script

The following script (to be executed as root or with sudo) should do all of the above automatically and should work on Debian, Ubuntu, archlinux, Manjaro, RHEL, CentOS, Fedora and almalinux.

#!/usr/bin/env bash
 
# Desired hostname for the container
HOSTNAME_VAR="container"
 
BASEDIR="/var/lib/machines"
CONTAINERNAME="openwrt-builder"
TEMPDIR="${BASEDIR}/${CONTAINERNAME}"
 
check_systemd_container() {
    if command -v systemd-nspawn &> /dev/null; then
        echo "systemd-container capability (systemd-nspawn) is already available."
        return 0
    else
        echo "systemd-container capability (systemd-nspawn) is NOT available."
        return 1
    fi
}
 
install_systemd_container_debian() {
    apt-get -y update
    apt-get install -y systemd-container
}
 
install_systemd_container_arch() {
    pacman -Sy --noconfirm systemd
}
 
install_systemd_container_rhel() {
    if command -v dnf &> /dev/null; then
        dnf install -y systemd-container || echo "systemd-container package not available in dnf repositories"
    elif command -v yum &> /dev/null; then
        yum install -y systemd-container || echo "systemd-container package not available in yum repositories"
    else
        echo "dnf or yum package manager not found"
    fi
}
 
install_debian() {
    apt-get install -y debootstrap
}
 
install_arch() {
    pacman -Sy --noconfirm debootstrap
}
 
install_rhel() {
    if command -v dnf &> /dev/null; then
        dnf install -y debootstrap || echo "debootstrap not available in dnf repositories"
    elif command -v yum &> /dev/null; then
        yum install -y debootstrap || echo "debootstrap not available in yum repositories"
    else
        echo "dnf or yum package manager not found"
    fi
}
 
if [ -f /etc/os-release ]; then
    . /etc/os-release
else
    echo "/etc/os-release not found. Cannot detect OS."
    exit 1
fi
 
echo "Detected distribution: $ID"
 
if ! check_systemd_container; then
    echo "Attempting to install systemd-container capability..."
    case "$ID" in
        debian|ubuntu)
            install_systemd_container_debian
            ;;
        arch|manjaro)
            install_systemd_container_arch
            ;;
        rhel|centos|fedora|almalinux)
            install_systemd_container_rhel
            ;;
        *)
            echo "Unsupported distribution: $ID"
            exit 1
            ;;
    esac
fi
 
if command -v debootstrap &> /dev/null; then
    echo "debootstrap is already installed."
else
    echo "Installing debootstrap..."
    case "$ID" in
        debian|ubuntu)
            install_debian
            ;;
        arch|manjaro)
            install_arch
            ;;
        rhel|centos|fedora|almalinux)
            install_rhel
            ;;
        *)
            echo "Unsupported distribution: $ID"
            exit 1
            ;;
    esac
fi
 
if [ ! -d /etc/systemd/nspawn ]; then
    echo -n "Creating directory /etc/systemd/nspawn..."
    mkdir -p /etc/systemd/nspawn
    echo "done"
else
    echo "Directory /etc/systemd/nspawn already exists"
fi
 
if [ -d "$TEMPDIR" ]; then
    echo "Container directory $TEMPDIR already exists. Please remove it or choose a different name."
    exit 1
else
    echo -n "Creating container directory ${TEMPDIR}..."
    mkdir -p "$TEMPDIR"
    echo "done"
fi
 
# Set ownership and permissions
chown -R root:root "$TEMPDIR"
chmod 755 "$TEMPDIR"
 
echo -n "Bootstrapping minimal Debian filesystem in $TEMPDIR with essential packages..."
debootstrap --arch=amd64 --include=dbus,libpam-systemd,locales stable "$TEMPDIR" http://deb.debian.org/debian >/dev/null
echo "done"
 
echo -n "Setting hostname inside the container filesystem to '${HOSTNAME_VAR}'..."
echo "$HOSTNAME_VAR" > "$TEMPDIR/etc/hostname"
echo "done"
 
echo -n "Prepare locale.gen, generating locales and create locales.conf ..."
systemd-nspawn --settings=false -D "$TEMPDIR" /bin/bash -c 'sed -i "s@^# \(en_US*\)@\1@g" /etc/locale.gen' >/dev/null 2>&1
systemd-nspawn --settings=false -D "$TEMPDIR" /bin/bash -c 'locale-gen' > /dev/null 2>&1
systemd-nspawn --settings=false -D "$TEMPDIR" /bin/bash -c 'echo -e "LANG=en_US.UTF-8\nLC_ALL=en_US.UTF-8" > "/etc/default/locale.conf"' >/dev/null 2>&1
echo "done"
 
echo -n "Installing packages inside container..."
systemd-nspawn -D "$TEMPDIR" /bin/bash -c 'apt -y install build-essential clang flex bison g++ gpg gcc-multilib g++-multilib python3-setuptools swig libncurses-dev zlib1g-dev gawk git gettext libssl-dev xsltproc rsync file wget vim unzip python3' > /dev/null 2>&1
echo "done"
 
echo -n "Creating non-privileged user 'builder' with home directory and default shell inside container..."
systemd-nspawn -D "$TEMPDIR" useradd -m -s /bin/bash builder > /dev/null 2>&1
echo "done"
 
echo -n "Adding banner message to /etc/profile to display on login shells for id 0 (root)..."
cat >> "$TEMPDIR/etc/profile" <<'EOF'
 
if [ "$(id -u)" -eq 0 ]; then
    echo '##########################################################'
    echo '# You are now inside the container                        #'
    echo '##########################################################'
    echo 'use the command'
    echo ''
    echo '    su - builder'
    echo ''
    echo 'to become the non-privileged user "builder".'
    echo '##########################################################'
fi
EOF
echo "done"
 
NSPAWN_FILE="/etc/systemd/nspawn/${CONTAINERNAME}.nspawn"
echo -n "Creating systemd-nspawn config file at $NSPAWN_FILE with hostname ${HOSTNAME_VAR}..."
 
cat > "$NSPAWN_FILE" <<EOF
[Exec]
Boot=true
PrivateUsers=no
Hostname=$HOSTNAME_VAR
 
[Network]
Private=no
VirtualEthernet=no
 
[Files]
EOF
echo "done"
echo "Container directory '$TEMPDIR' set up and ready."
echo -e "\n\n##############################################################################\n"
echo "You can start the container machine with: machinectl start $CONTAINERNAME"
echo "To interact with it, use                : machinectl shell $CONTAINERNAME"
echo "                                          or"
echo "                                          machinectl shell builder@$CONTAINERNAME"
echo -e "\n##############################################################################\n"