Skip to content



Nvidia Jetson »

Debootstrap a minimal Image for Jetson boards

The prebuilt system images for Jetson boards are quite large. We can reduce their size using JetPack SDK, but it's sometimes still to big. Here is a method to build a minimal system image from an Ubuntu base with some necessary packages from debootstrap.

Last update: 2022-06-04


Check out the script#

git clone https://github.com/vuquangtrong/jetson-custom-image.git

You have to edit some variables in the step0_env.sh script, and some additional options in other scripts !

Set up environment#

Run

sudo ./step0_env.sh

Detail

Chose the directories for the project:

ROOT_DIR=/home/vqtrong/jetson-custom/rootfs
WORK_DIR=/home/vqtrong/jetson-custom/build

The Linux platform and Ubuntu version:

ARCH=arm64
RELEASE=bionic
REPO=

To find a fast repo, use find_mirrors.sh script. By default, http://ports.ubuntu.com/ubuntu-ports will be used.

The Jetson platform and its BSP version:

JETSON_BOARD=jetson-nano-devkit
JETSON_BOARD_IMG=jetson-nano
JETSON_BOARD_REV=300
JETSON_PLAT=t210
JETSON_REL=r32.6
JETSON_BSP=jetson-210_linux_r32.6.1_aarch64.tbz2
JETSON_BSP_URL=https://developer.nvidia.com/embedded/l4t/r32_release_v6.1/t210/jetson-210_linux_r32.6.1_aarch64.tbz2

And select a desktop environment, for example:

# leave empty for CLI, or use openbox, lxde, xubuntu, or ubuntu-mate
JETSON_DESKTOP=xubuntu

Check root permission

All scripts need root permission to make new RootFS, therefore, check the permission at the beginning of the script:

if [ "x$(whoami)" != "xroot" ]; then
    echo "This script requires root privilege!!!"
    exit 1
fi

Make a RootFS#

Run

sudo ./step1_make_rootfs.sh

Detail

Host machine must have below packages:

apt install -y --no-install-recommends \
    qemu-user-static \
    debootstrap \
    binfmt-support \
    libxml2-utils

qemu-user-static is used by chroot to emulate the target machine. debootstrap is used to download and unpack new RootFS binfmt-support and libxml2-utils are used by Jetson tools

For faster download, use apt-fast.

Run debootstrap:

debootstrap \
    --verbose \
    --foreign \
    --arch=$ARCH \
    $RELEASE \
    $ROOT_DIR \
    $REPO

QEMU will be used to initialize system, we copy it to the new RootFS:

install -Dm755 $(which qemu-aarch64-static) $ROOT_DIR/usr/bin/qemu-aarch64-static

Ubuntu packages have a signing key, so copy the key to the new RootFS:

install -Dm644 /usr/share/keyrings/ubuntu-archive-keyring.gpg $ROOT_DIR/usr/share/keyrings/ubuntu-archive-keyring.gpg

Finally, unpack the new RootFS:

chroot $ROOT_DIR /debootstrap/debootstrap --second-stage

Customize the RootFS#

At this step, we can change the root to ROOT_DIR and work inside its environment:

sudo chroot `$ROOT_DIR`

Run

sudo ./step2_customize_rootfs.sh

Detail

However, we will keep making script to customize the new RootFS.

There are some mount points needed for running new root, we use binding on host RootFS:

for mnt in sys proc dev dev/pts tmp; do
    mount -o bind "/$mnt" "$ROOT_DIR/$mnt"
done

First, we generate locale:

chroot $ROOT_DIR locale-gen en_US
chroot $ROOT_DIR locale-gen en_US.UTF-8
chroot $ROOT_DIR update-locale LC_ALL=en_US.UTF-8

Add DNS, add repo list, and update package list:

cat << EOF > $ROOT_DIR/etc/resolv.conf
nameserver 1.1.1.1
EOF

