Move ChromeOS Flex notes into CHROMEOS-FLEX.md
Standalone doc covering install/reinstall via install-flex-emmc.sh, why a raw dd boots as live media, chromeos-install-from-Linux gotchas, the resulting layout, and boot selection. CLAUDE.md keeps a short quick reference pointing to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
93f4eca05c
commit
08ae7257ca
@@ -0,0 +1,165 @@
|
|||||||
|
# ChromeOS Flex on hp-chell's internal eMMC
|
||||||
|
|
||||||
|
hp-chell (HP Chromebook 13 G1, MrChromebox UEFI firmware) dual-boots:
|
||||||
|
|
||||||
|
| OS | Disk | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| KDE neon | `sda`, external USB/SD card (117GB) | primary OS, owns GRUB |
|
||||||
|
| ChromeOS Flex | `mmcblk0`, internal eMMC (29GB) | installed from neon, see below |
|
||||||
|
|
||||||
|
Because neon lives entirely on external media, the eMMC can be wiped and
|
||||||
|
reinstalled from the running neon system with no risk to neon.
|
||||||
|
|
||||||
|
## Installing / reinstalling
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo ./install-flex-emmc.sh ~/Downloads/chromeos_<ver>_reven_recovery_stable-channel_<keys>.bin.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
- Accepts the `.bin.zip` straight from Google's Flex download or an
|
||||||
|
already-extracted `.bin` (zips are extracted to `/var/tmp/flex-install`).
|
||||||
|
- Destination defaults to `/dev/mmcblk0`; pass a second argument to
|
||||||
|
override. **The destination is wiped entirely.**
|
||||||
|
- Refuses to run if the destination holds the running root filesystem,
|
||||||
|
shows the target's layout, and asks you to type `yes`.
|
||||||
|
- Takes about 2.5 minutes. Full installer log: `/var/log/flex-install.log`.
|
||||||
|
UEFI boot entries from before the run are saved to
|
||||||
|
`/var/log/flex-install-efiboot-before.txt` and the current ones are
|
||||||
|
printed at the end.
|
||||||
|
- Updates the GRUB chainload entry (below) with the new EFI partition's
|
||||||
|
UUID and runs `update-grub`.
|
||||||
|
|
||||||
|
Last installed: 2026-09-27, Flex `16640.57.0` (stable, `reven`).
|
||||||
|
|
||||||
|
## Why not just `dd` the image
|
||||||
|
|
||||||
|
The Flex recovery `.bin` is a full GPT disk image, but it's the
|
||||||
|
**installer** layout, not an installed system:
|
||||||
|
|
||||||
|
| Partition | Installer image (raw `dd`) | Proper install |
|
||||||
|
|---|---|---|
|
||||||
|
| ROOT-A | 2.4GB | 4GB |
|
||||||
|
| ROOT-B | 2MB placeholder | 4GB (A/B updates) |
|
||||||
|
| STATE | fixed 6GB | rest of the disk (~20.6GB) |
|
||||||
|
| unallocated | ~20.7GB | none |
|
||||||
|
|
||||||
|
Flex decides whether it's running from installer media with
|
||||||
|
`/usr/sbin/is_running_from_installer`, which compares the ROOT-A and
|
||||||
|
ROOT-B sizes. A raw `dd` therefore boots as live media ("Install
|
||||||
|
ChromeOS Flex / Try it first"), can't auto-update, and never grows
|
||||||
|
STATE. **There's no flag to set**, and resizing in place isn't
|
||||||
|
practical (ROOT-B sits in a 2MB gap between the ESP and ROOT-A). This
|
||||||
|
was the state from 2026-09-04 (raw `dd`) until the 2026-09-27 reinstall.
|
||||||
|
|
||||||
|
The fix is to run the image's own `chromeos-install`, which is what the
|
||||||
|
"Install ChromeOS Flex" button runs, against the eMMC.
|
||||||
|
|
||||||
|
## How the script works
|
||||||
|
|
||||||
|
1. Adds a temporary udev rule (`/run/udev/rules.d/99-flex-install-ignore.rules`,
|
||||||
|
`UDISKS_IGNORE=1`) so the Plasma desktop doesn't auto-mount eMMC
|
||||||
|
partitions mid-install, then unmounts anything already mounted there.
|
||||||
|
2. Attaches the `.bin` read-only as a partitioned loop device.
|
||||||
|
3. In a private mount namespace (`unshare -m`; every mount is discarded on
|
||||||
|
exit), mounts the image's ROOT-A read-only and builds a chroot:
|
||||||
|
tmpfs `/tmp` `/run` `/var` `/mnt/stateful_partition`, bind `/dev`
|
||||||
|
`/sys`, fresh `/proc`.
|
||||||
|
4. **Masks every disk except the destination**: each `/dev/sd*`,
|
||||||
|
`/dev/nvme*`, other `/dev/mmcblk*`, and `/dev/vd*` node gets
|
||||||
|
`/dev/null` bind-mounted over it, and `/dev/disk` and `/dev/block`
|
||||||
|
are hidden. Inside the chroot, reading `/dev/sda` returns 0 bytes, so
|
||||||
|
neon is unreachable.
|
||||||
|
5. Runs:
|
||||||
|
```
|
||||||
|
chromeos-install --dst /dev/mmcblk0 --payload_image /tmp/payload.bin \
|
||||||
|
--skip_dst_removable --yes --target_bios efi --debug
|
||||||
|
```
|
||||||
|
6. Checks the log for success, re-reads the partition table, and updates
|
||||||
|
GRUB. Cleanup (loop device, udev rule) runs on exit, including on failure.
|
||||||
|
|
||||||
|
## Gotchas when running `chromeos-install` from another Linux
|
||||||
|
|
||||||
|
Each of these broke an attempt during the 2026-09-27 install:
|
||||||
|
|
||||||
|
- **`No recognized cros_XXX bios option on kernel command line`**:
|
||||||
|
`chromeos_postinst` reads `/proc/cmdline` for `cros_efi` (or
|
||||||
|
`cros_legacy` etc.), even though it's passed `--bios efi`. The script
|
||||||
|
bind-mounts a copy of the command line with `cros_efi` appended over
|
||||||
|
`/proc/cmdline`. `chromeos-postinst` then re-chroots into the freshly
|
||||||
|
written root with a *non-recursive* `mount --bind /proc`, which drops
|
||||||
|
that overmount. So the image's `chroot` (a coreutils shim at
|
||||||
|
`/bin/chroot`) is also overmounted with a wrapper that re-applies the
|
||||||
|
fake cmdline inside the target root before exec'ing the real
|
||||||
|
`coreutils --coreutils-prog=chroot`.
|
||||||
|
- **`sfdisk: This disk is currently in use`**: udisks auto-mounted the
|
||||||
|
new STATE partition (`/media/matt/H-STATE`) from a previous attempt.
|
||||||
|
The udev rule in step 1 above handles this.
|
||||||
|
- **Exit status can't be trusted**: `chromeos-install` exited 0 while
|
||||||
|
its inner `chromeos-install.sh` had failed. It also logs harmless
|
||||||
|
`ERROR cros_installer` lines on success (metrics
|
||||||
|
`/var/lib/metrics/uma-events`, "Couldn't check the current
|
||||||
|
permission"). The script treats the run as successful only if the log
|
||||||
|
has `Installation to '/dev/mmcblk0' complete.` and no final `Error:` line.
|
||||||
|
- **`Touch(/mnt/stateful_partition/.install_completed) failed`**:
|
||||||
|
postinst marks the *installer's* STATE as done. In the chroot that's
|
||||||
|
the read-only image, so the script gives it a throwaway tmpfs.
|
||||||
|
- Harmless noise: `initctl` (no Upstart), `Failed to find physical
|
||||||
|
volume`, `BLKRRPART: Device or resource busy`, and ChromeOS ACPI
|
||||||
|
`GetAcpiSysfsPath` errors (not stock ChromeOS firmware).
|
||||||
|
- `nohup … &` over SSH got killed when the session closed, so long runs
|
||||||
|
go through `systemd-run --unit=... -p StandardOutput=file:/var/log/...`.
|
||||||
|
The log must not be in `/tmp` if another user owns the file (root is
|
||||||
|
denied by `fs.protected_regular`).
|
||||||
|
|
||||||
|
## Resulting layout (2026-09-27)
|
||||||
|
|
||||||
|
```
|
||||||
|
mmcblk0 29.1G
|
||||||
|
├─mmcblk0p1 20.6G ext4 STATE
|
||||||
|
├─mmcblk0p2 64M KERN-A (priority=1 successful=1)
|
||||||
|
├─mmcblk0p3 4G ext4 ROOT-A
|
||||||
|
├─mmcblk0p4 64M KERN-B
|
||||||
|
├─mmcblk0p5 4G ext4 ROOT-B
|
||||||
|
├─mmcblk0p6-7 512B KERN-C / ROOT-C
|
||||||
|
├─mmcblk0p8 16M ext4 OEM
|
||||||
|
├─mmcblk0p9-11 512B reserved / reserved / RWFW
|
||||||
|
└─mmcblk0p12 256M vfat EFI-SYSTEM (UUID CBE4-703E)
|
||||||
|
```
|
||||||
|
|
||||||
|
The ESP is copied from the image, so its FAT UUID (`CBE4-703E`) survived
|
||||||
|
the reinstall unchanged. The UEFI boot entries and `BootOrder` were
|
||||||
|
untouched.
|
||||||
|
|
||||||
|
## Booting Flex
|
||||||
|
|
||||||
|
Two ways:
|
||||||
|
|
||||||
|
1. **From GRUB** (neon's menu, shown for 5s): the entry "ChromeOS Flex
|
||||||
|
(internal eMMC)" in `/etc/grub.d/40_custom` chainloads Flex's own EFI
|
||||||
|
loader (`crdyboot`) from the eMMC's ESP:
|
||||||
|
```
|
||||||
|
menuentry "ChromeOS Flex (internal eMMC)" {
|
||||||
|
insmod part_gpt
|
||||||
|
insmod fat
|
||||||
|
insmod chain
|
||||||
|
search --no-floppy --fs-uuid --set=root CBE4-703E
|
||||||
|
chainloader /EFI/BOOT/BOOTX64.EFI
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Run `sudo update-grub` after editing `40_custom`. The install script
|
||||||
|
rewrites the `--fs-uuid` value automatically.
|
||||||
|
2. **From the firmware boot picker** (MrChromebox UEFI, `Esc` during
|
||||||
|
POST): `efibootmgr -v` shows `neon` (Boot0005, on the USB card) and
|
||||||
|
`Internal eMMC` (Boot0001), with `BootOrder` `0005,0001,0000,0003,0002`.
|
||||||
|
With the USB card unplugged, the firmware falls through to `Internal
|
||||||
|
eMMC` and boots Flex with no menu.
|
||||||
|
|
||||||
|
## History
|
||||||
|
|
||||||
|
- **2026-09-04**: first install by streaming the image with
|
||||||
|
`unzip -p … | dd of=/dev/mmcblk0 bs=4M conv=fsync` (~18 min; eMMC
|
||||||
|
writes dropped from ~130MB/s to ~9MB/s as its SLC cache filled, which
|
||||||
|
is normal for this class of eMMC). Booted as live/installer media, as
|
||||||
|
explained above. The note at the time that Flex expands STATE on
|
||||||
|
first boot was wrong.
|
||||||
|
- **2026-09-27**: reinstalled properly with `install-flex-emmc.sh`.
|
||||||
@@ -119,82 +119,18 @@ power-off + power-on before assuming anything is actually broken.
|
|||||||
|
|
||||||
## Dual boot: KDE neon (USB) + ChromeOS Flex (internal eMMC)
|
## Dual boot: KDE neon (USB) + ChromeOS Flex (internal eMMC)
|
||||||
|
|
||||||
2026-09-04: installed ChromeOS Flex onto the internal eMMC
|
ChromeOS Flex is installed on the internal eMMC (`/dev/mmcblk0`); neon
|
||||||
(`/dev/mmcblk0`), which was blank (see storage correction above — the
|
stays on the external `sda`. **Full details are in `CHROMEOS-FLEX.md`**,
|
||||||
KDE neon system boots from an external USB/SD card on `sda`, not from
|
covering install/reinstall via `install-flex-emmc.sh`, why a raw `dd` of
|
||||||
anything internal, so writing to the eMMC was safe to do live from the
|
the recovery image boots as live media, `chromeos-install`-from-Linux
|
||||||
running system with zero risk to the neon install).
|
gotchas, the partition layout, and boot selection.
|
||||||
|
|
||||||
**How it was installed**: the Flex recovery image
|
Quick reference:
|
||||||
(`chromeos_16640.57.0_reven_recovery_stable-channel_RevenMPKeys-v10.bin.zip`,
|
- Reinstall: `sudo ./install-flex-emmc.sh <flex .bin.zip or .bin>` (wipes
|
||||||
in `matt`'s `~/Downloads`) is a full raw GPT disk image — exactly what
|
the eMMC, masks every other disk, about 2.5 min, log in
|
||||||
the normal "boot from USB, then Install CloudReady/Chrome OS Flex" flow
|
`/var/log/flex-install.log`).
|
||||||
`dd`s onto the target disk. Since neon's root already lives on external
|
- Boot Flex from neon's GRUB menu with the "ChromeOS Flex (internal
|
||||||
media, that install flow was skipped entirely and the image was
|
eMMC)" entry (in `/etc/grub.d/40_custom`; chainloads the ESP with FS
|
||||||
streamed straight onto the eMMC instead:
|
UUID `CBE4-703E`), or from the firmware picker (`Esc` at POST, then
|
||||||
|
`Internal eMMC`). With the USB card unplugged, the firmware boots Flex
|
||||||
```
|
directly.
|
||||||
sudo sh -c 'unzip -p "<path to .zip>" <path to .bin inside the zip> \
|
|
||||||
| dd of=/dev/mmcblk0 bs=4M status=progress conv=fsync'
|
|
||||||
```
|
|
||||||
|
|
||||||
Unzipped image is ~8.9GB; eMMC is 29GB. (Original note here claimed
|
|
||||||
ChromeOS auto-expands the stateful partition on first boot — **wrong**,
|
|
||||||
see the 2026-09-27 reinstall below.) eMMC write speed degraded over the transfer (~130MB/s at the start down
|
|
||||||
to ~9MB/s by the end, total ~18 min for 8.9GB) — looks like the eMMC's
|
|
||||||
SLC write cache exhausting partway through; expected/normal for eMMC of
|
|
||||||
this class, not a fault.
|
|
||||||
|
|
||||||
**2026-09-27: reinstalled properly with `install-flex-emmc.sh`.** The
|
|
||||||
raw `dd` above left the *installer* image layout: ROOT-B a 2MB
|
|
||||||
placeholder, STATE a fixed 6GB, ~20.7GB unallocated. Flex's
|
|
||||||
`/usr/sbin/is_running_from_installer` compares ROOT-A/ROOT-B sizes, so
|
|
||||||
it booted as live media ("Install ChromeOS Flex / Try it first") and
|
|
||||||
couldn't do A/B updates. There is no flag to flip; the fix is running
|
|
||||||
the image's own `chromeos-install` (what the Install button runs)
|
|
||||||
against the eMMC. `install-flex-emmc.sh` (in this repo) does that from
|
|
||||||
neon: loop-attaches the `.bin`, chroots into its ROOT-A inside a private
|
|
||||||
mount namespace with every other disk (incl. neon's `sda`) masked by
|
|
||||||
`/dev/null`, and runs `chromeos-install --dst /dev/mmcblk0
|
|
||||||
--payload_image ... --target_bios efi`. Gotchas it handles:
|
|
||||||
- `chromeos_postinst` needs a `cros_efi` flag on the kernel cmdline;
|
|
||||||
faked via a bind over `/proc/cmdline`, plus a wrapper on the image's
|
|
||||||
`chroot` because postinst re-chroots with a non-recursive `/proc` bind.
|
|
||||||
- Plasma/udisks auto-mounts new eMMC partitions mid-install, making
|
|
||||||
`sfdisk` refuse a busy disk — a temporary `UDISKS_IGNORE` udev rule
|
|
||||||
in `/run/udev/rules.d` prevents it.
|
|
||||||
- `chromeos-install` can exit 0 on failure and logs harmless `ERROR`
|
|
||||||
lines on success — judge by its final `Installation to ... complete.`
|
|
||||||
line. Log: `/var/log/flex-install.log`.
|
|
||||||
- Whole run is only ~2.5 min (partitions copied at eMMC speed).
|
|
||||||
Result: ROOT-A/ROOT-B 4GB each, STATE 20.6GB (fills disk), KERN-A
|
|
||||||
`successful=1`. The ESP is copied from the image so its FS UUID stays
|
|
||||||
`CBE4-703E` (script still re-points `40_custom` in case it changes);
|
|
||||||
UEFI boot entries/order untouched.
|
|
||||||
|
|
||||||
**Boot selection** — both OSes are reachable two ways:
|
|
||||||
1. **From GRUB** (neon's own boot menu, visible for 5s per
|
|
||||||
`GRUB_TIMEOUT`): a custom entry "ChromeOS Flex (internal eMMC)" was
|
|
||||||
added to `/etc/grub.d/40_custom`, which chainloads the eMMC's own EFI
|
|
||||||
bootloader:
|
|
||||||
```
|
|
||||||
menuentry "ChromeOS Flex (internal eMMC)" {
|
|
||||||
insmod part_gpt
|
|
||||||
insmod fat
|
|
||||||
insmod chain
|
|
||||||
search --no-floppy --fs-uuid --set=root CBE4-703E
|
|
||||||
chainloader /EFI/BOOT/BOOTX64.EFI
|
|
||||||
}
|
|
||||||
```
|
|
||||||
`CBE4-703E` is the FAT filesystem UUID of the eMMC's `EFI-SYSTEM`
|
|
||||||
partition (`mmcblk0p12`); `/EFI/BOOT/BOOTX64.EFI` is Chrome OS's
|
|
||||||
fallback UEFI loader (`crdyboot`) on that partition. Run
|
|
||||||
`sudo update-grub` after editing `40_custom` for changes to take
|
|
||||||
effect.
|
|
||||||
2. **From the firmware's own UEFI boot picker** (MrChromebox UEFI,
|
|
||||||
typically `Esc` during POST) — `efibootmgr -v` shows both `neon`
|
|
||||||
(Boot0005, on the USB stick) and `Internal eMMC` (Boot0001) as
|
|
||||||
registered entries, `BootOrder` currently `0005,0001,0000,0003,0002`
|
|
||||||
(neon first). If the USB stick is unplugged, firmware falls through
|
|
||||||
past the missing `neon` entry straight to `Internal eMMC` and boots
|
|
||||||
Flex automatically — no menu needed.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user