Skip to content

elfshoe - Getting Started

This guide walks you through creating your first iPXE boot menu with elfshoe.

Prerequisites

Before using elfshoe, you need:

  1. Server infrastructure - DHCP, TFTP, and HTTP servers configured for network booting
  2. See the Server Setup Guide for complete instructions
  3. Includes getting iPXE boot files, configuring DHCP/TFTP, and setting up HTTP delivery

  4. Python 3.10 or higher - For running elfshoe (it probably works on earlier but is untested)

  5. Network boot enabled - On client machines (BIOS/UEFI settings)

New to network booting? Start with the Server Setup Guide to get your infrastructure ready.

How It Works

  1. Configure - Define OS distributions in config.yaml
  2. Generate - Run elfshoe to create elfshoe.ipxe
  3. Deploy - Serve elfshoe.ipxe from your web server
  4. Boot - Client machines PXE boot and display your menu

Quick Example

1. Create config.yaml

Start with the example configuration:

# Copy the example config
cp docs/examples/config.yaml config.yaml

# Or view it at: docs/examples/config.yaml

Example configuration:

# iPXE Menu Generator Configuration
# Complete example showing all features

# Main menu settings
menu:
  title: "Network Boot Menu"
  default_item: "fedora_menu"
  timeout: 30000  # milliseconds (30 seconds)
  error_timeout: 30000  # milliseconds - how long to show errors before returning to menu

# Distribution configurations
# Each distribution can be either "dynamic" (auto-fetch versions) or "static" (manual versions)
distributions:

  # Example 1: Dynamic distribution with metadata provider
  # Fedora versions are fetched automatically from releases.json
  fedora:
    enabled: true
    label: "Fedora"
    type: "dynamic"
    metadata_provider: "fedora"  # Use Fedora metadata fetcher
    metadata_url: "https://fedoraproject.org/releases.json"

    # Metadata filters control which versions/architectures to include
    metadata_filter:
      variant: "Server"  # Only Server variant (other options: Workstation, Cloud, etc.)
      architectures: [x86_64, aarch64]  # Fetch these architectures from metadata
      latest: 3  # Only show 3 most recent versions

    # Architecture name mapping: iPXE uses different names than distributions
    arch_map:
      arm64: aarch64  # Map iPXE "arm64" to Fedora's "aarch64"
      x86_64: x86_64  # iPXE and Fedora both use "x86_64"

    # URL template with {version} and {arch} placeholders
    url_template: "http://download.fedoraproject.org/pub/fedora/linux/releases/{version}/Server/{arch}/os"

    boot_files:
      kernel: "images/pxeboot/vmlinuz"
      initrd: "images/pxeboot/initrd.img"

    boot_params: "inst.repo={base_url}/"

  # Example 2: Static distribution without architecture mapping
  # CentOS versions are specified manually
  centos:
    enabled: true
    label: "CentOS Stream"
    type: "static"

    # Manually specify versions and their supported architectures
    versions:
      - version: "10"
        architectures: [x86_64]  # Stream 10 only supports x86_64 currently
      - version: "9"
        architectures: [x86_64, arm64]  # Stream 9 supports both

    url_template: "http://mirror.stream.centos.org/{version}-stream/BaseOS/{arch}/os"

    boot_files:
      kernel: "images/pxeboot/vmlinuz"
      initrd: "images/pxeboot/initrd.img"

    boot_params: "inst.repo={base_url}/"

  # Example 3: Static distribution with arch_map and release names
  # Debian uses different architecture names than iPXE
  debian:
    enabled: true
    label: "Debian"
    type: "static"

    # Debian calls x86_64 "amd64" - use arch_map to translate
    arch_map:
      x86_64: amd64  # iPXE "x86_64" → Debian "amd64"
      arm64: arm64   # Both use "arm64"

    versions:
      - version: "bookworm"
        name: "12 Bookworm"  # Optional: human-friendly release name
        architectures: [x86_64, arm64]
      - version: "trixie"
        name: "13 Trixie"
        architectures: [x86_64, arm64]

    url_template: "http://deb.debian.org/debian/dists/{version}/main/installer-{arch}/current/images/netboot/debian-installer/{arch}"

    boot_files:
      kernel: "linux"
      initrd: "initrd.gz"

    boot_params: ""

