Embed ISO in initrd for PXE boot with loop-mount wrapper
build-and-push / test (push) Successful in 35s
build-and-push / build-and-push (push) Successful in 1m8s

Instead of sanboot (which can't pass kernel params for automation),
switch back to kernel/initrd boot. The ISO is embedded in the initrd
as a CPIO append. A pxe-init wrapper script loop-mounts the ISO
before handing off to the original init, so the installer finds it
as a block device. Uses rdinit=/pxe-init kernel parameter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 12:11:18 -04:00
parent a8f8801a90
commit e2dd78d8f9
3 changed files with 147 additions and 5 deletions
+121
View File
@@ -0,0 +1,121 @@
package image
import (
"fmt"
"io"
"os"
)
const pxeInitScript = `#!/bin/sh
mkdir -p /dev
mount -t devtmpfs devtmpfs /dev 2>/dev/null
losetup /dev/loop0 /proxmox.iso 2>/dev/null
umount /dev 2>/dev/null
exec /init "$@"
`
func CreatePXEInitrd(origInitrdPath, isoPath, outputPath string) error {
out, err := os.Create(outputPath)
if err != nil {
return fmt.Errorf("create output: %w", err)
}
defer out.Close()
orig, err := os.Open(origInitrdPath)
if err != nil {
return fmt.Errorf("open initrd: %w", err)
}
if _, err := io.Copy(out, orig); err != nil {
orig.Close()
return fmt.Errorf("copy initrd: %w", err)
}
orig.Close()
if err := writeCPIOEntry(out, "pxe-init", []byte(pxeInitScript), 0o100755, 1); err != nil {
return err
}
isoStat, err := os.Stat(isoPath)
if err != nil {
return fmt.Errorf("stat iso: %w", err)
}
isoFile, err := os.Open(isoPath)
if err != nil {
return fmt.Errorf("open iso: %w", err)
}
defer isoFile.Close()
if err := writeCPIOEntryFromReader(out, "proxmox.iso", isoFile, isoStat.Size(), 0o100644, 2); err != nil {
return err
}
return writeCPIOTrailer(out)
}
func writeCPIOEntry(w io.Writer, name string, data []byte, mode uint32, ino uint32) error {
if err := writeCPIOHeader(w, name, int64(len(data)), mode, ino); err != nil {
return err
}
if _, err := w.Write(data); err != nil {
return err
}
if pad := (4 - len(data)%4) % 4; pad > 0 {
if _, err := w.Write(make([]byte, pad)); err != nil {
return err
}
}
return nil
}
func writeCPIOEntryFromReader(w io.Writer, name string, r io.Reader, size int64, mode uint32, ino uint32) error {
if err := writeCPIOHeader(w, name, size, mode, ino); err != nil {
return err
}
if _, err := io.Copy(w, r); err != nil {
return err
}
if pad := (4 - int(size%4)) % 4; pad > 0 {
if _, err := w.Write(make([]byte, pad)); err != nil {
return err
}
}
return nil
}
func writeCPIOHeader(w io.Writer, name string, fileSize int64, mode uint32, ino uint32) error {
nameLen := len(name) + 1
hdr := fmt.Sprintf("070701%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X",
ino, // ino
mode, // mode
0, // uid
0, // gid
1, // nlink
0, // mtime
fileSize, // filesize
0, // devmajor
0, // devminor
0, // rdevmajor
0, // rdevminor
nameLen, // namesize
0, // check
)
if _, err := io.WriteString(w, hdr); err != nil {
return err
}
if _, err := io.WriteString(w, name); err != nil {
return err
}
if _, err := w.Write([]byte{0}); err != nil {
return err
}
totalHeader := 110 + nameLen
if pad := (4 - totalHeader%4) % 4; pad > 0 {
if _, err := w.Write(make([]byte, pad)); err != nil {
return err
}
}
return nil
}
func writeCPIOTrailer(w io.Writer) error {
return writeCPIOHeader(w, "TRAILER!!!", 0, 0, 0)
}