diff --git a/CHANGELOG.md b/CHANGELOG.md index 6335b09f..dabf8b57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -127,8 +127,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update iPXE building script - Send Info, Recv, Send, and Out messages to stdout; and others to stderr -- default is now protected and can't be deleted - first container imported container is added to the default profile +- grub in combination can now be set as boot method with `warewulf.grubboot: true` in + `warewulf.conf`. For unknown nodes `grub.efi` and `shim.efi` will be extracted from + the host running warewulf. If node has container it will get these binaries from the + container image. + ## [4.4.0] 2023-01-18 ### Added diff --git a/Makefile b/Makefile index d6ef7fec..e85ac05b 100644 --- a/Makefile +++ b/Makefile @@ -94,6 +94,7 @@ install: build docs install -d -m 0755 $(DESTDIR)$(WWCHROOTDIR) install -d -m 0755 $(DESTDIR)$(WWPROVISIONDIR) install -d -m 0755 $(DESTDIR)$(WWOVERLAYDIR)/wwinit/$(WWCLIENTDIR) + install -d -m 0755 $(DESTDIR)$(WWOVERLAYDIR)/host/$(TFTPDIR)/warewulf/ install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/examples install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/ipxe install -d -m 0755 $(DESTDIR)$(WWCONFIGDIR)/grub @@ -113,7 +114,8 @@ install: build docs test -f $(DESTDIR)$(DATADIR)/warewulf/defaults.conf || install -m 0644 etc/defaults.conf $(DESTDIR)$(DATADIR)/warewulf/defaults.conf for f in etc/examples/*.ww; do install -m 0644 $$f $(DESTDIR)$(WWCONFIGDIR)/examples/; done for f in etc/ipxe/*.ipxe; do install -m 0644 $$f $(DESTDIR)$(WWCONFIGDIR)/ipxe/; done - for f in etc/grub/*; do install -m 0644 $$f $(DESTDIR)$(WWCONFIGDIR)/grub/; done + install -m 0644 etc/grub/grub.cfg.ww $(DESTDIR)$(WWCONFIGDIR)/grub/grub.cfg.ww + install -m 0644 etc/grub/chainload.ww $(DESTDIR)$(WWOVERLAYDIR)/host$(TFTPDIR)/warewulf/grub.cfg.ww (cd overlays && find * -type f -exec install -D -m 0644 {} $(DESTDIR)$(WWOVERLAYDIR)/{} \;) (cd overlays && find * -type d -exec mkdir -pv $(DESTDIR)$(WWOVERLAYDIR)/{} \;) (cd overlays && find * -type l -exec cp -av {} $(DESTDIR)$(WWOVERLAYDIR)/{} \;) diff --git a/etc/grub/chainload.ww b/etc/grub/chainload.ww new file mode 100644 index 00000000..dc3c51d8 --- /dev/null +++ b/etc/grub/chainload.ww @@ -0,0 +1,28 @@ +# This file is autogenerated by warewulf +# Host: {{ .BuildHost }} +# Time: {{ .BuildTime }} +# Source: {{ .BuildSource }} +echo "================================================================================" +echo "Warewulf v4 now iXPE booting with grub" +echo "================================================================================" +set timeout=2 +# Must chainload in order to get kernel args for specific node +menuentry "Load specific configfile" { + conf="(http,{{.Ipaddr}}:{{.Warewulf.Port}})/efiboot/grub.cfg" + configfile $conf +} +menuentry "Chainload shim of container" { + shim="(http,{{.Ipaddr}}:{{.Warewulf.Port}})/efiboot/shim.efi" + chainloader ${shim} +} +menuentry "UEFI Firmware Settings" --id "uefi-firmware" { + fwsetup +} +menuentry "System restart" { + echo "System rebooting..." + reboot +} +menuentry "System shutdown" { + echo "System shutting down..." + halt +} diff --git a/etc/grub/grub.cfg.ww b/etc/grub/grub.cfg.ww index add45e64..7efbbe0a 100644 --- a/etc/grub/grub.cfg.ww +++ b/etc/grub/grub.cfg.ww @@ -1,25 +1,51 @@ echo "================================================================================" -echo "Warewulf v4 now booting with grub" +echo "Warewulf v4 now http booting grub: {{.Fqdn}} ({{.Hwaddr}})" +echo "================================================================================" echo -uri="(http,{{.Ipaddr}}:9873)/provision/${net_default_mac}?assetkey=" +echo "Warewulf Controller: {{.Ipaddr}}" +echo +sleep 1 +smbios --type1 --get-string 8 --set assetkey +uri="(http,{{.Ipaddr}}:{{.Port}})/provision/${net_default_mac}?assetkey=${assetkey}" kernel="${uri}&stage=kernel" container="${uri}&stage=container&compress=gz" system="${uri}&stage=system&compress=gz" -echo "Warewulf Controller: {{.Ipaddr}}" -echo "Trying to load a kernel... " -linux $kernel wwid="${net_default_mac}" "{{.KernelArgs}}" -if [ x$? = x0 ] ; then -echo "Loading initrd..." -initrd $container $system -echo "Booting..." -boot -else -echo "MESSAGE: This node is unconfigured. Please have your system administrator add a" -echo " configuration for this node with HW address: ${net_default_mac}" -echo "" -echo "Rebooting in 1 minute..." -sleep 60 -reboot -fi - - +runtime="${uri}&stage=runtime&compress=gz" +set default=ww4 +set timeout=5 +menuentry "Network boot node: {{.Id}}" --id ww4 { + {{if .KernelOverride }} + echo "Kernel: {{.KernelOverride}}" + {{else}} + echo "Kernel: {{.ContainerName}} (container default)" + {{end}} + echo "KernelArgs: {{.KernelArgs}}" + linux $kernel wwid=${net_default_mac} {{.KernelArgs}} + if [ x$? = x0 ] ; then + echo "Loading Container: {{.ContainerName}}" + initrd $container $system $runtime + boot + else + echo "MESSAGE: This node seems to be unconfigured. Please have your system administrator add a" + echo " configuration for this node with HW address: ${net_default_mac}" + echo "" + echo "Rebooting in 1 minute..." + sleep 60 + reboot + fi +} +menuentry "Chainload specific configfile" { + conf="(http,{{.Ipaddr}}:{{.Port}})/efiboot/grub.cfg" + configfile $conf +} +menuentry "UEFI Firmware Settings" --id "uefi-firmware" { + fwsetup +} +menuentry "System restart" { + echo "System rebooting..." + reboot +} +menuentry "System shutdown" { + echo "System shutting down..." + halt +} \ No newline at end of file diff --git a/go.mod b/go.mod index c9e07d29..4d304414 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/creasty/defaults v1.7.0 github.com/fatih/color v1.15.0 github.com/golang/glog v1.0.0 + github.com/golang/protobuf v1.5.2 github.com/google/uuid v1.3.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 github.com/manifoldco/promptui v0.9.0 @@ -56,7 +57,6 @@ require ( github.com/docker/go-units v0.4.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.3 // indirect github.com/gorilla/mux v1.7.4 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect diff --git a/internal/app/wwctl/container/delete/main.go b/internal/app/wwctl/container/delete/main.go index b30b9c69..71e5a3d7 100644 --- a/internal/app/wwctl/container/delete/main.go +++ b/internal/app/wwctl/container/delete/main.go @@ -2,15 +2,11 @@ package delete import ( "fmt" - "os" "github.com/hpcng/warewulf/internal/pkg/api/container" "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" apiutil "github.com/hpcng/warewulf/internal/pkg/api/util" - "github.com/hpcng/warewulf/internal/pkg/util" - "github.com/hpcng/warewulf/internal/pkg/node" - "github.com/hpcng/warewulf/internal/pkg/wwlog" "github.com/spf13/cobra" ) @@ -18,21 +14,7 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) { cdp := &wwapiv1.ContainerDeleteParameter{ ContainerNames: args, } - nodeDB, err := node.New() - if err != nil { - wwlog.Error("Failed to open node database: %s", err) - os.Exit(1) - } - profiles, err := nodeDB.MapAllProfiles() - if err != nil { - wwlog.Error("Could not load all profiles: %s", err) - os.Exit(1) - } - - if util.InSlice(args, profiles["default"].ContainerName.Get()) { - return fmt.Errorf("can't delete container which is in the default profile") - } if !SetYes { yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to delete container %s", args)) if !yes { diff --git a/internal/app/wwctl/container/exec/main.go b/internal/app/wwctl/container/exec/main.go index f5ecd31c..f34ada9f 100644 --- a/internal/app/wwctl/container/exec/main.go +++ b/internal/app/wwctl/container/exec/main.go @@ -12,9 +12,7 @@ import ( "time" "github.com/hpcng/warewulf/internal/pkg/container" - "github.com/hpcng/warewulf/internal/pkg/node" "github.com/hpcng/warewulf/internal/pkg/util" - "github.com/hpcng/warewulf/internal/pkg/warewulfd" "github.com/hpcng/warewulf/internal/pkg/wwlog" "github.com/spf13/cobra" ) @@ -135,16 +133,6 @@ func CobraRunE(cmd *cobra.Command, args []string) error { wwlog.Error("Could not build container %s: %s", containerName, err) os.Exit(1) } - nodeDB, err := node.New() - if err != nil { - wwlog.Warn("couldn't open node database, so can't update shim/grub if this container is used in default profile: %s", err) - } else { - profileMap, _ := nodeDB.MapAllProfiles() - if _, ok := profileMap["default"]; ok && profileMap["default"].ContainerName.Get() == containerName { - return warewulfd.CopyShimGrub() - } - } - return nil } func SetBinds(myBinds []string) { diff --git a/internal/pkg/api/container/container.go b/internal/pkg/api/container/container.go index 54497288..1942aed8 100644 --- a/internal/pkg/api/container/container.go +++ b/internal/pkg/api/container/container.go @@ -271,10 +271,6 @@ func ContainerImport(cip *wwapiv1.ContainerImportParameter) (containerName strin err = errors.Wrap(err, "failed to update profile") return } - err = warewulfd.CopyShimGrub() - if err != nil { - wwlog.Warn("couldn't copy shim/grub of default container: %s", err) - } } } // TODO: We need this in a function with a flock around it. diff --git a/internal/pkg/api/profile/profile.go b/internal/pkg/api/profile/profile.go index 731d546c..349c57a7 100644 --- a/internal/pkg/api/profile/profile.go +++ b/internal/pkg/api/profile/profile.go @@ -8,7 +8,6 @@ import ( "github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1" "github.com/hpcng/warewulf/internal/pkg/node" "github.com/hpcng/warewulf/internal/pkg/util" - "github.com/hpcng/warewulf/internal/pkg/warewulfd" "github.com/hpcng/warewulf/internal/pkg/wwlog" "github.com/pkg/errors" "gopkg.in/yaml.v2" @@ -33,12 +32,6 @@ func ProfileSet(set *wwapiv1.ProfileSetParameter) (err error) { return } dbError := apinode.DbSave(&nodeDB) - if util.InSlice(set.ProfileNames, "default") { - err = warewulfd.CopyShimGrub() - if err != nil { - wwlog.Warn("shim/grub couldn't be copied: %s", err) - } - } return dbError } diff --git a/internal/pkg/config/warewulf.go b/internal/pkg/config/warewulf.go index e8f08ac5..9a0dc3f4 100644 --- a/internal/pkg/config/warewulf.go +++ b/internal/pkg/config/warewulf.go @@ -10,4 +10,5 @@ type WarewulfConf struct { EnableHostOverlay bool `yaml:"host overlay" default:"true"` Syslog bool `yaml:"syslog" default:"false"` DataStore string `yaml:"datastore" default:"/var/lib/warewulf"` + GrubBoot bool `yaml:"grubboot" default:"false"` } diff --git a/internal/pkg/configure/tftp.go b/internal/pkg/configure/tftp.go index 82e023f3..058ca059 100644 --- a/internal/pkg/configure/tftp.go +++ b/internal/pkg/configure/tftp.go @@ -1,12 +1,12 @@ package configure import ( - "fmt" "os" "path" warewulfconf "github.com/hpcng/warewulf/internal/pkg/config" "github.com/hpcng/warewulf/internal/pkg/util" + "github.com/hpcng/warewulf/internal/pkg/warewulfd" "github.com/hpcng/warewulf/internal/pkg/wwlog" ) @@ -20,28 +20,34 @@ func TFTP() error { return err } - fmt.Printf("Writing PXE files to: %s\n", tftpdir) - copyCheck := make(map[string]bool) - for _, f := range controller.TFTP.IpxeBinaries { - if !path.IsAbs(f) { - f = path.Join(controller.Paths.Ipxesource, f) - } - if copyCheck[f] { - continue - } - copyCheck[f] = true - err = util.SafeCopyFile(f, path.Join(tftpdir, path.Base(f))) + if controller.Warewulf.GrubBoot { + err := warewulfd.CopyShimGrub() if err != nil { - wwlog.Warn("ipxe binary could not be copied, booting may not work: %s", err) + wwlog.Warn("error when copying shim/grub binaries: %s", err) + } + } else { + wwlog.Info("Writing PXE files to: %s", tftpdir) + copyCheck := make(map[string]bool) + for _, f := range controller.TFTP.IpxeBinaries { + if !path.IsAbs(f) { + f = path.Join(controller.Paths.Ipxesource, f) + } + if copyCheck[f] { + continue + } + copyCheck[f] = true + err = util.SafeCopyFile(f, path.Join(tftpdir, path.Base(f))) + if err != nil { + wwlog.Warn("ipxe binary could not be copied, booting may not work: %s", err) + } } } - if !controller.TFTP.Enabled { wwlog.Info("Warewulf does not auto start TFTP services due to disable by warewulf.conf") os.Exit(0) } - fmt.Printf("Enabling and restarting the TFTP services\n") + wwlog.Info("Enabling and restarting the TFTP services") err = util.SystemdStart(controller.TFTP.SystemdName) if err != nil { wwlog.Error("%s", err) diff --git a/internal/pkg/container/grub.go b/internal/pkg/container/grub.go index 443ae995..ac934581 100644 --- a/internal/pkg/container/grub.go +++ b/internal/pkg/container/grub.go @@ -35,11 +35,15 @@ func GrubFind(container string) string { if container_path == "" { return "" } + return GrubFindPath(container_path) +} + +func GrubFindPath(grub_path string) string { for _, grubdir := range grubDirs() { wwlog.Debug("Checking grub directory: %s", grubdir) for _, grubname := range grubNames() { wwlog.Debug("Checking for grub name: %s", grubname) - grubPaths, _ := filepath.Glob(path.Join(container_path, grubdir, grubname)) + grubPaths, _ := filepath.Glob(path.Join(grub_path, grubdir, grubname)) for _, grubpath := range grubPaths { wwlog.Debug("Checking for grub path: %s", grubpath) // Only succeeds if grubpath exists and, if a diff --git a/internal/pkg/container/shim.go b/internal/pkg/container/shim.go index 6d1efe5e..8d14f2ac 100644 --- a/internal/pkg/container/shim.go +++ b/internal/pkg/container/shim.go @@ -25,7 +25,7 @@ func shimNames() []string { } /* -find the path of the shim binary +find the path of the shim binary in container */ func ShimFind(container string) string { container_path := RootFsDir(container) @@ -33,11 +33,18 @@ func ShimFind(container string) string { if container_path == "" { return "" } + return ShimFindPath(container_path) +} + +/* +find the path of the shim binary in container +*/ +func ShimFindPath(shimpath string) string { for _, shimdir := range shimDirs() { wwlog.Debug("Checking shim directory: %s", shimdir) for _, shimname := range shimNames() { wwlog.Debug("Checking for shim name: %s", shimname) - shimPaths, _ := filepath.Glob(path.Join(container_path, shimdir, shimname)) + shimPaths, _ := filepath.Glob(path.Join(shimpath, shimdir, shimname)) for _, shimPath := range shimPaths { wwlog.Debug("Checking for shim path: %s", shimPath) // Only succeeds if shimPath exists and, if a diff --git a/internal/pkg/node/datastructure.go b/internal/pkg/node/datastructure.go index 0965570b..34432454 100644 --- a/internal/pkg/node/datastructure.go +++ b/internal/pkg/node/datastructure.go @@ -52,7 +52,6 @@ type NodeConf struct { PrimaryNetDev string `yaml:"primary network,omitempty" lopt:"primarynet" sopt:"p" comment:"Set the primary network interface"` Disks map[string]*Disk `yaml:"disks,omitempty"` FileSystems map[string]*FileSystem `yaml:"filesystems,omitempty"` - BootMethod string `yaml:"boot method,omitempty" lopt:"bootmethod" comment:" boot method, can be grub or ipxe"` } type IpmiConf struct { @@ -166,7 +165,6 @@ type NodeInfo struct { Ipmi *IpmiEntry Profiles Entry PrimaryNetDev Entry - BootMethod Entry NetDevs map[string]*NetDevEntry Tags map[string]*Entry Disks map[string]*DiskEntry diff --git a/internal/pkg/warewulfd/copyshim.go b/internal/pkg/warewulfd/copyshim.go index ac021e13..002ed336 100644 --- a/internal/pkg/warewulfd/copyshim.go +++ b/internal/pkg/warewulfd/copyshim.go @@ -9,49 +9,29 @@ import ( "github.com/hpcng/warewulf/internal/pkg/wwlog" "github.com/hpcng/warewulf/internal/pkg/container" - "github.com/hpcng/warewulf/internal/pkg/node" "github.com/hpcng/warewulf/internal/pkg/util" ) /* -Copies the default shim, which is the shim located in the default container +Copies the default shim, which is the shim located on host to the tftp directory */ func CopyShimGrub() (err error) { - wwlog.Debug("copy shim and grub binaries") - nodeDB, err := node.New() - if err != nil { - return err - } conf := warewulfconf.Get() - if err != nil { - return err - } - profiles, err := nodeDB.MapAllProfiles() - if err != nil { - return err - } - if _, ok := profiles["default"]; !ok { - return fmt.Errorf("default profile doesn't exist") - } - // *Entry.Get doesn't work the same as it works for nodes! - if profiles["default"].BootMethod.Get() == "ipxe" || profiles["default"].BootMethod.Get() == "" { - wwlog.Verbose("default profile uses ipxe boot") - return - } - shimPath := container.ShimFind(profiles["default"].ContainerName.Get()) + wwlog.Debug("copy shim and grub binaries from host") + shimPath := container.ShimFindPath("/") if shimPath == "" { - return fmt.Errorf("no shim found in the container: %s", profiles["default"].ContainerName.Get()) + return fmt.Errorf("no shim found on the host os") } err = util.CopyFile(shimPath, path.Join(conf.Paths.Tftpdir, "warewulf", "shim.efi")) if err != nil { return err } _ = os.Chmod(path.Join(conf.Paths.Tftpdir, "warewulf", "shim.efi"), 0o755) - grubPath := container.GrubFind(profiles["default"].ContainerName.Get()) + grubPath := container.GrubFindPath("/") if grubPath == "" { - return fmt.Errorf("no grub found in the container: %s", profiles["default"].ContainerName.Get()) + return fmt.Errorf("no grub found on host os") } err = util.CopyFile(grubPath, path.Join(conf.Paths.Tftpdir, "warewulf", "grub.efi")) if err != nil { diff --git a/internal/pkg/warewulfd/warewulfd.go b/internal/pkg/warewulfd/warewulfd.go index aab8cd80..0848f4da 100644 --- a/internal/pkg/warewulfd/warewulfd.go +++ b/internal/pkg/warewulfd/warewulfd.go @@ -65,7 +65,6 @@ func RunServer() error { wwlog.Error("Could not prepopulate node status DB: %s", err) } - err = CopyShimGrub() if err != nil { wwlog.Warn("couldn't copy default shim: %s", err) } diff --git a/overlays/host/etc/dhcp/dhcpd.conf.ww b/overlays/host/etc/dhcp/dhcpd.conf.ww index b7b35e3f..1054d401 100644 --- a/overlays/host/etc/dhcp/dhcpd.conf.ww +++ b/overlays/host/etc/dhcp/dhcpd.conf.ww @@ -24,12 +24,12 @@ option PXE.mtftp-delay code 5 = unsigned integer 8; option architecture-type code 93 = unsigned integer 16; -{{- if eq $.ProfileMap.default.BootMethod.Get "grub" }} +{{- if $.Warewulf.GrubBoot }} if substring (option vendor-class-identifier, 0, 9) = "PXEClient" { next-server {{ $.Ipaddr }}; filename "warewulf/shim.efi"; } elsif substring (option vendor-class-identifier, 0, 10) = "HTTPClient" { - filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/grub/shim.efi"; + filename "http://{{$.Ipaddr}}:{{$.Warewulf.Port}}/efiboot/shim.efi"; } {{- else }} if exists user-class and option user-class = "iPXE" { diff --git a/userdocs/contents/grub.rst b/userdocs/contents/grub.rst index 80b3d463..7bd3e0db 100644 --- a/userdocs/contents/grub.rst +++ b/userdocs/contents/grub.rst @@ -7,13 +7,9 @@ can be used with the advantage that secure boot can be used. That means that only the signed kernel of a distribution can be booted. This can be a huge security benefit for some scenarios. -In order to enable the grub bootm the field `boot method` has -to be set to `grub` at least for the `default` profile in `nodes.conf`. -This can also be done with the command - -.. code-block:: console - - # wwctl profile set default --bootmethod grub +In order to enable the grub boot method it has to ne enabled in `warewulf.conf`. +Nodes which are not known to warewulf will then booted with the shim/grub from +the host on which warewulf is installed. Boot process @@ -43,8 +39,8 @@ of one Distribution, which means that every Distribution needs a separate the containers. For the case when the node is unknown to warewulf or -can't be identified during the `tFTP`` boot phase, the binaries are -extracted from the container defined in the `default` profile. +can't be identified during the `tFTP`` boot phase, the shim/grub binaries of +the host in which warewulf is running will be used. PXE/tFTP boot ------------- @@ -68,8 +64,8 @@ The standard network boot process with `grub` and `iPXE` has following steps bios->shim [lhead=cluster1,label="filename=shim.efi"]; subgraph cluster1{ label="Grub boot" - shim[shape=record label="{shim.efi|extracted from container of default profile}"]; - grub[shape=record label="{grubx64.efi | name hardcoded in shim.efi|extracted from container of default profile}"] + shim[shape=record label="{shim.efi|from ww4 host}"]; + grub[shape=record label="{grubx64.efi | name hardcoded in shim.efi|from ww4 host}"] shim->grub[label="tFTP"]; grubcfg[shape=record label="{grub.cfg|static under tFTP root}"]; grub->grubcfg[label="tFTP"]; @@ -80,7 +76,7 @@ The standard network boot process with `grub` and `iPXE` has following steps } As the tFTP server is independent of warewulf, the `shim` and `grub` EFI binaries -for the tFTP server are copied from the container defined in the `default` profile. +for the tFTP server are copied from the host on which warewulf is running. This means that for secure boot the distributor e.g. SUSE of the container in the `default` profile must match the distributor of the container which then also must be signed by the SUSE key.