# Additional menu items (non-distribution options)
additional_items:
  # Chain to another iPXE script
  - id: "netboot"
    label: "netboot.xyz"
    type: "chain"
    url: "http://boot.netboot.xyz"

  # Open iPXE shell
  - id: "shell"
    label: "iPXE Shell"
    type: "shell"

  # Exit to BIOS/UEFI
  - id: "exit"
    label: "Exit to BIOS"
    type: "exit"

2. Generate menu

elfshoe

3. Result

You get a elfshoe.ipxe file that looks like:

#!ipxe
dhcp

:start
menu Network Boot Menu
item --gap -- Operating Systems:
item fedora_menu Fedora
item centos_menu CentOS Stream
item debian_menu Debian
item --gap -- Other Options:
item netboot netboot.xyz
item --gap -- Advanced:
item shell iPXE Shell
item exit Exit to BIOS
choose --default fedora_menu --timeout 30000 target && goto ${target}

:fedora_menu
# Architecture-aware menu - shows only matching client architecture
menu Fedora - Select Version
iseq ${buildarch} x86_64 && item fedora_43_x86_64 Fedora 43 Server (x86_64) ||
iseq ${buildarch} arm64 && item fedora_43_arm64 Fedora 43 Server (aarch64) ||
iseq ${buildarch} x86_64 && item fedora_42_x86_64 Fedora 42 Server (x86_64) ||
iseq ${buildarch} arm64 && item fedora_42_arm64 Fedora 42 Server (aarch64) ||
item --gap --
item back_fedora_menu Back to main menu
choose --default fedora_43_x86_64 target && goto ${target}

:fedora_43_x86_64
initrd http://download.fedoraproject.org/pub/fedora/linux/releases/43/Server/x86_64/os/images/pxeboot/initrd.img
chain http://download.fedoraproject.org/pub/fedora/linux/releases/43/Server/x86_64/os/images/pxeboot/vmlinuz initrd=initrd.img inst.repo=http://download.fedoraproject.org/pub/fedora/linux/releases/43/Server/x86_64/os/ || goto fedora_menu_error

:fedora_43_arm64
initrd http://download.fedoraproject.org/pub/fedora/linux/releases/43/Server/aarch64/os/images/pxeboot/initrd.img
chain http://download.fedoraproject.org/pub/fedora/linux/releases/43/Server/aarch64/os/images/pxeboot/vmlinuz initrd=initrd.img inst.repo=http://download.fedoraproject.org/pub/fedora/linux/releases/43/Server/aarch64/os/ || goto fedora_menu_error

# ... similar sections for other versions ...

Key features:

  • Architecture-aware menus - x86_64 clients only see x86_64 options, ARM64 clients only see ARM64 options
  • Automatic filtering - Uses iPXE's iseq ${buildarch} to show only compatible entries
  • Human-friendly labels - "Fedora 43 Server (x86_64)" clearly shows what you're booting
  • Error handling - Built-in navigation and error recovery

Key Concepts

Architecture Support

elfshoe automatically handles multiple CPU architectures (x86_64, ARM64, etc.) with smart client filtering:

What you configure:

distributions:
  fedora:
    type: dynamic
    metadata_filter:
      variant: Server
      architectures: [x86_64, aarch64]  # Fetch both architectures
    arch_map:
      arm64: aarch64  # Map iPXE's arm64 to Fedora's aarch64

What clients see:

x86_64 client menu:

Fedora - Select Version
  • Fedora 43 Server (x86_64)
  • Fedora 42 Server (x86_64)
  • Fedora 41 Server (x86_64)

ARM64 client menu:

Fedora - Select Version
  • Fedora 43 Server (aarch64)
  • Fedora 42 Server (aarch64)
  • Fedora 41 Server (aarch64)

How it works:

  • Uses iPXE's ${buildarch} variable to detect client architecture
  • iseq ${buildarch} x86_64 && item ... shows items only to matching clients
  • No wrong architecture served - clients only see what works for them

