Skip to content

[1.4] Fix Navigator not being detected on the first boot of a Bookworm install - #4232

Open
joaoantoniocardoso wants to merge 12 commits into
bluerobotics:1.4-devfrom
joaoantoniocardoso:fix/1.4-navigator-overlays-bookworm-boot-config
Open

[1.4] Fix Navigator not being detected on the first boot of a Bookworm install#4232
joaoantoniocardoso wants to merge 12 commits into
bluerobotics:1.4-devfrom
joaoantoniocardoso:fix/1.4-navigator-overlays-bookworm-boot-config

Conversation

@joaoantoniocardoso

@joaoantoniocardoso joaoantoniocardoso commented Aug 21, 2026

Copy link
Copy Markdown
Member

Fixes the Pi4 Bookworm first-boot Navigator detection failure reported in #4230, the underlying reason the startup patches could not recover from it on any board, and the damage it left on devices that already ran the affected release.

Two independent bugs

The install scripts wrote to the wrong file. Bookworm moved the boot partition to /boot/firmware and left plain text stubs at /boot/config.txt and /boot/cmdline.txt. install/boards/bcm_27xx.sh hardcoded the old paths, so a Pi4 installed over Bookworm got its whole Navigator overlay set appended to a file the firmware never reads. Only those two are real files: /boot/overlays is a symlink into the boot partition, so the compiled overlay did land there, and 1.4.4-beta.20 ships overlays/spi0-led.dtbo next to a stock config.txt with nothing to load it. bcm_2712.sh (Pi5) already used /boot/firmware for its writes, which is why only Pi4 was reported, but it too reached the overlays directory through that symlink, which no package owns. bcm_28xx.sh had the same problem as the Pi4 script. All three now resolve the boot partition the same way.

The startup patch that should have repaired that was a no-op on its first run. boot_config_get_or_append_section() appended a missing board section as a single "\n[pi4]" list entry. The section lookup uses re.match, which anchors at offset 0 and therefore never matches a string starting with a newline, so every call appended another header. The start index was also set one past the end of the list, which made the section_start < i < section_end guard in the conflict filter unsatisfiable, so each configuration line was deleted immediately after being inserted.

A first pass over a stock config.txt produced 30 empty [pi4] sections and zero overlays. The overlays only landed on a later boot, once the saved file had been split back into lines, and since BOOT_LOOP_DETECTOR skips the boot right after a reboot, that took three boots. This part was never Pi4-specific; Pi5 was only saved by its install script writing the right file.

Repairing devices that already ran the affected release

Every device that booted 1.4.4-beta.20 carries roughly 30 stray [pi4] headers. Two consequences, both fixed here:

  • Only the first section is ever configured, so the strays stay forever, and any configuration the user put under one of them silently stops applying. boot_config_merge_duplicated_sections() now folds them into a single section on the next boot, keeping every user line in the order the firmware applies it, and leaves a config.txt with one section byte-identical so it does not ask for a needless reboot. Order matters because a duplicate header reopens the same filter rather than starting a section only the first of which is read: hoisting the strays above the survivor's own lines would have flipped the effective value of any key set under both headers, on exactly the devices this repairs.
  • Re-running install.sh on such a device removed its Navigator configuration and reported success. line_number=$(grep -n "\[pi4\]" ... ) returns one line per match, so $line_number became multi-line, every insertion sed failed with unknown command, and the deletion loop had already stripped the overlays. It now takes the first match only.

Making the failures loud

Both halves of the original bug shared a failure mode: writing to the wrong place still exits 0.

  • The boot-partition probe refuses to guess. /boot/firmware/config.txt wins; /boot/config.txt is used only when it is a real config and not the Bookworm stub (identified by DO NOT EDIT THIS FILE being its whole first line); otherwise the script prints why and exits 1. A Pi5 is newer than Bullseye, so bcm_2712.sh accepts nothing but /boot/firmware: boot files at /boot on that board are never its boot partition. Previously an unmounted boot partition silently configured a stub, and for the Pi5 that was a regression in failure mode, since hardcoding /boot/firmware at least made sed fail.
  • bcm_27xx.sh and bcm_28xx.sh now set -e, which bcm_2712.sh already had. Every command in both was audited and both were run end to end against fake roots to confirm nothing that legitimately fails now aborts an install.
  • The runtime repair path refuses the same stub. blueos_startup_update.py located config.txt/cmdline.txt with a first-match find, so on a Bookworm host whose boot partition was not mounted it settled on the /boot stub, wrote the whole board section into a file the firmware never reads, rebooted, and from then on found its own work in place and converged into a permanently unconfigured state with no error. It now checks the same DO NOT EDIT THIS FILE marker the install scripts key on and, on the stub, logs and skips the config patches through the existing config_file is None guards rather than writing nowhere. The marker has to be the file's whole first line, so a real config.txt that carries the phrase in a comment keeps being patched: refusing one would leave a correctly mounted board with no overlays, no cgroups and no OTG ethernet while still reporting success. It reads that line with run_command rather than load_file, because this runs before the run_command_is_working() check and load_file raises when the host connection is not up yet, which would take every patch of the boot down with it, including the ones this PR is not about.

