cargo-image-runner 0.5.0

A generic, customizable runner for building and booting kernel/embedded images with Limine, GRUB, QEMU, and more
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
# cargo-image-runner

A generic, highly customizable embedded/kernel development runner for Rust. Build and run bootable images with support for multiple bootloaders, image formats, and boot types.

## Features

- **Multiple Bootloaders**: Limine, GRUB, or direct boot (no bootloader)
- **Multiple Image Formats**: Directory (for QEMU), ISO, FAT
- **Multiple Boot Types**: BIOS, UEFI, or hybrid
- **I/O Capture & Streaming**: Capture serial output, react to patterns, send input programmatically
- **Trait-Based Architecture**: Easy to extend with custom bootloaders, image builders, and runners
- **Builder Pattern API**: Ergonomic, fluent API for programmatic use
- **Template Variables**: Powerful variable substitution in config files
- **Test Integration**: Automatic test detection and execution
- **Environment Variable Overrides**: Runtime configuration without editing files
- **Profile System**: Named configuration presets for different workflows
- **CLI Arg Passthrough**: Pass extra QEMU arguments via `--`

## Quick Start

### Installation

Add to your `Cargo.toml`:

```toml
[build-dependencies]
cargo-image-runner = "0.5"
```

Or install as a binary:

```bash
cargo install cargo-image-runner
```

### Basic UEFI Direct Boot

The simplest setup - boots your UEFI executable directly without a bootloader:

**Cargo.toml:**
```toml
[package.metadata.image-runner.boot]
type = "uefi"

[package.metadata.image-runner.bootloader]
kind = "none"

[package.metadata.image-runner.image]
format = "directory"

# Configure as cargo runner
[target.x86_64-unknown-none]
runner = "cargo-image-runner"
```

Then just run:
```bash
cargo run
```

### With Limine Bootloader

For a full bootloader experience with both BIOS and UEFI support:

**Cargo.toml:**
```toml
[package.metadata.image-runner.boot]
type = "hybrid"  # Supports both BIOS and UEFI

[package.metadata.image-runner.bootloader]
kind = "limine"
config-file = "limine.conf"

[package.metadata.image-runner.bootloader.limine]
version = "v8.4.0-binary"  # Use a specific Limine version

[package.metadata.image-runner.image]
format = "directory"

[package.metadata.image-runner.variables]
TIMEOUT = "5"
KERNEL_CMDLINE = "quiet"
```

**limine.conf:**
```
timeout: {{TIMEOUT}}

/My Kernel
    protocol: limine
    kernel_path: boot():/boot/{{EXECUTABLE_NAME}}
    cmdline: {{KERNEL_CMDLINE}}
```

The runner will automatically:
1. Fetch Limine binaries from GitHub (cached)
2. Process template variables in limine.conf
3. Copy your kernel and bootloader files
4. Run in QEMU with UEFI firmware

## Configuration Reference

All configuration lives under `[package.metadata.image-runner]` in your `Cargo.toml`. Workspace-level defaults can be set under `[workspace.metadata.image-runner]`.

### Boot Configuration

```toml
[package.metadata.image-runner.boot]
type = "uefi"    # Options: "bios", "uefi", "hybrid"
```

| Value | Description |
|-------|-------------|
| `bios` | BIOS boot only |
| `uefi` | UEFI boot only (default) |
| `hybrid` | Both BIOS and UEFI support |

### Bootloader Configuration

#### No Bootloader (Direct Boot)
```toml
[package.metadata.image-runner.bootloader]
kind = "none"
```

#### Limine Bootloader
```toml
[package.metadata.image-runner.bootloader]
kind = "limine"
config-file = "limine.conf"     # Path to your limine.conf (relative to workspace root)
extra-files = []                # Additional files to copy into the image

[package.metadata.image-runner.bootloader.limine]
version = "v8.x-binary"        # Git tag from limine repo (default: "v8.x-binary")
```

