Skip to content

Commit a76e4b2

Browse files
iavclaude
andcommitted
feat(core): introduce ROOTFS_TYPE=nfs-root for full netboot
`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>
1 parent 40fbf6c commit a76e4b2

File tree

5 files changed

+47
-32
lines changed

5 files changed

+47
-32
lines changed

extensions/netboot/README.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,13 @@ setup, network configuration, troubleshooting, end-to-end examples.
2727
## Why this exists
2828

2929
`ROOTFS_TYPE=nfs` alone produces a hybrid image: kernel and DTB still
30-
live on a local boot partition (SD/eMMC), only `/` comes over NFS. This
31-
extension takes it further — kernel, DTB and PXE config are also staged
32-
for TFTP, and the only thing the target needs to boot is a network with
33-
working DHCP+TFTP+NFS.
30+
live on a local boot partition (SD/eMMC), only `/` comes over NFS.
31+
`ROOTFS_TYPE=nfs-root` takes it further — kernel, DTB and PXE config
32+
are also staged for TFTP, and the only thing the target needs to boot
33+
is a network with working DHCP+TFTP+NFS. Selecting `nfs-root` is the
34+
single switch that turns this extension on; it is auto-enabled from
35+
the core `ROOTFS_TYPE` dispatch, no separate `ENABLE_EXTENSIONS` flag
36+
is needed.
3437

3538
Use cases:
3639

@@ -48,7 +51,7 @@ to come over the wire.
4851
## Build-time variables
4952

5053
All variables are optional. The only required step is
51-
`ENABLE_EXTENSIONS=netboot`; defaults give you a single shared rootfs
54+
`ROOTFS_TYPE=nfs-root`; defaults give you a single shared rootfs
5255
per `BOARD × BRANCH × RELEASE` and a `pxelinux.cfg/default.example`
5356
file to copy into place.
5457

@@ -59,7 +62,7 @@ file to copy into place.
5962
| `NETBOOT_NFS_PATH` | see below | Absolute NFS path of the rootfs on the server. The APPEND line uses exactly this string for `nfsroot=...`. |
6063
| `NETBOOT_HOSTNAME` | _(empty)_ | Per-host deployment. When set, the default `NETBOOT_NFS_PATH` becomes `/srv/netboot/rootfs/hosts/<hostname>` — each machine owns its own writable rootfs copy. When empty, the default is `/srv/netboot/rootfs/shared/${LINUXFAMILY}/${BOARD}/${BRANCH}-${RELEASE}` (one image, potentially reused by identical boards). |
6164
| `NETBOOT_CLIENT_MAC` | _(empty)_ | Client MAC (`aa:bb:cc:dd:ee:ff` or `aa-bb-cc-dd-ee-ff`). When set, the PXE config is written as `pxelinux.cfg/01-<mac>` (the PXELINUX per-MAC override) instead of `default.example`; multiple boards then coexist on one TFTP root, each picking its own extlinux. |
62-
| `ROOTFS_COMPRESSION` | `gzip` | Format of the rootfs archive produced by `create_image_from_sdcard_rootfs` when `ROOTFS_TYPE=nfs`. `gzip``.tar.gz`, `zstd``.tar.zst`, `none` → no archive at all. The `none` case requires `ROOTFS_EXPORT_DIR`. |
65+
| `ROOTFS_COMPRESSION` | `gzip` | Format of the rootfs archive produced by `create_image_from_sdcard_rootfs`. `gzip``.tar.gz`, `zstd``.tar.zst`, `none` → no archive at all. The `none` case requires `ROOTFS_EXPORT_DIR`. |
6366
| `ROOTFS_EXPORT_DIR` | _(empty)_ | Absolute path. When set, the rootfs tree is rsync'd directly into this directory in addition to (or instead of) the archive. Primary use: builder host is also the NFS server — single-step `build → boot` loop with no tar/unpack/rsync step. If this path is outside `${SRC}`, the extension bind-mounts it into the build container so the same path works on host and inside Docker. |
6467

6568
### Hook: `netboot_artifacts_ready`
@@ -268,7 +271,7 @@ export directory.
268271
./compile.sh build \
269272
BOARD=helios64 BRANCH=edge RELEASE=resolute \
270273
BUILD_MINIMAL=yes \
271-
ENABLE_EXTENSIONS=netboot \
274+
ROOTFS_TYPE=nfs-root \
272275
NETBOOT_SERVER=192.168.1.125 \
273276
NETBOOT_HOSTNAME=helios64-a \
274277
NETBOOT_CLIENT_MAC=aa:bb:cc:dd:ee:ff \
@@ -435,7 +438,7 @@ ssh root@192.168.1.1 \
435438
./compile.sh build \
436439
BOARD=helios64 BRANCH=edge RELEASE=resolute \
437440
BUILD_MINIMAL=yes \
438-
ENABLE_EXTENSIONS=netboot \
441+
ROOTFS_TYPE=nfs-root \
439442
NETBOOT_SERVER=192.168.1.125 \
440443
ROOTFS_COMPRESSION=none \
441444
ROOTFS_EXPORT_DIR=/srv/netboot/rootfs/shared/rockchip64/helios64/edge-resolute

extensions/netboot/netboot.sh

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,7 @@
3535
# etc. For builder-as-NFS-server workflows prefer
3636
# ROOTFS_EXPORT_DIR to skip the archive step.
3737

38-
function extension_prepare_config__netboot_force_nfs_rootfs() {
39-
if [[ "${ROOTFS_TYPE}" != "nfs" ]]; then
40-
display_alert "${EXTENSION}: forcing ROOTFS_TYPE=nfs" "was: ${ROOTFS_TYPE:-<unset>}" "info"
41-
declare -g ROOTFS_TYPE="nfs"
42-
fi
43-
38+
function extension_prepare_config__netboot_defaults_and_validate() {
4439
declare -g NETBOOT_SERVER="${NETBOOT_SERVER:-}"
4540
declare -g NETBOOT_HOSTNAME="${NETBOOT_HOSTNAME:-}"
4641
declare -g NETBOOT_CLIENT_MAC="${NETBOOT_CLIENT_MAC:-}"
@@ -80,7 +75,7 @@ function custom_kernel_config__netboot_enable_nfs_root() {
8075
# On an NFS-mounted root that's always meaningless (and would error) — strip the
8176
# systemd enablement symlink so the unit never runs.
8277
function post_customize_image__netboot_disable_resize_filesystem() {
83-
[[ "${ROOTFS_TYPE}" == "nfs" ]] || return 0
78+
[[ "${ROOTFS_TYPE}" == "nfs-root" ]] || return 0
8479
display_alert "${EXTENSION}: disabling armbian-resize-filesystem.service" "meaningless on NFS root" "info"
8580
run_host_command_logged find "${SDCARD}/etc/systemd/system/" \
8681
-name "armbian-resize-filesystem.service" -type l -delete
@@ -92,7 +87,7 @@ function post_customize_image__netboot_disable_resize_filesystem() {
9287
# wizard blocks the whole bring-up. Drop the trigger flag; default root/1234 login
9388
# keeps working, and armbian-firstrun.service still regenerates SSH host keys.
9489
function post_customize_image__netboot_skip_firstlogin_wizard() {
95-
[[ "${ROOTFS_TYPE}" == "nfs" ]] || return 0
90+
[[ "${ROOTFS_TYPE}" == "nfs-root" ]] || return 0
9691
[[ -f "${SDCARD}/root/.not_logged_in_yet" ]] || return 0
9792
display_alert "${EXTENSION}: skipping armbian-firstlogin wizard" "no interactive console assumed on netboot" "info"
9893
run_host_command_logged rm -f "${SDCARD}/root/.not_logged_in_yet"
@@ -115,7 +110,7 @@ function host_pre_docker_launch__netboot_mount_export_dir() {
115110
}
116111

117112
function pre_umount_final_image__900_collect_netboot_artifacts() {
118-
[[ "${ROOTFS_TYPE}" == "nfs" ]] || return 0
113+
[[ "${ROOTFS_TYPE}" == "nfs-root" ]] || return 0
119114

120115
# shellcheck disable=SC2154 # ${version} is a readonly global set in create_image_from_sdcard_rootfs
121116
declare tftp_out="${DEST}/images/${version}-netboot-tftp"
@@ -144,8 +139,10 @@ function pre_umount_final_image__900_collect_netboot_artifacts() {
144139
run_host_command_logged cp -a "${MOUNT}/boot/dtb/." "${tftp_prefix_dir}/dtb/"
145140
fi
146141

142+
declare initrd_line=""
147143
if [[ -f "${MOUNT}/boot/uInitrd" ]]; then
148144
run_host_command_logged cp -v "${MOUNT}/boot/uInitrd" "${tftp_prefix_dir}/uInitrd"
145+
initrd_line="INITRD ${NETBOOT_TFTP_PREFIX}/uInitrd"
149146
fi
150147

151148
# When NETBOOT_SERVER is empty, leave ${serverip} literal in nfsroot= so
@@ -184,7 +181,8 @@ function pre_umount_final_image__900_collect_netboot_artifacts() {
184181
LABEL armbian
185182
MENU LABEL Armbian ${BOARD} ${BRANCH} ${RELEASE} (netboot)
186183
KERNEL ${NETBOOT_TFTP_PREFIX}/${kernel_name}
187-
${fdt_line}
184+
${fdt_line}${initrd_line:+
185+
${initrd_line}}
188186
APPEND root=/dev/nfs nfsroot=${nfsroot_server}:${NETBOOT_NFS_PATH},tcp,v3 ip=dhcp rw rootwait earlycon loglevel=7 panic=10
189187
EXTLINUX_CONF
190188

lib/functions/configuration/main-config.sh

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,13 @@ function do_main_configuration() {
138138
nfs)
139139
FIXED_IMAGE_SIZE=256 # small SD card with kernel, boot script and .dtb/.bin files
140140
;;
141+
nfs-root)
142+
# Full netboot: no local storage in the early boot path at all. Kernel,
143+
# DTB and extlinux go to TFTP, rootfs is mounted over NFS. The netboot
144+
# extension owns artifact staging and PXE config generation.
145+
FIXED_IMAGE_SIZE=256
146+
enable_extension "netboot"
147+
;;
141148
f2fs)
142149
enable_extension "fs-f2fs-support"
143150
# Fixed image size is in 1M dd blocks (MiB)
@@ -161,9 +168,9 @@ function do_main_configuration() {
161168
esac
162169

163170
# Check if the filesystem type is supported by the build host.
164-
# Skipped for nfs: ROOTFS_TYPE=nfs produces a rootfs tarball;
165-
# host-side filesystem support is irrelevant for the nfs case.
166-
if [[ $CONFIG_DEFS_ONLY != yes && $ROOTFS_TYPE != nfs ]]; then
171+
# Skipped for nfs/nfs-root: those produce a rootfs tarball and/or export tree;
172+
# host-side filesystem support is irrelevant.
173+
if [[ $CONFIG_DEFS_ONLY != yes && $ROOTFS_TYPE != nfs && $ROOTFS_TYPE != nfs-root ]]; then
167174
check_filesystem_compatibility_on_host
168175
fi
169176

lib/functions/image/partitioning.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function prepare_partitions() {
2020

2121
# possible partition combinations
2222
# /boot: none, ext4, ext2, fat (BOOTFS_TYPE)
23-
# root: ext4, btrfs, f2fs, nilfs2, nfs (ROOTFS_TYPE)
23+
# root: ext4, btrfs, f2fs, nilfs2, nfs, nfs-root (ROOTFS_TYPE)
2424

2525
# declare makes local variables by default if used inside a function
2626
# NOTE: mountopts string should always start with comma if not empty
@@ -121,7 +121,7 @@ function prepare_partitions() {
121121
BOOTSIZE=0
122122
fi
123123
# Check if we need root partition
124-
[[ $ROOTFS_TYPE != nfs ]] &&
124+
[[ $ROOTFS_TYPE != nfs && $ROOTFS_TYPE != nfs-root ]] &&
125125
local rootpart=$((next++))
126126

127127
display_alert "calculated rootpart" "rootpart: ${rootpart}" "debug"
@@ -144,7 +144,7 @@ function prepare_partitions() {
144144
display_alert "Using user-defined image size" "$FIXED_IMAGE_SIZE MiB" "info"
145145
sdsize=$FIXED_IMAGE_SIZE
146146
# basic sanity check
147-
if [[ $ROOTFS_TYPE != nfs && $ROOTFS_TYPE != btrfs && $sdsize -lt $rootfs_size ]]; then
147+
if [[ $ROOTFS_TYPE != nfs && $ROOTFS_TYPE != nfs-root && $ROOTFS_TYPE != btrfs && $sdsize -lt $rootfs_size ]]; then
148148
exit_with_error "User defined image size is too small" "$sdsize <= $rootfs_size"
149149
fi
150150
else

lib/functions/image/rootfs-to-image.sh

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function calculate_image_version() {
1919
calculated_image_version="${vendor_version_prelude}${BOARD^}_${RELEASE}_${BRANCH}_${kernel_version_for_image}${DESKTOP_ENVIRONMENT:+_$DESKTOP_ENVIRONMENT}${EXTRA_IMAGE_SUFFIX}"
2020
[[ $BUILD_DESKTOP == yes ]] && calculated_image_version=${calculated_image_version}_desktop
2121
[[ $BUILD_MINIMAL == yes ]] && calculated_image_version=${calculated_image_version}_minimal
22-
[[ $ROOTFS_TYPE == nfs ]] && calculated_image_version=${calculated_image_version}_nfsboot
22+
[[ $ROOTFS_TYPE == nfs || $ROOTFS_TYPE == nfs-root ]] && calculated_image_version=${calculated_image_version}_nfsboot
2323
display_alert "Calculated image version" "${calculated_image_version}" "debug"
2424
}
2525

@@ -40,7 +40,7 @@ function create_image_from_sdcard_rootfs() {
4040
if [[ ${INCLUDE_HOME_DIR:-no} == yes ]]; then exclude_home=""; fi
4141
# nilfs2 fs does not have extended attributes support, and have to be ignored on copy
4242
if [[ $ROOTFS_TYPE == nilfs2 ]]; then rsync_ea=""; fi
43-
if [[ $ROOTFS_TYPE != nfs ]]; then
43+
if [[ $ROOTFS_TYPE != nfs && $ROOTFS_TYPE != nfs-root ]]; then
4444
display_alert "Copying files via rsync to" "/ (MOUNT root)"
4545
run_host_command_logged rsync -aHWh $rsync_ea \
4646
--exclude="/boot" \
@@ -69,11 +69,16 @@ function create_image_from_sdcard_rootfs() {
6969
if [[ "${rootfs_compression}" != "none" ]]; then
7070
ROOTFS_ARCHIVE_PATH="${DEST}/images/${version}-rootfs.${archive_ext}"
7171
display_alert "Creating rootfs archive" "${version}-rootfs.${archive_ext}" "info"
72-
tar cp --xattrs --directory=$SDCARD/ --exclude='./boot/*' --exclude='./dev/*' --exclude='./proc/*' --exclude='./run/*' --exclude='./tmp/*' \
73-
--exclude='./sys/*' $exclude_home . |
74-
pv -p -b -r -s "$(du -sb "$SDCARD"/ | cut -f1)" \
75-
-N "$(logging_echo_prefix_for_pv "create_rootfs_archive") rootfs.${archive_ext}" |
76-
${archive_filter} > "${ROOTFS_ARCHIVE_PATH}"
72+
# Subshell with pipefail so failures in tar/pv propagate (otherwise the
73+
# exit code of the final compressor stage hides truncation mid-archive).
74+
(
75+
set -o pipefail
76+
tar cp --xattrs --directory=$SDCARD/ --exclude='./boot/*' --exclude='./dev/*' --exclude='./proc/*' --exclude='./run/*' --exclude='./tmp/*' \
77+
--exclude='./sys/*' $exclude_home . |
78+
pv -p -b -r -s "$(du -sb "$SDCARD"/ | cut -f1)" \
79+
-N "$(logging_echo_prefix_for_pv "create_rootfs_archive") rootfs.${archive_ext}" |
80+
${archive_filter} > "${ROOTFS_ARCHIVE_PATH}"
81+
)
7782
fi
7883

7984
# ROOTFS_EXPORT_DIR: when set, also rsync rootfs tree into this directory.
@@ -82,7 +87,9 @@ function create_image_from_sdcard_rootfs() {
8287
if [[ -n "${ROOTFS_EXPORT_DIR}" ]]; then
8388
display_alert "Exporting rootfs tree" "${ROOTFS_EXPORT_DIR}" "info"
8489
run_host_command_logged mkdir -pv "${ROOTFS_EXPORT_DIR}"
85-
run_host_command_logged rsync -aHWh $rsync_ea \
90+
# --delete so files removed from the source rootfs don't survive in a
91+
# reused export tree (otherwise the NFS root silently drifts from the image).
92+
run_host_command_logged rsync -aHWh --delete $rsync_ea \
8693
--exclude="/boot/*" \
8794
--exclude="/dev/*" \
8895
--exclude="/proc/*" \

0 commit comments

Comments
 (0)