From fe7cc6af55d53b66f5a7ab0e4197533b0a71d3f6 Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Tue, 9 Jun 2026 09:40:23 -0600 Subject: [PATCH 01/19] Add BR2_PACKAGE_OPENIPC_NFS_ROOT buildroot package --- Makefile | 8 +++ general/linux/Config.ext.in | 4 ++ general/linux/linux-ext-openipc-nfs-root.mk | 21 ++++++ general/package/Config.in | 1 + general/package/openipc-nfs-root/Config.in | 22 +++++++ .../openipc-nfs-root/files/openipc_ethaddr.c | 45 +++++++++++++ .../openipc-nfs-root/files/rootfs-setup | 15 +++++ .../files/set-hostname-from-cmdline | 21 ++++++ ...pconfig-set-platform-mac-before-open.patch | 46 +++++++++++++ .../openipc-nfs-root/openipc-nfs-root.mk | 19 ++++++ .../overlay/etc/init.d/S40network | 64 +++++++++++++++++++ .../overlay/etc/init.d/S50dropbear | 61 ++++++++++++++++++ .../openipc-nfs-root/overlay/etc/inittab | 37 +++++++++++ general/package/openipc-nfs-root/overlay/init | 36 +++++++++++ .../openipc-nfs-root/post-build-hook.sh | 14 ++++ general/scripts/late-overlays.list | 2 + general/scripts/late-post-build-hooks.list | 2 + general/scripts/rootfs_script.sh | 34 ++++++++++ 18 files changed, 452 insertions(+) create mode 100644 general/linux/Config.ext.in create mode 100644 general/linux/linux-ext-openipc-nfs-root.mk create mode 100644 general/package/openipc-nfs-root/Config.in create mode 100644 general/package/openipc-nfs-root/files/openipc_ethaddr.c create mode 100644 general/package/openipc-nfs-root/files/rootfs-setup create mode 100644 general/package/openipc-nfs-root/files/set-hostname-from-cmdline create mode 100644 general/package/openipc-nfs-root/kernel-patches/0001-ipconfig-set-platform-mac-before-open.patch create mode 100644 general/package/openipc-nfs-root/openipc-nfs-root.mk create mode 100755 general/package/openipc-nfs-root/overlay/etc/init.d/S40network create mode 100755 general/package/openipc-nfs-root/overlay/etc/init.d/S50dropbear create mode 100644 general/package/openipc-nfs-root/overlay/etc/inittab create mode 100755 general/package/openipc-nfs-root/overlay/init create mode 100755 general/package/openipc-nfs-root/post-build-hook.sh create mode 100644 general/scripts/late-overlays.list create mode 100644 general/scripts/late-post-build-hooks.list diff --git a/Makefile b/Makefile index b788951792..67e0639776 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,14 @@ prepare: @if test ! -e $(TARGET)/buildroot-$(BR_VER); then \ wget -c -q $(BR_LINK)/$(BR_VER).tar.gz -O $(BR_FILE); \ mkdir -p $(TARGET); tar -xf $(BR_FILE) -C $(TARGET); fi + @if test -f $(TARGET)/buildroot-$(BR_VER)/linux/Config.in; then \ + sed -i '/source "$$(BR2_EXTERNAL_GENERAL_PATH)\/linux\/Config.ext.in"/d' \ + $(TARGET)/buildroot-$(BR_VER)/linux/Config.in; \ + grep -qF 'source "$$BR2_EXTERNAL_GENERAL_PATH/linux/Config.ext.in"' \ + $(TARGET)/buildroot-$(BR_VER)/linux/Config.in || \ + sed -i '/source "linux\/Config.ext.in"/a source "$$BR2_EXTERNAL_GENERAL_PATH/linux/Config.ext.in"' \ + $(TARGET)/buildroot-$(BR_VER)/linux/Config.in; \ + fi help: @printf "BR-OpenIPC usage:\n \ diff --git a/general/linux/Config.ext.in b/general/linux/Config.ext.in new file mode 100644 index 0000000000..be4a2a5e17 --- /dev/null +++ b/general/linux/Config.ext.in @@ -0,0 +1,4 @@ +config BR2_LINUX_KERNEL_EXT_OPENIPC_NFS_ROOT + bool "openipc-nfs-root kernel extension" + help + Apply OpenIPC NFS-root-specific kernel changes. diff --git a/general/linux/linux-ext-openipc-nfs-root.mk b/general/linux/linux-ext-openipc-nfs-root.mk new file mode 100644 index 0000000000..be39bfdcf7 --- /dev/null +++ b/general/linux/linux-ext-openipc-nfs-root.mk @@ -0,0 +1,21 @@ +################################################################################ +# +# openipc-nfs-root kernel extension +# +################################################################################ + +LINUX_EXTENSIONS += openipc-nfs-root + +define OPENIPC_NFS_ROOT_SYNC_ETHADDR_SOURCE + cp $(BR2_EXTERNAL_GENERAL_PATH)/package/openipc-nfs-root/files/openipc_ethaddr.c \ + $(@D)/drivers/net/ethernet/openipc_ethaddr.c + grep -q '^obj-y += openipc_ethaddr.o$$' $(@D)/drivers/net/ethernet/Makefile || \ + echo 'obj-y += openipc_ethaddr.o' >> $(@D)/drivers/net/ethernet/Makefile +endef + +define OPENIPC_NFS_ROOT_PREPARE_KERNEL + $(APPLY_PATCHES) $(@D) $(BR2_EXTERNAL_GENERAL_PATH)/package/openipc-nfs-root/kernel-patches \*.patch + $(OPENIPC_NFS_ROOT_SYNC_ETHADDR_SOURCE) +endef + +LINUX_PRE_BUILD_HOOKS += OPENIPC_NFS_ROOT_SYNC_ETHADDR_SOURCE diff --git a/general/package/Config.in b/general/package/Config.in index 53eba39b1e..b0babfa342 100644 --- a/general/package/Config.in +++ b/general/package/Config.in @@ -81,6 +81,7 @@ source "$BR2_EXTERNAL_GENERAL_PATH/package/novatek-osdrv-nt9856x/Config.in" source "$BR2_EXTERNAL_GENERAL_PATH/package/ntfy/Config.in" source "$BR2_EXTERNAL_GENERAL_PATH/package/onvif-simple-server/Config.in" source "$BR2_EXTERNAL_GENERAL_PATH/package/openipc-precision-time/Config.in" +source "$BR2_EXTERNAL_GENERAL_PATH/package/openipc-nfs-root/Config.in" source "$BR2_EXTERNAL_GENERAL_PATH/package/opus-openipc/Config.in" source "$BR2_EXTERNAL_GENERAL_PATH/package/osd-openipc/Config.in" source "$BR2_EXTERNAL_GENERAL_PATH/package/quirc-openipc/Config.in" diff --git a/general/package/openipc-nfs-root/Config.in b/general/package/openipc-nfs-root/Config.in new file mode 100644 index 0000000000..67cadbf9be --- /dev/null +++ b/general/package/openipc-nfs-root/Config.in @@ -0,0 +1,22 @@ +config BR2_PACKAGE_OPENIPC_NFS_ROOT + bool "OpenIPC NFS root" + select BR2_LINUX_KERNEL_EXT_OPENIPC_NFS_ROOT + help + NFS-root-specific files and scripts for OpenIPC builds. + + When enabled, the post-build flow replaces /init with an NFS + aware init script that mounts a writable tmpfs overlay on top + of a readonly NFS root. + + Add any NFS-only init scripts, helper files, or deployment + hooks here as the workflow is fleshed out. + +if BR2_PACKAGE_OPENIPC_NFS_ROOT + +config BR2_PACKAGE_OPENIPC_NFS_ROOT_TMPFS_SIZE + string "tmpfs size" + default "16M" + help + Size passed to the tmpfs mount used for the writable overlay. + +endif diff --git a/general/package/openipc-nfs-root/files/openipc_ethaddr.c b/general/package/openipc-nfs-root/files/openipc_ethaddr.c new file mode 100644 index 0000000000..0b3927bfdd --- /dev/null +++ b/general/package/openipc-nfs-root/files/openipc_ethaddr.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include + +// mac_pton moved in v6.4 +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,4,0) +#include +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,8) +#include +#else +#error "openipc_ethaddr(): mac_pton not supported in kernel" +#endif + +// Default u-boot env ethaddr on openipc platforms +static const u8 default_mac[ETH_ALEN] = {0x00,0x00,0x23,0x34,0x45,0x66}; +static u8 openipc_ethaddr[ETH_ALEN] __aligned(2); + +static int __init openipc_early_ethaddr(char *str) +{ + u8 mac[ETH_ALEN] __aligned(2); + + if (!str) + return 0; + + if (!mac_pton(str, mac)) { + pr_warn("openipc_ethaddr: invalid ethaddr bootarg '%s'\n", str); + return 0; + } + + if (ether_addr_equal(mac, default_mac)) + pr_warn("openipc_ethaddr: ignoring default ethaddr '%s'\n", + default_mac); + else + ether_addr_copy(openipc_ethaddr, mac); + + return 0; +} +early_param("ethaddr", openipc_early_ethaddr); + +unsigned char *arch_get_platform_mac_address(void) +{ + return is_valid_ether_addr(openipc_ethaddr) ? openipc_ethaddr : NULL; +} diff --git a/general/package/openipc-nfs-root/files/rootfs-setup b/general/package/openipc-nfs-root/files/rootfs-setup new file mode 100644 index 0000000000..5546fb36cf --- /dev/null +++ b/general/package/openipc-nfs-root/files/rootfs-setup @@ -0,0 +1,15 @@ +#!/bin/sh + +fstype=$(awk '$2=="/"{print $3}' /proc/mounts) +opts=$(awk '$2=="/"{print $4}' /proc/mounts) + +case "$fstype" in + nfs|nfs4|overlay) + echo "Skipping root remount for $fstype root ($opts)" + ;; + *) + mount -o remount,rw / + ;; +esac + +exit 0 diff --git a/general/package/openipc-nfs-root/files/set-hostname-from-cmdline b/general/package/openipc-nfs-root/files/set-hostname-from-cmdline new file mode 100644 index 0000000000..8ebe8698e9 --- /dev/null +++ b/general/package/openipc-nfs-root/files/set-hostname-from-cmdline @@ -0,0 +1,21 @@ +#!/bin/sh +# +# Use hostname from command line + +cmdline=$(cat /proc/cmdline) +hostname_arg=$(printf ' %s\n' "$cmdline" | sed -n 's/.*[[:space:]]hostname=\([^[:space:]]*\).*/\1/p') + +if [ -n "$hostname_arg" ]; then + hostname "$hostname_arg" + echo "$hostname_arg" > /etc/hostname + + if [ -w /etc/hosts ]; then + { + echo "127.0.0.1 localhost" + echo "127.0.1.1 $hostname_arg" + } > /etc/hosts + fi +fi + + +exit 0 diff --git a/general/package/openipc-nfs-root/kernel-patches/0001-ipconfig-set-platform-mac-before-open.patch b/general/package/openipc-nfs-root/kernel-patches/0001-ipconfig-set-platform-mac-before-open.patch new file mode 100644 index 0000000000..859f0b274a --- /dev/null +++ b/general/package/openipc-nfs-root/kernel-patches/0001-ipconfig-set-platform-mac-before-open.patch @@ -0,0 +1,46 @@ +--- a/net/ipv4/ipconfig.c ++++ b/net/ipv4/ipconfig.c +@@ -41,6 +41,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -65,6 +66,7 @@ + #include + #include + ++ + #if defined(CONFIG_IP_PNP_DHCP) + #define IPCONFIG_DHCP + #endif +@@ -205,6 +207,8 @@ + struct net_device *dev; + unsigned short oflags; + unsigned long start, next_msg; ++ bool first_mac_done = false; ++ u8 mac[ETH_ALEN]; + + last = &ic_first_dev; + rtnl_lock(); +@@ -230,6 +234,18 @@ + able &= ic_proto_enabled; + if (ic_proto_enabled && !able) + continue; ++ ++ /* Set MAC address from device tree or bootargs */ ++ if (!first_mac_done && ++ (able & IC_BOOTP) && ++ (dev->type == ARPHRD_ETHER) && ++ dev->dev.parent && ++ !eth_platform_get_mac_address(dev->dev.parent, mac)) { ++ ether_addr_copy(dev->dev_addr, mac); ++ first_mac_done = true; ++ pr_info("openipc_ethaddr: Override %s MAC to %pM for NFS root\n", ++ dev->name, dev->dev_addr); ++ } + oflags = dev->flags; + if (dev_change_flags(dev, oflags | IFF_UP) < 0) { + pr_err("IP-Config: Failed to open %s\n", diff --git a/general/package/openipc-nfs-root/openipc-nfs-root.mk b/general/package/openipc-nfs-root/openipc-nfs-root.mk new file mode 100644 index 0000000000..4b21d0fb30 --- /dev/null +++ b/general/package/openipc-nfs-root/openipc-nfs-root.mk @@ -0,0 +1,19 @@ +################################################################################ +# +# openipc-nfs-root +# +# NFS-root-specific files and scripts for OpenIPC builds. +# +################################################################################ + +OPENIPC_NFS_ROOT_VERSION = 1.0 +OPENIPC_NFS_ROOT_SITE_METHOD = local +OPENIPC_NFS_ROOT_SITE = $(BR2_EXTERNAL_GENERAL_PATH)/package/openipc-nfs-root +OPENIPC_NFS_ROOT_LICENSE = MIT + +define OPENIPC_NFS_ROOT_INSTALL_TARGET_CMDS + $(INSTALL) -D -m 0755 $(@D)/files/rootfs-setup $(TARGET_DIR)/usr/sbin/rootfs-setup + $(INSTALL) -D -m 0755 $(@D)/files/set-hostname-from-cmdline $(TARGET_DIR)/usr/sbin/set-hostname-from-cmdline +endef + +$(eval $(generic-package)) diff --git a/general/package/openipc-nfs-root/overlay/etc/init.d/S40network b/general/package/openipc-nfs-root/overlay/etc/init.d/S40network new file mode 100755 index 0000000000..bb441c6def --- /dev/null +++ b/general/package/openipc-nfs-root/overlay/etc/init.d/S40network @@ -0,0 +1,64 @@ +#!/bin/sh +dev=$(fw_printenv -n wlandev) +mac=$(fw_printenv -n wlanmac) +net=$(fw_printenv -n netaddr_fallback) +eth=$(fw_printenv -n ethaddr) + +set_wireless() { + path=/etc/wireless + if $path/usb "$dev" || $path/sdio "$dev"; then + [ -n "$mac" ] && ip link set dev wlan0 address "$mac" + ifup wlan0 + elif $path/modem "$dev"; then + ifup usb0 + ifup eth1 + fi + [ -e /sys/class/net/eth0 ] && ifconfig eth0 "${net:-192.168.2.10}" hw ether "${eth:-00:00:23:34:45:66}" up +} + +start() { + echo "Starting network..." + if [ -n "$dev" ]; then + set_wireless + else + if grep -qw 'root=/dev/nfs' /proc/cmdline; then + echo "<6>nfs-root: NFS root detected, leaving eth0/lo as configured by kernel" > /dev/kmsg + IP=$(ip route | sed -n 's/^default via \([0-9.]*\) .*/\1/p') + echo "<6>nfs-root: Using default gateway (${IP?}) for NTP and DNS..." > /dev/kmsg + echo "nameserver ${IP}" > /etc/resolv.conf + echo "server ${IP} iburst" > /etc/ntp.conf + else + ip addr show dev lo | grep -q 'inet 127.0.0.1/' || ifup lo + ifup eth0 + fi + fi +} + +stop() { + echo "Stopping network..." + ifdown -f lo + ifdown -f wlan0 + ifdown -f usb0 + ifdown -f eth1 + if grep -qw 'root=/dev/nfs' /proc/cmdline; then + echo "<6>NFS root detected, leaving eth0 up during shutdown" > /dev/kmsg + else + ifdown -f eth0 + fi +} + +case "$1" in + start|stop) + $1 + ;; + + restart|reload) + stop + start + ;; + + *) + echo "Usage: $0 {start|stop|restart|reload}" + exit 1 + ;; +esac diff --git a/general/package/openipc-nfs-root/overlay/etc/init.d/S50dropbear b/general/package/openipc-nfs-root/overlay/etc/init.d/S50dropbear new file mode 100755 index 0000000000..391b0c1b71 --- /dev/null +++ b/general/package/openipc-nfs-root/overlay/etc/init.d/S50dropbear @@ -0,0 +1,61 @@ +#!/bin/sh + +DAEMON="dropbear" +PIDFILE="/var/run/$DAEMON.pid" +DAEMON_ARGS="-B -k -p 22 -K 300" + +start() { + if fw_printenv >/dev/null 2>&1; then + if ! fw_printenv ssh_key; then + echo "<6>nfs-root: Generating SSH key and saving to uboot env" > /dev/kmsg + tmp="$(mktemp -u /tmp/dropbearkey.XXXXXX)" || exit 1 + trap 'rm -f "$tmp"' EXIT + + dropbearkey -t ed25519 -f "$tmp" >/dev/null 2>&1 || exit 1 + SSH_KEY=$(cat "$tmp" | base64 -w 0) + fw_setenv ssh_key $SSH_KEY + fi + echo "<6>nfs-root: Loading SSH key from uboot env" > /dev/kmsg + fw_printenv -n ssh_key 2>/dev/null | base64 -d > /etc/dropbear/dropbear_ed25519_host_key + else + echo "<6>nfs-root: fw_setenv inaccessible, SSH key will not be saved" + DAEMON_ARGS="${DAEMON_ARGS} -R" + fi + + echo -n "Starting $DAEMON: " + umask 077 + start-stop-daemon -S -q -p "$PIDFILE" -x "$DAEMON" -- $DAEMON_ARGS + if [ $? -eq 0 ]; then + echo "OK" + else + echo "FAIL" + fi +} + +stop() { + echo -n "Stopping $DAEMON: " + start-stop-daemon -K -q -p "$PIDFILE" + if [ $? -eq 0 ]; then + rm -f "$PIDFILE" + echo "OK" + else + echo "FAIL" + fi +} + +case "$1" in + start|stop) + $1 + ;; + + restart|reload) + stop + sleep 1 + start + ;; + + *) + echo "Usage: $0 {start|stop|restart|reload}" + exit 1 + ;; +esac diff --git a/general/package/openipc-nfs-root/overlay/etc/inittab b/general/package/openipc-nfs-root/overlay/etc/inittab new file mode 100644 index 0000000000..a6401475e6 --- /dev/null +++ b/general/package/openipc-nfs-root/overlay/etc/inittab @@ -0,0 +1,37 @@ +# /etc/inittab +# +# Copyright (C) 2001 Erik Andersen +# +# Note: BusyBox init doesn't support runlevels. The runlevels field is +# completely ignored by BusyBox init. If you want runlevels, use +# sysvinit. +# +# Format for each entry: ::: +# +# id == tty to run on, or empty for /dev/console +# runlevels == ignored +# action == one of sysinit, respawn, askfirst, wait, and once +# process == program to run + +# Startup the system +::sysinit:/bin/mount -t proc proc /proc +::sysinit:/usr/sbin/rootfs-setup +::sysinit:/bin/mkdir -p /dev/pts /dev/shm +::sysinit:/bin/mount -a +::sysinit:/bin/mkdir -p /run/lock/subsys +null::sysinit:/bin/ln -sf /proc/self/fd /dev/fd +null::sysinit:/bin/ln -sf /proc/self/fd/0 /dev/stdin +null::sysinit:/bin/ln -sf /proc/self/fd/1 /dev/stdout +null::sysinit:/bin/ln -sf /proc/self/fd/2 /dev/stderr +::sysinit:/usr/sbin/set-hostname-from-cmdline +::sysinit:/bin/hostname -F /etc/hostname + +# Run rc scripts +::sysinit:/etc/init.d/rcS + +# Put a getty on the serial port +console::respawn:/sbin/getty -L console 0 vt100 + +# Stuff to do before rebooting +::shutdown:/etc/init.d/rcK +::shutdown:/bin/umount -a -f diff --git a/general/package/openipc-nfs-root/overlay/init b/general/package/openipc-nfs-root/overlay/init new file mode 100755 index 0000000000..1964abbeca --- /dev/null +++ b/general/package/openipc-nfs-root/overlay/init @@ -0,0 +1,36 @@ +#!/bin/sh + +on_exit() { + mountpoint -q /proc && umount /proc + exec /sbin/init "$*" +} + +trap on_exit EXIT + +mount -t proc proc /proc || exit 1 +grep -q overlay /proc/filesystems || exit 1 + +if ! mount -t tmpfs -o mode=0755,size=@TMPFS_SIZE@ tmpfs /overlay; then + echo "Cannot mount tmpfs overlay." + exit 1 +fi + +if grep -q overlayfs /proc/filesystems; then + if ! mount -t overlayfs overlayfs -o lowerdir=/,upperdir=/overlay,ro /mnt; then + umount /overlay + exit 1 + fi +else + overlay_rootdir=/overlay/root + overlay_workdir=/overlay/work + mkdir -p $overlay_rootdir $overlay_workdir + if ! mount -t overlay overlay -o lowerdir=/,upperdir=$overlay_rootdir,workdir=$overlay_workdir /mnt; then + umount /overlay + exit 1 + fi +fi + +pivot_root /mnt /mnt/rom +mount -o noatime,move /rom/proc /proc +mount -o noatime,move /rom/dev /dev +mount -o noatime,move /rom/overlay /overlay diff --git a/general/package/openipc-nfs-root/post-build-hook.sh b/general/package/openipc-nfs-root/post-build-hook.sh new file mode 100755 index 0000000000..d451315be3 --- /dev/null +++ b/general/package/openipc-nfs-root/post-build-hook.sh @@ -0,0 +1,14 @@ +#!/bin/sh +set -eu + +TARGET_DIR="${1:?target dir required}" + +OPENIPC_NFS_ROOT_TMPFS_SIZE=$( + awk -F= '/^BR2_PACKAGE_OPENIPC_NFS_ROOT_TMPFS_SIZE=/{gsub(/"/, "", $2); print $2}' \ + "${BR2_CONFIG}" | tail -n1 +) +[ -n "${OPENIPC_NFS_ROOT_TMPFS_SIZE}" ] || OPENIPC_NFS_ROOT_TMPFS_SIZE=16M + +if [ -f "${TARGET_DIR}/init" ]; then + sed -i "s/@TMPFS_SIZE@/${OPENIPC_NFS_ROOT_TMPFS_SIZE}/" "${TARGET_DIR}/init" +fi diff --git a/general/scripts/late-overlays.list b/general/scripts/late-overlays.list new file mode 100644 index 0000000000..69c559e7b2 --- /dev/null +++ b/general/scripts/late-overlays.list @@ -0,0 +1,2 @@ +# config-symbol:overlay-relative-path +BR2_PACKAGE_OPENIPC_NFS_ROOT:package/openipc-nfs-root/overlay diff --git a/general/scripts/late-post-build-hooks.list b/general/scripts/late-post-build-hooks.list new file mode 100644 index 0000000000..91bcbf9e98 --- /dev/null +++ b/general/scripts/late-post-build-hooks.list @@ -0,0 +1,2 @@ +# config-symbol:hook-script-relative-path +BR2_PACKAGE_OPENIPC_NFS_ROOT:package/openipc-nfs-root/post-build-hook.sh diff --git a/general/scripts/rootfs_script.sh b/general/scripts/rootfs_script.sh index c4189d9980..b44cd73e0a 100755 --- a/general/scripts/rootfs_script.sh +++ b/general/scripts/rootfs_script.sh @@ -1,6 +1,8 @@ #!/bin/bash DATE=$(date +%y.%m.%d) FILE=${TARGET_DIR}/usr/lib/os-release +LATE_OVERLAY_LIST="${BR2_EXTERNAL_GENERAL_PATH}/scripts/late-overlays.list" +LATE_POST_BUILD_HOOKS="${BR2_EXTERNAL_GENERAL_PATH}/scripts/late-post-build-hooks.list" echo OPENIPC_VERSION=${DATE:0:1}.${DATE:1} >> ${FILE} date +GITHUB_VERSION="\"${GIT_BRANCH-local}+${GIT_HASH-build}, %Y-%m-%d"\" >> ${FILE} @@ -24,3 +26,35 @@ LIST="${BR2_EXTERNAL_GENERAL_PATH}/scripts/excludes/${OPENIPC_SOC_MODEL}_${OPENI if [ -f ${LIST} ]; then xargs -a ${LIST} -I % rm -f ${TARGET_DIR}% fi + +if [ -f "${LATE_OVERLAY_LIST}" ]; then + while IFS=: read -r symbol overlay_relpath; do + [ -n "${symbol}" ] || continue + case "${symbol}" in + \#*) continue ;; + esac + + if grep -q "^${symbol}=y" "${BR2_CONFIG}"; then + overlay_dir="${BR2_EXTERNAL_GENERAL_PATH}/${overlay_relpath}" + if [ -d "${overlay_dir}" ]; then + rsync -a "${overlay_dir}/" "${TARGET_DIR}/" + fi + fi + done < "${LATE_OVERLAY_LIST}" +fi + +if [ -f "${LATE_POST_BUILD_HOOKS}" ]; then + while IFS=: read -r symbol hook_relpath; do + [ -n "${symbol}" ] || continue + case "${symbol}" in + \#*) continue ;; + esac + + if grep -q "^${symbol}=y" "${BR2_CONFIG}"; then + hook_script="${BR2_EXTERNAL_GENERAL_PATH}/${hook_relpath}" + if [ -x "${hook_script}" ]; then + "${hook_script}" "${TARGET_DIR}" + fi + fi + done < "${LATE_POST_BUILD_HOOKS}" +fi From c49db59484e001131262c4a5edd3ea16effc562e Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Tue, 9 Jun 2026 10:32:21 -0600 Subject: [PATCH 02/19] Fix invalid str reference in openipc_ethaddr --- general/package/openipc-nfs-root/files/openipc_ethaddr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/general/package/openipc-nfs-root/files/openipc_ethaddr.c b/general/package/openipc-nfs-root/files/openipc_ethaddr.c index 0b3927bfdd..b50ac0c205 100644 --- a/general/package/openipc-nfs-root/files/openipc_ethaddr.c +++ b/general/package/openipc-nfs-root/files/openipc_ethaddr.c @@ -31,7 +31,7 @@ static int __init openipc_early_ethaddr(char *str) if (ether_addr_equal(mac, default_mac)) pr_warn("openipc_ethaddr: ignoring default ethaddr '%s'\n", - default_mac); + str); else ether_addr_copy(openipc_ethaddr, mac); From f0db9367d0dccbc571c411f8e1f828398df49543 Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Wed, 10 Jun 2026 15:52:09 -0600 Subject: [PATCH 03/19] Update userspace boot scripts, add password and timezone config --- general/package/openipc-nfs-root/Config.in | 19 +++- .../openipc-nfs-root/files/S45setupenv | 62 +++++++++++ .../openipc-nfs-root/openipc-nfs-root.mk | 1 + .../overlay/etc/init.d/S40network | 21 ++-- .../overlay/etc/init.d/S50dropbear | 61 ----------- .../openipc-nfs-root/post-build-hook.sh | 103 +++++++++++++++++- 6 files changed, 191 insertions(+), 76 deletions(-) create mode 100644 general/package/openipc-nfs-root/files/S45setupenv delete mode 100755 general/package/openipc-nfs-root/overlay/etc/init.d/S50dropbear diff --git a/general/package/openipc-nfs-root/Config.in b/general/package/openipc-nfs-root/Config.in index 67cadbf9be..e345f078f2 100644 --- a/general/package/openipc-nfs-root/Config.in +++ b/general/package/openipc-nfs-root/Config.in @@ -17,6 +17,23 @@ config BR2_PACKAGE_OPENIPC_NFS_ROOT_TMPFS_SIZE string "tmpfs size" default "16M" help - Size passed to the tmpfs mount used for the writable overlay. + Size passed to the tmpfs mount used for the writable overlay + when NFS root is mounted in readonly mode. + +config BR2_PACKAGE_OPENIPC_NFS_ROOT_PASSWD + string "root password" + default "12345" + help + Root password to be set during boot. Useful for immutable rootfs. + +config BR2_PACKAGE_OPENIPC_NFS_ROOT_TIMEZONE + string "IANA timezone" + default "" + help + IANA timezone name used to derive a POSIX TZ rule at build time, + for example: America/Denver or Europe/Berlin. + + When left empty, the build attempts to detect the timezone from the + build host and derives the POSIX TZ rule from that zone. endif diff --git a/general/package/openipc-nfs-root/files/S45setupenv b/general/package/openipc-nfs-root/files/S45setupenv new file mode 100644 index 0000000000..466f69ad82 --- /dev/null +++ b/general/package/openipc-nfs-root/files/S45setupenv @@ -0,0 +1,62 @@ +#!/bin/sh +# +# Customize environment for NFS boot +# + +#!/bin/sh + + +# Setup NFS password +setup_root_passwd() { + shadow_hash="$(awk -F: '$1=="root"{print $2}' /etc/shadow)" + salt="$(printf '%s\n' "$shadow_hash" | cut -d'$' -f3)" + test_hash="$(mkpasswd -m md5 -S "$salt" "@ROOTFS_PASSWD@")" + if [ ! "$test_hash" = "$shadow_hash" ]; then + echo "root:@ROOTFS_PASSWD@" | chpasswd 2>/dev/null + echo "nfs-root: Updated root password." + fi +} + +setup_ssh_key() { + if fw_printenv >/dev/null 2>&1; then + if ! fw_printenv ssh_key >/dev/null 2>&1; then + echo "nfs-root: Generating SSH key and saving to uboot env." + tmp="$(mktemp -u /tmp/dropbearkey.XXXXXX)" || exit 1 + trap 'rm -f "$tmp"' EXIT + + dropbearkey -t ed25519 -f "$tmp" >/dev/null 2>&1 || exit 1 + SSH_KEY=$(cat "$tmp" | base64 -w 0) + fw_setenv ssh_key $SSH_KEY || exit 1 + fi + echo "nfs-root: Loading SSH key from uboot env." + fw_printenv -n ssh_key 2>/dev/null | base64 -d > /etc/dropbear/dropbear_ed25519_host_key || exit 1 + else + echo "nfs-root: fw_setenv inaccessible, SSH key will not be saved." + fi +} + +setup_dns_ntp() { + IP=$(ip route | sed -n 's/^default via \([0-9.]*\) .*/\1/p') + echo "nfs-root: Using default gateway (${IP?}) for NTP and DNS." + echo "nameserver ${IP}" > /etc/resolv.conf + echo "server ${IP} iburst" > /etc/ntp.conf +} + +case "$1" in + start) + # Only run if booting from nfs root + if grep -qw 'root=/dev/nfs' /proc/cmdline; then + setup_root_passwd + setup_dns_ntp + setup_ssh_key + fi + ;; + + stop) + ;; + + *) + echo "Usage: $0 {start}" + exit 1 + ;; +esac diff --git a/general/package/openipc-nfs-root/openipc-nfs-root.mk b/general/package/openipc-nfs-root/openipc-nfs-root.mk index 4b21d0fb30..948ae021bb 100644 --- a/general/package/openipc-nfs-root/openipc-nfs-root.mk +++ b/general/package/openipc-nfs-root/openipc-nfs-root.mk @@ -14,6 +14,7 @@ OPENIPC_NFS_ROOT_LICENSE = MIT define OPENIPC_NFS_ROOT_INSTALL_TARGET_CMDS $(INSTALL) -D -m 0755 $(@D)/files/rootfs-setup $(TARGET_DIR)/usr/sbin/rootfs-setup $(INSTALL) -D -m 0755 $(@D)/files/set-hostname-from-cmdline $(TARGET_DIR)/usr/sbin/set-hostname-from-cmdline + $(INSTALL) -D -m 0755 $(@D)/files/S45setupenv $(TARGET_DIR)/etc/init.d/S45setupenv endef $(eval $(generic-package)) diff --git a/general/package/openipc-nfs-root/overlay/etc/init.d/S40network b/general/package/openipc-nfs-root/overlay/etc/init.d/S40network index bb441c6def..51f14f75e5 100755 --- a/general/package/openipc-nfs-root/overlay/etc/init.d/S40network +++ b/general/package/openipc-nfs-root/overlay/etc/init.d/S40network @@ -16,19 +16,20 @@ set_wireless() { [ -e /sys/class/net/eth0 ] && ifconfig eth0 "${net:-192.168.2.10}" hw ether "${eth:-00:00:23:34:45:66}" up } +is_nfs_root() { + grep -qw 'root=/dev/nfs' /proc/cmdline +} + start() { echo "Starting network..." if [ -n "$dev" ]; then + ifup lo set_wireless else - if grep -qw 'root=/dev/nfs' /proc/cmdline; then - echo "<6>nfs-root: NFS root detected, leaving eth0/lo as configured by kernel" > /dev/kmsg - IP=$(ip route | sed -n 's/^default via \([0-9.]*\) .*/\1/p') - echo "<6>nfs-root: Using default gateway (${IP?}) for NTP and DNS..." > /dev/kmsg - echo "nameserver ${IP}" > /etc/resolv.conf - echo "server ${IP} iburst" > /etc/ntp.conf + if is_nfs_root; then + echo "nfs-root: NFS root detected, leaving eth0/lo as configured by kernel" else - ip addr show dev lo | grep -q 'inet 127.0.0.1/' || ifup lo + ifup lo ifup eth0 fi fi @@ -36,12 +37,12 @@ start() { stop() { echo "Stopping network..." - ifdown -f lo + ifdown lo ifdown -f wlan0 ifdown -f usb0 ifdown -f eth1 - if grep -qw 'root=/dev/nfs' /proc/cmdline; then - echo "<6>NFS root detected, leaving eth0 up during shutdown" > /dev/kmsg + if is_nfs_root; then + echo "NFS root detected, leaving eth0 up during shutdown" else ifdown -f eth0 fi diff --git a/general/package/openipc-nfs-root/overlay/etc/init.d/S50dropbear b/general/package/openipc-nfs-root/overlay/etc/init.d/S50dropbear deleted file mode 100755 index 391b0c1b71..0000000000 --- a/general/package/openipc-nfs-root/overlay/etc/init.d/S50dropbear +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/sh - -DAEMON="dropbear" -PIDFILE="/var/run/$DAEMON.pid" -DAEMON_ARGS="-B -k -p 22 -K 300" - -start() { - if fw_printenv >/dev/null 2>&1; then - if ! fw_printenv ssh_key; then - echo "<6>nfs-root: Generating SSH key and saving to uboot env" > /dev/kmsg - tmp="$(mktemp -u /tmp/dropbearkey.XXXXXX)" || exit 1 - trap 'rm -f "$tmp"' EXIT - - dropbearkey -t ed25519 -f "$tmp" >/dev/null 2>&1 || exit 1 - SSH_KEY=$(cat "$tmp" | base64 -w 0) - fw_setenv ssh_key $SSH_KEY - fi - echo "<6>nfs-root: Loading SSH key from uboot env" > /dev/kmsg - fw_printenv -n ssh_key 2>/dev/null | base64 -d > /etc/dropbear/dropbear_ed25519_host_key - else - echo "<6>nfs-root: fw_setenv inaccessible, SSH key will not be saved" - DAEMON_ARGS="${DAEMON_ARGS} -R" - fi - - echo -n "Starting $DAEMON: " - umask 077 - start-stop-daemon -S -q -p "$PIDFILE" -x "$DAEMON" -- $DAEMON_ARGS - if [ $? -eq 0 ]; then - echo "OK" - else - echo "FAIL" - fi -} - -stop() { - echo -n "Stopping $DAEMON: " - start-stop-daemon -K -q -p "$PIDFILE" - if [ $? -eq 0 ]; then - rm -f "$PIDFILE" - echo "OK" - else - echo "FAIL" - fi -} - -case "$1" in - start|stop) - $1 - ;; - - restart|reload) - stop - sleep 1 - start - ;; - - *) - echo "Usage: $0 {start|stop|restart|reload}" - exit 1 - ;; -esac diff --git a/general/package/openipc-nfs-root/post-build-hook.sh b/general/package/openipc-nfs-root/post-build-hook.sh index d451315be3..36d4057515 100755 --- a/general/package/openipc-nfs-root/post-build-hook.sh +++ b/general/package/openipc-nfs-root/post-build-hook.sh @@ -2,13 +2,108 @@ set -eu TARGET_DIR="${1:?target dir required}" +HOST_DIR="${HOST_DIR:-$(dirname "${TARGET_DIR}")/host}" -OPENIPC_NFS_ROOT_TMPFS_SIZE=$( - awk -F= '/^BR2_PACKAGE_OPENIPC_NFS_ROOT_TMPFS_SIZE=/{gsub(/"/, "", $2); print $2}' \ - "${BR2_CONFIG}" | tail -n1 -) +read_kconfig_string() { + awk -F= -v key="$1" ' + $1 == key { + gsub(/"/, "", $2) + value = $2 + } + END { + if (value != "") { + print value + } + } + ' "${BR2_CONFIG}" | tail -n1 +} + +detect_host_timezone() { + local tz + + if [ -f /etc/timezone ]; then + tz="$(sed -n '1p' /etc/timezone)" + [ -n "${tz}" ] && { + printf '%s\n' "${tz}" + return 0 + } + fi + + tz="$(readlink /etc/localtime 2>/dev/null || true)" + case "${tz}" in + */zoneinfo/*) + printf '%s\n' "${tz#*/zoneinfo/}" + return 0 + ;; + esac + + return 1 +} + +extract_posix_tz() { + local zone="$1" + local zonefile= + local tz + + for candidate in \ + "${HOST_DIR}/share/zoneinfo/posix/${zone}" \ + "/usr/share/zoneinfo/posix/${zone}" \ + "/usr/share/zoneinfo/${zone}" + do + if [ -f "${candidate}" ]; then + zonefile="${candidate}" + break + fi + done + + [ -n "${zonefile}" ] || { + echo "openipc-nfs-root: missing zoneinfo file for '${zone}'" >&2 + return 1 + } + + tz="$(strings "${zonefile}" | tail -n1)" + [ -n "${tz}" ] || { + echo "openipc-nfs-root: failed to extract POSIX TZ from '${zonefile}'" >&2 + return 1 + } + + printf '%s\n' "${tz}" +} + +write_target_timezone() { + local zone="$1" + local tz="$2" + + printf '%s\n' "${tz}" > "${TARGET_DIR}/etc/TZ" + printf '%s\n' "${zone}" > "${TARGET_DIR}/etc/timezone" +} + +OPENIPC_NFS_ROOT_TMPFS_SIZE="$(read_kconfig_string BR2_PACKAGE_OPENIPC_NFS_ROOT_TMPFS_SIZE)" [ -n "${OPENIPC_NFS_ROOT_TMPFS_SIZE}" ] || OPENIPC_NFS_ROOT_TMPFS_SIZE=16M +OPENIPC_NFS_ROOT_PASSWD="$(read_kconfig_string BR2_PACKAGE_OPENIPC_NFS_ROOT_PASSWD)" +[ -n "${OPENIPC_NFS_ROOT_PASSWD}" ] || OPENIPC_NFS_ROOT_PASSWD="12345" + +OPENIPC_NFS_ROOT_TIMEZONE="$(read_kconfig_string BR2_PACKAGE_OPENIPC_NFS_ROOT_TIMEZONE)" +if [ -z "${OPENIPC_NFS_ROOT_TIMEZONE}" ]; then + OPENIPC_NFS_ROOT_TIMEZONE="$(detect_host_timezone)" +fi + +[ -n "${OPENIPC_NFS_ROOT_TIMEZONE}" ] || { + echo "openipc-nfs-root: unable to determine timezone; set BR2_PACKAGE_OPENIPC_NFS_ROOT_TIMEZONE" >&2 + exit 1 +} + +OPENIPC_NFS_ROOT_TZ_POSIX="$(extract_posix_tz "${OPENIPC_NFS_ROOT_TIMEZONE}")" + if [ -f "${TARGET_DIR}/init" ]; then sed -i "s/@TMPFS_SIZE@/${OPENIPC_NFS_ROOT_TMPFS_SIZE}/" "${TARGET_DIR}/init" fi + +if [ -f "${TARGET_DIR}/etc/init.d/S45setupenv" ]; then + sed -i "s/@ROOTFS_PASSWD@/${OPENIPC_NFS_ROOT_PASSWD}/" "${TARGET_DIR}/etc/init.d/S45setupenv" +fi + +if [ -d "${TARGET_DIR}/etc" ]; then + write_target_timezone "${OPENIPC_NFS_ROOT_TIMEZONE}" "${OPENIPC_NFS_ROOT_TZ_POSIX}" +fi From e9bb41acc23da591dd2d2a62a79843d5c59cec2f Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Thu, 11 Jun 2026 10:53:18 -0600 Subject: [PATCH 04/19] Add config for NTP and DNS. make_nfsroot helper script --- Makefile | 8 ++ contrib/make_nfsroot | 127 ++++++++++++++++++ general/package/openipc-nfs-root/Config.in | 16 +++ .../openipc-nfs-root/files/S45setupenv | 17 ++- .../openipc-nfs-root/post-build-hook.sh | 14 ++ 5 files changed, 179 insertions(+), 3 deletions(-) create mode 100755 contrib/make_nfsroot diff --git a/Makefile b/Makefile index b7ec528c1f..77b3f30404 100644 --- a/Makefile +++ b/Makefile @@ -107,6 +107,13 @@ endif @$(call BUNDLE_SDK) repack: +ifeq ($(BR2_PACKAGE_OPENIPC_NFS_ROOT),y) +ifeq ($(BR2_OPENIPC_SOC_VENDOR),"rockchip") + @$(call PREPARE_REPACK,zboot.img,16384,,,nfs-root) +else + @$(call PREPARE_REPACK,uImage,16384,,,nfs-root) +endif +else ifeq ($(BR2_OPENIPC_SOC_FAMILY),"hi3516cv6xx") @$(call PREPARE_REPACK,firmware.bin,$(shell expr $(subst ",,$(BR2_OPENIPC_FLASH_SIZE)) \* 1024),,,nor) else ifneq ($(wildcard $(TARGET)/images/firmware.bin),) @@ -132,6 +139,7 @@ ifeq ($(BR2_TARGET_ROOTFS_INITRAMFS),y) @$(call PREPARE_REPACK,uImage,16384,,,initramfs) endif endif +endif size-report: @TARGET_DIR=$(TARGET)/target \ diff --git a/contrib/make_nfsroot b/contrib/make_nfsroot new file mode 100755 index 0000000000..a8198ddecc --- /dev/null +++ b/contrib/make_nfsroot @@ -0,0 +1,127 @@ +#!/bin/sh +set -eu + +usage() { + echo "Usage: $0 [--passwd ] [--tz ] [--ntp ] [--dns ] [--pkgs
]" >&2 + exit 1 +} + +[ $# -ge 1 ] || usage + +BOARD= +PASSWD= +TIMEZONE= +FRAGMENT=general/openipc.fragment +FRAGMENT_BAK= +NTP= +DNS= +PKGS= + +while [ $# -gt 0 ]; do + case "$1" in + --passwd) + [ $# -ge 2 ] || usage + PASSWD=$2 + shift 2 + ;; + --tz) + [ $# -ge 2 ] || usage + TIMEZONE=$2 + shift 2 + ;; + --ntp) + [ $# -ge 2 ] || usage + NTP=$2 + shift 2 + ;; + --dns) + [ $# -ge 2 ] || usage + DNS=$2 + shift 2 + ;; + --pkgs) + [ $# -ge 2 ] || usage + PKGS=$2 + shift 2 + ;; + --help|-h) + usage + ;; + --*) + echo "Unknown option: $1" >&2 + usage + ;; + *) + if [ -z "$BOARD" ]; then + BOARD=$1 + shift + else + echo "Unexpected argument: $1" >&2 + usage + fi + ;; + esac +done + +[ -n "$BOARD" ] || usage + +cleanup() { + if [ -n "$FRAGMENT_BAK" ] && [ -f "$FRAGMENT_BAK" ]; then + cp "$FRAGMENT_BAK" "$FRAGMENT" + rm -f "$FRAGMENT_BAK" + fi +} + +FRAGMENT_BAK=$(mktemp "${TMPDIR:-/tmp}/openipc.fragment.XXXXXX") +cp "$FRAGMENT" "$FRAGMENT_BAK" +trap cleanup EXIT HUP INT TERM + +cat >> "$FRAGMENT" <> "$FRAGMENT" <> "$FRAGMENT" <> "$FRAGMENT" <> "$FRAGMENT" <> "$FRAGMENT" +fi + +# Note: Must pass BR2_PACKAGE_OPENIPC_NFS_ROOT=y on cmdline +# only to satisfy Makefile repack rules. +set -- \ + BOARD="$BOARD" \ + BR2_PACKAGE_OPENIPC_NFS_ROOT=y + +make "$@" || exit 1 + +KERNEL=$(find output/images/ -regextype posix-extended -iregex '.*/(uimage|zimage).*') +ROOTFS=$(find output/images/ -name '*.tar') +echo "\nNFS-root build complete" +echo "-----------------------" +echo kernel: "["$(du -h ${KERNEL} | awk '{print $1}')"]\t" $KERNEL +echo rootfs: "["$(du -h ${ROOTFS} | awk '{print $1}')"]\t" $ROOTFS diff --git a/general/package/openipc-nfs-root/Config.in b/general/package/openipc-nfs-root/Config.in index e345f078f2..fae7ec2bf2 100644 --- a/general/package/openipc-nfs-root/Config.in +++ b/general/package/openipc-nfs-root/Config.in @@ -36,4 +36,20 @@ config BR2_PACKAGE_OPENIPC_NFS_ROOT_TIMEZONE When left empty, the build attempts to detect the timezone from the build host and derives the POSIX TZ rule from that zone. +config BR2_PACKAGE_OPENIPC_NFS_ROOT_NTP_SERVER + string "NTP server" + default "" + help + Static NTP server to use. + + When left empty, the build uses the default route fo the NTP server. + +config BR2_PACKAGE_OPENIPC_NFS_ROOT_DNS_SERVER + string "DNS server" + default "" + help + Static DNS server to use. + + When left empty, the build uses the default route fo the DNS server. + endif diff --git a/general/package/openipc-nfs-root/files/S45setupenv b/general/package/openipc-nfs-root/files/S45setupenv index 466f69ad82..c5308e7987 100644 --- a/general/package/openipc-nfs-root/files/S45setupenv +++ b/general/package/openipc-nfs-root/files/S45setupenv @@ -37,9 +37,20 @@ setup_ssh_key() { setup_dns_ntp() { IP=$(ip route | sed -n 's/^default via \([0-9.]*\) .*/\1/p') - echo "nfs-root: Using default gateway (${IP?}) for NTP and DNS." - echo "nameserver ${IP}" > /etc/resolv.conf - echo "server ${IP} iburst" > /etc/ntp.conf + if [ -n "@NTP_SERVER@" ]; then + echo "server @NTP_SERVER@ iburst" > /etc/ntp.conf + echo "nfs-root: Using @NTP_SERVER@ for NTP." + else + echo "server ${IP} iburst" > /etc/ntp.conf + echo "nfs-root: Using default gateway (${IP?}) for NTP." + fi + if [ -n "@DNS_SERVER@" ]; then + echo "nameserver @DNS_SERVER@" > /etc/resolv.conf + echo "nfs-root: Using @DNS_SERVER@ for DNS." + else + echo "nameserver ${IP}" > /etc/resolv.conf + echo "nfs-root: Using default gateway (${IP?}) for DNS." + fi } case "$1" in diff --git a/general/package/openipc-nfs-root/post-build-hook.sh b/general/package/openipc-nfs-root/post-build-hook.sh index 36d4057515..0a68f318f5 100755 --- a/general/package/openipc-nfs-root/post-build-hook.sh +++ b/general/package/openipc-nfs-root/post-build-hook.sh @@ -84,6 +84,12 @@ OPENIPC_NFS_ROOT_TMPFS_SIZE="$(read_kconfig_string BR2_PACKAGE_OPENIPC_NFS_ROOT_ OPENIPC_NFS_ROOT_PASSWD="$(read_kconfig_string BR2_PACKAGE_OPENIPC_NFS_ROOT_PASSWD)" [ -n "${OPENIPC_NFS_ROOT_PASSWD}" ] || OPENIPC_NFS_ROOT_PASSWD="12345" +OPENIPC_NFS_ROOT_NTP_SERVER="$(read_kconfig_string BR2_PACKAGE_OPENIPC_NFS_ROOT_NTP_SERVER)" +[ -n "${OPENIPC_NFS_ROOT_NTP_SERVER}" ] || OPENIPC_NFS_ROOT_NTP_SERVER="" + +OPENIPC_NFS_ROOT_DNS_SERVER="$(read_kconfig_string BR2_PACKAGE_OPENIPC_NFS_ROOT_DNS_SERVER)" +[ -n "${OPENIPC_NFS_ROOT_DNS_SERVER}" ] || OPENIPC_NFS_ROOT_DNS_SERVER="" + OPENIPC_NFS_ROOT_TIMEZONE="$(read_kconfig_string BR2_PACKAGE_OPENIPC_NFS_ROOT_TIMEZONE)" if [ -z "${OPENIPC_NFS_ROOT_TIMEZONE}" ]; then OPENIPC_NFS_ROOT_TIMEZONE="$(detect_host_timezone)" @@ -104,6 +110,14 @@ if [ -f "${TARGET_DIR}/etc/init.d/S45setupenv" ]; then sed -i "s/@ROOTFS_PASSWD@/${OPENIPC_NFS_ROOT_PASSWD}/" "${TARGET_DIR}/etc/init.d/S45setupenv" fi +if [ -f "${TARGET_DIR}/etc/init.d/S45setupenv" ]; then + sed -i "s/@NTP_SERVER@/${OPENIPC_NFS_ROOT_NTP_SERVER}/" "${TARGET_DIR}/etc/init.d/S45setupenv" +fi + +if [ -f "${TARGET_DIR}/etc/init.d/S45setupenv" ]; then + sed -i "s/@DNS_SERVER@/${OPENIPC_NFS_ROOT_DNS_SERVER}/" "${TARGET_DIR}/etc/init.d/S45setupenv" +fi + if [ -d "${TARGET_DIR}/etc" ]; then write_target_timezone "${OPENIPC_NFS_ROOT_TIMEZONE}" "${OPENIPC_NFS_ROOT_TZ_POSIX}" fi From ad7e38a930ddd90dedd760187c466f6ad92b1557 Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Thu, 11 Jun 2026 12:18:56 -0600 Subject: [PATCH 05/19] Unify linux init script --- general/package/openipc-nfs-root/overlay/init | 77 ++++++++++++++----- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/general/package/openipc-nfs-root/overlay/init b/general/package/openipc-nfs-root/overlay/init index 1964abbeca..351610d1b4 100755 --- a/general/package/openipc-nfs-root/overlay/init +++ b/general/package/openipc-nfs-root/overlay/init @@ -10,27 +10,64 @@ trap on_exit EXIT mount -t proc proc /proc || exit 1 grep -q overlay /proc/filesystems || exit 1 -if ! mount -t tmpfs -o mode=0755,size=@TMPFS_SIZE@ tmpfs /overlay; then - echo "Cannot mount tmpfs overlay." - exit 1 -fi +if ! grep -q 'root=.*nfs\|mmcblk\|ram' /proc/cmdline; then + if grep -q ubifs /proc/cmdline; then + mount -t ubifs ubi0:rootfs_data /overlay + else + mtdblkdev=$(awk -F ':' '/rootfs_data/ {print $1}' /proc/mtd | sed 's/mtd/mtdblock/') + mtdchrdev=$(grep 'rootfs_data' /proc/mtd | cut -d: -f1) + if ! mount -t jffs2 /dev/$mtdblkdev /overlay || dmesg | grep -q "jffs2.*: Magic bitmask.*not found"; then + mountpoint -q /overlay && umount /overlay + echo "Formatting flash..." + grep -q 'nand' /proc/cmdline || jffs2="-j" + flash_eraseall $jffs2 /dev/$mtdchrdev + if ! mount -t jffs2 /dev/$mtdblkdev /overlay && ! mount -t tmpfs tmpfs /overlay; then + echo "Cannot mount overlay." + exit 1 + fi + fi + fi -if grep -q overlayfs /proc/filesystems; then - if ! mount -t overlayfs overlayfs -o lowerdir=/,upperdir=/overlay,ro /mnt; then - umount /overlay - exit 1 + if grep -q overlayfs /proc/filesystems; then + if ! mount -t overlayfs overlayfs -o lowerdir=/,upperdir=/overlay,ro /mnt; then + umount /overlay + exit 1 + fi + else + overlay_rootdir=/overlay/root + overlay_workdir=/overlay/work + mkdir -p $overlay_rootdir $overlay_workdir + if ! mount -t overlay overlay -o lowerdir=/,upperdir=$overlay_rootdir,workdir=$overlay_workdir /mnt; then + umount /overlay + exit 1 + fi fi -else - overlay_rootdir=/overlay/root - overlay_workdir=/overlay/work - mkdir -p $overlay_rootdir $overlay_workdir - if ! mount -t overlay overlay -o lowerdir=/,upperdir=$overlay_rootdir,workdir=$overlay_workdir /mnt; then - umount /overlay - exit 1 + + pivot_root /mnt /mnt/rom + mount -o noatime,move /rom/proc /proc + mount -o noatime,move /rom/dev /dev + mount -o noatime,move /rom/overlay /overlay + +elif grep -q 'root=.*nfs' /proc/cmdline; then + if grep -q ' ro ' /proc/cmdline; then + if ! mount -t tmpfs -o mode=0755,size=@TMPFS_SIZE@ tmpfs /overlay; then + echo "Cannot mount tmpfs overlay." + exit 1 + fi + + overlay_rootdir=/overlay/root + overlay_workdir=/overlay/work + mkdir -p $overlay_rootdir $overlay_workdir + if ! mount -t overlay overlay -o lowerdir=/,upperdir=$overlay_rootdir,workdir=$overlay_workdir /mnt; then + umount /overlay + exit 1 + fi + + pivot_root /mnt /mnt/rom + mount -o noatime,move /rom/proc /proc + mount -o noatime,move /rom/dev /dev + mount -o noatime,move /rom/overlay /overlay + else + echo "Warning: Only one device per NFS rw root partition!" fi fi - -pivot_root /mnt /mnt/rom -mount -o noatime,move /rom/proc /proc -mount -o noatime,move /rom/dev /dev -mount -o noatime,move /rom/overlay /overlay From 45297671a0fa8145143e823b795be6cbadecda30 Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Thu, 11 Jun 2026 13:00:35 -0600 Subject: [PATCH 06/19] Make nfs root rw behave as expected --- .../openipc-nfs-root/files/S45setupenv | 76 +++++++++---------- .../openipc-nfs-root/files/rootfs-setup | 15 ---- .../openipc-nfs-root/openipc-nfs-root.mk | 1 - .../openipc-nfs-root/overlay/etc/inittab | 2 +- 4 files changed, 37 insertions(+), 57 deletions(-) delete mode 100644 general/package/openipc-nfs-root/files/rootfs-setup diff --git a/general/package/openipc-nfs-root/files/S45setupenv b/general/package/openipc-nfs-root/files/S45setupenv index c5308e7987..673347d96a 100644 --- a/general/package/openipc-nfs-root/files/S45setupenv +++ b/general/package/openipc-nfs-root/files/S45setupenv @@ -3,18 +3,14 @@ # Customize environment for NFS boot # -#!/bin/sh - - -# Setup NFS password setup_root_passwd() { - shadow_hash="$(awk -F: '$1=="root"{print $2}' /etc/shadow)" - salt="$(printf '%s\n' "$shadow_hash" | cut -d'$' -f3)" - test_hash="$(mkpasswd -m md5 -S "$salt" "@ROOTFS_PASSWD@")" - if [ ! "$test_hash" = "$shadow_hash" ]; then - echo "root:@ROOTFS_PASSWD@" | chpasswd 2>/dev/null - echo "nfs-root: Updated root password." - fi + shadow_hash="$(awk -F: '$1=="root"{print $2}' /etc/shadow)" + salt="$(printf '%s\n' "$shadow_hash" | cut -d'$' -f3)" + test_hash="$(mkpasswd -m md5 -S "$salt" "@ROOTFS_PASSWD@")" + if [ ! "$test_hash" = "$shadow_hash" ]; then + echo "root:@ROOTFS_PASSWD@" | chpasswd 2>/dev/null + echo "nfs-root: Updated root password." + fi } setup_ssh_key() { @@ -36,38 +32,38 @@ setup_ssh_key() { } setup_dns_ntp() { - IP=$(ip route | sed -n 's/^default via \([0-9.]*\) .*/\1/p') - if [ -n "@NTP_SERVER@" ]; then - echo "server @NTP_SERVER@ iburst" > /etc/ntp.conf - echo "nfs-root: Using @NTP_SERVER@ for NTP." - else - echo "server ${IP} iburst" > /etc/ntp.conf - echo "nfs-root: Using default gateway (${IP?}) for NTP." - fi - if [ -n "@DNS_SERVER@" ]; then - echo "nameserver @DNS_SERVER@" > /etc/resolv.conf - echo "nfs-root: Using @DNS_SERVER@ for DNS." - else - echo "nameserver ${IP}" > /etc/resolv.conf - echo "nfs-root: Using default gateway (${IP?}) for DNS." - fi + IP=$(ip route | sed -n 's/^default via \([0-9.]*\) .*/\1/p') + if [ -n "@NTP_SERVER@" ]; then + echo "server @NTP_SERVER@ iburst" > /etc/ntp.conf + echo "nfs-root: Using @NTP_SERVER@ for NTP." + else + echo "server ${IP} iburst" > /etc/ntp.conf + echo "nfs-root: Using default gateway (${IP?}) for NTP." + fi + if [ -n "@DNS_SERVER@" ]; then + echo "nameserver @DNS_SERVER@" > /etc/resolv.conf + echo "nfs-root: Using @DNS_SERVER@ for DNS." + else + echo "nameserver ${IP}" > /etc/resolv.conf + echo "nfs-root: Using default gateway (${IP?}) for DNS." + fi } case "$1" in - start) - # Only run if booting from nfs root - if grep -qw 'root=/dev/nfs' /proc/cmdline; then - setup_root_passwd - setup_dns_ntp - setup_ssh_key - fi - ;; + start) + if grep -q 'root=/dev/nfs' /proc/cmdline && [ ! -f '/etc/firstboot' ]; then + setup_root_passwd + setup_dns_ntp + setup_ssh_key + touch /etc/firstboot + fi + ;; - stop) - ;; + stop) + ;; - *) - echo "Usage: $0 {start}" - exit 1 - ;; + *) + echo "Usage: $0 {start}" + exit 1 + ;; esac diff --git a/general/package/openipc-nfs-root/files/rootfs-setup b/general/package/openipc-nfs-root/files/rootfs-setup deleted file mode 100644 index 5546fb36cf..0000000000 --- a/general/package/openipc-nfs-root/files/rootfs-setup +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -fstype=$(awk '$2=="/"{print $3}' /proc/mounts) -opts=$(awk '$2=="/"{print $4}' /proc/mounts) - -case "$fstype" in - nfs|nfs4|overlay) - echo "Skipping root remount for $fstype root ($opts)" - ;; - *) - mount -o remount,rw / - ;; -esac - -exit 0 diff --git a/general/package/openipc-nfs-root/openipc-nfs-root.mk b/general/package/openipc-nfs-root/openipc-nfs-root.mk index 948ae021bb..fa954a626a 100644 --- a/general/package/openipc-nfs-root/openipc-nfs-root.mk +++ b/general/package/openipc-nfs-root/openipc-nfs-root.mk @@ -12,7 +12,6 @@ OPENIPC_NFS_ROOT_SITE = $(BR2_EXTERNAL_GENERAL_PATH)/package/openipc-nfs-root OPENIPC_NFS_ROOT_LICENSE = MIT define OPENIPC_NFS_ROOT_INSTALL_TARGET_CMDS - $(INSTALL) -D -m 0755 $(@D)/files/rootfs-setup $(TARGET_DIR)/usr/sbin/rootfs-setup $(INSTALL) -D -m 0755 $(@D)/files/set-hostname-from-cmdline $(TARGET_DIR)/usr/sbin/set-hostname-from-cmdline $(INSTALL) -D -m 0755 $(@D)/files/S45setupenv $(TARGET_DIR)/etc/init.d/S45setupenv endef diff --git a/general/package/openipc-nfs-root/overlay/etc/inittab b/general/package/openipc-nfs-root/overlay/etc/inittab index a6401475e6..21f59a3de5 100644 --- a/general/package/openipc-nfs-root/overlay/etc/inittab +++ b/general/package/openipc-nfs-root/overlay/etc/inittab @@ -15,7 +15,7 @@ # Startup the system ::sysinit:/bin/mount -t proc proc /proc -::sysinit:/usr/sbin/rootfs-setup +::sysinit:/bin/mount -o remount,rw / ::sysinit:/bin/mkdir -p /dev/pts /dev/shm ::sysinit:/bin/mount -a ::sysinit:/bin/mkdir -p /run/lock/subsys From fd5555704f3c4da3f8359ab602fe058d49af31d1 Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Thu, 11 Jun 2026 13:19:35 -0600 Subject: [PATCH 07/19] Update patched ipconfig.c kernel msg --- .../0001-ipconfig-set-platform-mac-before-open.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/general/package/openipc-nfs-root/kernel-patches/0001-ipconfig-set-platform-mac-before-open.patch b/general/package/openipc-nfs-root/kernel-patches/0001-ipconfig-set-platform-mac-before-open.patch index 859f0b274a..e964a44ab7 100644 --- a/general/package/openipc-nfs-root/kernel-patches/0001-ipconfig-set-platform-mac-before-open.patch +++ b/general/package/openipc-nfs-root/kernel-patches/0001-ipconfig-set-platform-mac-before-open.patch @@ -38,7 +38,7 @@ + !eth_platform_get_mac_address(dev->dev.parent, mac)) { + ether_addr_copy(dev->dev_addr, mac); + first_mac_done = true; -+ pr_info("openipc_ethaddr: Override %s MAC to %pM for NFS root\n", ++ pr_info("openipc: Overriding %s MAC from bootargs/DT %pM\n", + dev->name, dev->dev_addr); + } oflags = dev->flags; From baa57aa9d10ae5662fb37f4c043f8cadd9c7f183 Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Thu, 11 Jun 2026 13:39:29 -0600 Subject: [PATCH 08/19] Fix formatting space => tabs --- contrib/make_nfsroot | 6 +++--- general/package/openipc-nfs-root/Config.in | 8 ++++---- .../files/set-hostname-from-cmdline | 17 ++++++++--------- .../overlay/etc/init.d/S40network | 2 +- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/contrib/make_nfsroot b/contrib/make_nfsroot index a8198ddecc..9a01232941 100755 --- a/contrib/make_nfsroot +++ b/contrib/make_nfsroot @@ -39,11 +39,11 @@ while [ $# -gt 0 ]; do DNS=$2 shift 2 ;; - --pkgs) + --pkgs) [ $# -ge 2 ] || usage PKGS=$2 shift 2 - ;; + ;; --help|-h) usage ;; @@ -108,7 +108,7 @@ fi # Add additional packages for nfs build if [ -n "$PKGS" ] && [ -f "$PKGS" ]; then - cat "$PKGS" >> "$FRAGMENT" + cat "$PKGS" >> "$FRAGMENT" fi # Note: Must pass BR2_PACKAGE_OPENIPC_NFS_ROOT=y on cmdline diff --git a/general/package/openipc-nfs-root/Config.in b/general/package/openipc-nfs-root/Config.in index fae7ec2bf2..e349b55fd3 100644 --- a/general/package/openipc-nfs-root/Config.in +++ b/general/package/openipc-nfs-root/Config.in @@ -40,16 +40,16 @@ config BR2_PACKAGE_OPENIPC_NFS_ROOT_NTP_SERVER string "NTP server" default "" help - Static NTP server to use. - + Static NTP server to use. + When left empty, the build uses the default route fo the NTP server. config BR2_PACKAGE_OPENIPC_NFS_ROOT_DNS_SERVER string "DNS server" default "" help - Static DNS server to use. - + Static DNS server to use. + When left empty, the build uses the default route fo the DNS server. endif diff --git a/general/package/openipc-nfs-root/files/set-hostname-from-cmdline b/general/package/openipc-nfs-root/files/set-hostname-from-cmdline index 8ebe8698e9..4e937d74c5 100644 --- a/general/package/openipc-nfs-root/files/set-hostname-from-cmdline +++ b/general/package/openipc-nfs-root/files/set-hostname-from-cmdline @@ -6,16 +6,15 @@ cmdline=$(cat /proc/cmdline) hostname_arg=$(printf ' %s\n' "$cmdline" | sed -n 's/.*[[:space:]]hostname=\([^[:space:]]*\).*/\1/p') if [ -n "$hostname_arg" ]; then - hostname "$hostname_arg" - echo "$hostname_arg" > /etc/hostname + hostname "$hostname_arg" + echo "$hostname_arg" > /etc/hostname - if [ -w /etc/hosts ]; then - { - echo "127.0.0.1 localhost" - echo "127.0.1.1 $hostname_arg" - } > /etc/hosts - fi + if [ -w /etc/hosts ]; then + { + echo "127.0.0.1 localhost" + echo "127.0.1.1 $hostname_arg" + } > /etc/hosts + fi fi - exit 0 diff --git a/general/package/openipc-nfs-root/overlay/etc/init.d/S40network b/general/package/openipc-nfs-root/overlay/etc/init.d/S40network index 51f14f75e5..3763075ff0 100755 --- a/general/package/openipc-nfs-root/overlay/etc/init.d/S40network +++ b/general/package/openipc-nfs-root/overlay/etc/init.d/S40network @@ -17,7 +17,7 @@ set_wireless() { } is_nfs_root() { - grep -qw 'root=/dev/nfs' /proc/cmdline + grep -q 'root=/dev/nfs' /proc/cmdline } start() { From 228a5966a9d7e6310871bcc92cb6442e610e094f Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Fri, 12 Jun 2026 12:43:37 -0600 Subject: [PATCH 09/19] Cleanup, add hostname support, fix DNS/NTP, hash passwd in helper script --- contrib/make_nfsroot | 33 +++- general/package/openipc-nfs-root/Config.in | 14 +- .../openipc-nfs-root/files/S45setupenv | 141 +++++++++++++++--- .../files/set-hostname-from-cmdline | 20 --- .../openipc-nfs-root/openipc-nfs-root.mk | 1 - .../openipc-nfs-root/overlay/etc/inittab | 37 ----- .../openipc-nfs-root/post-build-hook.sh | 16 +- 7 files changed, 176 insertions(+), 86 deletions(-) delete mode 100644 general/package/openipc-nfs-root/files/set-hostname-from-cmdline delete mode 100644 general/package/openipc-nfs-root/overlay/etc/inittab diff --git a/contrib/make_nfsroot b/contrib/make_nfsroot index 9a01232941..0df7688b48 100755 --- a/contrib/make_nfsroot +++ b/contrib/make_nfsroot @@ -2,19 +2,28 @@ set -eu usage() { - echo "Usage: $0 [--passwd ] [--tz ] [--ntp ] [--dns ] [--pkgs
]" >&2 + printf '%s\n' \ + "Usage: $0 [optional args]" \ + ' Optional args:' \ + ' --passwd Plaintext or hash ($1$...)' \ + ' --tz iana-timezone (Europe/Berlin)' \ + ' --ntp NTP server' \ + ' --dns DNS server' \ + ' --hostname fallback hostname' \ + ' --pkgs File with additional buildroot pkgs' exit 1 } [ $# -ge 1 ] || usage +FRAGMENT=general/openipc.fragment +FRAGMENT_BAK= BOARD= PASSWD= TIMEZONE= -FRAGMENT=general/openipc.fragment -FRAGMENT_BAK= NTP= DNS= +HOSTNAME= PKGS= while [ $# -gt 0 ]; do @@ -23,6 +32,13 @@ while [ $# -gt 0 ]; do [ $# -ge 2 ] || usage PASSWD=$2 shift 2 + case "$PASSWD" in + '$'[0-9]'$'*) + ;; + *) + PASSWD=$(mkpasswd -5 "$PASSWD") + ;; + esac ;; --tz) [ $# -ge 2 ] || usage @@ -39,6 +55,11 @@ while [ $# -gt 0 ]; do DNS=$2 shift 2 ;; + --hostname) + [ $# -ge 2 ] || usage + HOSTNAME=$2 + shift 2 + ;; --pkgs) [ $# -ge 2 ] || usage PKGS=$2 @@ -106,6 +127,12 @@ BR2_PACKAGE_OPENIPC_NFS_ROOT_DNS_SERVER="$DNS" EOF fi +if [ -n "$HOSTNAME" ]; then + cat >> "$FRAGMENT" <> "$FRAGMENT" diff --git a/general/package/openipc-nfs-root/Config.in b/general/package/openipc-nfs-root/Config.in index e349b55fd3..86bd14b804 100644 --- a/general/package/openipc-nfs-root/Config.in +++ b/general/package/openipc-nfs-root/Config.in @@ -14,17 +14,18 @@ config BR2_PACKAGE_OPENIPC_NFS_ROOT if BR2_PACKAGE_OPENIPC_NFS_ROOT config BR2_PACKAGE_OPENIPC_NFS_ROOT_TMPFS_SIZE - string "tmpfs size" + string "TMPFS size" default "16M" help Size passed to the tmpfs mount used for the writable overlay when NFS root is mounted in readonly mode. config BR2_PACKAGE_OPENIPC_NFS_ROOT_PASSWD - string "root password" + string "Root password" default "12345" help Root password to be set during boot. Useful for immutable rootfs. + Can be plaintext or a password hash. config BR2_PACKAGE_OPENIPC_NFS_ROOT_TIMEZONE string "IANA timezone" @@ -52,4 +53,13 @@ config BR2_PACKAGE_OPENIPC_NFS_ROOT_DNS_SERVER When left empty, the build uses the default route fo the DNS server. +config BR2_PACKAGE_OPENIPC_NFS_ROOT_HOSTNAME + string "Hostname" + default "" + help + Fallback hostname. + + If server supplied hostname and uboot env hostname don't exist then + use this hostname. + endif diff --git a/general/package/openipc-nfs-root/files/S45setupenv b/general/package/openipc-nfs-root/files/S45setupenv index 673347d96a..f3fa1b470d 100644 --- a/general/package/openipc-nfs-root/files/S45setupenv +++ b/general/package/openipc-nfs-root/files/S45setupenv @@ -3,12 +3,83 @@ # Customize environment for NFS boot # +dhcp_fetch_dns_ntp() { + iface="$1" + hostname="$2" + out="${3:-/tmp/udhcpc.env.$$}" + hook="/tmp/udhcpc-hook.$$" + + [ -n "$iface" ] || return 2 + +cat > "$hook" <<'EOF' +#!/bin/sh +case "$1" in + bound|renew) + { + echo "dns='$dns'" + echo "ntpsrv='$ntpsrv'" + echo "serverid='$serverid'" + echo "ip='$ip'" + echo "subnet='$subnet'" + echo "router='$router'" + echo "hostname='$hostname'" + echo "domain='$domain'" + } > "$UDHCPC_CAPTURE" + exit 0 + ;; + *) + exit 0 + ;; +esac +EOF + + chmod +x "$hook" || return 1 + + UDHCPC_CAPTURE="$out" \ + udhcpc -i "$iface" -n -q \ + -s "$hook" \ + -O ntpsrv \ + -O dns \ + -O hostname \ + -O domain \ + -F "$hostname" \ + >/dev/null 2>&1 + + rc=$? + rm -f "$hook" + + [ $rc -eq 0 ] || return $rc + [ -s "$out" ] || return 1 + + . "$out" + #rm -f "$out" + + DHCP_NTP="$ntpsrv" + DHCP_DNS="$dns" + DHCP_HOSTNAME="$hostname" + DHCP_DOMAIN="$domain" + return 0 +} + +is_hash() { + case "$1" in + '$1$'*) return 0 ;; # md5-crypt + '$5$'*) return 0 ;; # sha256-crypt + '$6$'*) return 0 ;; # sha512-crypt + *) return 1 ;; # plaintext + esac +} + setup_root_passwd() { shadow_hash="$(awk -F: '$1=="root"{print $2}' /etc/shadow)" - salt="$(printf '%s\n' "$shadow_hash" | cut -d'$' -f3)" - test_hash="$(mkpasswd -m md5 -S "$salt" "@ROOTFS_PASSWD@")" - if [ ! "$test_hash" = "$shadow_hash" ]; then - echo "root:@ROOTFS_PASSWD@" | chpasswd 2>/dev/null + if is_hash '@ROOTFS_PASSWD@'; then + hash='@ROOTFS_PASSWD@' + else + salt="$(printf '%s\n' "$shadow_hash" | cut -d'$' -f3)" + hash="$(mkpasswd -m md5 -S "$salt" '@ROOTFS_PASSWD@')" + fi + if [ ! "$hash" = "$shadow_hash" ]; then + printf '%s\n' "root:$hash" | chpasswd -e 2>/dev/null echo "nfs-root: Updated root password." fi } @@ -31,30 +102,62 @@ setup_ssh_key() { fi } -setup_dns_ntp() { - IP=$(ip route | sed -n 's/^default via \([0-9.]*\) .*/\1/p') - if [ -n "@NTP_SERVER@" ]; then - echo "server @NTP_SERVER@ iburst" > /etc/ntp.conf - echo "nfs-root: Using @NTP_SERVER@ for NTP." - else - echo "server ${IP} iburst" > /etc/ntp.conf - echo "nfs-root: Using default gateway (${IP?}) for NTP." +setup_dns_ntp_hostname() { + # Compile time variables + BUILD_HOSTNAME='@HOSTNAME@' + BUILD_NTP='@NTP_SERVER@' + BUILD_DNS='@DNS_SERVER@' + + # Hostname ordering: SERVER supplied > UBOOT variable > static build > default overlay + UBOOT_HOSTNAME=$(printf ' %s\n' "$cmdline" | sed -n 's/.*[[:space:]]hostname=\([^[:space:]]*\).*/\1/p') + UBOOT_DNS="$(awk '/^nameserver / { print $2; exit }' /proc/net/pnp)" + DEFAULT_HOSTNAME=$(cat /etc/hostname) + HOSTNAME="${UBOOT_HOSTNAME:-${BUILD_HOSTNAME:-$DEFAULT_HOSTNAME}}" + + # If kernel IP autoconf used DHCP try getting missing variable not exposed + if grep -q '^#PROTO: DHCP$' /proc/net/pnp; then + if ! dhcp_fetch_dns_ntp eth0 $HOSTNAME; then + echo "nfs-root: DHCP request failed." + fi + fi + NTP="${BUILD_NTP:-$DHCP_NTP}" + DNS="${BUILD_DNS:-${UBOOT_DNS:-$DHCP_DNS}}" + HOSTNAME="${DHCP_HOSTNAME:-$HOSTNAME}" + if [ -n "$NTP" ]; then + echo "server ${NTP} iburst" > /etc/ntp.conf + echo "nfs-root: Using ${NTP} for NTP." + fi + if [ -n "$DNS" ]; then + echo "nameserver ${DNS}" > /etc/resolv.conf + echo "nfs-root: Using ${DNS} for DNS." fi - if [ -n "@DNS_SERVER@" ]; then - echo "nameserver @DNS_SERVER@" > /etc/resolv.conf - echo "nfs-root: Using @DNS_SERVER@ for DNS." + + if [ -n "$DHCP_DOMAIN" ]; then + FQDN="${HOSTNAME}.${DHCP_DOMAIN}" else - echo "nameserver ${IP}" > /etc/resolv.conf - echo "nfs-root: Using default gateway (${IP?}) for DNS." + FQDN="" + fi + echo "${HOSTNAME}" > /etc/hostname + if [ -w /etc/hosts ]; then + { + echo "127.0.0.1 localhost" + echo "127.0.1.1 $HOSTNAME $FQDN" + } > /etc/hosts fi + + # Set the hostname + /bin/hostname -F /etc/hostname >/dev/null 2>&1 + echo "nfs-root: Configured hostname to $HOSTNAME" } case "$1" in start) if grep -q 'root=/dev/nfs' /proc/cmdline && [ ! -f '/etc/firstboot' ]; then setup_root_passwd - setup_dns_ntp - setup_ssh_key + setup_dns_ntp_hostname + if grep -Eq '(^| )ro( |$)' /proc/cmdline; then + setup_ssh_key + fi touch /etc/firstboot fi ;; diff --git a/general/package/openipc-nfs-root/files/set-hostname-from-cmdline b/general/package/openipc-nfs-root/files/set-hostname-from-cmdline deleted file mode 100644 index 4e937d74c5..0000000000 --- a/general/package/openipc-nfs-root/files/set-hostname-from-cmdline +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh -# -# Use hostname from command line - -cmdline=$(cat /proc/cmdline) -hostname_arg=$(printf ' %s\n' "$cmdline" | sed -n 's/.*[[:space:]]hostname=\([^[:space:]]*\).*/\1/p') - -if [ -n "$hostname_arg" ]; then - hostname "$hostname_arg" - echo "$hostname_arg" > /etc/hostname - - if [ -w /etc/hosts ]; then - { - echo "127.0.0.1 localhost" - echo "127.0.1.1 $hostname_arg" - } > /etc/hosts - fi -fi - -exit 0 diff --git a/general/package/openipc-nfs-root/openipc-nfs-root.mk b/general/package/openipc-nfs-root/openipc-nfs-root.mk index fa954a626a..09b05cf102 100644 --- a/general/package/openipc-nfs-root/openipc-nfs-root.mk +++ b/general/package/openipc-nfs-root/openipc-nfs-root.mk @@ -12,7 +12,6 @@ OPENIPC_NFS_ROOT_SITE = $(BR2_EXTERNAL_GENERAL_PATH)/package/openipc-nfs-root OPENIPC_NFS_ROOT_LICENSE = MIT define OPENIPC_NFS_ROOT_INSTALL_TARGET_CMDS - $(INSTALL) -D -m 0755 $(@D)/files/set-hostname-from-cmdline $(TARGET_DIR)/usr/sbin/set-hostname-from-cmdline $(INSTALL) -D -m 0755 $(@D)/files/S45setupenv $(TARGET_DIR)/etc/init.d/S45setupenv endef diff --git a/general/package/openipc-nfs-root/overlay/etc/inittab b/general/package/openipc-nfs-root/overlay/etc/inittab deleted file mode 100644 index 21f59a3de5..0000000000 --- a/general/package/openipc-nfs-root/overlay/etc/inittab +++ /dev/null @@ -1,37 +0,0 @@ -# /etc/inittab -# -# Copyright (C) 2001 Erik Andersen -# -# Note: BusyBox init doesn't support runlevels. The runlevels field is -# completely ignored by BusyBox init. If you want runlevels, use -# sysvinit. -# -# Format for each entry: ::: -# -# id == tty to run on, or empty for /dev/console -# runlevels == ignored -# action == one of sysinit, respawn, askfirst, wait, and once -# process == program to run - -# Startup the system -::sysinit:/bin/mount -t proc proc /proc -::sysinit:/bin/mount -o remount,rw / -::sysinit:/bin/mkdir -p /dev/pts /dev/shm -::sysinit:/bin/mount -a -::sysinit:/bin/mkdir -p /run/lock/subsys -null::sysinit:/bin/ln -sf /proc/self/fd /dev/fd -null::sysinit:/bin/ln -sf /proc/self/fd/0 /dev/stdin -null::sysinit:/bin/ln -sf /proc/self/fd/1 /dev/stdout -null::sysinit:/bin/ln -sf /proc/self/fd/2 /dev/stderr -::sysinit:/usr/sbin/set-hostname-from-cmdline -::sysinit:/bin/hostname -F /etc/hostname - -# Run rc scripts -::sysinit:/etc/init.d/rcS - -# Put a getty on the serial port -console::respawn:/sbin/getty -L console 0 vt100 - -# Stuff to do before rebooting -::shutdown:/etc/init.d/rcK -::shutdown:/bin/umount -a -f diff --git a/general/package/openipc-nfs-root/post-build-hook.sh b/general/package/openipc-nfs-root/post-build-hook.sh index 0a68f318f5..e6508446fe 100755 --- a/general/package/openipc-nfs-root/post-build-hook.sh +++ b/general/package/openipc-nfs-root/post-build-hook.sh @@ -82,6 +82,7 @@ OPENIPC_NFS_ROOT_TMPFS_SIZE="$(read_kconfig_string BR2_PACKAGE_OPENIPC_NFS_ROOT_ [ -n "${OPENIPC_NFS_ROOT_TMPFS_SIZE}" ] || OPENIPC_NFS_ROOT_TMPFS_SIZE=16M OPENIPC_NFS_ROOT_PASSWD="$(read_kconfig_string BR2_PACKAGE_OPENIPC_NFS_ROOT_PASSWD)" +OPENIPC_NFS_ROOT_PASSWD=$(printf '%s' "$OPENIPC_NFS_ROOT_PASSWD" | sed 's/[\/&\\]/\\&/g') [ -n "${OPENIPC_NFS_ROOT_PASSWD}" ] || OPENIPC_NFS_ROOT_PASSWD="12345" OPENIPC_NFS_ROOT_NTP_SERVER="$(read_kconfig_string BR2_PACKAGE_OPENIPC_NFS_ROOT_NTP_SERVER)" @@ -90,6 +91,9 @@ OPENIPC_NFS_ROOT_NTP_SERVER="$(read_kconfig_string BR2_PACKAGE_OPENIPC_NFS_ROOT_ OPENIPC_NFS_ROOT_DNS_SERVER="$(read_kconfig_string BR2_PACKAGE_OPENIPC_NFS_ROOT_DNS_SERVER)" [ -n "${OPENIPC_NFS_ROOT_DNS_SERVER}" ] || OPENIPC_NFS_ROOT_DNS_SERVER="" +OPENIPC_NFS_ROOT_HOSTNAME="$(read_kconfig_string BR2_PACKAGE_OPENIPC_NFS_ROOT_HOSTNAME)" +[ -n "${OPENIPC_NFS_ROOT_HOSTNAME}" ] || OPENIPC_NFS_ROOT_HOSTNAME="" + OPENIPC_NFS_ROOT_TIMEZONE="$(read_kconfig_string BR2_PACKAGE_OPENIPC_NFS_ROOT_TIMEZONE)" if [ -z "${OPENIPC_NFS_ROOT_TIMEZONE}" ]; then OPENIPC_NFS_ROOT_TIMEZONE="$(detect_host_timezone)" @@ -103,19 +107,23 @@ fi OPENIPC_NFS_ROOT_TZ_POSIX="$(extract_posix_tz "${OPENIPC_NFS_ROOT_TIMEZONE}")" if [ -f "${TARGET_DIR}/init" ]; then - sed -i "s/@TMPFS_SIZE@/${OPENIPC_NFS_ROOT_TMPFS_SIZE}/" "${TARGET_DIR}/init" + sed -i "s|@TMPFS_SIZE@|${OPENIPC_NFS_ROOT_TMPFS_SIZE}|" "${TARGET_DIR}/init" +fi + +if [ -f "${TARGET_DIR}/etc/init.d/S45setupenv" ]; then + sed -i "s|@ROOTFS_PASSWD@|${OPENIPC_NFS_ROOT_PASSWD}|" "${TARGET_DIR}/etc/init.d/S45setupenv" fi if [ -f "${TARGET_DIR}/etc/init.d/S45setupenv" ]; then - sed -i "s/@ROOTFS_PASSWD@/${OPENIPC_NFS_ROOT_PASSWD}/" "${TARGET_DIR}/etc/init.d/S45setupenv" + sed -i "s|@NTP_SERVER@|${OPENIPC_NFS_ROOT_NTP_SERVER}|" "${TARGET_DIR}/etc/init.d/S45setupenv" fi if [ -f "${TARGET_DIR}/etc/init.d/S45setupenv" ]; then - sed -i "s/@NTP_SERVER@/${OPENIPC_NFS_ROOT_NTP_SERVER}/" "${TARGET_DIR}/etc/init.d/S45setupenv" + sed -i "s|@DNS_SERVER@|${OPENIPC_NFS_ROOT_DNS_SERVER}|" "${TARGET_DIR}/etc/init.d/S45setupenv" fi if [ -f "${TARGET_DIR}/etc/init.d/S45setupenv" ]; then - sed -i "s/@DNS_SERVER@/${OPENIPC_NFS_ROOT_DNS_SERVER}/" "${TARGET_DIR}/etc/init.d/S45setupenv" + sed -i "s|@HOSTNAME@|${OPENIPC_NFS_ROOT_HOSTNAME}|" "${TARGET_DIR}/etc/init.d/S45setupenv" fi if [ -d "${TARGET_DIR}/etc" ]; then From 0c941ca0275615768face3c145ed37f2993c6add Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Fri, 12 Jun 2026 23:21:48 -0600 Subject: [PATCH 10/19] Fix compile error when feature disabled. Start udhcpc daemon when kernel ip autoconf is dhcp. Use static mac_pton helper for older linux kernel support. --- general/linux/linux-ext-openipc-nfs-root.mk | 10 +++- general/package/openipc-nfs-root/Config.in | 1 + .../openipc-nfs-root/files/S45setupenv | 43 +++++++++++----- .../openipc-nfs-root/files/openipc_ethaddr.c | 49 ++++++++++++++----- 4 files changed, 78 insertions(+), 25 deletions(-) diff --git a/general/linux/linux-ext-openipc-nfs-root.mk b/general/linux/linux-ext-openipc-nfs-root.mk index be39bfdcf7..bb9b5bd2f9 100644 --- a/general/linux/linux-ext-openipc-nfs-root.mk +++ b/general/linux/linux-ext-openipc-nfs-root.mk @@ -13,9 +13,15 @@ define OPENIPC_NFS_ROOT_SYNC_ETHADDR_SOURCE echo 'obj-y += openipc_ethaddr.o' >> $(@D)/drivers/net/ethernet/Makefile endef +define OPENIPC_NFS_ROOT_CHECK_KERNEL_SUPPORT + grep -qr 'eth_platform_get_mac_address' $(@D)/net $(@D)/include 2>/dev/null || { \ + echo "openipc-nfs-root requires Linux >= 4.5 or a vendor kernel with eth_platform_get_mac_address() support" >&2; \ + exit 1; \ + } +endef + define OPENIPC_NFS_ROOT_PREPARE_KERNEL + $(OPENIPC_NFS_ROOT_CHECK_KERNEL_SUPPORT) $(APPLY_PATCHES) $(@D) $(BR2_EXTERNAL_GENERAL_PATH)/package/openipc-nfs-root/kernel-patches \*.patch $(OPENIPC_NFS_ROOT_SYNC_ETHADDR_SOURCE) endef - -LINUX_PRE_BUILD_HOOKS += OPENIPC_NFS_ROOT_SYNC_ETHADDR_SOURCE diff --git a/general/package/openipc-nfs-root/Config.in b/general/package/openipc-nfs-root/Config.in index 86bd14b804..54c59fdcf7 100644 --- a/general/package/openipc-nfs-root/Config.in +++ b/general/package/openipc-nfs-root/Config.in @@ -1,5 +1,6 @@ config BR2_PACKAGE_OPENIPC_NFS_ROOT bool "OpenIPC NFS root" + depends on BR2_LINUX_KERNEL select BR2_LINUX_KERNEL_EXT_OPENIPC_NFS_ROOT help NFS-root-specific files and scripts for OpenIPC builds. diff --git a/general/package/openipc-nfs-root/files/S45setupenv b/general/package/openipc-nfs-root/files/S45setupenv index f3fa1b470d..5fe9a9311b 100644 --- a/general/package/openipc-nfs-root/files/S45setupenv +++ b/general/package/openipc-nfs-root/files/S45setupenv @@ -6,8 +6,10 @@ dhcp_fetch_dns_ntp() { iface="$1" hostname="$2" - out="${3:-/tmp/udhcpc.env.$$}" - hook="/tmp/udhcpc-hook.$$" + state_dir="/var/run/udhcpc" + mkdir -p "$state_dir" + out="${3:-${state_dir}/udhcpc.env.$$}" + hook="${state_dir}/udhcpc-hook.$$" [ -n "$iface" ] || return 2 @@ -24,6 +26,10 @@ case "$1" in echo "router='$router'" echo "hostname='$hostname'" echo "domain='$domain'" + echo "search='$search'" + echo "lease='$lease'" + echo "event='$1'" + echo "updated_at='$(date '+%Y-%m-%d %H:%M:%S %Z')'" } > "$UDHCPC_CAPTURE" exit 0 ;; @@ -36,28 +42,30 @@ EOF chmod +x "$hook" || return 1 UDHCPC_CAPTURE="$out" \ - udhcpc -i "$iface" -n -q \ + udhcpc -i "$iface" -b -T 1 -t 5 \ -s "$hook" \ -O ntpsrv \ -O dns \ -O hostname \ -O domain \ - -F "$hostname" \ + -O search \ + -O lease \ + -x "hostname:${hostname}" \ + -p "/var/run/udhcpc.${iface}.pid" \ >/dev/null 2>&1 rc=$? - rm -f "$hook" [ $rc -eq 0 ] || return $rc [ -s "$out" ] || return 1 . "$out" - #rm -f "$out" DHCP_NTP="$ntpsrv" DHCP_DNS="$dns" DHCP_HOSTNAME="$hostname" DHCP_DOMAIN="$domain" + DHCP_SEARCH="$search" return 0 } @@ -114,21 +122,32 @@ setup_dns_ntp_hostname() { DEFAULT_HOSTNAME=$(cat /etc/hostname) HOSTNAME="${UBOOT_HOSTNAME:-${BUILD_HOSTNAME:-$DEFAULT_HOSTNAME}}" - # If kernel IP autoconf used DHCP try getting missing variable not exposed - if grep -q '^#PROTO: DHCP$' /proc/net/pnp; then + # If kernel used IP autoconf (DHCP) we must launch udhcpc + # to manage future leases. In addition we pass our choice for + # hostname and store the response to see if it was accepted. + if grep -q '^#PROTO: MANUAL$' /proc/net/pnp; then + IP_CIDR="$(ip -o -4 addr show dev eth0 scope global | awk '{print $4; exit}')" + echo "nfs-root: Using static IP $IP_CIDR" + else if ! dhcp_fetch_dns_ntp eth0 $HOSTNAME; then - echo "nfs-root: DHCP request failed." + echo "nfs-root: Warning: DHCP request failed." fi fi + + # Update hostname if DHCP server didn't honor the change + HOSTNAME="${DHCP_HOSTNAME:-$HOSTNAME}" NTP="${BUILD_NTP:-$DHCP_NTP}" DNS="${BUILD_DNS:-${UBOOT_DNS:-$DHCP_DNS}}" - HOSTNAME="${DHCP_HOSTNAME:-$HOSTNAME}" if [ -n "$NTP" ]; then echo "server ${NTP} iburst" > /etc/ntp.conf echo "nfs-root: Using ${NTP} for NTP." fi if [ -n "$DNS" ]; then - echo "nameserver ${DNS}" > /etc/resolv.conf + echo -n "" > /etc/resolv.conf + if [ -n "$DHCP_SEARCH" ]; then + echo "search $DHCP_SEARCH" >> /etc/resolv.conf + fi + echo "nameserver ${DNS}" >> /etc/resolv.conf echo "nfs-root: Using ${DNS} for DNS." fi @@ -141,7 +160,7 @@ setup_dns_ntp_hostname() { if [ -w /etc/hosts ]; then { echo "127.0.0.1 localhost" - echo "127.0.1.1 $HOSTNAME $FQDN" + echo "127.0.1.1 $FQDN $HOSTNAME" } > /etc/hosts fi diff --git a/general/package/openipc-nfs-root/files/openipc_ethaddr.c b/general/package/openipc-nfs-root/files/openipc_ethaddr.c index b50ac0c205..9bf57d9d16 100644 --- a/general/package/openipc-nfs-root/files/openipc_ethaddr.c +++ b/general/package/openipc-nfs-root/files/openipc_ethaddr.c @@ -1,22 +1,49 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include #include -#include +#include #include -// mac_pton moved in v6.4 -#if LINUX_VERSION_CODE >= KERNEL_VERSION(6,4,0) -#include -#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,8) -#include -#else -#error "openipc_ethaddr(): mac_pton not supported in kernel" -#endif - // Default u-boot env ethaddr on openipc platforms static const u8 default_mac[ETH_ALEN] = {0x00,0x00,0x23,0x34,0x45,0x66}; static u8 openipc_ethaddr[ETH_ALEN] __aligned(2); +static int openipc_hex_to_bin(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + return -1; +} + +static bool openipc_mac_pton(const char *str, u8 *mac) +{ + int i; + + /* XX:XX:XX:XX:XX:XX */ + if (strlen(str) != 3 * ETH_ALEN - 1) + return false; + + for (i = 0; i < ETH_ALEN; i++) { + int hi = openipc_hex_to_bin(str[i * 3]); + int lo = openipc_hex_to_bin(str[i * 3 + 1]); + + if (hi < 0 || lo < 0) + return false; + + if (i != ETH_ALEN - 1 && str[i * 3 + 2] != ':') + return false; + + mac[i] = (hi << 4) | lo; + } + + return true; +} + static int __init openipc_early_ethaddr(char *str) { u8 mac[ETH_ALEN] __aligned(2); @@ -24,7 +51,7 @@ static int __init openipc_early_ethaddr(char *str) if (!str) return 0; - if (!mac_pton(str, mac)) { + if (!openipc_mac_pton(str, mac)) { pr_warn("openipc_ethaddr: invalid ethaddr bootarg '%s'\n", str); return 0; } From 1266d364a0e86bbde21227923a879b1fa90a6dd3 Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Fri, 12 Jun 2026 23:43:45 -0600 Subject: [PATCH 11/19] network init/dhcpc should run on every boot for nfs root --- general/package/openipc-nfs-root/files/S45setupenv | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/general/package/openipc-nfs-root/files/S45setupenv b/general/package/openipc-nfs-root/files/S45setupenv index 5fe9a9311b..a037ba41d3 100644 --- a/general/package/openipc-nfs-root/files/S45setupenv +++ b/general/package/openipc-nfs-root/files/S45setupenv @@ -171,13 +171,15 @@ setup_dns_ntp_hostname() { case "$1" in start) - if grep -q 'root=/dev/nfs' /proc/cmdline && [ ! -f '/etc/firstboot' ]; then - setup_root_passwd + if grep -q 'root=/dev/nfs' /proc/cmdline; then setup_dns_ntp_hostname - if grep -Eq '(^| )ro( |$)' /proc/cmdline; then - setup_ssh_key + if [ ! -f '/etc/firstboot' ]; then + setup_root_passwd + if grep -Eq '(^| )ro( |$)' /proc/cmdline; then + setup_ssh_key + fi + touch /etc/firstboot fi - touch /etc/firstboot fi ;; From 0c5b28de0328e333fd4e6962428b69425c05fe2a Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Sun, 14 Jun 2026 14:36:22 -0600 Subject: [PATCH 12/19] Allow Makefile to see openipc.fragment for repack rule --- Makefile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 77b3f30404..02874ae663 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,11 @@ CONFIG := $(shell find br-ext-*/configs/*_defconfig | grep -m1 $(BOARD)) include $(CONFIG) endif -all: build repack timer +ifneq ($(filter repack,$(MAKECMDGOALS)),) +-include $(BR_CONF) +endif + +all: repack-final timer build: defconfig @$(BR_MAKE) all -j$(shell nproc) @@ -106,6 +110,9 @@ endif @$(BR_MAKE) sdk -j$(shell nproc) @$(call BUNDLE_SDK) +repack-final: build + @$(MAKE) --no-print-directory BOARD=$(BOARD) TARGET=$(TARGET) repack + repack: ifeq ($(BR2_PACKAGE_OPENIPC_NFS_ROOT),y) ifeq ($(BR2_OPENIPC_SOC_VENDOR),"rockchip") From 2b520c7d138d5cfdd186a40bc74e1949c9b83b85 Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Mon, 15 Jun 2026 09:13:11 -0600 Subject: [PATCH 13/19] Update make_nfsroot for prev Makefile commit --- contrib/make_nfsroot | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/contrib/make_nfsroot b/contrib/make_nfsroot index 0df7688b48..21cf1624b2 100755 --- a/contrib/make_nfsroot +++ b/contrib/make_nfsroot @@ -138,13 +138,7 @@ if [ -n "$PKGS" ] && [ -f "$PKGS" ]; then cat "$PKGS" >> "$FRAGMENT" fi -# Note: Must pass BR2_PACKAGE_OPENIPC_NFS_ROOT=y on cmdline -# only to satisfy Makefile repack rules. -set -- \ - BOARD="$BOARD" \ - BR2_PACKAGE_OPENIPC_NFS_ROOT=y - -make "$@" || exit 1 +make BOARD="$BOARD" || exit 1 KERNEL=$(find output/images/ -regextype posix-extended -iregex '.*/(uimage|zimage).*') ROOTFS=$(find output/images/ -name '*.tar') From 84b4844a9e8f32e9707918c55129b3606219e8de Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Mon, 15 Jun 2026 09:15:29 -0600 Subject: [PATCH 14/19] Cleanup S45setupenv, add msgs when fs defaults are used --- .../package/openipc-nfs-root/files/S45setupenv | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/general/package/openipc-nfs-root/files/S45setupenv b/general/package/openipc-nfs-root/files/S45setupenv index a037ba41d3..b83c25cf83 100644 --- a/general/package/openipc-nfs-root/files/S45setupenv +++ b/general/package/openipc-nfs-root/files/S45setupenv @@ -3,6 +3,8 @@ # Customize environment for NFS boot # +ETH_IFACE="eth0" + dhcp_fetch_dns_ntp() { iface="$1" hostname="$2" @@ -126,11 +128,13 @@ setup_dns_ntp_hostname() { # to manage future leases. In addition we pass our choice for # hostname and store the response to see if it was accepted. if grep -q '^#PROTO: MANUAL$' /proc/net/pnp; then - IP_CIDR="$(ip -o -4 addr show dev eth0 scope global | awk '{print $4; exit}')" + IP_CIDR="$(ip -o -4 addr show dev $ETH_IFACE scope global | awk '{print $4; exit}')" echo "nfs-root: Using static IP $IP_CIDR" else - if ! dhcp_fetch_dns_ntp eth0 $HOSTNAME; then - echo "nfs-root: Warning: DHCP request failed." + if dhcp_fetch_dns_ntp $ETH_IFACE $HOSTNAME; then + echo "nfs-root: Started dhcpc for lease renewal." + else + echo "nfs-root: Warning: dhcpc start failed." fi fi @@ -141,6 +145,8 @@ setup_dns_ntp_hostname() { if [ -n "$NTP" ]; then echo "server ${NTP} iburst" > /etc/ntp.conf echo "nfs-root: Using ${NTP} for NTP." + else + echo "nfs-root: Using system default for NTP." fi if [ -n "$DNS" ]; then echo -n "" > /etc/resolv.conf @@ -149,6 +155,8 @@ setup_dns_ntp_hostname() { fi echo "nameserver ${DNS}" >> /etc/resolv.conf echo "nfs-root: Using ${DNS} for DNS." + else + echo "nfs-root: Using system default for DNS." fi if [ -n "$DHCP_DOMAIN" ]; then @@ -166,7 +174,7 @@ setup_dns_ntp_hostname() { # Set the hostname /bin/hostname -F /etc/hostname >/dev/null 2>&1 - echo "nfs-root: Configured hostname to $HOSTNAME" + echo "nfs-root: Configured hostname to $HOSTNAME." } case "$1" in From 2ac483598b0eaa79e8b3b1d8f1cf146da9d78c68 Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Mon, 15 Jun 2026 09:53:41 -0600 Subject: [PATCH 15/19] Refactor networking, make lo/eth0 no-ops to prevent any process taking down nfs root --- .../openipc-nfs-root/files/S39netprofiles | 34 ++++++++++ general/package/openipc-nfs-root/files/eth0 | 5 ++ .../package/openipc-nfs-root/files/interfaces | 7 ++ .../openipc-nfs-root/openipc-nfs-root.mk | 3 + .../overlay/etc/init.d/S40network | 65 ------------------- 5 files changed, 49 insertions(+), 65 deletions(-) create mode 100644 general/package/openipc-nfs-root/files/S39netprofiles create mode 100644 general/package/openipc-nfs-root/files/eth0 create mode 100644 general/package/openipc-nfs-root/files/interfaces delete mode 100755 general/package/openipc-nfs-root/overlay/etc/init.d/S40network diff --git a/general/package/openipc-nfs-root/files/S39netprofiles b/general/package/openipc-nfs-root/files/S39netprofiles new file mode 100644 index 0000000000..ac04be9bd3 --- /dev/null +++ b/general/package/openipc-nfs-root/files/S39netprofiles @@ -0,0 +1,34 @@ +#!/bin/sh +# +# Install network profiles for nfs root +# + +install_net_profiles() { + # Backup original profiles + if [ ! -f /etc/network/profile/eth0 ]; then + cp /etc/network/interfaces.d/eth0 /etc/network/profiles/eth0 + fi + if [ ! -f /etc/network/profile/interfaces ]; then + cp /etc/network/interfaces /etc/network/profiles/interfaces + fi + + # Install no-op profiles for nfs root + cp /etc/network/profiles/eth0-nfs-root /etc/network/interfaces.d/eth0 + cp /etc/network/profiles/interfaces-nfs-root /etc/network/interfaces +} + +case "$1" in + start) + if grep -q 'root=/dev/nfs' /proc/cmdline; then + install_net_profiles + fi + ;; + + stop) + ;; + + *) + echo "Usage: $0 {start}" + exit 1 + ;; +esac diff --git a/general/package/openipc-nfs-root/files/eth0 b/general/package/openipc-nfs-root/files/eth0 new file mode 100644 index 0000000000..54c66f2eaa --- /dev/null +++ b/general/package/openipc-nfs-root/files/eth0 @@ -0,0 +1,5 @@ +# Symlink no-op profile for nfs root +# eth0 configured by early kernel (ipconfig.c) +iface eth0 inet manual + up true + down true diff --git a/general/package/openipc-nfs-root/files/interfaces b/general/package/openipc-nfs-root/files/interfaces new file mode 100644 index 0000000000..0c9562acaf --- /dev/null +++ b/general/package/openipc-nfs-root/files/interfaces @@ -0,0 +1,7 @@ +# Symlink no-op profile for nfs root +# lo configured by early kernel (ipconfig.c) +iface lo inet manual + up true + down true + +source-dir /etc/network/interfaces.d diff --git a/general/package/openipc-nfs-root/openipc-nfs-root.mk b/general/package/openipc-nfs-root/openipc-nfs-root.mk index 09b05cf102..0570bfcdcc 100644 --- a/general/package/openipc-nfs-root/openipc-nfs-root.mk +++ b/general/package/openipc-nfs-root/openipc-nfs-root.mk @@ -13,6 +13,9 @@ OPENIPC_NFS_ROOT_LICENSE = MIT define OPENIPC_NFS_ROOT_INSTALL_TARGET_CMDS $(INSTALL) -D -m 0755 $(@D)/files/S45setupenv $(TARGET_DIR)/etc/init.d/S45setupenv + $(INSTALL) -D -m 0755 $(@D)/files/S39netprofiles $(TARGET_DIR)/etc/init.d/S39netprofiles + $(INSTALL) -D -m 0444 $(@D)/files/interfaces $(TARGET_DIR)/etc/network/profiles/interfaces-nfs-root + $(INSTALL) -D -m 0444 $(@D)/files/eth0 $(TARGET_DIR)/etc/network/profiles/eth0-nfs-root endef $(eval $(generic-package)) diff --git a/general/package/openipc-nfs-root/overlay/etc/init.d/S40network b/general/package/openipc-nfs-root/overlay/etc/init.d/S40network deleted file mode 100755 index 3763075ff0..0000000000 --- a/general/package/openipc-nfs-root/overlay/etc/init.d/S40network +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/sh -dev=$(fw_printenv -n wlandev) -mac=$(fw_printenv -n wlanmac) -net=$(fw_printenv -n netaddr_fallback) -eth=$(fw_printenv -n ethaddr) - -set_wireless() { - path=/etc/wireless - if $path/usb "$dev" || $path/sdio "$dev"; then - [ -n "$mac" ] && ip link set dev wlan0 address "$mac" - ifup wlan0 - elif $path/modem "$dev"; then - ifup usb0 - ifup eth1 - fi - [ -e /sys/class/net/eth0 ] && ifconfig eth0 "${net:-192.168.2.10}" hw ether "${eth:-00:00:23:34:45:66}" up -} - -is_nfs_root() { - grep -q 'root=/dev/nfs' /proc/cmdline -} - -start() { - echo "Starting network..." - if [ -n "$dev" ]; then - ifup lo - set_wireless - else - if is_nfs_root; then - echo "nfs-root: NFS root detected, leaving eth0/lo as configured by kernel" - else - ifup lo - ifup eth0 - fi - fi -} - -stop() { - echo "Stopping network..." - ifdown lo - ifdown -f wlan0 - ifdown -f usb0 - ifdown -f eth1 - if is_nfs_root; then - echo "NFS root detected, leaving eth0 up during shutdown" - else - ifdown -f eth0 - fi -} - -case "$1" in - start|stop) - $1 - ;; - - restart|reload) - stop - start - ;; - - *) - echo "Usage: $0 {start|stop|restart|reload}" - exit 1 - ;; -esac From cdaf50e75a965652db875cf4fcb5fe361c02b94b Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Mon, 15 Jun 2026 09:57:52 -0600 Subject: [PATCH 16/19] Add sed comment in post-build-hook --- general/package/openipc-nfs-root/post-build-hook.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/general/package/openipc-nfs-root/post-build-hook.sh b/general/package/openipc-nfs-root/post-build-hook.sh index e6508446fe..f8093b3717 100755 --- a/general/package/openipc-nfs-root/post-build-hook.sh +++ b/general/package/openipc-nfs-root/post-build-hook.sh @@ -106,6 +106,7 @@ fi OPENIPC_NFS_ROOT_TZ_POSIX="$(extract_posix_tz "${OPENIPC_NFS_ROOT_TIMEZONE}")" +# Note: delimiter '|' chosen over '/' due to password hash collisions for OPENIPC_NFS_ROOT_PASSWD if [ -f "${TARGET_DIR}/init" ]; then sed -i "s|@TMPFS_SIZE@|${OPENIPC_NFS_ROOT_TMPFS_SIZE}|" "${TARGET_DIR}/init" fi From a92d85b6feddcfbacc2bb8a99f037aecaf5746e9 Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Mon, 15 Jun 2026 10:38:18 -0600 Subject: [PATCH 17/19] Merge /init from package overlay to common overlay --- general/overlay/init | 16 +++- general/package/openipc-nfs-root/overlay/init | 73 ------------------- 2 files changed, 15 insertions(+), 74 deletions(-) delete mode 100755 general/package/openipc-nfs-root/overlay/init diff --git a/general/overlay/init b/general/overlay/init index c25f4075ec..615f62c907 100755 --- a/general/overlay/init +++ b/general/overlay/init @@ -10,7 +10,12 @@ trap on_exit EXIT mount -t proc proc /proc || exit 1 grep -q overlay /proc/filesystems || exit 1 -if ! grep -q 'root=.*nfs\|mmcblk\|ram' /proc/cmdline; then +is_flash_root=true +is_nfs_root=false +grep -q 'root=.*nfs\|mmcblk\|ram' /proc/cmdline && is_flash_root=false +grep -q 'root=.*nfs' /proc/cmdline && is_nfs_root=true + +if $is_flash_root; then if grep -q ubifs /proc/cmdline; then mount -t ubifs ubi0:rootfs_data /overlay else @@ -27,7 +32,16 @@ if ! grep -q 'root=.*nfs\|mmcblk\|ram' /proc/cmdline; then fi fi fi +elif $is_nfs_root && grep -q ' ro ' /proc/cmdline; then + TMPFS_SIZE='@TMPFS_SIZE@' + TMPFS_SIZE="${TMPFS_SIZE:-16M}" + if ! mount -t tmpfs -o mode=0755,size=${TMPFS_SIZE} tmpfs /overlay; then + echo "Cannot mount tmpfs overlay." + exit 1 + fi +fi +if $is_flash_root || $is_nfs_root; then if grep -q overlayfs /proc/filesystems; then if ! mount -t overlayfs overlayfs -o lowerdir=/,upperdir=/overlay,ro /mnt; then umount /overlay diff --git a/general/package/openipc-nfs-root/overlay/init b/general/package/openipc-nfs-root/overlay/init deleted file mode 100755 index 351610d1b4..0000000000 --- a/general/package/openipc-nfs-root/overlay/init +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/sh - -on_exit() { - mountpoint -q /proc && umount /proc - exec /sbin/init "$*" -} - -trap on_exit EXIT - -mount -t proc proc /proc || exit 1 -grep -q overlay /proc/filesystems || exit 1 - -if ! grep -q 'root=.*nfs\|mmcblk\|ram' /proc/cmdline; then - if grep -q ubifs /proc/cmdline; then - mount -t ubifs ubi0:rootfs_data /overlay - else - mtdblkdev=$(awk -F ':' '/rootfs_data/ {print $1}' /proc/mtd | sed 's/mtd/mtdblock/') - mtdchrdev=$(grep 'rootfs_data' /proc/mtd | cut -d: -f1) - if ! mount -t jffs2 /dev/$mtdblkdev /overlay || dmesg | grep -q "jffs2.*: Magic bitmask.*not found"; then - mountpoint -q /overlay && umount /overlay - echo "Formatting flash..." - grep -q 'nand' /proc/cmdline || jffs2="-j" - flash_eraseall $jffs2 /dev/$mtdchrdev - if ! mount -t jffs2 /dev/$mtdblkdev /overlay && ! mount -t tmpfs tmpfs /overlay; then - echo "Cannot mount overlay." - exit 1 - fi - fi - fi - - if grep -q overlayfs /proc/filesystems; then - if ! mount -t overlayfs overlayfs -o lowerdir=/,upperdir=/overlay,ro /mnt; then - umount /overlay - exit 1 - fi - else - overlay_rootdir=/overlay/root - overlay_workdir=/overlay/work - mkdir -p $overlay_rootdir $overlay_workdir - if ! mount -t overlay overlay -o lowerdir=/,upperdir=$overlay_rootdir,workdir=$overlay_workdir /mnt; then - umount /overlay - exit 1 - fi - fi - - pivot_root /mnt /mnt/rom - mount -o noatime,move /rom/proc /proc - mount -o noatime,move /rom/dev /dev - mount -o noatime,move /rom/overlay /overlay - -elif grep -q 'root=.*nfs' /proc/cmdline; then - if grep -q ' ro ' /proc/cmdline; then - if ! mount -t tmpfs -o mode=0755,size=@TMPFS_SIZE@ tmpfs /overlay; then - echo "Cannot mount tmpfs overlay." - exit 1 - fi - - overlay_rootdir=/overlay/root - overlay_workdir=/overlay/work - mkdir -p $overlay_rootdir $overlay_workdir - if ! mount -t overlay overlay -o lowerdir=/,upperdir=$overlay_rootdir,workdir=$overlay_workdir /mnt; then - umount /overlay - exit 1 - fi - - pivot_root /mnt /mnt/rom - mount -o noatime,move /rom/proc /proc - mount -o noatime,move /rom/dev /dev - mount -o noatime,move /rom/overlay /overlay - else - echo "Warning: Only one device per NFS rw root partition!" - fi -fi From 144231e8edcf98ec3f545f4a51311436435086ba Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Mon, 15 Jun 2026 11:17:19 -0600 Subject: [PATCH 18/19] Update kernel patch to support missing eth_hw_addr_set --- ...pconfig-set-platform-mac-before-open.patch | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/general/package/openipc-nfs-root/kernel-patches/0001-ipconfig-set-platform-mac-before-open.patch b/general/package/openipc-nfs-root/kernel-patches/0001-ipconfig-set-platform-mac-before-open.patch index e964a44ab7..126263e53f 100644 --- a/general/package/openipc-nfs-root/kernel-patches/0001-ipconfig-set-platform-mac-before-open.patch +++ b/general/package/openipc-nfs-root/kernel-patches/0001-ipconfig-set-platform-mac-before-open.patch @@ -1,22 +1,29 @@ --- a/net/ipv4/ipconfig.c +++ b/net/ipv4/ipconfig.c -@@ -41,6 +41,7 @@ +@@ -41,6 +41,8 @@ #include #include #include +#include ++#include #include #include #include -@@ -65,6 +66,7 @@ - #include - #include - -+ - #if defined(CONFIG_IP_PNP_DHCP) - #define IPCONFIG_DHCP +@@ -173,6 +175,13 @@ + static int ic_dhcp_msgtype __initdata; /* DHCP msg type received */ #endif -@@ -205,6 +207,8 @@ + ++// Provide correct copy mechanism for older kernel ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 17, 0) ++static inline void eth_hw_addr_set(struct net_device *dev, const u8 *addr) ++{ ++ ether_addr_copy(dev->dev_addr, addr); ++} ++#endif + + /* + * Network devices +@@ -205,6 +214,8 @@ struct net_device *dev; unsigned short oflags; unsigned long start, next_msg; @@ -25,7 +32,7 @@ last = &ic_first_dev; rtnl_lock(); -@@ -230,6 +234,18 @@ +@@ -230,6 +241,18 @@ able &= ic_proto_enabled; if (ic_proto_enabled && !able) continue; @@ -36,7 +43,7 @@ + (dev->type == ARPHRD_ETHER) && + dev->dev.parent && + !eth_platform_get_mac_address(dev->dev.parent, mac)) { -+ ether_addr_copy(dev->dev_addr, mac); ++ eth_hw_addr_set(dev, mac); + first_mac_done = true; + pr_info("openipc: Overriding %s MAC from bootargs/DT %pM\n", + dev->name, dev->dev_addr); From b51a2272cbd4456b1d68675e3cd8fe39d6632911 Mon Sep 17 00:00:00 2001 From: Tiny Labs Inc Date: Mon, 15 Jun 2026 11:20:27 -0600 Subject: [PATCH 19/19] Add note about insecure default password in buildroot config --- general/package/openipc-nfs-root/Config.in | 1 + 1 file changed, 1 insertion(+) diff --git a/general/package/openipc-nfs-root/Config.in b/general/package/openipc-nfs-root/Config.in index 54c59fdcf7..2e22f7b8ae 100644 --- a/general/package/openipc-nfs-root/Config.in +++ b/general/package/openipc-nfs-root/Config.in @@ -27,6 +27,7 @@ config BR2_PACKAGE_OPENIPC_NFS_ROOT_PASSWD help Root password to be set during boot. Useful for immutable rootfs. Can be plaintext or a password hash. + Note: Should set password for production env. Default is 12345 config BR2_PACKAGE_OPENIPC_NFS_ROOT_TIMEZONE string "IANA timezone"