Skip to content

Make autoexec.ipxe the boot script, not the opt-in - #1190

Merged
mastacontrola merged 2 commits into
working-1.6from
embedless-efi-installer
Aug 18, 2026
Merged

Make autoexec.ipxe the boot script, not the opt-in#1190
mastacontrola merged 2 commits into
working-1.6from
embedless-efi-installer

Conversation

@mastacontrola

Copy link
Copy Markdown
Member

Installer half of fog-ipxe#7, which stops embedding a script in any EFI binary. Publish autoexec.ipxe at the TFTP root, hard-link it beside every EFI binary, retire the autoexec/ tree that no longer ships, and add --boot-delay so the 10-second STP/powersave sleep is a setting rather than a second set of binaries.

Do not merge before fog-ipxe#7 is tagged and the FOG_IPXE_VERSION bump is added here. The installer expects the new tarball layout; against v2.0.0-fog.7 it would delete the autoexec/ tree that release still depends on.

What reversed

configureTFTPandPXE() used to delete any autoexec.ipxe at the TFTP root. That comment is worth keeping in mind, because the reasoning still holds — it is the premise that changed:

efi_probe() calls efi_autoexec_load() unconditionally, so an EMBED-marked binary downloads autoexec.ipxe and then never runs it, because first_image() returns the embedded script ahead of it. Nothing unregisters it, so initrd_load_all() concatenates 2 KB of iPXE script ahead of init.xz and the kernel panics with VFS: Unable to mount root fs on "/dev/ram0" (forums #18213). efi_autoexec_network() falls back to /autoexec.ipxe when the binary's own directory has none, so one embedded EFI binary anywhere was enough to poison every client that fell back to the root.

With no EFI binary embedding anything, the script is executed rather than merely registered, and image_exec() unregisters it for the duration — so it is gone before boot runs. Legacy BIOS is untouched: no efi_autoexec_load() there, so a root autoexec.ipxe is a file it never asks for.

--boot-delay

Seconds of sleep before the first DHCP attempt. Bounded 0–120: this is a wait every client sits through on every boot, and a fat-fingered 600 is indistinguishable from a hung PXE stack to whoever is standing at the machine.

_applyBootDelay() brackets the inserted lines with sentinel comments rather than matching on the sleep line, so an admin's own sleep is never eaten and the option works in both directions — lowering or clearing rewrites the block exactly like raising it. Verified as a unit: insert → change value → clear round-trips to a file byte-identical to the original, and re-applying the same value is stable.

It writes in place with a redirect rather than a temp-and-mv, because by then the file may already be hard-linked and a rename would leave those links on the old inode — the drift the links exist to prevent.

BIOS cannot be served this way and 10secdelay/ is exactly ten seconds, so _biosBootFile() maps any non-zero delay to that build (both the ISC and Kea generators) and the run says so when the two cannot agree.

The manifest trap

_copyIpxeTree() hashes each file as it lays it down, and _applyBootDelay() rewrites autoexec.ipxe afterwards. Without re-stamping, the next run reads the difference as "the admin replaced this file", declines to update it, and the boot script is frozen for good — the same trap _signLocalIpxe() hits, for the same reason. _restampIpxeManifest() is called for autoexec.ipxe and every path it is linked into.

Breaking

A DHCP filename beginning autoexec/ stops resolving; drop the prefix. FOG's own generated dhcpd.conf and Kea config never used those paths, so only hand-written configs are affected. _retireAutoexecDir() deletes the stale tree rather than leaving it — it would otherwise go on serving whichever release last created it, getting quietly older every upgrade while looking maintained — and prints the mapping.

Still to do on this PR

  • FOG_IPXE_VERSIONv2.0.0-fog.8 once tagged
  • Live boot test on the Secure Boot VM
  • fog-docs note for the autoexec/ filename change

🤖 Generated with Claude Code

fog-ipxe v2.0.0-fog.8 stops embedding a script in any EFI binary, so every one
of them downloads autoexec.ipxe and runs it. This is the installer half:
publish that script at the TFTP root, hard-link it beside every EFI binary,
retire the autoexec/ tree that no longer ships, and add --boot-delay so the
10-second STP/powersave sleep is a setting rather than a second set of
binaries.

WHAT REVERSED

configureTFTPandPXE() used to DELETE any autoexec.ipxe at the TFTP root, and
the comment explaining why is worth keeping in mind because the reasoning
still holds -- it is the premise that changed, not the logic:

  efi_probe() calls efi_autoexec_load() unconditionally, so an EMBED-marked
  binary downloads autoexec.ipxe and then never runs it, because first_image()
  returns the embedded script ahead of it. Nothing unregisters it, so
  initrd_load_all() concatenates 2 KB of iPXE script ahead of init.xz and the
  kernel panics with "VFS: Unable to mount root fs on /dev/ram0"
  (forums #18213). efi_autoexec_network() falls back to /autoexec.ipxe when the
  binary's own directory has none, so one embedded EFI binary anywhere was
  enough to poison every client that fell back to the root.

With no EFI binary embedding anything, the script is EXECUTED rather than
merely registered, and image_exec() unregisters it for the duration -- so it
is gone before boot runs. Legacy BIOS is untouched: it has no
efi_autoexec_load(), so a root autoexec.ipxe is a file it never asks for.

--boot-delay

Seconds of sleep before the first DHCP attempt, for switches slow out of STP
or powersave. Bounded 0-120 -- this is a wait every client sits through on
every boot, and a fat-fingered 600 is indistinguishable from a hung PXE stack
to whoever is standing at the machine.

_applyBootDelay() brackets the inserted lines with sentinel comments rather
than matching on the sleep line, so an admin's own sleep is never eaten and
the option works in both directions: lowering or clearing the delay rewrites
the block exactly like raising it. It writes in place with a redirect rather
than a temp-and-mv, because by then the file may already be hard-linked and a
rename would leave those links on the old inode -- the drift the links exist
to prevent.

BIOS cannot be served this way and 10secdelay/ is exactly ten seconds, so
_biosBootFile() maps any non-zero delay to that build and the run says so when
the two cannot agree.

THE MANIFEST TRAP

_copyIpxeTree() hashes each file as it lays it down, and _applyBootDelay()
rewrites autoexec.ipxe afterwards. Without re-stamping, the next run reads the
difference as "the admin replaced this file", declines to update it, and the
boot script is frozen for good -- the same trap _signLocalIpxe() hits, for the
same reason. _restampIpxeManifest() is called for autoexec.ipxe and every path
it is linked into.

BREAKING

A DHCP filename beginning "autoexec/" stops resolving. Drop the prefix:
autoexec/snponly.efi becomes snponly.efi. FOG's own generated dhcpd.conf and
Kea config never used those paths, so only hand-written configs are affected.
_retireAutoexecDir() deletes the stale tree rather than leaving it -- it would
otherwise go on serving whichever release last created it, getting quietly
older every upgrade while looking maintained -- and prints the mapping.
The release the change above needs. v2.0.0-fog.8 is the first tarball whose
EFI binaries carry no embedded script and whose tree publishes autoexec.ipxe
at the root, which is what configureTFTPandPXE() now links from.

Deliberately in the same PR rather than a follow-up: against v2.0.0-fog.7 the
installer would delete the autoexec/ tree that release still depends on, and
find no root autoexec.ipxe to replace it with. The two halves are one change.
@mastacontrola
mastacontrola merged commit 658ae7c into working-1.6 Aug 18, 2026
3 checks passed
@mastacontrola
mastacontrola deleted the embedless-efi-installer branch August 18, 2026 16:50
mastacontrola added a commit that referenced this pull request Aug 18, 2026
#1190 retired autoexec/ but not 10secdelay/'s EFI files, and that is the half
that actually breaks a client.

Those .efi are EMBED-marked, and the root now carries an autoexec.ipxe.
efi_autoexec_network() falls back to /autoexec.ipxe when the binary's own
directory has none -- and 10secdelay/ has none -- so one of them downloads the
script, never runs it (first_image() returns its embedded one), nothing
unregisters it, and initrd_load_all() concatenates 2 KB of iPXE script ahead of
init.xz. The client panics with

    VFS: Unable to mount root fs on "/dev/ram0" or unknown-block(1,0)

That is precisely the failure the EMBED-less change exists to make impossible,
reintroduced by an upgrade on a path that worked before it. _copyIpxeTree()
cannot clear them: it only ever writes, so anything a release stops shipping
stays on disk. Observed on a server that had just upgraded -- the stale
10secdelay/snponly.efi still had "Sleeping 10 seconds" in its strings, and
_signLocalIpxe() had given it a fresh mtime, so it looked current.

_retireAutoexecDir() therefore becomes _retireStaleEfiPaths() and sweeps:

  autoexec/                     both there and under stock/
  10secdelay/*.efi              the loose files beside the BIOS builds
  10secdelay/{i386,arm64}-efi/  and the same under stock/

The BIOS files in 10secdelay/ stay -- they are why the directory still exists,
since legacy BIOS has no efi_autoexec_load() and so cannot take a --boot-delay.

An admin who deliberately placed their own .efi there loses it. Accepted: every
EFI binary that can legitimately sit in that directory now is one that would
panic the client booting it, and a file that cannot be booted safely is not
worth keeping over one that can.

Also rejoins _copyIpxeTree()'s leading comment, which #1190 split mid-sentence
by inserting these helpers through the middle of it. They now sit above the
block rather than inside it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant