How to Create a Micro Linux Distro With Busybox

Originally posted on my medium account

Have you ever wanted to learn how to create a micro distro? Something even smaller than Linux From Scratch (LFS) for no other purpose than to learn how Linux systems work at the most fundamental level? If so you’re in the right place.

To start we need to make sure our environment is setup and ready to function. The tools we’ll need: a working c/c++ compiler (GCC/Clang), make, bash, syslinux, tar, qemu for x86_64 or a flashdrive and system that supports legacy boot and our source code. In theory you can use any version, but I’ll link to the version used for this blog post.

Setting Up the Working Directory

First up we need to create a working directory. Then we will follow it up with creating a minimal rootfs. This setup follows the usr merge that many modern Linux distros follow. Where /bin -> /usr/bin, /sbin -> /usr/sbin and /lib -> /usr/lib

mkdir -pv ~/minidistro && cd ~/minidistro/
mkdir -pv packages
mkdir -pv rootfs && cd rootfs

export ROOTFS=$HOME/minidistro/rootfs

# Core top-level directories
mkdir -vp "$ROOTFS"/{boot,dev,proc,sys,run,tmp,home,mnt,etc,opt}

# Our init dir
mkdir -vp "$ROOTFS"/etc/init.d

# /usr — merged hierarchy, single-lib
mkdir -vp "$ROOTFS/usr"/{bin,sbin,lib,share}

# /var — variable data
mkdir -vp "$ROOTFS/var"/{log,run,cache,tmp,lib}

# Permissions
chmod 0755 "$ROOTFS"
chmod 1777 "$ROOTFS/tmp" "$ROOTFS/var/tmp"

# symlink our rootlevel dirs for backwards compatibility
ln -sv usr/bin "$ROOTFS/bin"
ln -sv usr/sbin "$ROOTFS/sbin"
ln -sv usr/lib "$ROOTFS/lib"

Your root filesystem should look the following. While this is more than we need for a simple busybox distro, it provides the ground work to expand upon later.

/$HOME/minidistro/rootfs
├── bin -> usr/bin
├── boot
├── dev
├── etc
├── home
├── lib -> usr/lib
├── mnt
├── opt
├── proc
├── run
├── sbin -> usr/sbin
├── sys
├── tmp
├── usr
│   ├── bin
│   ├── lib
│   ├── sbin
│   └── share
└── var
    ├── cache
    ├── lib
    ├── log
    ├── run
    └── tmp

Grabbing the Source Code

cd $HOME/minidistro/packages

wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.19.tar.xz
wget https://busybox.net/downloads/busybox-1.37.0.tar.bz2

Building the Kernel

It’s finally time to start building our kernel. Today we’ll be targeting a default config as it will have everything necessary to get us running inside a bios qemu session. There are many options that can be configured and it’s a good learning opportunity to read through the options and play around. The end result is the bzImage that we’ll use as our kernel.

tar -xf linux-6.19.tar.xz
cd linux-6.19
make defconfig
make -j$(nproc) bzImage
cp -vr arch/x86/boot/bzImage $ROOTFS/boot

Building Busybox

At a minimum you need to have a shell, coreutils, init and build it statically. You must disable tc for static builds. Otherwise the question is what utilities you desire to have.

cd ..
tar -xf busybox-1.37.0.tar.bz2
cd busybox-1.37.0

# A weird bug in the code where I had to run make menuconfig and let it fail then
# patch it with sed and then rerun make menuconfig
make menuconfig

# To get busybox make menuconfig to work
sed -i 's/^\(always\s*:=[^#]*\)$/#\1/' scripts/kconfig/lxdialog/Makefile

# Configure busybox. You must disable under Networking Utilities -> tc for static builds to work
# Static build option is Settings -> Build static binary (no shared libs)
make menuconfig

# Build and install
make -j$(nproc)
make install

# cd into busybox default installer dir
cd ./_install

cp -apvr bin/* $ROOTFS/usr/bin/
cp -apvr sbin/* $ROOTFS/usr/sbin/
cp -apvr usr/bin/* $ROOTFS/usr/bin/
cp -apvr usr/sbin/* $ROOTFS/usr/sbin/
cp -apvr linuxrc $ROOTFS/

# Even though busybox is in the ./_install directory we still need to copy it over
cp -apvr bin/busybox $ROOTFS/bin

Init Scripts

The last thing we need before we can assemble our .img is our init scripts. We merely setup our system directories proc, sys, dev, dev/pts and fire off a POSIX compatible sh.

# ---- inittab ----
cat > "$ROOTFS/etc/inittab" <<'EOF'
::sysinit:/usr/bin/mount -t proc proc /proc
::sysinit:/usr/bin/mount -t sysfs sys /sys
::sysinit:/usr/bin/mount -t devtmpfs dev /dev
::sysinit:/usr/bin/mkdir -p /dev/pts
::sysinit:/usr/bin/mount -t devpts devpts /dev/pts
::sysinit:/usr/bin/sh -c 'dmesg > /var/log/dmesg'
::respawn:/usr/bin/sh
EOF

# Copy busybox as our static init binary
cp -apvr $ROOTFS/usr/bin/busybox $ROOTFS/usr/sbin/init

Assembling the Disk Image

cd $ROOTFS/..

dd if=/dev/zero of=disk.img bs=1M count=1024
sudo mkfs.ext4 disk.img

mkdir -pv mntimg
sudo mount disk.img mntimg

sudo mkdir -vp mntimg/boot/extlinux
sudo cp -avpr $ROOTFS/* mntimg/

# Copy Syslinux modules - Arch Linux
sudo cp -vr /usr/lib/syslinux/bios/*.c32 mntimg/boot/extlinux/

# Copy Syslinux modules - Slackware
sudo cp -vr /usr/share/syslinux/*.c32 mntimg/boot/extlinux/

# If unsure, find them
find / -name "*.c32" 2>/dev/null

cat >> extlinux.conf << 'EOF'
DEFAULT linux
LABEL linux
    KERNEL /boot/bzImage
    APPEND root=/dev/sda rw init=/usr/sbin/init console=ttyS0
    TEXT HELP
        Boot the minimal playground kernel with your static rootfs.
    ENDTEXT
EOF

sudo cp -apvr extlinux.conf mntimg/boot/extlinux/extlinux.conf

sudo extlinux -i mntimg/boot/extlinux
sync
sudo umount mntimg

Booting in QEMU

qemu-system-x86_64 -cpu host -enable-kvm -m 2G -drive file=disk.img,format=raw -nographic -serial mon:stdio

Sincerely, a concrete worker.
May the peace and grace of our Lord be with you.

New Blog!