297 lines
14 KiB
Markdown
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.
|