Skip to content

Buildroot 2026.08: line bump, bluez backports retired, SSH key persistence - #150

Merged
mcfbytes merged 3 commits into
masterfrom
br-2026-08
Sep 6, 2026
Merged

Buildroot 2026.08: line bump, bluez backports retired, SSH key persistence#150
mcfbytes merged 3 commits into
masterfrom
br-2026-08

Conversation

@mcfbytes

@mcfbytes mcfbytes commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Two independent changes on one branch: the Buildroot 2026.05.2 → 2026.08 line bump and everything it surfaced, then an unrelated SSH key persistence fix.

1. Buildroot 2026.08

Done by hand because allowedVersions deliberately fences Renovate to one line at a time. Renovate is not broken here — the manager extracts fine, github-tags sees the tag, and loose versioning orders 2026.08 ([2026,8]) above 2026.05.2 ([2026,5,2]) correctly. Neither the differing component count nor the absent patch component is what stops it; allowedVersions filters it out on purpose. Same route PR #54 took for 2026.02.x → 2026.05.x.

BUILDROOT_SHA256 transcribed from the GPG-clearsigned .sign manifest, never from the tarball.

Moved: gcc 14.4.0 → 15.3.0, glibc 2.43 → 2.44, gdb 15.2 → 16.3, bluez 5.79 → 5.86, Samba 4.24.5 → 4.24.6, OpenSSL 3.6.3 → 3.6.4.

The bluez patch directory is gone

board/mister/de10nano/patches/bluez5_utils/ carried five upstream commits only because Buildroot pinned 5.79, and its own headers said to delete it on the first bump whose bluez contained them. 5.86 does.

Each was confirmed redundant against the pristine 5.86 tarball with apply-patches.sh:119's exact flags rather than inferred from release notes — which mattered: 0005's header predicted 5.87 and it is already in 5.86. 0002 is superseded, not merely redundant: 5.86 calls device_set_cable_pairing() outside the CABLE_PAIRING_SIXAXIS branch and adds server_set_cable_pairing(), so re-applying our backport would have narrowed upstream's behaviour.

The server_set_cable_pairing CI gate is kept — it asserts behaviour reached the build tree, never which mechanism put it there, so it now guards "the pinned bluez still carries the series".

A DE25 regression only the golden hash caught

2026.08 retired the 7.0 kernel-headers series into Config.in.legacy. A retired symbol still sets cleanly, so check-config-fragments' "every fragment symbol survives" assertion passed with 0 dropped — the literal BR2_KERNEL_HEADERS_7_0=y really was in the resolved config. What collapsed was downstream: headers fell back to AS_KERNEL and the DE25 libc fell from glibc to uClibc.

Pin is now BR2_KERNEL_HEADERS_7_1 (7.1.13, newest ≤ our 7.2.3 kernel); glibc restored, BR2_LEGACY clear. The lesson is recorded in the docs: a Config.in.legacy demotion is invisible to a symbol-presence check, and the resolved-config diff must actually be read on a line bump. (The first golden regeneration here did bake in the broken config; it was redone after the fix.)

Golden hashes verified deterministic — resolving the fragments against an unpacked 2026.05.2 tree reproduces the recorded 2026.05.2 hashes exactly.

Still load-bearing, not fixed upstream

dhcpcd is still 10.2.4, dhcpcd.mk still passes no --with-hooks, and configure:1880 still guards the host probes with if ! $HOOKSET. external.mk's override (#139) stays.

Flagged, not changed

2026.08's BR2_TARGET_UBOOT_LATEST_VERSION is 2026.07 — exactly the DE25's custom pin, now redundant in version terms. Left alone: DE25 is not built in CI, and swapping an unbooted board's bootloader source should not ride along on a Buildroot bump.

2. SSH key that survives an image update

An update replaces linux.img wholesale and / is a read-only loop-mounted ext4, so /root/.ssh cannot be written at runtime and a fresh image ships no /root/.ssh at all. The stock-parity path could only be populated by baking a key into the image before flashing, and every update discarded it.

sshd accepts multiple AuthorizedKeysFile paths, so the shipped config now also reads /media/fat/linux/authorized_keys — one config line, no init script, no hook.

