- Shell 49.6%
- Nix 34.5%
- Python 15.6%
- C 0.3%
the pinned nixpkgs systemd 257.9 source already handles the
pidfd_spawn → posix_spawn fallback via the clone_support state
machine in posix_spawn_wrapper(). the shimboot-specific patch no
longer applies because that function was restructured, causing
cachix-systemd CI to fail with 'Hunk #1 FAILED at 2208' on every
run since 2026-05.
ref:
-
|
||
|---|---|---|
| .backup | ||
| .dev-conventions-sync-cache/conventions | ||
| .github | ||
| .temp | ||
| bootloader | ||
| conventions | ||
| docs/archive | ||
| flake_modules | ||
| manifests | ||
| overlays | ||
| patches | ||
| readme_manifest | ||
| shimboot_config | ||
| tests | ||
| tools | ||
| .gitattributes | ||
| .gitignore | ||
| AGENTS.md | ||
| context.md | ||
| flake.lock | ||
| flake.nix | ||
| generate-changelog.sh | ||
| git-intent-watch.sh | ||
| LICENSE | ||
| README.md | ||
NixOS Shimboot
Important
Project development is paused as of May 20 (EDT) until I have a compatible hardware to test with. Please open a discussion thread if you encounter issues or have any feedback regarding this project.
Note
This is a proof-of-concept project. Most of the codebase were generated by LLMs.
Boot NixOS on locked ChromeOS devices via the RMA shim vulnerability, no firmware modification. Runs from a persistent USB drive. Only tested functional on dedede.
How It Works
Based on ading2210/shimboot, which first applied the RMA shim rootfs vulnerability (originally exploited by MercuryWorkshop's SH1MMER) to boot Linux distributions.
ChromeOS RMA shims are bootable recovery images that run even on enterprise-enrolled devices. The shim's root filesystem is unverified, allowing it to be replaced with a Linux rootfs.
The ChromeOS kernel fails systemd's API filesystem mounts. Systemd resolves mount targets through /proc/self/fd/, which the ChromeOS kernel handles differently. The patch replaces this with a direct mount() call (cb0ea14/patches/systemd-mountpoint-util-chromeos.patch).
The patch is needed whenever systemd components run in the boot chain, as the init system or as supporting daemons like udev. Inits that don't pull systemd components, like Alpine's OpenRC, work without the patch.
Artix's OpenRC pulls udev and related packages split from systemd source, so the patch is still needed there (shimboot#405 (comment)).
Distribution-specific integration is also needed. Upstream shimboot supports Debian; this project adds NixOS.
ChromeOS firmware → RMA shim (patched initramfs) → bootloader → NixOS
A custom bootloader inside the patched initramfs handles the transition.
It mounts the NixOS rootfs, binds vendor firmware and kernel modules, then pivot_root into NixOS.
All of this fits on a USB drive. Reboot without it and the device returns to ChromeOS untouched.
Supported Boards
| Board | Arch | Kernel | Systemd |
|---|---|---|---|
| snappy | Intel | 4.4.35 | 257 |
| grunt | AMD | 4.14.75 | 257 |
| octopus | Intel | 4.14.91 | 257 |
| hatch | Intel | 4.19.84 | 257 |
| dedede | Intel | 5.4.85 | 259 |
| zork | AMD | 5.4.85 | 259 |
| nissa | Intel | 5.15.74 | 260 |
Maximum systemd version per board kernel:
- 257: kernel ≥ 3.15 (systemd 257 README)
- 258/259: kernel ≥ 5.4 (systemd 258 README, systemd 259 README)
- 260: kernel ≥ 5.10 (systemd 260 README)
Dedede is the only board tested so far (258 and 259 confirmed working). All other ceilings are theoretical.
The pinned systemd is built with unstable's stdenv for glibc compat (cb0ea14/flake.nix#L60-L61), patched for ChromeOS kernel mount behavior (cb0ea14/patches/systemd-mountpoint-util-chromeos.patch), and supplemented with stub units and binaries for items nixos-unstable expects but 257.9 lacks (cb0ea14/flake.nix#L95-L103).
Injected via specialArgs, not overlay, to avoid cross-version function argument issues (cb0ea14/flake.nix#L280).
Systemd 260 raised the minimum kernel baseline from 5.4 to 5.10 (systemd 259 README vs systemd 260 README).
Dedede's 5.4 ChromeOS kernel meets 259's minimum (5.4) but falls below 260's (5.10), which is why 259 works and 260 fails. The baseline bump alone is sufficient — dedede simply can't run 260.
Kernel 5.4 does include open_tree/move_mount/fsopen (added in 5.2) and the dedede shim has them. It does not include mount_setattr (added in 5.12). Systemd 259 uses mount_new_api_supported() to probe for mount_setattr and graciously falls back to classic mount()/MS_BIND/MS_MOVE when unavailable. Systemd 260 extended new mount API usage into get_sub_mounts() and bind_mount_submounts() without runtime guards, but this is academic — the 5.10 baseline already rules out dedede before any syscall is attempted.
259.x is the ceiling. 258 and 259 tested working on dedede (shimboot#405 (comment)).
Building an Image
Two build paths are available: the shell orchestrator (assemble-final.sh) and pure-Nix derivations.
Nix derivation (pure, sandboxed)
# Base desktop image
nix build .#shimboot-image-<board>
# Headless SSH-only
nix build .#shimboot-image-<board>-headless
# LUKS2-capable (wrapping is post-build)
nix build .#shimboot-image-<board>-luks
nix build .#shimboot-image-<board>-headless-luks
The Nix build runs fully in the sandbox — no sudo, no loop devices. Store optimization
(hard-link dedup) and git self-repair metadata are applied at build time via debugfs
fakeroot.
LUKS2 variants include static cryptsetup in the initramfs and expect
/dev/mapper/rootfs. Actual LUKS container wrapping must be done post-build
(dm-crypt is unavailable in the Nix sandbox):
sudo ./tools/write/wrap-luks.sh --image $(nix eval .#shimboot-image-<board>-luks.outPath --raw)/shimboot.img
Output is a single file at result/shimboot.img. Write it to USB:
sudo ./tools/write/write-shimboot-image.sh
Shell orchestrator (legacy, all-in-one)
sudo ./tools/build/assemble-final.sh --board <board> --rootfs base
The script builds Nix derivations and harvests ChromeOS drivers from the recovery image.
Assembles a partitioned disk image at work/shimboot.img.
Shell build options
--board, one of the seven supported boards (required)--rootfs base, base config (system, boot, hardware)--drivers vendor, store ChromeOS drivers on a separate vendor partition (default)--drivers inject, inject drivers directly into the rootfs--drivers none, skip driver harvesting--drivers both, vendor partition + inject--dry-run, test the build without destructive changes--prewarm-cache, fetch derivations from Cachix before building--luks, enable LUKS2 encryption for rootfs partition--luks-password PASS, passphrase for LUKS2 (skips interactive prompt)--push-to-cachix, push built derivations to Cachix--firmware-upstream/--no-firmware-upstream, control upstream firmware (default: enabled)--cleanup-keep N, keep last N generations during cleanup (default: 3)
WiFi for headless images
Headless variants need WiFi to be usable via SSH. Two options:
-
Via
secrets.nix(gitignored) — createshimboot_config/secrets.nix:{ wifi = { ssid = "MyNetwork"; psk = "password"; }; }Build with
path:fetcher to include the untracked file:nix build "path:$PWD#shimboot-image-<board>-headless" -
Via derivation parameter — pass credentials directly (pure, no secrets.nix needed):
mkShimbootImage { headless = true; wifi = { ssid = "MyNetwork"; psk = "password"; }; }
Tooling overview
| Directory | Purpose |
|---|---|
| tools/build/ | Image assembly, driver harvesting, partitioning |
| tools/write/ | Safe USB flashing, LUKS wrapping |
| tools/rescue/ | Boot troubleshooting, generation management, chroot recovery |
| tools/lib/ | Shared logging, device detection, Nix helpers |
| tools/inspect/ | Image inspection and log collection |
Step-by-Step Guide
Prerequisites
- A compatible Chromebook (Intel: dedede, octopus, nissa, hatch, snappy; AMD: zork, grunt)
- ChromeOS RMA shim image for your specific board
- USB drive with at least 16GB, recommended ≥32GB
- NixOS system or any Linux with Nix installed for building the image
- The pure-Nix build runs without sudo; the shell orchestrator needs root for loop mounts
Quick Build and Flash
1. Clone and Enter the Repository
git clone https://github.com/PopCat19/nixos-shimboot.git
cd nixos-shimboot
2. Build the Shimboot Image
Option A: Nix derivation (simpler, no sudo)
nix build .#shimboot-image-dedede
For other variants: -headless, -luks, -headless-luks.
See Building an Image for all options.
Option B: Shell orchestrator (legacy)
Replace BOARD with your Chromebook's board name:
# For dedede board (e.g., HP Chromebook 11 G9 EE) - base image
sudo ./tools/build/assemble-final.sh --board dedede --rootfs base
# For other boards, replace 'dedede' with your board name:
# grunt, hatch, nissa, octopus, snappy, zork
Options:
--rootfs base: Base image with system configuration (headless also available)--drivers vendor: Store ChromeOS drivers on separate vendor partition (default)--drivers inject: Inject drivers directly into the rootfs--drivers none: Skip driver harvesting--drivers both: Place drivers in vendor partition AND inject into rootfs--inspect: Inspect the final image after building--dry-run: Test build process without making destructive changes--prewarm-cache: Fetch derivations from Cachix before building--cleanup-rootfs: Remove old shimboot rootfs generations after build--cleanup-keep N: Keep last N generations during cleanup (default: 3)--no-dry-run: Actually delete files during cleanup (default: dry-run)--fresh: Start build from beginning, ignoring any checkpoints
The script will:
- Verify Cachix cache configuration and connectivity
- Build Nix outputs with retry logic and CI optimization
- Harvest ChromeOS drivers and firmware with upstream augmentation
- Create partitioned image at
work/shimboot.img - Populate all partitions with bootloader, rootfs, and drivers
3. Flash to USB Drive / SD Card
WARNING: This will overwrite the target device.
Interactive device selection with predefined image input:
sudo ./tools/write/write-shimboot-image.sh
Afterwards, the imaged usb/sd is ready to boot.
4. Boot Your Chromebook
- Insert the prepared USB drive into your Chromebook
- Enter recovery mode (Esc + Refresh + Power)
- If not already, enable Developer Mode via Ctrl+D and confirm, then enter recovery mode again
- Select the "shimboot" option from the recovery menu
- The system should boot into NixOS with the LightDM greeter
First Boot (base configuration)
- Root user:
root(initial password:nixos-shimboot) - Default user: username defined in your profile's
user-config.nix(default:nixos-user, initial password:nixos-shimboot) - Desktop: LightDM + Hyprland (base config)
- Network: NetworkManager with wpa_supplicant backend
- WiFi should work out of the box if vendor drivers are available
- Configure with
nmtuior runsetup-nixos
Base setup flow
A terminal opens automatically on first boot. Run setup-nixos to step through:
- WiFi - connects and enables autoconnect
- Expand rootfs - grows the partition to fill the USB drive
- Verify config - checks
~/nixos-shimboot, optionally pulls updates - Link
/etc/nixos- runssetup-nixos-shimbootto wire flake fornixos-rebuild - Rebuild - optional first rebuild from base config
After completing, the system is usable as a minimal NixOS install. For a full desktop environment, layer on the companion config repo.
Desktop Configuration
This step is optional. The base config provides a minimal Hyprland environment. For Home Manager integration, theming, and personal applications, layer on the companion config repo.
git clone https://github.com/PopCat19/nixos-shimboot-config.git
cd nixos-shimboot-config
git checkout main # or your personal branch
The config repo imports shimboot as a flake input (shimboot.nixosModules.chromeos) and layers personal configuration on top. Users can fork the config repo and create their own branch for personalized setups.
Troubleshooting
"Git fetch failed" during setup-nixos
The git remote may be pointing to the build machine's path. Fix with:
cd ~/nixos-shimboot
git remote set-url origin https://github.com/PopCat19/nixos-shimboot-config.git # replace URL with your fork if utilized
git fetch origin
"blockdev: Unknown command" during expand-rootfs
Run the script with DEBUG=1 to see what's failing:
sudo DEBUG=1 expand-rootfs
If it still fails, manually expand:
sudo growpart /dev/sdX N # Replace X and N with your disk/partition
sudo resize2fs /dev/sdXN
Build Issues
- Ensure you're using the correct board name (case-sensitive): dedede, grunt, hatch, nissa, octopus, snappy, zork
- For ChromeOS artifacts, ensure you have the correct board manifest
- Check cache health before building:
./tools/build/check-cachix.sh dedede - Use
--dry-runto test the build process without destructive operations - Enable cache pre-warming:
--prewarm-cacheto fetch derivations before building
Cache Management
- Built-in Cachix integration for faster builds
- Check cache coverage:
./tools/build/check-cachix.sh [BOARD] - Cache automatically configured in Nix settings
- Push built derivations to cache for faster subsequent builds
Boot Issues
- Verify your Chromebook board is in the supported list above
- Confirm the shim image matches your exact device model
- Check that recovery mode key combination is correct for your model
- Inspect build metadata:
cat /etc/shimboot-build.jsonon the running system
Space Issues
- Ensure
sudo expand_rootfssucceeded in allocating rootfs to full USB space - The base image is ~6-8GB (expandable); ensure your USB drive has enough space
- Use
--cleanup-rootfsto remove old generations and free space - Use
nix-shellfor temporary packages to save space
Next Steps
- Fork the config repo and create a personal branch (use
mainas template) - Import
shimboot.nixosModules.chromeosas a hardware layer in your own flake - Experiment with different desktop environments
- Contribute bug reports or improvements
Configuration
This repository is the build system + ChromeOS hardware abstraction layer. Personal desktop configuration belongs in a separate repo.
nixos-shimboot/ # build system + ChromeOS HAL
├── flake.nix # exports nixosModules.chromeos
├── flake_modules/ # image building, kernel extraction
├── shimboot_config/
│ ├── base_configuration/ # boot, fs, hardware, users
│ ├── boards/ # per-board hardware database
│ ├── user-config.nix # shared hostname, username
│ ├── shimboot-options.nix # shimboot.headless toggle
│ └── nix-options.nix # allowUnfree predicate
├── bootloader/ # initramfs bootstrap menu
├── patches/ # systemd ChromeOS mount patch
├── tools/
│ ├── build/ # image assembly, driver harvesting
│ ├── write/ # safe USB flashing
│ └── rescue/ # boot troubleshooting, chroot recovery
└── manifests/ # ChromeOS shim chunk manifests
The tree is designed to be self-documenting. Module headers with Purpose: blocks serve as in-code docs, explore by directory rather than relying on external references.
This repo follows the dev-mini conventions.
nixos-shimboot-config/ # companion repo, personal desktop
├── flake.nix # imports shimboot as flake input
└── main/ # reference template (fork this)
External flakes import the ChromeOS module as a hardware layer:
inputs.shimboot.url = "github:PopCat19/nixos-shimboot";
modules = [
shimboot.nixosModules.chromeos # ChromeOS boot, fs, hardware
./my-config.nix # DE, packages, home-manager
];
Binary Cache
Patched systemd and NixOS closures are cached on Cachix. The cache is auto-configured when importing nixosModules.chromeos.
Manual cache setup
- Substituter:
https://shimboot-systemd-nixos.cachix.org - Public key:
shimboot-systemd-nixos.cachix.org-1:vCWmEtJq7hA2UOLN0s3njnGs9/EuX06kD7qOJMo2kAA=
cachix use shimboot-systemd-nixos
CI Cache Strategy
CI uses Cachix daemon mode (post-build hook). Only locally-compiled derivations are pushed -- substituted paths from cache.nixos.org are never re-uploaded.
A cache-hit check runs before each build. If the toplevel closure already exists on Cachix, the build is skipped entirely. On cache miss, daemon mode pushes each derivation as it finishes compiling, naturally excluding Hydra-cached dependencies from the upload.
Limitations
- No suspend support (ChromeOS kernel limitation)
- Audio only works on octopus and snappy; other boards need USB/Bluetooth audio
- hatch: 5 GHz WiFi networks may have connectivity issues
- trogdor: WiFi may be unreliable
nixos-rebuildrequires--option sandbox falseon kernels < 5.6 (missing namespace support)- bwrap/Steam: ChromeOS LSM blocks tmpfs mounts; workaround available below
Bwrap / Steam Workaround
Overview
ChromeOS's security model includes a Linux Security Module (LSM) called chromiumos that restricts certain operations, including mounting tmpfs filesystems. This causes issues with bwrap (bubblewrap), which is commonly used for sandboxing applications like Steam, AppImages, and various Nix packages.
Problem
When applications try to use bwrap with tmpfs mounts, they encounter:
bwrap: Failed to mount tmpfs: Operation not permitted
This occurs because the ChromeOS LSM blocks tmpfs mounts even when running as root or with SUID permissions.
Solution
On dedede (kernel 5.4.85), SUID bwrap alone handles tmpfs — the chromiumos
LSM does not block mount("tmpfs", ...) when running as root via
security.wrappers.bwrap. No additional workaround is needed for basic bwrap
sandboxing.
For boards where the LSM blocks tmpfs despite SUID (unconfirmed for any board
so far), bwrap-mount-shim provides an LD_PRELOAD-based fallback that
intercepts mount() at the libc level and converts tmpfs to bind mounts.
Flatpak on kernel 5.4 has a separate incompatibility: flatpak run uses
statx with fields not available until kernel 5.8, producing ENODATA.
As a workaround, bwrap-mount-shim --sandbox or direct bwrap invocation can
launch flatpak apps bypassing flatpak's sandbox setup.
Usage
# SUID bwrap — works on dedede without any shim
bwrap --ro-bind / / --dev /dev --proc /proc --tmpfs /tmp -- ./myapp
# bwrap-mount-shim — optional LD_PRELOAD fallback
bwrap-mount-shim steam # LD_PRELOAD only
bwrap-mount-shim --sandbox ./myapp # LD_PRELOAD + bwrap sandbox
# Flatpak apps — bypass flatpak's broken sandbox on 5.4
bwrap-mount-shim --sandbox -- \
/var/lib/flatpak/app/.../files/bin/app
Implementation
Security Configuration
The security.nix module provides:
bwrap— SUID wrapper for namespace creation (ChromeOS kernels restrict unprivileged user namespaces)bwrap-mount-shim— LD_PRELOAD shim compiled frommount_shim.c, plus a convenience wrapper script
The C shim intercepts mount("tmpfs", ...) calls, creates a unique directory via mkdtemp under $BWRAP_CACHE_DIR (default: /tmp/bwrap-cache), and converts the call to mount("bind", ...). Created directories are cleaned up on normal exit via atexit.
Steam Integration
Steam compatibility is unverified on shimboot hardware. On Debian-based
shimboot (upstream), Steam works with SUID bwrap alone (shimboot#26).
If the ChromeOS LSM blocks tmpfs on a given board, bwrap-mount-shim steam
can be used as a fallback — the LD_PRELOAD shim intercepts mount() inside
pressure-vessel without any symlink patching or per-update maintenance.
The legacy fix-steam-bwrap.sh
(symlink patch) remains available but rots on Steam client updates.
Technical Details
How It Works
mount_shim.sois loaded viaLD_PRELOAD- It resolves the real
mount()viadlsym(RTLD_NEXT, "mount") - When
mount("tmpfs", ...)is called:- Creates a unique directory via
mkdtempunder$BWRAP_CACHE_DIR/tmpfs-XXXXXX - Registers the directory for cleanup via
atexit - Redirects to
mount(NULL, target, NULL, MS_BIND, data)
- Creates a unique directory via
- All other mount calls pass through to the real
mount()unchanged
Cache Directory
Temp directories are created under:
${BWRAP_CACHE_DIR:-/tmp/bwrap-cache}/tmpfs-XXXXXXXX
Directories are cleaned up on normal process exit via atexit. On signal kill or abrupt namespace teardown, cleanup may not run — /tmp is cleared on reboot regardless.
Limitations
- Performance — bind mounts may have slightly different characteristics than tmpfs
- Compatibility — some applications may expect true tmpfs behavior (e.g., size limits via
--tmpfs-size) - Cleanup —
atexitis best-effort; directories may persist until reboot on abnormal exit
Troubleshooting
bwrap still fails with "Operation not permitted"
-
Check wrappers exist:
ls -la /run/wrappers/bin/bwrap which bwrap-mount-shim -
Verify bwrap has SUID permission:
stat -c '%a %n' /run/wrappers/bin/bwrap # Should show 4xxx (SUID bit set) -
Test with the shim:
bwrap-mount-shim bwrap --ro-bind / / --dev /dev --proc /proc --tmpfs /tmp echo "works"
Application-specific issues
Some applications bundle their own bwrap. The LD_PRELOAD shim handles these transparently — no per-application configuration needed. If tmpfs calls still fail, verify the shim is loaded:
LD_PRELOAD=/path/to/mount_shim.so ldd /path/to/app | grep mount_shim
License
Project code is licensed under GPLv3 (LICENSE), covering Nix configs, system modules, build scripts, and bootloader integration.
ChromeOS RMA shims, recovery images, and extracted firmware/drivers are proprietary and excluded from GPLv3 coverage. Derivations processing these artifacts are marked meta.license = lib.licenses.unfree.
Background
This project uses a flake-based approach over the original ading2210/shimboot scripts, which expect a FHS-compliant build host (Debian). Nix flakes with raw-efi image building provide reliable, declarative image generation on NixOS.
Earlier attempts, nixos-shimboot-legacy and shimboot-nixos, produced fragile, semi-bootable images. This repo was started fresh to avoid inherited complexity.
Why NixOS: While not the lightest distro for low-end Chromebook hardware, NixOS provides reproducible, declarative system configuration.
The same flake that builds the image also serves as the device's runtime configuration.
Users unfamiliar with Nix should try it in a VM first (nixos.org/download).
Credits
- ading2210, original shimboot project and bootloader source
- ading2210/chromeos-systemd, systemd mount patch
- shimboot discussion #335, early feedback on the NixOS approach
- nixos-generators, initial image building method (now deprecated in favor of upstream
raw-efi)
Roadmap
Roadmap
Roadmap
Done
- Builds without flake errors
- Bootable NixOS via RMA shim
- Multi-board compatibility (Intel/AMD, ARM theoretical)
- Functional networking, Hyprland, user environment
- Per-board hardware database with conditional config
- Kill-frecon graphics handoff
nixos-rebuildsupport (requires--option sandbox falseon older kernels)- NixOS generation selector in bootstrap menu
- Battery SoC in bootstrap menu
- Expand rootfs to fill USB
- Export
nixosModules.chromeosas hardware abstraction layer - GitHub CI with caching
- ZRAM
- LUKS2 encryption (passphrase, keyfile, rescue tooling)
Pending
- SDDM greeter support (blank backlit screen after kill-frecon)
- XDG redirect fixes
- bwrap/Steam workaround validation (untested)
- ARM board testing
- Audio for non-octopus/snappy boards
- Refine and cleanup base configuration
- Upstream systemd 258+ integration (dedede ceiling is 259)