Date Tags linux / bash

Since we have a few dozen raspberry pi to use for testing protocols in my research group, we needed some centralized way to set them up. After some googling, I decided to use read-only NFS for the root filesystem. On top of this, we put a read-write tmpfs to satisfy various applications, which need to write data to e.g. /var.

Initially, I wanted to use u-boot to fetch the kernel from tftp, but the rpi-edition of u-boot was unstable when doing networking. Instead I decided to put the kernel image on the boot partition as usual.

After some fiddling with cpio, nfs, and a lot more, I created this script:

rasp_aufs_and_nfs.sh download
#!/bin/bash

project_path=rasp

linux_path=linux
linux_ver=3.10
linux_url=https://github.com/raspberrypi/linux.git
linux_cross=~/arm-unknown-linux-gnueabi/bin/arm-unknown-linux-gnueabi-
linux_config_url=http://dl.hundeboll.net/rasp/rasp-aufs-kconfig-3.10

aufs_path=aufs3-standalone
aufs_ver=$linux_ver
aufs_url=git://git.code.sf.net/p/aufs/aufs3-standalone

bb_deb_url=http://archive.raspbian.org/raspbian/pool/main/b/busybox/busybox-static_1.20.0-9_armhf.deb
bb_deb_file=$(basename $bb_deb_url)
e2fs_deb_url=http://archive.raspbian.org/raspbian/pool/main/e/e2fsprogs/e2fsck-static_1.42.8-1_armhf.deb
e2fs_deb_file=$(basename $e2fs_deb_url)

cpio_path=initramfs
cpio_init_url=http://dl.hundeboll.net/rasp/rasp-aufs-init.sh
ntpc_path=ntpclient
ntpc_url=https://github.com/troglobit/ntpclient.git

boot_path=boot
boot_url=http://dl.hundeboll.net/rasp/rasp-boot.tar.gz
boot_file=$(basename $boot_url)
boot_config_url=http://dl.hundeboll.net/rasp/rasp-boot-cfg.txt
boot_cmdline_url=http://dl.hundeboll.net/rasp/rasp-boot-cmd.txt

nfsroot_path=/srv/nfsroot
nfsroot_url=http://dl.hundeboll.net/rasp/rasp-nfsroot.tar.gz
nfsroot_file=$(basename $nfsroot_url)
nfsroot_ip=172.26.72.172
nfsroot_opts="(ro,fsid=root,async,no_root_squash,no_subtree_check)"
nfsroot_net=172.26.0.0/16

# check cross compiler
if [ ! -x ${linux_cross}gcc ]; then
    echo "Please make sure you have cross compiler tool-chain installed"
    echo "and configured this script to use it:"
    grep -E "^linux_cross" $0
    exit 1
fi

# check for ar to extract .deb files
if ! which ar &> /dev/null; then
    echo "Please install 'ar'"
    exit 1
fi

# check for cpio to create initramfs
if ! which cpio &> /dev/null; then
    echo "Please install 'cpio'"
    exit 1
fi

# check for git to get sources
if ! which git &> /dev/null; then
    echo "Please install 'git'"
    exit 1
fi

# check for curl to download files
if ! which curl &> /dev/null; then
    echo "Please install curl"
    exit 1
fi

# check for bc needed by kernel compile
if ! which bc &> /dev/null; then
    echo "Please install bc"
    exit 1
fi

# check for /etc/exports needed by nfs
if [ ! -f /etc/exports ]; then
    echo "Please install nfs-utils or the like"
    exit 1
fi

# create a project folder
if [ ! -d $project_path ]; then
    mkdir -p $project_path || exit 1
fi
cd $project_path
project_path=$(pwd)

# get ntpclient source
if [ ! -d $ntpc_path ]; then
    git clone $ntpc_url $ntpc_path || exit 1
fi

# compile ntpclient with static libs
cd $ntpc_path
sed -i -E "s/CFLAGS([\t ]+)=/CFLAGS\1+=/" Makefile || exit 1
CC=${linux_cross}gcc CFLAGS="-static -static-libgcc" make || exit 1
cd ..

# create initramfs image
if [ ! -d $cpio_path ]; then
    mkdir -p $cpio_path || exit 1
fi

cd $cpio_path

# prepare fs layout
mkdir -p aufs bin dev etc lib proc rootfs rw sbin sys usr/{bin,sbin}
touch etc/mdev.conf
if [ ! -c dev/console ]; then sudo mknod -m 622 dev/console c 5 1 || exit 1; fi
if [ ! -c dev/tty0 ];    then sudo mknod -m 622 dev/tty0 c 4 0    || exit 1; fi
if [ ! -b dev/nfs ];     then sudo mknod -m 622 dev/nfs b 0 255   || exit 1; fi
if [ ! -c dev/null ];    then sudo mknod -m 622 dev/null c 1 3    || exit 1; fi

# install ntpclient
cp -a ../$ntpc_path/ntpclient bin/ntpclient || exit 1

# get busybox
if [ ! -f ../$bb_deb_file ]; then
    curl $bb_deb_url -o ../$bb_deb_file || exit 1
fi

# extract busybox
ar p ../$bb_deb_file data.tar.gz | \
    tar zxf - ./bin/busybox -O > bin/busybox || exit 1

# install busybox and sh
chmod +x bin/busybox || exit 1
if [ ! -h bin/sh ]; then ln -s busybox bin/sh || exit 1; fi

# get e2fsck.static
if [ ! -f ../$e2fs_deb_file ]; then
    curl $e2fs_deb_url -o ../$e2fs_deb_file || exit 1
fi

# extract and install e2fsck.static
ar p ../$e2fs_deb_file data.tar.gz | \
    tar zxf - ./sbin/e2fsck.static -O > sbin/e2fsck.static || exit 1
chmod +x sbin/e2fsck.static || exit 1

# get init script
if [ ! -f init ]; then
    curl $cpio_init_url -o init || exit 1
fi

# install init script
chmod +x init || exit 1

# create initramfs image
find . | cpio -H newc -o > ../initramfs.cpio
cd ..

# prepare escaped strings for sed
i=$(echo $nfsroot_ip | sed -e 's/[\/&]/\\&/g') || exit 1
p=$(echo $nfsroot_path | sed -e 's/[\/&]/\\&/g') || exit 1
n=$(echo $nfsroot_net | sed -e 's/[\/&]/\\&/g') || exit 1
o=$(echo $nfsroot_opts | sed -e 's/[\/&]/\\&/g') || exit 1

# get and extract nfs root files
if [ ! -f $nfsroot_file ]; then
    curl $nfsroot_url -o $nfsroot_file || exit 1
fi
#sudo tar pxf $project_path/$nfsroot_file -C $(dirname $nfsroot_path) || exit 1

# setup nfs exports
if grep -q $nfsroot_path /etc/exports; then
    sudo sed -E -i "s/^$p.*/$p $n$o/" /etc/exports || exit 1
else
    echo "$nfsroot_path $nfsroot_net$nfsroot_opts" | sudo tee -a /etc/exports || exit 1
fi
sudo exportfs -rav || exit 1

# get kernel source
if [ ! -d $linux_path ]; then
    git clone -b rpi-${linux_ver}.y $linux_url $linux_path || exit 1
fi

# get aufs source
if [ ! -d $aufs_path ]; then
    git clone -b aufs$aufs_ver $aufs_url $aufs_path || exit 1
fi

# setup and compile kernel
cd $linux_path

# patch kernel source
aufs_hdr_path=include/uapi/linux
git reset --hard HEAD
cp -a ../$aufs_path/fs .
cp -a ../$aufs_path/$aufs_hdr_path/aufs_type.h $aufs_hdr_path || exit 1
for f in aufs3-kbuild.patch aufs3-base.patch aufs3-mmap.patch; do
    git apply ../$aufs_path/$f || exit 1
done

# get config file
if [ ! -f .config ]; then
    curl $linux_config_url -o .config
fi

# configure kernel
make ARCH=arm CROSS_COMPILE=$linux_cross olddefconfig || exit 1
for o in CONFIG_BLK_DEV_INITRD \
         CONFIG_AUFS_FS \
         CONFIG_AUFS_EXPORT; do
    sed -i -E "s/.*${o}.*/${o}=y/" .config || exit 1

    # update config with new options
    echo update after $o
    make ARCH=arm CROSS_COMPILE=$linux_cross olddefconfig || exit 1