Why not ssh.ext4 (ADR 0015's mechanism): it is an ext4 image inside a file — right for host keys, which the device writes. An authorized_keys is written by the user, and an ext4-in-a-file cannot be opened from Windows/macOS with a card reader, while editing it on the box needs the very shell access the key is meant to grant. The split is who writes the file.

StrictModes stays on and the FAT path satisfies it — the initramfs mounts fmask=0022,dmask=0022 with no uid/gid options, so the file lands root-owned 0755 under 0755 parents. Those options are ours and fixed.

Verified on hardware: a second sshd on port 2223 with only the FAT path and StrictModes yes accepted a key login (OpenSSH 10.5p1, exFAT, real DE10-Nano). Two new fail-closed CI gates assert the path is in the shipped config and that StrictModes no is absent.

Verification

  • make all exit 0
  • scripts/ci-tests.sh 176 passed, 0 failed, 0 skipped — the two ABI checks now run rather than skip, a stock MiSTer binary having been placed for them
  • scripts/test-initramfs.sh 8/8 QEMU boot cases
  • All 4 config stacks regenerate; renovate-config-validator clean; make buildroot-verify passes

🤖 Generated with Claude Code

https://claude.ai/code/session_01PFQovqPVshbtdzJnZyr5bc

mcfbytes and others added 3 commits September 5, 2026 15:10
A line bump, done by hand, because allowedVersions deliberately fences
Renovate to one line at a time. Renovate is NOT broken here: the manager
extracts fine, github-tags sees the tag, and `loose` versioning orders
2026.08 ([2026,8]) above 2026.05.2 ([2026,5,2]) correctly -- the differing
component count is not what stops it, and neither is the absent patch
component. `allowedVersions` filters the candidate out on purpose, so moving
the line stays a human decision. Same route PR #54 took for 2026.02.x ->
2026.05.x.

BUILDROOT_SHA256 transcribed from the GPG-clearsigned .sign manifest
(`make buildroot-showsig`), never from the tarball; `make buildroot-verify`
passes.

WHAT THE BUMP MOVED: gcc 14.4.0 -> 15.3.0, glibc 2.43 -> 2.44, gdb 15.2 ->
16.3, bluez 5.79 -> 5.86, Samba 4.24.5 -> 4.24.6, OpenSSL 3.6.3 -> 3.6.4.
Read off `make show-info`, not guessed.

THE bluez PATCH DIRECTORY IS GONE. board/mister/de10nano/patches/bluez5_utils/
carried five upstream commits only because Buildroot pinned 5.79, and its own
headers said to delete it on the first bump whose bluez contained them. 5.86
does. Each was confirmed redundant against the PRISTINE 5.86 tarball with
apply-patches.sh:119's exact flags rather than inferred from release notes --
which mattered: 0005's header predicted 5.87 and it is already in 5.86. 0002
is superseded rather than merely redundant: 5.86 calls
device_set_cable_pairing() OUTSIDE the CABLE_PAIRING_SIXAXIS branch and adds
server_set_cable_pairing(), so re-applying our backport would have NARROWED
upstream's behaviour. ci-tests' server_set_cable_pairing gate is KEPT: it
asserts behaviour reached the build tree, never which mechanism put it there,
so it now guards "the pinned bluez still carries the series" instead.

DE25 REGRESSION CAUGHT BY THE GOLDEN HASH, AND IT IS THE INTERESTING PART.
Buildroot 2026.08 RETIRED the 7.0 kernel-headers series into Config.in.legacy.
A retired symbol still SETS cleanly, so check-config-fragments' "every fragment
symbol survives" assertion passed with 0 dropped -- the literal
BR2_KERNEL_HEADERS_7_0=y line really was in the resolved config. What silently
collapsed was everything downstream: the headers choice fell back to AS_KERNEL
and the DE25 libc fell from glibc to uClibc. Only the golden hash moved. Pin is
now BR2_KERNEL_HEADERS_7_1 (7.1.13, newest <= our 7.2.3 kernel); glibc restored,
BR2_LEGACY clear. Lesson recorded in docs: a Config.in.legacy demotion is
invisible to a symbol-presence check, and the resolved-config diff must actually
be read on a line bump -- regenerating the hash alone would have shipped it.
(The first golden regeneration here DID bake in the broken config; it was
redone after the fix.)

Golden hashes verified deterministic: resolving the fragments against an
unpacked 2026.05.2 tree reproduces the recorded 2026.05.2 hashes exactly.

NOT FIXED UPSTREAM, STILL LOAD-BEARING: dhcpcd is still 10.2.4, dhcpcd.mk still
passes no --with-hooks, and configure:1880 still guards the host probes with
`if ! $HOOKSET`. external.mk's override (PR #139) stays; the build asserts the
pinned hook set.

FLAGGED, NOT CHANGED: 2026.08's BR2_TARGET_UBOOT_LATEST_VERSION is 2026.07 --
exactly the DE25's custom pin -- so that pin is now redundant in version terms.
Left alone: DE25 is not built in CI, and swapping an unbooted board's bootloader
source should not ride along on a Buildroot bump.

Verified: `make all` exit 0; ci-tests 174/174 (0 skipped -- the two ABI checks
now run, a stock MiSTer binary having been placed for them); all 4 config stacks
regenerate; renovate-config-validator clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFQovqPVshbtdzJnZyr5bc
…rvives an update

THE GAP. An OS update replaces linux.img wholesale, and / is a read-only
loop-mounted ext4 (`/dev/loop0 on / type ext4 (ro)`). So /root/.ssh cannot be
written on the box at runtime AT ALL, and a freshly built image ships no
/root/.ssh whatsoever -- only /root/.config/mc/. The stock-parity
`AuthorizedKeysFile .ssh/authorized_keys` can therefore only ever be populated
by injecting a key into linux.img before flashing, and the next update discards
it. That is the "persistent state must live on /media/fat" trap, and it had been
costing a manual re-push on every single flash.

THE FIX. sshd accepts multiple AuthorizedKeysFile paths and tries each, so the
shipped config now lists the stock path plus /media/fat/linux/authorized_keys.
The FAT partition is never reflashed, so a key there is picked up after every
update. No init script, no bind-mount, no user-startup.sh hook, no new
persistence image -- one config line.

WHY NOT ssh.ext4 (ADR 0015's mechanism), which was the obvious candidate: it is
an ext4 image INSIDE a file, and that is right for HOST keys because the DEVICE
writes them. An authorized_keys file is written by the USER, and an
ext4-in-a-file cannot be opened from Windows or macOS with a card reader, while
editing it on the box needs the very shell access the key is meant to grant --
circular for anyone setting key auth up for the first time. The split is who
writes the file: machine-written state in ssh.ext4, user-supplied state on
exFAT, which every OS can write with no tooling.

StrictModes STAYS ON (default yes) and the FAT path satisfies it. sshd rejects
an authorized_keys whose file or parents are group/world-writable; the initramfs
mounts the partition fmask=0022,dmask=0022 with no uid/gid options
(initramfs-overlay/init:27), so the file lands root-owned 0755 under 0755
parents. Those mount options are OURS and fixed, so this cannot be quietly
invalidated by a card mounted elsewhere. Disabling StrictModes was never
needed.

VERIFIED ON HARDWARE, not reasoned about: a second sshd on port 2223 configured
with ONLY the FAT path and StrictModes yes accepted a key login (OpenSSH
10.5p1, exFAT, real DE10-Nano). The shipped config also passes `sshd -t` and
reports both paths under `sshd -T` on the device.

CI: two new fail-closed gates assert the FAT path is in the SHIPPED sshd_config
(not merely the overlay source -- the overlay only matters if it reaches the
rootfs) and that `StrictModes no` is absent. Dropping either would silently
return every user to "your key is gone after each update" with nothing else
failing. ci-tests 176/176, 0 skipped.

User-facing instructions: docs/user/faq.md#ssh-key-persist. Rationale and the
rejected alternative: docs/ssh-ftp-parity.md §1.3.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFQovqPVshbtdzJnZyr5bc
…eys path

The previous commit justified /media/fat/linux/authorized_keys partly with
"/root/.ssh cannot be written at runtime at all". That is WRONG, and observing a
booted rig is what caught it: `/dev/loop0 on / type ext4 (rw,noatime,nodiratime)`.

/ is mounted read-only at BOOT (`ro` on the cmdline; inittab's remount-rw line
deliberately left commented, ADR 0011) and becomes writable on interactive
login, because /etc/profile ends with `mount -o remount,rw /`. That is stock
parity and is documented as the mechanism by which / ever becomes writable at
all (docs/init-parity.md, the /etc/profile row) -- not a regression, and not
something this change should have contradicted. So a key CAN be written to
/root/.ssh by hand after logging in.

THE CONCLUSION IS UNCHANGED, only the reasoning. The durable justification never
needed the read-only claim: an update replaces linux.img wholesale, and
/root/.ssh lives inside that file, so a key there is destroyed by every update
regardless of how / is mounted at the time. The secondary point also survives in
weaker form -- a fresh image ships no /root/.ssh at all and / is read-only until
you log in, so seeding the stock path by hand requires a login first, which is
circular when the key IS the login method.

Reworded in all four places that carried the overstatement: the sshd_config
comment, ci-tests.sh's gate rationale, docs/ssh-ftp-parity.md §1.3 (which also
now records the correction, so the next reader does not re-derive the wrong
model), README's security-posture bullet and the user FAQ.

Comment-only in the shipped sshd_config, but rebuilt and re-verified rather than
assumed: ci-tests 176/176, 0 skipped, and the shipped config still carries the
two-path AuthorizedKeysFile line.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PFQovqPVshbtdzJnZyr5bc
@mcfbytes
mcfbytes merged commit 20339f1 into master Sep 6, 2026
8 checks passed
@mcfbytes
mcfbytes deleted the br-2026-08 branch September 6, 2026 04:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant