diff --git a/local/AGENTS.md b/local/AGENTS.md index 91cbe00a..89107645 100644 --- a/local/AGENTS.md +++ b/local/AGENTS.md @@ -270,7 +270,10 @@ make all CONFIG_NAME=redbear-desktop # GRUB boot manager (installer-native, Phase 2): make r.grub # Build GRUB recipe make all CONFIG_NAME=redbear-full-grub # Build with GRUB chainload -# Or post-build script (Phase 1): +# Linux-compatible CLI (add local/scripts to PATH): +grub-install --target=x86_64-efi --disk-image=build/x86_64/harddrive.img +grub-mkconfig -o local/recipes/core/grub/grub.cfg +# Or legacy post-build script: ./local/scripts/install-grub.sh build/x86_64/harddrive.img # Modify existing image ``` @@ -439,13 +442,13 @@ recipes/core/fatd → ../../local/recipes/core/fatd **Dependencies**: fatfs 0.3.6, fscommon 0.1.1, redox_syscall, redox-scheme, libredox, libc **Tool verification status** (2026-04-17): -- `fat-mkfs`: ✅ Creates FAT12/16/32, labels, auto-detection, tested up to 1GB +- `fat-mkfs`: ✅ Creates FAT12/16/32, labels, auto-detection, cluster size option (`-c`), tested up to 1GB - `fat-label`: ✅ Reads labels; writes BPB + creates/updates root-directory volume-label entry; verifies round-trip on all FAT types (including previously unlabeled volumes) -- `fat-check`: ✅ BPB validation, boot signature check, directory tree walk, cluster stats; ✅ safe repair (dirty flag, FSInfo, lost clusters, orphaned LFN). Handles 0xFFFFFFFF FSInfo sentinel on fresh images. -- `fatd`: ✅ Compiles (links on Redox target only — expected). NOT runtime-tested (requires QEMU/bare metal). +- `fat-check`: ✅ BPB validation, boot signature check, directory tree walk, cluster stats; ✅ safe repair (dirty flag including FAT12, FSInfo, lost clusters, orphaned LFN). Handles 0xFFFFFFFF FSInfo sentinel on fresh images. +- `fatd`: ✅ Compiles (links on Redox target only — expected). ✅ `frename` + rmdir non-empty check implemented. NOT runtime-tested (requires QEMU/bare metal). - Phase 4 (runtime auto-mount): Deferred to runtime validation. Static init service exists. - Known limitation: fatfs v0.3.6 strictly requires `total_sectors_16 == 0` for FAT32, rejecting some Linux `mkfs.fat` images -- `cargo test`: 0 unit tests (all testing done via integration tests with disk images) +- `cargo test`: 56 unit tests (25 scheme + 7 label + 24 check) + 13+ integration edge cases ## BRANDING ASSETS diff --git a/local/docs/GRUB-INTEGRATION-PLAN.md b/local/docs/GRUB-INTEGRATION-PLAN.md index 83590212..f3f85855 100644 --- a/local/docs/GRUB-INTEGRATION-PLAN.md +++ b/local/docs/GRUB-INTEGRATION-PLAN.md @@ -1,10 +1,10 @@ # GRUB Integration Plan — Red Bear OS **Date:** 2026-04-17 -**Status:** Phase 2 complete — installer-native GRUB support implemented and compiling. -**Prerequisite:** The `grub` package must be in the build plan (included in `redbear-full-grub.toml`) -for `make all` to work from a clean tree. Phase 2 is automatic only after `grub` is available in -the local repo or build output. +**Status:** Fully implemented (build-tested, not yet runtime boot-tested). ESP formatted as FAT32 +per UEFI spec. Both Phase 1 (post-build script) and Phase 2 (installer-native) are wired. +**Remaining:** Runtime UEFI boot validation in QEMU (`make all CONFIG_NAME=redbear-full-grub && make qemu`). +**Prerequisite:** The `grub` package is included in `redbear-full-grub.toml` for clean-tree builds. **Approach:** Option A — GRUB as boot manager, chainloading Redox bootloader ## Overview @@ -129,6 +129,54 @@ The default ESP is 1 MiB (too small for GRUB). Configs using GRUB must set: efi_partition_size = 16 # 16 MiB, enough for GRUB + Redox bootloader + margin ``` +## Linux-Compatible CLI + +Red Bear OS provides `grub-install` and `grub-mkconfig` wrappers that match GNU GRUB +command-line conventions. Users migrating from Linux can use familiar switches. + +| Linux Command | Red Bear OS Location | +|---------------|---------------------| +| `grub-install` | `local/scripts/grub-install` | +| `grub-mkconfig` | `local/scripts/grub-mkconfig` | + +Add to PATH for convenience: +```bash +export PATH="$PWD/local/scripts:$PATH" +``` + +### grub-install + +```bash +# Install GRUB into a disk image +grub-install --target=x86_64-efi --disk-image=build/x86_64/harddrive.img + +# Verbose mode +grub-install --target=x86_64-efi --disk-image=build/x86_64/harddrive.img --verbose + +# Show help +grub-install --help +``` + +Supported options: `--target=`, `--efi-directory=`, `--bootloader-id=`, `--removable`, +`--disk-image=`, `--modules=`, `--no-nvram`, `--verbose`, `--help`, `--version`. + +Unsupported Linux options are accepted and ignored silently for script compatibility. + +### grub-mkconfig + +```bash +# Preview generated config +grub-mkconfig + +# Write to file +grub-mkconfig -o local/recipes/core/grub/grub.cfg + +# Custom timeout +grub-mkconfig --timeout=10 -o /boot/grub/grub.cfg +``` + +Supported options: `-o`/`--output=`, `--timeout=`, `--set-default=`, `--help`, `--version`. + ## Implementation — Phase 1: Post-Build Script Phase 1 uses a post-build script to modify the ESP in an existing disk image. @@ -302,7 +350,7 @@ make r.grub make all CONFIG_NAME=redbear-full-grub # Or via CLI flag -make all CONFIG_NAME=redbear-full INSTALLER_OPTS="--bootloader grub" +make all CONFIG_NAME=redbear-full INSTALLER_OPTS="--bootloader grub --cookbook=." # Verify ESP contents python3 local/scripts/fat_tool.py ls build/x86_64/harddrive.img 1048576 / diff --git a/local/patches/installer/redox.patch b/local/patches/installer/redox.patch index 8b001440..94af0f31 100644 --- a/local/patches/installer/redox.patch +++ b/local/patches/installer/redox.patch @@ -90,7 +90,7 @@ index e3c6700..b1d5d72 100644 "termion", "uuid", diff --git a/src/bin/installer.rs b/src/bin/installer.rs -index c3ce487..456efce 100644 +index c3ce487..7d3139b 100644 --- a/src/bin/installer.rs +++ b/src/bin/installer.rs @@ -20,6 +20,8 @@ Using redox_installer as an installer: @@ -102,7 +102,18 @@ index c3ce487..456efce 100644 --skip-partition Skip writing GPT partition tables Use this only if you plan to use other partition tool --live Use bootloader configured for live disk -@@ -39,6 +41,8 @@ fn main() { +@@ -31,6 +33,10 @@ Using redox_installer as a configuration parser: + --list-packages List packages will be installed + --filesystem-size Output filesystem size in MB + --output-config Path to write the parsed config as another TOML ++ ++For GRUB installation with Linux-compatible CLI, use: ++ local/scripts/grub-install --target=x86_64-efi --disk-image=build/x86_64/harddrive.img ++ local/scripts/grub-mkconfig -o local/recipes/core/grub/grub.cfg + "#; + + fn main() { +@@ -39,6 +45,8 @@ fn main() { .add_opt("c", "config") .add_opt("o", "output-config") .add_opt("", "write-bootloader") @@ -111,7 +122,7 @@ index c3ce487..456efce 100644 .add_flag(&["skip-partition"]) .add_flag(&["filesystem-size"]) .add_flag(&["r", "repo-binary"]) // TODO: Remove -@@ -116,6 +120,12 @@ fn main() { +@@ -116,6 +124,12 @@ fn main() { if parser.found("no-mount") { config.general.no_mount = Some(true); } @@ -180,7 +191,7 @@ index 417ff2d..4ad2202 100644 } } diff --git a/src/installer.rs b/src/installer.rs -index 4e077a9..b11d5b8 100644 +index 4e077a9..654f6fb 100644 --- a/src/installer.rs +++ b/src/installer.rs @@ -3,6 +3,13 @@ use anyhow::{bail, Result}; @@ -569,7 +580,23 @@ index 4e077a9..b11d5b8 100644 } //TODO: make bootloaders use Option, dynamically create BIOS and EFI partitions -@@ -683,20 +970,58 @@ where +@@ -672,10 +959,13 @@ where + fscommon::StreamSlice::new(&mut disk_file, disk_efi_start, disk_efi_end)?; + + eprintln!( +- "Formatting EFI partition with size {:#x}", ++ "Formatting EFI partition as FAT32 (size {:#x})", + disk_efi_end - disk_efi_start + ); +- fatfs::format_volume(&mut disk_efi, fatfs::FormatVolumeOptions::new())?; ++ fatfs::format_volume( ++ &mut disk_efi, ++ fatfs::FormatVolumeOptions::new().fat_type(fatfs::FatType::Fat32), ++ )?; + + eprintln!("Opening EFI partition"); + let fs = fatfs::FileSystem::new(&mut disk_efi, fatfs::FsOptions::new())?; +@@ -683,20 +973,58 @@ where eprintln!("Creating EFI directory"); let root_dir = fs.root_dir(); root_dir.create_dir("EFI")?; @@ -640,7 +667,7 @@ index 4e077a9..b11d5b8 100644 } // Format and install RedoxFS partition -@@ -712,6 +1037,222 @@ where +@@ -712,6 +1040,225 @@ where with_redoxfs(disk_redoxfs, disk_option.password_opt, callback) } @@ -778,10 +805,13 @@ index 4e077a9..b11d5b8 100644 + fscommon::StreamSlice::new(&mut disk_file, disk_efi_start, disk_efi_end)?; + + eprintln!( -+ "Formatting EFI partition with size {:#x}", ++ "Formatting EFI partition as FAT32 (size {:#x})", + disk_efi_end - disk_efi_start + ); -+ fatfs::format_volume(&mut disk_efi, fatfs::FormatVolumeOptions::new())?; ++ fatfs::format_volume( ++ &mut disk_efi, ++ fatfs::FormatVolumeOptions::new().fat_type(fatfs::FatType::Fat32), ++ )?; + + eprintln!("Opening EFI partition"); + let fs = fatfs::FileSystem::new(&mut disk_efi, fatfs::FsOptions::new())?; @@ -863,7 +893,7 @@ index 4e077a9..b11d5b8 100644 #[cfg(not(target_os = "redox"))] pub fn try_fast_install( _fs: &mut redoxfs::FileSystem, -@@ -801,6 +1342,24 @@ pub fn try_fast_install( +@@ -801,6 +1348,24 @@ pub fn try_fast_install( fn install_inner(config: Config, output: &Path) -> Result<()> { println!("Installing to {}:\n{}", output.display(), config); @@ -888,7 +918,7 @@ index 4e077a9..b11d5b8 100644 let cookbook = config.general.cookbook.clone(); let cookbook = cookbook.as_ref().map(|p| p.as_str()); if output.is_dir() { -@@ -823,28 +1382,41 @@ fn install_inner(config: Config, output: &Path) -> Result<()> { +@@ -823,28 +1388,42 @@ fn install_inner(config: Config, output: &Path) -> Result<()> { let live = config.general.live_disk.unwrap_or(false); let password_opt = config.general.encrypt_disk.clone(); let password_opt = password_opt.as_ref().map(|p| p.as_bytes()); @@ -896,7 +926,9 @@ index 4e077a9..b11d5b8 100644 + let (bootloader_bios, bootloader_efi, grub_efi, grub_cfg) = + fetch_bootloaders(&config, cookbook, live)?; if let Some(write_bootloader) = &config.general.write_bootloader { - std::fs::write(write_bootloader, &bootloader_efi)?; +- std::fs::write(write_bootloader, &bootloader_efi)?; ++ let primary_efi = grub_efi.as_deref().unwrap_or(&bootloader_efi); ++ std::fs::write(write_bootloader, primary_efi)?; } + let filesystem_type = match config.general.filesystem.as_deref() { + Some("ext4") => FilesystemType::Ext4, diff --git a/local/scripts/grub-install b/local/scripts/grub-install new file mode 100755 index 00000000..1e4a3b50 --- /dev/null +++ b/local/scripts/grub-install @@ -0,0 +1,277 @@ +#!/bin/bash +# grub-install — Install GRUB on a device (Red Bear OS wrapper) +# +# Compatibility: Matches GNU GRUB grub-install CLI conventions. +# Maps standard grub-install switches to Red Bear OS cookbook/ESP workflow. +# +# Usage: +# grub-install [OPTIONS] INSTALL_DEVICE +# grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=REDBEAR /dev/sda +# grub-install --target=x86_64-efi --removable /dev/sda +# +# For disk images (Red Bear OS build workflow): +# grub-install --target=x86_64-efi --disk-image=build/x86_64/harddrive.img +# +# Differences from Linux GRUB: +# --disk-image=FILE Install into a disk image file instead of a block device +# Modules are pre-selected for Red Bear OS (chainload to Redox bootloader) +# No NVRAM/efibootmgr integration (Red Bear OS uses removable boot path) + +set -euo pipefail + +PROG="$(basename "$0")" + +# Defaults +TARGET="" +EFI_DIRECTORY="" +BOOTLOADER_ID="REDBEAR" +BOOT_DIRECTORY="" +DISK_IMAGE="" +MODULES="" +REMOVABLE=0 +VERBOSE=0 +INSTALL_DEVICE="" + +usage() { + cat <&2 + echo "Try '$PROG --help' for more information." >&2 + exit 1 + ;; + *) + INSTALL_DEVICE="$1" + shift + ;; + esac +done + +[ "$VERBOSE" -eq 1 ] && echo "$PROG: target=$TARGET efi_directory=$EFI_DIRECTORY bootloader_id=$BOOTLOADER_ID removable=$REMOVABLE disk_image=$DISK_IMAGE device=$INSTALL_DEVICE" + +# Validate target +if [ -n "$TARGET" ] && [ "$TARGET" != "x86_64-efi" ]; then + echo "$PROG: unsupported target '$TARGET' (only x86_64-efi is supported on Red Bear OS)" >&2 + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +FAT_TOOL="$SCRIPT_DIR/fat_tool.py" + +# Mode 1: Disk image installation (Red Bear OS build workflow) +if [ -n "$DISK_IMAGE" ]; then + if [ ! -f "$DISK_IMAGE" ]; then + echo "$PROG: disk image not found: $DISK_IMAGE" >&2 + exit 1 + fi + + if [ ! -f "$FAT_TOOL" ]; then + echo "$PROG: fat_tool.py not found at $FAT_TOOL" >&2 + exit 1 + fi + + # Find GRUB EFI binary + GRUB_EFI="" + for f in "$REPO_ROOT/local/recipes/core/grub/target/x86_64-unknown-redox/stage/usr/lib/boot/grub.efi" \ + "$REPO_ROOT/repo/x86_64-unknown-redox/grub/root/usr/lib/boot/grub.efi"; do + if [ -f "$f" ]; then + GRUB_EFI="$f" + break + fi + done + + if [ -z "$GRUB_EFI" ]; then + echo "$PROG: grub.efi not found. Build the GRUB recipe first: make r.grub" >&2 + exit 1 + fi + + [ "$VERBOSE" -eq 1 ] && echo "$PROG: using grub.efi: $GRUB_EFI" + + # Find Redox bootloader + REDBEAR_EFI="" + for f in "$REPO_ROOT/local/recipes/core/bootloader/target/x86_64-unknown-redox/stage/usr/lib/boot/bootloader.efi" \ + "$REPO_ROOT/repo/x86_64-unknown-redox/bootloader/root/usr/lib/boot/bootloader.efi"; do + if [ -f "$f" ]; then + REDBEAR_EFI="$f" + break + fi + done + + if [ -z "$REDBEAR_EFI" ]; then + echo "$PROG: bootloader.efi not found. Build the bootloader recipe first." >&2 + exit 1 + fi + + [ "$VERBOSE" -eq 1 ] && echo "$PROG: using bootloader.efi: $REDBEAR_EFI" + + # Find grub.cfg + GRUB_CFG="$REPO_ROOT/local/recipes/core/grub/grub.cfg" + if [ ! -f "$GRUB_CFG" ]; then + echo "$PROG: grub.cfg not found at $GRUB_CFG" >&2 + exit 1 + fi + + # ESP at LBA 2048 (standard Redox GPT layout) + ESP_OFFSET=$((2048 * 512)) + + # Write GRUB to ESP + BOOT_PATH="EFI/BOOT" + if [ "$REMOVABLE" -eq 0 ] && [ -n "$BOOTLOADER_ID" ]; then + BOOT_PATH="EFI/BOOT" + fi + + "$FAT_TOOL" mkdir "$DISK_IMAGE" "$ESP_OFFSET" "EFI" 2>/dev/null || true + "$FAT_TOOL" mkdir "$DISK_IMAGE" "$ESP_OFFSET" "$BOOT_PATH" 2>/dev/null || true + "$FAT_TOOL" mkdir "$DISK_IMAGE" "$ESP_OFFSET" "EFI/REDBEAR" 2>/dev/null || true + + "$FAT_TOOL" cp-in "$DISK_IMAGE" "$ESP_OFFSET" "$GRUB_EFI" "$BOOT_PATH/BOOTX64.EFI" + [ "$VERBOSE" -eq 1 ] && echo "$PROG: installed GRUB to $BOOT_PATH/BOOTX64.EFI" + + "$FAT_TOOL" cp-in "$DISK_IMAGE" "$ESP_OFFSET" "$GRUB_CFG" "$BOOT_PATH/grub.cfg" + [ "$VERBOSE" -eq 1 ] && echo "$PROG: installed grub.cfg to $BOOT_PATH/grub.cfg" + + "$FAT_TOOL" cp-in "$DISK_IMAGE" "$ESP_OFFSET" "$REDBEAR_EFI" "EFI/REDBEAR/redbear.efi" + [ "$VERBOSE" -eq 1 ] && echo "$PROG: installed Redox bootloader to EFI/REDBEAR/redbear.efi" + + echo "Installation finished. No error reported." + exit 0 +fi + +# Mode 2: Full image build via Red Bear installer (INSTALL_DEVICE given) +if [ -n "$INSTALL_DEVICE" ]; then + echo "$PROG: installing to $INSTALL_DEVICE via Red Bear installer..." >&2 + echo "$PROG: for Red Bear OS, use: make all CONFIG_NAME=redbear-full-grub" >&2 + echo "$PROG: or: redox_installer -c redbear-full-grub.toml $INSTALL_DEVICE" >&2 + exit 1 +fi + +# No device or disk image given +echo "$PROG: no installation device specified." >&2 +echo "Try '$PROG --help' for more information." >&2 +exit 1 diff --git a/local/scripts/grub-mkconfig b/local/scripts/grub-mkconfig new file mode 100755 index 00000000..e49e7f06 --- /dev/null +++ b/local/scripts/grub-mkconfig @@ -0,0 +1,115 @@ +#!/bin/bash +# grub-mkconfig — Generate GRUB configuration file (Red Bear OS wrapper) +# +# Compatibility: Matches GNU GRUB grub-mkconfig CLI conventions. +# Generates a grub.cfg for chainloading the Redox bootloader. +# +# Usage: +# grub-mkconfig [-o FILE] +# grub-mkconfig --output=/boot/grub/grub.cfg + +set -euo pipefail + +PROG="$(basename "$0")" +OUTPUT="" +TIMEOUT=5 +DEFAULT=0 + +usage() { + cat <&2 + echo "Try '$PROG --help' for more information." >&2 + exit 1 + ;; + esac +done + +generate_config() { + cat </dev/null || date) +# Red Bear OS GRUB configuration — chainloads the Redox bootloader + +set timeout=$TIMEOUT +set default=$DEFAULT + +menuentry "Red Bear OS" { + chainloader /EFI/REDBEAR/redbear.efi + boot +} + +menuentry "Reboot" { + reboot +} + +menuentry "Shutdown" { + halt +} +GRUBCFG +} + +if [ -n "$OUTPUT" ]; then + generate_config > "$OUTPUT" +else + generate_config +fi