cat << EOF > $ROOT_DIR/etc/apt/sources.list
deb [arch=$ARCH] $REPO ${RELEASE} main restricted universe multiverse
deb [arch=$ARCH] $REPO ${RELEASE}-updates main restricted universe multiverse
deb [arch=$ARCH] $REPO ${RELEASE}-security main restricted universe multiverse
EOF

chroot $ROOT_DIR apt update

Then, we can install additional packages to RootFS which are needed for installing Jetson packages in the next step:

chroot ${ROOT_DIR} apt install -y --no-install-recommends \
    libasound2 \
    libcairo2 \
    libdatrie1 \
    libegl1 \
    libegl1-mesa \
    libevdev2 \
    libfontconfig1 \
    libgles2 \
    libgstreamer1.0-0 \
    libgstreamer-plugins-base1.0-0 \
    libgstreamer-plugins-bad1.0-0 \
    libgtk-3-0 \
    libharfbuzz0b \
    libinput10 \
    libjpeg-turbo8 \
    libpango-1.0-0 \
    libpangocairo-1.0-0 \
    libpangoft2-1.0-0 \
    libpixman-1-0 \
    libpng16-16 \
    libunwind8 \
    libwayland-client0 \
    libwayland-cursor0 \
    libwayland-egl1-mesa \
    libx11-6 \
    libxext6 \
    libxkbcommon0 \
    libxrender1 \
    python \
    python3
    device-tree-compiler \

Below packages are for network settings with default kernel drivers:

chroot ${ROOT_DIR} apt install -y --no-install-recommends \
    wget \
    curl \
    linux-firmware \
    network-manager \
    net-tools \
    wireless-tools \
    ssh

Then we install X GUI server:

chroot ${ROOT_DIR} apt install -y --no-install-recommends \
    xorg

Next, install a Desktop Manager is it is selected:

if [ ${JETSON_DESKTOP} == 'openbox' ]; then
    echo "Install Openbox"

    # minimal desktop, only greeter, no taskbar and background
    chroot ${ROOT_DIR} apt install -y --no-install-recommends \
        lightdm-gtk-greeter \
        lightdm \
        openbox \

fi

if [ ${JETSON_DESKTOP} == 'lxde' ]; then
    echo "Install LXDE core"

    # lxde with some components
    chroot ${ROOT_DIR} apt install -y --no-install-recommends \
        lightdm-gtk-greeter \
        lightdm \
        lxde-icon-theme \
        lxde-core \
        lxde-common \
        policykit-1 lxpolkit \
        lxsession-logout \
        gvfs-backends \

fi

if [ ${JETSON_DESKTOP} == 'xubuntu' ]; then
    echo "Install Xubuntu core"

    # Xubuntu, better than lxde
    chroot ${ROOT_DIR} apt install -y --no-install-recommends \
        xubuntu-core \

fi

if [ ${JETSON_DESKTOP} == 'ubuntu-mate' ]; then
    echo "Install Ubuntu Mate"

    # Ubuntu-Mate, similar to Ubuntu
    chroot ${ROOT_DIR} apt install -y --no-install-recommends \
        ubuntu-mate-core \

fi

And, last but not least, set up network interface. We prefer using Ethernet. Ubuntu 18.04 us Netplan, so it does not use /etc/network/interfaces anymore:

cat << EOF > ${ROOT_DIR}/etc/netplan/01-netconf.yaml
network:
    ethernets:
        eth0:
            dhcp4: true
EOF

cat << EOF > ${ROOT_DIR}/etc/hostname
${JETSON_NAME}
EOF

After installing new packages, we can unmount the binding files:

echo "Unmount dependency points"

for mnt in tmp dev/pts dev proc sys; do
  umount "$ROOT_DIR/$mnt"
done

Apply Jetson BSP#

Run

sudo ./step3_apply_bsp.sh

Detail

Nvidia provides BSP which include everything to boot up their board, include CBoot, U-Boot, Linux Kernel, Device Tree, etc.

Download and extract the BSP:

if [ ! -f $JETSON_BSP ]
then
    wget $JETSON_BSP_URL
fi