Other bugs found on the way

  • update_dwc2() picked its section as "pi4" if get_cpu_type() == CpuType.PI4 else "pi5", so every board that is not a Pi4 got a [pi5] section, and a board filter the firmware does not recognise is applied rather than ignored. Nothing reached a Pi3 in the field, since main() only runs this patch for a Pi4 and a Pi5, but a board check that once went wrong is exactly why its sibling revert_update_dwc2() exists. It now handles Pi4 and Pi5 explicitly and refuses anything else.
  • boot_cmdline_add_modules() used a falsy index check, so when modules-load= was the first word of cmdline.txt the merged line was never written and a second, dead modules-load= was appended instead.
  • Configuration values were interpolated into regexes unescaped. The Pi5 list holds both dtoverlay=i2c3-pi5,baudrate=400000 and dtoverlay=i2c3-pi5.baudrate=400000, where the . matches the comma variant, so keeping both spellings depended on undocumented list order.
  • boot_config_remove_section() on an absent section appended the header, deleted it again, and reported a change, requesting a reboot for a section that never existed.

Guardrails

core/tools/blueos_startup_update/test_blueos_startup_update.py (54 tests) runs the patches against the authentic stock config.txt and cmdline.txt of both the Bullseye and the Bookworm base images, for every board we configure, and pins:

  • the resulting board section matches exactly what the image install script writes, parsed out of the script rather than duplicated, so the runtime patch and the image build cannot drift apart
  • one pass is enough, and a second pass changes nothing, so the reboot the patches request settles
  • each patch individually reports whether it needs a restart, so a patch that rewrites a boot file and stays silent fails the suite — that is precisely the reported symptom
  • conflicting and misplaced configuration is replaced while # custom protected lines survive
  • a Pi3 never receives Navigator or dwc2 configuration, and the Pi3 cleanup leaves only sections its firmware recognises
  • a device carrying stray board sections converges to one, keeps every user line, and does not loop
  • the boot-partition probe of every board script, executed under real bash against four fake-root layouts, resolves correctly and exits non-zero when it cannot tell, down to the Pi5 script refusing a /boot layout
  • the runtime patcher refuses the Bookworm stub the install scripts refuse, for both config.txt and cmdline.txt, and still accepts a real Bullseye /boot and a mounted config.txt whose comments happen to carry the marker
  • main() itself resolves both boot files through that guard, so reverting the one line that wires it in fails the suite instead of quietly restoring the original bug
  • merging strays keeps the user's configuration in the order the firmware reads it, and keeps it inside the section the other patches look in, for the two-header case a single boot on the bad release produces
  • no install script reaches a boot file without BOOT_PATH, anywhere, outside the probe that resolves it

Every guard was checked by reverting the fix it protects and confirming the suite goes red, including the shell ones.

The image build now asserts that the boot partition of the image it just produced really carries the board configuration: in config.txt the board section and the Navigator overlays, and in cmdline.txt the memory cgroup docker needs and the removal of the serial console that would hold the autopilot port. cmdline.txt is reached through a path of its own and was lost the same way — the shipped 1.4.4-beta.20 Bookworm image still carries stock console=serial0,115200 and no cgroup parameters. Run against that image, this check fails on every line it looks for, which is the bug sitting in a released artifact.

That check only ever runs upstream, so test_image_build_checks_what_the_install_scripts_write ties each line it looks for back to the install script that writes it. A check that stopped matching the scripts fails the test suite instead of going unnoticed until a release.

Out of scope

Three problems found while working on this, none of them fixed here:

Test plan

Four combinations to cover: Pi4 Bullseye, Pi4 Bookworm, Pi5 Bookworm and Pi3 Bullseye. Three images cover them, and all three were built from this branch: arm-v7 bullseye for the Pi3 and the Pi4, arm-v7 bookworm for the Pi4, and arm64-v8 bookworm for the Pi5. They came from 266584936, a temporary commit that opened the image build up to pull requests while writing nothing to any registry, and which differs from the current head only in that build plumbing. It has since been dropped, so the branch is the fix alone.

Each artifact was read before burning: its FAT boot partition was opened with mtools and checked against expectations parsed out of the install scripts. The same check reports 5 problems against 1.4.4-beta.20.

The Pi5 probe was narrowed to /boot/firmware after those images were built, so the probe of all three board scripts was run again, read only, on four live boards: two Pi5 Bookworm, a Pi4 Bookworm and a Pi4 Bullseye. Each resolved the boot partition its own board actually uses, /boot/config.txt is a stub on all three Bookworm boards, and bcm_2712.sh refused the Pi4 Bullseye layout.

Every image carries blueos-core:1.4-dev, so the boot files come from the fixed install scripts while the patches running on the first boot are the old ones. That is what makes a first boot a test of the install scripts on their own, and it held: on both Bookworm boards the first boot asked to restart for dns, noIPV6, swap, wpa and networkmanager, and for no boot configuration patch, because there was nothing left to fix. The fixed patches were then deployed to each board and run twice.

Pi4 Bullseye — Pi 4B Rev 1.2, arm-v7 bullseye pi4

  • BOOT_PATH resolved to /boot and the configuration went into the [pi4] section the Bullseye image already ships, leaving [cm4], [all], [pi4], [all] — no second board section, and the two stock [all] sections left alone
  • First boot: the 10 overlay lines are active, cgroup_enable=memory is set, console=serial is gone, and i2c-4, i2c-6, spidev0.0, spidev1.0-1.2 and ttyAMA0-3 are all present
  • Detected Linux board: NavigatorPi4, Using Navigator flight-controller
  • The fixed patches converge on the image as burned: no change to config.txt or cmdline.txt and no restart requested, on either of two runs
  • Repair on hardware: 31 stray [pi4] headers with user configuration under one of the strays collapsed to a single section with that line inside it, and a second run changed nothing and asked for no reboot

Pi4 Bookworm — Pi 4B Rev 1.5, arm-v7 bookworm

  • BOOT_PATH resolved to /boot/firmware, spi0-led.dtbo was compiled into /boot/firmware/overlays, and [pi4] carries the whole set
  • First boot: i2c-4, i2c-6, spidev0.0, spidev1.0-1.2 and ttyAMA3/4/5 present, cgroup_enable=memory set, console=serial gone
  • Detected Linux board: NavigatorPi4, Using Navigator flight-controller
  • The fixed patches converge: no change to config.txt or cmdline.txt and no restart requested, on either of two runs
  • With /boot/firmware unmounted, bcm_27xx.sh printed why and exited 1, leaving the /boot stub untouched, and that status carries: configure_board.sh ends on the board script's pipeline and install.sh runs under set -e, so the install stops there rather than reporting success

Pi5 Bookworm — Pi 5B Rev 1.0, arm64-v8 bookworm pi5

  • BOOT_PATH resolved to /boot/firmware, the overlay was compiled into /boot/firmware/overlays, and [pi5] carries the Pi5 set with both i2c3-pi5 spellings
  • First boot: i2c-3, i2c-6, spidev0.0, spidev1.0-1.2 and ttyAMA0/2/3/4/10 present, cgroup_enable=memory set, console=serial gone
  • Detected Linux board: NavigatorPi5
  • The fixed patches converge: no change and no restart requested, on either of two runs

Pi3 Bullseye — Pi 3B, arm-v7 bullseye pi4

This board booted the image three times and came all the way up, but it never got a DHCP lease on eth0 and lost its hotspot a minute in to #4233, so it stayed unreachable and was read back from its SD card rather than over ssh.

  • The cleanup left [all], [all]: the [cm4] and [pi4] sections the Pi4 image ships are gone, and no i2c, spi, uart or dwc2 overlay line survives anywhere in config.txt
  • modules-load=dwc2,g_ether was taken back out of cmdline.txt
  • It converged in one pass: the first boot applied revert_update_dwc2 and clean_config_pi3 and rebooted once, and the boot after that applied nothing and asked for no restart
  • Replaying the fixed patches over that card's own boot files, as a Pi3 on Bullseye, converges on the first pass and produces a config.txt byte-identical to the one the shipped patches left on the board

bcm_28xx.sh cannot be reached through an image: configure_board.sh picks the board script from the running model, and the Bullseye image is built on a Pi4, so a Pi 0-3 only runs that script when install.sh is executed on it directly. The suite covers it by running it under real bash against fake roots.

@joaoantoniocardoso
joaoantoniocardoso marked this pull request as draft August 21, 2026 22:01
@github-actions

github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown

Automated PR Review

0. Summary

  • Verdict: LGTM :shipit:

