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
Table of Content
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 bychroot
to emulate the target machine.debootstrap
is used to download and unpack new RootFSbinfmt-support
andlibxml2-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#
- find_mirror.sh
- Ubuntu arm64 installation guide
- Minimal RootFS
- Create your own image for Jetson Nano board
- How can I install Debian the arch way
- TX2 Ubuntu Base
- How do you run Ubuntu Server with a GUI?