done
sed -i -E "s/.*(CONFIG_INITRAMFS_SOURCE).*/\1=\"..\/initramfs.cpio\"/" .config || exit 1
sed -i -E "s/.*(CONFIG_LOCALVERSION)(=| ).*/\1=\"-aufs\"/" .config || exit 1
sed -i -E "s/.*(CONFIG_LOCALVERSION_AUTO).*/\1=y/" .config || exit 1
make ARCH=arm CROSS_COMPILE=$linux_cross olddefconfig || exit 1
sed -i -E "s/.*(CONFIG_AUFS_BR_HFSPLUS).*/\1=n/" .config || exit 1

# now compile it
make ARCH=arm CROSS_COMPILE=$linux_cross -j$(nproc) || exit 1

# install modules to nfsroot
sudo make ARCH=arm CROSS_COMPILE=$linux_cross INSTALL_MOD_PATH=$nfsroot_path \
    modules_install || exit 1
cd ..

# prepare boot folder
if [ ! -f $boot_file ]; then
    curl $boot_url -o $boot_file || exit 1
fi
tar xf $boot_file || exit 1

# prepare boot files
cp $linux_path/arch/arm/boot/zImage $boot_path || exit 1
curl $boot_config_url -o $boot_path/config.txt || exit 1
curl $boot_cmdline_url -o $boot_path/cmdline.txt || exit 1
sed -i -E "s/nfsroot=[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[\/0-9a-zA-Z]+/nfsroot=$i:$p/" \
    $boot_path/cmdline.txt || exit 1
sed -i -E "s/ntphost=[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ntphost=$i/" \
    $boot_path/cmdline.txt || exit 1

# done :)
cat <<EOF

Done!

Now please start your nfs server and make sure it is listening on $nfsroot_ip

  On systemd-based distributions:
  $ sudo systemctl start rpc-idmapd
  $ sudo systemctl start rpc-mountd

  On debian/ubuntu:
  $ sudo service nfs-kernel-server start

Then copy the contents of
  $project_path/$boot_path
to the first partion on the sd-card and enjoy network booting with copy-on-write
overlay filesystem :)

EOF

The script compiles a Linux kernel with AUFS and a initramfs to do the NFS and AUFS mounting. It also downloads a tarball with the minimal raspbian install and unpacks it to /srv/nfsroot (unless changed).

When you have run the script, you can prepare the sdcard using this script:

rasp_setup_sdcard.sh download
#!/bin/bash

dev=$1
path=$2

if [ "$dev" == "" ]; then
    echo "Please specify device to setup"
    exit 1
fi

if [ ! -b $dev ]; then
    echo "$dev is not a block device"
    exit 1
fi

if [ "$dev" == "/dev/sda" ]; then
    echo "I refuse to work with $dev"
    exit 1
fi

if [ "$path" == "" ] || [ ! -d $path ]; then
    echo "Please specify a folder with boot content"
    exit 1
fi

echo Configuring $dev
echo Using $path

# clear and add fat partition for boot
(echo o; echo n; echo p; echo 1; echo ; echo +100M; echo t; echo b; echo w) | sudo fdisk $dev || exit 1

# add linux partition for home
(echo n; echo p; echo 2; echo ; echo ; echo w) | sudo fdisk $dev || exit 1

# make fat fs
sudo mkfs.vfat -n BOOT ${dev}1 || exit 1

# make ext4 fs
sudo mkfs.ext4 -L HOME ${dev}2 || exit 1

# mount and copy contents to boot
sudo mount ${dev}1 /mnt || exit 1
sudo cp $path/* /mnt/ || exit 1
sudo umount /mnt || exit 1

# mount and create home folder on home
sudo mount ${dev}2 /mnt || exit 1
sudo mkdir /mnt/pi || exit 1
sudo chown -R 1000:1000 /mnt/pi || exit 1
sudo umount /mnt || exit 1

echo "Done"

There might be something I have missed, but the scripts can also be used for inspiration.