Booting a camera with its root filesystem on an NFS share lets you develop and debug without reflashing. Edit files on your workstation, reboot the camera, and the change is live. It also scales to a fleet: many cameras can boot from one server, each optionally served a different kernel/rootfs.
There are two ways to do this:
- Manual method — point u-boot at an NFS share by hand, using any existing firmware. Quick, no rebuild.
- The
openipc-nfs-rootpackage — build a purpose-made NFS-root image with stable MAC handling, a writable overlay, and automatic boot-time setup (password, DNS/NTP, hostname, SSH key).
This walkthrough boots a camera over NFS/TFTP using any existing firmware, by
typing commands at the u-boot prompt over a UART console. Nothing in flash is
modified unless you explicitly saveenv, so it is non-destructive and easy to
revert. The worked example uses a XiongMai XM530 camera; only the SoC-specific
values (mem=, load address, mtdparts, console name) differ between boards.
You will need two services reachable from the camera — a TFTP server for the kernel and an NFS server for the rootfs. They can be the same machine.
Wire a 3.3 V USB-UART adapter to the camera's GND / RX / TX pads and open a serial console at 115200 baud:
picocom -b 115200 /dev/ttyUSB0Warning: never connect the UART adapter to a pad carrying 12 V — it will destroy the adapter. Confirm pad voltages with a multimeter first (RX/TX idle at ~3.3 V).
Power the camera and press Ctrl+C repeatedly to interrupt autoboot and drop to
the u-boot prompt.
Before changing anything, print and copy the existing variables so you can restore them later:
printenv
Note especially bootcmd, bootargs, ipaddr, serverip, ethaddr, and the
mtdparts string. A typical XM530 stock environment looks like:
bootargs=mem=35M console=ttyAMA0,115200 root=/dev/mtdblock2 rootfstype=cramfs mtdparts=xm_sfc:256K(boot),1536K(kernel),1280K(romfs),4544K(user),256K(custom),320K(mtd)
bootcmd=sf probe 0;sf read 80007fc0 40000 180000;bootm 80007fc0
ipaddr=192.168.1.10
serverip=192.168.1.107
ethaddr=00:12:41:00:00:01
Check the network works from u-boot:
ping 192.168.1.1
Install a TFTP daemon and point it at a root directory, e.g. /mnt/tftproot. With
atftp on the server (/etc/conf.d/atftp or your distro's equivalent):
TFTPD_ROOT="/mnt/tftproot"
TFTPD_OPTS="--daemon --user nobody --group nobody"
Obtain a kernel image. Either download the prebuilt OpenIPC release archive for
your SoC and extract the uImage, or build it from source. Place it in the TFTP
root under a recognisable name, e.g. /mnt/tftproot/uImage.xm530.
Verify the server serves it:
atftp --get -r uImage.xm530 -l /tmp/uImage.xm530 192.168.1.1Export a directory that will hold the unpacked rootfs (/etc/exports):
/mnt/nfsroot 192.168.0.0/16(async,rw,no_subtree_check,no_root_squash)
sudo exportfs -raPopulate it with an OpenIPC rootfs. From a release archive, unpack the SquashFS
image with unsquashfs into /mnt/nfsroot/; from a local build, untar
rootfs.tar there (preserving permissions). Confirm it is writable:
sudo mount -o nolock,rw 192.168.1.3:/mnt/nfsroot /mnt/test
sudo touch /mnt/test/writetest && sudo rm /mnt/test/writetestIf you get read-only errors, a parent directory is likely exported read-only — the camera will still boot but cannot write. Fix the export for a usable system.
Set the server/client addresses, build the NFS bootargs, and boot. Note u-boot
setenv uses a space (no =) after the variable name:
setenv serverip 192.168.1.1
setenv ipaddr 192.168.1.10
setenv bootargsnfs 'mem=35M console=ttyAMA0,115200 panic=20 root=/dev/nfs rootfstype=nfs nfsroot=192.168.1.3:/mnt/nfsroot,v4,nolock,tcp rw ip=192.168.1.10:192.168.1.3:192.168.1.1:255.255.255.0::eth0'
setenv nfsboot 'tftp 0x81000000 uImage.xm530;setenv setargs setenv bootargs ${bootargsnfs};run setargs;bootm 0x81000000'
run nfsboot
The kernel ip= argument is positional:
ip=<client-ip>:<server-ip>:<gateway>:<netmask>:<hostname>:<device>:<autoconf>
A successful boot prints something like:
IP-Config: Complete:
device=eth0, hwaddr=00:12:41:09:9c:1b, ipaddr=192.168.1.10, mask=255.255.255.0, gw=192.168.1.1
bootserver=192.168.1.3, rootserver=192.168.1.3, rootpath=
Always use
tcpin thenfsroot=options. NFS over UDP can fail silently on some setups.panic=20reboots the board 20 s after a kernel panic, which is handy while iterating.
Log in over the console or SSH as root. A camera that has not been set up yet has no
password: it asks you to choose one, and only then gives you a shell. That password is
also what the web interface and RTSP check.
On XiongMai boards, stop the watchdog immediately so it doesn't reboot the camera mid-setup:
rmmod xm_wdtIf sensor auto-detection didn't run (common on a read-only rootfs), create the sensor symlink by hand and start the streamer — for example:
ln -s /usr/lib/sensors/libsns_X50_XM530.so /usr/lib/libsns.so
majesticTo boot from NFS automatically on every power-on, set bootcmd and persist it —
only after you've confirmed the manual boot works:
setenv bootcmd 'tftp 0x81000000 uImage.xm530;setenv setargs setenv bootargs ${bootargsnfs};run setargs;bootm 0x81000000'
saveenv
To return to the firmware in flash, restore the bootcmd/bootargs you saved in
step 2 and saveenv again. Because the NFS setup lives only in u-boot variables
(unless you flashed anything), reverting is just restoring those variables.
The bootargs above differ per SoC. For example, a HI3516EV200 board uses an
ttyAMA0 console and its own memory size:
setenv bootargsnfs 'mem=${osmem:-32M} console=ttyAMA0,115200 panic=20 root=/dev/nfs rootfstype=nfs nfsroot=192.168.1.254:/media/nfs/hi3516ev200,v3,nolock,tcp rw ip=192.168.1.55:192.168.1.254:192.168.1.254:255.255.255.0::eth0'
The openipc-nfs-root Buildroot package builds a kernel + rootfs tarball pair
tailored for NFS boot, instead of a flashable firmware image. It is opt-in and
disabled by default — boards that don't enable it get the normal flashable
firmware and no kernel changes.
- No flash wear, no reflash cycle — iterate on
/etc, init scripts, or binaries directly on the server. - Bigger rootfs — NFS root is not bound by the 8/16 MB flash limits, so you
can include debug tooling (
gdb,strace, test harnesses) that never fits in a normal build. - Stable per-device MAC — a single kernel patch makes the early-boot network
take its MAC from the device tree if present, otherwise from an
ethaddr=boot argument, before falling back to a random MAC. Stable MACs make DHCP reservations and per-device NFS exports reliable. - Fleet provisioning — boot many cameras from one server; key each device on
its MAC or on DHCP option 60 (settable in the u-boot env as
bootp_vci) to decide which kernel/rootfs to serve.
The helper script builds without permanently editing any defconfig — it
temporarily appends the package settings to general/openipc.fragment, builds,
and restores the fragment afterwards:
./contrib/make_nfsroot <defconfig> [options]| Flag | Meaning |
|---|---|
--passwd |
Root password — plaintext, or a $1$…/$5$…/$6$… hash |
--tz |
IANA timezone, e.g. Europe/Berlin (POSIX TZ derived at build) |
--ntp |
Static NTP server (else learned from DHCP) |
--dns |
Static DNS server (else learned from DHCP / u-boot) |
--hostname |
Fallback hostname if none supplied by DHCP / u-boot env |
--pkgs |
File listing extra BR2_PACKAGE_*=y lines to fold in |
Example:
./contrib/make_nfsroot gk7205v300_lite_defconfig \
--passwd 'hunter2' --tz Europe/Berlin --hostname cam-lab-01On success it prints the output paths:
NFS-root build complete
-----------------------
kernel: [1.8M] output/images/uImage.gk7205v300
rootfs: [15M] output/images/rootfs.gk7205v300.tar
Alternatively, enable it directly and build as usual — the two paths are equivalent:
echo 'BR2_PACKAGE_OPENIPC_NFS_ROOT=y' >> general/openipc.fragment
make BOARD=gk7205v300_lite| Symbol | Default | Notes |
|---|---|---|
BR2_PACKAGE_OPENIPC_NFS_ROOT |
n | Master switch |
…_TMPFS_SIZE |
16M |
Writable overlay size in read-only mode |
…_PASSWD |
12345 |
Root password (plaintext or hash) — change it for anything but a lab |
…_TIMEZONE |
(host) | IANA zone; empty → detect from build host |
…_NTP_SERVER |
(DHCP) | Static NTP server; empty → DHCP / default route |
…_DNS_SERVER |
(DHCP) | Static DNS server; empty → DHCP / u-boot |
…_HOSTNAME |
(DHCP) | Fallback hostname |
Unpack the rootfs onto your NFS export, preserving permissions:
sudo mkdir -p /srv/nfs/cam-gk7205v300
sudo tar -xpf output/images/rootfs.gk7205v300.tar -C /srv/nfs/cam-gk7205v300Export it (/etc/exports):
/srv/nfs/cam-gk7205v300 192.168.1.0/24(rw,no_root_squash,no_subtree_check)
sudo exportfs -raServe the kernel (output/images/uImage.<soc>) over TFTP, or flash only the
kernel partition while leaving the rootfs on NFS.
setenv bootargs console=ttyS0,115200 \
ip=dhcp \
root=/dev/nfs \
nfsroot=192.168.1.10:/srv/nfs/cam-gk7205v300,vers=3,tcp \
ro \
ethaddr=00:11:22:33:44:55 \
hostname=cam-lab-01 \
mtdparts=sfc:256k(boot),64k(env),-(unused)
Include an
mtdparts=definition with at least thebootandenvpartitions. Without itfw_setenvcannot locate the u-boot environment, so/etc/fw_env.configis not created on boot (causing log warnings) and SSH host key persistence fails. Adjust the partition layout to match your flash.
Tokens the OpenIPC init keys on:
root=/dev/nfs— selects the NFS-root path in/init.ro/rw— read-only (tmpfs overlay) vs read-write (direct). See below.ethaddr=— stable MAC, used only when the device tree has no MAC. A device-tree MAC always wins, even ifethaddr=is also present. With neither, a random MAC is used. The u-boot default00:00:23:34:45:66is deliberately ignored so it never overrides a real address.hostname=— the preferred way to name a device, because it works across multiple cameras sharing one read-only rootfs. It is only overridden if the hostname is sent in the DHCP request and the server returns a different name (e.g. a static lease); with dynamic leases the requested name is honoured. For DHCP fleets, sethostname=(bootargs) andbootp_vci(u-boot env) to the same unique name — that makes per-device tagging and option-serving easy to configure.
Static IP instead of DHCP:
ip=192.168.1.50:192.168.1.10:192.168.1.1:255.255.255.0:cam-lab-01:eth0:off
Read-only (ro) — recommended for fleets. The export is mounted read-only and
a tmpfs overlay (…_TMPFS_SIZE, default 16 MB) provides the writable layer. Many
cameras can share one export safely, each with its own private, volatile overlay.
Runtime changes are lost on reboot. The root password is reapplied every boot, and
a persistent SSH host key is generated once and stored in the u-boot environment,
then restored each boot — so the key is stable even though the rootfs isn't.
Read-write (rw) — single device per export. The camera writes directly to
the share and changes persist, but only one device may use a given export —
concurrent writers will corrupt it. First-boot setup runs once, guarded by
/etc/firstboot.
A convenient layout that keeps a shared base image but allows per-device tweaks,
using overlayfs on the NFS host:
- Keep a single read-only base rootfs per platform (the
make_nfsrootoutput). - For each camera, create a small overlay directory on the server and mount it
with
overlayfs, using the shared base aslowerdirand the per-camera directory asupperdir. - Export and mount each camera RW against its own merged overlay.
- Per-device changes (SSH keys,
majestictweaks, etc.) land in the camera's upper layer; the common base stays pristine and can be replaced wholesale when you deploy a newmake_nfsrootbuild.
This cleanly separates device-specific state from the base image. Note it relies
on overlayfs running on the NFS host (Linux-specific) — it cannot be done on
the camera itself, because the kernel's NFS client does not support using an NFS
mount as an overlay upperdir.
On an NFS root, eth0 and lo are brought up by the kernel's early network
config before userspace starts. To stop any service from reconfiguring or downing
eth0 — which would instantly kill the rootfs — the package installs no-op
ifup/ifdown profiles. Userspace still sees the interfaces as up and may
"toggle" them, but the operations do nothing; the link stays as the kernel left
it. This is intentional and specific to NFS-root builds.
| Symptom | Likely cause / fix |
|---|---|
Build fails: requires Linux >= 4.5 |
The board's kernel predates eth_platform_get_mac_address(); that board can't use the package. |
| Random/wrong MAC each boot | No ethaddr= and no device-tree MAC. Add ethaddr= to bootargs or provide it via the device tree. |
| Changes vanish after reboot | You are in ro mode — expected (tmpfs overlay). Use rw for persistence, single device only. |
| Corrupted rootfs with several cameras | Multiple devices on one rw export. Switch them to ro (shared) or give each its own rw export. |
SSH host-key changes each boot / fw_env.config warnings |
fw_setenv can't reach the u-boot env. Add an mtdparts= with boot+env partitions to the bootargs, and check the env is writable. |
- Buildroot manual: Linux kernel specific infrastructure