if [ ! -d $WORK_DIR/Linux_for_Tegra ]
then
    tar jxpf $JETSON_BSP -C $WORK_DIR
fi

We copy our customized RootFS to the BSP RootFS:

rm -rf $WORK_DIR/Linux_for_Tegra/rootfs
cp -rf $ROOT_DIR $WORK_DIR/Linux_for_Tegra/

Then we install Jetson packages:

pushd ${WORK_DIR}/Linux_for_Tegra/
./apply_binaries.sh
popd

Then we set the flag for sudo:

chroot ${WORK_DIR}/Linux_for_Tegra/rootfs bash -c "echo root:$ROOT_PWD | chpasswd"
chroot ${WORK_DIR}/Linux_for_Tegra/rootfs bash -c "chown root:root /usr/bin/sudo"
chroot ${WORK_DIR}/Linux_for_Tegra/rootfs bash -c "chmod u+s /usr/bin/sudo"

And fix the error when install package:

chroot ${WORK_DIR}/Linux_for_Tegra/rootfs bash -c "chown -R man:man /var/cache/man"
chroot ${WORK_DIR}/Linux_for_Tegra/rootfs bash -c "chmod -R 775 /var/cache/man"

And finally, add a user:

pushd $WORK_DIR/Linux_for_Tegra/tools
./l4t_create_default_user.sh -u $JETSON_USR -p $JETSON_PWD -n $JETSON_NAME --accept-license

Add --autologin if needed

To skip entering password when running as sudo, add a new file:

cat << EOF > ${WORK_DIR}/Linux_for_Tegra/rootfs/etc/sudoers.d/${JETSON_USR}
${JETSON_USR} ALL=(ALL) NOPASSWD: ALL
EOF

Customize BSP

If you need to update U-boot or any modififcation of image, update them at this step after the base BSP is applied. For example: custom_bsp_for_nano_dev_kit.

Flash the image#

Run

sudo ./step4_flash.sh

Detail

Put the board into Recovery Mode, and call flash.sh:

pushd ${WORK_DIR}/Linux_for_Tegra
./flash.sh ${JETSON_BOARD} mmcblk0p1

Create image#

Run

sudo ./step5_create_image.sh

Detail

The image will be used to flash to a micro-SD Card:

IMAGE=${JETSON_BOARD}_${RELEASE}_${JETSON_PLAT}_${JETSON_REL}_${JETSON_DESKTOP}.img

pushd ${WORK_DIR}/Linux_for_Tegra/tools
./jetson-disk-image-creator.sh -o ${IMAGE} -b ${JETSON_BOARD_IMG} -r ${JETSON_BOARD_REV}

Flash to SD#

Run

sudo ./step6_flash_SD.sh

Detail

Check that the target device is a block device:

if [ ! -b $1 ] || [ "$(lsblk | grep -w $(basename $1) | awk '{print $6}')" != "disk" ]; then
    echo "$1 is not a block device!!!"
    exit 1
fi

Then unmount it:

if [ "$(mount | grep $1)" ]; then
    echo "Unmount $1"
    for mount_point in $(mount | grep $1 | awk '{ print $1}'); do
        sudo umount $mount_point > /dev/null
    done
fi

And flash it using dd:

dd if=${IMAGE} of=$1 bs=4M conv=fsync status=progress

Next is to extend the partition:

partprobe $1
sgdisk -e $1

end_sector=$(sgdisk -p $1 |  grep -i "Total free space is" | awk '{ print $5 }')
start_sector=$(sgdisk -i 1 $1 | grep "First sector" | awk '{print $3}')

sgdisk -d 1 $1
sgdisk -n 1:${start_sector}:${end_sector} $1
sgdisk -c 1:APP $1

Finally, extend the filesystem:

if [[ $(basename $1) =~ mmc ]]; then
    e2fsck -fp $1"p1"
    resize2fs $1"p1"
fi

if [[ $(basename $1) =~ sd ]]; then
    e2fsck -fp $1"1"
    resize2fs $1"1"
fi
sync

Reference sources#

Comments