Vagrant hangs with message “Waiting for domain to get an IP address…”

The problem may be (it was in my case) that there are firewall rules preventing NAT firewall rules for the virbr0 network device created by Vagrant via libvirt, which may look like the following (excerpt of iptables -L -n):

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination
ACCEPT     all  --  0.0.0.0/0            192.168.121.0/24     ctstate RELATED,ESTABLISHED
ACCEPT     all  --  192.168.121.0/24     0.0.0.0/0

A possible fix is to disable those restrictive rules (for example, clear all iptables rules before starting the Vagrant machine).


Wake-on-LAN (WoL) in Linux is disabled by default. How to enable it without ethtool.

Accomplishing the simple often takes a day or two.

According to countless tutorials on the internet, in order for a working WoL, you need to fulfill the following preconditions (where point 4 below is rarely mentioned [that’s why this article exists]):

  1. An ethernet hardware/card supporting WoL
  2. Enabling WoL in the BIOS
  3. Enabling the WoL feature on the ethernet card (usually using the user space tool ethtool)
  4. Enabling the WoL feature in the Linux kernel (and this was the major pitfall for me)

Support of WoL can be checked for a given ethernet card in the following way:

ethtool <card_label> | grep Wake-on

According to my own experiments on my local hardware, WoL seems to always be enabled by default:

Supports Wake-on: pumbg
Wake-on: g 

Because it already prints Wake-on: g, signalling that WoL is enabled (for Magic Packets), one is tempted to not run ethtool -s <card_label> wol g to achieve the same.

However, this ethtool command does at least one other thing, being equivalent to the following:

echo enabled > /sys/class/net/<card_label>/device/power/wakeup

And the main pitfall is: After a reboot, cat /sys/class/net/<card_label>/device/power/wakeup will return disabled.

That is why attempts to make WoL work without ethtool may fail. The feature will stay disabled in the Linux kernel, which will completely shut off the ethernet card in most powersave and power-off states. (I verified this observing the LEDs of a connected ethernet switch).

Conclusion

On modern Linux systems, given supporting hardware, it seems to be enough to write the string enabled to /sys/class/net/<card_label>/device/power/wakeup to make WoL work. ethtool doesn’t seem to be required any more for that, since Ethernet cards seem to already be configured properly by default. The Ethernet card will stay powered in most power-save and power-off states, so that it can receive Wake-on-LAN packets.

Resources

https://www.kernel.org/doc/html/v4.14/driver-api/pm/devices.html
https://www.kernel.org/doc/Documentation/power/states.txt

How to mount Google Drive in KDE’s Dolphin file manager

While this is not a filesystem mount via the Linux kernel (such as I just described in a previous blog post), KIO GDrive (part of KDE) enables KIO-aware applications (such as the Dolphin file manager, Kate editor, or Gwenview image viewer) to access, navigate, and edit Google Drive files.

kio-gdrive is available as a package in several Linux distributions. If installed, the Dolphin file manager will get an entry “Google Drive” under “Network”. There, an unprivileged desktop user can ‘mount’ a GoogleDrive account via a guided graphical configuration (during which the default browser will be opened where one needs to give KDE KAccounts permission to access the GoogleDrive account).

This method doesn’t provide access to GoogleDrive via a terminal, but it integrates it nicely with a graphical desktop. But the best part is that you don’t have to be root/superuser in order to do this, nor do you have to use the command line or write configuration files!

The following screenshots will walk you through the entire process!

Install kio-gdrive (on Debian: apt install kio-gdrive). After installation, Dolphin file manager immediately should get an entry called “Google Drive” under “Network” (see also featured image of this blog post).


Click on “New account” under “Google Drive”. A dialog window opens:

Click on “+ Create” and then on “Google”. It now will ask you to allow access to your Google Drive. A small web frame should open.

Enter your Google credentials and proceed until you are asked to give access to KDE KAccounts Provider.

Once you give permission, you will see:

You will get back to the former dialog:

Exit this dialog and you will already be able to browse your files!

How to mount Google Drive as a file system in Linux

This was surprisingly simple thanks to the excellent google-drive-ocamlfuse project!

For Debian 10 “Buster”, the steps are as follows:

As root:

apt install opam

apt install libcurl4-gnutls-dev libfuse-dev libgmp-dev libsqlite3-dev m4 zlib1g-dev # dependencies for google-drive-ocamlfuse

Then, as unprivileged user, you can install google-drive-ocamlfuse into ~/.opam:

opam init
eval `opam config env` # set needed environment variables
opam update
opam install depext
opam depext google-drive-ocamlfuse
opam install google-drive-ocamlfuse

This compiles a native binary ~/.opam/system/bin/google-drive-ocamlfuse .

The first time, simply run this binary without arguments:

google-drive-ocamlfuse

This will start your default browser where you have to authorize gdfuse to access your Google Drive.

Then, mounting your actual Google Drive is as simple as running

google-drive-ocamlfuse ~/path/to/where/to/mount
ls -l ~/path/to/where/to/mount

Voila! Problem solved in 10 minutes!

 

 

Running a graphical window program via SSH on a remote machine (with GPU hardware acceleration)

Note 1: Even though it’s mid-2018, this post is still about the X Window System. Things still are in the transition phase towards Wayland, and things might get better or different over time.

Note 2: This post is not about displaying a graphical window of a program running on a remote machine on the local machine (like VNC or X forwarding). It is about running a remote program and displaying its graphical window on the remote machine itself, as if it had been directly started by a user sitting in front of the remote display. One obvious use case for the solution to this problem would be a remote graphics rendering farm, where programs must make use of the GPU hardware acceleration of the machine they’re running on.

Note that graphical programs started via Xvfb or via X login sessions on fake/software displays (started by some VNC servers) will not use GPU hardware acceleration. The project VirtualGL might be a viable solution too, but I haven’t looked into that yet.

Some experiments on localhost

I’m going to explore the behavior of localhost relative to our problem first. You’ll  need to be logged in to an X graphical environment with monitor attached.

The trivial case: No SSH login session

Running a local program with a graphical window from a local terminal on a local machine is trivial when you are logged into the graphical environment: For example, in a terminal, simply type glxgears and it will run and display with GPU hardware acceleration.

With SSH login session to the same user

Things become a bit more interesting when you use SSH to connect to your current user on localhost. Let’s say your local username is “me”. Try

ssh me@localhost
glxgears

It will output:

Error: couldn't open display (null)

This can be fixed by setting the DISPLAY variable to the same value that is set for the non-SSH session:

DISPLAY=:0 glxgears

Glxgears will run at this point.

With SSH login session to another user

Things become even more interesting when you SSH into some other local user on localhost, called “other” below.

ssh other@localhost
glxgears

You will get the message:

Error: couldn't open display (null)

Trying to export DISPLAY as before won’t help us now:

DISPLAY=:0 glxgears

You will receive the message:

No protocol specified 
Error: couldn't open display :0

This is now a permission problem. There are two solutions for it:

Solution 1: Relax permissions vIA XHOST PROGRAM

To allow non-networked connections to the X server, you can run (as user “me” which is currently using the X environment):

xhost + local:

Then DISPLAY=:0 glxgears will start working as user “other”.

For security reasons, you should undo what you just did:

xhost - local:

Settings via xhost are not permanent across reboots.

Solution 2: via Xauthority file

If you don’t want or can’t use the xhost program, there is a second way (which I like better because it only involves files and file permissions):

User “me” has an environment variable env | grep XAUTHORITY

XAUTHORITY=/run/user/1000/gdm/Xauthority

(I’m using the gdm display manager. The path could be different in your case.)

This file contains a secret which is readable only for user “me”, for security reasons. As a quick test, make this file available world-readable in /tmp:

cp /run/user/1000/gdm/Xauthority /tmp/xauthority_me
chmod a+r /tmp/xauthority_me

Then, as user “other”:

DISPLAY=:0 XAUTHORITY=/tmp/xauthority_me glxgears

Glxgears will run again.

To make sure that we are using hardware acceleration, run glxinfo:

XAUTHORITY=/tmp/xauthority_me DISPLAY=:0 glxinfo | grep Device

This prints for me:

Device: Mesa DRI Intel(R) HD Graphics 630 (Kaby Lake GT2)  (0x5912)

Make sure you remove /tmp/xauthority_me after this test.

Note that the Xauthority file is different after each reboot. But it should be trivial to make it available to other users in a secure way if done properly.

Application on remote machine

If you were able to make things work on the local machine, the same steps should work on a remote machine, too. To clarify, the remote machine needs:

  • A real X login session active (you will likely need to set up auto-login in your display manager if the machine is not accessible).
  • A real monitor attached. Modern graphics cards and/or BIOSes simply shut down the GPU to save power when there is no real device attached to the HDMI port. This is is not Linux or driver specific. Instead of real monitors, you probably want to use “HDMI emulator” hardware plugs – they are cheap-ish and small. Otherwise, the graphical window might not even get painted into the graphics memory. The usual symptom is a black screen when using VNC.

Summary

If you SSH-login into the remote machine, as the user that is currently logged in to the X graphical environment, you can just set the DISPLAY environment variable when running a program, and the program should show on the screen.

If you SSH-login into the remote machine, as a user that is not currently logged in to the X graphical environment, but some other user is, you can set both DISPLAY and XAUTHORITY environment variables as explained further above, and the program should show up on the screen.

Related Links

https://serverfault.com/questions/186805/remote-offscreen-rendering

https://stackoverflow.com/questions/6281998/can-i-run-glu-opengl-on-a-headless-server#8961649

https://superuser.com/questions/305220/issue-with-vnc-when-there-is-no-monitor

https://askubuntu.com/questions/453109/add-fake-display-when-no-monitor-is-plugged-in

https://software.intel.com/en-us/forums/intel-business-client-software-development/topic/279956

“Green Energy” complication: gdm3 suspends machine after 20 minutes: A solution

I just posted a bug report on https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=896083

The solution to the problem is further down in the bug report.

Dear Maintainer,

gdm3’s default dconf energy settings suspend the machine after 20 minutes.

This is independent of the power settings made by an unprivileged user within a Gnome login session.

While this could be forgiven on a locally accessible desktop machine, it also suspends remote/headless machines (e.g. in a data center). Activity on a SSH terminal or VNC connection does not prevent this issue. Having no easy way to re-wake remote machines, this may create highly inconvenient situations for administrators. In addition, unexpected suspension may also have disastrous consequences, depending on the use of the machine.

To reproduce, install task-gnome-desktop and wait for 20 minutes on a machine which supports power management.

The offending settings can be printed to the console. As superuser:

su -s /bin/bash Debian-gdm

unset XDG_RUNTIME_DIR

dbus-launch gsettings get org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type

dbus-launch gsettings get org.gnome.settings-daemon.plugins.power sleep-inactive-ac-timeout

This prints ‘suspend’ and ‘1200’, respectively.

For quicker reproduction of the problem, reduce the timeout to 2 minutes:

dbus-launch gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-timeout 120

Then reboot and wait 2 minutes.

To turn off suspension, set:

dbus-launch gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type nothing

Regards,
Michael Franzl

HTML5 + JavaScript + CSS3 RGBA video overlays on top of live GStreamer video pipelines

GStreamer comes with a number of plugins that allow rendering of text and/or graphics overlays on top of video: rsvgoverlay, subtitleoverlay, textoverlay, cairooverlay, gdkpixbufoverlay, opencvtextoverlay, etc. However, some of these plugins often allow only static graphics and text, and often do not approach the flexibility and power of dedicated video post-processing software products.

“noweffects” (a play on the name of a popular video post-processing software) is a proof-of-concept of leveraging the power of a modern HTML5 + JavaScript + CSS3 web browser engine to render high-quality, programmable, alpha-aware, animated, vector- and bitmap based content, which is then rendered into an RGBA raw video stream, which can then be transferred via some kind of IPC method to separate GStreamer processeses, where it can be composited with other content via GStreamers regular `compositor` or `videomixer` plugins.

Qt was chosen for its ease of integration of modern WebKit (QtWebKit) and GStreamer (qt-gstreamer), and its ability to render widgets to RGBA images. The QMainWindow widget is rendered in regular intervals to QImages in RGBA format, then inserted into a GStreamer pipeline via the `appsrc` plugin. This pipeline simply uses `udpsink` to multicast the raw video RTP packets on localhost to allow for multiple ‘subscribers’. A second GStreamer pipleline can then use `udpsrc` and apply the overlay.

Proof-of-concept code available at: https://github.com/michaelfranzl/noweffects

The following demonstration video was generated with “noweffects”: A website (showing CSS3 animations), rendered to an RGBA video via QtWebKit, then overlaid on top of a video test pattern in a separate GStreamer process.

How to digitize old VHS videos with an EasyCAP UTV007 USB converter on Linux

2018: VHS is dead! If you don’t have a functioning VHS player any more, your only option is to buy second-hand devices. But if you still have old, valuable VHS videos (e.g. family videos) you should digitize them today, as long as there are still working VHS players around.

Our goal is to feed the audio/video (AV) signals coming out of an old VHS player into an EasyCAP UTV007 USB video grabber, which can receive 3 RCA cables (yellow for Composite Video, white for left channel audio, red for right channel audio).

EasyCAP UTV007 USB video grabber

 

VHS players usually have a SCART output which lucklily carries all the needed signals.

SCART connector

Via a Multi AV SCART adapter you can output the AV signals into three separate RCA cables (male-to-male), and from there into the EasyCap video grabber. If your adapter should have an input/output switch, set it to “output”.

Multi AV Adapter outputting 3 RCA connectors (yellow for Composite Video, white for left channel audio, red for right channel audio)

The EasyCAP USB converter uses a UTV007 chip, which is supported by Linux out-of-the-box. (Who said that installing drivers is a pain in Linux???) After plugging the converter into an USB slot, you should get two additional devices:

  1. A video device called “usbtv”
  2. A sound card called “USBTV007 Video Grabber [EasyCAP] Analog Stereo”

Too see if you have the video device, run v4l2-ctl –list-devices . It will output something like:

usbtv (usb-0000:00:14.0-7): 
       /dev/video0

To see if you have the audio device, run

pactl list | grep -C 50 'Description: USBTV007'

It will output something like:

Source #1
        State: SUSPENDED
        Name: alsa_input.pci-0000_00_14.0-usb-0_7.analog-stereo
        Description: USBTV007 Video Grabber [EasyCAP] Analog Stereo
        Driver: module-alsa-card.c
        Sample Specification: s16le 2ch 48000Hz
        Channel Map: front-left,front-right
        Owner Module: 7
        Mute: no
        Volume: front-left: 65536 / 100% / 0.00 dB,   front-right: 65536 / 100% / 0.00 dB
                balance 0.00
        Base Volume: 65536 / 100% / 0.00 dB
        Monitor of Sink: n/a
        Latency: 0 usec, configured 0 usec
        Flags: HARDWARE DECIBEL_VOLUME LATENCY 
        Properties:
                alsa.resolution_bits = "16"
                device.api = "alsa"
                device.class = "sound"
                alsa.class = "generic"
                alsa.subclass = "generic-mix"
                alsa.name = "USBTV Audio Input"
                alsa.id = "USBTV Audio"
                alsa.subdevice = "0"
                alsa.subdevice_name = "subdevice #0"
                alsa.device = "0"
                alsa.card = "3"
                alsa.card_name = "usbtv"
                alsa.long_card_name = "USBTV Audio at bus 3 device 3"
                alsa.driver_name = "usbcore"
                device.bus_path = "pci-0000:00:14.0-usb-0:7"
                sysfs.path = "/devices/pci0000:00/0000:00:14.0/usb3/3-7/sound/card3"
                device.vendor.name = "Fushicai"
                device.product.name = "USBTV007 Video Grabber [EasyCAP]"
                device.string = "hw:3"
                device.buffering.buffer_size = "22120"
                device.buffering.fragment_size = "11060"
                device.access_mode = "mmap"
                device.profile.name = "analog-stereo"
                device.profile.description = "Analog Stereo"
                device.description = "USBTV007 Video Grabber [EasyCAP] Analog Stereo"
                module-udev-detect.discovered = "1"
                device.icon_name = "audio-card"
        Ports:
                analog-input: Analog Input (priority: 10000)
        Active Port: analog-input
        Formats:
                pcm

To quickly test if you are getting any video, use a webcam application of your choice (e.g. “cheese“) and select “usbtv” as video source under “Preferences”. Note that this will only get video, but no audio.

We will use GStreamer to grab video and audio separately, and mux them together into a container format.

Install GStreamer

To install GStreamer on Debian-based distributions (like Ubuntu), run

apt-get installgstreamer1.0-tools gstreamer1.0-alsa gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly

Test video with GStreamer

Now, test if you can grab the video with GStreamer. This will read the video from /dev/video0 (device name from v4l2-ctl –list-devices above) and directly output in a window:

gst-launch-1.0 v4l2src device=/dev/video0 ! autovideosink

Test audio with GStreamer

Now, test if you can grab the audio with GStreamer. This will read the audio from the ALSA soundcard ID hw:3 (this ID comes from the output of pactl list above) and output it to PulseAudio (should go to your currently selected speakers/headphones):

gst-launch-1.0 alsasrc device="hw:3" ! pulsesink

Convert audio and video into a file

If both audio and video tested OK separately, we now can grab them both at the same time, mux them into a container format, and output it to a file /tmp/vhs.mkv. I’m choosing Matroska .mkv containing H264 video and Ogg Vorbis audio:

gst-launch-1.0 -e \
matroskamux name="muxer" ! queue ! filesink location=/tmp/vhs.mkv \
v4l2src ! queue ! x264enc ! queue ! muxer. \
alsasrc device="hw:3" ! queue ! audioconvert ! queue ! vorbisenc ! queue ! muxer.

Record some video and then press Ctrl+C. The file /tmp/vhs.mkv should now have audio and video.

It would be nice if we could see the video as we are recording it, so that we know when it ends. The command below will do this:

gst-launch-1.0 -e \
matroskamux name="muxer" ! queue ! filesink location=/tmp/vhs.mkv async=false \
v4l2src ! tee name=mytee \
mytee. ! queue ! x264enc ! queue ! muxer. \
mytee. ! queue ! autovideosink \
alsasrc device="hw:3" ! queue ! audioconvert ! queue ! vorbisenc ! queue ! muxer.

You also can re-encode the video by running it through ffmpeg:

ffmpeg -i vhs.mkv -vb 700k -ab 100k seekable-vhs.mkv

You can adjust the video and audio bitrate depending on the type and length of video so that your file will not be too large. The nice side-effect is that the coarser the video encoding, the more of the fine-grained noise in the VHS video is smoothed out.

Voila! You now should be able to record and archive all your old family videos for posterity!

Digitization of VHS video with Gstreamer.

 

How to compile ezstream from source

Debian Stretch’s version of ezstream is currently a bit out of date. Here is how you compile ezstream from source to get the latest improvements and bugfixes. Not even the INSTALL file in the ezstream repo has all the steps:

apt-get install libshout3-dev libxml2-dev libtag1-dev libshout3-dev libvorbis-dev libogg-dev check libtag-extras-dev libtagc0-dev

git clone https://github.com/xiph/ezstream.git

cd ezstream

libtoolize --force
aclocal
autoheader
automake --force-missing --add-missing
autoconf
autoreconf -f

./configure
make
make install

Note that the configuration file structure has changed from what can be found on older blog posts on the internet. For example, to pipe OGG Vorbis data into ezstream without re-encoding, you can use something like teststream.xml:

<ezstream>
  <server>
    <hostname>media.example.com</hostname>
    <password>hackme</password>
  </server>
  
  <stream>
    <mountpoint>test.ogg</mountpoint>
    <format>Vorbis</format>
  </stream>
  
  <media>
    <type>stdin</type>
    <filename>stdin</filename>
    <stream_once>1</stream_once>
  </media>
</ezstream>

Then, to stream 30 seconds of brown noise with a sine sweep to an Icecast server for testing purposes:

sox --null -p synth 00:00:30 brownnoise synth 00:00:30 sine 300-3000 | \
sox -r 48k -t raw -e signed -b 16 -c 1 -V1 - -r 48000 -t ogg - | \
ezstream -vvc teststream.xml

 

Zero Client: Boot kernel and root filesystem from network with a Raspberry Pi2 or Pi3

A Zero Client is a computer that has nothing on its permanent storage but a bootloader. Rather, it loads everything from the network.

With the method presented in this article, you will be able to boot a Raspberry Pi into a full Debian OS with nothing more on the SD card other than the Raspberry firmware files and the u-boot bootloader on a FAT file system. The Linux kernel and the actual OS will be served over the local ethernet network.

We will only focus on the Raspberry Pi 3, but the instructions should work with minor adaptations also on a Pi 2.

The following instructions assume that you have already built…

  1. a full root file system for the Raspberry
  2. a u-boot binary, and
  3. a Linux kernel

… based on my previous blog post. Thus, you should already have the following directory structure:

~/workspace
  |- rpi23-gen-image
  |- linux
  |- u-boot
  |- raspberry-firmware

We will do all the work inside of the ~/workspace directory.

Preparation of the SD card

You will only need a small SD card with a FAT filesystem on it. The actual storage of files in the running OS will be transparently done over the network. Mount the filesystem on /mnt/sdcard and do the following:

Copy firmware

cp ./raspberry-firmware/* /mnt/sdcard

Copy u-boot bootloader

cp ./u-boot/u-boot.bin /mnt/sdcard

Create config.txt

config.txt is the configuration file read by the Raspberry firmware blobs. Most importantly, it tells the firmware what kernel to load. “Kernel” is a misleading term here, since we will boot u-boot rather than the kernel.

Create /mnt/sdcard/config.txt with the following contents:

avoid_warnings=2

# boot u-boot kernel
kernel=u-boot.bin

# run in 64bit mode
arm_control=0x200

# enable serial console
enable_uart=1

 

Make an universal boot script for the u-boot bootloader

To achieve maximum flexibility — to avoid the repetitive dance of manually removing the SD card, copying files to it, and re-inserting it — we will make an universal u-boot startup script that does nothing else than loading yet another u-boot script from the network. This way, there is nothing specific about the to-be-loaded Kernel or OS on the SD card at all.

Create a file boot.scr.mkimage  with the following contents:

setenv autoload no
setenv autostart no
dhcp

setenv serverip 192.168.0.250

tftp 0x100000 /netboot-${serial#}.scr

imi
source 0x100000

Replace the server IP with the actual static IP of your server. Note that this script does nothing else other than loading yet another script called netboot-${serial#}.scr  from the server. serial# is the serial number which u-boot extracts from the Raspberry Pi hardware. This is usually the ethernet network device HW address. This way, you can have separate startup scripts for several Raspberry Pi’s if you have more than one. To keep the setup simple, set the file name to something predictable.

Compile the script into an u-boot readable image:

./u-boot/tools/mkimage -A arm64 -O linux -T script \
-C none -a 0x00 -e 0x00 \
-d boot.scr.mkimage \
boot.scr

Copy boot.scr to the SD card:

cp boot.scr /mnt/sdcard

The SD card preparation is complete at this point. We will now focus on the serving of the files necessary for boot.

Preparation of the file server

Do all of the following as ‘root’ user on a regular PC running Debian 9 (“Stretch”). This PC will act as the “server”.  This server will serve the files necessary to network-boot the Raspberry.

The directory /srv/tftp will hold …

  • an u-boot start script file
  • the kernel uImage file
  • and the binary device tree file.

… to be served by a TFTP server.

mkdir /srv/tftp

The directory /srv/rootfs_rpi3 will hold our entire root file system to be served by a NFS server:

mkdir /srv/rootfs_rpi3

You will find installation instructions of both TFTP and NFS servers further down.

 

Serve the root file system

Let’s copy the pre-built root file system into the directory from where it will be served by the NFS server:

rsync -a ./rpi23-gen-image/images/stretch/build/chroot/ /srv/rootfs_rpi3

(notice the slash at the end of the source directory)

 

Fix the root file system for network booting

Edit /srv/rootfs_rpi3/etc/fstab  and comment out all lines. We don’t need to mount anything from the SD card.

When network-booting the Linux kernel, the kernel will configure the network device for us (either with a static IP or DHCP). Any userspace programs attempting to re-configure the network device will cause problems, i.e. a loss of conncection to the NFS server. Thus, we need to prevent systemd-networkd from managing the Ethernet device. Make the device unmanaged by removing the folowing ethernet configuration file:

rm /srv/rootfs_rpi3/etc/systemd/network/eth.network

If you don’t do that, you’ll get the following kernel message during boot:

nfs: server not responding, still trying

That is because systemd has shut down and then re-started the ethernet device. Apparently NFS transfers are sensitive to that.

In case you want to log into the chroot to make additional changes that can only be done from within (e.g. running systemctl scripts etc.), you can do:

cp /usr/bin/qemu-aarch64-static /srv/rpi3fs/usr/bin
LANG=C LC_ALL=C chroot /srv/rpi3fs

 

Serve Kernel uImage

In this step, we create a Linux kernel uImage that can be directly read by the u-boot bootloader. We read Image.gz directly from the Kernel source directory, and output it into the /srv/tftp directory where a TFTP server will serve it to the Raspberry:

./u-boot/tools/mkimage -A arm64 -O linux -T kernel \
-C gzip -a 0x80000 -e 0x80000 \
-d ./linux/arch/arm64/boot/Image.gz \
/srv/tftp/linux-rpi3.uImage

 

Serve device tree binary

The u-boot bootloader will also need to load the device tree binary and pass it to the Linux kernel, so copy that too into the /srv/tftp directory.

cp ./linux/arch/arm64/boot/dts/broadcom/bcm2837-rpi-3-b.dtb /srv/tftp/

 

Serve secondary u-boot script loading the kernel

Create a file netboot-rpi3.scr.mkimage with the following contents:

setenv autoload no
setenv autostart no
dhcp

setenv serverip 192.168.0.250

setenv bootargs "earlyprintk console=tty1 dwc_otg.lpm_enable=0 root=/dev/nfs rw rootfstype=nfs nfsroot=192.168.0.250:/srv/rpi3fs,udp,vers=3 ip=dhcp nfsrootdebug smsc95xx.turbo_mode=N elevator=deadline rootdelay cma=256M@512M net.ifnames=1 init=/bin/systemd loglevel=7 systemd.log_level=debug systemd.log_target=console"

tftp ${kernel_addr_r} linux-rpi3.uImage
tftp ${fdt_addr_r} bcm2837-rpi-3-b.dtb
bootm ${kernel_addr_r} - ${fdt_addr_r}

Replace the server IP with the static IP of your server PC. Then compile this script into an u-boot readable image and output it directly to the /srv/tftp directory:

./u-boot/tools/mkimage -A arm64 -O linux -T script \
-C none -a 0x00 -e 0x00 \
-d netboot-rpi3.scr.mkimage \
/srv/tftp/netboot-0000000012345678.scr

Make sure that the filename of the .scr file matches with whatever file name you’ve set in the universal .scr script that we’ve prepared further above.

 

Install a NFS server

The NFS server will serve the root file system to the Raspberry and provide transparent storage.

apt-get install nfs-kernel-server

Edit /etc/exports and add:

/srv/rootfs_rpi3  *(rw,sync,no_root_squash,no_subtree_check,insecure)

To apply the changed ‘exports’ configuration, run

exportfs -rv

Useful to know about the NFS server:

You can restart the NFS server by running service nfs-kernel-server restart

Configuration files are /etc/default/nfs-kernel-server  and /etc/default/nfs-common

 

Test NFS server

If you want to be sure that the NFS server works correctly, do the following on another PC:

apt-get install nfs-common

Mount the root file system (fix the static IP for your server):

mkdir /tmp/testmount
mount 192.168.0.250:/srv/rootfs_rpi3 /tmp/testmount
ls -al /tmp/testmount

 

 

Install a TFTP server

To install:

apt-get install tftpd-hpa

After installation, check if the TFTP server is running:

ps -ejHf | grep ftp

This command will tell you the default serving directory (/srv/tftp):

/usr/sbin/in.tftpd --listen --user tftp --address 0.0.0.0:69 --secure /srv/tftp

Here is another command that tells you if the TFTP server is listening:

netstat -l -u | grep ftp

To get help about this server: man tftpd

Test TFTP

If you want to be sure that the TFTP server works correctly, do the following on another PC:

apt-get install tftp-hpa

Then see if the server serves the Linux kernel we’ve installed before:

tftp 192.168.0.250
tftp> get linux-rpi3.uImage
tftp> quit

You now should have a local copy of the linux-rpi3.uImage file.

 

Complete

If you’ve done all of the above correctly, you can insert the prepared SD card into your Raspberry Pi and reboot it. The following will happen:

  1. The Raspberry Pi GPU will load the firmware blobs from the SD card.
  2. The firmware blobs will boot the image specified in config.txt. In our case, this is the u-boot binary on the SD card.
  3. The u-boot bootloader will boot.
  4. The u-boot bootloader loads and runs the universal boot.scr script from the SD card.
  5. The boot.scr downloads the specified secondary boot script from the network and runs it.
  6. The secondary boot script …
    • downloads the device tree binary from the network and loads it into memory.
    • downloads the Linux kernel from the network and loads it into memory
    • passes the device tree binary to the kernel, and boots the kernel
  7. the Linux kernel will bring up the ethernet device, connect to the NFS server, and load the regular OS from there.

Many things can go wrong in this rather long sequence, so if you run into trouble, check the Raspberry boot messages output on an attached screen or serial console, and the log files of the NFS and TFTP servers on your server PC.

 

Resources

https://www.raspberrypi.org/documentation/linux/kernel/building.md

http://www.whaleblubber.ca/boot-raspberry-pi-nfs/

https://cellux.github.io/articles/moving-to-nfs-root/

http://billauer.co.il/blog/2011/01/diskless-boot-nfs-cobbler/

https://www.kernel.org/doc/Documentation/filesystems/nfs/nfsroot.txt

http://wiki.linux-nfs.org/wiki/index.php/General_troubleshooting_recommendations

https://wiki.archlinux.org/index.php/NFS