feat(extensions): add netboot extension for TFTP+NFS diskless boot#9656
feat(extensions): add netboot extension for TFTP+NFS diskless boot#9656iav wants to merge 3 commits intoarmbian:mainfrom
Conversation
…YPE=nfs ROOTFS_TYPE=nfs produces a rootfs tarball via `tar cp ... | gzip` in rootfs-to-image.sh and never mounts NFS on the build host. The NFS client runs on the target's kernel at boot time, so host-side NFS filesystem support is irrelevant. Additionally, the check runs inside the Armbian docker launcher where /lib/modules is empty, causing `modprobe nfs` to fail even when the host kernel has nfs.ko available. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous template was hardcoded for sunxi arm32 — `zImage`/`bootz`,
`console=tty1 console=ttyS0,115200`, `disp.screen0_output_mode=...`,
`ext4load/fatload mmc 0`. It did not work on arm64 boards (e.g.
rockchip64/helios64) and baked in a 115200 baud that breaks non-standard
UART speeds (helios64 runs at 1500000).
Rewrites the U-Boot hush script to:
- Prefer `Image` + `booti` (arm64), fall back to `zImage` + `bootz` (arm32).
- Load via `${devtype} ${devnum}:${distro_bootpart}` from U-Boot's distro
bootflow scanner instead of hardcoded `mmc 0`.
- Drop the `console=` overrides — let the kernel resolve the console from
DTB `/chosen/stdout-path`, keeping `earlycon` for early output.
- Drop sunxi-specific `disp.screen0_output_mode` and legacy `.next`/
`script.bin` probe paths.
For the full TFTP+NFS netboot flow see the new `netboot` extension; this
template remains the hybrid path where the kernel + DTB live on a local
boot partition and only the rootfs is NFS-mounted.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR introduces a comprehensive netboot extension for Armbian that enables network boot via TFTP and NFS root filesystems. Changes include a revised U-Boot boot script for NFS root support, a new netboot extension system with artifact staging, configurable rootfs compression/export, and extensive documentation covering deployment workflows and troubleshooting. Changes
Sequence Diagram(s)sequenceDiagram
participant Builder as Armbian Builder
participant Ext as Netboot Extension
participant KernelCfg as Kernel Config
participant ImgGen as Image Generator
participant TFTP as TFTP Staging
participant Hook as netboot_artifacts_ready Hook
Builder->>Ext: trigger extension (ROOTFS_TYPE=nfs)
Ext->>Ext: validate & normalize variables<br/>(server, MAC, TFTP/NFS paths)
Ext->>KernelCfg: configure NFS root support<br/>(IP_PNP, ROOT_NFS, NFS_FS, etc.)
Ext->>ImgGen: customize image<br/>(disable resize, skip firstlogin)
ImgGen->>ImgGen: build rootfs
Ext->>ImgGen: handle ROOTFS_COMPRESSION<br/>(gzip/zstd/none)
ImgGen->>ImgGen: generate compressed archive<br/>or export via rsync
Ext->>TFTP: collect kernel, DTB, uInitrd<br/>to TFTP directory structure
Ext->>TFTP: generate extlinux/PXELINUX<br/>config (default + per-MAC)
Ext->>TFTP: construct nfsroot= param
Ext->>Hook: invoke with artifact context<br/>(NETBOOT_TFTP_OUT, NETBOOT_PXE_FILE, etc.)
Hook->>Hook: deploy to server
sequenceDiagram
participant DHCP as DHCP Server
participant Client as Client Device
participant UBoot as U-Boot
participant TFTP as TFTP Server
participant NFS as NFS Server
Client->>DHCP: request IP + PXE options
DHCP->>Client: provide IP, siaddr, bootfile
Client->>UBoot: load via PXE
UBoot->>TFTP: load pxelinux.cfg/01-MAC<br/>or pxelinux.cfg/default
TFTP->>UBoot: extlinux config
UBoot->>TFTP: load kernel (Image/zImage)
TFTP->>UBoot: kernel image
UBoot->>TFTP: load DTB
TFTP->>UBoot: device tree blob
UBoot->>TFTP: load uInitrd (optional)
TFTP->>UBoot: initramfs
UBoot->>UBoot: construct kernel cmdline<br/>(root=/dev/nfs, nfsroot=, etc.)
UBoot->>NFS: mount root filesystem<br/>via configured nfsroot path
NFS->>Client: provide rootfs mount
Client->>Client: boot kernel with NFS root
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@extensions/netboot/netboot.sh`:
- Around line 38-42: The function
extension_prepare_config__netboot_force_nfs_rootfs sets ROOTFS_TYPE too late (it
runs during do_extra_configuration) so NFS-specific branches in
do_main_configuration never see it; move the ROOTFS_TYPE assignment earlier by
ensuring extension_prepare_config__netboot_force_nfs_rootfs (or its logic) runs
before do_main_configuration—either call that function in the init/startup hook
that executes prior to main configuration (e.g., extension_init or top-level
script before do_main_configuration) or export/set declare -g ROOTFS_TYPE="nfs"
at script initialization so the global variable is in effect for
do_main_configuration and the NFS setup in
lib/functions/configuration/main-config.sh executes.
- Around line 147-149: The PXE staging copies ${MOUNT}/boot/uInitrd but never
references it in the generated extlinux.cfg, so U-Boot won't load the initramfs;
modify the netboot stanza generation to set an initrd_line variable when the
file exists (mirror how fdt_line is created — e.g. check for
"${MOUNT}/boot/uInitrd", set initrd_line="INITRD ${tftp_prefix_dir}/uInitrd")
and then include ${initrd_line} in the emitted PXE stanza alongside fdt_line so
the INITRD directive is present for U-Boot.
In `@extensions/netboot/README.md`:
- Around line 137-155: The README has multiple unlabeled fenced code blocks
(e.g., the directory tree block showing "/srv/netboot/" and other blocks around
lines 159-164, 171-175, 465-489, 544-547); update each triple-backtick fence to
include an appropriate info string (for example use sh, ini, or text as
appropriate) so markdownlint stops flagging them—locate the fences by searching
for the blocks that display the directory tree and configuration snippets and
add the matching language tag to each opening ``` line.
In `@lib/functions/image/rootfs-to-image.sh`:
- Around line 82-93: The rsync into ROOTFS_EXPORT_DIR can leave stale files
behind when the export tree is reused; update the rsync invocation in
rootfs-to-image.sh (the run_host_command_logged rsync call) to include --delete
(and optionally --delete-excluded) so files removed from the source are removed
from "${ROOTFS_EXPORT_DIR}" as well; add the flag either into $rsync_ea or
directly in the rsync command invocation to ensure exported NFS root mirrors the
built image.
- Around line 69-76: The archive creation pipeline (tar … | pv … |
${archive_filter} > "${ROOTFS_ARCHIVE_PATH}") must be guarded with pipefail so
failures in tar or pv aren't masked by a later stage; wrap that pipeline in a
shell context with set -o pipefail (or enable set -o pipefail locally before
running the pipeline) so a nonzero exit from tar or pv will cause the script to
fail and propagate the error from creating ROOTFS_ARCHIVE_PATH; update the block
around display_alert/ tar / pv / ${archive_filter} to ensure pipefail is active
for that pipeline.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: fca3bef5-36a8-4aaa-81dd-645853e7579e
📒 Files selected for processing (5)
config/templates/nfs-boot.cmd.templateextensions/netboot/README.mdextensions/netboot/netboot.shlib/functions/configuration/main-config.shlib/functions/image/rootfs-to-image.sh
`nfs-root` is a new rootfs type distinct from the existing `nfs` hybrid mode. Selecting it wires the `netboot` extension from the core `ROOTFS_TYPE` dispatch in `do_main_configuration`, so callers no longer need a separate `ENABLE_EXTENSIONS=netboot`. The legacy `nfs` branch (kernel+DTB on local boot partition, `/` over NFS) is untouched — both paths coexist until the hybrid mode's future is decided. Core plumbing mirrors the `nfs` branch for all paths where local root storage would be meaningless: partition layout skip (`prepare_partitions`), archive/export gate and version suffix (`rootfs-to-image.sh`), and the host-side filesystem compatibility check in `main-config.sh`. Extension hooks now key on `ROOTFS_TYPE=nfs-root` instead of guessing from `nfs`, removing the `force ROOTFS_TYPE=nfs` shim that ran too late relative to `do_main_configuration`. Also folded in from CodeRabbit review on PR armbian#9656: - pipefail around tar|pv|compressor so truncated archives no longer slip through on an intermediate stage failure - `rsync --delete` on `ROOTFS_EXPORT_DIR` so stale files from a previous build don't linger in the NFS export tree - explicit `INITRD` directive in extlinux when `uInitrd` is staged; U-Boot only loads an initramfs when the stanza names it - README updated to document `ROOTFS_TYPE=nfs-root` as the single switch Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Very nice. With recent u-boot (2026.04, thanks to Kwiboo), one can enable LWIP networking stack for better TFTP performance (loading kernel and initramfs) -- there's also some TFTP Window Size stuff that can be tuned. U-boot with LWIP + u-boot's |
|
Thanks for the pointer! Let's get this merged first — iterating on something that works is the easy part. |
Adds ROOTFS_TYPE=nfs-root — a new rootfs type for full network boot
where the only thing on the device's local storage is U-Boot itself.
The kernel, DTB, optional uInitrd and PXE config come from TFTP;
rootfs is mounted over NFS.
A new case branch in do_main_configuration auto-enables the netboot
extension, symmetric with existing fs-f2fs-support / fs-btrfs wiring.
The legacy ROOTFS_TYPE=nfs (hybrid: kernel on local storage, only /
over NFS) is untouched — both paths coexist.
extensions/netboot/netboot.sh hooks:
- extension_prepare_config: validate variables, compute defaults for
NETBOOT_TFTP_PREFIX / NETBOOT_NFS_PATH (shared by
LINUXFAMILY/BOARD/BRANCH/RELEASE, or per-host when NETBOOT_HOSTNAME
is set), normalize NETBOOT_CLIENT_MAC to PXELINUX 01-<mac> form,
fail fast on bad ROOTFS_COMPRESSION/ROOTFS_EXPORT_DIR combinations.
- custom_kernel_config: enable ROOT_NFS, NFS_FS, NFS_V3, IP_PNP,
IP_PNP_DHCP so root=/dev/nfs ip=dhcp works without an initrd.
- post_customize_image: drop armbian-resize-filesystem.service
(meaningless on NFS root) and /root/.not_logged_in_yet (the
armbian-firstlogin interactive wizard blocks bring-up when there is
no interactive console). armbian-firstrun.service stays — it only
regenerates SSH host keys.
- host_pre_docker_launch: bind-mount ROOTFS_EXPORT_DIR into the build
container when it lives outside ${SRC}.
- pre_umount_final_image: assemble the TFTP tree (Image/zImage, dtb/,
uInitrd), write pxelinux.cfg/{default.example | 01-<mac>} with the
right FDT/FDTDIR line and explicit INITRD directive when uInitrd is
present, expose a netboot_artifacts_ready hook for userpatches.
Core plumbing (main-config.sh, partitioning.sh, rootfs-to-image.sh):
- nfs-root case branch in ROOTFS_TYPE dispatch enables the extension
- prepare_partitions skips root partition and SD-size sanity check
- check_filesystem_compatibility_on_host skipped for nfs-root
- ROOTFS_COMPRESSION=gzip|zstd|none with ROOTFS_EXPORT_DIR support
- pipefail around tar|pv|compressor pipeline
- rsync --delete on ROOTFS_EXPORT_DIR to prevent stale files
- ROOTFS_ARCHIVE_PATH exported for downstream hooks
Directory-based extension layout (extensions/netboot/) so the usage
guide (README.md) lives next to the code.
No console= in APPEND — hardcoding a baud breaks boards with
non-standard UART speeds; the kernel resolves console from DTB
/chosen/stdout-path, earlycon keeps early output.
Validated end-to-end on helios64 (Armbian 26.05 edge/resolute):
archive-workflow and tree-workflow both boot cleanly over TFTP+NFS
to a login prompt with no armbian-firstlogin wizard and no resize2fs
errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
a76e4b2 to
fec99ac
Compare
Summary
Adds an opt-in
netbootextension that produces a full network-boot payload — kernel, DTB, optionaluInitrd,pxelinux.cfgand an NFS-exportable rootfs — from a regular Armbian build. For netboot the only thing that has to live on the device's local storage is U-Boot itself: the kernel and DTB come from TFTP,/is mounted over NFS, nothing else on mmc/eMMC/USB is touched during early boot.Today
ROOTFS_TYPE=nfsalone only gives a hybrid image (kernel+DTB still on SD/eMMC, only/over NFS). This PR keeps that path working and layers a real netboot flow on top via a newROOTFS_TYPE=nfs-root.What changes
lib/functions/configuration/main-config.sh— newROOTFS_TYPE=nfs-rootcase branch, symmetric with the existingfs-f2fs-support/fs-btrfswiring: the branch auto-enables thenetbootextension soROOTFS_TYPE=nfs-rootis the single switch that selects the full netboot flow.check_filesystem_compatibility_on_hostis skipped for bothnfsandnfs-root— the host-side check is a sanity net for block-device targets and falsely rejects valid NFS configurations.extensions/netboot/netboot.sh— new, directory-based extension. Directory layout (rather than a singleextensions/netboot.shfile) exists so the long-form usage guide can live next to the code asREADME.md; inlining documentation of that size into the script body would be unwieldy. Hooks:extension_prepare_config— validate variables, compute defaults forNETBOOT_TFTP_PREFIX/NETBOOT_NFS_PATH(shared byLINUXFAMILY/BOARD/BRANCH/RELEASE, or per-host whenNETBOOT_HOSTNAMEis set), normalizeNETBOOT_CLIENT_MACto the01-<mac>PXELINUX form, fail fast on badROOTFS_COMPRESSION/ROOTFS_EXPORT_DIRcombinations before debootstrap.custom_kernel_config— enableROOT_NFS,NFS_FS,NFS_V3,IP_PNP,IP_PNP_DHCPsoroot=/dev/nfs ip=dhcpworks without an initrd.post_customize_image— droparmbian-resize-filesystem.service(meaningless on NFS root) and/root/.not_logged_in_yet(thearmbian-firstlogininteractive wizard blocks bring-up when there is no interactive console).armbian-firstrun.servicestays — it only regenerates SSH host keys.host_pre_docker_launch— bind-mountROOTFS_EXPORT_DIRinto the build container when it lives outside${SRC}, so the single-step builder-as-NFS-server workflow works the same inside and outside Docker.pre_umount_final_image— assemble the TFTP tree (Image/zImage,dtb/,uInitrd), writepxelinux.cfg/{default.example | 01-<mac>}with the rightFDT/FDTDIRline and an explicitINITRDdirective whenuInitrdis present (U-Boot's PXE parser only loads an initramfs when the stanza names it), expose anetboot_artifacts_readyhook for userpatches that deploy to a real server.lib/functions/image/rootfs-to-image.sh— two new controls for NFS-rootfs builds (bothROOTFS_TYPE=nfsandnfs-root):ROOTFS_COMPRESSION=gzip|zstd|none(defaultgzip).gzip→.tar.gz,zstd→.tar.zst,none→ no archive at all. Rejected early ifnoneis set withoutROOTFS_EXPORT_DIR. Thetar | pv | compressorpipeline now runs in aset -o pipefailsubshell so a broken archive step actually fails the build instead of producing a silently truncated file.ROOTFS_EXPORT_DIR=/abs/path— also (or only) rsync the rootfs tree into this directory. Can point anywhere on the build host's filesystem — inside or outside the Armbian tree (the extension bind-mounts external paths into the Docker container automatically). If the path is an NFS mount or any other network filesystem, the build writes directly to the remote server with no intermediate archive. The rsync uses--deleteso a re-used export dir stays in sync with the new build instead of accumulating stale files from previous runs. Combined withROOTFS_COMPRESSION=nonethis turns deploy-to-NFS-server into a single build step.ROOTFS_ARCHIVE_PATHis exported so downstream hooks (the newnetboot_artifacts_ready) can reference the produced archive.lib/functions/image/partitioning.sh—prepare_partitionsskips the root partition entry fornfs-rootthe same way it does fornfs, and the SD-size sanity check no longer applies to either (there is no image file to size).config/templates/nfs-boot.cmd.template— previously sunxi/arm32-only (zImage+bootz). Made arch-agnostic so the hybridROOTFS_TYPE=nfspath builds for arm64 boards too.extensions/netboot/README.md— long-form guide: variable reference, server setup (tftpd-hpa+nfs-kernel-server), DHCP 66/67 config (OpenWRT + others), per-host / per-MAC deployments, firstrun vs firstlogin, end-to-end helios64 example, troubleshooting.Variables (quick reference)
All optional.
ROOTFS_TYPE=nfs-rootalone gives you a shared-rootfs build with apxelinux.cfg/default.examplefile.NETBOOT_SERVERnfsroot=keeps${serverip}literal for U-Boot to fill from DHCP siaddr.NETBOOT_TFTP_PREFIXarmbian/${LINUXFAMILY}/${BOARD}/${BRANCH}-${RELEASE}NETBOOT_NFS_PATH/srv/netboot/rootfs/shared/...or/srv/netboot/rootfs/hosts/<hostname>nfsroot=.NETBOOT_HOSTNAMEhosts/<hostname>/— each machine owns its own writable copy.NETBOOT_CLIENT_MAC01-<mac>(per-MAC PXELINUX override). Accepts:or-, normalized to lowercase.ROOTFS_COMPRESSIONgzipgzip/zstd/none.ROOTFS_EXPORT_DIRTest plan
Validated end-to-end on helios64 (
rockchip64,edge/resolute,ttyS2 @ 1500000), Armbian 26.05:ROOTFS_TYPE=nfs-root NETBOOT_SERVER=192.168.1.125, shared NFS path, FDTDIR fallback for boards withoutBOOT_FDT_FILE, no hardcodedconsole=). 6:57 min, all artifacts correct.NETBOOT_HOSTNAME=helios64-a NETBOOT_CLIENT_MAC=aa:bb:cc:dd:ee:ff).pxelinux.cfg/01-aa-bb-cc-dd-ee-ffcontainsnfsroot=.../hosts/helios64-a. 7:44 min.ROOTFS_COMPRESSION=zstd— produces.tar.zst,ROOTFS_ARCHIVE_PATHset correctly. 460 MB vs 503 MB gzip.ROOTFS_COMPRESSION=none ROOTFS_EXPORT_DIR=...— single-step tree workflow, 1.5 GB tree in export dir, no archive produced. 5:17 min.ROOTFS_COMPRESSION=nonewithoutROOTFS_EXPORT_DIR— fails fast inextension_prepare_configbefore debootstrap, not hours later.tftpd-hpa+ NFS rootfs via rsync tonfs-kernel-server. DHCP options 66/67 on OpenWRT. Helios64 cold boot → U-Bootbootflow scan -lb→ TFTP →booti→ kernel →ip_auto_config→ NFS mount → systemdgraphical.target→ login prompt onttyS2@1500000. Noarmbian-firstloginwizard, noresize2fserrors,armbian-firstrun.serviceregenerates SSH host keys normally. Verified twice: once with archive workflow + manual unpack, once with tree workflow + rsync deploy.mvebu,armhf, U-Boot v2025.10) — second SoC family. PXE netboot: DHCP → TFTP (zImage + DTB) →bootz→ kernel → NFS root → systemd → login prompt. No initrd needed (see notes below).BOOT_FDT_FILEset (the FDTDIR vs FDT code path is exercised by helios64's missingBOOT_FDT_FILE, but a board with it set should also work).Known issues found during helios4 testing (not blockers for this PR):
ramdisk_addr_r(0x2880000) is only 8 MB abovekernel_addr_r(0x2080000), but zImage 6.12 is 8.1 MB → U-Boot refuses to boot with initrd. Workaround: omitINITRDfrom PXE config — kernel withCONFIG_ROOT_NFS=ymounts NFS root directly without initrd.ip=dhcpwrites DNS to/proc/net/pnpbutsystemd-resolveddoesn't read it → "No DNS servers known". Workaround:resolvectl dns <iface> <gateway>. Needs a networkd.networkfile or oneshot service inpost_customize_image(future improvement).Post-review update: architecture reworked after CodeRabbit #1 — the extension no longer forces
ROOTFS_TYPEfrom insideextension_prepare_config(too late in the config lifecycle); instead a newROOTFS_TYPE=nfs-rootcase branch inmain-config.shenables the extension, symmetric with existing filesystem-support extensions.pipefailon the archive pipeline (#4),rsync --deleteon the export dir (#5), and an explicitINITRDdirective whenuInitrdis present (#2) are all in.Related work
A companion PR to armbian/documentation will add
Developer-Guide_Netboot.mdwith the short overview + variable reference; this extension'sREADME.mdis the long-form guide and is linked from that page.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation