Architecture#

Efiboot follows a simple, 3-layer architecture.

  • At the top is the command line interface.

  • In the middle is the facade, which provides the main Python API.

  • At the bottom is the component layer, which consists of the configuration and controller components.

    This document is organized from the top down.

Efiboot architecture diagram Efiboot architecture diagram

Command Line Interface#

Efiboot implements a toolbox-style CLI, similar to git.

Lets take a look at the top-level help message:

$ efiboot --help
Efiboot is a tool for managing EFI boot entries.

Usage:
    efiboot [-vd] [-c <cfg>] [-x <key>=<value>...] <command> [<args>...]
    efiboot --version
    efiboot --help

Options:
    -c <cfg>, --config <cfg>    Override the path to the config.
    -x <key>=<value>            Override a config value. May be repeated.
    -v, --verbose               Log additional information to stderr.
    -d, --debug                 Log debug info to stderr. Implies --verbose.
    -V, --version               Print the version string and exit.
    -h, --help                  Print this help message and exit.

Commands:
    bootnext    Get or set the entry to boot into next.
    push        Push the config to the EFI.
    status      Print EFI boot entries.
    timeout     Get or set the EFI boot timeout.

All invocations start with efiboot and any global options, followed by a subcommand name and any subcommand-specific options.

The top-level efiboot command is equivalent to python3 -m efiboot.cli.

The subcommands live in the efiboot.cli module. Each subcommand is a “command” object, implemented as a regular Python function annotated with the @command decorator.

Let’s take a look at the implementation of the status subcommand.

from efiboot.cli import command
from efiboot.config import Config
from efiboot.facade import EfiBoot

@command
def status(args: Dict[str, Any], config: Config) -> int:
    '''Print EFI boot entries.

    Usage:
        efiboot status
        efiboot status --help

    Options:
        -h, --help    Print this help message and exit.
    '''
    facade = EfiBoot(config)
    facade.print_state()
    return 0

A subcommand takes an argument dictionary and a config (described below) and returns an integer exit code. The docstring of the function describes the CLI using the docopt language. The docstring is used to generate an argument parser for the command.

Subcommands must be registered as an entry point in the efiboot_cli namespace. Third-party packages are also allowed to register additional subcommands. See efiboot.cli for more information.

Subcommands are usually lightweight functions that delegate their main functionality to the facade. When implementing a new subcommand, prefer to add the business logic to the facade class so that users of the Python API also have access to the feature.

The CLI lives under src/efiboot/cli/. The primary entry point is src/efiboot/cli/__main__.py.

All first-party subcommands are registered in the pyproject.toml file.

Python Facade#

The main Python API is provided by the EfiBoot facade class.

This facade combines a Config and an EfiController and provides the highest-level functionality. For example, a facade can push configured boot entries to the EFI variables on your machine.

The EfiBoot constructor can take a Config and an EfiController or it can read the standard config (/boot/efiboot.toml) and pick an appropriate controller.

All significant user-facing features should be implemented as a method on the facade with a simple companion command in the CLI.

The current state of the EFI variables as understood by the facade is available through the member variable EfiBoot.state.

The facade code live in src/efiboot/facade.py.

Configuration Files#

See also

See the Configuration Format document for syntax details.

The configuration is represented by the Config and BootEntry classes. These are both simple dataclasses.

The Config class provides a stack of classmethods for parsing the config from a TOML source:

The default config file path is /boot/efiboot.toml.

The config parsing code lives in src/efiboot/config.py.

Controllers and State#

At its core, efiboot is a tool for manipulating EFI variables. We use the EfiController and EfiState classes to accomplish this.

An EfiState is a simple dataclass representing the contents of EFI variables. It tracks boot entries as integer IDs, which are typically printed as 4-digit hex values. State objects are immutable.

An EfiController is an object that is capable of manipulating EFI variables. Almost all controller methods return a new state object. Since APIs for manipulating EFI variables differ by platform, the EfiController is an abstract base class. Concrete controllers are provided by backends.

Controllers are usually created using the EfiController.from_config() classmethod, which allows controllers to read values from the config during initialization.

The controller API lives in src/efiboot/controller.py.

Backends#

Backends provide a concrete controller class for a particular platform.

Backends are described by BackendMetadata objects. The backend metadata describes what platforms the backend is compatible with and instructs efiboot how to import and construct the corresponding controller class.

The main backend is called default. This backend implements a proxy pattern. Depending on the users current platform, the default backend selects an appropriate controller class from among the other backends and delegates all functionality.

Backends may support additional options in the config.

Backends must be registered as an entry point in the efiboot_backends namespace. Like CLI commands, backends can be provided by third-party packages. See efiboot.backends for details.

The backend code lives under src/efiboot/backends/.

All first-party backends are registered in the pyproject.toml file.