Skip to content

Commit

Permalink
Merge pull request warewulf#1191 from anderbubble/dracut-initramfs-boot
Browse files Browse the repository at this point in the history
Dracut initramfs boot
  • Loading branch information
anderbubble authored Jun 6, 2024
2 parents 892db0f + 05b951d commit 88f0b1b
Show file tree
Hide file tree
Showing 17 changed files with 461 additions and 32 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added

- Add examples for building overlays in parallel to documentation
- Add `stage=initramfs` to warewulfd provision to serve initramfs from container image. #1115
- Add `warewulf-dracut` package to support building Warewulf-compatible initramfs images with dracut. #1115
- Add iPXE template `dracut.ipxe` to boot a dracut initramfs. #1115
- Add dracut menuentry to `grub.cfg.ww` to boot a dracut initramfs. #1115
- Add `.NetDevs` variable to iPXE and GRUB templates, similar to overlay templates. #1115
- Add `.Tags` variable to iPXE and GRUB templates, similar to overlay templates. #1115

### Changed

- Replace reference to docusaurus with Sphinx
- `wwctl container import` now only runs syncuser if explicitly requested. #1212
- wwinit now configures NetworkManager to not retain configurations from dracut. #1115
- Improved detection of SELinux capable root fs #1093

### Fixed

