Write Your OS

Basic Concepts

Once you followed the Setup And Installation to prepare your environment, then you’re ready to write your fist OS.

First of all, lets start from a basic building block:

from lbhelper import build_image
from lbhelper import UpstreamPackages

gnome_desktop_packages = UpstreamPackages(
    packages=[
        "dconf-editor",
        "task-gnome-desktop",
        "task-english",
    ],
    package_set_code="desktop",
)

targets = [
    gnome_desktop_packages
]

if __name__ == '__main__':
  build_image(targets=targets, iso_build_dir=Path("build"), image_name="firstos")

Here are things the sample code does:

  1. Create a target, gnome_desktop_packages, which define 3 upstream packages to install: dconf-editor, task-gnome-desktop and task-english. The upstream means packages to install are from the Debian official package repository.

  2. Passing the target as parameter to the build_image to start build process. Once you run the code with python main.py (assume your filename is main.py):

  • A directory called build will be created.

  • The auto scripts and config will also be generated.

  • Build process will start(depends on your network and machine, it’ll take 20-90 minutes to build) and logs will show on your terminal.

  • When build process finish, you can find an ISO image, firstos-amd64.hybrid.iso, in the build directory.

Now you can boot your first OS via hypervisors like Qemu or VirtualBox with this image.

Utilities From lbhelper

In addition to UpstreamPackages, lbhelper also provides variety of utilities to configure image.

Targets

The lbhelper provides the following targets:

  • UpstreamPackages - Packages to install from Debian official package repository. The package_set_code decides package filename under the config/package-lists.

    UpstreamPackages(
        packages=[
            "dconf-editor",
           "task-gnome-desktop",
            "task-english",
        ],
        package_set_code="desktop",
    )
    
  • CustomDeb - Custom .deb file to install. The get_deb should be a callback function to get file path, which makes file creation dynamic(e.g., download from internet).

    CustomDeb(
        get_deb=lambda: Path("path/to/deb.deb"),
    )
    
  • HookScript - Hooks scripts to run after upstream/customdeb packages installed. The hook_name decides hook scripts filename under the config/hooks/*.

    HookScript(
        get_script_file=lambda: Path("path/to/script.sh"),
        hook_name="somehook.sh"
    )
    
  • StaticFile - Files to include in the binary stage. The target_filepath defines file path in built system, which will also be created with the same file path under config/includes.*/ (e.g. /opt/some/file.txt ),

    StaticFile(
        get_source_file=lambda: Path("path/to/file")
        target_filepath=Path("/opt/some/file.txt")
    )
    
  • AptPreference - Set package preferences for upstream packages. See https://live-team.pages.debian.net/live-manual/html/live-manual/customizing-package-installation.en.html#514 and https://linux.die.net/man/5/apt_preferences for more details. The preference_type denotes the preference should take effects at runtime (for built system, under config/apt/preferences) or build time (for build process, under config/includes.chroot/etc/apt/preferences).

    AptPreference(
        package="libroffice",
        pin="version *",
        pin_priority=-1,
        preference_type=AptPreferenceType.RUN_TIME
    )
    
  • DirectConfig - If you have special requires which targets above cannot satisfy(i.e., customize bootloader), you can create config file under the build directly with this target. Use this target carefully as it might break/override other targets unintentionally. The target_filepath denotes file path under the build directory to write files.

    DirectConfig(
        target_filepath="config/path/to/direct/config", # file will be written to "${build_dir}/config/path/to/direct/config"
        get_source_file=lambda :source_file
    )
    

Note: Build stage follows orders described in Build Process. Carefully managing dependencies to avoid the situation like hook scripts rely files from binary stage. Also, though the orders of config depends on the order of targets passed to build_image, please consider making targets idempotent to reduce complexity and avoid unintentional side effects.

Build Image

The lbhelper provides an all-in-one function, build_image to turn targets into actual config files and start build process. Accepted parameters are:

  • targets – Targets to process. Elements can be Target or List[Target].

  • iso_build_dir – Image build directory path.

  • fresh_build – If True, remove build directory before performing any operations for fresh build.

  • distribution – Base Debian distribution for image. trixie by default.

  • image_name – Name of image. myos by default.

  • skip_build – If True, lb build won’t run. You can use this option to validate configs manually.

Helper Functions

The lbhelper also provides useful helper function to create scripts and files.

  • render_template_to_file - Render a jinja2 template as file.

    render_template_to_file(
        template_path=lambda: Path("path/to/template"),
        target_path=Path("some/path"), # if not given, then rendered file will be written to /tmp with random name.
        arg1="arg1", # args for template
        arg2="arg2", # args for template
    )
    
  • render_template_to_string <lbhelper.render_template_to_string() - Render a jinja2 template as string.

    render_template_to_string(
        template_path=lambda: Path("path/to/template"),
        arg1="arg1",
        arg2="arg2",
    )
    
  • escape_string_for_shell_script - Escape “\” and “$”. Useful when writing strings to files via shell scripts. For instance, when writing strings via:

    cat > file<<EOL
    $SOME_STRING
    EOL
    

    The \ and $ of $SOME_STRING need to be escaped. Otherwise, they’ll be treated as special characters from source script and cause unexpected results.

  • download_file - Download given URL as file under /tmp with random filename.

    discord_download_url = "https://discord.com/api/download?platform=linux"
    
    discord_deb = CustomDeb(
        get_deb=lambda : download_file(discord_download_url),
    )
    

Further Reading

For more comprehensive example using utilities above, check myos.

For more detailed API documents, check lbhelper package.