From 1b2cb3359b2ad898fd6a9d4f85bb403ef6ea75b5 Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Sat, 29 Aug 2026 08:40:53 +0200 Subject: [PATCH 1/4] firstrun: set per-model hostname on the NanoPi 6-series unified image PR #10442 introduces a single FriendlyElec RK3588 image that boots on NanoPi R6S/R6C/M6/M6V2 and NanoPC-T6 variants, with the unified U-Boot detecting the board via SARADC and exporting nanopi6_model= on the kernel command line. Mirror the existing rpi4b first-boot hostname adjustment for this image: in armbian-firstrun, when nanopi6_model= is present on the cmdline (only these unified images have it), map the model to the matching per-board hostname (nanopi-r6s, nanopi-r6c, nanopi-m6, nanopi-m6v2, nanopct6, nanopct6-lts, nanopct6-lts-plus) and apply it via /etc/hosts + hostnamectl so the unified image is indistinguishable from a dedicated per-board build. Guarded on the cmdline arg rather than a single ${BOARD} (several boards source the common config and can build the image); only rewrites when the mapped name is non-empty and differs from the current hostname, so an unknown model or a non-unified U-Boot leaves the build-time default in place. Runs once via the self-disabling armbian-firstrun service. Signed-off-by: Igor Pecovnik --- .../common/usr/lib/armbian/armbian-firstrun | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/bsp/common/usr/lib/armbian/armbian-firstrun b/packages/bsp/common/usr/lib/armbian/armbian-firstrun index cac05fe02a8e..978016b3eb54 100755 --- a/packages/bsp/common/usr/lib/armbian/armbian-firstrun +++ b/packages/bsp/common/usr/lib/armbian/armbian-firstrun @@ -142,6 +142,30 @@ case "$1" in hostnamectl set-hostname "$NEW_HOSTNAME" fi + # adjust hostname for the FriendlyElec NanoPi 6-series unified image. + # A single image boots on several boards; the unified U-Boot detects the + # model via SARADC and exports nanopi6_model= on the kernel command + # line, which is only present on these images. + if grep -q 'nanopi6_model=' /proc/cmdline; then + nanopi6_model=$(sed -n 's/.*nanopi6_model=\([^ ]*\).*/\1/p' /proc/cmdline) + NEW_HOSTNAME="" + case "$nanopi6_model" in + r6s) NEW_HOSTNAME="nanopi-r6s" ;; + r6c) NEW_HOSTNAME="nanopi-r6c" ;; + m6) NEW_HOSTNAME="nanopi-m6" ;; + m6v2) NEW_HOSTNAME="nanopi-m6v2" ;; + t6) NEW_HOSTNAME="nanopct6" ;; + t6-lts) NEW_HOSTNAME="nanopct6-lts" ;; + t6-lts-plus) NEW_HOSTNAME="nanopct6-lts-plus" ;; + esac + if [[ -n "$NEW_HOSTNAME" && "$NEW_HOSTNAME" != "$(hostname)" ]]; then + # Update /etc/hosts to reflect the new hostname + sed -i "s/$(hostname)/$NEW_HOSTNAME/g" /etc/hosts + # Change the hostname + hostnamectl set-hostname "$NEW_HOSTNAME" + fi + fi + # Remove KDE Neon base files upgrade pin [[ -f /etc/apt/preferences.d/99-neon-base-files ]] && rm -f /etc/apt/preferences.d/99-neon-base-files From 7b49734d780a4206d018dfa00a918fcd02256da5 Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Sat, 29 Aug 2026 08:52:51 +0200 Subject: [PATCH 2/4] firstrun: harden hostname handling for rpi and NanoPi 6-series Address review feedback and factor the shared logic into one helper used by both the rpi4b and the NanoPi 6-series first-boot hostname blocks: - set_new_hostname(): applies the rename with `hostnamectl` FIRST and edits /etc/hosts only on success (so a failed rename can't leave the hosts file ahead of the running hostname), replaces the old name only where it stands as a whole word on the 127.0.1.1/::1 records (escaped), and no-ops on an empty or unchanged target. - rpi4b: this fixes a latent bug -- a base Pi 4B matches none of the model arms, so NEW_HOSTNAME was empty and the old code ran `sed s/rpi4b//g` + `hostnamectl set-hostname ""`, wiping the hostname. The helper's empty guard now leaves the default rpi4b in place. - NanoPi 6-series: parse the kernel cmdline word-by-word so nanopi6_model= only matches as its own token, never embedded inside another argument. Signed-off-by: Igor Pecovnik --- .../common/usr/lib/armbian/armbian-firstrun | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/packages/bsp/common/usr/lib/armbian/armbian-firstrun b/packages/bsp/common/usr/lib/armbian/armbian-firstrun index 978016b3eb54..0ec34d371a1f 100755 --- a/packages/bsp/common/usr/lib/armbian/armbian-firstrun +++ b/packages/bsp/common/usr/lib/armbian/armbian-firstrun @@ -12,6 +12,21 @@ . /lib/lsb/init-functions . /usr/lib/armbian/armbian-common +# Apply a new system hostname once and mirror it in /etc/hosts. Replaces the +# previous hostname only where it stands as a whole word (the 127.0.1.1 and ::1 +# records written at build time), leaving loopback names, aliases and comments +# untouched. No-op when the target is empty or already current; the /etc/hosts +# edit runs only after the rename itself succeeds. +set_new_hostname() { + local new="$1" old esc_old esc_new + old="$(hostname)" + [[ -z "$new" || "$new" == "$old" ]] && return 0 + hostnamectl set-hostname "$new" || return 1 + esc_old=$(printf '%s' "$old" | sed 's#[][\\.*^$/]#\\&#g') + esc_new=$(printf '%s' "$new" | sed 's#[\\/&]#\\&#g') + sed -i "s/\b${esc_old}\b/${esc_new}/g" /etc/hosts +} + case "$1" in start) @@ -132,22 +147,27 @@ case "$1" in # adjust hostname for rpi's if [[ "${BOARD}" == rpi4b && "$(hostname)" == rpi4b ]]; then BOARD_NAME=$(cat /proc/device-tree/model | tr '\0' '\n' | sed -E 's/ Rev [0-9.]+$//') + NEW_HOSTNAME="" [[ "$BOARD_NAME" == *"Zero 2 W"* ]] && NEW_HOSTNAME="rpizero2w" [[ "$BOARD_NAME" == *"Pi 5 Model B"* ]] && NEW_HOSTNAME="rpi5b" [[ "$BOARD_NAME" == *"Pi 3 Model B"* ]] && NEW_HOSTNAME="rpi3b" [[ "$BOARD_NAME" == *"Pi 400"* ]] && NEW_HOSTNAME="rpi400" - # Update /etc/hosts to reflect the new hostname - sed -i "s/$(hostname)/$NEW_HOSTNAME/g" /etc/hosts - # Change the hostname - hostnamectl set-hostname "$NEW_HOSTNAME" + # Leaves the default rpi4b hostname in place when no model matched. + set_new_hostname "$NEW_HOSTNAME" fi # adjust hostname for the FriendlyElec NanoPi 6-series unified image. # A single image boots on several boards; the unified U-Boot detects the - # model via SARADC and exports nanopi6_model= on the kernel command - # line, which is only present on these images. - if grep -q 'nanopi6_model=' /proc/cmdline; then - nanopi6_model=$(sed -n 's/.*nanopi6_model=\([^ ]*\).*/\1/p' /proc/cmdline) + # model via SARADC and exports nanopi6_model= as its own token on the + # kernel command line, present only on these images. Parse it word-by-word so + # it can never match text embedded inside another kernel argument. + nanopi6_model="" + for arg in $(cat /proc/cmdline); do + case "$arg" in + nanopi6_model=*) nanopi6_model="${arg#nanopi6_model=}"; break ;; + esac + done + if [[ -n "$nanopi6_model" ]]; then NEW_HOSTNAME="" case "$nanopi6_model" in r6s) NEW_HOSTNAME="nanopi-r6s" ;; @@ -158,12 +178,7 @@ case "$1" in t6-lts) NEW_HOSTNAME="nanopct6-lts" ;; t6-lts-plus) NEW_HOSTNAME="nanopct6-lts-plus" ;; esac - if [[ -n "$NEW_HOSTNAME" && "$NEW_HOSTNAME" != "$(hostname)" ]]; then - # Update /etc/hosts to reflect the new hostname - sed -i "s/$(hostname)/$NEW_HOSTNAME/g" /etc/hosts - # Change the hostname - hostnamectl set-hostname "$NEW_HOSTNAME" - fi + set_new_hostname "$NEW_HOSTNAME" fi # Remove KDE Neon base files upgrade pin From 148ccbb74decccdefb8a2cbe9511371f20c6ce43 Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Sat, 29 Aug 2026 09:02:11 +0200 Subject: [PATCH 3/4] firstrun: cover the full 64-bit Raspberry Pi lineup The single arm64 rpi4b image boots every 64-bit Pi, but the first-boot hostname map only recognised four models; the rest kept the generic rpi4b hostname. Expand it to the whole 64-bit lineup (Pi 3B+ onward) using the authoritative model strings from the upstream Raspberry Pi kernel device trees, and switch from sequential [[ ]] tests to an ordered case so more specific names win (e.g. "Model B+" over "Model B", "Pi 500" over "Pi 5", "Compute Module 4S" over "Compute Module 4"). Covered: 3A+, 3B+, 3B, 4B, 400, 5, 500, Zero 2 W, and Compute Module 3/4/4S/5/5 Lite (Compute Modules matched by SoM substring since the reported string comes from the carrier board). 32-bit-only and pre-3B models are intentionally excluded and keep the default hostname. Signed-off-by: Igor Pecovnik --- .../common/usr/lib/armbian/armbian-firstrun | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/bsp/common/usr/lib/armbian/armbian-firstrun b/packages/bsp/common/usr/lib/armbian/armbian-firstrun index 0ec34d371a1f..43d94a80bded 100755 --- a/packages/bsp/common/usr/lib/armbian/armbian-firstrun +++ b/packages/bsp/common/usr/lib/armbian/armbian-firstrun @@ -144,14 +144,30 @@ case "$1" in [[ "${LINUXFAMILY}" == meson64 ]] && set_fixed_mac [[ "${BOARD}" == nanopi-r6* ]] && set_fixed_mac - # adjust hostname for rpi's + # adjust hostname for rpi's: the single arm64 image boots the whole Pi 3B+ + # onward lineup, so pick a per-model hostname from the board model string. + # Ordered most-specific-first so e.g. "Model B Plus" wins over "Model B". if [[ "${BOARD}" == rpi4b && "$(hostname)" == rpi4b ]]; then BOARD_NAME=$(cat /proc/device-tree/model | tr '\0' '\n' | sed -E 's/ Rev [0-9.]+$//') NEW_HOSTNAME="" - [[ "$BOARD_NAME" == *"Zero 2 W"* ]] && NEW_HOSTNAME="rpizero2w" - [[ "$BOARD_NAME" == *"Pi 5 Model B"* ]] && NEW_HOSTNAME="rpi5b" - [[ "$BOARD_NAME" == *"Pi 3 Model B"* ]] && NEW_HOSTNAME="rpi3b" - [[ "$BOARD_NAME" == *"Pi 400"* ]] && NEW_HOSTNAME="rpi400" + # Model strings are the upstream RPi kernel DT values (arm64 / 64-bit + # lineup only). Compute Modules report their carrier-board string, so a + # substring of the SoM name is matched. + case "$BOARD_NAME" in + *"Compute Module 3"*) NEW_HOSTNAME="rpicm3" ;; + *"Compute Module 4S"*) NEW_HOSTNAME="rpicm4s" ;; + *"Compute Module 4"*) NEW_HOSTNAME="rpicm4" ;; + *"Compute Module 5 Lite"*) NEW_HOSTNAME="rpicm5lite" ;; + *"Compute Module 5"*) NEW_HOSTNAME="rpicm5" ;; + *"Zero 2 W"*) NEW_HOSTNAME="rpizero2w" ;; + *"Pi 3 Model A+"*) NEW_HOSTNAME="rpi3aplus" ;; + *"Pi 3 Model B+"*) NEW_HOSTNAME="rpi3bplus" ;; + *"Pi 3 Model B"*) NEW_HOSTNAME="rpi3b" ;; + *"Pi 400"*) NEW_HOSTNAME="rpi400" ;; + *"Pi 4 Model B"*) NEW_HOSTNAME="rpi4b" ;; + *"Pi 500"*) NEW_HOSTNAME="rpi500" ;; + *"Pi 5"*) NEW_HOSTNAME="rpi5b" ;; + esac # Leaves the default rpi4b hostname in place when no model matched. set_new_hostname "$NEW_HOSTNAME" fi From d7de28d7a47b70aa6e4591d2cd609ae88e90a311 Mon Sep 17 00:00:00 2001 From: Igor Pecovnik Date: Wed, 23 Sep 2026 13:50:01 +0200 Subject: [PATCH 4/4] firstrun: reconcile /etc/hosts on retry, don't disable on failure Two CodeRabbit findings from PR #10554, still open after the earlier round (token-boundary matching, /etc/hosts scope, and hostnamectl- before-hosts ordering) was already fixed in 283ef6c: - set_new_hostname() early-returned when new==old, which also skipped the /etc/hosts reconciliation. That's wrong for a retry: a prior run can have set the hostname successfully via hostnamectl but then failed the /etc/hosts sed (or been interrupted between the two), and the next boot enters with new==old - the early return then left the build-generated 127.0.1.1/::1 records stale forever instead of repairing them. Now the hostnamectl call is skipped when new==old (nothing to change there), but the /etc/hosts reconciliation always runs, matched by anchored position (the 127.0.1.1 line, and the token right after "localhost" on the ::1 line - Armbian's own build-time /etc/hosts template, see distro-agnostic.sh) rather than by literal old-hostname text. - Both call sites (rpi4b and NanoPi 6-series) ignored set_new_hostname()'s return value, so a transient hostnamectl/sed failure still fell through to `systemctl disable armbian-firstrun` - permanently stranding the board on the wrong hostname instead of retrying on the next boot. Both now `|| exit 1` before that point. Verified the /etc/hosts reconciliation standalone against Armbian's actual build-time template (distro-agnostic.sh's here-doc): a normal rename updates both the 127.0.1.1 and ::1 hostname slots and leaves ip6-localhost/ip6-loopback and the other aliases untouched, and a retry with new==old still repairs stale records without re-calling hostnamectl. Co-authored-by: Muhammed Efe Cetin Signed-off-by: Igor Pecovnik --- .../common/usr/lib/armbian/armbian-firstrun | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/packages/bsp/common/usr/lib/armbian/armbian-firstrun b/packages/bsp/common/usr/lib/armbian/armbian-firstrun index 43d94a80bded..67ff64517439 100755 --- a/packages/bsp/common/usr/lib/armbian/armbian-firstrun +++ b/packages/bsp/common/usr/lib/armbian/armbian-firstrun @@ -12,19 +12,25 @@ . /lib/lsb/init-functions . /usr/lib/armbian/armbian-common -# Apply a new system hostname once and mirror it in /etc/hosts. Replaces the -# previous hostname only where it stands as a whole word (the 127.0.1.1 and ::1 -# records written at build time), leaving loopback names, aliases and comments -# untouched. No-op when the target is empty or already current; the /etc/hosts -# edit runs only after the rename itself succeeds. +# Apply a new system hostname once and reconcile the 127.0.1.1/::1 records in +# /etc/hosts written at build time (loopback names, aliases and comments are +# left untouched). No-op only when the target is empty. The /etc/hosts +# reconciliation runs even when new==old (skipping only the hostnamectl call +# itself): a prior run can have set the hostname successfully but failed the +# /etc/hosts edit, and a retry must still repair those records rather than +# short-circuit on "already current". set_new_hostname() { - local new="$1" old esc_old esc_new + local new="$1" old esc_new old="$(hostname)" - [[ -z "$new" || "$new" == "$old" ]] && return 0 - hostnamectl set-hostname "$new" || return 1 - esc_old=$(printf '%s' "$old" | sed 's#[][\\.*^$/]#\\&#g') + [[ -z "$new" ]] && return 0 + if [[ "$new" != "$old" ]]; then + hostnamectl set-hostname "$new" || return 1 + fi esc_new=$(printf '%s' "$new" | sed 's#[\\/&]#\\&#g') - sed -i "s/\b${esc_old}\b/${esc_new}/g" /etc/hosts + sed -i -E \ + -e "s|^(127\.0\.1\.1[[:space:]]+)[^[:space:]#]+|\1${esc_new}|" \ + -e "s|^(::1[[:space:]]+localhost[[:space:]]+)[^[:space:]#]+|\1${esc_new}|" \ + /etc/hosts } case "$1" in @@ -169,7 +175,10 @@ case "$1" in *"Pi 5"*) NEW_HOSTNAME="rpi5b" ;; esac # Leaves the default rpi4b hostname in place when no model matched. - set_new_hostname "$NEW_HOSTNAME" + # A failure here must not reach the service-disable below - if it did, a + # transient hostnamectl/sed failure would strand the board on the wrong + # hostname forever instead of retrying on the next boot. + set_new_hostname "$NEW_HOSTNAME" || exit 1 fi # adjust hostname for the FriendlyElec NanoPi 6-series unified image. @@ -194,7 +203,9 @@ case "$1" in t6-lts) NEW_HOSTNAME="nanopct6-lts" ;; t6-lts-plus) NEW_HOSTNAME="nanopct6-lts-plus" ;; esac - set_new_hostname "$NEW_HOSTNAME" + # Same reasoning as the rpi4b case above: don't let a failed hostname + # update reach the service-disable, or first-run never gets to retry. + set_new_hostname "$NEW_HOSTNAME" || exit 1 fi # Remove KDE Neon base files upgrade pin