Expand Down
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ install: build docs
install -m 0644 etc/bash_completion.d/wwctl $(DESTDIR)$(BASHCOMPDIR)/wwctl
for f in docs/man/man1/*.1.gz; do install -m 0644 $$f $(DESTDIR)$(MANDIR)/man1/; done
for f in docs/man/man5/*.5.gz; do install -m 0644 $$f $(DESTDIR)$(MANDIR)/man5/; done
install -pd -m 0755 $(DESTDIR)$(DRACUTMODDIR)/90wwinit
install -m 0644 dracut/modules.d/90wwinit/*.sh $(DESTDIR)$(DRACUTMODDIR)/90wwinit

.PHONY: installapi
installapi:
Expand Down
3 changes: 2 additions & 1 deletion Variables.mk
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ else
endif

# OS-Specific Service Locations
VARLIST += TFTPDIR FIREWALLDDIR SYSTEMDDIR BASHCOMPDIR
VARLIST += TFTPDIR FIREWALLDDIR SYSTEMDDIR BASHCOMPDIR DRACUTMODDIR
SYSTEMDDIR ?= /usr/lib/systemd/system
BASHCOMPDIR ?= /etc/bash_completion.d
FIREWALLDDIR ?= /usr/lib/firewalld/services
DRACUTMODDIR ?= /usr/lib/dracut/modules.d
ifeq ($(OS),suse)
TFTPDIR ?= /srv/tftpboot
endif
Expand Down
13 changes: 13 additions & 0 deletions dracut/modules.d/90wwinit/load-wwinit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

info "Mounting tmpfs at $NEWROOT"
mount -t tmpfs ${wwinit_tmpfs_size_option} tmpfs "$NEWROOT"

for archive in "${wwinit_container}" "${wwinit_kmods}" "${wwinit_system}" "${wwinit_runtime}"
do
if [ -n "${archive}" ]
then
info "Loading ${archive}"
(curl --silent -L "${archive}" | gzip -d | cpio -im --directory="${NEWROOT}") || die "Unable to load ${archive}"
fi
done
20 changes: 20 additions & 0 deletions dracut/modules.d/90wwinit/module-setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash

check() {
# Don't include in hostonly mode
[[ $hostonly ]] && return 1

# Don't include by default
return 255
}

depends() {
echo network
return 0
}

install() {
inst_multiple cpio curl
inst_hook cmdline 30 "$moddir/parse-wwinit.sh"
inst_hook pre-mount 30 "$moddir/load-wwinit.sh"
}
28 changes: 28 additions & 0 deletions dracut/modules.d/90wwinit/parse-wwinit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/sh
# root=wwinit

[ -z "$root" ] && root=$(getarg root=)

if [ "${root}" = "wwinit" ]
then
info "root=${root}"
export wwinit_container=$(getarg wwinit.container=); info "wwinit.container=${wwinit_container}"
export wwinit_system=$(getarg wwinit.system=); info "wwinit.system=${wwinit_system}"
export wwinit_runtime=$(getarg wwinit.runtime=); info "wwinit.runtime=${wwinit_runtime}"
export wwinit_kmods=$(getarg wwinit.kmods=); info "wwinit.kmods=${wwinit_kmods}"

wwinit_tmpfs_size=$(getarg wwinit.tmpfs.size=)
if [ -n "$wwinit_tmpfs_size" ]
then
info "wwinit.tmpfs.size=${wwinit_tmpfs_size}"
export wwinit_tmpfs_size_option="-o size=${wwinit_tmpfs_size}"
fi

if [ -n "${wwinit_container}" ]
then
info "Found root=${root} and a Warewulf container image. Will boot from Warewulf."
rootok=1
else
die "Found root=${root} but no container image. Cannot boot from Warewulf."
fi
fi
42 changes: 41 additions & 1 deletion etc/grub/grub.cfg.ww
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ 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"
runtime="${uri}&stage=runtime&compress=gz"
set default=ww4

set default={{ or .Tags.GrubMenuEntry "ww4" }}
set timeout=5

menuentry "Network boot node: {{.Id}}" --id ww4 {
{{if .KernelOverride }}
echo "Kernel: {{.KernelOverride}}"
Expand All @@ -34,17 +37,54 @@ menuentry "Network boot node: {{.Id}}" --id ww4 {
reboot
fi
}

menuentry "Network boot node with dracut: {{.Id}}" --id dracut {
initramfs="${uri}&stage=initramfs"

uri="http://{{.Ipaddr}}:{{.Port}}/provision/${net_default_mac}?assetkey=${assetkey}"
container="${uri}&stage=container&compress=gz"
system="${uri}&stage=system&compress=gz"
runtime="${uri}&stage=runtime&compress=gz"

{{if .KernelOverride }}
echo "Kernel: {{.KernelOverride}}"
{{else}}
echo "Kernel: {{.ContainerName}} (container 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.container=${container} wwinit.system=${system} wwinit.runtime=${runtime} init=/init"
linux $kernel wwid=${net_default_mac} {{.KernelArgs}} $net_args $wwinit_args

if [ x$? = x0 ] ; then
echo "Loading Container: {{.ContainerName}}"
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
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
Expand Down
46 changes: 46 additions & 0 deletions etc/ipxe/dracut.ipxe
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!ipxe

echo
echo ================================================================================
echo Warewulf v4 now booting via dracut: {{.Fqdn}} ({{.Hwaddr}})
echo
echo Container: {{.ContainerName}}
{{if .KernelOverride }}
echo Kernel: {{.KernelOverride}}
{{else}}
echo Kernel: {{.ContainerName}} (container default)
{{end}}
echo KernelArgs: {{.KernelArgs}}
echo

set uri http://{{.Ipaddr}}:{{.Port}}/provision/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid}
echo Warewulf Controller: {{.Ipaddr}}

echo Downloading Kernel Image:
kernel --name kernel ${uri}&stage=kernel || goto reboot

{{if ne .KernelOverride ""}}
echo Downloading Kernel Modules:
imgextract --name kmods ${uri}&stage=kmods&compress=gz || initrd --name kmods ${uri}&stage=kmods || goto reboot
set kernel_mods initrd=kmods
{{end}}

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}} {{end}}{{end}}
set dracut_wwinit root=wwinit wwinit.container=${uri}&stage=container&compress=gz wwinit.system=${uri}&stage=system&compress=gz wwinit.runtime=${uri}&stage=runtime&compress=gz {{if ne .KernelOverride ""}}wwinit.kmods=${uri}&stage=kmods&compress=gz{{end}} init=/init

echo Booting initramfs
#echo Network KernelArgs: ${dracut_net}
#echo Dracut wwinit KernelArgs: ${dracut_wwinit}
#sleep 15
boot kernel initrd=initramfs ${kernel_mods} ${dracut_net} ${dracut_wwinit} wwid={{.Hwaddr}} {{.KernelArgs}}


:reboot
echo
echo There was an error, rebooting in 15s...
echo
sleep 15
reboot
27 changes: 27 additions & 0 deletions internal/pkg/container/config.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
package container

import (
"fmt"
"path"

"github.com/warewulf/warewulf/internal/pkg/util"
"github.com/warewulf/warewulf/internal/pkg/wwlog"

warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
)

var (
initramfsSearchPaths = []string{
// This is a printf format where the %s will be the kernel version
"boot/initramfs-%s",
"boot/initramfs-%s.img",
"boot/initrd-%s",
"boot/initrd-%s.img",
}
)

func SourceParentDir() string {
conf := warewulfconf.Get()
return conf.Paths.WWChrootdir
Expand All @@ -27,3 +41,16 @@ func ImageParentDir() string {
func ImageFile(name string) string {
return path.Join(ImageParentDir(), name+".img")
}

// InitramfsBootPath returns the dracut built initramfs path, as dracut built initramfs inside container
// the function returns host path of the built file
func InitramfsBootPath(image, kver string) (string, error) {
for _, searchPath := range initramfsSearchPaths {
initramfs_path := path.Join(RootFsDir(image), fmt.Sprintf(searchPath, kver))
wwlog.Debug("Looking for initramfs at: %s", initramfs_path)
if util.IsFile(initramfs_path) {
return initramfs_path, nil
}
}
return "", fmt.Errorf("Failed to find a target kernel version initramfs")
}
88 changes: 88 additions & 0 deletions internal/pkg/container/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package container

import (
"fmt"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
)

func TestInitramfsBootPath(t *testing.T) {
conf := warewulfconf.Get()
temp, err := os.MkdirTemp(os.TempDir(), "ww-conf-*")
assert.NoError(t, err)
defer os.RemoveAll(temp)
conf.Paths.WWChrootdir = temp

assert.NoError(t, os.MkdirAll(filepath.Join(RootFsDir("image"), "boot"), 0700))

tests := []struct {
name string
initramfs []string
ver string
err error
retName string
}{
{
name: "ok case 1",
initramfs: []string{"initramfs-1.1.1.aarch64.img"},
ver: "1.1.1.aarch64",
err: nil,
},
{
name: "ok case 2",
initramfs: []string{"initrd-1.1.1.aarch64"},
ver: "1.1.1.aarch64",
err: nil,
},
{
name: "ok case 3",
initramfs: []string{"initramfs-1.1.1.aarch64"},
ver: "1.1.1.aarch64",
err: nil,
},
{
name: "ok case 4",
initramfs: []string{"initrd-1.1.1.aarch64.img"},
ver: "1.1.1.aarch64",
err: nil,
},
{
name: "error case, wrong init name",
initramfs: []string{"initrr-1.1.1.aarch64.img"},
ver: "1.1.1.aarch64",
err: fmt.Errorf("Failed to find a target kernel version initramfs"),
},
{
name: "error case, wrong ver",
initramfs: []string{"initrr-1.1.1.aarch64.img"},
ver: "1.1.2.aarch64",
err: fmt.Errorf("Failed to find a target kernel version initramfs"),
},
}

for _, tt := range tests {
t.Logf("running test: %s", tt.name)
for _, init := range tt.initramfs {
assert.NoError(t, os.WriteFile(filepath.Join(RootFsDir("image"), "boot", init), []byte(""), 0600))
}
initPath, err := InitramfsBootPath("image", tt.ver)
assert.Equal(t, tt.err, err)
if err == nil {
assert.NotEmpty(t, initPath)
} else {
assert.Empty(t, initPath)
}

if tt.retName != "" {
assert.Equal(t, filepath.Base(initPath), tt.retName)
}
// remove the file
for _, init := range tt.initramfs {
assert.NoError(t, os.Remove(filepath.Join(RootFsDir("image"), "boot", init)))
}
}
}
2 changes: 2 additions & 0 deletions internal/pkg/warewulfd/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ func parseReq(req *http.Request) (parserInfo, error) {
ret.stage = "runtime"
} else if stage == "efiboot" {
ret.stage = "efiboot"
} else if stage == "initramfs" {
ret.stage = "initramfs"
}
}

Expand Down
Loading

0 comments on commit 88f0b1b

Please sign in to comment.