Backports to 1.4-dev a set of related fixes for Navigator detection on first boot of Bookworm images: install/boards/bcm_2{7,8}xx.sh now resolve the boot partition through a probe that refuses the Bookworm /boot stub, boot_config_get_or_append_section() no longer prepends the header with a leading \n that broke its own re.match and left the section index one past the list end, boot_config_merge_duplicated_sections() collapses the stray [pi4] runs the previous release accumulated, update_dwc2() picks its section explicitly rather than falling back to [pi5] for every non-Pi4, boot_cmdline_add_modules() treats the falsy 0 index correctly, interpolated configuration values are re.escaped, boot_config_remove_section() short-circuits on an absent section, locate_boot_file() refuses the same stub on the runtime patch path, and the image-build workflow now asserts the boot partition of the built image carries the expected board section and cmdline parameters. The 829-line test_blueos_startup_update.py pins every symptom listed above against stock Bullseye and Bookworm config.txt/cmdline.txt and executes the install-script boot-partition probes under real bash against four fake-root layouts.

No further comments, nice job 👍

Generated by PR Review Bot. This is advisory, a human reviewer must still approve.

@joaoantoniocardoso
joaoantoniocardoso force-pushed the fix/1.4-navigator-overlays-bookworm-boot-config branch from a841622 to 990ead9 Compare August 21, 2026 22:06
@joaoantoniocardoso
joaoantoniocardoso marked this pull request as ready for review August 21, 2026 22:44
@joaoantoniocardoso
joaoantoniocardoso requested a review from a team August 21, 2026 22:44
@joaoantoniocardoso joaoantoniocardoso changed the title Fix Navigator not being detected on the first boot of a Bookworm install [1.4] Fix Navigator not being detected on the first boot of a Bookworm install Aug 21, 2026
Comment thread install/boards/bcm_2712.sh Outdated
@joaoantoniocardoso
joaoantoniocardoso force-pushed the fix/1.4-navigator-overlays-bookworm-boot-config branch 2 times, most recently from 4895884 to 68c3190 Compare August 22, 2026 03:04
@joaoantoniocardoso
joaoantoniocardoso marked this pull request as draft August 22, 2026 03:10
@joaoantoniocardoso
joaoantoniocardoso force-pushed the fix/1.4-navigator-overlays-bookworm-boot-config branch 4 times, most recently from dac735b to fa49b37 Compare August 22, 2026 03:59
@joaoantoniocardoso
joaoantoniocardoso marked this pull request as ready for review August 22, 2026 04:23
@joaoantoniocardoso
joaoantoniocardoso marked this pull request as draft August 22, 2026 04:35
@joaoantoniocardoso
joaoantoniocardoso force-pushed the fix/1.4-navigator-overlays-bookworm-boot-config branch 5 times, most recently from 2665849 to bdab005 Compare August 22, 2026 19:22
@joaoantoniocardoso
joaoantoniocardoso marked this pull request as ready for review August 22, 2026 19:25
@joaoantoniocardoso

Copy link
Copy Markdown
Member Author

Using the Raspbian image built from this branch (from the HACK commit, now ditched, but artifacts still available here), I burned the SD cards for each device and booted them up.

Aside from what the LLM verified by accessing them via SSH, I manually and independently tested and verified myself:

  • Pi3 bullseye (BlueOS-raspberry-linux-arm-v7-bullseye-pi4.zip) w/ no board -> cmdline.txt and config.txt all good.
  • Pi4 bullseye (BlueOS-raspberry-linux-arm-v7-bullseye-pi4.zip) w/ Navigator -> Navigator recognized, cmdline.txt and config.txt all good.
  • Pi4 bookworm, (BlueOS-raspberry-linux-arm-v7-bookworm.zip) w/ Navigator -> Navigator recognized, cmdline.txt and config.txt all good.
  • Pi5 bookworm, (BlueOS-raspberry-linux-arm64-v8-bookworm-pi5.zip) w/ Navigator -> Navigator recognized, cmdline.txt and config.txt all good.

Functionally, I believe this is ready. @patrickelectric can you check whether we are missing anything?

@joaoantoniocardoso joaoantoniocardoso modified the milestones: 1.4.4, 1.4.5 Aug 24, 2026
joaoantoniocardoso added a commit to joaoantoniocardoso/BlueOS that referenced this pull request Aug 25, 2026
@joaoantoniocardoso
joaoantoniocardoso force-pushed the fix/1.4-navigator-overlays-bookworm-boot-config branch from 48e3e83 to 5e808b0 Compare September 3, 2026 22:59
@joaoantoniocardoso

Copy link
Copy Markdown
Member Author

ping @patrickelectric

