From fb9f269fc56296aa8375e90faea20403cc74f36c Mon Sep 17 00:00:00 2001 From: Jonathon Anderson Date: Wed, 12 Feb 2025 11:43:43 -0700 Subject: [PATCH 1/3] Update ipxe and grub configuration for clarity #1156 Also: - Defined a menu for iPXE - Support a IPXEMenuEntry tag to select the boot method - Merge iPXE dracut support into default.ipxe (use IPXEMenuEntry=dracut) Signed-off-by: Jonathon Anderson --- CHANGELOG.md | 6 + etc/grub/grub.cfg.ww | 210 +++++++++++++++++-------- etc/ipxe/default.ipxe | 213 +++++++++++++++----------- etc/ipxe/dracut.ipxe | 47 ------ internal/pkg/upgrade/node.go | 78 ++++++---- internal/pkg/upgrade/node_test.go | 23 +++ userdocs/contents/boot-management.rst | 21 +-- userdocs/contents/troubleshooting.rst | 4 +- 8 files changed, 358 insertions(+), 244 deletions(-) delete mode 100644 etc/ipxe/dracut.ipxe diff --git a/CHANGELOG.md b/CHANGELOG.md index 438cbe4f..b4efd2d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Added `WW_HISTFILE` to control shell history location during `wwctl image shell`. #1732 - Added target help in Makefile. #1740 - Added fstab mounts for `/home` and `/opt` to initial default profile. #1744 +- Add support for an `IPXEMenuEntry` tag to select the boot method during iPXE. ### Changed @@ -30,6 +31,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Update syncuser documentation. #1736 - Update PS1 during `wwctl image shell` to include working directory by default, and to include `PS1` from the environment if present. #1245 +- DHCP template generates as much of the subnet and range definition as possible. #1469 +- Updated overlay flags to `wwctl [--runtime-overlays|--system-overlays]`. #1495 +- Update ipxe and grub configuration for clarity and logging. #1156 +- Defined a menu for iPXE. #1156 ### Fixed @@ -48,6 +53,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Remove syncuser warning messages in `wwctl` that assume its use. #1321 - Remove syncuser from the list of default runtime overlays. #1322 - Removed check for "discoverable" profiles during `wwctl upgrade nodes`. +- Removed `dracut.ipxe` template. (Use `default.ipxe` and set tag `IPXEMenuEntry=dracut`.) ## v4.6.0rc2, 2025-02-07 diff --git a/etc/grub/grub.cfg.ww b/etc/grub/grub.cfg.ww index a0963266..c3ad0727 100644 --- a/etc/grub/grub.cfg.ww +++ b/etc/grub/grub.cfg.ww @@ -1,77 +1,170 @@ -{{ if .ImageName }} -echo "================================================================================" -echo "Warewulf v4 now http booting grub: {{.Fqdn}} ({{.Hwaddr}})" -echo "================================================================================" echo -echo "Warewulf Controller: {{.Ipaddr}}" +echo "Warewulf v4 (GRUB)" echo -sleep 1 +echo "Warewulf Server:" +echo "* Ipaddr: {{.Ipaddr}}" +echo "* Port: {{.Port}}" +echo +echo "This node:" +echo "* Fqdn: {{.Fqdn}}" +echo "* Hwaddr: {{.Hwaddr}}" +echo "* ImageName: {{.ImageName}}" +{{- if .KernelVersion }} +echo "* KernelVersion: {{.KernelVersion}}" +{{- else }} +echo "* KernelVersion: (image default)" +{{- end }} +echo "* KernelArgs: {{.KernelArgs}}" + +{{- if not .ImageName }} +echo "!!" +echo "!! No image defined for {{.Fqdn}}." +echo "!! Rebooting in 30s... +echo "!!" +sleep 30 +reboot +{{- end }} + +echo "Reading asset key..." smbios --type 3 --get-string 8 --set assetkey uri="(http,{{.Ipaddr}}:{{.Port}})/provision/${net_default_mac}?assetkey=${assetkey}" kernel="${uri}&stage=kernel" -image="${uri}&stage=image&compress=gz" -system="${uri}&stage=system&compress=gz" -runtime="${uri}&stage=runtime&compress=gz" -set default={{ or .Tags.GrubMenuEntry "ww4" }} -set timeout=5 +set default={{ or .Tags.GrubMenuEntry "single-stage" }} +set timeout=2 -menuentry "Network boot node: {{.Id}}" --id ww4 { - {{if .KernelVersion }} - echo "Kernel: {{.KernelVersion}}" - {{else}} - echo "Kernel: {{.ImageName}} (image default)" - {{end}} - echo "KernelArgs: {{.KernelArgs}}" +menuentry "Single-stage boot" --id single-stage { + echo "Warewulf Server:" + echo "* Ipaddr: {{.Ipaddr}}" + echo "* Port: {{.Port}}" + echo + echo "This node:" + echo "* Fqdn: {{.Fqdn}}" + echo "* Hwaddr: {{.Hwaddr}}" + echo "* ImageName: {{.ImageName}}" + {{- if .KernelVersion }} + echo "* KernelVersion: {{.KernelVersion}}" + {{- else }} + echo "* KernelVersion: (image default)" + {{- end }} + echo "* KernelArgs: {{.KernelArgs}}" + echo + echo "Downloading kernel image..." linux $kernel wwid=${net_default_mac} {{.KernelArgs}} - if [ x$? = x0 ] ; then - echo "Loading Image: {{.ImageName}}" - initrd $image $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 + if [ $? != 0 ] + then + echo "!!" + echo "!! Unable to load kernel." + echo "!! Rebooting in 15s..." + echo "!!" fi + + echo "Downloading images..." + image="${uri}&stage=image&compress=gz" + system="${uri}&stage=system&compress=gz" + runtime="${uri}&stage=runtime&compress=gz" + initrd $image $system $runtime + if [ $? != 0 ] + then + echo "!!" + echo "!! Unable to load images." + echo "!! Rebooting in 15s..." + echo "!!" + fi + + echo "Booting..." + boot } -menuentry "Network boot node with dracut: {{.Id}}" --id dracut { +menuentry "Single-stage boot (no compression)" --id single-stage-nocompress { + echo "Warewulf Server:" + echo "* Ipaddr: {{.Ipaddr}}" + echo "* Port: {{.Port}}" + echo + echo "This node:" + echo "* Fqdn: {{.Fqdn}}" + echo "* Hwaddr: {{.Hwaddr}}" + echo "* ImageName: {{.ImageName}}" + {{- if .KernelVersion }} + echo "* KernelVersion: {{.KernelVersion}}" + {{- else }} + echo "* KernelVersion: (image default)" + {{- end }} + echo "* KernelArgs: {{.KernelArgs}}" + echo + echo "Downloading kernel image..." + linux $kernel wwid=${net_default_mac} {{.KernelArgs}} + if [ $? != 0 ] + then + echo "!!" + echo "!! Unable to load kernel." + echo "!! Rebooting in 15s..." + echo "!!" + fi + + echo "Downloading images..." + image="${uri}&stage=image" + system="${uri}&stage=system" + runtime="${uri}&stage=runtime" + initrd $image $system $runtime + if [ $? != 0 ] + then + echo "!!" + echo "!! Unable to load images." + echo "!! Rebooting in 15s..." + echo "!!" + fi + + echo "Booting..." + boot +} + +menuentry "Two stage boot with dracut" --id dracut { + echo "Warewulf Server:" + echo "* Ipaddr: {{.Ipaddr}}" + echo "* Port: {{.Port}}" + echo + echo "This node:" + echo "* Fqdn: {{.Fqdn}}" + echo "* Hwaddr: {{.Hwaddr}}" + echo "* ImageName: {{.ImageName}}" + {{- if .KernelVersion }} + echo "* KernelVersion: {{.KernelVersion}}" + {{- else }} + echo "* KernelVersion: (image default)" + {{- end }} + echo "* KernelArgs: {{.KernelArgs}}" + initramfs="${uri}&stage=initramfs" wwinit_uri="http://{{.Ipaddr}}:{{.Port}}/provision/${net_default_mac}" - - {{if .KernelVersion }} - echo "Kernel: {{.KernelVersion}}" - {{else}} - echo "Kernel: {{.ImageName}} (image default)" - {{end}} - echo "KernelArgs: {{.KernelArgs}}" - net_args="rd.neednet=1 {{range $devname, $netdev := .NetDevs}}{{if and $netdev.Hwaddr $netdev.Device}} ifname={{$netdev.Device}}:{{$netdev.Hwaddr}} {{end}}{{end}}" wwinit_args="root=wwinit wwinit.uri=${wwinit_uri} init=/init" + + echo + echo "Downloading kernel image..." linux $kernel wwid=${net_default_mac} {{.KernelArgs}} $net_args $wwinit_args - - if [ x$? = x0 ] ; then - echo "Loading Image: {{.ImageName}}" - initrd $initramfs - 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 + if [ $? != 0 ] + then + echo "!!" + echo "!! Unable to load kernel." + echo "!! Rebooting in 15s..." + echo "!!" fi -} -menuentry "Chainload specific configfile" { - conf="(http,{{.Ipaddr}}:{{.Port}})/efiboot/grub.cfg?assetkey=${assetkey}" - configfile $conf + echo "Downloading initramfs..." + initrd $initramfs + if [ $? != 0 ] + then + echo "!!" + echo "!! Unable to load initramfs." + echo "!! Rebooting in 15s..." + echo "!!" + fi + + echo "Booting..." + boot } menuentry "UEFI Firmware Settings" --id "uefi-firmware" { @@ -87,12 +180,3 @@ menuentry "System shutdown" { echo "System shutting down..." halt } -{{ else }} -echo "================================================================================" -echo " Warewulf v4:" -echo " No node image defined for this node ({{.Fqdn}})." -echo " Rebooting in 30s." -echo "================================================================================" -sleep 30 -reboot -{{ end }} diff --git a/etc/ipxe/default.ipxe b/etc/ipxe/default.ipxe index 9977e702..92909a4d 100644 --- a/etc/ipxe/default.ipxe +++ b/etc/ipxe/default.ipxe @@ -1,94 +1,129 @@ #!ipxe -{{ if .ImageName }} -echo -echo ================================================================================ -echo Warewulf v4 now booting: {{.Fqdn}} ({{.Hwaddr}}) -echo -echo Image: {{.ImageName}} -{{if .KernelVersion }} -echo Kernel: {{.KernelVersion}} -{{else}} -echo Kernel: {{.ImageName}} (image default) -{{end}} -echo KernelArgs: {{.KernelArgs}} +echo +echo Warewulf v4 (iPXE) echo +set next start +goto metadata -set uri_base http://{{.Ipaddr}}:{{.Port}}/provision/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid} -echo Warewulf Controller: {{.Ipaddr}} - -echo Downloading Kernel Image: -kernel --name kernel ${uri_base}&stage=kernel || goto reboot - -# imgextract causes RAM space problems on non-EFI systems (because of the 3GB barrier -# in 32-Bit mode). -# -> Use the old initrd method with a compressed image to save as much RAM as possible -# in this early boot stage. -# See for more details. -iseq ${platform} efi && goto efi || goto noefi - -:efi - -# try extracting compressed images first -# NOTE: system overlay tends to be the smallest, so failure here is the cheapest -echo Downloading Image: -imgextract --name image ${uri_base}&stage=image&compress=gz || goto nocompress - -echo Downloading System Overlay: -imgextract --name system ${uri_base}&stage=system&compress=gz || goto reboot - -echo Downloading Runtime Overlay: -imgextract --name runtime ${uri_base}&stage=runtime&compress=gz && set runtime_initrd initrd=runtime || echo Failed downloading runtime overlay. - -goto imoktogo - -:nocompress - -echo -echo Image extract not supported in this iPXE, using standard initrd mode - -echo Downloading Image: -initrd --name image ${uri_base}&stage=image || goto reboot - -echo Downloading System Overlay: -initrd --name system ${uri_base}&stage=system || goto reboot - -echo Downloading Runtime Overlay: -initrd --name runtime ${uri_base}&stage=runtime && set runtime_initrd initrd=runtime || echo Failed downloading runtime overlay. - -goto imoktogo - -:noefi - -echo -echo Use legacy initrd mode with compressed images - -echo Downloading Image: -initrd --name image ${uri_base}&stage=image&compress=gz || goto reboot - -echo Downloading System Overlay: -initrd --name system ${uri_base}&stage=system&compress=gz || goto reboot - -echo Downloading Runtime Overlay: -initrd --name runtime ${uri_base}&stage=runtime&compress=gz && set runtime_initrd initrd=runtime || echo Failed downloading runtime overlay. - - -:imoktogo - -echo boot kernel initrd=image initrd=system ${runtime_initrd} wwid={{.Hwaddr}} {{.KernelArgs}} -boot kernel initrd=image initrd=system ${runtime_initrd} wwid={{.Hwaddr}} {{.KernelArgs}} || goto reboot - -:reboot -echo -echo There was an error, rebooting in 15s... -echo -sleep 15 -reboot -{{ else }} -echo ================================================================================ -echo Warewulf v4: -echo No image defined for this node ({{.Fqdn}}). -echo Rebooting in 30s. -echo ================================================================================ +:start +{{- if not .ImageName }} +echo !! +echo !! No image defined for {{.Fqdn}}. +echo !! Rebooting in 30s... +echo !! sleep 30 reboot -{{ end }} +{{- end }} + +set baseuri http://{{.Ipaddr}}:{{.Port}}/provision/{{.Hwaddr}} +set uri ${baseuri}?assetkey=${asset}&uuid=${uuid} + +echo Downloading kernel image... +kernel --name kernel ${uri}&stage=kernel || goto reboot + +{{- if .Tags.IPXEMenuEntry }} +set method {{ .Tags.IPXEMenuEntry }} +{{- else }} +# https://github.com/warewulf/warewulf/issues/222 +iseq ${platform} efi && set method imgextract || set method initrd +{{- end }} + +:menu +menu Warewulf v4 image provisioning method +item imgextract Single-stage with imgextract +item initrd Single-stage with initrd +item initrd_nocompress Single stage with initrd (no compression) +item dracut Two stage with dracut +item shell iPXE shell +choose --default ${method} --timeout 2000 method && goto ${method} || goto menu + +:imgextract +set next imgextract_continue +goto metadata +:imgextract_continue +echo +echo Downloading compressed image with imgextract... +imgextract --name image ${uri}&stage=image&compress=gz || goto error_use_initrd +echo Downloading compressed system overlay image with imgextract... +imgextract --name system ${uri}&stage=system&compress=gz || goto error_reboot +echo Downloading compressed runtime overlay image with imgextract... +imgextract --name runtime ${uri}&stage=runtime&compress=gz && set runtime_initrd initrd=runtime || echo Unable to download runtime overlay. (ignored) +goto boot_single_stage + +:error_use_initrd +echo Encountered an error. Now using initrd. +goto initrd_continue + +:initrd +set next initrd_continue +goto metadata +:initrd_continue +echo +echo Downloading compressed image with initrd... +initrd --name image ${uri}&stage=image&compress=gz || goto error_reboot +echo Downloading compressed system overlay with initrd... +initrd --name system ${uri}&stage=system&compress=gz || goto error_reboot +echo Downloading compressed runtime overlay with initrd... +initrd --name runtime ${uri}&stage=runtime&compress=gz && set runtime_initrd initrd=runtime || echo Unable to download runtime overlay. (ignored) +goto boot_single_stage + +:initrd_nocompress +set next initrd_nocompress_continue +goto metadata +:initrd_nocompress_continue +echo +echo Downloading uncompressed image with initrd... +initrd --name image ${uri}&stage=image || goto error_reboot +echo Downloading uncompressed system overlay with initrd... +initrd --name system ${uri}&stage=system || goto error_reboot +echo Downloading uncompressed runtime overlay with initrd... +initrd --name runtime ${uri}&stage=runtime && set runtime_initrd initrd=runtime || echo Unable to download runtime overlay. (ignored) +goto boot_single_stage + +:dracut +set next dracut_continue +goto metadata +:dracut_continue +echo +echo Downloading dracut initramfs... +initrd --name initramfs ${uri}&stage=initramfs || goto error_reboot +set dracut_net rd.neednet=1 {{range $devname, $netdev := .NetDevs}}{{if and $netdev.Hwaddr $netdev.Device}} ifname={{$netdev.Device}}:{{$netdev.Hwaddr}} ip={{$netdev.Device}}:dhcp {{end}}{{end}} +set dracut_wwinit root=wwinit wwinit.uri=${baseuri} init=/init +goto boot_two_stage_dracut + +:boot_single_stage +echo Booting (single stage)... +boot kernel initrd=image initrd=system ${runtime_initrd} wwid={{.Hwaddr}} {{.KernelArgs}} || goto error_reboot + +:boot_two_stage_dracut +echo Booting dracut (first stage)... +boot kernel initrd=initramfs ${dracut_net} ${dracut_wwinit} wwid={{.Hwaddr}} {{.KernelArgs}} || error_reboot + +:error_reboot +echo !! +echo !! Encountered an error. +echo !! rebooting in 15s... +echo !! +sleep 15 +reboot + +:shell +shell +goto menu + +:metadata +echo Warewulf Server: +echo * Ipaddr: {{.Ipaddr}} +echo * Port: {{.Port}} +echo +echo This node: +echo * Fqdn: {{.Fqdn}} +echo * Hwaddr: {{.Hwaddr}} +echo * ImageName: {{.ImageName}} +{{- if .KernelVersion }} +echo * KernelVersion: {{.KernelVersion}} +{{- else }} +echo * KernelVersion: (image default) +{{- end }} +echo * KernelArgs: {{.KernelArgs}} +goto ${next} diff --git a/etc/ipxe/dracut.ipxe b/etc/ipxe/dracut.ipxe deleted file mode 100644 index e9814e0c..00000000 --- a/etc/ipxe/dracut.ipxe +++ /dev/null @@ -1,47 +0,0 @@ -#!ipxe -{{ if .ImageName }} -echo -echo ================================================================================ -echo Warewulf v4 now booting via dracut: {{.Fqdn}} ({{.Hwaddr}}) -echo -echo Image: {{.ImageName}} -{{if .KernelVersion }} -echo Kernel: {{.KernelVersion}} -{{else}} -echo Kernel: {{.ImageName}} (image default) -{{end}} -echo KernelArgs: {{.KernelArgs}} -echo - -set baseuri http://{{.Ipaddr}}:{{.Port}}/provision/{{.Hwaddr}} -set uri ${baseuri}?assetkey=${asset}&uuid=${uuid} -echo Warewulf Controller: {{.Ipaddr}} - -echo Downloading Kernel Image: -kernel --name kernel ${uri}&stage=kernel || goto reboot - -echo Downloading initramfs -initrd --name initramfs ${uri}&stage=initramfs || goto reboot - -set dracut_net rd.neednet=1 {{range $devname, $netdev := .NetDevs}}{{if and $netdev.Hwaddr $netdev.Device}} ifname={{$netdev.Device}}:{{$netdev.Hwaddr}} ip={{$netdev.Device}}:dhcp {{end}}{{end}} -set dracut_wwinit root=wwinit wwinit.uri=${baseuri} init=/init - -echo Booting initramfs -boot kernel initrd=initramfs ${dracut_net} ${dracut_wwinit} wwid={{.Hwaddr}} {{.KernelArgs}} - - -:reboot -echo -echo There was an error, rebooting in 15s... -echo -sleep 15 -reboot -{{ else }} -echo ================================================================================ -echo Warewulf v4: -echo No image defined for this node ({{.Fqdn}}). -echo Rebooting in 30s. -echo ================================================================================ -sleep 30 -reboot -{{ end }} diff --git a/internal/pkg/upgrade/node.go b/internal/pkg/upgrade/node.go index 0fb1522f..d6c44765 100644 --- a/internal/pkg/upgrade/node.go +++ b/internal/pkg/upgrade/node.go @@ -226,7 +226,31 @@ func (legacy *Node) Upgrade(addDefaults bool, replaceOverlays bool) (upgraded *n if upgraded.Ipmi.Write == "" && legacy.IpmiWrite != "" { warnError(upgraded.Ipmi.Write.Set(legacy.IpmiWrite)) } - upgraded.Ipxe = legacy.Ipxe + if legacy.Keys != nil { + for key, value := range legacy.Keys { + upgraded.Tags[key] = value + } + } + if legacy.Tags != nil { + for key, value := range legacy.Tags { + upgraded.Tags[key] = value + } + } + for _, tag := range legacy.TagsDel { + delete(upgraded.Tags, tag) + } + if legacy.Ipxe == "dracut" { + if _, ok := upgraded.Tags["IPXEMenuEntry"]; !ok { + wwlog.Info("Replacing ipxe template 'dracut' with 'default' and setting tag 'IPXEMenuEntry=dracut'") + upgraded.Ipxe = "default" + upgraded.Tags["IPXEMenuEntry"] = "dracut" + } else { + wwlog.Warn("Found ipxe template 'dracut' but tag 'IPXEMenuEntry' is already set; ignoring") + upgraded.Ipxe = legacy.Ipxe + } + } else { + upgraded.Ipxe = legacy.Ipxe + } if legacy.Kernel != nil { upgraded.Kernel = legacy.Kernel.Upgrade(upgraded.ImageName) } else { @@ -237,11 +261,6 @@ func (legacy *Node) Upgrade(addDefaults bool, replaceOverlays bool) (upgraded *n } upgraded.Kernel = inlineKernel.Upgrade(upgraded.ImageName) } - if legacy.Keys != nil { - for key, value := range legacy.Keys { - upgraded.Tags[key] = value - } - } if legacy.NetDevs != nil { for name, netDev := range legacy.NetDevs { upgraded.NetDevs[name] = netDev.Upgrade(false) @@ -313,14 +332,6 @@ func (legacy *Node) Upgrade(addDefaults bool, replaceOverlays bool) (upgraded *n genericSplitOverlays) } } - if legacy.Tags != nil { - for key, value := range legacy.Tags { - upgraded.Tags[key] = value - } - } - for _, tag := range legacy.TagsDel { - delete(upgraded.Tags, tag) - } return } @@ -427,7 +438,31 @@ func (legacy *Profile) Upgrade(addDefaults bool, replaceOverlays bool) (upgraded if upgraded.Ipmi.Write == "" && legacy.IpmiWrite != "" { warnError(upgraded.Ipmi.Write.Set(legacy.IpmiWrite)) } - upgraded.Ipxe = legacy.Ipxe + if legacy.Keys != nil { + for key, value := range legacy.Keys { + upgraded.Tags[key] = value + } + } + if legacy.Tags != nil { + for key, value := range legacy.Tags { + upgraded.Tags[key] = value + } + } + for _, tag := range legacy.TagsDel { + delete(upgraded.Tags, tag) + } + if legacy.Ipxe == "dracut" { + if _, ok := upgraded.Tags["IPXEMenuEntry"]; !ok { + wwlog.Info("Replacing ipxe template 'dracut' with 'default' and setting tag 'IPXEMenuEntry=dracut'") + upgraded.Ipxe = "default" + upgraded.Tags["IPXEMenuEntry"] = "dracut" + } else { + wwlog.Warn("Found ipxe template 'dracut' but tag 'IPXEMenuEntry' is already set; ignoring") + upgraded.Ipxe = legacy.Ipxe + } + } else { + upgraded.Ipxe = legacy.Ipxe + } if legacy.Kernel != nil { upgraded.Kernel = legacy.Kernel.Upgrade(upgraded.ImageName) } else { @@ -438,11 +473,6 @@ func (legacy *Profile) Upgrade(addDefaults bool, replaceOverlays bool) (upgraded } upgraded.Kernel = inlineKernel.Upgrade(upgraded.ImageName) } - if legacy.Keys != nil { - for key, value := range legacy.Keys { - upgraded.Tags[key] = value - } - } if legacy.NetDevs != nil { for name, netDev := range legacy.NetDevs { upgraded.NetDevs[name] = netDev.Upgrade(addDefaults) @@ -503,14 +533,6 @@ func (legacy *Profile) Upgrade(addDefaults bool, replaceOverlays bool) (upgraded genericSplitOverlays) } } - if legacy.Tags != nil { - for key, value := range legacy.Tags { - upgraded.Tags[key] = value - } - } - for _, tag := range legacy.TagsDel { - delete(upgraded.Tags, tag) - } if legacy.Resources != nil { for key, value := range legacy.Resources { upgraded.Resources[key] = value diff --git a/internal/pkg/upgrade/node_test.go b/internal/pkg/upgrade/node_test.go index 7cb32a2a..083a277d 100644 --- a/internal/pkg/upgrade/node_test.go +++ b/internal/pkg/upgrade/node_test.go @@ -852,6 +852,29 @@ nfs: mount: true - path: /var mount: false +`, + }, + { + name: "Replace dracut template with IPXEMenuEntry", + legacyYaml: ` +nodeprofiles: + default: + ipxe template: dracut +nodes: + n1: + ipxe template: dracut +`, + upgradedYaml: ` +nodeprofiles: + default: + ipxe template: default + tags: + IPXEMenuEntry: dracut +nodes: + n1: + ipxe template: default + tags: + IPXEMenuEntry: dracut `, }, } diff --git a/userdocs/contents/boot-management.rst b/userdocs/contents/boot-management.rst index f246298c..263b6b46 100644 --- a/userdocs/contents/boot-management.rst +++ b/userdocs/contents/boot-management.rst @@ -332,24 +332,22 @@ initramfs inside the image. necessary to remove ``/etc/machine-id`` for dracut to properly generate the initramfs in the location that Warewulf is expecting. -Set the node's iPXE template to ``dracut`` to direct iPXE to fetch the -node's initramfs image and boot with dracut semantics, rather than -booting the node image directly. +To direct iPXE to fetch the node's initramfs image and boot with dracut +semantics, set an ``IPXEMenuEntry`` tag for the node. .. note:: - Warewulf iPXE templates are located at ``/etc/warewulf/ipxe/`` when - Warewulf is installed via official packages. You can learn more - about how dracut booting works by inspecting its iPXE template at - ``/etc/warewulf/ipxe/dracut.ipxe``. + Warewulf configures iPXE with a template located at + ``/etc/warewulf/ipxe/default.ipxe``. Inspect the template to learn more about + the dracut booting process. .. code-block:: shell - wwctl node set wwnode1 --ipxe dracut + wwctl node set wwnode1 --tagadd IPXEMenuEntry=dracut .. note:: - The iPXE template may be set at the node or profile level. + The IPXEMenuEntry variable may be set at the node or profile level. Alternatively, to direct GRUB to fetch the node's initramfs image and boot with dracut semantics, set a ``GrubMenuEntry`` tag for the node. @@ -379,8 +377,3 @@ to use up to 50% of physical memory. This size limit may be adjustd using the kernel argument `wwinit.tmpfs.size`. (This parameter is passed to the `size` option during tmpfs mount. See ``tmpfs(5)`` for more details.) - -.. warning:: - - Kernel overrides are not currently fully supported during dracut initramfs boot. - diff --git a/userdocs/contents/troubleshooting.rst b/userdocs/contents/troubleshooting.rst index a74b57cd..cc9e3345 100644 --- a/userdocs/contents/troubleshooting.rst +++ b/userdocs/contents/troubleshooting.rst @@ -23,12 +23,10 @@ iPXE If you're using iPXE to boot (the default), you can get a command prompt by pressing with C-b during boot. -From the iPXE command prompt, you can run the same commands from `default.ipxe`_ (or `dracut.ipxe`_) to troubleshoot potential boot problems. +From the iPXE command prompt, you can run the same commands from `default.ipxe`_ to troubleshoot potential boot problems. .. _default.ipxe: https://github.com/warewulf/warewulf/blob/main/etc/ipxe/default.ipxe -.. _dracut.ipxe: https://github.com/warewulf/warewulf/blob/main/etc/ipxe/dracut.ipxe - For example, the following commands perform a (relatively) normal Warewulf boot. (Substitute your Warewulf server's IP address in place of 10.0.0.1, update the port number if you have changed it from the default of 9873, From 4fa8601b75419a56871c6c92674387f0f8a03847 Mon Sep 17 00:00:00 2001 From: Jonathon Anderson Date: Wed, 12 Feb 2025 11:45:55 -0700 Subject: [PATCH 2/3] Updated wwinit for clarity #1156 Also: - Renamed /warewulf/wwinit to /warewulf/prescripts Signed-off-by: Jonathon Anderson --- CHANGELOG.md | 4 +- Makefile | 2 +- init.bak | 48 +++++++++++++++++++ internal/pkg/upgrade/node.go | 6 +++ internal/pkg/upgrade/node_test.go | 30 +++++++++++- overlays/wwinit/rootfs/init | 33 +++++++------ .../wwinit/rootfs/warewulf/init.d/20-loopback | 6 +-- .../wwinit/rootfs/warewulf/init.d/50-ipmi | 9 +++- .../rootfs/warewulf/init.d/75-vnfs_fixes | 28 +++++------ .../wwinit/rootfs/warewulf/init.d/90-selinux | 30 ++++++++---- overlays/wwinit/rootfs/warewulf/wwinit | 17 ------- overlays/wwinit/rootfs/warewulf/wwprescripts | 14 ++++++ 12 files changed, 159 insertions(+), 68 deletions(-) create mode 100755 init.bak delete mode 100755 overlays/wwinit/rootfs/warewulf/wwinit create mode 100755 overlays/wwinit/rootfs/warewulf/wwprescripts diff --git a/CHANGELOG.md b/CHANGELOG.md index b4efd2d8..26319b01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,8 +33,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). and to include `PS1` from the environment if present. #1245 - DHCP template generates as much of the subnet and range definition as possible. #1469 - Updated overlay flags to `wwctl [--runtime-overlays|--system-overlays]`. #1495 -- Update ipxe and grub configuration for clarity and logging. #1156 +- Added logging and updated output during iPXE and GRUB. #1156 - Defined a menu for iPXE. #1156 +- Added logging to wwinit scripts. #1156 +- Renamed /warewulf/wwinit to /warewulf/prescripts. #1156 ### Fixed diff --git a/Makefile b/Makefile index 9444f78d..245bdc64 100644 --- a/Makefile +++ b/Makefile @@ -216,7 +216,7 @@ install: build docs ## Install Warewulf from source (cd overlays && find * -path '*/internal' -prune -o -type d -exec mkdir -pv $(DESTDIR)$(DATADIR)/warewulf/overlays/{} \;) (cd overlays && find * -path '*/internal' -prune -o -type l -exec cp -av {} $(DESTDIR)$(DATADIR)/warewulf/overlays/{} \;) chmod 0755 $(DESTDIR)$(DATADIR)/warewulf/overlays/wwinit/rootfs/init - chmod 0755 $(DESTDIR)$(DATADIR)/warewulf/overlays/wwinit/rootfs/$(WWCLIENTDIR)/wwinit + chmod 0755 $(DESTDIR)$(DATADIR)/warewulf/overlays/wwinit/rootfs/$(WWCLIENTDIR)/wwprescripts chmod 0600 $(DESTDIR)$(DATADIR)/warewulf/overlays/wwinit/rootfs/$(WWCLIENTDIR)/config.ww chmod 0600 $(DESTDIR)$(DATADIR)/warewulf/overlays/ssh.host_keys/rootfs/etc/ssh/ssh* chmod 0644 $(DESTDIR)$(DATADIR)/warewulf/overlays/ssh.host_keys/rootfs/etc/ssh/ssh*.pub.ww diff --git a/init.bak b/init.bak new file mode 100755 index 00000000..e189c850 --- /dev/null +++ b/init.bak @@ -0,0 +1,48 @@ +#!/bin/sh + +echo "Warewulf pre-init (/init)" +echo +echo "Mounting kernel file systems..." +mountpoint -q /proc || (mkdir -p /proc && mount -t proc proc /proc && echo "Mounted /proc") +mountpoint -q /dev || (mkdir -p /dev && mount -t devtmpfs devtmpfs /dev && echo "Mounted /dev") +mountpoint -q /sys || (mkdir -p /sys && mount -t sysfs sysfs /sys && echo "Mounted /sys") +echo +echo "Loading /warewulf/config..." +if [ -f "/warewulf/config" ]; then + . /warewulf/config +else + echo "ERROR: Warewulf configuration file not found." + echo "Rebooting in 1 minute..." + sleep 60 + echo b > /proc/sysrq-trigger || /sbin/reboot -f +fi +echo +echo "Configuring root file system..." + +WWPRESCRIPTS=/warewulf/wwprescripts +chmod +rx "${WWPRESCRIPTS}" + +WWROOT="${WWROOT:-initramfs}" +if [ "${WWROOT}" = "initramfs" ]; then + echo "WWROOT=${WWROOT}: using initial rootfs and invoking ${WWPRESCRIPTS}..." + exec "${WWPRESCRIPTS}" +elif [ "${WWROOT}" = "ramfs" -o "${WWROOT}" = "tmpfs" ]; then + echo "WWROOT=${WWROOT}: setting up new ${WWROOT} rootfs (/newroot)..." + mkdir /newroot + mount wwroot /newroot -t ${WWROOT} -o mpol=interleave # mpol ignored for ramfs + tar -cf - --exclude ./proc --exclude ./sys --exclude ./dev --exclude --exclude ./newroot . | tar -xf - -C /newroot + mkdir /newroot/proc /newroot/dev /newroot/sys 2>/dev/null + echo "Switching to new rootfs (/newroot) and invoking ${WWPRESCRIPTS}..." + exec /sbin/switch_root /newroot "${WWPRESCRIPTS}" +else + echo "ERROR: Unrecognized rootfs type requested: ${WWROOT}" + echo "Rebooting in 1 minute..." + sleep 60 + echo b > /proc/sysrq-trigger || /sbin/reboot -f +fi + +echo +echo "ERROR: wwinit encountered a problem." +echo "Rebooting in 1 minute..." +sleep 60 +echo b > /proc/sysrq-trigger || /sbin/reboot -f diff --git a/internal/pkg/upgrade/node.go b/internal/pkg/upgrade/node.go index d6c44765..70960787 100644 --- a/internal/pkg/upgrade/node.go +++ b/internal/pkg/upgrade/node.go @@ -165,6 +165,7 @@ type Node struct { func (legacy *Node) Upgrade(addDefaults bool, replaceOverlays bool) (upgraded *node.Node) { upgraded = new(node.Node) upgraded.Tags = make(map[string]string) + upgraded.Resources = make(map[string]node.Resource) upgraded.Disks = make(map[string]*node.Disk) upgraded.FileSystems = make(map[string]*node.FileSystem) upgraded.Ipmi = new(node.IpmiConf) @@ -332,6 +333,11 @@ func (legacy *Node) Upgrade(addDefaults bool, replaceOverlays bool) (upgraded *n genericSplitOverlays) } } + if legacy.Resources != nil { + for key, value := range legacy.Resources { + upgraded.Resources[key] = value + } + } return } diff --git a/internal/pkg/upgrade/node_test.go b/internal/pkg/upgrade/node_test.go index 083a277d..79037b2f 100644 --- a/internal/pkg/upgrade/node_test.go +++ b/internal/pkg/upgrade/node_test.go @@ -254,7 +254,7 @@ nodes: `, }, { - name: "keys and tags", + name: "keys, tags, and resources", addDefaults: false, replaceOverlays: false, legacyYaml: ` @@ -269,6 +269,13 @@ nodeprofiles: key4: valD tagsdel: - key4 + resources: + res1: + - val1a + - val1b + res2: + - val2a + - val2b network devices: default: tags: @@ -295,6 +302,13 @@ nodes: key4: valD tagsdel: - key4 + resources: + resn1: + - valn1a + - valn1b + resn2: + - valn2a + - valn2b network devices: default: tags: @@ -327,6 +341,13 @@ nodeprofiles: key1: val1 key2: valB key3: valC + resources: + res1: + - val1a + - val1b + res2: + - val2a + - val2b nodes: n1: ipmi: @@ -342,6 +363,13 @@ nodes: key1: val1 key2: valB key3: valC + resources: + resn1: + - valn1a + - valn1b + resn2: + - valn2a + - valn2b `, }, { diff --git a/overlays/wwinit/rootfs/init b/overlays/wwinit/rootfs/init index 3982ae7d..e189c850 100755 --- a/overlays/wwinit/rootfs/init +++ b/overlays/wwinit/rootfs/init @@ -1,14 +1,13 @@ #!/bin/sh -# -# This is one of those types of files that you shouldn't edit unless you really -# know what you are doing and even then you should make a backup. -# -# Edit at your own risk! DANGER DANGER. +echo "Warewulf pre-init (/init)" +echo +echo "Mounting kernel file systems..." mountpoint -q /proc || (mkdir -p /proc && mount -t proc proc /proc && echo "Mounted /proc") mountpoint -q /dev || (mkdir -p /dev && mount -t devtmpfs devtmpfs /dev && echo "Mounted /dev") mountpoint -q /sys || (mkdir -p /sys && mount -t sysfs sysfs /sys && echo "Mounted /sys") - +echo +echo "Loading /warewulf/config..." if [ -f "/warewulf/config" ]; then . /warewulf/config else @@ -17,24 +16,24 @@ else sleep 60 echo b > /proc/sysrq-trigger || /sbin/reboot -f fi - -echo -echo "Warewulf v4 is now booting: ${WWHOSTNAME}" echo +echo "Configuring root file system..." -chmod 755 /warewulf/wwinit +WWPRESCRIPTS=/warewulf/wwprescripts +chmod +rx "${WWPRESCRIPTS}" -if [ -z "${WWROOT}" -o "${WWROOT}" = "initramfs" ]; then - echo "Retaining initramfs and invoking /warewulf/wwinit..." - exec /warewulf/wwinit +WWROOT="${WWROOT:-initramfs}" +if [ "${WWROOT}" = "initramfs" ]; then + echo "WWROOT=${WWROOT}: using initial rootfs and invoking ${WWPRESCRIPTS}..." + exec "${WWPRESCRIPTS}" elif [ "${WWROOT}" = "ramfs" -o "${WWROOT}" = "tmpfs" ]; then - echo "Setting up new ${WWROOT} rootfs..." + echo "WWROOT=${WWROOT}: setting up new ${WWROOT} rootfs (/newroot)..." mkdir /newroot mount wwroot /newroot -t ${WWROOT} -o mpol=interleave # mpol ignored for ramfs tar -cf - --exclude ./proc --exclude ./sys --exclude ./dev --exclude --exclude ./newroot . | tar -xf - -C /newroot mkdir /newroot/proc /newroot/dev /newroot/sys 2>/dev/null - echo "Switching to new rootfs and invoking /warewulf/wwinit..." - exec /sbin/switch_root /newroot /warewulf/wwinit + echo "Switching to new rootfs (/newroot) and invoking ${WWPRESCRIPTS}..." + exec /sbin/switch_root /newroot "${WWPRESCRIPTS}" else echo "ERROR: Unrecognized rootfs type requested: ${WWROOT}" echo "Rebooting in 1 minute..." @@ -43,7 +42,7 @@ else fi echo -echo "ERROR: There was a problem with the initial provisioning process." +echo "ERROR: wwinit encountered a problem." echo "Rebooting in 1 minute..." sleep 60 echo b > /proc/sysrq-trigger || /sbin/reboot -f diff --git a/overlays/wwinit/rootfs/warewulf/init.d/20-loopback b/overlays/wwinit/rootfs/warewulf/init.d/20-loopback index 01791174..0e603cc9 100644 --- a/overlays/wwinit/rootfs/warewulf/init.d/20-loopback +++ b/overlays/wwinit/rootfs/warewulf/init.d/20-loopback @@ -1,6 +1,6 @@ #!/bin/sh -. /warewulf/config - -echo "Bringing up lo:127.0.0.1" +echo "Warewulf prescript: localhost" +echo +echo "Bringing up lo..." /sbin/ip link set dev lo up diff --git a/overlays/wwinit/rootfs/warewulf/init.d/50-ipmi b/overlays/wwinit/rootfs/warewulf/init.d/50-ipmi index dcd602a7..f6cf8a45 100755 --- a/overlays/wwinit/rootfs/warewulf/init.d/50-ipmi +++ b/overlays/wwinit/rootfs/warewulf/init.d/50-ipmi @@ -4,17 +4,22 @@ export PATH=/usr/bin:/bin:/usr/sbin:/sbin +echo "Warewulf prescript: IPMI" +echo + if [ "$WWIPMI_WRITE" != "true" ]; then echo "IPMI write not configured: skipping" exit fi +echo "Loading IPMI kernel modules..." modprobe ipmi_si ipmi_ssif ipmi_devintf ipmi_msghandler || ( - echo "unable to load IPMI kernel modules: skipping IPMI configuration" + echo "Unable to load IPMI kernel modules: skipping IPMI configuration" exit ) if [ ! -e /dev/ipmi0 ]; then + echo "/dev/ipmi0 does not exist; creating..." sleep 1 ipmi_dev=$(grep ipmidev /proc/devices | awk '{ print $1 }') mknod -m 0666 /dev/ipmi0 c "$ipmi_dev" 0 @@ -83,7 +88,7 @@ if [ -n "$WWIPMI_PASSWORD" ]; then fi fi -# Serial Over LAN +echo "Configuring Serial over LAN..." ipmitool channel setaccess 1 2 link=on ipmi=on callin=on privilege=4 ipmitool sol set force-encryption true 1 ipmitool sol set force-authentication true 1 diff --git a/overlays/wwinit/rootfs/warewulf/init.d/75-vnfs_fixes b/overlays/wwinit/rootfs/warewulf/init.d/75-vnfs_fixes index 5ce5b735..587bfeee 100644 --- a/overlays/wwinit/rootfs/warewulf/init.d/75-vnfs_fixes +++ b/overlays/wwinit/rootfs/warewulf/init.d/75-vnfs_fixes @@ -2,23 +2,19 @@ . /warewulf/config -# This will eventually be optional -if true; then - if [ -f "/etc/pam.d/system-auth" ]; then - echo "FIXING: system-auth" - sed -i -e '/^account.*pam_unix\.so\s*$/s/\s*$/\ broken_shadow/' /etc/pam.d/system-auth +echo "Warewulf prescript: VNFS fixes" +echo + +add_broken_shadow() { + if [ -f "${1}" ]; then + echo "Adding broken_shadow to ${1}..." + sed -i -e '/^account.*pam_unix\.so\s*$/s/\s*$/\ broken_shadow/' "${1}" fi +} - if [ -f "/etc/pam.d/password-auth" ]; then - echo "FIXING: password-auth" - sed -i -e '/^account.*pam_unix\.so\s*$/s/\s*$/\ broken_shadow/' /etc/pam.d/password-auth - fi - - if [ -f "/etc/pam.d/common-account" ]; then - echo "FIXING: common-account" - sed -i -e '/^account.*pam_unix\.so\s*$/s/\s*$/\ broken_shadow/' /etc/pam.d/common-account - fi -fi - +add_broken_shadow /etc/pam.d/system-auth +add_broken_shadow /etc/pam.d/password-auth +add_broken_shadow /etc/pam.d/common-account +echo "Adjusting root directory permissions..." chmod 755 / diff --git a/overlays/wwinit/rootfs/warewulf/init.d/90-selinux b/overlays/wwinit/rootfs/warewulf/init.d/90-selinux index 2b910b2e..90d2d0c7 100644 --- a/overlays/wwinit/rootfs/warewulf/init.d/90-selinux +++ b/overlays/wwinit/rootfs/warewulf/init.d/90-selinux @@ -2,32 +2,42 @@ . /warewulf/config +echo "Warewulf prescript: SELinux" + if test -f "/etc/sysconfig/selinux"; then . /etc/sysconfig/selinux else - echo "Skipping SELinux configuration: Host config not found: /etc/sysconfig/selinux" + echo "File not found: /etc/sysconfig/selinux" + echo "Skipping SELinux configuration." exit fi -if test "$SELINUX" == "disabled"; then - echo "Skipping SELinux setup per /etc/sysconfig/selinux" +if [ "$SELINUX" = "disabled" ]; then + echo "/etc/sysconfig/selinux:SELINUX=${SELINUX}: skipping SELinux configuration." exit fi if grep -q "selinux=0" /proc/cmdline; then - echo "Skipping SELinux setup per kernel command line" + echo "/proc/cmdline:selinux=0: skipping SELinux configuration." exit fi -if [ $(findmnt / --noheadings --output SOURCE) == 'rootfs' ]; then - echo "Skipping SELinux configuration: rootfs does not support SELinux contexts" +if [ $(findmnt / --noheadings --output SOURCE) = 'rootfs' ]; then + echo "/ is a 'rootfs' file system, which does not support SELinux contexts." + echo "Skipping SELinux configuration." echo - echo "WARNING: SELinux prep is being skipped, but SELinux is enabled on host! This may" - echo "WARNING: cause the system to not work properly. Try setting 'Root=tmpfs'" - sleep 5 + echo "WARNING: SELinux configuration is being skipped, but SELinux is" + echo "WARNING: enabled in the image! The node will probably not boot" + echo "WARNING: properly." + echo "WARNING:" + echo "WARNING: Try setting 'Root=tmpfs'" + echo + echo "Continuing in 60s..." + sleep 60 exit fi -echo "Setting up SELinux" +echo "Loading SELinux policy..." /sbin/load_policy -i +echo "Restoring filesystem labels..." /sbin/restorecon -e /sys -r / diff --git a/overlays/wwinit/rootfs/warewulf/wwinit b/overlays/wwinit/rootfs/warewulf/wwinit deleted file mode 100755 index 1302c042..00000000 --- a/overlays/wwinit/rootfs/warewulf/wwinit +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh - -echo "Hello from WWINIT" - -. /warewulf/config - -myPATH=/warewulf/init.d -ls -1 $myPATH/ | while read -r NAME; do - echo "Launching: $NAME" - sh "$myPATH/$NAME" -done - -echo "Calling $WWINIT..." -echo - -sleep 2 -exec $WWINIT diff --git a/overlays/wwinit/rootfs/warewulf/wwprescripts b/overlays/wwinit/rootfs/warewulf/wwprescripts new file mode 100755 index 00000000..92342b3a --- /dev/null +++ b/overlays/wwinit/rootfs/warewulf/wwprescripts @@ -0,0 +1,14 @@ +#!/usr/bin/sh + +. /warewulf/config + +echo "Warewulf prescript runner (/warewulf/wwprescripts)" +scriptdir=/warewulf/init.d +echo "Looking for prescripts in /warewulf/init.d/..." +ls -1 "${scriptdir}/" | while read -r name; do + echo "Running prescript: ${name}..." + sh "${scriptdir}/${name}" +done +echo +echo "Running ${WWINIT}..." +exec $WWINIT From fd49254ac592d325056aa58a564933a008539607 Mon Sep 17 00:00:00 2001 From: Jonathon Anderson Date: Wed, 12 Feb 2025 11:46:49 -0700 Subject: [PATCH 3/3] Display auto-detected kernel version during iPXE and GRUB Signed-off-by: Jonathon Anderson --- CHANGELOG.md | 1 + internal/pkg/warewulfd/provision.go | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26319b01..b5d4f7b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Defined a menu for iPXE. #1156 - Added logging to wwinit scripts. #1156 - Renamed /warewulf/wwinit to /warewulf/prescripts. #1156 +- Display auto-detected kernel version during iPXE and GRUB. #1742 ### Fixed diff --git a/internal/pkg/warewulfd/provision.go b/internal/pkg/warewulfd/provision.go index ab1d16da..07e261dd 100644 --- a/internal/pkg/warewulfd/provision.go +++ b/internal/pkg/warewulfd/provision.go @@ -104,6 +104,11 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { kernelArgs = strings.Join(remoteNode.Kernel.Args, " ") kernelVersion = remoteNode.Kernel.Version } + if kernelVersion == "" { + if kernel_ := kernel.FromNode(&remoteNode); kernel_ != nil { + kernelVersion = kernel_.Version() + } + } tmpl_data = &templateVars{ Id: remoteNode.Id(), Cluster: remoteNode.ClusterName, @@ -186,6 +191,11 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) { kernelArgs = strings.Join(remoteNode.Kernel.Args, " ") kernelVersion = remoteNode.Kernel.Version } + if kernelVersion == "" { + if kernel_ := kernel.FromNode(&remoteNode); kernel_ != nil { + kernelVersion = kernel_.Version() + } + } tmpl_data = &templateVars{ Id: remoteNode.Id(), Cluster: remoteNode.ClusterName,