**Available Limine versions:** Check [Limine releases](https://github.com/limine-bootloader/limine/releases) for tags like `v8.4.0-binary`, `v8.3.0-binary`, etc. Always use the `-binary` suffix.

#### GRUB Bootloader
```toml
[package.metadata.image-runner.bootloader]
kind = "grub"

[package.metadata.image-runner.bootloader.grub]
modules = []                    # GRUB modules to include
```

### Image Configuration

```toml
[package.metadata.image-runner.image]
format = "directory"            # Options: "directory", "iso", "fat"
output = "custom-name.iso"      # Optional: custom output path
volume_label = "MYKERNEL"       # Optional: volume label (default: "BOOT")
```

| Format | Description | Requires Feature |
|--------|-------------|-----------------|
| `directory` | Directory structure (works with QEMU `fat:rw:`) | *(always available)* |
| `iso` | ISO 9660 image | `iso` |
| `fat` | FAT filesystem image | `fat` |

### Runner Configuration

```toml
[package.metadata.image-runner.runner]
kind = "qemu"                   # Currently the only runner

[package.metadata.image-runner.runner.qemu]
binary = "qemu-system-x86_64"   # QEMU binary to use
machine = "q35"                  # Machine type
memory = 1024                    # RAM in MB
cores = 1                        # Number of CPU cores
kvm = true                       # Enable KVM acceleration (Linux only)
extra_args = []                  # Additional QEMU arguments (always applied)
```

### Serial Configuration

```toml
[package.metadata.image-runner.runner.qemu.serial]
mode = "mon:stdio"              # Serial mode (see table below)
separate-monitor = false        # Separate QEMU monitor from serial port
```

| Mode | QEMU Flag | Description |
|------|-----------|-------------|
| `mon:stdio` | `-serial mon:stdio` | Serial + monitor multiplexed on stdio (default) |
| `stdio` | `-serial stdio` | Serial only on stdio |
| `none` | `-serial none` | No serial output |

When using an I/O handler via the programmatic API, serial is automatically set to `stdio` with the monitor disabled, regardless of this setting.

### Test Configuration

```toml
[package.metadata.image-runner.test]
success-exit-code = 33          # Exit code that indicates test success
extra-args = [                  # Additional args for test runs only
    "-device", "isa-debug-exit,iobase=0xf4,iosize=0x4"
]
timeout = 60                    # Test timeout in seconds
```

Test binaries are automatically detected by examining the executable name for Cargo's hash suffix pattern (e.g., `my-test-a1b2c3d4e5f6a7b8`).

### Run Configuration

```toml
[package.metadata.image-runner.run]
extra-args = [                  # Additional args for normal (non-test) runs only
    "-no-reboot",
    "-serial", "stdio"
]
gui = false                     # Use GUI display
```

### Verbose Output

```toml
verbose = true                  # Enable verbose output (show build progress messages)
```

### Template Variables

Define custom variables for use in bootloader config files:

```toml
[package.metadata.image-runner.variables]
TIMEOUT = "5"
KERNEL_CMDLINE = "quiet loglevel=3"
CUSTOM_VAR = "value"
```

**Built-in variables** (always available, cannot be overridden by config):

| Variable | Description |
|----------|-------------|
| `{{EXECUTABLE}}` | Full path to the executable |
| `{{EXECUTABLE_NAME}}` | Executable filename only |
| `{{WORKSPACE_ROOT}}` | Project workspace root |
| `{{OUTPUT_DIR}}` | Output directory path |
| `{{IS_TEST}}` | `1` if running tests, `0` otherwise |

**Syntax:** Use `{{VAR}}` or `$VAR` in your config files.

**Variable layering** (later overrides earlier):
1. Config variables (`[variables]`)
2. Environment variables (`CARGO_IMAGE_RUNNER_VAR_*`)
3. Built-in variables (always win)

## Environment Variable Overrides

Override any configuration at runtime without editing files. Useful for debugging, CI/CD, and quick experiments.

### Key Config Field Overrides

| Environment Variable | Overrides | Example |
|---------------------|-----------|---------|
| `CARGO_IMAGE_RUNNER_QEMU_BINARY` | QEMU binary path | `qemu-system-aarch64` |
| `CARGO_IMAGE_RUNNER_QEMU_MEMORY` | Memory in MB | `4096` |
| `CARGO_IMAGE_RUNNER_QEMU_CORES` | CPU cores | `4` |
| `CARGO_IMAGE_RUNNER_QEMU_MACHINE` | Machine type | `virt` |
| `CARGO_IMAGE_RUNNER_BOOT_TYPE` | Boot type | `bios`, `uefi`, `hybrid` |
| `CARGO_IMAGE_RUNNER_VERBOSE` | Verbose output | `1`, `true`, `yes` |
| `CARGO_IMAGE_RUNNER_KVM` | KVM acceleration | `1`/`true`/`yes` or `0`/`false`/`no` |
| `CARGO_IMAGE_RUNNER_SERIAL_MODE` | Serial mode | `mon:stdio`, `stdio`, `none` |

Invalid values are silently ignored (the config file value is kept).

### Extra QEMU Arguments

```bash
# Whitespace-separated, appended to the QEMU command line
CARGO_IMAGE_RUNNER_QEMU_ARGS="-s -S -device virtio-net" cargo run
```

### Template Variables from Environment

```bash
# Set/override template variables: CARGO_IMAGE_RUNNER_VAR_<NAME>=<value>
CARGO_IMAGE_RUNNER_VAR_TIMEOUT=10 CARGO_IMAGE_RUNNER_VAR_DEBUG=1 cargo run
```

The `VAR_` prefix is stripped, so `CARGO_IMAGE_RUNNER_VAR_TIMEOUT=10` sets the template variable `TIMEOUT` to `10`.

## Profile System

Profiles let you define named configuration presets and switch between them with an environment variable.

### Defining Profiles

Add profiles under `[package.metadata.image-runner.profiles.<name>]`:

```toml
[package.metadata.image-runner.boot]
type = "uefi"

[package.metadata.image-runner.runner.qemu]
memory = 1024

# Debug profile: more memory, GDB server, verbose
[package.metadata.image-runner.profiles.debug]
verbose = true

[package.metadata.image-runner.profiles.debug.runner.qemu]
memory = 4096
extra_args = ["-s", "-S"]

[package.metadata.image-runner.profiles.debug.variables]
DEBUG = "1"

# CI profile: no KVM, no GUI
[package.metadata.image-runner.profiles.ci]

[package.metadata.image-runner.profiles.ci.runner.qemu]
kvm = false

[package.metadata.image-runner.profiles.ci.variables]
CI = "1"
```

### Activating a Profile

```bash
CARGO_IMAGE_RUNNER_PROFILE=debug cargo run
```

Profile values are **deep-merged** into the base config:
- Object fields merge recursively (only specified keys are overridden)
- Scalars and arrays are replaced entirely
- Unspecified fields keep their base values

If the profile name doesn't exist, an error is returned listing available profiles.

### Profile Sources

Profiles can be defined at both workspace and package level. Package-level profiles override workspace-level profiles with the same name.

## Configuration Layering

All configuration follows a strict priority order (later overrides earlier):

### Config Values

| Priority | Source |
|----------|--------|
| 1 (lowest) | Built-in defaults |
| 2 | Workspace metadata (`[workspace.metadata.image-runner]`) |
| 3 | Package metadata (`[package.metadata.image-runner]`) |
| 4 | Standalone TOML file (if provided via API) |
| 5 | Profile overlay (`CARGO_IMAGE_RUNNER_PROFILE`) |
| 6 (highest) | Individual env var overrides (`CARGO_IMAGE_RUNNER_*`) |

### QEMU Extra Args (appended in order)

| Priority | Source |
|----------|--------|
| 1 (first) | `extra_args` from `[runner.qemu]` config |
| 2 | `extra-args` from `[test]` or `[run]` (based on mode) |
| 3 | `CARGO_IMAGE_RUNNER_QEMU_ARGS` env var |
| 4 (last) | CLI `-- args` passthrough |

All sources are appended (not replaced), so args from all layers are present.

### Template Variables

| Priority | Source |
|----------|--------|
| 1 (lowest) | Config variables (`[variables]`) |
| 2 | Environment variables (`CARGO_IMAGE_RUNNER_VAR_*`) |
| 3 (highest) | Built-in variables (`EXECUTABLE`, `WORKSPACE_ROOT`, etc.) |

## CLI Usage

```bash
# Run an executable
cargo-image-runner path/to/executable

# Run with explicit subcommand
cargo-image-runner run path/to/executable

# Pass extra QEMU arguments via --
cargo-image-runner run path/to/executable -- -s -S

# Build image without running
cargo-image-runner build path/to/executable

# Check configuration (shows active profile, env overrides, QEMU settings)
cargo-image-runner check

# Clean build artifacts
cargo-image-runner clean

# Show version
cargo-image-runner version
```

### As a Cargo Runner

Configure in `.cargo/config.toml`:

```toml
[target.x86_64-unknown-none]
runner = "cargo-image-runner"
```

Then `cargo run` and `cargo test` work directly.

## Programmatic API

Use cargo-image-runner as a library:

```rust
use cargo_image_runner::builder;

fn main() -> cargo_image_runner::Result<()> {
    builder()
        .from_cargo_metadata()?
        .executable("path/to/kernel")
        .extra_args(vec!["-s".into(), "-S".into()])
        .run()
}
```

### Builder Methods

| Method | Description |
|--------|-------------|
| `.from_cargo_metadata()?` | Load config from Cargo.toml (includes profiles + env overrides) |
| `.from_config_file(path)?` | Load from a standalone TOML file |
| `.with_config(config)` | Set config directly |
| `.executable(path)` | Set the kernel/executable path |
| `.workspace_root(path)` | Set workspace root |
| `.extra_args(vec)` | Set CLI passthrough QEMU args |
| `.no_bootloader()` | Use no bootloader |
| `.limine()` | Use Limine bootloader |
| `.grub()` | Use GRUB bootloader |
| `.directory_output()` | Output as directory |
| `.iso_image()` | Output as ISO |
| `.fat_image()` | Output as FAT image |
| `.qemu()` | Use QEMU runner |
| `.io_handler(handler)` | Set an I/O handler for serial capture/streaming |
| `.build()?` | Build `ImageRunner` (does not execute) |
| `.run()?` | Build and immediately execute |
| `.run_with_result()?` | Build, execute, and return the full `RunResult` |

### Custom Bootloader

Implement the `Bootloader` trait to add custom bootloader support:

```rust
use cargo_image_runner::bootloader::{Bootloader, BootloaderFiles, ConfigFile};
use cargo_image_runner::config::BootType;
use cargo_image_runner::core::{Context, Result};

struct MyBootloader;

impl Bootloader for MyBootloader {
    fn prepare(&self, ctx: &Context) -> Result<BootloaderFiles> {
        let files = BootloaderFiles::new();
        Ok(files)
    }

    fn config_files(&self, ctx: &Context) -> Result<Vec<ConfigFile>> {
        Ok(Vec::new())
    }

    fn boot_type(&self) -> BootType {
        BootType::Uefi
    }

    fn name(&self) -> &str {
        "MyBootloader"
    }
}

fn main() -> cargo_image_runner::Result<()> {
    cargo_image_runner::builder()
        .from_cargo_metadata()?
        .bootloader(MyBootloader)
        .run()
}
```

### I/O Capture & Streaming

The `IoHandler` trait enables programmatic interaction with QEMU's serial port. Built-in handlers cover common use cases, and you can implement custom handlers for advanced scenarios.

#### Capture Output for Assertions

```rust
use cargo_image_runner::{builder, Config, CaptureHandler};

fn main() -> cargo_image_runner::Result<()> {
    let config = Config::from_toml_str(r#"
        [boot]
        type = "uefi"
        [bootloader]
        kind = "none"
        [image]
        format = "directory"
    "#)?;

    let result = builder()
        .with_config(config)
        .workspace_root(".")
        .executable("target/x86_64-unknown-none/debug/my-kernel")
        .io_handler(CaptureHandler::new())
        .run_with_result()?;

    // Serial output is available for inspection
    if let Some(output) = &result.captured_output {
        if let Some(serial) = &output.serial {
            assert!(serial.contains("TEST PASSED"));
        }
    }
    Ok(())
}
```

#### React to Output Patterns

```rust
use cargo_image_runner::{builder, Config};
use cargo_image_runner::runner::io::PatternResponder;

fn main() -> cargo_image_runner::Result<()> {
    let config = Config::from_toml_str("...")?;

    // Automatically respond to prompts
    builder()
        .with_config(config)
        .workspace_root(".")
        .executable("target/x86_64-unknown-none/debug/my-kernel")
        .io_handler(
            PatternResponder::new()
                .on_pattern("login:", b"root\n")
                .on_pattern("$ ", b"run-tests\n")
        )
        .run()?;
    Ok(())
}
```

#### Custom I/O Handler

```rust
use cargo_image_runner::{IoHandler, IoAction};

struct PanicDetector {
    panicked: bool,
}

impl IoHandler for PanicDetector {
    fn on_output(&mut self, data: &[u8]) -> IoAction {
        if String::from_utf8_lossy(data).contains("PANIC") {
            self.panicked = true;
            IoAction::Shutdown
        } else {
            IoAction::Continue
        }
    }
}
```

#### Built-in Handlers

| Handler | Description |
|---------|-------------|
| `CaptureHandler` | Accumulates all serial + stderr output, available via `finish()` |
| `TeeHandler` | Captures AND echoes to the real terminal |
| `PatternResponder` | Matches string patterns in serial output and sends responses |

## Examples

Located under `examples/`, each demonstrating a different configuration combination:

| Example | Boot | Bootloader | Image | Notes |
|---------|------|------------|-------|-------|
| `uefi-simple` | UEFI | None | Directory | Simplest possible setup |
| `limine-directory` | Hybrid | Limine | Directory | Fast iteration with Limine |
| `limine-iso` | Hybrid | Limine | ISO | Bootable ISO image |
| `uefi-fat` | UEFI | None | FAT | Real FAT filesystem image |
| `limine-fat` | UEFI | Limine | FAT | Limine with FAT image |
| `bios-limine-iso` | BIOS | Limine | ISO | Legacy BIOS boot |
| `profiles` | UEFI | None | Directory | Profile system and env var overrides |
| `extra-files` | Hybrid | Limine | Directory | Extra files and custom template variables |

## Feature Flags

Minimize dependencies by selecting only the features you need:

```toml
[dependencies]
cargo-image-runner = { version = "0.3", default-features = false, features = ["uefi", "limine", "qemu"] }
```

| Feature | Description | Dependencies |
|---------|-------------|--------------|
| `uefi` | UEFI boot support | `ovmf-prebuilt` |
| `bios` | BIOS boot support | - |
| `limine` | Limine bootloader | `git2` |
| `grub` | GRUB bootloader | - |
| `iso` | ISO image format | `hadris-iso` |
| `fat` | FAT filesystem image | `fatfs`, `fscommon` |
| `qemu` | QEMU runner | - |
| `progress` | Progress reporting | `indicatif` |

**Default features:** `uefi`, `bios`, `limine`, `iso`, `qemu`

## Architecture

cargo-image-runner uses a trait-based pipeline architecture:

```
Bootloader -> ImageBuilder -> Runner
```

### Core Traits

**Bootloader** - Prepares bootloader files and processes configuration:
```rust
pub trait Bootloader {
    fn prepare(&self, ctx: &Context) -> Result<BootloaderFiles>;
    fn config_files(&self, ctx: &Context) -> Result<Vec<ConfigFile>>;
    fn boot_type(&self) -> BootType;
    fn name(&self) -> &str;
}
```

**ImageBuilder** - Builds bootable images in various formats:
```rust
pub trait ImageBuilder {
    fn build(&self, ctx: &Context, files: &[FileEntry]) -> Result<PathBuf>;
    fn output_path(&self, ctx: &Context) -> PathBuf;
    fn supported_boot_types(&self) -> &[BootType];
    fn name(&self) -> &str;
}
```

**Runner** - Executes images:
```rust
pub trait Runner {
    fn run(&self, ctx: &Context, image_path: &Path) -> Result<RunResult>;
    fn run_with_io(&self, ctx: &Context, image_path: &Path,
        handler: &mut dyn IoHandler) -> Result<RunResult>;
    fn is_available(&self) -> bool;
    fn name(&self) -> &str;
}
```

### Module Map

| Module | Role |
|--------|------|
| `core/` | `Context`, `ImageRunnerBuilder`, `ImageRunner`, `Error`/`Result` |
| `config/` | `Config` struct, `ConfigLoader`, `env` (environment variable processing) |
| `bootloader/` | `Bootloader` trait + impls: `limine`, `grub`, `none`; `fetcher` for downloads |
| `image/` | `ImageBuilder` trait + impls: `directory`, `iso`, `fat`; `template` processor |
| `runner/` | `Runner` trait + impl: `qemu`; `io` module for I/O capture/streaming |
| `firmware/` | UEFI firmware (`ovmf`) |
| `util/` | Filesystem helpers (`fs`), hashing (`hash`) |

### Build Artifacts

All build artifacts go to `target/image-runner/`:
- `cache/` - Downloaded files (Limine binaries, OVMF firmware)
- `output/` - Built images

## Troubleshooting

### "limine-bios.sys not found"
Make sure you're using a binary release version like `v8.4.0-binary`, not a source version like `v8.4.0`.

### "revspec not found" error
The Limine version must be a valid git tag. Check available versions at https://github.com/limine-bootloader/limine/releases

### QEMU not found
Install QEMU for your platform:
- **macOS:** `brew install qemu`
- **Linux:** `sudo apt install qemu-system-x86` or `sudo dnf install qemu-system-x86`
- **Windows:** Download from https://www.qemu.org/download/

### Missing OVMF firmware
The runner automatically downloads OVMF firmware for UEFI boot. If you see firmware errors, ensure you have internet connectivity and the `uefi` feature is enabled.

### Profile not found
If you get `profile 'xyz' not found`, check:
1. The profile is defined under `[package.metadata.image-runner.profiles.xyz]`
2. The `CARGO_IMAGE_RUNNER_PROFILE` value matches the profile name exactly

Use `cargo-image-runner check` to see available configuration and active overrides.

## Contributing

Contributions are welcome! The architecture is designed for extensibility:

- **Add a bootloader:** Implement the `Bootloader` trait
- **Add an image format:** Implement the `ImageBuilder` trait
- **Add a runner:** Implement the `Runner` trait

## License

MIT

## Credits

- Built on [Limine]https://github.com/limine-bootloader/limine bootloader
- Uses [OVMF]https://github.com/tianocore/edk2 for UEFI firmware
- Inspired by the Rust OS development community