# 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 ```bash # 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: ```bash 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: ```yaml 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 || goto `. - Exit pattern: clear menu and `exit 0`: ```ipxe :distro_exit clear menu exit 0 ``` - Error pattern: echo, prompt, return to menu: ```ipxe :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_64` → `x86_64`, `arm64` → `aarch64` - Debian: `x86_64` → `amd64`, `arm64` → `arm64` - Guard optional values: `isset ${variable}` in iPXE, `{% if value.field is defined %}` in Jinja2. ### Jinja2 Patterns - Loop over releases: `{% for item in releases..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`) ```yaml 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`) ```yaml utilitiesefi: # or utilitiespcbios64, utilitiespcbios32, utilitiesarm utility_key: name: "Display Name" enabled: true type: direct # one of: direct, ipxemenu, memdisk, memtest, sanboot kernel: "" initrd: "" # 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/.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.