Skip to content

Architecture Documentation

Overview

The elfshoe uses a modular, plugin-based architecture that separates concerns and makes it easy to extend with new distributions.

Directory Structure

ipxe/
├── src/
│   └── elfshoe/                # Main package
│       ├── __init__.py               # Package exports
│       ├── __main__.py               # Module entry point (python -m)
│       ├── cli.py                    # Command-line interface
│       ├── builder.py                # DistributionBuilder class
│       ├── core/                     # Core functionality
│       │   ├── __init__.py           # Package exports
│       │   ├── models.py             # Data models (BootEntry, DistributionMenu)
│       │   ├── validator.py          # URL validation
│       │   └── generator.py          # Jinja2 menu generation
│       ├── distributions/            # Distribution-specific modules
│       │   ├── __init__.py           # Provider registry
│       │   ├── base.py               # AbstractMetadataFetcher interface
│       │   ├── fedora.py             # Fedora implementation
│       │   └── [future: centos.py, debian.py, windows.py, ...]
│       └── templates/                # Jinja2 templates
│           ├── main_elfshoe.ipxe.j2
│           ├── distribution_submenus.ipxe.j2
│           └── additional_items.ipxe.j2
├── tests/                            # Test suite
│   ├── conftest.py                   # Pytest fixtures
│   ├── test_elfshoe.py        # Model tests
│   ├── test_url_validator.py        # Validation tests
│   └── test_menu_generator.py        # Generation tests
├── config.yaml                       # Main configuration
└── elfshoe.ipxe                         # Generated output

Key Components

1. Core Package (src/core/)

Purpose: Provides reusable components independent of specific distributions.

models.py

  • BootEntry: Dataclass representing a single boot option
  • DistributionMenu: Dataclass representing a distribution submenu

validator.py

  • URLValidator: Validates that boot files exist before including in menu
  • Uses HEAD requests for efficiency
  • Optional validation for faster iteration

generator.py

  • MenuGenerator: Renders iPXE menus using Jinja2 templates
  • Separates presentation logic from business logic
  • Template path configurable for customization

2. Distributions Package (src/distributions/)

Purpose: Pluggable system for distribution-specific metadata fetching.

base.py

  • AbstractMetadataFetcher: Interface that all fetchers must implement
  • Single method: fetch_versions(metadata_url, **filters) -> List[str]

Provider Implementations

  • Each distribution gets its own module (e.g., fedora.py)
  • Implements metadata fetching logic specific to that distribution
  • Returns sorted list of available versions

Registry (distributions/__init__.py)

METADATA_PROVIDERS = {
    'fedora': FedoraMetadataFetcher,
    # Add more providers here
}

3. Command-Line Interface (cli.py)

Purpose: CLI orchestration and argument parsing.

main() function

  • Parses command-line arguments
  • Loads configuration from YAML
  • Orchestrates builder and generator
  • Handles verbose/quiet output

4. Distribution Builder (builder.py)

Purpose: Builds distribution menus from configuration.

DistributionBuilder class

  • Builds distribution menus from configuration
  • Supports two types:
  • Static: Fixed version list from config
  • Dynamic: Fetches versions from metadata provider
  • Handles URL validation
  • Manages verbose/quiet output

Data Flow

┌─────────────┐
│ config.yaml │
└──────┬──────┘
┌──────────────────────┐
│ cli.py               │
│  - Loads config      │
│  - Creates Builder   │
└──────┬───────────────┘
┌──────────────────────────────────┐
│ DistributionBuilder              │
│  - For each distribution:        │
│    • Static → use config versions│
│    • Dynamic → fetch metadata    │
│  - Validates URLs (optional)     │
│  - Creates BootEntry objects     │
└──────┬───────────────────────────┘
       ├─[dynamic]──┐
       │            ▼
       │    ┌────────────────────────┐
       │    │ MetadataFetcher        │
       │    │  - Fetch from source   │
       │    │  - Parse format        │
       │    │  - Return versions     │
       │    └────────┬───────────────┘
       │             │
       ├─────────────┘
┌──────────────────────┐
│ MenuGenerator        │
│  - Renders templates │
│  - Combines sections │
└──────┬───────────────┘
┌──────────────┐
│  elfshoe.ipxe   │
└──────────────┘

Plugin System

How Distribution Plugins Work

  1. Implement the Interface: Create a class inheriting from AbstractMetadataFetcher
  2. Register: Add to METADATA_PROVIDERS dictionary
  3. Configure: Reference in config.yaml with metadata_provider: "name"

Example: Adding Windows Support

Step 1: Create src/distributions/windows.py

from .base import AbstractMetadataFetcher

class WindowsMetadataFetcher(AbstractMetadataFetcher):
    def fetch_versions(self, metadata_url, **filters):
        # Implement Windows-specific logic
        return ['11', '10', '7']

Step 2: Register in src/distributions/__init__.py

from .windows import WindowsMetadataFetcher

METADATA_PROVIDERS = {
    'fedora': FedoraMetadataFetcher,
    'windows': WindowsMetadataFetcher,  # Add this
}

Step 3: Use in config.yaml

distributions:
  windows:
    type: "dynamic"
    metadata_provider: "windows"
    metadata_url: "http://your-server/windows-versions.json"
    # ... rest of config

Design Principles

Separation of Concerns

  • Core: Reusable, distribution-agnostic
  • Distributions: Specific to each OS/distro
  • Templates: Presentation logic only
  • Main: Orchestration and CLI

Open/Closed Principle

  • Open for extension: Add new distributions without modifying existing code
  • Closed for modification: Core components rarely change

Dependency Inversion

  • Core depends on abstractions (AbstractMetadataFetcher)
  • Specific implementations depend on same abstractions
  • No dependency on concrete implementations

Configuration Over Code

  • Everything configurable via YAML
  • No Python changes needed for new static distributions
  • Templates customizable without code changes

Testing Strategy

Unit Tests

  • test_elfshoe.py: Test data models
  • test_url_validator.py: Test URL validation (mocked)
  • test_menu_generator.py: Test menu generation

Integration Tests

  • Run with --no-validate for fast testing
  • Run with validation for complete testing
  • Mock HTTP requests to avoid network dependencies

Manual Testing

# Fast iteration (no network calls)
make fast

# Full validation
make validate

# Custom config
python3 -m elfshoe -c custom.yaml

Extension Points

Add New Distribution

See ADDING_DISTRIBUTIONS.md for detailed guide.

Custom Templates

  1. Copy src/templates/ to custom location
  2. Modify templates as needed
  3. Use with MenuGenerator(config, template_dir=custom_path)

Custom Validation

Extend URLValidator class or implement custom validator.

Additional Boot Types

Modify templates to add new boot types beyond chain/shell/exit.

Performance Considerations

URL Validation

  • Most expensive operation (network I/O)
  • Use --no-validate during development
  • Validation runs in serial (could be parallelized in future)

Metadata Fetching

  • Cached by default (reuse connection)
  • Timeout: 10 seconds
  • Could add persistent caching in future

Template Rendering

  • Negligible overhead
  • Pre-compiled by Jinja2
  • Happens once per invocation

Future Enhancements

Potential Improvements

  1. Parallel URL Validation: Use asyncio or threading for faster validation
  2. Metadata Caching: Cache API responses locally to reduce network calls
  3. Auto-Discovery: Automatically scan distributions/ directory for plugins instead of manual registry (currently requires manual registration in __init__.py)
  4. Configuration Validation: Use JSON Schema to validate config.yaml structure
  5. More Providers: CentOS, Debian, Ubuntu, Windows, BSD metadata fetchers
  6. Multi-Architecture: ARM, aarch64 support in addition to x86_64
  7. CI/CD Integration: GitHub Actions workflow examples
  8. Web UI: Optional web interface for configuration management

Backward Compatibility

  • Configuration format is stable
  • Core API unlikely to change
  • New providers don't affect existing ones

Migration Notes

From Old Architecture

The refactoring (February 2026) moved from a monolithic design to modular:

Before (monolithic file):

  • All code in single elfshoe.py file (401 lines)
  • FedoraMetadataFetcher hardcoded
  • String check for 'fedoraproject.org'

After (modular):

  • Core: Data models, validation, generation
  • Distributions: Plugin system for metadata fetching
  • CLI: User interface and orchestration
  • Easier to test, extend, and maintain

Breaking Changes

  • Import paths changed:
  • from elfshoe import BootEntryfrom core import BootEntry
  • from elfshoe import URLValidatorfrom core import URLValidator
  • Config requires metadata_provider field for dynamic distributions
  • Tests updated to use new imports

Non-Breaking Changes

  • CLI interface unchanged
  • Configuration format mostly unchanged (only metadata_provider added)
  • Template format unchanged
  • Generated elfshoe.ipxe format unchanged