Metadata-Version: 2.4
Name: boots-from
Version: 0.0.1
Summary: Make a bootable image that loads iPXE and chains to a URL you choose
Project-URL: Repository, https://github.com/safl/boots-from
Project-URL: Homepage, https://github.com/safl/boots-from
Author-email: "Simon A. F. Lund" <os@safl.dk>
License-Expression: GPL-3.0-only
License-File: LICENSE
Keywords: boot,ipxe,netboot,pxe,usb
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.11
Provides-Extra: mascot
Requires-Dist: pillow>=10; extra == 'mascot'
Description-Content-Type: text/markdown

<p align="center">
  <img src="_static/boots-mascot.png" alt="boots-from mascot - a pair of boots cropped from the bty bat's lower half" width="180">
</p>

# boots-from - make a bootable image that loads iPXE and chains to a URL

> One image, one chain URL, every boot goes through your control plane.

Build a bootable image that runs [iPXE](https://ipxe.org) and
chains to a URL you pick. iPXE fires at boot, fetches its first
script from the URL you embedded, and dispatches on whatever the
server returns -- chainload an installer, sanboot the local disk,
drop into a menu, anything iPXE can do.

The image knows nothing about what's on the other end. It's the
equivalent of "PXE-boot but without setting up DHCP / TFTP" --
your bootstrap is the image instead of the LAN.

```bash
# Build a hybrid ISO -- bootable on iKVM virtual media + USB.
# Output is always ./boots-from.iso in the current directory.
boots-from https://my-bootserver.example/boot
```

The output is a **hybrid ISO** (~6 MB) at `./boots-from.iso`:
bootable as a CD-ROM on JetKVM / PiKVM / Supermicro iKVM
virtual media, AND as a USB stick after dd. Same artifact, two
paths. Plug in or attach over BMC, the machine runs iPXE, iPXE
fetches your script, your server runs the show from there.

Writing the ISO to a device is out of scope -- pipe it to
`dd`, open it in Balena Etcher, hand it to `bty flash`, or
attach it over BMC virtual media. One tool, one job.

No state lives on the artifact -- the chain URL is baked into
the iPXE binary via ``EMBED=`` at build time. Different URL?
Rebake (build time ~5-10s).

## The chain URL: one argument, a lot of flexibility

The single positional you pass is whatever iPXE's `chain`
command accepts. That's broader than just HTTP -- iPXE speaks a
range of transports natively, so the same ISO can point at any
of them depending on how the URL is written:

| URL form                              | Transport        | Typical use                                     |
|---------------------------------------|------------------|-------------------------------------------------|
| `http://host[:port]/path`             | HTTP             | The common case. Plain text, no certs to manage.|
| `https://host[:port]/path`            | HTTPS            | Requires an iPXE build with TLS + trusted CA.   |
| `tftp://host/path`                    | TFTP             | Classic PXE-style script delivery.              |
| `ftp://host/path`                     | FTP              | Rare, but supported.                            |
| `nfs://host/export/path`              | NFS              | Read a script straight off an NFS export.       |
| `iscsi:host::::iqn.target`            | iSCSI sanboot    | Skip the script step, boot a remote LUN.        |

**Requirements / semantics**:

- **Format**: must be a single URL iPXE understands -- no
  shell quoting, no spaces, no shell-glob expansion (the URL
  goes straight into the embedded script as a literal). If your
  URL legitimately needs spaces or odd characters, percent-encode
  them.
- **Reachability**: at boot time, from the target machine's
  network -- not from wherever you ran `boots-from`. The build
  step does NOT fetch or validate the URL. A typo only surfaces
  at boot.
- **DHCP**: the embedded script runs `dhcp` before `chain`, so
  the target machine needs to be on a LAN that hands out
  addresses. Static-IP / DHCP-less environments are out of
  scope (rebake from a fork if you need them).
- **Hostnames**: resolved via the DHCP-provided DNS. Using a
  hostname like `bty-server` works if the LAN's DNS (or each
  client's `/etc/hosts`) resolves it. IPs work unconditionally.
- **What the server returns**: an iPXE script (text starting
  with `#!ipxe`). It can do anything iPXE can do -- chainload
  another binary, sanboot a disk, display a menu, prompt the
  operator, conditionally branch on MAC / serial / arch, etc.
- **Failure mode**: if `dhcp` or `chain` fails, iPXE drops to
  its interactive shell with a diagnostic message rather than
  rebooting in a loop. Operators can debug manually from the
  prompt.

## Why does this exist?

iPXE absolutely supports embedding a chain script in the binary
(`make bin-x86_64-efi/ipxe.efi EMBED=script.ipxe`), but the
toolchain is a multi-step "git clone, install deps, configure,
make, copy binaries, write to USB the right way for hybrid
BIOS+UEFI boot" affair. Most writeups online walk through it
manually.

[netboot.xyz](https://netboot.xyz/) ships pre-built USB ISOs
but the embedded chain URL is hardcoded to `boot.netboot.xyz`.
Brilliant for their use case, not for "I want to point at my
own server".

`boots-from` fills the gap: one command produces a ready-to-flash
hybrid ISO with your URL baked in. Server-agnostic. Pairs with
anything that serves iPXE scripts (MAAS, Foreman, FOG, Cobbler,
custom Flask apps, [bty](https://github.com/safl/bty),
netboot.xyz on a self-hosted mirror, ...).

## Scope

Custom embedded scripts, CA-cert bundles, static-IP fallback,
alternate output paths, different iPXE upstream -- all
**rebake-from-fork** territory. The source is small enough that
forking and editing is a lower-cost path than maintaining
flags. The project's bet is simplicity scales further than
configurability.

## Pre-baked release: zero-build for the common case

The GitHub release page also publishes a pre-baked
`boots-from-bty.iso` whose embedded chain URL is
`http://bty-server/boot`. Use it when you don't want to build
locally: set up your LAN DNS (router, pihole, dnsmasq, or
`/etc/hosts` on the operator workstation) to resolve
`bty-server` to your bty appliance's IP, attach the ISO over
BMC virtual media or write it to a USB stick, and boot. The
embedded iPXE chains to `http://bty-server/boot` -- which is
exactly the URL the bty docs already tell you to configure for
PXE clients, so no extra setup is needed beyond the DNS record.

Operators who want a different default hostname rebake with
`boots-from <their-url>` and `mv boots-from.iso <whatever>.iso`
if they want a different filename. The build is ~5-10 seconds;
the pre-baked release exists to spare the ceremony for the
bty-default case, not to lock anyone in.

## Companion projects

* [`bty`](https://github.com/safl/bty) -- the **flasher**. Uses
  iPXE for its PXE control plane; takes a `boots-from`-made
  image pointed at a bty-server and gets full PXE-equivalent
  behaviour without configuring the LAN's DHCP for PXE.
* [`nosi`](https://github.com/safl/nosi) -- the **image
  builder**. Builds Debian / Ubuntu / Fedora sysdev images and
  publishes them to GHCR as ORAS artefacts; bty consumes those
  via `oras://` refs in its catalog.

Same `boots-from` shape applies to any iPXE-script-serving
stack (MAAS, Foreman, FOG, Cobbler, netboot.xyz on a
self-hosted mirror, ...).