Supported architectures:

  • x86_64 - 64-bit x86 (most common)
  • arm64 - 64-bit ARM (Raspberry Pi 4, Apple Silicon, etc.)
  • i386 - 32-bit x86 (legacy)
  • arm - 32-bit ARM (older devices)

Static vs Dynamic Distributions

Static - You specify versions and architectures manually:

distributions:
  debian:
    type: "static"
    arch_map:
      x86_64: amd64  # Map iPXE names to Debian names
    versions:
      - version: "bookworm"
        name: "12 Bookworm"
        architectures: [x86_64, arm64]

Dynamic - Versions and architectures fetched automatically from metadata:

distributions:
  fedora:
    type: "dynamic"
    metadata_provider: "fedora"
    metadata_url: "https://fedoraproject.org/releases.json"
    metadata_filter:
      variant: Server
      architectures: [x86_64, aarch64]

URL Validation

By default, the generator checks that kernel/initrd files exist before adding them to the menu:

  • Fast mode (elfshoe --no-validate): Skip validation (~1 second)
  • Validated mode (elfshoe): Check all URLs (~30-45 seconds)

Use fast mode during development, validated mode before deployment.

Note: With multi-architecture support, each architecture is validated separately, so validation may take longer for distributions with multiple architectures.

Architecture Overview

config.yaml → Builder → MenuGenerator → elfshoe.ipxe
          [Validator]
       [MetadataFetchers]

For detailed architecture, see Architecture Documentation.

Common Workflows

Adding a New Distribution

Option 1: Static (simple)

distributions:
  rocky:
    enabled: true
    type: "static"
    label: "Rocky Linux"
    arch_map:
      arm64: aarch64  # Map architecture names if needed
    versions:
      - version: "9"
        label: "Rocky Linux 9"
        architectures: [x86_64, arm64]  # Optional: specify per version
    url_template: "http://mirror.example.com/rocky/{version}/BaseOS/{arch}/os"
    boot_files:
      kernel: "images/pxeboot/vmlinuz"
      initrd: "images/pxeboot/initrd.img"

Option 2: Dynamic (advanced)

See Adding Distributions for creating metadata fetcher plugins.

Customizing Boot Parameters

boot_params: "console=ttyS0,115200 inst.ks=http://server/kickstart.cfg"

Testing Your Menu

# Quick test without validation
elfshoe --skip-validation

# Check generated menu
cat elfshoe.ipxe

# Validate all URLs work
elfshoe

Performance

  • Without validation: <1 second
  • With validation: 30-45 seconds (depends on network/mirrors)
  • URL checks: Parallel HEAD requests with 10s timeout

Security

  • No code execution - Config is pure YAML data, no code
  • Safe YAML parsing - Uses yaml.safe_load() only
  • URL validation - Prevents broken links in menus
  • Timeouts - All HTTP requests have 10 second timeouts

Next Steps

Deploy Your Menu

Copy the generated elfshoe.ipxe to your HTTP server:

# Example: Copy to nginx web root
sudo cp elfshoe.ipxe /var/www/pxe/

# Or to Apache
sudo cp elfshoe.ipxe /var/www/html/pxe/

Don't have a server set up yet? See the Server Setup Guide for:

  • Installing and configuring DHCP/TFTP servers (dnsmasq, ISC DHCP, Windows)
  • Setting up HTTP servers (nginx, Apache)
  • Getting iPXE boot files
  • Complete deployment instructions

Test Network Booting

  1. Enable network boot in your client's BIOS/UEFI settings
  2. Boot the client - it should:
  3. Get an IP from DHCP
  4. Download iPXE bootloader via TFTP
  5. Load your elfshoe menu via HTTP
  6. Display your boot options

Troubleshooting

Menu doesn't appear:

  • Verify HTTP server is accessible: curl http://your-server/elfshoe.ipxe
  • Check client can reach server (ping, firewall rules)
  • Review DHCP/TFTP logs

URLs fail validation:

  • Check mirror URLs are accessible
  • Try a different mirror
  • Use --skip-validation for testing (not recommended for production)

For infrastructure issues, see the Server Setup Guide - Troubleshooting.

Learn More