netboot.xyz

GitHub

Your favorite operating systems in one place. A network-based bootable operating system installer based on iPXE.

RAW Rules

AGENTS.md

# 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 <var> || goto <exit_label>`.
- 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.<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`)

```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: "<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.