diff --git a/internal/image/isomod.go b/internal/image/isomod.go index f6607fc..1af1058 100644 --- a/internal/image/isomod.go +++ b/internal/image/isomod.go @@ -3,7 +3,6 @@ package image import ( "bytes" "fmt" - "io" "log" "os" "strings" @@ -17,10 +16,12 @@ func ModifyISOForAutoInstall(isoPath, answerURL string) error { return err } if origConfig == nil { - log.Printf("image: no grub.cfg found in ISO, skipping auto-install modification") - return nil + return fmt.Errorf("no grub.cfg found in ISO filesystem") } + log.Printf("image: found grub.cfg (%d bytes), first 200 chars: %s", + len(origConfig), truncate(string(origConfig), 200)) + newConfig := rewriteGrubConfig(string(origConfig), answerURL) if len(newConfig) > len(origConfig) { @@ -33,22 +34,27 @@ func ModifyISOForAutoInstall(isoPath, answerURL string) error { padded[i] = '\n' } - offset, err := findContentInISO(isoPath, origConfig) + offsets, err := findAllOccurrences(isoPath, origConfig) if err != nil { 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) if err != nil { return fmt.Errorf("open ISO for writing: %w", err) } defer f.Close() - if _, err := f.WriteAt(padded, offset); err != nil { - return fmt.Errorf("write modified grub.cfg: %w", err) + for _, offset := range offsets { + if _, err := f.WriteAt(padded, offset); err != nil { + 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 } @@ -75,7 +81,7 @@ func readGrubCfgFromISO(isoPath string) ([]byte, error) { } reader := grubFile.Reader() - data, err := io.ReadAll(reader) + data, err := readAll(reader) if err != nil { return nil, fmt.Errorf("read grub.cfg: %w", err) } @@ -103,6 +109,7 @@ func rewriteGrubConfig(original, answerURL string) string { lines := strings.Split(original, "\n") var result []string depth := 0 + firstMenuModified := false for _, line := range lines { trimmed := strings.TrimSpace(line) @@ -120,10 +127,14 @@ func rewriteGrubConfig(original, answerURL string) string { depth++ } if trimmed == "}" && depth > 0 { + if depth == 1 { + firstMenuModified = true + } 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") { line = strings.TrimRight(line, " \t") + " proxmox-start-auto-installer" } @@ -138,32 +149,48 @@ func rewriteGrubConfig(original, answerURL string) string { return strings.Join(result, "\n") } -func findContentInISO(isoPath string, content []byte) (int64, error) { - f, err := os.Open(isoPath) +// findAllOccurrences searches the entire ISO file for all locations where +// 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 { - return 0, err - } - defer f.Close() - - prefix := content - if len(prefix) > 256 { - prefix = prefix[:256] + return nil, fmt.Errorf("read ISO: %w", err) } - buf := make([]byte, 2048) - stat, _ := f.Stat() + needle := content + if len(needle) > 256 { + needle = needle[:256] + } - for offset := int64(0); offset < stat.Size(); offset += 2048 { - n, err := f.ReadAt(buf, offset) - if n == 0 { + var offsets []int64 + start := 0 + for { + idx := bytes.Index(data[start:], needle) + if idx == -1 { break } - if err != nil && err != io.EOF { - return 0, err - } - if bytes.HasPrefix(buf[:n], prefix) { - return offset, nil - } + offsets = append(offsets, int64(start+idx)) + start += idx + 1 } - return 0, fmt.Errorf("grub.cfg content not found in ISO raw data") + + 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 +} + +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] + "..." }