Compare commits
2 commits
d2ac8cb091
...
1383272d2c
Author | SHA1 | Date | |
---|---|---|---|
1383272d2c | |||
914781cd00 |
8 changed files with 330 additions and 0 deletions
1
config/.gitignore
vendored
Normal file
1
config/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
build
|
5
config/Makefile
Normal file
5
config/Makefile
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.PHONY: build
|
||||||
|
|
||||||
|
build:
|
||||||
|
mkdir -p build/
|
||||||
|
go build -o build/
|
11
config/go.mod
Normal file
11
config/go.mod
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
module git.faercol.me/faercol/http-boot-config/config
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require github.com/stretchr/testify v1.8.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
10
config/go.sum
Normal file
10
config/go.sum
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
101
config/logger/logger.go
Normal file
101
config/logger/logger.go
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
nc = "\033[0m"
|
||||||
|
red = "\033[0;31m"
|
||||||
|
yellow = "\033[0;33m"
|
||||||
|
gray = "\033[0;30m"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
errorPrefix = "Error: "
|
||||||
|
fatalPrefix = "Fatal: "
|
||||||
|
warningprefix = "Warning: "
|
||||||
|
debugprefix = "Debug: "
|
||||||
|
)
|
||||||
|
|
||||||
|
type SimpleLogger struct {
|
||||||
|
enableColour bool
|
||||||
|
debug bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *SimpleLogger) printMsg(msg string, dest io.Writer) {
|
||||||
|
if msg[len(msg)-1:] != "\n" {
|
||||||
|
msg = msg + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := fmt.Fprint(dest, msg)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to write message to %v: %w", dest, err))
|
||||||
|
}
|
||||||
|
if n != len(msg) {
|
||||||
|
panic(fmt.Errorf("failed to write the entire message (%d/%d)", n, len(msg)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *SimpleLogger) colourText(msg string, colour string) string {
|
||||||
|
if !sl.enableColour {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
return colour + msg + nc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *SimpleLogger) Info(msg string) {
|
||||||
|
sl.printMsg(msg, os.Stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *SimpleLogger) Infof(format string, a ...any) {
|
||||||
|
sl.printMsg(fmt.Sprintf(format, a...), os.Stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *SimpleLogger) Error(msg string) {
|
||||||
|
sl.printMsg(sl.colourText(errorPrefix+msg, red), os.Stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *SimpleLogger) Errorf(format string, a ...any) {
|
||||||
|
sl.printMsg(sl.colourText(errorPrefix+fmt.Sprintf(format, a...), red), os.Stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *SimpleLogger) Warning(msg string) {
|
||||||
|
sl.printMsg(sl.colourText(warningprefix+msg, yellow), os.Stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *SimpleLogger) Warningf(format string, a ...any) {
|
||||||
|
sl.printMsg(sl.colourText(warningprefix+fmt.Sprintf(format, a...), yellow), os.Stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *SimpleLogger) Debug(msg string) {
|
||||||
|
if !sl.debug {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sl.printMsg(sl.colourText(debugprefix+msg, gray), os.Stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *SimpleLogger) Debugf(format string, a ...any) {
|
||||||
|
if !sl.debug {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sl.printMsg(sl.colourText(debugprefix+fmt.Sprintf(format, a...), gray), os.Stdout)
|
||||||
|
}
|
||||||
|
func (sl *SimpleLogger) Fatal(msg string) {
|
||||||
|
sl.printMsg(sl.colourText(fatalPrefix+msg, red), os.Stderr)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sl *SimpleLogger) Fatalf(format string, a ...any) {
|
||||||
|
sl.printMsg(sl.colourText(fatalPrefix+fmt.Sprintf(format, a...), red), os.Stderr)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(colour, debug bool) *SimpleLogger {
|
||||||
|
return &SimpleLogger{
|
||||||
|
debug: debug,
|
||||||
|
enableColour: colour,
|
||||||
|
}
|
||||||
|
}
|
54
config/main.go
Normal file
54
config/main.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"io/fs"
|
||||||
|
|
||||||
|
"git.faercol.me/faercol/http-boot-config/config/logger"
|
||||||
|
"git.faercol.me/faercol/http-boot-config/config/prober"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cliArgs struct {
|
||||||
|
debug bool
|
||||||
|
colour bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseArgs() cliArgs {
|
||||||
|
debugFlag := flag.Bool("debug", false, "Display debug logs")
|
||||||
|
noColourFlag := flag.Bool("no-colour", false, "Disable colour logs")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
return cliArgs{
|
||||||
|
debug: *debugFlag,
|
||||||
|
colour: !*noColourFlag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func displayAppList(apps []prober.EfiApp, l *logger.SimpleLogger) {
|
||||||
|
l.Info("Found the following EFI applications:")
|
||||||
|
for _, a := range apps {
|
||||||
|
prefix := " "
|
||||||
|
if a.Active {
|
||||||
|
prefix = "*"
|
||||||
|
}
|
||||||
|
l.Infof("\t- %s[%d] %s: %s", prefix, a.ID, a.Name, a.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
args := parseArgs()
|
||||||
|
l := logger.New(args.colour, args.debug)
|
||||||
|
|
||||||
|
l.Info("Checking EFI directory for available boot images...")
|
||||||
|
images, err := prober.GetEFIApps(l)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, fs.ErrPermission) {
|
||||||
|
l.Fatal("Permission error, try to run the command as sudo")
|
||||||
|
}
|
||||||
|
l.Fatalf("Failed to check EFI directory: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
displayAppList(images, l)
|
||||||
|
}
|
102
config/prober/prober.go
Normal file
102
config/prober/prober.go
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
package prober
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.faercol.me/faercol/http-boot-config/config/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var efiBootmgrRegexp = regexp.MustCompile(`Boot(?P<id>\d+)\* (?P<name>[\w ]+)\t(\w+\(.*\))/File\((?P<filepath>.+)\)`)
|
||||||
|
var activeRegexp = regexp.MustCompile(`BootCurrent: (\d+)`)
|
||||||
|
|
||||||
|
type EfiApp struct {
|
||||||
|
ID int
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
Active bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func efiAppFromBootMgrOutput(rawVal string, activeId int) (app EfiApp, ok bool) {
|
||||||
|
match := efiBootmgrRegexp.FindStringSubmatch(rawVal)
|
||||||
|
if len(match) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result := make(map[string]string)
|
||||||
|
for i, name := range efiBootmgrRegexp.SubexpNames() {
|
||||||
|
if i != 0 && name != "" {
|
||||||
|
result[name] = match[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
id, ok := result["id"]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
idInt, err := strconv.Atoi(id)
|
||||||
|
if err != nil {
|
||||||
|
return app, false
|
||||||
|
}
|
||||||
|
name, ok := result["name"]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filepath, ok := result["filepath"]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return EfiApp{
|
||||||
|
ID: idInt,
|
||||||
|
Name: strings.TrimSpace(name),
|
||||||
|
Path: strings.TrimSpace(filepath),
|
||||||
|
Active: idInt == activeId,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func getActiveEFIApp(output string) (int, error) {
|
||||||
|
match := activeRegexp.FindStringSubmatch(output)
|
||||||
|
if len(match) == 0 {
|
||||||
|
return -1, errors.New("no active boot image found")
|
||||||
|
}
|
||||||
|
strId := match[1]
|
||||||
|
id, err := strconv.Atoi(strId)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEFIApps(log *logger.SimpleLogger) (apps []EfiApp, err error) {
|
||||||
|
cmd := exec.Command("/usr/bin/efibootmgr")
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cmd.ProcessState.ExitCode() != 0 {
|
||||||
|
return nil, fmt.Errorf("error running efibootmgr, returncode %d", cmd.ProcessState.ExitCode())
|
||||||
|
}
|
||||||
|
|
||||||
|
outStr := out.String()
|
||||||
|
activeBootID, err := getActiveEFIApp(outStr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, l := range strings.Split(outStr, "\n") {
|
||||||
|
log.Debugf("Parsing line %q", l)
|
||||||
|
app, ok := efiAppFromBootMgrOutput(l, activeBootID)
|
||||||
|
if !ok {
|
||||||
|
log.Debug("Line is not a valid EFI application")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
apps = append(apps, app)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
46
config/prober/prober_test.go
Normal file
46
config/prober/prober_test.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package prober
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEfiFomBootMgr(t *testing.T) {
|
||||||
|
t.Run("OK - Windows", func(t *testing.T) {
|
||||||
|
rawVal := (`Boot0000* Windows Boot Manager HD(1,GPT,4ad714ba-6a01-4f76-90e3-1bb93a59b67e,0x800,0x32000)/File(\EFI\MICROSOFT\BOOT\BOOTMGFW.EFI)` +
|
||||||
|
`57494e444f5753000100000088000000780000004200430044004f0042004a004500430054003d007b00390064006500610038003600320063002d0035006300640064002d00`)
|
||||||
|
expected := EfiApp{ID: 0, Name: "Windows Boot Manager", Path: `\EFI\MICROSOFT\BOOT\BOOTMGFW.EFI`, Active: true}
|
||||||
|
app, ok := efiAppFromBootMgrOutput(rawVal, 0)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, expected, app)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("OK - Manjaro", func(t *testing.T) {
|
||||||
|
rawVal := `Boot0001* Manjaro HD(1,GPT,16f06d01-50da-6544-86bd-f3457f980086,0x1000,0x96000)/File(\EFI\MANJARO\GRUBX64.EFI)`
|
||||||
|
expected := EfiApp{ID: 1, Name: "Manjaro", Path: `\EFI\MANJARO\GRUBX64.EFI`, Active: false}
|
||||||
|
app, ok := efiAppFromBootMgrOutput(rawVal, 0)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, expected, app)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("OK - EFI Base", func(t *testing.T) {
|
||||||
|
rawVal := `Boot0002* UEFI OS HD(1,GPT,16f06d01-50da-6544-86bd-f3457f980086,0x1000,0x96000)/File(\EFI\BOOT\BOOTX64.EFI)0000424f`
|
||||||
|
expected := EfiApp{ID: 2, Name: "UEFI OS", Path: `\EFI\BOOT\BOOTX64.EFI`, Active: false}
|
||||||
|
app, ok := efiAppFromBootMgrOutput(rawVal, 0)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, expected, app)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NOK - CD", func(t *testing.T) {
|
||||||
|
rawVal := `Boot0003* UEFI:CD/DVD Drive BBS(129,,0x0)`
|
||||||
|
_, ok := efiAppFromBootMgrOutput(rawVal, 0)
|
||||||
|
assert.False(t, ok)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NOK - Other line", func(t *testing.T) {
|
||||||
|
rawVal := `BootOrder: 0001,0002,0000,0003,0004,0005`
|
||||||
|
_, ok := efiAppFromBootMgrOutput(rawVal, 0)
|
||||||
|
assert.False(t, ok)
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue