<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>It's Pi all the way down... - net</title><link href="https://waldorf.waveform.org.uk/" rel="alternate"></link><link href="https://waldorf.waveform.org.uk/feeds/net.atom.xml" rel="self"></link><id>https://waldorf.waveform.org.uk/</id><updated>2024-12-03T15:52:56+00:00</updated><entry><title>I ain’t got NBD</title><link href="https://waldorf.waveform.org.uk/2024/i-aint-got-nbd.html" rel="alternate"></link><published>2024-12-03T00:00:00+00:00</published><updated>2024-12-03T15:52:56+00:00</updated><author><name>Dave Jones</name></author><id>tag:waldorf.waveform.org.uk,2024-12-03:/2024/i-aint-got-nbd.html</id><summary type="html">&lt;p class="first last"&gt;The final part of the &lt;span class="caps"&gt;NBD&lt;/span&gt; netboot series, where we solve our issues
with the coherence of the boot&amp;nbsp;partition&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://waldorf.waveform.org.uk/2023/nbd-does-it-better.html"&gt;Last time&lt;/a&gt; we set up an &lt;abbr title="Network Block Device"&gt;&lt;span class="caps"&gt;NBD&lt;/span&gt;&lt;/abbr&gt; server, and the associated &lt;abbr title="Dynamic Host Configuration Protocol"&gt;&lt;span class="caps"&gt;DHCP&lt;/span&gt;&lt;/abbr&gt; machinery to netboot our Pi. We then discovered a
rather serious problem: when the Pi updated the content of the boot partition,
the &lt;abbr title="Trivial File Transfer Protocol"&gt;&lt;span class="caps"&gt;TFTP&lt;/span&gt;&lt;/abbr&gt; wouldn&amp;#8217;t notice and would
serve old files because the mount of the boot partition on the server didn&amp;#8217;t
realize it was &amp;#8220;shared&amp;#8221; with the &lt;span class="caps"&gt;NBD&lt;/span&gt;&amp;nbsp;server.&lt;/p&gt;
&lt;div class="section" id="come-and-take-a-chance-with-me"&gt;
&lt;h2&gt;Come and take a chance with&amp;nbsp;me?&lt;/h2&gt;
&lt;p&gt;As mentioned in the last post, in trying to solve this I ventured through a
myriad of hacky or simple solutions, all attempting to work around the
&amp;#8220;caching&amp;#8221; of pages by the file-system driver on the server side. Yet again,
caching rears its head as one of the two &amp;#8220;hard problems&amp;#8221; of computer science
&lt;a class="footnote-reference" href="#hard" id="footnote-reference-1"&gt;[1]&lt;/a&gt;, though the root of the issue here is the assumption that the
file-system driver has exclusive access to the underlying block device (the
cache is merely a consequence of&amp;nbsp;this).&lt;/p&gt;
&lt;p&gt;In hindsight, I should&amp;#8217;ve looked at the pieces and concluded what I eventually
did: just write your own &lt;span class="caps"&gt;TFTP&lt;/span&gt; server! One which can read files directly out of
the boot partition of the &lt;span class="caps"&gt;OS&lt;/span&gt; image. This solution sounds like a sledgehammer to
crack a nut, but actually it&amp;#8217;s less complex than it seems. All it&amp;#8217;s got to
handle is simple partitioning (&lt;abbr title="Master Boot Record"&gt;&lt;span class="caps"&gt;MBR&lt;/span&gt;&lt;/abbr&gt; or &lt;abbr title="GUID Partition Tables"&gt;&lt;span class="caps"&gt;GPT&lt;/span&gt;&lt;/abbr&gt;), reading a &lt;abbr title="File Allocation Table"&gt;&lt;span class="caps"&gt;FAT&lt;/span&gt;&lt;/abbr&gt;
file-system , and &lt;abbr title="Trivial File Transfer Protocol"&gt;&lt;span class="caps"&gt;TFTP&lt;/span&gt;&lt;/abbr&gt;.&lt;/p&gt;
&lt;p&gt;I may not be selling this as &amp;#8220;simple&amp;#8221;, but let&amp;#8217;s break down what each of these
actually&amp;nbsp;entails:&lt;/p&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;&lt;span class="caps"&gt;MBR&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Master_boot_record"&gt;Master Boot Record&lt;/a&gt; partitioning is an ancient method of partitioning a
&lt;span class="strike"&gt;floppy disk&lt;/span&gt; &lt;span class="strike"&gt;hard drive&lt;/span&gt; &lt;span class="strike"&gt;&lt;span class="caps"&gt;USB&lt;/span&gt; stick&lt;/span&gt; small
storage medium, but while it&amp;#8217;s very difficult to accurately &lt;em&gt;write&lt;/em&gt; (in
such a way that it&amp;#8217;ll actually … boot things), it&amp;#8217;s not hard to &lt;em&gt;read&lt;/em&gt;
(other than the minor mess that is logical partitioning, intended to get
around the original 4-partition limit). A few hours work.&amp;nbsp;Maybe.&lt;/dd&gt;
&lt;dt&gt;&lt;span class="caps"&gt;GPT&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/GUID_Partition_Table"&gt;&lt;span class="caps"&gt;GUID&lt;/span&gt; Partition Tables&lt;/a&gt; are the modern, and frankly trivial (by comparison
to &lt;span class="caps"&gt;MBR&lt;/span&gt;) means of partitioning storage media. No more than an hour needed&amp;nbsp;here.&lt;/dd&gt;
&lt;dt&gt;&lt;span class="caps"&gt;FAT&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p class="first"&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/File_Allocation_Table"&gt;File Allocation Tables&lt;/a&gt; are another positively ancient piece of computing
history, used for storing files on just about every piece of computing
media with the possible exception of the tape drive (and I wouldn&amp;#8217;t put it
past someone to have tried that). The file-system isn&amp;#8217;t complicated in
basis (in fact I&amp;#8217;ve written a library for something very similar before
&lt;a class="footnote-reference" href="#beenthere" id="footnote-reference-2"&gt;[2]&lt;/a&gt;); the complexity comes from the myriad implementations that
use the same structures, but disagree subtly (and not so subtly) about the
meaning or even position of certain fields within&amp;nbsp;them.&lt;/p&gt;
&lt;p class="last"&gt;Still, a few evenings to get the basics done, then several more to work out
the rough edges (&lt;span class="caps"&gt;FAT&lt;/span&gt;-12, long filename handling, etc. etc.), then a few
more to build a nice Path-like &lt;span class="caps"&gt;API&lt;/span&gt; on top of it, and we&amp;#8217;re&amp;nbsp;good!&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;span class="caps"&gt;TFTP&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;a class="reference external" href="https://datatracker.ietf.org/doc/html/rfc1350"&gt;Trivial File Transfer Protocol&lt;/a&gt; is, tautologically, trivial. Getting the
basic protocol implemented took only a couple of hours (including testing
with several implementations). Then it was onto the extensions; I wound up
only bothering with those that the Pi bootloader attempts to negotiate and
one more that&amp;#8217;s useful for testing (&lt;tt class="docutils literal"&gt;tsize&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;blocksize&lt;/tt&gt;, and
&lt;tt class="docutils literal"&gt;timeout&lt;/tt&gt;) though hopefully I&amp;#8217;ve structured things suitably for a future
addition of the &lt;tt class="docutils literal"&gt;windowsize&lt;/tt&gt; option. Still, only a few more hours on&amp;nbsp;that.&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;All in all, despite a few headaches (mostly around &lt;span class="caps"&gt;FAT&lt;/span&gt;&amp;#8217;s obscure history), it
wasn&amp;#8217;t difficult to throw all this together, stir it around a bit, and produce
what I needed (would&amp;#8217;ve been nice to make it into a &amp;#8220;learn Rust&amp;#8221; project, but I
was already &lt;em&gt;waaay&lt;/em&gt; overdue on this post series, so I wound up going with my
comfortable old&amp;nbsp;Python).&lt;/p&gt;
&lt;p&gt;The result is &lt;a class="reference external" href="https://github.com/waveform80/nobodd"&gt;nobodd&lt;/a&gt;: a &lt;span class="caps"&gt;TFTP&lt;/span&gt; boot-server that will read files directly out
of the &lt;span class="caps"&gt;FAT&lt;/span&gt; partition of an image without mounting&amp;nbsp;it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="nbd-nbd"&gt;
&lt;h2&gt;&lt;span class="caps"&gt;NBD&lt;/span&gt;! &lt;span class="caps"&gt;NBD&lt;/span&gt;!&lt;/h2&gt;
&lt;p&gt;Let&amp;#8217;s walk through a set up using &lt;tt class="docutils literal"&gt;nobodd&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nbd-server&lt;/span&gt;&lt;/tt&gt;. The client
side is exactly the same as before so I&amp;#8217;ll just refer you to the start of the
&lt;a class="reference external" href="https://waldorf.waveform.org.uk/2023/nbd-does-it-better.html"&gt;last post&lt;/a&gt; for that part. Go through
requirements (a Pi and a server), configuring the Pi for netboot, adding
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;linux-modules-extra-&lt;/span&gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nbd-client&lt;/span&gt;&lt;/tt&gt;, and transferring the regenerated
initramfs and identity to the server, and shutting down the Pi &lt;a class="footnote-reference" href="#headings" id="footnote-reference-3"&gt;[3]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now, on the server we&amp;#8217;re going to do things a bit differently. This time we&amp;#8217;re
going to configure a &amp;#8220;template&amp;#8221; image that we can copy to &amp;#8220;instance&amp;#8221; images any
time we want a new install. And this time we&amp;#8217;re going to use the fancy little
&lt;a class="reference external" href="https://nobodd.readthedocs.io/en/stable/cli_prep.html"&gt;nobodd-prep&lt;/a&gt; tool from &lt;tt class="docutils literal"&gt;nobodd&lt;/tt&gt; to do&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Start as we did before by downloading the image and verifying&amp;nbsp;it:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Password:
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;/srv/images&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/srv/images&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;wget&lt;span class="w"&gt; &lt;/span&gt;http://cdimage.ubuntu.com/releases/22.04.3/release/ubuntu-22.04.3-preinstalled-server-arm64+raspi.img.xz&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt; ...
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;wget&lt;span class="w"&gt; &lt;/span&gt;http://cdimage.ubuntu.com/releases/22.04.3/release/SHA256SUMS&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt; ...
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;sha256sum&lt;span class="w"&gt; &lt;/span&gt;--check&lt;span class="w"&gt; &lt;/span&gt;--ignore-missing&lt;span class="w"&gt; &lt;/span&gt;SHA256SUMS&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;ubuntu-22.04.3-preinstalled-server-arm64+raspi.img.xz: OK
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;SHA256SUMS
&lt;/pre&gt;
&lt;p&gt;Now, we simply use &lt;a class="reference external" href="https://nobodd.readthedocs.io/en/stable/cli_prep.html"&gt;nobodd-prep&lt;/a&gt; to customize it. This tool can be used to
resize the image, re-write the &lt;tt class="docutils literal"&gt;cmdline.txt&lt;/tt&gt; parameters, and copy files onto
the boot partition (including the customized &lt;tt class="docutils literal"&gt;initrd.img&lt;/tt&gt; and a cloud-init
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;user-data&lt;/span&gt;&lt;/tt&gt; file), all in one go. It can only operate on uncompressed images
though, so we still need to do&amp;nbsp;that:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;# &lt;/span&gt;unxz&lt;span class="w"&gt; &lt;/span&gt;ubuntu-22.04.3-preinstalled-server-arm64+raspi.img.xz&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;mv&lt;span class="w"&gt; &lt;/span&gt;ubuntu-22.04.3-preinstalled-server-arm64+raspi.img&lt;span class="w"&gt; &lt;/span&gt;jammy-template.img&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;nobodd-tools&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt; ...
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;&amp;lt;&lt;span class="w"&gt; &lt;/span&gt;EOF&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;user-data&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;package_update: true
packages:
- avahi-daemon
- nbd-client
- linux-modules-extra-raspi
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;jammy-template.img&lt;span class="w"&gt; &lt;/span&gt;jammy.img&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;nobodd-prep&lt;span class="w"&gt; &lt;/span&gt;--copy&lt;span class="w"&gt; &lt;/span&gt;user-data&lt;span class="w"&gt; &lt;/span&gt;--copy&lt;span class="w"&gt; &lt;/span&gt;initrd.img&lt;span class="w"&gt; &lt;/span&gt;--size&lt;span class="w"&gt; &lt;/span&gt;16GB&lt;span class="w"&gt; &lt;/span&gt;jammy.img
&lt;/pre&gt;
&lt;p&gt;And that&amp;#8217;s it! My hope is that, in noble (24.04) most of these steps will not
be necessary. In that release, it &lt;em&gt;should&lt;/em&gt; be possible to download the image
unpack it, give it a nice name, run &lt;a class="reference external" href="https://nobodd.readthedocs.io/en/stable/cli_prep.html"&gt;nobodd-prep&lt;/a&gt; to customize it, and go
from&amp;nbsp;there.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hi, Future Dave here! Well, the &lt;abbr title="Main Inclusion Request"&gt;&lt;span class="caps"&gt;MIR&lt;/span&gt;&lt;/abbr&gt; for
&lt;a class="reference external" href="https://bugs.launchpad.net/ubuntu/+source/nbd/+bug/2054480"&gt;nbd-client&lt;/a&gt;, which I had hoped would be a simple affair, erm, wasn&amp;#8217;t
&lt;a class="footnote-reference" href="#easy" id="footnote-reference-4"&gt;[4]&lt;/a&gt;. It didn&amp;#8217;t make it into noble (24.04), or oracular (24.10). It
&lt;em&gt;might&lt;/em&gt; make it into plucky (25.04), thanks largely to the efforts of my
new side-kick &lt;a class="footnote-reference" href="#sidekick" id="footnote-reference-5"&gt;[5]&lt;/a&gt;. But this update is so overdue, I&amp;#8217;m just going
to post it anyway, and hope it&amp;#8217;ll be of use to Future You if/when the
relevant things finally make it into the&amp;nbsp;image.&lt;/p&gt;
&lt;p class="attribution"&gt;&amp;mdash;Future&amp;nbsp;Dave&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Next, we&amp;#8217;ll install and configure the required daemons. As before, we&amp;#8217;ll use
&lt;tt class="docutils literal"&gt;dnsmasq&lt;/tt&gt; as a &lt;span class="caps"&gt;DHCP&lt;/span&gt; proxy, and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nbd-server&lt;/span&gt;&lt;/tt&gt;. However, this time instead of
&lt;tt class="docutils literal"&gt;dnsmasq&lt;/tt&gt; also handling &lt;span class="caps"&gt;TFTP&lt;/span&gt; duties, we&amp;#8217;ll use &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nobodd-tftpd&lt;/span&gt;&lt;/tt&gt; for that.
First up, install the&amp;nbsp;packages:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;# &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;dnsmasq&lt;span class="w"&gt; &lt;/span&gt;nbd-server&lt;span class="w"&gt; &lt;/span&gt;nobodd-tftpd
&lt;/pre&gt;
&lt;p&gt;Next, configure &lt;tt class="docutils literal"&gt;dnsmasq&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;&amp;lt;&lt;span class="w"&gt; &lt;/span&gt;EOF&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/dnsmasq.conf&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;interface=eth0
bind-interfaces
dhcp-range=192.168.255.255,proxy
pxe-service=0,&amp;quot;Raspberry Pi Boot&amp;quot;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;restart&lt;span class="w"&gt; &lt;/span&gt;dnsmasq
&lt;/pre&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;Adjust the reference to &lt;tt class="docutils literal"&gt;eth0&lt;/tt&gt; if your Ethernet &lt;span class="caps"&gt;NIC&lt;/span&gt; is named something
else. If your network&amp;#8217;s mask is not 192.168.255.255, adjust this&amp;nbsp;accordingly.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Now the &lt;span class="caps"&gt;NBD&lt;/span&gt;&amp;nbsp;server:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;# &lt;/span&gt;chown&lt;span class="w"&gt; &lt;/span&gt;nbd:nbd&lt;span class="w"&gt; &lt;/span&gt;jammy.img&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-lh&lt;span class="w"&gt; &lt;/span&gt;jammy.img&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;-rw-r--r-- 1 nbd  nbd 16.0G Dec  3 13:48 jammy.img
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;&amp;lt;&lt;span class="w"&gt; &lt;/span&gt;EOF&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/nbd-server/conf.d/jammy.conf&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;[jammy]
exportname = /srv/images/jammy.img
EOF
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;restart&lt;span class="w"&gt; &lt;/span&gt;nbd-server
&lt;/pre&gt;
&lt;p&gt;And finally the &lt;span class="caps"&gt;TFTP&lt;/span&gt;&amp;nbsp;server:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;~ubuntu/pi-ident.txt&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Serial          : 1000000089025d75
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;&lt;span class="nv"&gt;piserial&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'1s/^Serial.*\([0-9a-f]\{8\}\)$/\1/'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~ubuntu/pi-ident.txt&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$piserial&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;89025d75
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;&amp;lt;&lt;span class="w"&gt; &lt;/span&gt;EOF&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/nobodd/conf.d/jammy.conf&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;[board:$piserial]
image = /srv/images/jammy.img
partition = 1
EOF
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;reload&lt;span class="w"&gt; &lt;/span&gt;nobodd-tftpd
&lt;/pre&gt;
&lt;p&gt;The final config piece (&lt;tt class="docutils literal"&gt;/etc/nobodd/conf.d/jammy.conf&lt;/tt&gt;) ties the Pi&amp;#8217;s board
serial number to the image that we want to actually boot. The &lt;tt class="docutils literal"&gt;cmdline.txt&lt;/tt&gt;
on the boot partition of that image needs to point to the &lt;span class="caps"&gt;NBD&lt;/span&gt; server serving
that same image. This was actually handled implicitly by the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nobodd-prep&lt;/span&gt;&lt;/tt&gt;
command above. It&amp;nbsp;assumes:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The &lt;span class="caps"&gt;NBD&lt;/span&gt; server is the machine it is running&amp;nbsp;on&lt;/li&gt;
&lt;li&gt;The &lt;span class="caps"&gt;NBD&lt;/span&gt; share will be named after the stem of the image&amp;#8217;s filename (in other
words it transforms &amp;#8220;jammy.img&amp;#8221; to&amp;nbsp;&amp;#8220;jammy&amp;#8221;)&lt;/li&gt;
&lt;li&gt;The root partition is the first non-&lt;span class="caps"&gt;FAT&lt;/span&gt;-type partition in the image
(partition 2 in this&amp;nbsp;case)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hence, in this case it will have automatically inserted the following in the
&lt;tt class="docutils literal"&gt;cmdline.txt&lt;/tt&gt; of the&amp;nbsp;image:&lt;/p&gt;
&lt;pre class="code text literal-block"&gt;
ip=dhcp nbdroot=ubuntu/jammy root=/dev/nbd0p2 ...
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="when-the-end-comes-i-know"&gt;
&lt;h2&gt;When the end comes, I&amp;nbsp;know&lt;/h2&gt;
&lt;p&gt;At this point, you should be ready to go. Power up the Pi, and if everything is
working correctly, it should netboot into your jammy image, applying the custom
cloud-init along the&amp;nbsp;way.&lt;/p&gt;
&lt;p&gt;How do you add more boards? Now you&amp;#8217;ve got your template image, the process
should be as simple&amp;nbsp;as:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Find your next board&amp;#8217;s serial&amp;nbsp;number&lt;/li&gt;
&lt;li&gt;Create another copy of &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;jammy-template.img&lt;/span&gt;&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Customize it with &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nobodd-prep&lt;/span&gt;&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Add the &lt;span class="caps"&gt;NBD&lt;/span&gt; server configuration, reload the &lt;span class="caps"&gt;NBD&lt;/span&gt;&amp;nbsp;server&lt;/li&gt;
&lt;li&gt;Add the &lt;span class="caps"&gt;TFTPD&lt;/span&gt; server configuration, reload the &lt;span class="caps"&gt;TFTPD&lt;/span&gt;&amp;nbsp;server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;#8217;s worth noting that &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nobodd-prep&lt;/span&gt;&lt;/tt&gt; is technically capable of generating the
required configurations for the last two items. However, I&amp;#8217;m a bit unhappy with
the design of it at the moment. It&amp;#8217;s become a horribly convoluted &amp;#8220;big&amp;#8221; tool,
and I think things would be much more flexible if it was &lt;a class="reference external" href="https://github.com/waveform80/nobodd/issues/9"&gt;broken up&lt;/a&gt; into a
series of much simpler tools that emulate the functionality of &lt;tt class="docutils literal"&gt;cat&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;cp&lt;/tt&gt;,
&lt;tt class="docutils literal"&gt;rm&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;ls&lt;/tt&gt;, and providing something that uses those pieces to achieve the
customization of the current&amp;nbsp;tool.&lt;/p&gt;
&lt;p&gt;Another aspect of this setup worth noting is that we haven&amp;#8217;t used &lt;tt class="docutils literal"&gt;mount&lt;/tt&gt;
anywhere. Neither &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nbd-server&lt;/span&gt;&lt;/tt&gt; nor &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nobodd-tftpd&lt;/span&gt;&lt;/tt&gt; require mounts or loop
devices to operate. Why is this important? Because now this entire set up can
run in an unprivileged container, which makes testing things &lt;em&gt;much&lt;/em&gt; simpler.
There are still hoops to jump through &lt;a class="footnote-reference" href="#hoops" id="footnote-reference-6"&gt;[6]&lt;/a&gt; if you want to go this route, which are
beyond the scope of this post, but it is at least &lt;em&gt;possible&lt;/em&gt;.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="hard" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Alongside naming things and off-by-one errors …&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="beenthere" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;I&amp;#8217;ve written &lt;a class="reference external" href="https://compound-files.readthedocs.io/"&gt;stuff&lt;/a&gt;
very similar to a &lt;span class="caps"&gt;FAT&lt;/span&gt; file system driver in Python before.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="headings" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-3"&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;In other words, follow the last post until &amp;#8220;Why&amp;#8217;d you have to be
so good?&amp;#8221;. Incidentally, I make no apologies for the musical puns in my
titles!&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="easy" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-4"&gt;[4]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;After several years in Foundations, you&amp;#8217;d think I&amp;#8217;d have learned by
now, wouldn&amp;#8217;t you? If it looks hard, it is. If it looks easy, it isn&amp;#8217;t.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="sidekick" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-5"&gt;[5]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Yes! It&amp;#8217;s not just me anymore! Say &amp;#8220;hi&amp;#8221; to &lt;a class="reference external" href="https://launchpad.net/~r41k0u"&gt;r41k0u&lt;/a&gt; who&amp;#8217;s also
doing some sterling work on the camera stack this cycle.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="hoops" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-6"&gt;[6]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Specifically this involves getting your container to appear
&lt;em&gt;directly&lt;/em&gt; on your network, rather than hiding behind &lt;span class="caps"&gt;NAT&lt;/span&gt;, so that it can
proxy &lt;span class="caps"&gt;DHCP&lt;/span&gt; requests&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="pi"></category><category term="net"></category><category term="nbd"></category></entry><entry><title>NBD Does it Better</title><link href="https://waldorf.waveform.org.uk/2023/nbd-does-it-better.html" rel="alternate"></link><published>2023-10-30T00:00:00+00:00</published><updated>2024-12-03T15:52:56+00:00</updated><author><name>Dave Jones</name></author><id>tag:waldorf.waveform.org.uk,2023-10-30:/2023/nbd-does-it-better.html</id><summary type="html">&lt;p class="first last"&gt;An alternative means of net-booting a Pi, using &lt;span class="caps"&gt;NBD&lt;/span&gt; instead of &lt;span class="caps"&gt;NFS&lt;/span&gt; to
permit block-layer manipulation (docker overlays, snaps,&amp;nbsp;etc.)&lt;/p&gt;
</summary><content type="html">&lt;p&gt;In the &lt;a class="reference external" href="https://waldorf.waveform.org.uk/2023/nbd-knows.html"&gt;prior post&lt;/a&gt; we looked at the issues with
using &lt;abbr title="Network File System"&gt;&lt;span class="caps"&gt;NFS&lt;/span&gt;&lt;/abbr&gt; as our netboot root, and some of the
alternatives. This time we&amp;#8217;ll go through setting up the server and client of an
&lt;abbr title="Network Block Device"&gt;&lt;span class="caps"&gt;NBD&lt;/span&gt;&lt;/abbr&gt; netboot system. If you want to play along at
home you&amp;#8217;ll&amp;nbsp;need:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;A Raspberry Pi. I&amp;#8217;ll be using a Pi 4B &lt;a class="footnote-reference" href="#earlypi" id="footnote-reference-1"&gt;[1]&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A real &lt;a class="footnote-reference" href="#novms" id="footnote-reference-2"&gt;[2]&lt;/a&gt; server running Ubuntu. I&amp;#8217;m going to be using another Pi for
this, but any old &lt;span class="caps"&gt;PC&lt;/span&gt; should be fine too (whatever server you use will benefit
from as much &lt;span class="caps"&gt;IO&lt;/span&gt; bandwidth, both disk and network, as you can&amp;nbsp;get).&lt;/li&gt;
&lt;li&gt;An ethernet network. You can&amp;#8217;t netboot a Pi over wifi, so you&amp;#8217;ll need an
ethernet set up for&amp;nbsp;this.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before we begin you should ensure your server is up and running, and that you
have remote &lt;span class="caps"&gt;SSH&lt;/span&gt; access to it. In the following instructions I&amp;#8217;m assuming that
your server&amp;#8217;s hostname is &lt;tt class="docutils literal"&gt;server&lt;/tt&gt;, and that you have an &lt;tt class="docutils literal"&gt;ubuntu&lt;/tt&gt; user on
it which can &lt;tt class="docutils literal"&gt;sudo&lt;/tt&gt; to root. Adjust according to your&amp;nbsp;setup!&lt;/p&gt;
&lt;p&gt;We&amp;#8217;re going to start with the client side of things, oddly, but there is method
in the madness. Firstly, we need to ensure that the Pi&amp;#8217;s bootloader is
configured to attempt bootloading (which none are by default). And secondly, we
need to do some surgery on the initramfs for&amp;nbsp;later.&lt;/p&gt;
&lt;div class="section" id="some-kind-of-magic"&gt;
&lt;h2&gt;Some Kind of&amp;nbsp;Magic&lt;/h2&gt;
&lt;p&gt;We&amp;#8217;re going to be using the current Ubuntu &lt;abbr title="Long Term Service"&gt;&lt;span class="caps"&gt;LTS&lt;/span&gt;&lt;/abbr&gt;
(&amp;#8220;jammy&amp;#8221;, 22.04.3) throughout this guide, both on the server later, and for the
Pi&amp;#8217;s client image, to keep things relatively simple. Fire up &lt;a class="reference external" href="https://www.raspberrypi.com/software/"&gt;rpi-imager&lt;/a&gt; and
flash Ubuntu 22.04 server onto an &lt;span class="caps"&gt;SD&lt;/span&gt; card, then boot that &lt;span class="caps"&gt;SD&lt;/span&gt; card on your
chosen&amp;nbsp;Pi.&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="first admonition-title"&gt;Warning&lt;/p&gt;
&lt;p class="last"&gt;Do &lt;em&gt;not&lt;/em&gt; be tempted to upgrade packages at this point. Specifically, the
kernel package must &lt;em&gt;not&lt;/em&gt; be upgraded&amp;nbsp;yet.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Now we need to ensure that the Pi is configured to attempt network booting.
This is a one-time change which will be stored in the &lt;abbr title="Electrically Erasable Programmable Read-Only Memory"&gt;&lt;span class="caps"&gt;EEPROM&lt;/span&gt;&lt;/abbr&gt; of the Pi in question.
On the Pi, extract the current boot configuration from the &lt;span class="caps"&gt;EEPROM&lt;/span&gt;, modify the
existing &lt;tt class="docutils literal"&gt;BOOT_ORDER=&lt;/tt&gt; line (or append a new one if none is present), and
apply the modified&amp;nbsp;configuration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span id="line-1"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;rpi-eeprom-config&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;boot.conf
&lt;/span&gt;&lt;span id="line-2"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;boot.conf
&lt;/span&gt;&lt;span id="line-3"&gt;&lt;span class="go"&gt;[all]&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-4"&gt;&lt;span class="go"&gt;BOOT_UART=1&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-5"&gt;&lt;span class="go"&gt;WAKE_ON_GPIO=1&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-6"&gt;&lt;span class="go"&gt;POWER_OFF_ON_HALT=0&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-7"&gt;&lt;span class="hll"&gt;&lt;span class="go"&gt;BOOT_ORDER=0xf41&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span id="line-8"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/^BOOT_ORDER=/d&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;boot.conf
&lt;/span&gt;&lt;span id="line-9"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;BOOT_ORDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0xf21&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;boot.conf
&lt;/span&gt;&lt;span id="line-10"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;boot.conf
&lt;/span&gt;&lt;span id="line-11"&gt;&lt;span class="go"&gt;[all]&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-12"&gt;&lt;span class="go"&gt;BOOT_UART=1&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-13"&gt;&lt;span class="go"&gt;WAKE_ON_GPIO=1&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-14"&gt;&lt;span class="go"&gt;POWER_OFF_ON_HALT=0&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-15"&gt;&lt;span class="hll"&gt;&lt;span class="go"&gt;BOOT_ORDER=0xf21&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span id="line-16"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;rpi-eeprom-config&lt;span class="w"&gt; &lt;/span&gt;--apply&lt;span class="w"&gt; &lt;/span&gt;boot.conf
&lt;/span&gt;&lt;span id="line-17"&gt;&lt;span class="go"&gt;Updating bootloader EEPROM&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-18"&gt;&lt;span class="go"&gt; ...&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-19"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;reboot
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Obviously, feel free to fire up your favourite editor and just change the
&lt;tt class="docutils literal"&gt;BOOT_ORDER=&lt;/tt&gt; line yourself, instead of messing with &lt;tt class="docutils literal"&gt;sed&lt;/tt&gt;. The mysterious
&lt;tt class="docutils literal"&gt;0xf21&lt;/tt&gt; value is explained fully in the &lt;a class="reference external" href="https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#BOOT_ORDER"&gt;BOOT_ORDER documentation&lt;/a&gt; on the Raspberry Pi website, but simply means try the &lt;span class="caps"&gt;SD&lt;/span&gt; card
first (1), followed by the network (2), instead of &lt;span class="caps"&gt;USB&lt;/span&gt; boot (4) previously, and
if both fail then repeat (f) &lt;a class="footnote-reference" href="#usbalso" id="footnote-reference-3"&gt;[3]&lt;/a&gt;. The digits are specified in reverse
order for&amp;nbsp;$reasons.&lt;/p&gt;
&lt;p&gt;The reboot at the end is required to apply the new configuration to the boot
&lt;span class="caps"&gt;EEPROM&lt;/span&gt;. You can run &lt;tt class="docutils literal"&gt;sudo &lt;span class="pre"&gt;rpi-eeprom-config&lt;/span&gt;&lt;/tt&gt; after rebooting to check the
newly applied&amp;nbsp;configuration.&lt;/p&gt;
&lt;p&gt;Next, we need to install the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;linux-modules-extra-raspi&lt;/span&gt;&lt;/tt&gt; package for the
currently running kernel version. The reason is that the &lt;tt class="docutils literal"&gt;nbd&lt;/tt&gt; kernel module
was moved out of the default &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;linux-modules-raspi&lt;/span&gt;&lt;/tt&gt; package for efficiency. We
specifically need the version matching the running kernel version because
installing this package will regenerate the initramfs (&lt;tt class="docutils literal"&gt;initrd.img&lt;/tt&gt;). We&amp;#8217;ll
be copying that regenerated file into the image we&amp;#8217;re going to netboot and it
&lt;em&gt;must&lt;/em&gt; match the kernel version in that image. This is why it was important not
to upgrade any packages after the first&amp;nbsp;boot.&lt;/p&gt;
&lt;p&gt;We also need to install the &lt;span class="caps"&gt;NBD&lt;/span&gt; client package. This will add the
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nbd-client&lt;/span&gt;&lt;/tt&gt; executable to the initramfs, along with some scripts to call it
if the kernel command line specifies an &lt;span class="caps"&gt;NBD&lt;/span&gt; device as root &lt;a class="footnote-reference" href="#mantic" id="footnote-reference-4"&gt;[4]&lt;/a&gt;:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;linux-modules-extra-&lt;span class="k"&gt;$(&lt;/span&gt;uname&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nbd-client
&lt;/pre&gt;
&lt;p&gt;We need to gather one piece of information about the client Pi for use later on
the server: its serial number. We&amp;#8217;ll store this in a file and copy it and the
&lt;tt class="docutils literal"&gt;initrd.img&lt;/tt&gt; to the server. Finally, we&amp;#8217;ll shut down the Pi and move to the
server side of&amp;nbsp;things:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;Serial&lt;span class="w"&gt; &lt;/span&gt;/proc/cpuinfo&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;pi-ident.txt&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;pi-ident.txt&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Serial          : 1000000089025d75
&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;scp&lt;span class="w"&gt; &lt;/span&gt;-q&lt;span class="w"&gt; &lt;/span&gt;pi-ident.txt&lt;span class="w"&gt; &lt;/span&gt;ubuntu&amp;#64;server:&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;scp&lt;span class="w"&gt; &lt;/span&gt;-q&lt;span class="w"&gt; &lt;/span&gt;/boot/firmware/initrd.img&lt;span class="w"&gt; &lt;/span&gt;ubuntu&amp;#64;server:&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;poweroff
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="why-d-you-have-to-be-so-good"&gt;
&lt;h2&gt;Why&amp;#8217;d you have to be so&amp;nbsp;good?&lt;/h2&gt;
&lt;p&gt;The first thing to do on the server is get the image &lt;a class="footnote-reference" href="#imgcache" id="footnote-reference-5"&gt;[5]&lt;/a&gt; we want to
serve, and do a little surgery on it. We flashed Ubuntu 22.04.3 so we set up a
directory under &lt;tt class="docutils literal"&gt;/srv&lt;/tt&gt; to hold the image, &lt;tt class="docutils literal"&gt;wget&lt;/tt&gt; it, and check the
&lt;abbr title="Secure Hash Algorithm 256-bit"&gt;&lt;span class="caps"&gt;SHA256&lt;/span&gt;&lt;/abbr&gt; checksum. Note that we&amp;#8217;re going
to perform most of these steps as&amp;nbsp;root:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Password:
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;/srv/images&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/srv/images&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;wget&lt;span class="w"&gt; &lt;/span&gt;http://cdimage.ubuntu.com/releases/22.04.3/release/ubuntu-22.04.3-preinstalled-server-arm64+raspi.img.xz&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt; ...
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;wget&lt;span class="w"&gt; &lt;/span&gt;http://cdimage.ubuntu.com/releases/22.04.3/release/SHA256SUMS&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt; ...
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;sha256sum&lt;span class="w"&gt; &lt;/span&gt;--check&lt;span class="w"&gt; &lt;/span&gt;--ignore-missing&lt;span class="w"&gt; &lt;/span&gt;SHA256SUMS&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;ubuntu-22.04.3-preinstalled-server-arm64+raspi.img.xz: OK
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;SHA256SUMS
&lt;/pre&gt;
&lt;p&gt;Now we&amp;#8217;re going to unpack the image (it&amp;#8217;s no good mounting something that&amp;#8217;s &lt;span class="caps"&gt;XZ&lt;/span&gt;
compressed), rename it to something more manageable , and expand the image file
to the full size of &lt;span class="caps"&gt;SD&lt;/span&gt; card we want to emulate (I&amp;#8217;m using &lt;span class="caps"&gt;8GB&lt;/span&gt; here, but change
the &lt;tt class="docutils literal"&gt;fallocate&lt;/tt&gt; command&amp;nbsp;accordingly):&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;# &lt;/span&gt;unxz&lt;span class="w"&gt; &lt;/span&gt;ubuntu-22.04.3-preinstalled-server-arm64+raspi.img.xz&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;mv&lt;span class="w"&gt; &lt;/span&gt;ubuntu-22.04.3-preinstalled-server-arm64+raspi.img&lt;span class="w"&gt; &lt;/span&gt;jammy.img&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-lh&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;-rw-rw-r-- 1 root root 4.0G Oct  5 14:12 jammy.img
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;fallocate&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;8G&lt;span class="w"&gt; &lt;/span&gt;jammy.img&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-lh&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;-rw-rw-r-- 1 root root 8.0G Oct  5 14:51 jammy.img&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;We&amp;#8217;ve expanded the image, but naturally the partitions inside it haven&amp;#8217;t been
changed in size, and nor have the file-systems inside those partitions.
However, that&amp;#8217;s fine. This is exactly what a freshly flashed &lt;span class="caps"&gt;8GB&lt;/span&gt; &lt;span class="caps"&gt;SD&lt;/span&gt; card looks
like. The device (in this case the image file) is &lt;span class="caps"&gt;8GB&lt;/span&gt;, but the root partition
inside is a mere 3.7 &lt;span class="caps"&gt;GB&lt;/span&gt;. We can use &lt;tt class="docutils literal"&gt;fdisk&lt;/tt&gt; to see&amp;nbsp;this:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;# &lt;/span&gt;fdisk&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;jammy.img&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Disk jammy.img: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x542d34fa

Device     Boot  Start     End Sectors  Size Id Type
jammy.img1 *      2048  526335  524288  256M  c W95 FAT32 (LBA)
jammy.img2      526336 8320243 7793908  3.7G 83 Linux&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;On first boot of a &lt;span class="caps"&gt;8GB&lt;/span&gt; &lt;span class="caps"&gt;SD&lt;/span&gt; card, the cloud-init service checks its
configuration, sees that it should expand the root, re-writes the root
partition, and expands the file-system. Exactly the same will happen here when
we first boot this&amp;nbsp;image.&lt;/p&gt;
&lt;p&gt;Next, we need to overwrite the &lt;tt class="docutils literal"&gt;initrd.img&lt;/tt&gt; in the boot partition of this
image, with the one we generated on our client (and which we copied to the
server earlier). In order to do so we need to mount this image. Normally, if
the image file contained &lt;em&gt;only&lt;/em&gt; the file-system we wanted to manipulate, this
would be as trivial as running &lt;tt class="docutils literal"&gt;mount &lt;span class="pre"&gt;-o&lt;/span&gt; loop jammy.img &lt;span class="pre"&gt;some-path&lt;/span&gt;&lt;/tt&gt;. But this
image contains &lt;em&gt;partitions&lt;/em&gt;, so the file-system we wish to mount isn&amp;#8217;t at the
start of the&amp;nbsp;image.&lt;/p&gt;
&lt;p&gt;To get around this, instead of having a loop device created implicitly (with
mount&amp;#8217;s &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-o&lt;/span&gt; loop&lt;/tt&gt; option), we need to make our own loop-device and tell the
kernel to scan it for partitions. Then we&amp;#8217;ll create a mount-point and mount the
first partition there. Finally, we&amp;#8217;ll copy our customized &lt;tt class="docutils literal"&gt;initrd.img&lt;/tt&gt; into
the&amp;nbsp;mount-point:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;# &lt;/span&gt;losetup&lt;span class="w"&gt; &lt;/span&gt;--find&lt;span class="w"&gt; &lt;/span&gt;--show&lt;span class="w"&gt; &lt;/span&gt;--partscan&lt;span class="w"&gt; &lt;/span&gt;jammy.img&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;/dev/loop5
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;/dev/loop5*&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;brw-rw---- 1 root disk   7, 5 Oct  5 21:20 /dev/loop5
brw-rw---- 1 root disk 259, 0 Oct  5 21:20 /dev/loop5p1
brw-rw---- 1 root disk 259, 1 Oct  5 21:20 /dev/loop5p2
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;boot&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;boot/jammy&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;mount&lt;span class="w"&gt; &lt;/span&gt;/dev/loop5p1&lt;span class="w"&gt; &lt;/span&gt;/srv/images/boot/jammy&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;initrd.img&lt;span class="w"&gt; &lt;/span&gt;boot/jammy/
&lt;/pre&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="first admonition-title"&gt;Warning&lt;/p&gt;
&lt;p class="last"&gt;The loop device on your system will likely have a different number; adjust
&lt;tt class="docutils literal"&gt;/dev/loop5&lt;/tt&gt; references&amp;nbsp;accordingly.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Next, we should also customize the cloud-init initial configuration to ensure
the image installs the same packages that we installed on the client&amp;nbsp;earlier:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;&amp;lt;&lt;span class="w"&gt; &lt;/span&gt;EOF&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;boot/jammy/user-data&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;package_update: true
packages:
- avahi-daemon
- nbd-client
- linux-modules-extra-raspi
EOF&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;If we don&amp;#8217;t do this, the next time our netbooted client refreshes its
initramfs, it would generate it &lt;em&gt;without&lt;/em&gt; the &lt;span class="caps"&gt;NBD&lt;/span&gt; client (and would naturally
fail at the next&amp;nbsp;reboot).&lt;/p&gt;
&lt;p&gt;Now we need to edit the kernel command line to tell it that its root device is
an &lt;span class="caps"&gt;NBD&lt;/span&gt; share. The kernel command line is one long line of text with
space-separated portions. We&amp;#8217;re going to change those space-separated bits into
individual lines to make it easier to manipulate, remove the existing
&lt;tt class="docutils literal"&gt;root=&lt;span class="caps"&gt;LABEL&lt;/span&gt;=writable&lt;/tt&gt; portion &lt;a class="footnote-reference" href="#labelroot" id="footnote-reference-6"&gt;[6]&lt;/a&gt;, and insert the following
portions&amp;nbsp;instead:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;ip=dhcp&lt;/tt&gt; &amp;#8212; we need an &lt;span class="caps"&gt;IP&lt;/span&gt; address to find the root device, and that it
should obtain it via &lt;span class="caps"&gt;DHCP&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;nbdroot=server/jammy&lt;/tt&gt; &amp;#8212; set up an &lt;span class="caps"&gt;NBD&lt;/span&gt; client and connect to the &lt;tt class="docutils literal"&gt;jammy&lt;/tt&gt;
share on the host &lt;tt class="docutils literal"&gt;server&lt;/tt&gt; (adjust this to match your server&amp;#8217;s name or &lt;span class="caps"&gt;IP&lt;/span&gt;&amp;nbsp;address)&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;root=/dev/nbd0p2&lt;/span&gt;&lt;/tt&gt; &amp;#8212; find the actual root on the second partition of the
connected &lt;span class="caps"&gt;NBD&lt;/span&gt;&amp;nbsp;device&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of doing my usual confusing one-liner, we&amp;#8217;ll step through the actions
below, but feel free to fire up your favourite text editor and just edit
&lt;tt class="docutils literal"&gt;cmdline.txt&lt;/tt&gt; directly if you find that&amp;nbsp;easier:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span id="line-1"&gt;&lt;span class="gp"&gt;# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;boot/jammy/cmdline.txt
&lt;/span&gt;&lt;span id="line-2"&gt;&lt;span class="go"&gt;console=serial0,115200 dwc_otg.lpm_enable=0 console=tty1 root=LABEL=writable rootfstype=ext4 rootwait fixrtc quiet splash&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-3"&gt;&lt;span class="gp"&gt;# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;boot/jammy/cmdline.txt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tr&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\n&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/tmp/cmdline.txt
&lt;/span&gt;&lt;span id="line-4"&gt;&lt;span class="gp"&gt;# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/tmp/cmdline.txt
&lt;/span&gt;&lt;span id="line-5"&gt;&lt;span class="go"&gt;console=serial0,115200&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-6"&gt;&lt;span class="go"&gt;dwc_otg.lpm_enable=0&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-7"&gt;&lt;span class="go"&gt;console=tty1&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-8"&gt;&lt;span class="hll"&gt;&lt;span class="go"&gt;root=LABEL=writable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span id="line-9"&gt;&lt;span class="go"&gt;rootfstype=ext4&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-10"&gt;&lt;span class="go"&gt;rootwait&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-11"&gt;&lt;span class="go"&gt;fixrtc&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-12"&gt;&lt;span class="go"&gt;quiet&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-13"&gt;&lt;span class="go"&gt;splash&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-14"&gt;&lt;span class="gp"&gt;# &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/^root=/ s@=.*$@=/dev/nbd0p2@&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/tmp/cmdline.txt
&lt;/span&gt;&lt;span id="line-15"&gt;&lt;span class="gp"&gt;# &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/^root=/ i ip=dhcp&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/tmp/cmdline.txt
&lt;/span&gt;&lt;span id="line-16"&gt;&lt;span class="gp"&gt;# &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/^root=/ i nbdroot=server/jammy&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/tmp/cmdline.txt
&lt;/span&gt;&lt;span id="line-17"&gt;&lt;span class="gp"&gt;# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/tmp/cmdline.txt
&lt;/span&gt;&lt;span id="line-18"&gt;&lt;span class="go"&gt;console=serial0,115200&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-19"&gt;&lt;span class="go"&gt;dwc_otg.lpm_enable=0&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-20"&gt;&lt;span class="go"&gt;console=tty1&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-21"&gt;&lt;span class="hll"&gt;&lt;span class="go"&gt;ip=dhcp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span id="line-22"&gt;&lt;span class="hll"&gt;&lt;span class="go"&gt;nbdroot=server/jammy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span id="line-23"&gt;&lt;span class="hll"&gt;&lt;span class="go"&gt;root=/dev/nbd0p2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span id="line-24"&gt;&lt;span class="go"&gt;rootfstype=ext4&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-25"&gt;&lt;span class="go"&gt;rootwait&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-26"&gt;&lt;span class="go"&gt;fixrtc&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-27"&gt;&lt;span class="go"&gt;quiet&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-28"&gt;&lt;span class="go"&gt;splash&lt;/span&gt;
&lt;/span&gt;&lt;span id="line-29"&gt;&lt;span class="gp"&gt;# &lt;/span&gt;paste&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/tmp/cmdline.txt&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;boot/jammy/cmdline.txt
&lt;/span&gt;&lt;span id="line-30"&gt;&lt;span class="gp"&gt;# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;boot/jammy/cmdline.txt
&lt;/span&gt;&lt;span id="line-31"&gt;&lt;span class="go"&gt;console=serial0,115200 dwc_otg.lpm_enable=0 console=tty1 ip=dhcp nbdroot=server/jammy root=/dev/nbd0p2 rootfstype=ext4 rootwait fixrtc quiet splash&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now it&amp;#8217;s time to configure the &lt;abbr title="Dynamic Host Configuration Protocol"&gt;&lt;span class="caps"&gt;DHCP&lt;/span&gt;&lt;/abbr&gt; proxy and &lt;span class="caps"&gt;NBD&lt;/span&gt; server we talked about in the last article. We&amp;#8217;ll
start with the packages we&amp;#8217;re going to need: the &lt;span class="caps"&gt;NBD&lt;/span&gt; server itself, and the
ubiquitous &lt;tt class="docutils literal"&gt;dnsmasq&lt;/tt&gt; daemon which will be handling &lt;span class="caps"&gt;DHCP&lt;/span&gt;, and &lt;abbr title="Trivial File Transfer Protocol"&gt;&lt;span class="caps"&gt;TFTP&lt;/span&gt;&lt;/abbr&gt; for our netbooting&amp;nbsp;clients.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;Don&amp;#8217;t worry if you&amp;#8217;ve already got a &lt;span class="caps"&gt;DHCP&lt;/span&gt; server on your network. I&amp;#8217;ve
assumed that you almost certainly do and will be configuring the &lt;span class="caps"&gt;DHCP&lt;/span&gt;
portion of &lt;tt class="docutils literal"&gt;dnsmasq&lt;/tt&gt; in &lt;span class="caps"&gt;DHCP&lt;/span&gt; &amp;#8220;proxy&amp;#8221; mode where it simply steps in to
augment the options transmitted by the authoritative &lt;span class="caps"&gt;DHCP&lt;/span&gt;&amp;nbsp;server.&lt;/p&gt;
&lt;p class="last"&gt;To put it another way: you shouldn&amp;#8217;t have to dismantle or reconfigure your
network to play&amp;nbsp;along!&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Install the required&amp;nbsp;packages:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;# &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;dnsmasq&lt;span class="w"&gt; &lt;/span&gt;nbd-server
&lt;/pre&gt;
&lt;p&gt;During this installation you may see several warnings about &lt;tt class="docutils literal"&gt;dnsmasq&lt;/tt&gt; being
unable to start due to the address already being in use. This is normal and
occurs because &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;systemd-resolved&lt;/span&gt;&lt;/tt&gt; is already listening on port 53 (the &lt;span class="caps"&gt;DNS&lt;/span&gt;
port) for the loopback address, so it can cache &lt;span class="caps"&gt;DNS&lt;/span&gt; requests. We now configure
&lt;tt class="docutils literal"&gt;dnsmasq&lt;/tt&gt; to only listen on port 53 of the &lt;em&gt;ethernet&lt;/em&gt; &lt;abbr title="Network Interface Card"&gt;&lt;span class="caps"&gt;NIC&lt;/span&gt;&lt;/abbr&gt;, to act as a &lt;span class="caps"&gt;DHCP&lt;/span&gt; proxy, and &lt;span class="caps"&gt;TFTP&lt;/span&gt;&amp;nbsp;server:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;&amp;lt;&lt;span class="w"&gt; &lt;/span&gt;EOF&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/dnsmasq.conf&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;interface=eth0
bind-interfaces
dhcp-range=192.168.255.255,proxy
pxe-service=0,&amp;quot;Raspberry Pi Boot&amp;quot;
enable-tftp
tftp-root=/srv/images/boot
EOF
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;restart&lt;span class="w"&gt; &lt;/span&gt;dnsmasq
&lt;/pre&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;Adjust the reference to &lt;tt class="docutils literal"&gt;eth0&lt;/tt&gt; if your Ethernet &lt;span class="caps"&gt;NIC&lt;/span&gt; is named something
else. If your network&amp;#8217;s mask is not 192.168.255.255, adjust this&amp;nbsp;accordingly.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Next up is the &lt;span class="caps"&gt;NBD&lt;/span&gt; server, which simply needs to point the share &amp;#8220;jammy&amp;#8221; at our
&amp;#8220;jammy.img&amp;#8221;. However, we also need to remember to change ownership of our
images so the unprivileged &amp;#8220;nbd&amp;#8221; user can write to&amp;nbsp;it:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;# &lt;/span&gt;chown&lt;span class="w"&gt; &lt;/span&gt;nbd:nbd&lt;span class="w"&gt; &lt;/span&gt;jammy.img&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-lh&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;total 8.1G
drwxr-xr-x 3 root root 4.0K Oct 30 16:49 boot
-rw-r--r-- 1 nbd  nbd  8.0G Oct 30 16:55 jammy.img
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;&amp;lt;&lt;span class="w"&gt; &lt;/span&gt;EOF&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/nbd-server/conf.d/jammy.conf&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;[jammy]
exportname = /srv/images/jammy.img
EOF
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;restart&lt;span class="w"&gt; &lt;/span&gt;nbd-server
&lt;/pre&gt;
&lt;p&gt;Finally, we link our Pi&amp;#8217;s serial number (or more precisely, the last 8 digits
of it, if it&amp;#8217;s longer than that) with the mounted boot&amp;nbsp;partition.&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;~ubuntu/pi-ident.txt&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Serial          : 1000000089025d75
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;&lt;span class="nv"&gt;piserial&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'1s/^Serial.*\([0-9a-f]\{8\}\)$/\1/'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~ubuntu/pi-ident.txt&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$piserial&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;89025d75
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;ln&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;jammy&lt;span class="w"&gt; &lt;/span&gt;boot/&lt;span class="nv"&gt;$piserial&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;boot&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;total 3
lrwxrwxrwx 1 root root    5 Oct 30 16:49 89025d75 -&amp;gt; jammy
drwxr-xr-x 3 root root 2560 Jan  1  1970 jammy&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="keeps-me-from-runnin"&gt;
&lt;h2&gt;Keeps Me From&amp;nbsp;Runnin&amp;#8217;&lt;/h2&gt;
&lt;p&gt;You&amp;#8217;re now at a point where you can try netbooting your client Pi. Remove its
&lt;span class="caps"&gt;SD&lt;/span&gt; card, and plug it in. You should see the &amp;#8220;rainbow&amp;#8221; boot screen appear fairly
quickly, but there&amp;#8217;ll be a &lt;em&gt;long&lt;/em&gt; pause on that screen. The reason is that your
Pi is transferring &lt;tt class="docutils literal"&gt;initrd.img&lt;/tt&gt; (which is now much larger than normal due to
our installation of &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;linux-modules-extra&lt;/span&gt;&lt;/tt&gt;) over &lt;span class="caps"&gt;TFTP&lt;/span&gt; which is not an
efficient protocol without certain extensions, which the Pi&amp;#8217;s bootloader
doesn&amp;#8217;t implement. However, eventually you should be greeted by the typical
Linux kernel log scrolling by and reach a typical &amp;#8220;booted&amp;#8221; state the same as
you would with an &lt;span class="caps"&gt;SD&lt;/span&gt;&amp;nbsp;card.&lt;/p&gt;
&lt;p&gt;If you hit any snags here, the following things are worth&amp;nbsp;checking:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Pay attention to any errors shown on the Pi&amp;#8217;s bootloader screen. In
particular, you should be able to see the Pi obtaining an &lt;span class="caps"&gt;IP&lt;/span&gt; address via &lt;span class="caps"&gt;DHCP&lt;/span&gt;
and various &lt;span class="caps"&gt;TFTP&lt;/span&gt; request&amp;nbsp;attempts.&lt;/li&gt;
&lt;li&gt;Run &lt;tt class="docutils literal"&gt;journalctl &lt;span class="pre"&gt;-f&lt;/span&gt; &lt;span class="pre"&gt;--unit&lt;/span&gt; dnsmasq.service&lt;/tt&gt; on your server to follow the
dnsmasq log output. Again, if things are working, you should be seeing
several &lt;span class="caps"&gt;TFTP&lt;/span&gt; requests here. If you see nothing, double check the network mask
is specified correctly in the dnsmasq configuration, and that any firewall on
the server is permitting inbound traffic to port 69 (the &lt;span class="caps"&gt;TFTP&lt;/span&gt;&amp;nbsp;port).&lt;/li&gt;
&lt;li&gt;You &lt;em&gt;will&lt;/em&gt; see numerous &amp;#8220;Early terminate&amp;#8221; &lt;span class="caps"&gt;TFTP&lt;/span&gt; errors in the dnsmasq log
output. This is normal, and appears to be how the Pi&amp;#8217;s bootloader operates
(my guess would be it&amp;#8217;s attempting to determine the size of a file with the
tsize extension, terminating the transfer, allocating &lt;span class="caps"&gt;RAM&lt;/span&gt; for the file, then
starting the transfer&amp;nbsp;again).&lt;/li&gt;
&lt;li&gt;If cloud-init&amp;#8217;s final phase running &lt;tt class="docutils literal"&gt;apt update&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;apt install
&lt;span class="pre"&gt;avahi-daemon&lt;/span&gt; &lt;span class="pre"&gt;linux-modules-extra-raspi&lt;/span&gt; &lt;span class="pre"&gt;nbd-client&lt;/span&gt;&lt;/tt&gt; fails (it seems to
randomly on my test Pi), just login and run them&amp;nbsp;manually.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At this point you should have a fully booted system with a block device as the
root. All is good! We can use anything we would on a regular Pi with an &lt;span class="caps"&gt;SD&lt;/span&gt;
card, including snapd, docker, or anything else. But there&amp;#8217;s a rather serious
problem waiting silently for us. If things have worked correctly,
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nbd-client&lt;/span&gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;linux-modules-extra-raspi&lt;/span&gt;&lt;/tt&gt; will have been installed. This
will have re-built the initramfs, and likely also have upgraded the kernel
package. If you attempt to reboot at this point, you&amp;#8217;ll likely find the next
boot fails on the rainbow&amp;nbsp;screen.&lt;/p&gt;
&lt;p&gt;Spot the problem? &lt;em&gt;Two&lt;/em&gt; things are accessing the boot partition&amp;#8217;s block device.
First, the &lt;span class="caps"&gt;TFTP&lt;/span&gt; server (&lt;tt class="docutils literal"&gt;dnsmasq&lt;/tt&gt;) is reading the boot partition via a loop
device. Second, the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nbd-server&lt;/span&gt;&lt;/tt&gt; is serving the boot partition directly from
the&amp;nbsp;image.&lt;/p&gt;
&lt;p&gt;Recall that mounts of block devices assume they have &lt;em&gt;exclusive access&lt;/em&gt; to the
underlying block device. But here, the &lt;tt class="docutils literal"&gt;vfat&lt;/tt&gt; driver on the server does &lt;em&gt;not&lt;/em&gt;
have exclusive access to the boot partition. What happens if we change the boot
partition on the client. Does the server&amp;nbsp;notice?&lt;/p&gt;
&lt;p&gt;Try the following&amp;nbsp;experiment:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;On the client: &lt;tt class="docutils literal"&gt;sudo touch /boot/firmware/foo&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;On the server: &lt;tt class="docutils literal"&gt;ls /srv/images/boot/jammy/foo&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You should find that, even if you &lt;tt class="docutils literal"&gt;sudo sync&lt;/tt&gt; on the client the &lt;tt class="docutils literal"&gt;foo&lt;/tt&gt; file
simply won&amp;#8217;t show up on the server&amp;#8217;s boot mount. The boot mount needs
&lt;em&gt;remounting&lt;/em&gt; to make it re-read the necessary blocks from the underlying image.
This bodes badly for anything that writes to the boot partition, as happens
when installing a new kernel, or anything that causes the initramfs to be&amp;nbsp;rebuilt.&lt;/p&gt;
&lt;p&gt;Thus, to resurrect your netbooting Pi do the following on the&amp;nbsp;server:&lt;/p&gt;
&lt;pre class="code console literal-block"&gt;
&lt;span class="gp"&gt;# &lt;/span&gt;umount&lt;span class="w"&gt; &lt;/span&gt;/srv/images/boot/jammy&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;# &lt;/span&gt;mount&lt;span class="w"&gt; &lt;/span&gt;/dev/loop5p1&lt;span class="w"&gt; &lt;/span&gt;/srv/images/boot/jammy
&lt;/pre&gt;
&lt;p&gt;Well, this sucks.&amp;nbsp;Solutions?&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Use &lt;span class="caps"&gt;NFS&lt;/span&gt; for the boot partition mount instead of &lt;span class="caps"&gt;NBD&lt;/span&gt;. This will work, but
there&amp;#8217;re several issues here. Firstly … it requires an &lt;span class="caps"&gt;NFS&lt;/span&gt; server! Secondly,
you need to customize the &lt;tt class="docutils literal"&gt;fstab&lt;/tt&gt; in the image before first boot (urgh).
Thirdly you need to extract the content of the boot partition to an exported
directory and point &lt;span class="caps"&gt;TFTP&lt;/span&gt; to that (which wastes space) &lt;em&gt;or&lt;/em&gt; you can stick
with the looped mount, but you must be absolutely certain that you mount the
boot partition with &lt;span class="caps"&gt;NFS&lt;/span&gt; on the client, &lt;em&gt;not&lt;/em&gt; &lt;span class="caps"&gt;NBD&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Try and remount the boot partition when the client reboots. This is risky as
it&amp;#8217;s almost impossible to guarantee in practice. If you think you can get
clever with &lt;span class="caps"&gt;DHCP&lt;/span&gt; hook scripts in &lt;tt class="docutils literal"&gt;dnsmasq&lt;/tt&gt; to remount just as the machine
is booting (because &lt;span class="caps"&gt;DHCP&lt;/span&gt; comes before the first &lt;span class="caps"&gt;TFTP&lt;/span&gt; request), you&amp;#8217;re wrong
&lt;a class="footnote-reference" href="#askmehow" id="footnote-reference-7"&gt;[7]&lt;/a&gt;. &lt;tt class="docutils literal"&gt;dnsmasq&lt;/tt&gt; scripts run asynchronously and you can&amp;#8217;t unmount a
&amp;#8220;busy&amp;#8221; partition. It&amp;#8217;s still possible to do this manually, but that&amp;#8217;s
boring! &lt;a class="footnote-reference" href="#noremount" id="footnote-reference-8"&gt;[8]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Write our own &lt;span class="caps"&gt;TFTP&lt;/span&gt; server that directly accesses the image without ever
mounting it!&amp;nbsp;Hmmm…&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Join me next time for the conclusion of this series where … well, you can
probably guess where this is going&amp;nbsp;…&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="earlypi" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;The Pi 2B, 3B, and 3B+ &lt;em&gt;can&lt;/em&gt; also netboot, but the instructions
differ on each, and this article is long enough as it is. See &lt;a class="reference external" href="https://www.raspberrypi.com/documentation/computers/remote-access.html#network-boot-your-raspberry-pi"&gt;Network
boot&lt;/a&gt; for more information, and drop me a question in the comments if you
need more!&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="novms" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Your server needs to be a &amp;#8220;real&amp;#8221; server, or at the very least a
full virtual machine, not a container. Specifically, you need to be able to
create loop devices and mount images (both of which are not typically
possible in a container environment).&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="usbalso" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-3"&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;If you just want to &lt;em&gt;add&lt;/em&gt; network boot instead of replacing the
&lt;span class="caps"&gt;USB&lt;/span&gt; boot option, that&amp;#8217;s fine too &amp;#8212; you can use &lt;tt class="docutils literal"&gt;0xf421&lt;/tt&gt; or &lt;tt class="docutils literal"&gt;0xf241&lt;/tt&gt;
here too. Also, the reason we&amp;#8217;ve left &lt;span class="caps"&gt;SD&lt;/span&gt; card boot in the order is simply
for safety (there are recovery methods available even if you remove it but
it&amp;#8217;s simpler to just leave it there).&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="mantic" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-4"&gt;[4]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;In mantic, the &lt;tt class="docutils literal"&gt;nbd&lt;/tt&gt; module moved back to linux-modules-raspi,
so you can skip installing &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;linux-modules-extra&lt;/span&gt;&lt;/tt&gt; there if you want, but
you&amp;#8217;ll still need &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nbd-client&lt;/span&gt;&lt;/tt&gt;. In noble, I&amp;#8217;m intending to seed
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nbd-client&lt;/span&gt;&lt;/tt&gt; in the image, so no modifications at all should be required.
It&amp;#8217;s a pretty small install (&lt;span class="caps"&gt;172KB&lt;/span&gt;) so it constitutes little bloat, and the
ability to netboot out of the box (with an appropriately configured Pi)
seems to justify the change to me.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="imgcache" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-5"&gt;[5]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;If you&amp;#8217;re cunning, you can avoid downloading the image again by
extracting it from the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;rpi-imager&lt;/span&gt;&lt;/tt&gt; cache. On Linux at least, this is
under &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.cache/Raspberry&lt;/span&gt; Pi/Imager/lastdownload.cache&lt;/tt&gt;. Because the
filename has lost it&amp;#8217;s extension I usually use &lt;tt class="docutils literal"&gt;file&lt;/tt&gt; to determine what it
was (e.g. if it&amp;#8217;s &amp;#8220;&lt;span class="caps"&gt;XZ&lt;/span&gt; compressed data, checksum &lt;span class="caps"&gt;CRC64&lt;/span&gt;&amp;#8221; I know it was
&lt;tt class="docutils literal"&gt;.img.xz&lt;/tt&gt;).&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="labelroot" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-6"&gt;[6]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Unfortunately, the initramfs isn&amp;#8217;t intelligent enough to go
searching for a label-based root in an &lt;span class="caps"&gt;NBD&lt;/span&gt; device, even though technically
this is valid.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="askmehow" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-7"&gt;[7]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Ask me how I know this!&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="noremount" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-8"&gt;[8]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Before anyone asks, no &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-o&lt;/span&gt; remount&lt;/tt&gt; isn&amp;#8217;t sufficient. It
needs to be completely unmounted and re-mounted.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="pi"></category><category term="net"></category><category term="nbd"></category></entry><entry><title>NBD Knows</title><link href="https://waldorf.waveform.org.uk/2023/nbd-knows.html" rel="alternate"></link><published>2023-10-27T00:00:00+01:00</published><updated>2024-12-03T15:52:56+00:00</updated><author><name>Dave Jones</name></author><id>tag:waldorf.waveform.org.uk,2023-10-27:/2023/nbd-knows.html</id><summary type="html">&lt;p class="first last"&gt;The problems with &lt;span class="caps"&gt;NFS&lt;/span&gt; as a netboot system, and what the alternatives&amp;nbsp;are&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This is part 1 of an exploration of netbooting Raspberry Pis, with an emphasis
on &lt;span class="caps"&gt;NBD&lt;/span&gt; over the more traditional &lt;span class="caps"&gt;NFS&lt;/span&gt;. While obviously we&amp;#8217;re going to be looking
at Ubuntu in this series, I&amp;#8217;m hoping this should be generic enough to be useful
to be useful on a variety of&amp;nbsp;distributions.&lt;/p&gt;
&lt;div class="section" id="the-trouble-i-ve-seen"&gt;
&lt;h2&gt;The trouble I&amp;#8217;ve&amp;nbsp;seen&lt;/h2&gt;
&lt;p&gt;The obvious question is &amp;#8220;why &lt;abbr title="Network Block Device"&gt;&lt;span class="caps"&gt;NBD&lt;/span&gt;&lt;/abbr&gt;?&amp;#8221; or put
another way &amp;#8220;what&amp;#8217;s wrong with &lt;abbr title="Network File Sytem"&gt;&lt;span class="caps"&gt;NFS&lt;/span&gt;&lt;/abbr&gt;?&amp;#8221;. After all,
&lt;span class="caps"&gt;NFS&lt;/span&gt; is the current &amp;#8220;best practice&amp;#8221; for booting Raspberry Pis, and widely used,
including by some very large installations (e.g. &lt;a class="reference external" href="https://mythic-beasts.com/"&gt;Mythic Beasts&amp;#8217;&lt;/a&gt; fantastic Pi&amp;nbsp;cloud).&lt;/p&gt;
&lt;p&gt;Ultimately, the reason is that the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Network_File_System"&gt;Network File System&lt;/a&gt; … is a&amp;nbsp;file-system.&lt;/p&gt;
&lt;p&gt;What a ridiculous objection, of course it&amp;#8217;s a file-system! The clue&amp;#8217;s in the
name! And surely you &lt;em&gt;want&lt;/em&gt; a file-system? More typically, though, you actually
have a root &lt;em&gt;block device&lt;/em&gt;, which your kernel will then &lt;em&gt;transform&lt;/em&gt; into a
file-system, rather than being given a file-system&amp;nbsp;directly.&lt;/p&gt;
&lt;p&gt;What problems arise from the lack of (access to) an underlying block device?
The crux of the issue is what the kernel can assume about the file-system, most
particularly whether it can assume it has exclusive access to it, and whether
it can trivially access the underlying blocks of certain&amp;nbsp;files.&lt;/p&gt;
&lt;p&gt;Consider the common &amp;#8220;root is a block device&amp;#8221;&amp;nbsp;case:&lt;/p&gt;
&lt;p&gt;The kernel on &lt;em&gt;your&lt;/em&gt; machine runs the transformation (the file system driver)
that converts that block device into a file-system. Crucially, it can assume
that it is the &lt;em&gt;only&lt;/em&gt; entity accessing this block device (that it has exclusive
access), and that for certain operations it can (with some jiggery-pokery)
bypass the file-system and treat a file as a block device &lt;a class="footnote-reference" href="#simples" id="footnote-reference-1"&gt;[1]&lt;/a&gt;, which
makes certain things nice and&amp;nbsp;simple.&lt;/p&gt;
&lt;p&gt;Need to allocate a large contiguous file, for example as a swap file? No
problem! The file-system driver implements this &lt;a class="footnote-reference" href="#btrfs" id="footnote-reference-2"&gt;[2]&lt;/a&gt;, and can even give
the kernel the contiguous blocks for use in the swap system (remember how block
devices &amp;#8220;make storage look like &lt;span class="caps"&gt;RAM&lt;/span&gt;&amp;#8221;?&amp;nbsp;Foreshadowing!).&lt;/p&gt;
&lt;p&gt;Need to lock a file? The kernel knows nothing else is mounting file systems
from that block device, so it knows all the locks that exist on it and doesn&amp;#8217;t
need to coordinate with anything else. Likewise, caching is simple
&lt;a class="footnote-reference" href="#neversimple" id="footnote-reference-3"&gt;[3]&lt;/a&gt;. The kernel can assume it has absolute knowledge of which
blocks are dirty and need writing back because nothing else can be producing
file systems from that block&amp;nbsp;device.&lt;/p&gt;
&lt;p&gt;Need some temporary file space, private to your process? Create a unique
temporary file and delete it, leaving the file handle open, then use the file
as a temporary store. The space won&amp;#8217;t be reclaimed because, even though there
are no links to the file, the kernel knows there&amp;#8217;s still an open file&amp;nbsp;handle.&lt;/p&gt;
&lt;p&gt;Now the &amp;#8220;root is a file-system&amp;#8221;&amp;nbsp;case:&lt;/p&gt;
&lt;p&gt;In this case, there&amp;#8217;s a block device &lt;em&gt;somewhere&lt;/em&gt;, but your kernel has no
visibility of it, and must assume that other entities can change its blocks
without&amp;nbsp;notification.&lt;/p&gt;
&lt;p&gt;This interferes with several of the scenarios above. Historically, it was fatal
to several of them (&lt;tt class="docutils literal"&gt;fallocate&lt;/tt&gt; for cheap allocation of large files, and swap
over &lt;span class="caps"&gt;NFS&lt;/span&gt; did not work). Whilst these features &lt;em&gt;do&lt;/em&gt; work today, it&amp;#8217;s notable
that kernel support needed to be added for swap &lt;a class="footnote-reference" href="#swapnfs" id="footnote-reference-4"&gt;[4]&lt;/a&gt;, and the &lt;span class="caps"&gt;NFS&lt;/span&gt;
protocol extended specifically for &lt;tt class="docutils literal"&gt;fallocate&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Caching becomes tricky because files are complicated and messy things that have
data and &amp;#8220;meta-data&amp;#8221;. Here&amp;#8217;s an excerpt from the &lt;span class="caps"&gt;NFS&lt;/span&gt; mount&amp;nbsp;options:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;ac / noac&lt;/tt&gt;&lt;/p&gt;
&lt;p&gt;Selects whether the client may cache file attributes. If neither option is
specified (or if &lt;tt class="docutils literal"&gt;ac&lt;/tt&gt; is specified), the client caches file&amp;nbsp;attributes.&lt;/p&gt;
&lt;p&gt;To improve performance, &lt;span class="caps"&gt;NFS&lt;/span&gt; clients cache file attributes. Every few
seconds, an &lt;span class="caps"&gt;NFS&lt;/span&gt; client checks the server&amp;#8217;s version of each file&amp;#8217;s
attributes for updates. Changes that occur on the server in those small
intervals remain undetected until the client checks the server again. The
&lt;tt class="docutils literal"&gt;noac&lt;/tt&gt; option prevents clients from caching file attributes so that
applications can more quickly detect file changes on the&amp;nbsp;server.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yikes.&lt;/p&gt;
&lt;p&gt;The practice of using unlinked temporary files also works over &lt;span class="caps"&gt;NFS&lt;/span&gt; but again …
required &lt;a class="reference external" href="https://nfs.sourceforge.net/#faq_d2"&gt;workarounds&lt;/a&gt; because &lt;span class="caps"&gt;NFS&lt;/span&gt; is
stateless &lt;a class="footnote-reference" href="#simples" id="footnote-reference-5"&gt;[1]&lt;/a&gt; so open file handles locally do not correspond to open
file handles on the&amp;nbsp;server.&lt;/p&gt;
&lt;p&gt;All this doesn&amp;#8217;t matter too much when the portion of the virtual file-system
being mounted over &lt;span class="caps"&gt;NFS&lt;/span&gt; is relatively limited, as in the common case of mounting
user home directories over &lt;span class="caps"&gt;NFS&lt;/span&gt;. However, when the &lt;em&gt;entire&lt;/em&gt; root file-system is
&lt;span class="caps"&gt;NFS&lt;/span&gt;, quite a few applications can start having &amp;#8220;difficulty&amp;#8221;. One of the more
prominent, on Ubuntu especially, is snapd which &lt;a class="reference external" href="https://bugs.launchpad.net/ubuntu/+source/snapd/+bug/1884299"&gt;doesn&amp;#8217;t&lt;/a&gt; &lt;a class="reference external" href="https://bugs.launchpad.net/snapd/+bug/1973321"&gt;like&lt;/a&gt; running on an &lt;span class="caps"&gt;NFS&lt;/span&gt; root. Like
it or not, it&amp;#8217;s a pretty integral part of the Ubuntu eco-system at this point
(providing Firefox on the desktop and &lt;span class="caps"&gt;LXD&lt;/span&gt; on the server), so it would be nice
to have a netboot system that can support it too &lt;a class="footnote-reference" href="#docker" id="footnote-reference-6"&gt;[5]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Okay, we&amp;#8217;ve established there are some issues with running &lt;span class="caps"&gt;NFS&lt;/span&gt; as root. What&amp;#8217;re
the&amp;nbsp;alternatives?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="sometimes-i-m-up"&gt;
&lt;span id="skip-here"&gt;&lt;/span&gt;&lt;h2&gt;Sometimes I&amp;#8217;m&amp;nbsp;up&lt;/h2&gt;
&lt;p&gt;There are several daemons and protocols that support serving block devices over
the network, and they differ in some quite interesting&amp;nbsp;ways:&lt;/p&gt;
&lt;dl class="docutils"&gt;
&lt;dt&gt;iSCSI&lt;/dt&gt;
&lt;dd&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/ISCSI"&gt;iSCSI&lt;/a&gt; can be trivially summarized as: &lt;a class="reference external" href="https://en.wikipedia.org/wiki/SCSI"&gt;&lt;span class="caps"&gt;SCSI&lt;/span&gt;&lt;/a&gt; commands over &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Internet_protocol_suite"&gt;&lt;span class="caps"&gt;TCP&lt;/span&gt;/&lt;span class="caps"&gt;IP&lt;/span&gt;&lt;/a&gt;.
This is by &lt;em&gt;far&lt;/em&gt; the most popular method of serving block devices. It has
the advantages that it doesn&amp;#8217;t require expensive equipment (unlike &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Fibre_Channel"&gt;Fibre
Channel&lt;/a&gt;, its major competitor in the enterprise), and can be routed over
multiple networks (as it&amp;#8217;s built on &lt;span class="caps"&gt;IP&lt;/span&gt;). However, it&amp;#8217;s not entirely trivial
to configure (it is &lt;em&gt;very&lt;/em&gt; flexible, but for our purposes here I wanted
something simpler to start out&amp;nbsp;with).&lt;/dd&gt;
&lt;dt&gt;AoE&lt;/dt&gt;
&lt;dd&gt;&lt;p class="first"&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/ATA_over_Ethernet"&gt;&lt;span class="caps"&gt;ATA&lt;/span&gt; over Ethernet&lt;/a&gt;. Like iSCSI, the name says it all. The client simply
passes &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Parallel_ATA"&gt;&lt;span class="caps"&gt;ATA&lt;/span&gt;&lt;/a&gt; disk commands over Ethernet to the server. Note that this
does indeed run over Ethernet &lt;em&gt;only&lt;/em&gt; (layer 2), &lt;em&gt;not&lt;/em&gt; &lt;span class="caps"&gt;TCP&lt;/span&gt;/&lt;span class="caps"&gt;IP&lt;/span&gt; so it&amp;#8217;s not
routable over the Internet, only local networks. It&amp;#8217;s very simple to set up
(moreso than iSCSI), but two things made me skip it here. The first is that
routing over layer 2 may be perfectly sufficient for many use-cases, but
there&amp;#8217;s several others where it&amp;#8217;ll be a limiting&amp;nbsp;factor.&lt;/p&gt;
&lt;p class="last"&gt;The second is that &lt;a class="reference external" href="https://launchpad.net/ubuntu/+source/aoetools"&gt;aoetools&lt;/a&gt;, the package for AoE in Ubuntu, is an
extremely &amp;#8220;mature&amp;#8221; package. Specifically, the upstream version in Ubuntu
hasn&amp;#8217;t changed since Xenial (16.04, 7 years ago at the time of writing).
That isn&amp;#8217;t to say it&amp;#8217;s bad or entirely unmaintained, but there&amp;#8217;s no active
work on it as best as I can tell (and that usually doesn&amp;#8217;t bode too well
from a security point of&amp;nbsp;view).&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;span class="caps"&gt;NBD&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p class="first"&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Network_block_device"&gt;Network Block Devices&lt;/a&gt; differs in that doesn&amp;#8217;t implement the commands
from an existing disk protocol (&lt;span class="caps"&gt;SCSI&lt;/span&gt; or &lt;span class="caps"&gt;ATA&lt;/span&gt;). Instead, it uses its own
protocol which, at it&amp;#8217;s core, is almost laughably&amp;nbsp;simple.&lt;/p&gt;
&lt;p class="last"&gt;It also operates over &lt;span class="caps"&gt;TCP&lt;/span&gt;/&lt;span class="caps"&gt;IP&lt;/span&gt;, avoiding the layer 2 limitations of AoE, and
is an actively maintained project which optionally includes facilities for
&lt;span class="caps"&gt;TLS&lt;/span&gt; encryption. I won&amp;#8217;t be using those here, but you&amp;#8217;ll have some options
for better security down the&amp;nbsp;line.&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;Simplicity is another argument in favour of serving block devices instead of
file-systems. Compare the two&amp;nbsp;scenarios:&lt;/p&gt;
&lt;object data="https://waldorf.waveform.org.uk/images/nbd-vs.svg" type="image/svg+xml"&gt;&lt;/object&gt;
&lt;p&gt;They don&amp;#8217;t look that different but consider what serving files over a network
means, versus serving block devices. What operations can be performed against a
file? Opening, closing, reading, writing, truncating, locking, linking,
touching, the list goes on. All this &lt;em&gt;must&lt;/em&gt; be handled by the protocol to
implement even a bare-bones network file-system. The bare-bones case for block
devices (as noted above) is &lt;em&gt;radically&lt;/em&gt;&amp;nbsp;simpler.&lt;/p&gt;
&lt;p&gt;The baseline portion of the &lt;a class="reference external" href="https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md"&gt;&lt;span class="caps"&gt;NBD&lt;/span&gt; Protocol&lt;/a&gt; consists of commands to &amp;#8220;read some
bytes&amp;#8221;, &amp;#8220;write some bytes&amp;#8221;, and &amp;#8220;disconnect&amp;#8221;. That&amp;#8217;s it. There are some other
commands which may optionally tell the server to trim, flush, or cache blocks,
and some other messages for option negotiation at connection time, but the core
of the protocol really is &lt;em&gt;that simple&lt;/em&gt;. I like&amp;nbsp;simple.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="sometimes-i-m-down"&gt;
&lt;h2&gt;Sometimes I&amp;#8217;m&amp;nbsp;down&lt;/h2&gt;
&lt;p&gt;So far, we&amp;#8217;ve looked at how the root file-system will be handled when
netbooting. However, the root file-system only matters &lt;em&gt;after&lt;/em&gt; the Linux kernel
has started. How do we obtain the Linux kernel itself (and other sundry boot
resources) at system start? This involves neither &lt;span class="caps"&gt;NFS&lt;/span&gt; nor &lt;span class="caps"&gt;NBD&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;When netbooting, the Pi first requests an &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Internet_Protocol_version_4"&gt;IPv4&lt;/a&gt; address from the local
network via &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol"&gt;&lt;span class="caps"&gt;DHCP&lt;/span&gt;&lt;/a&gt;. The local router responds with a &lt;span class="caps"&gt;DHCP&lt;/span&gt; offer, and our
netboot server tacks a (minimal) &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Preboot_Execution_Environment"&gt;&lt;span class="caps"&gt;PXE&lt;/span&gt;&lt;/a&gt; boot menu on the end suggesting where
the client may find a &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Trivial_File_Transfer_Protocol"&gt;&lt;span class="caps"&gt;TFTP&lt;/span&gt;&lt;/a&gt; server for&amp;nbsp;booting.&lt;/p&gt;
&lt;p&gt;This is typical jargon-laden nonsense, so let&amp;#8217;s translate a&amp;nbsp;bit:&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="19%" /&gt;
&lt;col width="29%" /&gt;
&lt;col width="24%" /&gt;
&lt;col width="27%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;Jargon&lt;/th&gt;
&lt;th class="head"&gt;Raspberry Pi&lt;/th&gt;
&lt;th class="head"&gt;Router&lt;/th&gt;
&lt;th class="head"&gt;Netboot Server&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;&lt;span class="caps"&gt;DHCP&lt;/span&gt; &lt;span class="caps"&gt;DISCOVER&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&amp;#8220;Hello? Can anybody
give me an IPv4
address? By the
way, I&amp;#8217;m a netboot
client&amp;#8221;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;span class="caps"&gt;DHCP&lt;/span&gt; &lt;span class="caps"&gt;OFFER&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;#8220;Sure, would you
like to be
192.168.0.200?&amp;#8221;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;span class="caps"&gt;DHCP&lt;/span&gt; Proxy
Option 43
&lt;span class="caps"&gt;PXE&lt;/span&gt; Boot Menu&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;#8220;By the way, for
&amp;#8216;Raspberry Pi Boot&amp;#8217;
see &lt;span class="caps"&gt;TFTP&lt;/span&gt; server at
192.168.0.4&amp;#8221;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;span class="caps"&gt;DHCP&lt;/span&gt; &lt;span class="caps"&gt;REQUEST&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&amp;#8220;Okay, I&amp;#8217;d like to
be 192.168.0.200,
please?&amp;#8221;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;span class="caps"&gt;DHCP&lt;/span&gt; &lt;span class="caps"&gt;ACK&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;#8220;Right, you are
192.168.0.200 for
the next 12
hours, see me
again after that&amp;#8221;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;span class="caps"&gt;TFTP&lt;/span&gt; &lt;span class="caps"&gt;RRQ&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&amp;#8220;Can you send me
the content of
&lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;SERIAL&lt;/span&gt;/start.elf&lt;/tt&gt;,
please?&amp;#8221;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;span class="caps"&gt;TFTP&lt;/span&gt; &lt;span class="caps"&gt;OACK&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;#8220;Sure, it&amp;#8217;s going
to be 225065 bytes
long, and I&amp;#8217;ll send
it in chunks of
1468 bytes&amp;#8221;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The important things to note here are as&amp;nbsp;follows:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;We need a &lt;abbr title="Dynamic Host Configuration Protocol"&gt;&lt;span class="caps"&gt;DHCP&lt;/span&gt;&lt;/abbr&gt; server. This is
pretty much taken-as-read on any network these&amp;nbsp;days.&lt;/li&gt;
&lt;li&gt;We need a netboot server with a &lt;span class="caps"&gt;DHCP&lt;/span&gt; proxy and a &lt;abbr title="Trivial File Transfer Protocol"&gt;&lt;span class="caps"&gt;TFTP&lt;/span&gt;&lt;/abbr&gt; server. This is fairly simple. Any Ubuntu server can
install &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Dnsmasq"&gt;dnsmasq&lt;/a&gt; (if it hasn&amp;#8217;t already) to obtain&amp;nbsp;this.&lt;/li&gt;
&lt;li&gt;We need the Raspberry Pi&amp;#8217;s serial&amp;nbsp;number.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This last point may seem a bit strange, but it&amp;#8217;s because &lt;span class="caps"&gt;TFTP&lt;/span&gt; is, as the name
suggests, trivial. The protocol provides no means for the client to identify
itself to the server, so how are we to know which boot partition we should read
files&amp;nbsp;from?&lt;/p&gt;
&lt;p&gt;Identification by &lt;a class="reference external" href="https://en.wikipedia.org/wiki/MAC_address"&gt;&lt;span class="caps"&gt;MAC&lt;/span&gt;&lt;/a&gt; address is one possibility &lt;a class="footnote-reference" href="#macsecure" id="footnote-reference-7"&gt;[6]&lt;/a&gt;, but that&amp;#8217;s
not an option for us (unsupported by the &lt;span class="caps"&gt;TFTP&lt;/span&gt; server in dnsmasq). Instead we
rely upon the Pi identifying itself by the sequence of files it initially
attempts to request. When netbooting, a Pi (more specifically a Pi 4 or later)
will attempt the following sequence of files &lt;a class="footnote-reference" href="#simples" id="footnote-reference-8"&gt;[1]&lt;/a&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;SERIAL&lt;/span&gt;/start4.elf&lt;/tt&gt;, e.g. &lt;tt class="docutils literal"&gt;1234abcd/start4.elf&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;SERIAL&lt;/span&gt;/start.elf&lt;/tt&gt;, e.g. &lt;tt class="docutils literal"&gt;1234abcd/start.elf&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;start.elf&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the bootloader finds files with the &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;SERIAL&lt;/span&gt;/&lt;/tt&gt; &lt;a class="footnote-reference" href="#serial" id="footnote-reference-9"&gt;[7]&lt;/a&gt; prefix, all
subsequent requests will also have that prefix, allowing us to easily determine
which &lt;span class="caps"&gt;OS&lt;/span&gt; image files should be served&amp;nbsp;from.&lt;/p&gt;
&lt;p&gt;The Pi then proceeds to request (over &lt;span class="caps"&gt;TFTP&lt;/span&gt;):&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The rest of the tertiary bootloader (e.g. &lt;tt class="docutils literal"&gt;start4.elf&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;fixup4.dat&lt;/tt&gt;, and
its configuration files like &lt;tt class="docutils literal"&gt;config.txt&lt;/tt&gt;).&lt;/li&gt;
&lt;li&gt;The device-tree for the specific board (e.g. &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;bcm2711-rpi-4-b.dtb&lt;/span&gt;&lt;/tt&gt;) and any
overlays required by the configuration, or by devices that are plugged in
(e.g. &lt;tt class="docutils literal"&gt;overlays/dwc2.dtbo&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;overlays/ov5647.dtbo&lt;/tt&gt;).&lt;/li&gt;
&lt;li&gt;The kernel and initramfs requested by the configuration, and its command line
(e.g. &lt;tt class="docutils literal"&gt;vmlinuz&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;initrd.img&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;cmdline.txt&lt;/tt&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With all this loaded into appropriate locations in memory, the bootloader hands
over to the Linux kernel, which mounts the initramfs as its initial root
file-system, and launches the &lt;tt class="docutils literal"&gt;/init&lt;/tt&gt; binary within&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;In the case of Ubuntu, this is the usual initramfs you&amp;#8217;ll find on pretty much
any Ubuntu installation. It&amp;#8217;ll search the kernel command line for the &amp;#8220;real&amp;#8221;
root device, attempt to mount it, and &amp;#8220;pivot&amp;#8221; the root that&amp;nbsp;mount.&lt;/p&gt;
&lt;p&gt;For the &lt;span class="caps"&gt;NFS&lt;/span&gt; case, the kernel command line would include something like
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nfsroot=server:/exports/ubuntu-jammy&lt;/span&gt; &lt;span class="pre"&gt;root=/dev/nfs&lt;/span&gt;&lt;/tt&gt;. For the &lt;span class="caps"&gt;NBD&lt;/span&gt; case, the
kernel command line would include something like &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;nbdroot=server/ubuntu-jammy&lt;/span&gt;
&lt;span class="pre"&gt;root=/dev/nbd0p2&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;At this point you should have a basic understanding of the Pi&amp;#8217;s netboot
process. Let&amp;#8217;s explore what the server side configuration for &lt;span class="caps"&gt;TFTP&lt;/span&gt;, &lt;span class="caps"&gt;NFS&lt;/span&gt;, and
&lt;span class="caps"&gt;NBD&lt;/span&gt; can look like from a high level. We&amp;#8217;ll get into specifics in the next post;
this is just to give you an idea of the considerations and possibilities&amp;nbsp;involved.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="glory-hallelujah"&gt;
&lt;h2&gt;Glory,&amp;nbsp;hallelujah!&lt;/h2&gt;
&lt;p&gt;A typical server &lt;span class="caps"&gt;TFTP&lt;/span&gt; configuration (whether subsequently &lt;span class="caps"&gt;NFS&lt;/span&gt;, &lt;span class="caps"&gt;NBD&lt;/span&gt;, or anything
else) is to have the boot partition of an &lt;span class="caps"&gt;OS&lt;/span&gt; image unpacked or mounted under a
particular path, and then make a symlink from the Raspberry Pi&amp;#8217;s serial number
to that path. Re-writing the symlink is then enough to switch which Pi boots
which&amp;nbsp;image.&lt;/p&gt;
&lt;p&gt;While this much does not differ between the &lt;span class="caps"&gt;NFS&lt;/span&gt; and &lt;span class="caps"&gt;NBD&lt;/span&gt; cases, there is the
question of how to make the boot files&amp;nbsp;available.&lt;/p&gt;
&lt;p&gt;In the case of &lt;span class="caps"&gt;NFS&lt;/span&gt;, as the server is serving a file-system it is typical to
simply unpack the entire &lt;span class="caps"&gt;OS&lt;/span&gt; image, both the root and boot file-systems, into a
directory and call that the &amp;#8220;image&amp;#8221; that is served. The symlink for the Pi&amp;#8217;s
serial number then points to the &lt;tt class="docutils literal"&gt;/boot/firmware&lt;/tt&gt; directory within the
unpacked&amp;nbsp;image.&lt;/p&gt;
&lt;p&gt;For example, if we have two &lt;span class="caps"&gt;OS&lt;/span&gt; images, &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;ubuntu-jammy&lt;/span&gt;&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;ubuntu-mantic&lt;/span&gt;&lt;/tt&gt;,
and two Raspberry Pis with serial numbers &lt;tt class="docutils literal"&gt;1234abcd&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;4567cdef&lt;/tt&gt; we
might lay out our files like&amp;nbsp;so:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
/
├─ …
├─ srv/
│  ├─ ubuntu-jammy/
│  │  ├─ bin/
│  │  ├─ boot/
│  │  │  ├─ firmware/
│  │  │  └─ …
│  │  └─ …
│  ├─ ubuntu-mantic/
│  │  ├─ bin/
│  │  ├─ boot/
│  │  │  ├─ firmware/
│  │  │  └─ …
│  │  └─ …
│  └─ boot/
│     ├─ 1234abcd-&amp;gt;/srv/ubuntu-jammy/boot/firmware
│     └─ 4567cdef-&amp;gt;/srv/ubuntu-mantic/boot/firmware
└─ …
&lt;/pre&gt;
&lt;p&gt;The two images are completely unpacked under &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/srv/ubuntu-jammy&lt;/span&gt;&lt;/tt&gt; and
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/srv/ubuntu-mantic&lt;/span&gt;&lt;/tt&gt;, then symlinks under &lt;tt class="docutils literal"&gt;/srv/boot&lt;/tt&gt; point to the
&lt;tt class="docutils literal"&gt;/boot/firmware&lt;/tt&gt; directories of the unpacked images. Our &lt;span class="caps"&gt;TFTP&lt;/span&gt; server is
configured to serve &lt;tt class="docutils literal"&gt;/srv/boot&lt;/tt&gt;, and our &lt;span class="caps"&gt;NFS&lt;/span&gt; server to export &lt;tt class="docutils literal"&gt;/srv&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;The advantages are that it&amp;#8217;s a relatively simple setup, requiring no special
mounts on the server side, and that (assuming all unpacked images are in the
same file-system on the server), all available space is shared between all
netbooting Pis. The disadvantage (other than it being an &lt;span class="caps"&gt;NFS&lt;/span&gt; boot setup) is
that all available space is shared between all netbooting Pis so one Pi can use
up all available space for everyone, unless additional steps like quotas are&amp;nbsp;taken.&lt;/p&gt;
&lt;p&gt;In the case of &lt;span class="caps"&gt;NBD&lt;/span&gt;, which we&amp;#8217;ll explore more in the next post, I would suggest
a simple setup is to leave the &lt;span class="caps"&gt;OS&lt;/span&gt; image as it is (in a file) and simply expand
the file to the desired size. A loop device can be created for the image file,
with a partition scan to find the boot partition, which can then be mounted. I
would still recommend using a symlink to point to the mount for ease of
changing&amp;nbsp;later.&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s consider the scenario from before (two images, jammy and mantic, two Pis
with known serial numbers) in this&amp;nbsp;setup:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
/
├─ …
├─ srv/
│  ├─ ubuntu-jammy.img
│  ├─ ubuntu-mantic.img
│  ├─ mnt/
│  │  ├─ ubuntu-jammy/  (mount of ubuntu-jammy.img partition 1)
│  │  └─ ubuntu-mantic/  (mount of ubuntu-mantic.img partition 1)
│  └─ boot/
│     ├─ 1234abcd-&amp;gt;/srv/mnt/ubuntu-jammy
│     └─ 4567cdef-&amp;gt;/srv/mnt/ubuntu-mantic
└─ …
&lt;/pre&gt;
&lt;p&gt;The two images are simply placed under &lt;tt class="docutils literal"&gt;/srv&lt;/tt&gt;. Loop devices are created of
each, and the first partition mounted under appropriate directories under
&lt;tt class="docutils literal"&gt;/srv/mnt&lt;/tt&gt;. Symlinks under &lt;tt class="docutils literal"&gt;/srv/boot&lt;/tt&gt; link Pi serial numbers to the
appropriate mount. Our &lt;span class="caps"&gt;TFTP&lt;/span&gt; server is configured to serve &lt;tt class="docutils literal"&gt;/srv/boot&lt;/tt&gt; as
before, and our &lt;span class="caps"&gt;NBD&lt;/span&gt; server is configured to export each &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;/srv/*.img&lt;/span&gt;&lt;/tt&gt; file as
a block&amp;nbsp;device.&lt;/p&gt;
&lt;p&gt;The advantages are that this is a trivial setup (the image doesn&amp;#8217;t even need
unpacking), and that each image is necessarily limited to its own storage. The
disadvantage is that storage isn&amp;#8217;t shared between images. However, we saw in
the &lt;a class="reference external" href="https://waldorf.waveform.org.uk/2023/building-blocks.html"&gt;prior post&lt;/a&gt; that there&amp;#8217;s all
manner of things we can do with block devices, so we&amp;#8217;ll explore some
possibilities there in a future post&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;I should warn there&amp;#8217;s also a nicely hidden, but quite serious issue with this
set up which we&amp;#8217;ll look at next time when we actually build it (and then fix
it … obviously). Anyway, that&amp;#8217;s all for&amp;nbsp;now!&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="simples" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[1]&lt;/td&gt;&lt;td&gt;&lt;em&gt;(&lt;a class="fn-backref" href="#footnote-reference-1"&gt;1&lt;/a&gt;, &lt;a class="fn-backref" href="#footnote-reference-5"&gt;2&lt;/a&gt;, &lt;a class="fn-backref" href="#footnote-reference-8"&gt;3&lt;/a&gt;)&lt;/em&gt; This is a gross over-simplification (or an outright lie), but
serves to make the point.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="btrfs" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Alright, some of them don&amp;#8217;t or only recently added the
functionality, or it&amp;#8217;s still experimental (&lt;em&gt;cough&lt;/em&gt; btrfs), but they&amp;#8217;re
usually the file-systems that have their own rules for block mapping.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="neversimple" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-3"&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Caching is never simple.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="swapnfs" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-4"&gt;[4]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;No other file system has a &amp;#8220;make swap work on this file-system&amp;#8221;
&lt;a class="reference external" href="https://cateee.net/lkddb/web-lkddb/NFS_SWAP.html"&gt;kernel config&lt;/a&gt; item&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="docker" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-6"&gt;[5]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;I&amp;#8217;m told Docker overlays also have issues with &lt;span class="caps"&gt;NFS&lt;/span&gt; but it&amp;#8217;s not
something I&amp;#8217;ve played with directly and I&amp;#8217;ve not had the time to verify it
that for this article.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="macsecure" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-7"&gt;[6]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Though not for &amp;#8220;security&amp;#8221; (&lt;a class="reference external" href="https://en.wikipedia.org/wiki/MAC_address"&gt;MACs&lt;/a&gt; can be trivially
&lt;a class="reference external" href="https://en.wikipedia.org/wiki/MAC_spoofing"&gt;spoofed&lt;/a&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="serial" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-9"&gt;[7]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;The &lt;tt class="docutils literal"&gt;&lt;span class="caps"&gt;SERIAL&lt;/span&gt;&lt;/tt&gt; portion is the serial number of the Pi (which can
be found at the end of the output of &lt;tt class="docutils literal"&gt;cat /proc/cpuinfo&lt;/tt&gt;), in lower-case
hexidecimal format. If the serial number is longer than 8 characters, only
the last 8 characters are used.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="pi"></category><category term="net"></category><category term="nbd"></category></entry></feed>