Additional overlays to support provision-to-disk without ignition

- sfdisk - partitions the disk
- mkfs - formats partitions
- mkswap - formats partitions for swap
- systemd.mount - explicit creation of systemd mount units
- systemd.swap - explicit creation of systemd swap units

Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
Jonathon Anderson
2025-06-30 23:24:20 -06:00
parent b98eae4b01
commit db3a3fee05
21 changed files with 1547 additions and 17 deletions

View File

@@ -12,7 +12,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Added userdocs for `wwctl node import` from yaml/csv.
- Added uid, gid, and permissions to OverlayFile in REST API. #1925
- (preview) Support provisioning to local storage with `wwctl <node|partition> set --root=/path/to/disk`. #1894
- (preview) Support disk provisioning during dracut boot stage with Ignition. #1894
- (preview) Support disk provisioning during dracut boot stage with Ignition, sfdisk, mkfs, and mkswap. #1894
- Support creating multiple symlinks from a single overlay template. #1894
- Added systemd.mount and systemd.swap overlays. #1894
- Support configuring Ignition with resources. #1894
- Added `wwctl <node|partition> set --parttype`. #1894

View File

@@ -368,17 +368,24 @@ func BuildOverlayIndir(nodeData node.Node, allNodes []node.Node, overlayNames []
// search for magic file name comment
fileScanner := bufio.NewScanner(bytes.NewReader(buffer.Bytes()))
fileScanner.Split(ScanLines)
foundFileComment := false
writingToNamedFile := false
isLink := false
for fileScanner.Scan() {
line := fileScanner.Text()
filenameFromTemplate := regFile.FindAllStringSubmatch(line, -1)
softlinkFromTemplate := regLink.FindAllStringSubmatch(line, -1)
if len(softlinkFromTemplate) != 0 {
wwlog.Debug("Creating soft link %s -> %s", outputPath, softlinkFromTemplate[0][1])
return os.Symlink(softlinkFromTemplate[0][1], outputPath)
targetFromTemplate := regLink.FindAllStringSubmatch(line, -1)
if len(targetFromTemplate) != 0 {
target := targetFromTemplate[0][1]
wwlog.Debug("Creating soft link %s -> %s", outputPath, target)
err := os.Symlink(target, outputPath)
if err != nil {
return fmt.Errorf("could not create symlink from template: %w", err)
} else {
isLink = true
}
} else if len(filenameFromTemplate) != 0 {
wwlog.Debug("Writing file %s", filenameFromTemplate[0][1])
if foundFileComment {
if writingToNamedFile && !isLink {
err = CarefulWriteBuffer(outputPath, fileBuffer, backupFile, info.Mode())
if err != nil {
return fmt.Errorf("could not write file from template: %w", err)
@@ -390,22 +397,25 @@ func BuildOverlayIndir(nodeData node.Node, allNodes []node.Node, overlayNames []
fileBuffer.Reset()
}
outputPath = path.Join(path.Dir(originalOutputPath), filenameFromTemplate[0][1])
foundFileComment = true
writingToNamedFile = true
isLink = false
} else {
if _, err = fileBuffer.WriteString(line); err != nil {
return fmt.Errorf("could not write to template buffer: %w", err)
}
}
}
err = CarefulWriteBuffer(outputPath, fileBuffer, backupFile, info.Mode())
if err != nil {
return fmt.Errorf("could not write file from template: %w", err)
if !isLink {
err = CarefulWriteBuffer(outputPath, fileBuffer, backupFile, info.Mode())
if err != nil {
return fmt.Errorf("could not write file from template: %w", err)
}
err = util.CopyUIDGID(walkPath, outputPath)
if err != nil {
return fmt.Errorf("failed setting permissions on template output file: %w", err)
}
wwlog.Debug("Wrote template file into overlay: %s", outputPath)
}
err = util.CopyUIDGID(walkPath, outputPath)
if err != nil {
return fmt.Errorf("failed setting permissions on template output file: %w", err)
}
wwlog.Debug("Wrote template file into overlay: %s", outputPath)
} else if info.Mode()&os.ModeSymlink == os.ModeSymlink {
wwlog.Debug("Found symlink %s", walkPath)

View File

@@ -253,6 +253,22 @@ T3
"test-link": "/test-target",
},
},
"multiple symlinks from template": {
overlays: []string{"o1"},
overlayFiles: map[string]string{
"/var/lib/warewulf/overlays/o1/rootfs/test-link.ww": `
{{ file "test-link1"}}
{{ softlink "/test-target1" }}
{{ file "test-link2"}}
{{ softlink "/test-target2" }}
`,
},
outputDir: "/image",
outputSymlinks: map[string]string{
"test-link1": "/test-target1",
"test-link2": "/test-target2",
},
},
"expansion of nodes": {
overlays: []string{"o1"},
overlayFiles: map[string]string{

View File

@@ -0,0 +1,216 @@
package mkfs
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"github.com/warewulf/warewulf/internal/app/wwctl/overlay/show"
"github.com/warewulf/warewulf/internal/pkg/testenv"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func Test_mkfsOverlay(t *testing.T) {
tests := map[string]struct {
args []string
nodesConf string
output string
}{
"mkfs:20-mkfs.sh.ww (empty)": {
args: []string{"--quiet=true", "--render=node1", "mkfs", "warewulf/wwinit.d/20-mkfs.sh.ww"},
nodesConf: `
nodes:
node1: {}`,
output: `#!/bin/sh
PATH=$PATH:/sbin:/usr/sbin:/bin:/usr/bin
if ! command -v info >/dev/null; then
info() {
printf '%s\n' "$*"
}
fi
if ! command -v die >/dev/null; then
die() {
printf '%s\n' "$*" >&2
exit 1
}
fi
already_formatted() {
if ! command -v wipefs >/dev/null ; then
info "warewulf: wipefs not found, cannot check if device is already formatted"
return 0
fi
if wipefs -n "${1}" &>/dev/null; then
info "warewulf: ${1} already formatted"
return 0
fi
return 1
}
if command -v mkfs >/dev/null ; then :
else
info "warewulf: mkfs not found"
fi
`,
},
"mkfs:20-mkfs.sh.ww (resource)": {
args: []string{"--quiet=true", "--render=node1", "mkfs", "warewulf/wwinit.d/20-mkfs.sh.ww"},
nodesConf: `
nodes:
node1:
resources:
mkfs:
- device: /dev/disk/by-partlabel/rootfs
type: ext4
- device: /dev/disk/by-partlabel/scratch
type: ext4
overwrite: true`,
output: `#!/bin/sh
PATH=$PATH:/sbin:/usr/sbin:/bin:/usr/bin
if ! command -v info >/dev/null; then
info() {
printf '%s\n' "$*"
}
fi
if ! command -v die >/dev/null; then
die() {
printf '%s\n' "$*" >&2
exit 1
}
fi
already_formatted() {
if ! command -v wipefs >/dev/null ; then
info "warewulf: wipefs not found, cannot check if device is already formatted"
return 0
fi
if wipefs -n "${1}" &>/dev/null; then
info "warewulf: ${1} already formatted"
return 0
fi
return 1
}
if command -v mkfs >/dev/null ; then :
if false || ! already_formatted /dev/disk/by-partlabel/rootfs; then
info "warewulf: mkfs: formatting /dev/disk/by-partlabel/rootfs"
mkfs --type=ext4 /dev/disk/by-partlabel/rootfs || die "warewulf: mkfs: failed to format /dev/disk/by-partlabel/rootfs"
else
info "warewulf: mkfs: skipping /dev/disk/by-partlabel/rootfs"
continue
fi
if true || ! already_formatted /dev/disk/by-partlabel/scratch; then
info "warewulf: mkfs: formatting /dev/disk/by-partlabel/scratch"
mkfs --type=ext4 /dev/disk/by-partlabel/scratch || die "warewulf: mkfs: failed to format /dev/disk/by-partlabel/scratch"
else
info "warewulf: mkfs: skipping /dev/disk/by-partlabel/scratch"
continue
fi
else
info "warewulf: mkfs not found"
fi
`,
},
"mkfs:20-mkfs.sh.ww (native)": {
args: []string{"--quiet=true", "--render=node1", "mkfs", "warewulf/wwinit.d/20-mkfs.sh.ww"},
nodesConf: `
nodes:
node1:
filesystems:
/dev/disk/by-partlabel/rootfs:
format: ext4
path: /
/dev/disk/by-partlabel/scratch:
format: ext4
path: /scratch
wipe_filesystem: true
/dev/disk/by-partlabel/swap:
format: swap
path: swap`,
output: `#!/bin/sh
PATH=$PATH:/sbin:/usr/sbin:/bin:/usr/bin
if ! command -v info >/dev/null; then
info() {
printf '%s\n' "$*"
}
fi
if ! command -v die >/dev/null; then
die() {
printf '%s\n' "$*" >&2
exit 1
}
fi
already_formatted() {
if ! command -v wipefs >/dev/null ; then
info "warewulf: wipefs not found, cannot check if device is already formatted"
return 0
fi
if wipefs -n "${1}" &>/dev/null; then
info "warewulf: ${1} already formatted"
return 0
fi
return 1
}
if command -v mkfs >/dev/null ; then :
if false || ! already_formatted /dev/disk/by-partlabel/rootfs; then
info "warewulf: mkfs: formatting /dev/disk/by-partlabel/rootfs"
mkfs --type=ext4 /dev/disk/by-partlabel/rootfs || die "warewulf: mkfs: failed to format /dev/disk/by-partlabel/rootfs"
else
info "warewulf: mkfs: skipping /dev/disk/by-partlabel/rootfs"
continue
fi
if true || ! already_formatted /dev/disk/by-partlabel/scratch; then
info "warewulf: mkfs: formatting /dev/disk/by-partlabel/scratch"
mkfs --type=ext4 /dev/disk/by-partlabel/scratch || die "warewulf: mkfs: failed to format /dev/disk/by-partlabel/scratch"
else
info "warewulf: mkfs: skipping /dev/disk/by-partlabel/scratch"
continue
fi
else
info "warewulf: mkfs not found"
fi
`,
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll()
env.ImportFile("var/lib/warewulf/overlays/mkfs/rootfs/warewulf/wwinit.d/20-mkfs.sh.ww", "../rootfs/warewulf/wwinit.d/20-mkfs.sh.ww")
env.WriteFile("etc/warewulf/nodes.conf", tt.nodesConf)
cmd := show.GetCommand()
cmd.SetArgs(tt.args)
stdout := bytes.NewBufferString("")
stderr := bytes.NewBufferString("")
logbuf := bytes.NewBufferString("")
cmd.SetOut(stdout)
cmd.SetErr(stderr)
wwlog.SetLogWriter(logbuf)
err := cmd.Execute()
assert.NoError(t, err)
assert.Empty(t, stdout.String())
assert.Empty(t, stderr.String())
assert.Equal(t, tt.output, logbuf.String())
})
}
}

View File

@@ -0,0 +1,69 @@
#!/bin/sh
PATH=$PATH:/sbin:/usr/sbin:/bin:/usr/bin
if ! command -v info >/dev/null; then
info() {
printf '%s\n' "$*"
}
fi
if ! command -v die >/dev/null; then
die() {
printf '%s\n' "$*" >&2
exit 1
}
fi
already_formatted() {
if ! command -v wipefs >/dev/null ; then
info "warewulf: wipefs not found, cannot check if device is already formatted"
return 0
fi
if wipefs -n "${1}" &>/dev/null; then
info "warewulf: ${1} already formatted"
return 0
fi
return 1
}
if command -v mkfs >/dev/null ; then :
{{- $filesystems := list }}
{{- if .FileSystems }}
{{- range $fs := .ThisNode.FileSystemList }}
{{- if ne $fs.Format "swap" }}
{{- $fsDict := dict "device" $fs.Id "overwrite" $fs.WipeFileSystem }}
{{- if $fs.Format }}
{{- $_ := set $fsDict "type" $fs.Format }}
{{- end }}
{{- if $fs.Options }}
{{- $_ := set $fsDict "options" ($fs.Options | join " ") }}
{{- end }}
{{- if $fs.Label }}
{{- $_ := set $fsDict "label" $fs.Label }}
{{- end }}
{{- if $fs.Uuid }}
{{- $_ := set $fsDict "uuid" $fs.Uuid }}
{{- end }}
{{- $filesystems = append $filesystems $fsDict }}
{{- end }}
{{- end }}
{{- else }}
{{- $filesystems = index .Resources "mkfs" }}
{{- end }}
{{- range $i, $fs := $filesystems }}
{{- if and $fs.type $fs.device }}
if {{if $fs.overwrite}}true{{else}}false{{end}} || ! already_formatted {{ $fs.device }}; then
info "warewulf: mkfs: formatting {{ $fs.device }}"
mkfs {{ not (empty $fs.type) | ternary (print "--type=" $fs.type) "" }} {{ not (empty $fs.label) | ternary (print "-L " $fs.label) "" }} {{ not (empty $fs.uuid) | ternary (print "-U " $fs.uuid) "" }} {{ default "" $fs.options }} {{ $fs.device }} {{ default "" $fs.size }} || die "warewulf: mkfs: failed to format {{ $fs.device }}"
else
info "warewulf: mkfs: skipping {{ $fs.device }}"
continue
fi
{{- end }}
{{- end }}
else
info "warewulf: mkfs not found"
fi

View File

@@ -0,0 +1,314 @@
package mkswap
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"github.com/warewulf/warewulf/internal/app/wwctl/overlay/show"
"github.com/warewulf/warewulf/internal/pkg/testenv"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func Test_mkswapOverlay(t *testing.T) {
tests := map[string]struct {
args []string
nodesConf string
output string
}{
"mkswap:20-mkswap.sh.ww (empty)": {
args: []string{"--quiet=true", "--render=node1", "mkswap", "warewulf/wwinit.d/20-mkswap.sh.ww"},
nodesConf: `
nodes:
node1: {}`,
output: `#!/bin/sh
PATH=$PATH:/sbin:/usr/sbin:/bin:/usr/bin
if ! command -v info >/dev/null; then
info() {
printf '%s\n' "$*"
}
fi
if ! command -v die >/dev/null; then
die() {
printf '%s\n' "$*" >&2
exit 1
}
fi
already_formatted() {
if ! command -v wipefs >/dev/null ; then
info "warewulf: wipefs not found, cannot check if device is already formatted"
return 0
fi
if wipefs -n "${1}" &>/dev/null; then
info "warewulf: ${1} already formatted"
return 0
fi
return 1
}
if command -v mkswap >/dev/null; then :
else
info "warewulf: mkswap not found"
fi
`,
},
"mkswap:20-mkswap.sh.ww (resource)": {
args: []string{"--quiet=true", "--render=node1", "mkswap", "warewulf/wwinit.d/20-mkswap.sh.ww"},
nodesConf: `
nodes:
node1:
resources:
mkswap:
- device: /dev/disk/by-partlabel/swap`,
output: `#!/bin/sh
PATH=$PATH:/sbin:/usr/sbin:/bin:/usr/bin
if ! command -v info >/dev/null; then
info() {
printf '%s\n' "$*"
}
fi
if ! command -v die >/dev/null; then
die() {
printf '%s\n' "$*" >&2
exit 1
}
fi
already_formatted() {
if ! command -v wipefs >/dev/null ; then
info "warewulf: wipefs not found, cannot check if device is already formatted"
return 0
fi
if wipefs -n "${1}" &>/dev/null; then
info "warewulf: ${1} already formatted"
return 0
fi
return 1
}
if command -v mkswap >/dev/null; then :
if false || ! already_formatted /dev/disk/by-partlabel/swap; then
info "warewulf: mkswap: formatting /dev/disk/by-partlabel/swap"
mkswap /dev/disk/by-partlabel/swap || die "warewulf: mkswap: failed to format /dev/disk/by-partlabel/swap"
else
info "warewulf: mkswap: skipping /dev/disk/by-partlabel/swap"
continue
fi
else
info "warewulf: mkswap not found"
fi
`,
},
"mkswap:20-mkswap.sh.ww (resource overwrite)": {
args: []string{"--quiet=true", "--render=node1", "mkswap", "warewulf/wwinit.d/20-mkswap.sh.ww"},
nodesConf: `
nodes:
node1:
resources:
mkswap:
- device: /dev/disk/by-partlabel/swap
overwrite: true`,
output: `#!/bin/sh
PATH=$PATH:/sbin:/usr/sbin:/bin:/usr/bin
if ! command -v info >/dev/null; then
info() {
printf '%s\n' "$*"
}
fi
if ! command -v die >/dev/null; then
die() {
printf '%s\n' "$*" >&2
exit 1
}
fi
already_formatted() {
if ! command -v wipefs >/dev/null ; then
info "warewulf: wipefs not found, cannot check if device is already formatted"
return 0
fi
if wipefs -n "${1}" &>/dev/null; then
info "warewulf: ${1} already formatted"
return 0
fi
return 1
}
if command -v mkswap >/dev/null; then :
if true || ! already_formatted /dev/disk/by-partlabel/swap; then
info "warewulf: mkswap: formatting /dev/disk/by-partlabel/swap"
mkswap /dev/disk/by-partlabel/swap || die "warewulf: mkswap: failed to format /dev/disk/by-partlabel/swap"
else
info "warewulf: mkswap: skipping /dev/disk/by-partlabel/swap"
continue
fi
else
info "warewulf: mkswap not found"
fi
`,
},
"mkswap:20-mkswap.sh.ww (native)": {
args: []string{"--quiet=true", "--render=node1", "mkswap", "warewulf/wwinit.d/20-mkswap.sh.ww"},
nodesConf: `
nodes:
node1:
filesystems:
/dev/disk/by-partlabel/rootfs:
format: ext4
path: /
/dev/disk/by-partlabel/scratch:
format: ext4
path: /scratch
wipe_filesystem: true
/dev/disk/by-partlabel/swap:
format: swap
path: swap`,
output: `#!/bin/sh
PATH=$PATH:/sbin:/usr/sbin:/bin:/usr/bin
if ! command -v info >/dev/null; then
info() {
printf '%s\n' "$*"
}
fi
if ! command -v die >/dev/null; then
die() {
printf '%s\n' "$*" >&2
exit 1
}
fi
already_formatted() {
if ! command -v wipefs >/dev/null ; then
info "warewulf: wipefs not found, cannot check if device is already formatted"
return 0
fi
if wipefs -n "${1}" &>/dev/null; then
info "warewulf: ${1} already formatted"
return 0
fi
return 1
}
if command -v mkswap >/dev/null; then :
if false || ! already_formatted /dev/disk/by-partlabel/swap; then
info "warewulf: mkswap: formatting /dev/disk/by-partlabel/swap"
mkswap /dev/disk/by-partlabel/swap || die "warewulf: mkswap: failed to format /dev/disk/by-partlabel/swap"
else
info "warewulf: mkswap: skipping /dev/disk/by-partlabel/swap"
continue
fi
else
info "warewulf: mkswap not found"
fi
`,
},
"mkswap:20-mkswap.sh.ww (native overwrite)": {
args: []string{"--quiet=true", "--render=node1", "mkswap", "warewulf/wwinit.d/20-mkswap.sh.ww"},
nodesConf: `
nodes:
node1:
filesystems:
/dev/disk/by-partlabel/rootfs:
format: ext4
path: /
/dev/disk/by-partlabel/scratch:
format: ext4
path: /scratch
wipe_filesystem: true
/dev/disk/by-partlabel/swap:
format: swap
path: swap
wipe_filesystem: true`,
output: `#!/bin/sh
PATH=$PATH:/sbin:/usr/sbin:/bin:/usr/bin
if ! command -v info >/dev/null; then
info() {
printf '%s\n' "$*"
}
fi
if ! command -v die >/dev/null; then
die() {
printf '%s\n' "$*" >&2
exit 1
}
fi
already_formatted() {
if ! command -v wipefs >/dev/null ; then
info "warewulf: wipefs not found, cannot check if device is already formatted"
return 0
fi
if wipefs -n "${1}" &>/dev/null; then
info "warewulf: ${1} already formatted"
return 0
fi
return 1
}
if command -v mkswap >/dev/null; then :
if true || ! already_formatted /dev/disk/by-partlabel/swap; then
info "warewulf: mkswap: formatting /dev/disk/by-partlabel/swap"
mkswap /dev/disk/by-partlabel/swap || die "warewulf: mkswap: failed to format /dev/disk/by-partlabel/swap"
else
info "warewulf: mkswap: skipping /dev/disk/by-partlabel/swap"
continue
fi
else
info "warewulf: mkswap not found"
fi
`,
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll()
env.ImportFile("var/lib/warewulf/overlays/mkswap/rootfs/warewulf/wwinit.d/20-mkswap.sh.ww", "../rootfs/warewulf/wwinit.d/20-mkswap.sh.ww")
env.WriteFile("etc/warewulf/nodes.conf", tt.nodesConf)
cmd := show.GetCommand()
cmd.SetArgs(tt.args)
stdout := bytes.NewBufferString("")
stderr := bytes.NewBufferString("")
logbuf := bytes.NewBufferString("")
cmd.SetOut(stdout)
cmd.SetErr(stderr)
wwlog.SetLogWriter(logbuf)
err := cmd.Execute()
assert.NoError(t, err)
assert.Empty(t, stdout.String())
assert.Empty(t, stderr.String())
assert.Equal(t, tt.output, logbuf.String())
})
}
}

View File

@@ -0,0 +1,69 @@
#!/bin/sh
PATH=$PATH:/sbin:/usr/sbin:/bin:/usr/bin
if ! command -v info >/dev/null; then
info() {
printf '%s\n' "$*"
}
fi
if ! command -v die >/dev/null; then
die() {
printf '%s\n' "$*" >&2
exit 1
}
fi
already_formatted() {
if ! command -v wipefs >/dev/null ; then
info "warewulf: wipefs not found, cannot check if device is already formatted"
return 0
fi
if wipefs -n "${1}" &>/dev/null; then
info "warewulf: ${1} already formatted"
return 0
fi
return 1
}
if command -v mkswap >/dev/null; then :
{{- $filesystems := list }}
{{- if .FileSystems }}
{{- range $fs := .ThisNode.FileSystemList }}
{{- if eq $fs.Format "swap" }}
{{- $fsDict := dict "device" $fs.Id "overwrite" $fs.WipeFileSystem }}
{{- if $fs.Format }}
{{- $_ := set $fsDict "type" $fs.Format }}
{{- end }}
{{- if $fs.Options }}
{{- $_ := set $fsDict "options" ($fs.Options | join " ") }}
{{- end }}
{{- if $fs.Label }}
{{- $_ := set $fsDict "label" $fs.Label }}
{{- end }}
{{- if $fs.Uuid }}
{{- $_ := set $fsDict "uuid" $fs.Uuid }}
{{- end }}
{{- $filesystems = append $filesystems $fsDict }}
{{- end }}
{{- end }}
{{- else }}
{{- $filesystems = index .Resources "mkswap" }}
{{- end }}
{{- range $fs := $filesystems }}
{{- if $fs.device }}
if {{if $fs.overwrite}}true{{else}}false{{end}} || ! already_formatted {{ $fs.device }}; then
info "warewulf: mkswap: formatting {{ $fs.device }}"
mkswap {{ not (empty $fs.label) | ternary (print "--label=" $fs.label) "" }} {{ not (empty $fs.uuid) | ternary (print "--uuid=" $fs.uuid) "" }} {{ $fs.device }} {{ default "" $fs.size }} || die "warewulf: mkswap: failed to format {{ $fs.device }}"
else
info "warewulf: mkswap: skipping {{ $fs.device }}"
continue
fi
{{- end }}
{{- end }}
else
info "warewulf: mkswap not found"
fi

View File

@@ -0,0 +1,286 @@
package sfdisk
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"github.com/warewulf/warewulf/internal/app/wwctl/overlay/show"
"github.com/warewulf/warewulf/internal/pkg/testenv"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func Test_sfdiskOverlay(t *testing.T) {
tests := map[string]struct {
args []string
nodesConf string
output string
}{
"sfdisk:disks.ww (empty)": {
args: []string{"--quiet=false", "--render=node1", "sfdisk", "warewulf/sfdisk/disks.ww"},
nodesConf: `
nodes:
node1: {}`,
output: `backupFile: true
writeFile: true
Filename: warewulf/sfdisk/disks
`,
},
"sfdisk:disks.ww (resource)": {
args: []string{"--quiet=false", "--render=node1", "sfdisk", "warewulf/sfdisk/disks.ww"},
nodesConf: `
nodes:
node1:
resources:
sfdisk:
- device: /dev/sda
first-lba: 34
label: gpt
last-lba: 20971486
partitions:
- device: /dev/sda1
name: sfdisk-rootfs
size: 4194304
start: 2048
type: linux
- device: /dev/sda2
name: sfdisk-scratch
size: 1048576
start: 4196352
type: linux
- device: /dev/sda3
name: sfdisk-swap
size: 2097152
start: 5244928
type: linux
sector-size: 512`,
output: `backupFile: true
writeFile: true
Filename: device-0
label: gpt
first-lba: 34
last-lba: 20971486
sector-size: 512
/dev/sda1 : start=2048 size=4194304 name=sfdisk-rootfs type=linux
/dev/sda2 : start=4196352 size=1048576 name=sfdisk-scratch type=linux
/dev/sda3 : start=5244928 size=2097152 name=sfdisk-swap type=linux
`,
},
"sfdisk:disks.ww (native)": {
args: []string{"--quiet=false", "--render=node1", "sfdisk", "warewulf/sfdisk/disks.ww"},
nodesConf: `
nodes:
node1:
disks:
/dev/sda:
wipe_table: true
partitions:
rootfs:
number: "1"
start_mib: "2"
size_mib: "4096"
should_exist: true
type_guid: linux
scratch:
number: "2"
start_mib: "4098"
size_mib: "10240"
should_exist: true
type_guid: linux
swap:
number: "3"
start_mib: "5122"
size_mib: "2048"
should_exist: true
type_guid: swap`,
output: `backupFile: true
writeFile: true
Filename: device-0
start=2MiB size=4096MiB name=rootfs type=linux
start=4098MiB size=10240MiB name=scratch type=linux
start=5122MiB size=2048MiB name=swap type=swap
`,
},
"sfdisk:10-sfdisk.sh.ww (empty)": {
args: []string{"--quiet", "--render=node1", "sfdisk", "warewulf/wwinit.d/10-sfdisk.sh.ww"},
nodesConf: `
nodes:
node1: {}`,
output: `#!/bin/sh
PATH=$PATH:/sbin:/usr/sbin:/bin:/usr/bin
if ! command -v info >/dev/null; then
info() {
printf '%s\n' "$*"
}
fi
if ! command -v die >/dev/null; then
die() {
printf '%s\n' "$*" >&2
exit 1
}
fi
if ! command -v sfdisk >/dev/null ; then
info "warewulf: sfdisk not found, skipping partitioning"
else :
fi
`,
},
"sfdisk:10-sfdisk.sh.ww (resource)": {
args: []string{"--quiet", "--render=node1", "sfdisk", "warewulf/wwinit.d/10-sfdisk.sh.ww"},
nodesConf: `
nodes:
node1:
resources:
sfdisk:
- device: /dev/sda
first-lba: 34
label: gpt
last-lba: 20971486
partitions:
- device: /dev/sda1
name: sfdisk-rootfs
size: 4194304
start: 2048
type: linux
- device: /dev/sda2
name: sfdisk-scratch
size: 1048576
start: 4196352
type: linux
- device: /dev/sda3
name: sfdisk-swap
size: 2097152
start: 5244928
type: linux
sector-size: 512`,
output: `#!/bin/sh
PATH=$PATH:/sbin:/usr/sbin:/bin:/usr/bin
if ! command -v info >/dev/null; then
info() {
printf '%s\n' "$*"
}
fi
if ! command -v die >/dev/null; then
die() {
printf '%s\n' "$*" >&2
exit 1
}
fi
if ! command -v sfdisk >/dev/null ; then
info "warewulf: sfdisk not found, skipping partitioning"
else :
info "warewulf: sfdisk: partitioning /dev/sda"
sfdisk --wipe "auto" "/dev/sda" < "${PREFIX}/warewulf/sfdisk/device-0" || die "warewulf: sfdisk: failed to partition /dev/sda"
if command -v blockdev >/dev/null ; then
info "warewulf: blockdev: re-reading partition table"
blockdev --rereadpt /dev/sda
fi
if command -v udevadm >/dev/null ; then
info "warewulf: udevadm: triggering udev events for block devices"
udevadm trigger --subsystem-match=block --action=add
udevadm settle
fi
fi
`,
},
"sfdisk:10-sfdisk.sh.ww (native)": {
args: []string{"--quiet", "--render=node1", "sfdisk", "warewulf/wwinit.d/10-sfdisk.sh.ww"},
nodesConf: `
nodes:
node1:
disks:
/dev/sda:
partitions:
rootfs:
number: "1"
size_mib: "4096"
should_exist: true
scratch:
number: "2"
size_mib: "10240"
should_exist: true
swap:
number: "3"
size_mib: "2048"
should_exist: true
/dev/sdb:
wipe_table: true`,
output: `#!/bin/sh
PATH=$PATH:/sbin:/usr/sbin:/bin:/usr/bin
if ! command -v info >/dev/null; then
info() {
printf '%s\n' "$*"
}
fi
if ! command -v die >/dev/null; then
die() {
printf '%s\n' "$*" >&2
exit 1
}
fi
if ! command -v sfdisk >/dev/null ; then
info "warewulf: sfdisk not found, skipping partitioning"
else :
info "warewulf: sfdisk: partitioning /dev/sda"
sfdisk --wipe "auto" "/dev/sda" < "${PREFIX}/warewulf/sfdisk/device-0" || die "warewulf: sfdisk: failed to partition /dev/sda"
if command -v blockdev >/dev/null ; then
info "warewulf: blockdev: re-reading partition table"
blockdev --rereadpt /dev/sda
fi
info "warewulf: sfdisk: partitioning /dev/sdb"
sfdisk --wipe "always" "/dev/sdb" < "${PREFIX}/warewulf/sfdisk/device-1" || die "warewulf: sfdisk: failed to partition /dev/sdb"
if command -v blockdev >/dev/null ; then
info "warewulf: blockdev: re-reading partition table"
blockdev --rereadpt /dev/sdb
fi
if command -v udevadm >/dev/null ; then
info "warewulf: udevadm: triggering udev events for block devices"
udevadm trigger --subsystem-match=block --action=add
udevadm settle
fi
fi
`,
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll()
env.ImportFile("var/lib/warewulf/overlays/sfdisk/rootfs/warewulf/sfdisk/disks.ww", "../rootfs/warewulf/sfdisk/disks.ww")
env.ImportFile("var/lib/warewulf/overlays/sfdisk/rootfs/warewulf/wwinit.d/10-sfdisk.sh.ww", "../rootfs/warewulf/wwinit.d/10-sfdisk.sh.ww")
env.WriteFile("etc/warewulf/nodes.conf", tt.nodesConf)
cmd := show.GetCommand()
cmd.SetArgs(tt.args)
stdout := bytes.NewBufferString("")
stderr := bytes.NewBufferString("")
logbuf := bytes.NewBufferString("")
cmd.SetOut(stdout)
cmd.SetErr(stderr)
wwlog.SetLogWriter(logbuf)
err := cmd.Execute()
assert.NoError(t, err)
assert.Empty(t, stdout.String())
assert.Empty(t, stderr.String())
assert.Equal(t, tt.output, logbuf.String())
})
}
}

View File

@@ -0,0 +1,52 @@
{{- $disks := list }}
{{- if .Disks }}
{{- $diskNames := list }}
{{- range $disk := .ThisNode.DiskList }}
{{- $partitions := list }}
{{- range $partition := $disk.PartitionList }}
{{- if $partition.ShouldExist }}
{{- $partitionDict := dict "name" $partition.Id }}
{{- if $partition.StartMiB }}
{{- $_ := set $partitionDict "start" (printf "%s%s" $partition.StartMiB "MiB") }}
{{- end }}
{{- if $partition.SizeMiB }}
{{- $_ := set $partitionDict "size" (printf "%s%s" $partition.SizeMiB "MiB") }}
{{- end }}
{{- if $partition.TypeGuid }}
{{- $_ := set $partitionDict "type" $partition.TypeGuid }}
{{- end }}
{{- if $partition.Guid }}
{{- $_ := set $partitionDict "uuid" $partition.Guid }}
{{- end }}
{{- $partitions = append $partitions $partitionDict }}
{{- end }}
{{- end }}
{{- $disks = append $disks (dict "partitions" $partitions) }}
{{- end }}
{{- else }}
{{- $disks = index .Resources "sfdisk" }}
{{- end }}
{{- range $i, $device := $disks }}
{{ file (print "device-" $i) }}
{{- range $header := list "unit" "label" "label-id" "first-lba" "last-lba" "table-length" "grain" "sector-size" }}
{{- if index $device $header }}
{{ $header }}: {{ index $device $header }}
{{- end }}
{{- end }}
{{- range $partition := $device.partitions }}
{{- $fields := list }}
{{- range $field := list "start" "size" "attrs" "uuid" "name" "type" }}
{{- if index $partition $field }}
{{- $fields = append $fields (print $field "=" (index $partition $field)) }}
{{- end }}
{{- if $partition.bootable }}
{{- $fields = append $fields "bootable" }}
{{- end }}
{{- end }}
{{- if $partition.device }}
{{ $partition.device }} : {{ $fields | join " " }}
{{- else }}
{{ $fields | join " " }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,51 @@
#!/bin/sh
PATH=$PATH:/sbin:/usr/sbin:/bin:/usr/bin
if ! command -v info >/dev/null; then
info() {
printf '%s\n' "$*"
}
fi
if ! command -v die >/dev/null; then
die() {
printf '%s\n' "$*" >&2
exit 1
}
fi
if ! command -v sfdisk >/dev/null ; then
info "warewulf: sfdisk not found, skipping partitioning"
else :
{{- $disks := list }}
{{- if .Disks }}
{{- range $disk := .ThisNode.DiskList }}
{{- $diskDict := dict "device" $disk.Id }}
{{- if $disk.WipeTable }}
{{- $_ := set $diskDict "wipe" "always" }}
{{- end }}
{{- $disks = append $disks $diskDict }}
{{- end }}
{{- else }}
{{- $disks = index .Resources "sfdisk" }}
{{- end }}
{{- range $i, $device := $disks }}
{{- if $device.device }}
info "warewulf: sfdisk: partitioning {{ $device.device }}"
sfdisk --wipe "{{ default "auto" $device.wipe }}" "{{ $device.device }}" < "${PREFIX}/warewulf/sfdisk/device-{{ $i }}" || die "warewulf: sfdisk: failed to partition {{ $device.device }}"
if command -v blockdev >/dev/null ; then
info "warewulf: blockdev: re-reading partition table"
blockdev --rereadpt {{ $device.device }}
fi
{{- end }}
{{- end }}
{{- if $disks }}
if command -v udevadm >/dev/null ; then
info "warewulf: udevadm: triggering udev events for block devices"
udevadm trigger --subsystem-match=block --action=add
udevadm settle
fi
{{- end }}
fi

View File

@@ -0,0 +1,118 @@
package systemd_mount
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"github.com/warewulf/warewulf/internal/app/wwctl/overlay/show"
"github.com/warewulf/warewulf/internal/pkg/testenv"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func Test_systemdMountOverlay(t *testing.T) {
tests := map[string]struct {
args []string
nodesConf string
output string
}{
"systemd.mount:disk.mount.ww": {
args: []string{"--quiet=false", "--render=node1", "systemd.mount", "etc/systemd/system/disk.mount.ww"},
nodesConf: `
nodes:
node1:
filesystems:
/dev/disk/by-partlabel/rootfs:
format: ext4
path: /
/dev/disk/by-partlabel/scratch:
format: ext4
path: /scratch
wipe_filesystem: true
/dev/disk/by-partlabel/swap:
format: swap
path: swap`,
output: `backupFile: true
writeFile: true
Filename: -.mount
[Unit]
Before=local-fs.target
[Mount]
Where=/
What=/dev/disk/by-partlabel/rootfs
Type=ext4
[Install]
RequiredBy=local-fs.target
backupFile: true
writeFile: true
Filename: scratch.mount
[Unit]
Before=local-fs.target
[Mount]
Where=/scratch
What=/dev/disk/by-partlabel/scratch
Type=ext4
[Install]
RequiredBy=local-fs.target
`,
},
"systemd.mount:local-fs.target.wants/disk.mount.ww": {
args: []string{"--quiet=false", "--render=node1", "systemd.mount", "etc/systemd/system/local-fs.target.wants/disk.mount.ww"},
nodesConf: `
nodes:
node1:
filesystems:
/dev/disk/by-partlabel/rootfs:
format: ext4
path: /
/dev/disk/by-partlabel/scratch:
format: ext4
path: /scratch
wipe_filesystem: true
/dev/disk/by-partlabel/swap:
format: swap
path: swap`,
output: `backupFile: true
writeFile: true
Filename: -.mount
{{ /* softlink "/etc/systemd/system/-.mount" */ }}
backupFile: true
writeFile: true
Filename: scratch.mount
{{ /* softlink "/etc/systemd/system/scratch.mount" */ }}
`,
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll()
env.ImportFile("var/lib/warewulf/overlays/systemd.mount/rootfs/etc/systemd/system/disk.mount.ww", "../rootfs/etc/systemd/system/disk.mount.ww")
env.ImportFile("var/lib/warewulf/overlays/systemd.mount/rootfs/etc/systemd/system/local-fs.target.wants/disk.mount.ww", "../rootfs/etc/systemd/system/local-fs.target.wants/disk.mount.ww")
env.WriteFile("etc/warewulf/nodes.conf", tt.nodesConf)
cmd := show.GetCommand()
cmd.SetArgs(tt.args)
stdout := bytes.NewBufferString("")
stderr := bytes.NewBufferString("")
logbuf := bytes.NewBufferString("")
cmd.SetOut(stdout)
cmd.SetErr(stderr)
wwlog.SetLogWriter(logbuf)
err := cmd.Execute()
assert.NoError(t, err)
assert.Empty(t, stdout.String())
assert.Empty(t, stderr.String())
assert.Equal(t, tt.output, logbuf.String())
})
}
}

View File

@@ -0,0 +1,24 @@
{{- $abort := true }}
{{- range $fs := .ThisNode.FileSystemList }}
{{- if and $fs.Path (ne $fs.Format "swap") }}
{{- $abort = false }}
{{ file (print ($fs.Path | SystemdEscapePath) ".mount") }}
[Unit]
Before=local-fs.target
[Mount]
Where={{ $fs.Path }}
What={{ $fs.Id }}
Type={{ $fs.Format }}
{{- if $fs.MountOptions }}
Options={{ $fs.MountOptions }}
{{- end }}
[Install]
RequiredBy=local-fs.target
{{- end }}
{{- end }}
{{- if $abort }}
{{ abort }}
{{- end }}

View File

@@ -0,0 +1,11 @@
{{- $abort := true }}
{{- range $fs := .ThisNode.FileSystemList }}
{{- if and $fs.Path (ne $fs.Format "swap") }}
{{- $abort = false }}
{{ file (print ($fs.Path | SystemdEscapePath) ".mount") }}
{{ softlink (print "/etc/systemd/system/" ($fs.Path | SystemdEscapePath) ".mount") }}
{{- end }}
{{- end }}
{{- if $abort }}
{{ abort }}
{{- end }}

View File

@@ -0,0 +1,98 @@
package systemd_swap
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"github.com/warewulf/warewulf/internal/app/wwctl/overlay/show"
"github.com/warewulf/warewulf/internal/pkg/testenv"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func Test_systemdSwapOverlay(t *testing.T) {
tests := map[string]struct {
args []string
nodesConf string
output string
}{
"systemd.swap:disk.swap.ww": {
args: []string{"--quiet=false", "--render=node1", "systemd.swap", "etc/systemd/system/disk.swap.ww"},
nodesConf: `
nodes:
node1:
filesystems:
/dev/disk/by-partlabel/rootfs:
format: ext4
path: /
/dev/disk/by-partlabel/scratch:
format: ext4
path: /scratch
wipe_filesystem: true
/dev/disk/by-partlabel/swap:
format: swap
path: swap`,
output: `backupFile: true
writeFile: true
Filename: dev-disk-by\x2dpartlabel-swap.swap
[Unit]
Before=swap.target
[Swap]
What=/dev/disk/by-partlabel/swap
[Install]
RequiredBy=swap.target
`,
},
"systemd.swap:local-fs.target.wants/disk.swap.ww": {
args: []string{"--quiet=false", "--render=node1", "systemd.swap", "etc/systemd/system/local-fs.target.wants/disk.swap.ww"},
nodesConf: `
nodes:
node1:
filesystems:
/dev/disk/by-partlabel/rootfs:
format: ext4
path: /
/dev/disk/by-partlabel/scratch:
format: ext4
path: /scratch
wipe_filesystem: true
/dev/disk/by-partlabel/swap:
format: swap
path: swap`,
output: `backupFile: true
writeFile: true
Filename: dev-disk-by\x2dpartlabel-swap.swap
{{ /* softlink "/etc/systemd/system/dev-disk-by\x2dpartlabel-swap.swap" */ }}
`,
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll()
env.ImportFile("var/lib/warewulf/overlays/systemd.swap/rootfs/etc/systemd/system/disk.swap.ww", "../rootfs/etc/systemd/system/disk.swap.ww")
env.ImportFile("var/lib/warewulf/overlays/systemd.swap/rootfs/etc/systemd/system/local-fs.target.wants/disk.swap.ww", "../rootfs/etc/systemd/system/local-fs.target.wants/disk.swap.ww")
env.WriteFile("etc/warewulf/nodes.conf", tt.nodesConf)
cmd := show.GetCommand()
cmd.SetArgs(tt.args)
stdout := bytes.NewBufferString("")
stderr := bytes.NewBufferString("")
logbuf := bytes.NewBufferString("")
cmd.SetOut(stdout)
cmd.SetErr(stderr)
wwlog.SetLogWriter(logbuf)
err := cmd.Execute()
assert.NoError(t, err)
assert.Empty(t, stdout.String())
assert.Empty(t, stderr.String())
assert.Equal(t, tt.output, logbuf.String())
})
}
}

View File

@@ -0,0 +1,19 @@
{{- $abort := true }}
{{- range $fs := .ThisNode.FileSystemList }}
{{- if eq $fs.Format "swap" }}
{{- $abort = false }}
{{ file (print ($fs.Id | SystemdEscapePath) ".swap") }}
[Unit]
Before=swap.target
[Swap]
What={{ $fs.Id }}
[Install]
RequiredBy=swap.target
{{- end }}
{{- end }}
{{- if $abort }}
{{ abort }}
{{- end }}

View File

@@ -0,0 +1,11 @@
{{- $abort := true }}
{{- range $fs := .ThisNode.FileSystemList }}
{{- if eq $fs.Format "swap" }}
{{- $abort = false }}
{{ file (print ($fs.Id | SystemdEscapePath) ".swap") }}
{{ softlink (print "/etc/systemd/system/" ($fs.Id | SystemdEscapePath) ".swap") }}
{{- end }}
{{- end }}
{{- if $abort }}
{{ abort }}
{{- end }}

View File

@@ -4,7 +4,8 @@ Provisioning disks
As a tech preview, Warewulf provides structures to define disks, partitions, and
file systems. These structures can generate a configuration for `Ignition`_ to
provision partitions and file systems dynamically on cluster nodes.
provision partitions and file systems dynamically on cluster nodes, or with
sfdisk, mkfs, and mkswap during boot.
.. _Ignition: https://coreos.github.io/ignition/

View File

@@ -371,6 +371,105 @@ specified with a ``localtime`` tag.
wwctl profile set default --tagadd="localtime=UTC"
sfdisk
------
The **sfdisk** overlay partitions block devices during wwinit. Configuration may
be provided using native disk and partition configuration or via an ``sfdisk``
resource.
Multiple devices can be partitioned, with each device provided as an item in
``sfdisk`` list.
.. code-block:: yaml
sfdisk:
- device: /dev/sda
label: gpt
partitions:
- device: /dev/sda1
name: sfdisk-rootfs
size: 4194304
- device: /dev/sda2
name: sfdisk-scratch
size: 1048576
- device: /dev/sda3
name: sfdisk-swap
size: 2097152
All headers and named partition fields supported by the ``sfdisk`` input format
are supported in the ``sfdisk`` resource.
If any disk/partition configuration is provided for a node with explicit
arguments to ``wwctl <node|profile> set``, the ``sfdisk`` resource is ignored.
To use the sfdisk overlay, include sfdisk in the Dracut image. Optionally also
include blockdev and/or udevadm, to allow the partition table to be re-scanned.
.. code-block:: shell
wwctl image exec rockylinux-8 -- /usr/bin/dracut --force --no-hostonly --add wwinit --install sfdisk --install blockdev --install udevadm --regenerate-all
For more information, see the :ref:`provision to disk` section.
mkfs
----
The **mkfs** overlay formats block devices during wwinit. Configuration may be
provided using native filesystem fields or via an ``mkfs`` resource.
.. code-block:: yaml
mkfs:
- device: /dev/sda1 # the device to format
type: xfs # what type of file system to create
options: -b 1024 # additional options to pass to mkfs
overwrite: false # whether to overwrite an existing format
size: 0 # defaults to the full device
If any filesystem configuration is provided for a node with explicit arguments
to ``wwctl <node|profile> set``, the ``mkfs`` resource is ignored.
To use the mkfs overlay, include mkfs and any necessary file-system-specific
sub-commands in the Dracut image. Optionally also include wipefs to detect
existing file systems.
.. code-block:: shell
wwctl image exec rockylinux-9 -- /usr/bin/dracut --force --no-hostonly --add wwinit --install mkfs --install mkfs.ext4 --install wipefs --regenerate-all
For more information, see the :ref:`provision to disk` section.
mkswap
------
The **mkswap** overlay formats block devices during wwinit. Configuration may be
provided using native filesystem fields or via a ``mkswap`` resource.
.. code-block:: yaml
mkswap:
- device: /dev/sda2 # the device to format
overwrite: false # whether to overwrite an existing format
label: swap # the label to set for the swap device
If any filesystem configuration is provided for a node with explicit arguments
to ``wwctl <node|profile> set``, the ``mkswap`` resource is ignored.
To use the mkswap overlay, include mkswap in the Dracut image. Optionally also
include wipefs to detect existing file systems.
.. code-block:: shell
wwctl image exec rockylinux-9 -- /usr/bin/dracut --force --no-hostonly --add wwinit --install mkswap --regenerate-all
systemd mounts
--------------
Two overlays, **systemd.mount** and **systemd.swap**, configure mounted and swap
storage based on the configuration of native file system fields. They are often
paired with the ``mkfs`` and ``mkswap`` overlays.
host
----

View File

@@ -95,6 +95,16 @@ referenced symlink.
{{ ImportLink "/etc/localtime" }}
When paired with ``file``, ``ImportLink`` can create multiple symlinks from a
single template.
.. code-block::
{{ file "/tmp/test-link1"}}
{{ softlink "/tmp/test-target1" }}
{{ file "/tmp/test-link2"}}
{{ softlink "/tmp/test-target2" }}
basename
--------
@@ -133,6 +143,9 @@ Causes the processed template file to become a symlink to the referenced target.
{{ printf "%s/%s" "/usr/share/zoneinfo" .Tags.localtime | softlink }}
When paired with ``file``, ``softlink`` can create multiple symlinks from a
single template.
.. _readlink:
readlink

View File

@@ -454,6 +454,52 @@ Ignition must be included in the Dracut image.
The necessary file system may alternatively be prepared out-of-band.
With sfdisk and mkfs
--------------------
Systems that do not have access to Ignition (e.g., Rocky Linux 8) can provision
the root file system using a combination of ``sfdisk`` and ``mkfs``. To use
them, include ``sfdisk`` and ``mkfs`` in your system overlay. The ``sfdisk`` and
``mkfs`` overlays provision disk and file systems during the first stage of a
two-stage boot. This allows the root file system to be provisioned before the
image is loaded.
Configure the ``sfdisk`` and ``mkfs`` overlays using resources:
.. code-block:: shell
wwctl node set wwnode1 \
--diskname /dev/vda --diskwipe \
--partname rootfs --partcreate --partnumber 1 \
--fsname rootfs --fsformat ext4 --fspath /
In order to allow Dracut to provision the disk, partition, and file system, some
additional commands must be included in the Dracut image, depending on which
functionality is used:
- **sfdisk:** writes the partition table
- **blockdev:** used to re-read the partition table after writing
- **udevadm:** used to trigger udev events after writing the partition table
- **mkfs:** formats file systems (may also require file-system-specific commands like mkfs.ext4)
- **mkfs.ext4**, **mkfs.btrfs**, etc: used by mkfs to format specific file systems
- **wipefs:** used to determine if a file system already exists
.. code-block:: shell
wwctl image exec rockylinux-8 -- /usr/bin/dracut --force --no-hostonly \
--add wwinit \
--install sfdisk \
--install blockdev \
--install udevadm \
--install mkfs \
--install mkfs.ext4 \
--install wipefs \
--regenerate-all
Configuring the root device
---------------------------

View File

@@ -190,6 +190,11 @@ getent group %{wwgroup} >/dev/null || groupadd -r %{wwgroup}
%{_datadir}/warewulf/overlays/wwclient/rootfs/*
%{_datadir}/warewulf/overlays/wwinit/rootfs/*
%{_datadir}/warewulf/overlays/localtime/rootfs/*
%{_datadir}/warewulf/overlays/sfdisk/rootfs/*
%{_datadir}/warewulf/overlays/mkfs/rootfs/*
%{_datadir}/warewulf/overlays/mkswap/rootfs/*
%{_datadir}/warewulf/overlays/systemd.mount/rootfs/*
%{_datadir}/warewulf/overlays/systemd.swap/rootfs/*
%{_bindir}/wwctl
%{_prefix}/lib/firewalld/services/warewulf.xml