Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dracut initramfs boot #1191

Merged
merged 7 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

die isn't a posix shell function, so (echo "Unable to load ${archive}";exit 1) should do the same.

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
anderbubble marked this conversation as resolved.
Show resolved Hide resolved
# 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
Loading