Comment thread install/boards/bcm_2712.sh Outdated
Comment thread install/boards/bcm_27xx.sh
Comment thread install/boards/bcm_2712.sh
Comment thread install/boards/bcm_27xx.sh
Comment thread core/tools/blueos_startup_update/test_blueos_startup_update.py
@joaoantoniocardoso
joaoantoniocardoso force-pushed the fix/1.4-navigator-overlays-bookworm-boot-config branch 4 times, most recently from cac27ce to e6b62d4 Compare September 4, 2026 01:52
Bookworm moved the boot partition to /boot/firmware and left plain text stubs
behind at /boot/config.txt and /boot/cmdline.txt, so the Pi4 and BCM28XX scripts
were appending the Navigator overlays to files the firmware never reads. A Pi4
installed over Bookworm came up with no I2C, SPI or UART configured, and the
Navigator went undetected.

The dtc output path was hardcoded to /boot/overlays as well. On Bookworm that
only resolves because the boot partition migration leaves a /boot/overlays
symlink behind, which no package owns, so the Navigator LED overlay was one
missing compatibility link away from landing outside the boot partition.

Resolve the boot partition once per script and abort when neither layout is
mounted, rather than configuring a stub and reporting success. A Pi5 is newer
than Bullseye, so its script only ever accepts /boot/firmware.
A missing board section was appended as a single "\n[pi4]" entry, which the
section lookup can never match again because re.match anchors at the start of
the string. Every call appended yet another header, and the start index pointed
one past the list, so the section bounds check in the conflict filter could
never hold and each configuration line was deleted right after being inserted.

A first pass over a stock config.txt left 30 empty [pi4] sections and no
overlays, so a fresh install only got them two reboots later, once the saved
file had been split back into lines.
The section fell back to [pi5] for every board that is not a Pi4, and a board
filter the firmware does not recognise is applied rather than ignored, so on
anything older than a Pi5 that configuration would have reached the pins.

Nothing gets here on a Pi3 today, main() only runs this patch for a Pi4 and a
Pi5, but a board detection that once went wrong is exactly what its sibling
revert_update_dwc2 exists to undo. Guard it the way update_navigator_overlays
already does.
A board install script that writes to the /boot stub instead of the boot
partition still exits 0, so the built image is the only place the mistake shows.
Check that the image carries the board section, the Navigator overlays, the
memory cgroup docker needs, and no serial console holding the autopilot port.
The startup patches and the board install scripts have to agree on what a
Navigator needs, and both had drifted into writing configuration where the
firmware never reads it. Cover the two boot layouts BlueOS ships on: Bullseye,
whose boot partition is at /boot and whose config.txt already carries a [pi4]
section in the middle of the file followed by a second [all], and Bookworm, at
/boot/firmware and with no board section to patch at all.

Both baselines are the stock files of the images BlueOS builds from, so the
result can be checked against hardware, and patching the Bullseye one reproduces
the [pi4] section of a Pi4 that has been running BlueOS on Bullseye.

A Navigator cannot be used on a Pi3, its pinout differs, and for the same reason
the overlays of a Pi4 and a Pi5 are not interchangeable. Both are pinned down
too: a Pi3 has to come out of the patches untouched, and neither board may end up
carrying the other's overlays.

The build time image check only runs on the upstream repository, where nobody
reads it until a release ships, so its expectations are pinned here to what the
install scripts actually write.
The install scripts now refuse to configure the plain text stub Bookworm leaves
at /boot once the boot partition moved to /boot/firmware, but the runtime repair
path did not. locate_file returns the first candidate that exists, so on a
Bookworm host whose boot partition is not mounted it settled on /boot/config.txt,
the stub, and the patches wrote the whole board section into a file the firmware
never reads, rebooted, and from then on found their own work in place and
converged into a permanently unconfigured state with no error.

Refuse the stub by the same DO NOT EDIT THIS FILE marker the install scripts key
on, so both halves of the contract agree, and let the existing config_file None
guards skip the patch loudly rather than write nowhere.
@joaoantoniocardoso
joaoantoniocardoso force-pushed the fix/1.4-navigator-overlays-bookworm-boot-config branch from e6b62d4 to 1349efc Compare September 4, 2026 03:06
@joaoantoniocardoso
joaoantoniocardoso marked this pull request as draft September 4, 2026 03:10
@joaoantoniocardoso
joaoantoniocardoso marked this pull request as ready for review September 4, 2026 03:13
printf ' %s\n' "${MISSING[@]}"
echo "The board install script did not configure the image's boot partition."
exit 1
fi

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to be sure.. have you tested this in the pimod ci machine ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pi4, Bookworm doesn't detect Navigator on first boot

2 participants