Fix ISO GRUB modification to search entire ISO and use robust kernel matching
Two fixes: (1) Search entire ISO for grub.cfg content instead of only at 2048-byte sector boundaries — catches the copy inside the EFI boot partition image that UEFI firmware actually reads. (2) Match kernel lines by GRUB 'linux' command prefix instead of requiring '/boot/linux' path. Added detailed logging of original and modified grub.cfg content. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+54
-27
@@ -3,7 +3,6 @@ package image
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -17,10 +16,12 @@ func ModifyISOForAutoInstall(isoPath, answerURL string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if origConfig == nil {
|
if origConfig == nil {
|
||||||
log.Printf("image: no grub.cfg found in ISO, skipping auto-install modification")
|
return fmt.Errorf("no grub.cfg found in ISO filesystem")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("image: found grub.cfg (%d bytes), first 200 chars: %s",
|
||||||
|
len(origConfig), truncate(string(origConfig), 200))
|
||||||
|
|
||||||
newConfig := rewriteGrubConfig(string(origConfig), answerURL)
|
newConfig := rewriteGrubConfig(string(origConfig), answerURL)
|
||||||
|
|
||||||
if len(newConfig) > len(origConfig) {
|
if len(newConfig) > len(origConfig) {
|
||||||
@@ -33,22 +34,27 @@ func ModifyISOForAutoInstall(isoPath, answerURL string) error {
|
|||||||
padded[i] = '\n'
|
padded[i] = '\n'
|
||||||
}
|
}
|
||||||
|
|
||||||
offset, err := findContentInISO(isoPath, origConfig)
|
offsets, err := findAllOccurrences(isoPath, origConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("locate grub.cfg in ISO: %w", err)
|
return fmt.Errorf("locate grub.cfg in ISO: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("image: found grub.cfg at %d location(s) in ISO: %v", len(offsets), offsets)
|
||||||
|
|
||||||
f, err := os.OpenFile(isoPath, os.O_WRONLY, 0)
|
f, err := os.OpenFile(isoPath, os.O_WRONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("open ISO for writing: %w", err)
|
return fmt.Errorf("open ISO for writing: %w", err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
|
for _, offset := range offsets {
|
||||||
if _, err := f.WriteAt(padded, offset); err != nil {
|
if _, err := f.WriteAt(padded, offset); err != nil {
|
||||||
return fmt.Errorf("write modified grub.cfg: %w", err)
|
return fmt.Errorf("write at offset %d: %w", offset, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("image: modified grub.cfg at offset %d for auto-install", offset)
|
log.Printf("image: modified grub.cfg at %d location(s) for auto-install", len(offsets))
|
||||||
|
log.Printf("image: new grub.cfg:\n%s", newConfig)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +81,7 @@ func readGrubCfgFromISO(isoPath string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reader := grubFile.Reader()
|
reader := grubFile.Reader()
|
||||||
data, err := io.ReadAll(reader)
|
data, err := readAll(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read grub.cfg: %w", err)
|
return nil, fmt.Errorf("read grub.cfg: %w", err)
|
||||||
}
|
}
|
||||||
@@ -103,6 +109,7 @@ func rewriteGrubConfig(original, answerURL string) string {
|
|||||||
lines := strings.Split(original, "\n")
|
lines := strings.Split(original, "\n")
|
||||||
var result []string
|
var result []string
|
||||||
depth := 0
|
depth := 0
|
||||||
|
firstMenuModified := false
|
||||||
|
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
trimmed := strings.TrimSpace(line)
|
trimmed := strings.TrimSpace(line)
|
||||||
@@ -120,10 +127,14 @@ func rewriteGrubConfig(original, answerURL string) string {
|
|||||||
depth++
|
depth++
|
||||||
}
|
}
|
||||||
if trimmed == "}" && depth > 0 {
|
if trimmed == "}" && depth > 0 {
|
||||||
|
if depth == 1 {
|
||||||
|
firstMenuModified = true
|
||||||
|
}
|
||||||
depth--
|
depth--
|
||||||
}
|
}
|
||||||
|
|
||||||
if depth == 1 && strings.Contains(trimmed, "linux ") && strings.Contains(trimmed, "/boot/linux") {
|
if depth > 0 && !firstMenuModified &&
|
||||||
|
(strings.HasPrefix(trimmed, "linux ") || strings.HasPrefix(trimmed, "linux\t")) {
|
||||||
if !strings.Contains(line, "proxmox-start-auto-installer") {
|
if !strings.Contains(line, "proxmox-start-auto-installer") {
|
||||||
line = strings.TrimRight(line, " \t") + " proxmox-start-auto-installer"
|
line = strings.TrimRight(line, " \t") + " proxmox-start-auto-installer"
|
||||||
}
|
}
|
||||||
@@ -138,32 +149,48 @@ func rewriteGrubConfig(original, answerURL string) string {
|
|||||||
return strings.Join(result, "\n")
|
return strings.Join(result, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func findContentInISO(isoPath string, content []byte) (int64, error) {
|
// findAllOccurrences searches the entire ISO file for all locations where
|
||||||
f, err := os.Open(isoPath)
|
// the grub.cfg content appears — this catches both the ISO9660 filesystem
|
||||||
|
// copy and any copy inside an embedded EFI boot partition image.
|
||||||
|
func findAllOccurrences(isoPath string, content []byte) ([]int64, error) {
|
||||||
|
data, err := os.ReadFile(isoPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return nil, fmt.Errorf("read ISO: %w", err)
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
prefix := content
|
|
||||||
if len(prefix) > 256 {
|
|
||||||
prefix = prefix[:256]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := make([]byte, 2048)
|
needle := content
|
||||||
stat, _ := f.Stat()
|
if len(needle) > 256 {
|
||||||
|
needle = needle[:256]
|
||||||
|
}
|
||||||
|
|
||||||
for offset := int64(0); offset < stat.Size(); offset += 2048 {
|
var offsets []int64
|
||||||
n, err := f.ReadAt(buf, offset)
|
start := 0
|
||||||
if n == 0 {
|
for {
|
||||||
|
idx := bytes.Index(data[start:], needle)
|
||||||
|
if idx == -1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err != nil && err != io.EOF {
|
offsets = append(offsets, int64(start+idx))
|
||||||
return 0, err
|
start += idx + 1
|
||||||
}
|
}
|
||||||
if bytes.HasPrefix(buf[:n], prefix) {
|
|
||||||
return offset, nil
|
if len(offsets) == 0 {
|
||||||
|
return nil, fmt.Errorf("grub.cfg content not found in ISO raw data (searched %d bytes with %d-byte needle)",
|
||||||
|
len(data), len(needle))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return offsets, nil
|
||||||
}
|
}
|
||||||
return 0, fmt.Errorf("grub.cfg content not found in ISO raw data")
|
|
||||||
|
func readAll(r interface{ Read([]byte) (int, error) }) ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err := buf.ReadFrom(r)
|
||||||
|
return buf.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func truncate(s string, n int) string {
|
||||||
|
if len(s) <= n {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return s[:n] + "..."
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user