netboot.xyz/AGENTS.md

7.4 KiB

AGENTS.md

Guidelines for AI coding agents working in the netboot.xyz repository.

Project Summary

netboot.xyz generates iPXE bootloaders and menus for 190+ operating systems using Ansible and Jinja2 templates. There is no application code in a traditional sense — the project produces .ipxe scripts and bootloader binaries.

Build Commands

# Syntax check (fast, run first)
ansible-playbook site.yml --syntax-check

# Lint Ansible tasks
ansible-lint -v roles/netbootxyz/tasks

# Full local build (generates menus + bootloaders to /var/www/html)
ansible-playbook site.yml

# Menu-only build (skip bootloader compilation)
ansible-playbook site.yml -e "generate_disks=false generate_checksums=false generate_signatures=false"

# Docker build (outputs to buildout/)
docker build -t localbuild --platform=linux/amd64 -f Dockerfile .
docker run --rm -it --platform=linux/amd64 -v $(pwd):/buildout localbuild

# Release builds
./script/build_release dev      # Development
./script/build_release pr       # Pull request test
./script/build_release rc       # Release candidate
./script/build_release release  # Production
./script/build_release rolling  # Rolling

CI Checks (what PRs must pass)

  1. ansible-playbook site.yml --syntax-check
  2. ansible-lint -v roles/netbootxyz/tasks
  3. Full Docker build via ./script/build_release pr

Testing

Molecule tests exist but require Docker:

pip install molecule molecule-docker
molecule test

There are no unit tests. Validation is done through syntax checks, linting, and full builds.

Code Style

YAML / Ansible

  • Start every YAML file with --- on line 1.
  • Use 2-space indentation consistently.
  • Use fully-qualified collection names (FQCN) for all modules:
    • ansible.builtin.template, ansible.builtin.set_fact, ansible.builtin.shell, etc.
    • Never use short names like template: or shell:.
  • Use snake_case for all variable names: boot_domain, generate_menus, netbootxyz_root.
  • Guard booleans with the | default(true) | bool pattern:
    when:
      - generate_menus | default(true) | bool
    
  • Use when: as a list even for single conditions.
  • Use descriptive name: for every task.
  • The .ansible-lint config skips these rules (do not add workarounds for them):
    • command-instead-of-module, command-instead-of-shell
    • no-changed-when, risky-shell-pipe
    • literal-compare, var-naming[no-role-prefix]

iPXE Templates (.ipxe.j2 files)

  • Start with #!ipxe shebang on line 1.
  • Add a comment header with OS name and URL on lines 2-4.
  • iPXE scripts are flat — do not indent iPXE commands.
  • Use # for comments inside templates.
  • Variable conventions:
    • iPXE runtime variables: ${variable_name} (evaluated at boot time)
    • Jinja2 template variables: {{ variable }} (evaluated at build time)
    • These are often mixed: ${live_endpoint}{{ endpoints.foo.path }}
  • Labels use :label_name in snake_case.
  • Navigation pattern: goto ${menu} || at template start.
  • User selection: choose <var> || goto <exit_label>.
  • Exit pattern: clear menu and exit 0:
    :distro_exit
    clear menu
    exit 0
    
  • Error pattern: echo, prompt, return to menu:
    :error
    echo Error occurred, press any key to return to menu
    prompt
    goto main_menu
    
  • Fallback chains: command || goto fallback for graceful degradation.
  • Architecture mapping varies by OS family:
    • Red Hat: x86_64x86_64, arm64aarch64
    • Debian: x86_64amd64, arm64arm64
  • Guard optional values: isset ${variable} in iPXE, {% if value.field is defined %} in Jinja2.

Jinja2 Patterns

  • Loop over releases: {% for item in releases.<distro>.versions %}
  • Sort by name: {% for key, value in releases.items() | sort(attribute='1.name') %}
  • Filter enabled items: {% if value.enabled is defined and value.enabled | bool %}
  • Template iteration uses with_community.general.filetree in tasks.

OS Definition Schema (defaults/main.yml)

releases:
  distro_key:          # lowercase, no hyphens (e.g., almalinux, rockylinux)
    name: "Display Name"
    mirror: "http://mirror.url"
    base_dir: "path/on/mirror"
    enabled: true
    menu: linux         # one of: linux, bsd, dos, unix
    versions:
      - code_name: "version_id"
        name: "Display Version"

Optional fields: archive_mirror, paths, flavors, platforms, version.

Utility Definition Schema (defaults/main.yml)

utilitiesefi:          # or utilitiespcbios64, utilitiespcbios32, utilitiesarm
  utility_key:
    name: "Display Name"
    enabled: true
    type: direct        # one of: direct, ipxemenu, memdisk, memtest, sanboot
    kernel: "<url>"
    initrd: "<url>"     # optional

File Organization

Path Purpose
site.yml Main playbook entry point
defaults/main.yml All OS/utility definitions and default config
endpoints.yml Live image endpoint URLs
user_overrides.yml Local overrides (not committed)
templates/menu/*.ipxe.j2 ~100 iPXE menu templates
templates/disks/*.j2 Bootloader embedded scripts
templates/pipxe/*.j2 Raspberry Pi Makefile templates
tasks/*.yml 14 Ansible task files
vars/{debian,redhat,ubuntu}.yml Per-distro package lists
script/ Build and release shell scripts

Menu Hierarchy

menu.ipxe (main) → linux.ipxe → ubuntu.ipxe, fedora.ipxe, ...
                  → bsd.ipxe   → freebsd.ipxe, openbsd.ipxe, ...
                  → live.ipxe  → live-ubuntu.ipxe, ...
                  → utils-*.ipxe
                  → windows.ipxe

Menus chain via chain ${menu}.ipxe || goto error. Signature verification gates chaining when sigs_enabled is true.

Error Handling

  • Ansible: Relies on default fail-fast behavior. No block/rescue/always. Guard tasks with when: conditions. Use | default() to prevent undefined variable errors.
  • iPXE: Use command || goto fallback chains. Protocol degradation: HTTPS → HTTP → local boot. Always provide a :error label that prompts the user.
  • Shell scripts: Use set -e at the top of all scripts.

Adding a New Operating System

  1. Add entry to releases: in roles/netbootxyz/defaults/main.yml.
  2. Create roles/netbootxyz/templates/menu/<distro>.ipxe.j2 following existing templates.
  3. The menu template is auto-discovered via filetree iteration — no registration needed.
  4. Add the distro to the appropriate category menu (e.g., linux.ipxe.j2) if it needs a menu entry.
  5. If using live images, add endpoint to endpoints.yml.
  6. Test: ansible-playbook site.yml --syntax-check && ansible-lint -v roles/netbootxyz/tasks

Key Variables

  • boot_domain: Target domain for generated menus.
  • boot_version: Version string for releases.
  • site_name: Custom branding (defaults to netboot.xyz).
  • generate_menus / generate_disks: Enable/disable build components.
  • sigs_enabled: Enable signature verification for menu chaining.
  • live_endpoint: Base URL for live/rescue images.
  • Runtime variables: ${distro}_mirror and ${distro}_base_dir are auto-generated from releases: entries in boot.cfg.j2 and available to all menu templates at boot time.

Git Workflow

  • development: Main development branch, default PR target.
  • RC: Release candidate staging.
  • master: Production releases.
  • Commit style: imperative mood, descriptive. Automated commits use Version bump for ... format.