Skip to content

grow-var: resolve the whole disk with lsblk -d; refuse anything else - #3

Merged
mobileoverlord merged 4 commits into
mainfrom
jschneck/grow-var-dm-disk
Sep 5, 2026
Merged

grow-var: resolve the whole disk with lsblk -d; refuse anything else#3
mobileoverlord merged 4 commits into
mainfrom
jschneck/grow-var-dm-disk

Conversation

@mobileoverlord

@mobileoverlord mobileoverlord commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

lsblk -no PKNAME <partition> also lists the partition's holders, and for a dm slave the dm child's row comes first — its PKNAME is the partition itself. On an encrypted /var this resolved "disk" to /dev/nvme0n1p16 and sgdisk -e wrote a fresh GPT over the LUKS2 header (primary and secondary both destroyed; the mapping survived only because the key was already in the kernel). Observed on a Jetson Orin Nano on its first encrypted-/var boot (meta-avocado #310). Plaintext boards never hit it because there the var device is the partition and lsblk's first row is its own — but every encrypted-var target (imx93, qemu) is exposed.

Fix: lsblk -dno PKNAME, and refuse to run sgdisk unless the resolved device is TYPE=disk.

Verified on the board: fixed resolution gives /dev/nvme0n1 type=disk ptable=gpt; the old form gives nvme0n1p16 type=part.

`lsblk -no PKNAME <partition>` also lists the partition's holders, and for
a dm slave the dm child's row comes first - its PKNAME is the partition
itself. On an encrypted /var this resolved "disk" to /dev/nvme0n1p16 and
`sgdisk -e` then wrote a fresh GPT over the LUKS2 header (primary at 0,
secondary at 16 KiB both destroyed; the mapping survived only because the
key was already in the kernel). Observed on a Jetson Orin Nano on the
first encrypted-/var boot; the plaintext path never hit it because there
the var device is the partition and lsblk's first row is its own.

Use -d (no dependents) and, before sgdisk touches anything, require the
resolved device to be of TYPE disk.

Claude-Session: https://claude.ai/code/session_01AqE5abpz1hSdeM9ZCRLLrf
The LUKS branch used blockdev --getsz, which util-linux-blockdev provides and
the image does not ship: on a Jetson AGX Thor with encrypted /var the
partition was extended and then the unit died with
'line 128: blockdev: command not found' (status 127) before the dm/partition
size comparison. /sys/class/block/<dev>/size is the same 512-byte sector
count for both the partition and the dm-N node behind /dev/mapper/var.
@mobileoverlord

Copy link
Copy Markdown
Contributor Author

Pushed one commit: release: r0r1.

The two fixes here are correct, but without a version move they would not have reached a device. The extension declares 0.1.0-r0, which is the exact NEVR already in 2026/next — a rebuild republishes the same version and a resolving device keeps whichever copy it has, i.e. the broken one.

That is not hypothetical. avocado-img-bootfiles did exactly this: the correct package was built, published, and sat in 2026/next unreachable because 0.0.0-r0.1 sorts below the stale 2026.20-r0.0. It cost a day of debugging an unbootable imx8mp-evk.

Why this PR matters more than it looks

Confirmed on hardware today (Avocado OS 2026.38, imx8mp-evk):

avocado-grow-var[3594]: /usr/bin/avocado-grow-var: line 119: blockdev: command not found
systemd[1]: avocado-grow-var.service: Main process exited, code=exited, status=127/n/a
systemd[1]: Failed to start Grow /var partition and btrfs filesystem.

blockdev(8) is not in the runtime image, so the service fails 127 on every boot. It is not fatal — the unit is WantedBy=multi-user.target, so /var is already mounted by then — but it fails on every boot of every dev image, and the published avocado-ext-dev-0.1.0-r0 still carries it. This PR has been open and unreviewed since 2026-08-26.

One design question for the reviewer

The distro now grows /var itself: avocado-var-grow in meta-avocado, installed to ${libexecdir}, WantedBy=initrd-root-fs.target and ordered Before=cryptsetup-var.service var.mount. It grows the partition in the initramfs, before the LUKS container is opened.

This extension ships its own vendored copy at /usr/bin/avocado-grow-var, WantedBy=multi-user.target — a post-boot grow of an already-mounted filesystem. On a board with both, the distro one has already done the work before this one runs.

Worth deciding whether the extension should carry this at all, or drop the overlay and defer to the distro. That vendored snapshot is precisely how a copy survived the upstream rewrite and kept failing after the component it duplicates had been redesigned. Out of scope for this PR — merging it stops the boot-time failure either way.

https://claude.ai/code/session_0126qmPTM1gce9r9CEh2vxbo

@mobileoverlord
mobileoverlord force-pushed the jschneck/grow-var-dm-disk branch from 4a069fb to 1366c1e Compare September 5, 2026 12:13
@mobileoverlord

Copy link
Copy Markdown
Contributor Author

Corrected the version bump: 0.1.0-r0 → 0.2.0-r0, not a release bump.

A release bump would have been wrong here. The payload changed, not just its packaging — grow-var no longer shells out to blockdev(8) and resolves the whole disk differently. That is a new version of the thing being shipped, so r0 restarts for it the way it does for any new upstream version.

Amended in place (force-with-lease); the two fix commits are untouched.

Release path is already correct on this repo: main is at ci: use the next channel (#2), and the matrix publishes to 2026/next + 2024/next — the channels a 2026 project actually resolves. The workflow guards that the pushed tag equals the avocado.yaml version, so after merge the tag is 0.2.0.

https://claude.ai/code/session_0126qmPTM1gce9r9CEh2vxbo

The two commits above fix the runtime failure, but the extension still declared
0.1.0-r0 - the exact NEVR already published. Rebuilding at that version
republishes the same package and a resolving device keeps whichever copy it
already has: the broken one.

0.2.0 rather than a release bump: the payload changed, not just its packaging.
grow-var no longer calls blockdev(8) and resolves the whole disk differently, so
this is a new version of the thing being shipped. r0 restarts for it, as it does
for any new upstream version.

This is the same trap that just cost a day on avocado-img-bootfiles, where a
correctly-built package sat in 2026/next unreachable because its version did not
sort above the stale one. A content fix that does not move the version is not a
release.

Claude-Session: https://claude.ai/code/session_0126qmPTM1gce9r9CEh2vxbo
@mobileoverlord
mobileoverlord force-pushed the jschneck/grow-var-dm-disk branch from 1366c1e to ab9bce3 Compare September 5, 2026 12:15
release.yml publishes to 2026/next and 2024/next; test.yml only built against
2024/next. A change that breaks against 2026 - the feed a 2026 project actually
resolves - goes green on the PR and ships anyway.

Mirror the release matrix so a PR builds against exactly the feeds its tag
publishes into.

Claude-Session: https://claude.ai/code/session_0126qmPTM1gce9r9CEh2vxbo
@mobileoverlord
mobileoverlord merged commit 50b8355 into main Sep 5, 2026
2 checks passed
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