Files
hp-chell/CHROMEOS-FLEX.md
T
2026-09-28 23:28:23 -04:00

297 lines
14 KiB
Markdown

# ChromeOS (Flex / FydeOS) on hp-chell's internal eMMC
**Currently installed: FydeOS** (`iris` build, 2026-09-27). It replaced
ChromeOS Flex; `install-flex-emmc.sh` handles both images.
hp-chell (HP Chromebook 13 G1, MrChromebox UEFI firmware) dual-boots:
| OS | Disk | Notes |
|---|---|---|
| KDE neon | `sda`, microSD card in the built-in slot (117GB; the reader is USB-attached internally) | primary OS, owns GRUB |
| FydeOS (was ChromeOS Flex) | `mmcblk0`, internal eMMC (29GB) | installed from neon, see below |
Because neon lives entirely on the microSD card, 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`.
Install history on the eMMC:
- 2026-09-27: ChromeOS Flex `16640.57.0` (stable, `reven`). It
auto-updated to `16640.61.0` on first boot.
- 2026-09-27: **FydeOS** `FydeOS_for_PC_iris_v23.0-SP1-io.bin`
(`amd64-fydeos_iris-io`, `16700.56.23.51`), which replaced Flex.
## 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
FydeOS (current, 2026-09-27):
```
mmcblk0 29.1G
├─mmcblk0p1 23.7G ext4 STATE
├─mmcblk0p2 16M KERN-A
├─mmcblk0p3 2.7G ext4 ROOT-A
├─mmcblk0p4 16M KERN-B
├─mmcblk0p5 2.7G ext4 ROOT-B
├─mmcblk0p6-7 512B KERN-C / ROOT-C
├─mmcblk0p8 16M ext4 OEM
├─mmcblk0p9-10 512B reserved / reserved
├─mmcblk0p11 8M RWFW
└─mmcblk0p12 32M vfat EFI-SYSTEM (UUID F35E-F13C, PARTUUID 18151E4F-...)
```
ChromeOS Flex (2026-09-27, before FydeOS replaced it):
```
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 comes from the image:
`CBE4-703E` for Flex (it survived the Flex reinstall unchanged) and
`F35E-F13C` for FydeOS. The install itself left the UEFI boot entries
and `BootOrder` untouched, but Flex's first auto-update changed them.
See "Flex takes over the UEFI boot order on updates" below.
## Booting ChromeOS (FydeOS)
Two ways:
1. **From GRUB** (neon's menu, shown for 5s): the entry "ChromeOS Flex
(internal eMMC)" in `/etc/grub.d/40_custom` chainloads the eMMC ESP's
own loader: FydeOS's GRUB now (Flex's was `crdyboot`). The title was
kept on purpose, since FydeOS is still ChromeOS.
```
menuentry "ChromeOS Flex (internal eMMC)" {
insmod part_gpt
insmod fat
insmod chain
search --no-floppy --fs-uuid --set=root F35E-F13C
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 microSD card) and
`Internal eMMC` (Boot0001), with `BootOrder` `0005,0001,0000,0003,0002`.
With the microSD card removed, the firmware falls through to `Internal
eMMC` and boots FydeOS with no menu.
## Flex takes over the UEFI boot order on updates
Seen 2026-09-27: after the install, Flex booted fine, found an update,
installed it, and rebooted. The next boot went straight to Flex, and
getting back to neon took the firmware setup. `efibootmgr` afterwards:
```
BootOrder: 0004,0005,0001,0000,0003,0002 (was 0005,0001,0000,0003,0002)
Boot0004* ChromeOS Flex HD(12,GPT,2877e60b-...)/File(\efi\boot\bootx64.efi) <- new
Boot0005* neon HD(1,GPT,34facff5-...)/File(\EFI\neon\shimx64.efi)
```
**What does it:** `cros_installer`'s post-install
(`platform2/installer/efi_boot_management.cc`, strings "Adding EFI Boot
entry." / "Creating new BootOrder."). It makes sure a "ChromeOS Flex"
entry for the eMMC's ESP exists and puts it first in `BootOrder`. Nothing
else in the rootfs writes `BootOrder` (only `cros_installer`,
`efibootmgr` and fwupd's EFI binary mention it), and no boot-time
upstart job runs `cros_installer`. It runs:
- at **install** time, but not under `install-flex-emmc.sh`: the chroot
binds `/sys` non-recursively, so `efivarfs` isn't visible and it logs
`EFI runtime services not [available]` and skips EFI management.
That's why the entry list printed straight after the install was
unchanged.
- **after every auto-update**, when `update_engine` runs post-install
on the running Flex system with real EFI access. That's what happened
here: the eMMC afterwards had ROOT-A `16640.57.0` (the install) and
ROOT-B `16640.61.0` (the update), with KERN-B the higher priority.
So resetting the order once won't last: expect it again after each
Flex update. No flag to turn it off was found. The binary does have an
"EFI is buggy continue without EFI management" fallback, but nothing
user-settable was found that triggers it.
**FydeOS has the same code**: its `cros_installer` includes the same
`efi_boot_management.cc` and `libefivar`, so FydeOS updates will very
likely do the same. The FydeOS `fydeos_dualboot` flag is *not* a fix:
it's for FydeOS's dual-boot mode, where it runs from an image file on
another OS's partition, and only gates its `update_initrd.sh`.
Ways to live with it (none implemented yet):
- Fix it by hand when it happens: pick neon from the firmware boot
picker (`Esc` at POST), then
`sudo efibootmgr -o 0005,0001,0000,0003,0002` (adjust to taste).
- A small neon systemd oneshot at boot that puts `neon` back first
whenever a ChromeOS entry is ahead of it. It still needs one manual
`Esc` boot into neon after each ChromeOS update, but then it's fixed
for good until the next update.
- Deleting `Boot0004` doesn't help: the next update recreates it.
Neon's GRUB entry chainloads the ESP directly and doesn't need it.
After the FydeOS install, `Boot0004 "ChromeOS Flex"` was stale: it
pointed at the old Flex ESP (PARTUUID `2877e60b…`, start sector
`0x49000`), which no longer exists, so the firmware skipped it and fell
through to neon. It was deleted 2026-09-27 (`efibootmgr -b 0004 -B`),
which put `BootOrder` back to `0005,0001,0000,0003,0002`. Expect FydeOS
to add its own entry, and move it first, after its first update.
## FydeOS
Checked 2026-09-27 by reading the image, then **installed the same day
with `install-flex-emmc.sh` unchanged. It worked on the first try.**
`FydeOS_for_PC_iris_v23.0-SP1-io.bin.zip` (board `amd64-fydeos_iris-io`,
version `16700.56.23.51`), in `matt`'s `~/Downloads`. The extracted copies
(`~/fydeos-check`, `~/flex-install`) were deleted 2026-09-28; the
script takes the `.bin.zip` directly and extracts to `/var/tmp/flex-install`.
- **Same installer image layout:** ROOT-A is p3 (2.7GB), ROOT-B is a
2MB placeholder, the ESP is p12. The same `chromeos-install` (Rust)
and `chromeos-install.sh`, `write_gpt.sh`, `cros_installer`,
`is_running_from_installer`, and coreutils `/bin/chroot` shim, so the
chroot wrapper applies.
- `cros_installer` has the same `No recognized cros_XXX bios option`
check, so the fake `cros_efi` is still needed. The success line is the
same `Installation to '${DST}' complete.`
- The target layout from `write_gpt.sh` is 2.66GB each for ROOT-A and
ROOT-B, with STATE filling the rest of the disk.
- **FydeOS's `chromeos-postinst` adds steps:** it mounts the
destination's ESP (`${install_dev%[0-9]}12`) to sync a `cros_debug`
(dev-mode) flag in its `grub.cfg`, then runs
`/usr/share/dualboot/update_initrd.sh` if `rootdev` returns a loop
device, which it does in our chroot. That's harmless:
`update_initrd.sh` exits unless `fydeos_dualboot` is on
`/proc/cmdline`, which isn't in our faked cmdline, and other disks are
masked anyway.
- The ESP uses GRUB (`/efi/boot/bootx64.efi` + `grub.cfg`, kernels in
`/syslinux/vmlinuz.{A,B}`) instead of Flex's `crdyboot`. The
`40_custom` chainload path still works. It's unsigned, which is fine
because Secure Boot is disabled on hp-chell (`mokutil --sb-state`).
The image's ESP UUID is `F35E-F13C`.
Notes:
- It replaced Flex (same eMMC, full wipe).
- The script rewrites the UUID in the `40_custom` entry titled
"ChromeOS Flex", so FydeOS boots under that title. Decided
2026-09-27 to keep it, since FydeOS is effectively still ChromeOS.
- Hardware support: FydeOS's site says the `iris` build supports Intel
6th to 14th gen, which covers this 6th-gen (Skylake) Core m7-6Y75 /
HD 515 (checked 2026-09-27).
## 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 Flex properly with `install-flex-emmc.sh`.
It worked and auto-updated, but took over the UEFI boot order.
- **2026-09-27**: switched to FydeOS (`iris` build) with the same
script; deleted the stale Flex UEFI entry.