Refactor ignition support during dracut
- Control root device using the node/profile root field - Add support for `/warewulf/wwinit.d` to run scripts during first stage - Move first-stage ignition support to `/warewulf/wwinit.d/` - Add `wwctl <node|profile> set --parttype` - Add overlay template functions `SystemdEscape` and `SystemdEscapePath` - Support configuring ignition with resources Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
@@ -11,6 +11,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
- Added override.conf for nm-wait-online-initrd.service with dracut.
|
||||
- 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
|
||||
- Support configuring Ignition with resources. #1894
|
||||
- Added `wwctl <node|partition> set --parttype`. #1894
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -258,8 +262,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
- Add man pages and command reference to userdocs. #1488
|
||||
- Document building images from scratch with Apptainer. #1485
|
||||
- Added warewulfd:/overlay-file/{overlay}/{path...}?render={id}
|
||||
- Add `wwct configure rsync` and the template `rsyncd.conf.ww`
|
||||
- Make dracut aware of `root==perisitent` which will install the node image to the parition `rootfs`
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
3
Makefile
3
Makefile
@@ -199,7 +199,8 @@ 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)/wwprescripts
|
||||
chmod 0755 $(DESTDIR)$(DATADIR)/warewulf/overlays/wwinit/rootfs/$(WWCLIENTDIR)/run-init
|
||||
chmod 0755 $(DESTDIR)$(DATADIR)/warewulf/overlays/wwinit/rootfs/$(WWCLIENTDIR)/run-wwinit.d
|
||||
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
|
||||
|
||||
@@ -1,49 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
function setup_disks() {
|
||||
# format and prepare the disk(s)
|
||||
/usr/bin/ignition --root=/sysroot --platform=metal --stage=disks || die "warewulf: failed to partition/format disk"
|
||||
# mount /dev/disk/by-partlabel/root "$NEWROOT" || die "warewulf: failed to mount new root"
|
||||
/usr/bin/ignition --root=/sysroot --platform=metal --stage=mount || die "warewulf: failed to mount disk"
|
||||
|
||||
}
|
||||
|
||||
|
||||
info "warewulf: Running warewulf v4 dracut init ${wwinit_persistent}"
|
||||
archives="image system runtime"
|
||||
if [ ${wwinit_persistent} -ne 1 ] ; then
|
||||
info "warewulf: Mounting tmpfs at $NEWROOT"
|
||||
mount -t tmpfs -o mpol=interleave ${wwinit_tmpfs_size_option} tmpfs "$NEWROOT"
|
||||
else
|
||||
info "warewulf: Using persistent setup ${wwinit_ignition}/ignition.json.ww?render=${wwinit_id}"
|
||||
# get the igintion config and store in /run/igintion.ign, don't use igntion as so we can
|
||||
# construct the download link
|
||||
curl --location --silent --get ${localport} \
|
||||
--retry 60 --retry-delay 1 \
|
||||
"${wwinit_ignition}/ignition.json.ww?render=${wwinit_id}" -o /run/ignition-rootfs.json || die "warewulf: failed to fetch ignition configuration from ${wwinit_ignition}/ignition.json.ww?render=${wwinit_id}"
|
||||
jq '.storage.filesystems |= map(select(.device=="/dev/disk/by-partlabel/rootfs").path="/")' /run/ignition-rootfs.json > /run/ignition.json || die "warewulf: failed to rewrite ignition configuration"
|
||||
setup_disks
|
||||
# if [ -e ${NEWROOT}/warewulf/ww_${wwinit_imagename} ] ; then
|
||||
# info "warewulf: found ${NEWROOT}/warewulf/ww_${wwinit_imagename} running rsync for update"
|
||||
# # we need only the overlays as we update the root
|
||||
# # with rsync when the same image is used
|
||||
# archives="system runtime"
|
||||
# rsync -aux --delete --exclude=/proc/ --exclude=/sys/ --exclude=/dev rsync://${wwinit_ip}/${wwinit_imagename} $NEWROOT || die "warewulf: failed to run rsync"
|
||||
# elif [ -e ${NEWROOT}/warewulf/ww_* ] ; then
|
||||
# info "warewulf: found $(ls ${NEWROOT}/warewulf/ww_*) but image is ${wwinit_imagename}, wiping $NEWROOT"
|
||||
# # we didn't have the wanted image but provisioned with warewulf, wipe the rootfs
|
||||
# /usr/bin/ignition --root=/sysroot --platform=metal --stage=umount || die "warewulf: failed to mount disk"
|
||||
# jq '.storage.filesystems |= map(select(.device=="/dev/disk/by-partlabel/rootfs").wipeFilesystem=true)' /run/ignition.json > /run/ignition.json.mod || die "couldn't reformation ignition configuration"
|
||||
# mv /run/ignition.json.mod /run/ignition.json || die "warewulf: couldn't mv ignition configuration back"
|
||||
# setup_disks
|
||||
# fi
|
||||
fi
|
||||
for stage in $archives ; do
|
||||
info "warewulf: Loading ${stage}"
|
||||
get_stage() {
|
||||
stage="${1}"
|
||||
info "warewulf: loading stage: ${stage}"
|
||||
# Load runtime overlay from a static privledged port.
|
||||
# Others use default settings.
|
||||
localport=""
|
||||
if [[ "${stage}" == "runtime" ]] ; then
|
||||
if [ "${stage}" = "runtime" ]; then
|
||||
localport="--local-port 1-1023"
|
||||
fi
|
||||
(
|
||||
@@ -55,19 +18,29 @@ for stage in $archives ; do
|
||||
--data-urlencode "compress=gz" \
|
||||
"${wwinit_uri}" \
|
||||
| gzip -d \
|
||||
| cpio -im --directory="${NEWROOT}"
|
||||
| cpio -ium --directory="${NEWROOT}"
|
||||
) || die "Unable to load stage: ${stage}"
|
||||
done
|
||||
}
|
||||
|
||||
if [ ${wwinit_persistent} -eq 1 ] ; then
|
||||
# this avoids that ignition runs a second time
|
||||
info "warewulf: removing ignition config from wwinit image"
|
||||
rm -rf ${NEWROOT}/warewulf/ignition.json
|
||||
echo "Container name of persistent install: ${wwinit_imagename}" > ${NEWROOT}/warewulf/ww_${wwinit_imagename}
|
||||
echo "# created from ${wwinit_ignition_uri}/fstab.ww?render=${winit_id}" > ${NEWROOT}/etc/fstab
|
||||
curl --location --silent --get ${localport} \
|
||||
--retry 60 --retry-delay 1 \
|
||||
"${wwinit_ignition}/fstab.ww?render=${wwinit_id}" >> ${NEWROOT}/etc/fstab || die "warewulf: failed to write correct fstab"
|
||||
mkdir /tmp/wwinit
|
||||
(
|
||||
# fetch the system overlay into /tmp/wwinit
|
||||
local NEWROOT=/tmp/wwinit
|
||||
get_stage "system"
|
||||
)
|
||||
if [ -x /tmp/wwinit/warewulf/run-wwinit.d ]; then
|
||||
PREFIX=/tmp/wwinit /tmp/wwinit/warewulf/run-wwinit.d
|
||||
fi
|
||||
info "warewulf: Finished warewulf v4 dracut"
|
||||
|
||||
info "warewulf: mounting ${wwinit_root_device} at ${NEWROOT}"
|
||||
(
|
||||
if [ "${wwinit_root_device}" = "tmpfs" ]; then
|
||||
mount -t tmpfs -o mpol=interleave ${wwinit_tmpfs_size_option} "${wwinit_root_device}" "${NEWROOT}"
|
||||
else
|
||||
mount "${wwinit_root_device}" "${NEWROOT}"
|
||||
fi
|
||||
) || die "warewulf: failed to mount ${wwinit_root_device} at ${NEWROOT}"
|
||||
|
||||
for stage in "image" "system" "runtime"; do
|
||||
get_stage "${stage}"
|
||||
done
|
||||
|
||||
@@ -9,12 +9,12 @@ check() {
|
||||
}
|
||||
|
||||
depends() {
|
||||
echo network ignition
|
||||
echo network
|
||||
return 0
|
||||
}
|
||||
|
||||
install() {
|
||||
inst_multiple cpio curl dmidecode rsync jq
|
||||
inst_multiple cpio curl dmidecode
|
||||
inst_hook cmdline 30 "$moddir/parse-wwinit.sh"
|
||||
inst_hook pre-mount 30 "$moddir/load-wwinit.sh"
|
||||
if dracut_module_included "network-manager" && dracut_module_included "systemd"
|
||||
|
||||
@@ -1,29 +1,42 @@
|
||||
#!/bin/sh
|
||||
# root=wwinit
|
||||
# root=wwinit|wwinit:*
|
||||
|
||||
[ -z "$root" ] && root=$(getarg root=)
|
||||
|
||||
if [ "${root}" = "wwinit" ] || [ "${root}" = "persistent" ] ; then
|
||||
case "${root}" in
|
||||
wwinit|wwinit:*)
|
||||
info "warewulf: root=${root}"
|
||||
export wwinit_uuid=$(dmidecode -s system-uuid)
|
||||
export wwinit_assetkey=$(dmidecode -s chassis-asset-tag)
|
||||
|
||||
export wwinit_uri="$(getarg wwinit.uri)"
|
||||
export wwinit_ignition="$(getarg wwinit.ignition)"
|
||||
export wwinit_ip="$(getarg wwinit.ip)"
|
||||
export wwinit_imagename="$(getarg wwinit.imagename)"
|
||||
export wwinit_id=$(getarg wwinit.id)
|
||||
wwinit_tmpfs_size=$(getarg wwinit.tmpfs.size=)
|
||||
if [ -n "$wwinit_tmpfs_size" ] ; then
|
||||
info "warewulf: wwinit.tmpfs.size=${wwinit_tmpfs_size}"
|
||||
export wwinit_tmpfs_size_option="-o size=${wwinit_tmpfs_size}"
|
||||
fi
|
||||
if [ "${root}" = "persistent" ] ; then
|
||||
export wwinit_persistent="1"; info "warewulf: wwinit_persistent=$wwinit_persistent"
|
||||
fi
|
||||
if [ -n "${wwinit_uri}" ]; then
|
||||
info "warewulf: Found root=${root} and a Warewulf server uri. Will boot from Warewulf."
|
||||
info "warewulf: Found root=${root} and wwinit.uri=${wwinit_uri}. Will boot from Warewulf."
|
||||
rootok=1
|
||||
else
|
||||
die "warewulf: Found root=${root} but no Warewulf server uri. Cannot boot from Warewulf."
|
||||
die "warewulf: Found root=${root} but no wwinit.uri. Cannot boot from Warewulf."
|
||||
fi
|
||||
|
||||
export wwinit_uuid=$(dmidecode -s system-uuid)
|
||||
export wwinit_assetkey=$(dmidecode -s chassis-asset-tag)
|
||||
|
||||
wwinit_tmpfs_size="$(getarg wwinit.tmpfs.size)"
|
||||
if [ -n "$wwinit_tmpfs_size" ]; then
|
||||
export wwinit_tmpfs_size_option="-o size=${wwinit_tmpfs_size}"
|
||||
fi
|
||||
|
||||
case "${root}" in
|
||||
wwinit)
|
||||
export wwinit_root_device="tmpfs"
|
||||
;;
|
||||
wwinit:*)
|
||||
export wwinit_root_device="${root#wwinit:}"
|
||||
;;
|
||||
esac
|
||||
|
||||
case "${wwinit_root_device}" in
|
||||
initramfs|rootfs)
|
||||
info "warewulf: using tmpfs in stead of ${wwinit_root_device}"
|
||||
export wwinit_root_device=tmpfs
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -31,10 +31,7 @@ smbios --type 3 --get-string 8 --set assetkey
|
||||
uri="(http,{{.Ipaddr}}:{{.Port}})/provision/${net_default_mac}?assetkey=${assetkey}"
|
||||
kernel="${uri}&stage=kernel"
|
||||
|
||||
{{- $defentry := "singlestage" }}
|
||||
{{- if eq .Root "persistent"}}{{ $defentry = "dracut" }}{{ end }}
|
||||
{{- if .Tags.GrubMenuEntry }}{{ $defentry = .Tags.GrubMenuEntry }}{{ end }}
|
||||
set default={{ $defentry}}
|
||||
set default={{ or .Tags.GrubMenuEntry "single-stage" }}
|
||||
set timeout=2
|
||||
|
||||
menuentry "Single-stage boot" --id single-stage {
|
||||
@@ -117,13 +114,15 @@ menuentry "Single-stage boot (no compression)" --id single-stage-nocompress {
|
||||
echo "!! Unable to load images."
|
||||
echo "!! Rebooting in 15s..."
|
||||
echo "!!"
|
||||
sleep 15
|
||||
reboot
|
||||
fi
|
||||
|
||||
echo "Booting..."
|
||||
boot
|
||||
}
|
||||
|
||||
menuentry "Two stage boot with dracut {{ if eq .Root "persistent"}}(persistent){{ end }}" --id dracut {
|
||||
menuentry "Two stage boot with dracut" --id dracut {
|
||||
echo "Warewulf Server:"
|
||||
echo "* Ipaddr: {{.Ipaddr}}"
|
||||
echo "* Port: {{.Port}}"
|
||||
@@ -143,18 +142,11 @@ menuentry "Two stage boot with dracut {{ if eq .Root "persistent"}}(persistent){
|
||||
|
||||
wwinit_uri="http://{{.Ipaddr}}:{{.Port}}/provision/${net_default_mac}"
|
||||
net_args="rd.neednet=1 {{range $devname, $netdev := .NetDevs}}{{if and $netdev.Hwaddr $netdev.Device}} ifname={{$netdev.Device}}:{{$netdev.Hwaddr}} {{end}}{{end}}"
|
||||
{{- if eq .Root "persistent"}}
|
||||
ignition_args="wwinit.ignition=http://{{.Ipaddr}}:{{.Port}}/overlay-file/persistent wwinit.imagename={{ .ImageName }} wwinit.id={{ .Id }} wwinit.ip={{ .Ipaddr }}"
|
||||
root="persistent"
|
||||
echo "* Persistent install"
|
||||
{{- else }}
|
||||
root="wwinit"
|
||||
{{- end }}
|
||||
wwinit_args="root=$root wwinit.uri=${wwinit_uri} init=/init"
|
||||
wwinit_args="root=wwinit:{{default "tmpfs" .Root}} wwinit.uri=${wwinit_uri} init=/warewulf/run-init"
|
||||
|
||||
echo
|
||||
echo "Downloading kernel image..."
|
||||
linux $kernel wwid=${net_default_mac} {{.KernelArgs}} $net_args $wwinit_args $ignition_args
|
||||
linux $kernel wwid=${net_default_mac} {{.KernelArgs}} $net_args $wwinit_args
|
||||
if [ $? != 0 ]
|
||||
then
|
||||
echo "!!"
|
||||
|
||||
@@ -88,7 +88,7 @@ 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
|
||||
set dracut_wwinit root=wwinit:{{default "tmpfs" .Root}} wwinit.uri=${baseuri} init=/warewulf/run-init
|
||||
goto boot_two_stage_dracut
|
||||
|
||||
:boot_single_stage
|
||||
|
||||
2
go.mod
2
go.mod
@@ -12,6 +12,7 @@ require (
|
||||
github.com/containers/image/v5 v5.32.2
|
||||
github.com/containers/storage v1.57.1
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
|
||||
github.com/coreos/go-systemd/v22 v22.5.0
|
||||
github.com/coreos/ignition/v2 v2.20.0
|
||||
github.com/coreos/vcontext v0.0.0-20230201181013-d72178a18687
|
||||
github.com/creasty/defaults v1.8.0
|
||||
@@ -58,7 +59,6 @@ require (
|
||||
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
|
||||
github.com/containers/ocicrypt v1.2.0 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||
github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
|
||||
|
||||
@@ -194,3 +194,16 @@ func (config *NodesYaml) FindDiscoverableNode() (Node, string, error) {
|
||||
|
||||
return EmptyNode(), "", ErrNoUnconfigured
|
||||
}
|
||||
|
||||
func (node *Node) SetIds(id string) {
|
||||
node.id = id
|
||||
for diskId, disk := range node.Disks {
|
||||
disk.id = diskId
|
||||
for partitionId, partition := range disk.Partitions {
|
||||
partition.id = partitionId
|
||||
}
|
||||
}
|
||||
for fsId, fs := range node.FileSystems {
|
||||
fs.id = fsId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,7 @@ type NetDev struct {
|
||||
Holds the disks of a node
|
||||
*/
|
||||
type Disk struct {
|
||||
id string `yaml:"-" json:"-"`
|
||||
WipeTable bool `yaml:"wipe_table,omitempty" json:"wipe_table,omitempty" lopt:"diskwipe" comment:"whether or not the partition tables shall be wiped"`
|
||||
Partitions map[string]*Partition `yaml:"partitions,omitempty" json:"partitions,omitempty"`
|
||||
}
|
||||
@@ -101,13 +102,14 @@ partition definition, the label must be uniq so its used as the key in the
|
||||
Partitions map
|
||||
*/
|
||||
type Partition struct {
|
||||
Number string `yaml:"number,omitempty" json:"number,omitempty" lopt:"partnumber" comment:"set the partition number, if not set next free slot is used" type:"uint"`
|
||||
SizeMiB string `yaml:"size_mib,omitempty" json:"size_mib,omitempty" lopt:"partsize" comment:"set the size of the partition, if not set maximal possible size is used" type:"uint"`
|
||||
id string `yaml:"-" json:"-"`
|
||||
Number string `yaml:"number,omitempty" json:"number,omitempty" lopt:"partnumber" comment:"Set the partition number, if not set next free slot is used" type:"uint"`
|
||||
SizeMiB string `yaml:"size_mib,omitempty" json:"size_mib,omitempty" lopt:"partsize" comment:"Set the size of the partition, if not set maximal possible size is used" type:"uint"`
|
||||
StartMiB string `yaml:"start_mib,omitempty" json:"start_mib,omitempty" comment:"the start of the partition" type:"uint"`
|
||||
TypeGuid string `yaml:"type_guid,omitempty" json:"type_guid,omitempty" comment:"Linux filesystem data will be used if empty"`
|
||||
TypeGuid string `yaml:"type_guid,omitempty" json:"type_guid,omitempty" lopt:"parttype" comment:"Set the partition type GUID"`
|
||||
Guid string `yaml:"guid,omitempty" json:"guid,omitempty" comment:"the GPT unique partition GUID"`
|
||||
WipePartitionEntry bool `yaml:"wipe_partition_entry,omitempty" json:"wipe_partition_entry,omitempty" comment:"if true, Ignition will clobber an existing partition if it does not match the config"`
|
||||
ShouldExist bool `yaml:"should_exist,omitempty" json:"should_exist,omitempty" lopt:"partcreate" comment:"create partition if not exist"`
|
||||
ShouldExist bool `yaml:"should_exist,omitempty" json:"should_exist,omitempty" lopt:"partcreate" comment:"Create partition if it does not exist"`
|
||||
Resize bool `yaml:"resize,omitempty" json:"resize,omitempty" comment:"whether or not the existing partition should be resize"`
|
||||
}
|
||||
|
||||
@@ -115,6 +117,7 @@ type Partition struct {
|
||||
Definition of a filesystem. The device is uniq so its used as key
|
||||
*/
|
||||
type FileSystem struct {
|
||||
id string `yaml:"-" json:"-"`
|
||||
Format string `yaml:"format,omitempty" json:"format,omitempty" lopt:"fsformat" comment:"format of the file system"`
|
||||
Path string `yaml:"path,omitempty" json:"path,omitempty" lopt:"fspath" comment:"the mount point of the file system"`
|
||||
WipeFileSystem bool `yaml:"wipe_filesystem,omitempty" json:"wipe_filesystem,omitempty" lopt:"fswipe" comment:"wipe file system at boot"`
|
||||
|
||||
@@ -157,7 +157,7 @@ func (config *NodesYaml) MergeNode(id string) (node Node, fields fieldMap, err e
|
||||
delete(fields, "Profiles")
|
||||
}
|
||||
|
||||
node.id = id
|
||||
node.SetIds(id)
|
||||
node.valid = true
|
||||
node.updatePrimaryNetDev()
|
||||
node.Profile.cleanLists()
|
||||
|
||||
@@ -10,10 +10,97 @@ import (
|
||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
func (node *Node) DiskList() (disks []*Disk) {
|
||||
names := make([]string, 0, len(node.Disks))
|
||||
for name := range node.Disks {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
disk := node.Disks[name]
|
||||
if disk != nil {
|
||||
disks = append(disks, disk)
|
||||
}
|
||||
}
|
||||
return disks
|
||||
}
|
||||
|
||||
func (node *Node) FileSystemList() (fs []*FileSystem) {
|
||||
names := make([]string, 0, len(node.FileSystems))
|
||||
for name := range node.FileSystems {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
fsys := node.FileSystems[name]
|
||||
if fsys != nil {
|
||||
fs = append(fs, fsys)
|
||||
}
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
func (disk *Disk) Id() string {
|
||||
return disk.id
|
||||
}
|
||||
|
||||
func (disk *Disk) PartitionList() (partitions []*Partition) {
|
||||
names := make([]string, 0, len(disk.Partitions))
|
||||
for name := range disk.Partitions {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.SliceStable(names, func(i, j int) bool {
|
||||
pi := disk.Partitions[names[i]]
|
||||
pj := disk.Partitions[names[j]]
|
||||
|
||||
// Try to compare by Number (as int)
|
||||
ni, erri := strconv.Atoi(pi.Number)
|
||||
nj, errj := strconv.Atoi(pj.Number)
|
||||
if erri == nil && errj == nil {
|
||||
if ni != nj {
|
||||
return ni < nj
|
||||
}
|
||||
} else if erri == nil {
|
||||
return true // i has a number, j does not
|
||||
} else if errj == nil {
|
||||
return false // j has a number, i does not
|
||||
}
|
||||
|
||||
// Next, compare by id if set
|
||||
if pi.id != "" && pj.id != "" {
|
||||
if pi.id != pj.id {
|
||||
return pi.id < pj.id
|
||||
}
|
||||
} else if pi.id != "" {
|
||||
return true // i has id, j does not
|
||||
} else if pj.id != "" {
|
||||
return false // j has id, i does not
|
||||
}
|
||||
|
||||
// Fallback: compare by name (map key)
|
||||
return names[i] < names[j]
|
||||
})
|
||||
for _, name := range names {
|
||||
part := disk.Partitions[name]
|
||||
if part != nil {
|
||||
partitions = append(partitions, part)
|
||||
}
|
||||
}
|
||||
return partitions
|
||||
}
|
||||
|
||||
func (partition *Partition) Id() string {
|
||||
return partition.id
|
||||
}
|
||||
|
||||
func (fs *FileSystem) Id() string {
|
||||
return fs.id
|
||||
}
|
||||
|
||||
/*
|
||||
Create a ignition struct class for ignition
|
||||
*/
|
||||
func (node *Node) GetStorage() (stor types_3_4.Storage, rep string, err error) {
|
||||
func (node *Node) GetIgnitionStorage() (stor types_3_4.Storage, rep string, err error) {
|
||||
var fileSystems []types_3_4.Filesystem
|
||||
for fsdevice, fs := range node.FileSystems {
|
||||
var mountOptions []types_3_4.MountOption
|
||||
@@ -141,8 +228,8 @@ type SimpleIgnitionConfig struct {
|
||||
/*
|
||||
Get a simple config which can be marshalled to json
|
||||
*/
|
||||
func (node *Node) GetConfig() (conf SimpleIgnitionConfig, rep string, err error) {
|
||||
conf.Storage, rep, err = node.GetStorage()
|
||||
func (node *Node) GetIgnitionConfig() (conf SimpleIgnitionConfig, rep string, err error) {
|
||||
conf.Storage, rep, err = node.GetIgnitionStorage()
|
||||
conf.Ignition.Version = "3.1.0"
|
||||
return
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"time"
|
||||
|
||||
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
||||
"github.com/warewulf/warewulf/internal/pkg/image"
|
||||
"github.com/warewulf/warewulf/internal/pkg/kernel"
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
)
|
||||
@@ -39,7 +38,6 @@ type TemplateStruct struct {
|
||||
Tftp warewulfconf.TFTPConf
|
||||
Paths warewulfconf.BuildConfig
|
||||
AllNodes []node.Node
|
||||
ImageList []string
|
||||
node.Node
|
||||
// backward compatiblity
|
||||
Container string
|
||||
@@ -50,7 +48,8 @@ type TemplateStruct struct {
|
||||
/*
|
||||
Initialize an TemplateStruct with the given node.NodeInfo
|
||||
*/
|
||||
func InitStruct(overlayName string, nodeData node.Node, allNodes []node.Node) (tstruct TemplateStruct, err error) {
|
||||
func InitStruct(overlayName string, nodeData node.Node, allNodes []node.Node) (TemplateStruct, error) {
|
||||
var tstruct TemplateStruct
|
||||
tstruct.Overlay = overlayName
|
||||
hostname, _ := os.Hostname()
|
||||
tstruct.BuildHost = hostname
|
||||
@@ -77,10 +76,6 @@ func InitStruct(overlayName string, nodeData node.Node, allNodes []node.Node) (t
|
||||
// init some convenience vars
|
||||
tstruct.Id = nodeData.Id()
|
||||
tstruct.Hostname = nodeData.Id()
|
||||
tstruct.ImageList, err = image.ListSources()
|
||||
if err != nil {
|
||||
return tstruct, err
|
||||
}
|
||||
tstruct.Container = nodeData.ImageName
|
||||
tstruct.ContainerName = nodeData.ImageName
|
||||
// Backwards compatibility for templates using "Keys"
|
||||
|
||||
@@ -102,7 +102,7 @@ func templateImageFileInclude(imagename string, filepath string) string {
|
||||
// Don't return an error as we use this function for template evaluation, so
|
||||
// error will turn up there as the return string
|
||||
func createIgnitionJson(node *node.Node) string {
|
||||
conf, rep, err := node.GetConfig()
|
||||
conf, rep, err := node.GetIgnitionConfig()
|
||||
if len(conf.Storage.Disks) == 0 && len(conf.Storage.Filesystems) == 0 {
|
||||
wwlog.Debug("no disks or filesystems present, don't create a json object")
|
||||
return ""
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/coreos/go-systemd/v22/unit"
|
||||
|
||||
"github.com/warewulf/warewulf/internal/pkg/config"
|
||||
"github.com/warewulf/warewulf/internal/pkg/node"
|
||||
@@ -496,12 +497,7 @@ func RenderTemplateFile(fileName string, data TemplateStruct) (
|
||||
"softlink": softlink,
|
||||
"readlink": filepath.EvalSymlinks,
|
||||
"IgnitionJson": func() string {
|
||||
str := createIgnitionJson(data.ThisNode)
|
||||
if str != "" {
|
||||
return str
|
||||
}
|
||||
writeFile = false
|
||||
return ""
|
||||
return createIgnitionJson(data.ThisNode)
|
||||
},
|
||||
"abort": func() string {
|
||||
wwlog.Debug("abort file called in %s", fileName)
|
||||
@@ -514,6 +510,8 @@ func RenderTemplateFile(fileName string, data TemplateStruct) (
|
||||
return ""
|
||||
},
|
||||
"UniqueField": UniqueField,
|
||||
"SystemdEscape": unit.UnitNameEscape,
|
||||
"SystemdEscapePath": unit.UnitNamePathEscape,
|
||||
}
|
||||
|
||||
// Merge sprig.FuncMap with our FuncMap
|
||||
|
||||
@@ -11,66 +11,36 @@ import (
|
||||
)
|
||||
|
||||
func Test_ignitionOverlay(t *testing.T) {
|
||||
env := testenv.New(t)
|
||||
defer env.RemoveAll()
|
||||
env.ImportFile("etc/warewulf/nodes.conf", "nodes.conf")
|
||||
env.ImportFile("var/lib/warewulf/overlays/ignition/rootfs/etc/systemd/system/ww4-disks.target.ww", "../rootfs/etc/systemd/system/ww4-disks.target.ww")
|
||||
env.ImportFile("var/lib/warewulf/overlays/ignition/rootfs/etc/systemd/system/ww4-mounts.ww", "../rootfs/etc/systemd/system/ww4-mounts.ww")
|
||||
env.ImportFile("var/lib/warewulf/overlays/ignition/rootfs/warewulf/ignition.json.ww", "../rootfs/warewulf/ignition.json.ww")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
tests := map[string]struct {
|
||||
args []string
|
||||
log string
|
||||
json bool
|
||||
nodesConf string
|
||||
output string
|
||||
isJson bool
|
||||
}{
|
||||
{
|
||||
name: "ignition:ww4-disks.target",
|
||||
args: []string{"--render", "node1", "ignition", "etc/systemd/system/ww4-disks.target.ww"},
|
||||
log: ignition_disks,
|
||||
json: false,
|
||||
},
|
||||
{
|
||||
name: "ignition:ww4-mounts",
|
||||
args: []string{"--render", "node1", "ignition", "etc/systemd/system/ww4-mounts.ww"},
|
||||
log: ignition_mounts,
|
||||
json: false,
|
||||
},
|
||||
{
|
||||
name: "ignition:ignition.json",
|
||||
args: []string{"--quiet", "--render", "node1", "ignition", "warewulf/ignition.json.ww"},
|
||||
log: ignition_json,
|
||||
json: true,
|
||||
},
|
||||
}
|
||||
"ignition:ww4-disks.target": {
|
||||
args: []string{"--quiet", "--render", "node1", "ignition", "etc/systemd/system/ww4-disks.target.ww"},
|
||||
nodesConf: `
|
||||
nodes:
|
||||
node1:
|
||||
disks:
|
||||
/dev/vda:
|
||||
wipe_table: true
|
||||
partitions:
|
||||
scratch:
|
||||
should_exist: true
|
||||
swap:
|
||||
number: "1"
|
||||
size_mib: "1024"
|
||||
filesystems:
|
||||
/dev/disk/by-partlabel/scratch:
|
||||
format: btrfs
|
||||
path: /scratch
|
||||
wipe_filesystem: true
|
||||
/dev/disk/by-partlabel/swap:
|
||||
format: swap
|
||||
path: swap`,
|
||||
output: `# This file is autogenerated by warewulf
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
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())
|
||||
if tt.json {
|
||||
assert.JSONEq(t, tt.log, logbuf.String())
|
||||
} else {
|
||||
assert.Equal(t, tt.log, logbuf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const ignition_disks string = `backupFile: true
|
||||
writeFile: true
|
||||
Filename: etc/systemd/system/ww4-disks.target
|
||||
# This file is autogenerated by warewulf
|
||||
[Unit]
|
||||
Description=mount ww4 disks
|
||||
# make sure that the disks are available
|
||||
@@ -80,14 +50,76 @@ Requisite=ignition-ww4-disks.service
|
||||
# Get the mounts
|
||||
Wants=scratch.mount
|
||||
Wants=dev-disk-by\x2dpartlabel-swap.swap
|
||||
`
|
||||
`,
|
||||
isJson: false,
|
||||
},
|
||||
"ignition:ww4-disks.target (resources)": {
|
||||
args: []string{"--quiet", "--render", "node1", "ignition", "etc/systemd/system/ww4-disks.target.ww"},
|
||||
nodesConf: `
|
||||
nodes:
|
||||
node1:
|
||||
resources:
|
||||
ignition:
|
||||
storage:
|
||||
disks:
|
||||
- device: /dev/vda
|
||||
wipeTable: true
|
||||
partitions:
|
||||
- label: scratch
|
||||
shouldExist: true
|
||||
- label: swap
|
||||
number: 1
|
||||
sizeMiB: 1024
|
||||
filesystems:
|
||||
- device: /dev/disk/by-partlabel/scratch
|
||||
format: btrfs
|
||||
path: /scratch
|
||||
wipeFilesystem: true
|
||||
- device: /dev/disk/by-partlabel/swap
|
||||
format: swap
|
||||
path: swap`,
|
||||
output: `# This file is autogenerated by warewulf
|
||||
|
||||
const ignition_mounts string = `backupFile: true
|
||||
[Unit]
|
||||
Description=mount ww4 disks
|
||||
# make sure that the disks are available
|
||||
Requires=ignition-ww4-disks.service
|
||||
After=ignition-ww4-disks.service
|
||||
Requisite=ignition-ww4-disks.service
|
||||
# Get the mounts
|
||||
Wants=scratch.mount
|
||||
Wants=dev-disk-by\x2dpartlabel-swap.swap
|
||||
`,
|
||||
isJson: false,
|
||||
},
|
||||
"ignition:ww4-mounts": {
|
||||
args: []string{"--quiet=false", "--render", "node1", "ignition", "etc/systemd/system/ww4-mounts.ww"},
|
||||
nodesConf: `
|
||||
nodes:
|
||||
node1:
|
||||
disks:
|
||||
/dev/vda:
|
||||
wipe_table: true
|
||||
partitions:
|
||||
scratch:
|
||||
should_exist: true
|
||||
swap:
|
||||
number: "1"
|
||||
size_mib: "1024"
|
||||
filesystems:
|
||||
/dev/disk/by-partlabel/scratch:
|
||||
format: btrfs
|
||||
path: /scratch
|
||||
wipe_filesystem: true
|
||||
/dev/disk/by-partlabel/swap:
|
||||
format: swap
|
||||
path: swap`,
|
||||
output: `backupFile: true
|
||||
writeFile: true
|
||||
Filename: scratch.mount
|
||||
|
||||
|
||||
# This file is autogenerated by warewulf
|
||||
|
||||
[Unit]
|
||||
ConditionPathExists=/warewulf/ignition.json
|
||||
Before=local-fs.target
|
||||
@@ -99,11 +131,11 @@ What=/dev/disk/by-partlabel/scratch
|
||||
Type=btrfs
|
||||
[Install]
|
||||
RequiredBy=local-fs.target
|
||||
|
||||
backupFile: true
|
||||
writeFile: true
|
||||
Filename: dev-disk-by\x2dpartlabel-swap.swap
|
||||
# This file is autogenerated by warewulf
|
||||
|
||||
[Unit]
|
||||
ConditionPathExists=/warewulf/ignition.json
|
||||
Requires=ignition-ww4-disks.service
|
||||
@@ -113,9 +145,91 @@ Before=swap.target
|
||||
What=/dev/disk/by-partlabel/swap
|
||||
[Install]
|
||||
RequiredBy=swap.target
|
||||
`
|
||||
`,
|
||||
isJson: false,
|
||||
},
|
||||
"ignition:ww4-mounts (resources)": {
|
||||
args: []string{"--quiet=false", "--render", "node1", "ignition", "etc/systemd/system/ww4-mounts.ww"},
|
||||
nodesConf: `
|
||||
nodes:
|
||||
node1:
|
||||
resources:
|
||||
ignition:
|
||||
storage:
|
||||
disks:
|
||||
- device: /dev/vda
|
||||
wipeTable: true
|
||||
partitions:
|
||||
scratch:
|
||||
shouldExist: true
|
||||
swap:
|
||||
number: 1
|
||||
sizeMiB: 1024
|
||||
filesystems:
|
||||
- device: /dev/disk/by-partlabel/scratch
|
||||
format: btrfs
|
||||
path: /scratch
|
||||
wipeFilesystem: true
|
||||
- device: /dev/disk/by-partlabel/swap
|
||||
format: swap
|
||||
path: swap`,
|
||||
output: `backupFile: true
|
||||
writeFile: true
|
||||
Filename: scratch.mount
|
||||
|
||||
const ignition_json string = `{
|
||||
# This file is autogenerated by warewulf
|
||||
|
||||
[Unit]
|
||||
ConditionPathExists=/warewulf/ignition.json
|
||||
Before=local-fs.target
|
||||
Requires=ignition-ww4-disks.service
|
||||
After=ignition-ww4-disks.service
|
||||
[Mount]
|
||||
Where=/scratch
|
||||
What=/dev/disk/by-partlabel/scratch
|
||||
Type=btrfs
|
||||
[Install]
|
||||
RequiredBy=local-fs.target
|
||||
backupFile: true
|
||||
writeFile: true
|
||||
Filename: dev-disk-by\x2dpartlabel-swap.swap
|
||||
# This file is autogenerated by warewulf
|
||||
|
||||
[Unit]
|
||||
ConditionPathExists=/warewulf/ignition.json
|
||||
Requires=ignition-ww4-disks.service
|
||||
After=ignition-ww4-disks.service
|
||||
Before=swap.target
|
||||
[Swap]
|
||||
What=/dev/disk/by-partlabel/swap
|
||||
[Install]
|
||||
RequiredBy=swap.target
|
||||
`,
|
||||
isJson: false,
|
||||
},
|
||||
"ignition:ignition.json": {
|
||||
args: []string{"--quiet", "--render", "node1", "ignition", "warewulf/ignition.json.ww"},
|
||||
nodesConf: `
|
||||
nodes:
|
||||
node1:
|
||||
disks:
|
||||
/dev/vda:
|
||||
wipe_table: true
|
||||
partitions:
|
||||
scratch:
|
||||
should_exist: true
|
||||
swap:
|
||||
number: "1"
|
||||
size_mib: "1024"
|
||||
filesystems:
|
||||
/dev/disk/by-partlabel/scratch:
|
||||
format: btrfs
|
||||
path: /scratch
|
||||
wipe_filesystem: true
|
||||
/dev/disk/by-partlabel/swap:
|
||||
format: swap
|
||||
path: swap`,
|
||||
output: `{
|
||||
"ignition": {
|
||||
"version": "3.1.0"
|
||||
},
|
||||
@@ -155,4 +269,85 @@ const ignition_json string = `{
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
}`,
|
||||
isJson: true,
|
||||
},
|
||||
"ignition:ignition.json (resources)": {
|
||||
args: []string{"--quiet", "--render", "node1", "ignition", "warewulf/ignition.json.ww"},
|
||||
nodesConf: `
|
||||
nodes:
|
||||
node1:
|
||||
resources:
|
||||
ignition:
|
||||
storage:
|
||||
disks:
|
||||
- device: /dev/vda
|
||||
partitions:
|
||||
- label: rootfs
|
||||
number: 1
|
||||
shouldExist: true
|
||||
wipePartitionEntry: false
|
||||
wipeTable: true
|
||||
filesystems:
|
||||
- device: /dev/disk/by-partlabel/rootfs
|
||||
format: ext4
|
||||
path: /
|
||||
wipeFilesystem: false`,
|
||||
output: `{
|
||||
"storage": {
|
||||
"disks": [
|
||||
{
|
||||
"device": "/dev/vda",
|
||||
"partitions": [
|
||||
{
|
||||
"label": "rootfs",
|
||||
"number": 1,
|
||||
"shouldExist": true,
|
||||
"wipePartitionEntry": false
|
||||
}
|
||||
],
|
||||
"wipeTable": true
|
||||
}
|
||||
],
|
||||
"filesystems": [
|
||||
{
|
||||
"device": "/dev/disk/by-partlabel/rootfs",
|
||||
"format": "ext4",
|
||||
"path": "/",
|
||||
"wipeFilesystem": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}`,
|
||||
isJson: true,
|
||||
},
|
||||
}
|
||||
|
||||
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/ignition/rootfs/etc/systemd/system/ww4-disks.target.ww", "../rootfs/etc/systemd/system/ww4-disks.target.ww")
|
||||
env.ImportFile("var/lib/warewulf/overlays/ignition/rootfs/etc/systemd/system/ww4-mounts.ww", "../rootfs/etc/systemd/system/ww4-mounts.ww")
|
||||
env.ImportFile("var/lib/warewulf/overlays/ignition/rootfs/warewulf/ignition.json.ww", "../rootfs/warewulf/ignition.json.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())
|
||||
if tt.isJson {
|
||||
assert.JSONEq(t, tt.output, logbuf.String())
|
||||
} else {
|
||||
assert.Equal(t, tt.output, logbuf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
nodes:
|
||||
node1:
|
||||
disks:
|
||||
/dev/vda:
|
||||
wipe_table: true
|
||||
partitions:
|
||||
scratch:
|
||||
should_exist: true
|
||||
swap:
|
||||
number: "1"
|
||||
size_mib: "1024"
|
||||
filesystems:
|
||||
/dev/disk/by-partlabel/scratch:
|
||||
format: btrfs
|
||||
path: /scratch
|
||||
wipe_filesystem: true
|
||||
/dev/disk/by-partlabel/swap:
|
||||
format: swap
|
||||
path: swap
|
||||
@@ -1,4 +1,5 @@
|
||||
# This file is autogenerated by warewulf
|
||||
|
||||
[Unit]
|
||||
Description=mount ww4 disks
|
||||
# make sure that the disks are available
|
||||
@@ -6,19 +7,20 @@ Requires=ignition-ww4-disks.service
|
||||
After=ignition-ww4-disks.service
|
||||
Requisite=ignition-ww4-disks.service
|
||||
# Get the mounts
|
||||
{{- if .FileSystems }}
|
||||
{{- range $fsdevice, $fs := .FileSystems }}
|
||||
{{- $prefix := $fsdevice }}
|
||||
{{- $prefix = replace "-" "\\x2d" $fsdevice }}
|
||||
{{- $prefix = replace "/" "-" $prefix }}
|
||||
{{- $prefix = substr 1 -1 $prefix }}{{ $suffix := "mount" }}
|
||||
{{- if eq $fs.Format "swap" }}
|
||||
{{- $prefix = replace "/dev/disk/by-partlabel/" "dev-disk-by\\x2dpartlabel-" $fsdevice }}{{ $suffix = "swap"}}
|
||||
{{- else }}
|
||||
{{- if ne $fs.Path "" }}
|
||||
{{- $prefix = replace "-" "\\x2d" $fs.Path }}
|
||||
{{- $prefix = replace "/" "-" $prefix }}
|
||||
{{- $prefix = substr 1 -1 $prefix }}
|
||||
Wants={{ $fsdevice | SystemdEscapePath }}.swap
|
||||
{{- else if ne $fs.Path "" }}
|
||||
Wants={{ $fs.Path | SystemdEscapePath }}.mount
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if .Resources.ignition.storage.filesystems }}
|
||||
{{- range $fs := .Resources.ignition.storage.filesystems }}
|
||||
{{- if eq $fs.format "swap" }}
|
||||
Wants={{ $fs.device | SystemdEscapePath }}.swap
|
||||
{{- else if ne $fs.path "" }}
|
||||
Wants={{ $fs.path | SystemdEscapePath }}.mount
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
Wants={{ print $prefix "." $suffix }}
|
||||
{{- end }}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
{{- if .FileSystems }}
|
||||
{{- range $fsdevice, $fs := .FileSystems }}
|
||||
{{- $prefix := replace "-" "\\x2d" $fs.Path }}
|
||||
{{- $prefix = replace "/" "-" $prefix }}
|
||||
{{- $prefix = substr 1 -1 $prefix }}
|
||||
{{ $suffix := "mount"}}
|
||||
{{- if $fs.Label }}{{ $prefix = $fs.Label }}{{ end }}
|
||||
{{- if eq $fs.Format "swap" }}{{ $prefix = replace "/dev/disk/by-partlabel/" "dev-disk-by\\x2dpartlabel-" $fsdevice }}{{ $suffix = "swap" }}{{ end }}
|
||||
{{ file (print $prefix "." $suffix) }}
|
||||
# This file is autogenerated by warewulf
|
||||
{{- if eq $fs.Format "swap" }}
|
||||
{{ file (print ($fsdevice | SystemdEscapePath) ".swap") }}
|
||||
# This file is autogenerated by warewulf
|
||||
|
||||
[Unit]
|
||||
ConditionPathExists=/warewulf/ignition.json
|
||||
Requires=ignition-ww4-disks.service
|
||||
@@ -17,7 +13,10 @@ Before=swap.target
|
||||
What={{ $fsdevice }}
|
||||
[Install]
|
||||
RequiredBy=swap.target
|
||||
{{- else }}
|
||||
{{- else if ne $fs.Path "" }}
|
||||
{{ file (print ($fs.Path | SystemdEscapePath) ".mount") }}
|
||||
# This file is autogenerated by warewulf
|
||||
|
||||
[Unit]
|
||||
ConditionPathExists=/warewulf/ignition.json
|
||||
Before=local-fs.target
|
||||
@@ -28,9 +27,49 @@ Where={{ $fs.Path }}
|
||||
What={{ $fsdevice }}
|
||||
Type={{ $fs.Format }}
|
||||
{{- if $fs.MountOptions }}
|
||||
Options={{range $index,$opt := $fs.MountOptions }}{{if $index }},{{ end }}{{ $opt }}{{ end }}
|
||||
Options={{ $fs.MountOptions | join "." }}
|
||||
{{- end }}
|
||||
[Install]
|
||||
RequiredBy=local-fs.target
|
||||
{{- else }}
|
||||
{{- continue }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if .Resources.ignition.storage.filesystems }}
|
||||
{{- range $fs := .Resources.ignition.storage.filesystems }}
|
||||
{{- if eq $fs.format "swap" }}
|
||||
{{ file (print ($fs.device | SystemdEscapePath) ".swap") }}
|
||||
# This file is autogenerated by warewulf
|
||||
|
||||
[Unit]
|
||||
ConditionPathExists=/warewulf/ignition.json
|
||||
Requires=ignition-ww4-disks.service
|
||||
After=ignition-ww4-disks.service
|
||||
Before=swap.target
|
||||
[Swap]
|
||||
What={{ $fs.device }}
|
||||
[Install]
|
||||
RequiredBy=swap.target
|
||||
{{- else if ne $fs.path "" }}
|
||||
{{ file (print ($fs.path | SystemdEscapePath) ".mount") }}
|
||||
# This file is autogenerated by warewulf
|
||||
|
||||
[Unit]
|
||||
ConditionPathExists=/warewulf/ignition.json
|
||||
Before=local-fs.target
|
||||
Requires=ignition-ww4-disks.service
|
||||
After=ignition-ww4-disks.service
|
||||
[Mount]
|
||||
Where={{ $fs.path }}
|
||||
What={{ $fs.device }}
|
||||
Type={{ $fs.format }}
|
||||
{{- if $fs.mountOptions }}
|
||||
Options={{ $fs.mountOptions | join "." }}
|
||||
{{- end }}
|
||||
[Install]
|
||||
RequiredBy=local-fs.target
|
||||
{{- else }}
|
||||
{{- continue }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{ IgnitionJson }}
|
||||
{{ coalesce IgnitionJson (.Resources.ignition | toJson) }}
|
||||
23
overlays/ignition/rootfs/warewulf/wwinit.d/10-ignition.sh
Normal file
23
overlays/ignition/rootfs/warewulf/wwinit.d/10-ignition.sh
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/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 ignition >/dev/null; then :
|
||||
info "warewulf: ignition: partition and format disks"
|
||||
ignition --config-cache="${PREFIX}/warewulf/ignition.json" --platform=metal --stage=disks || die "warewulf: ignition: failed to partition/format disk"
|
||||
else
|
||||
info "warewulf: ignition not found"
|
||||
fi
|
||||
@@ -1 +0,0 @@
|
||||
{{ IgnitionJson }}
|
||||
@@ -1,53 +1,65 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Warewulf pre-init (/init)"
|
||||
if ! command -v info >/dev/null; then
|
||||
info() {
|
||||
printf '%s\n' "$*"
|
||||
}
|
||||
fi
|
||||
|
||||
if ! command -v die >/dev/null; then
|
||||
die() {
|
||||
printf '%s\n' "$*" >&2
|
||||
echo
|
||||
echo "Mounting kernel file systems..."
|
||||
echo "Rebooting in 1 minute..."
|
||||
sleep 60
|
||||
echo b > /proc/sysrq-trigger || /sbin/reboot -f
|
||||
}
|
||||
fi
|
||||
|
||||
info "warewulf: running pre-init wwinit process..."
|
||||
|
||||
info "warewulf: 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")
|
||||
# https://systemd.io/INITRD_INTERFACE/
|
||||
mountpoint -q /run || (mkdir -p /run && mount -t tmpfs -o mode=0755,nodev,nosuid,strictatime tmpfs /run && echo "Mounted /run")
|
||||
echo
|
||||
echo "Loading /warewulf/config..."
|
||||
|
||||
info "warewulf: 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
|
||||
die "warewulf: ERROR: /warewulf/config not found"
|
||||
fi
|
||||
echo
|
||||
echo "Configuring root file system..."
|
||||
|
||||
WWPRESCRIPTS=/warewulf/wwprescripts
|
||||
chmod +rx "${WWPRESCRIPTS}"
|
||||
if [ -x /warewulf/run-wwinit.d ]; then
|
||||
/warewulf/run-wwinit.d
|
||||
fi
|
||||
|
||||
NEXT_INIT=/warewulf/run-init
|
||||
chmod +rx "${NEXT_INIT}"
|
||||
|
||||
info "warewulf: configuring root file system..."
|
||||
WWROOT="${WWROOT:-initramfs}"
|
||||
if [ "${WWROOT}" = "initramfs" ]; then
|
||||
echo "WWROOT=${WWROOT}: using initial rootfs and invoking ${WWPRESCRIPTS}..."
|
||||
exec "${WWPRESCRIPTS}"
|
||||
elif [ "${WWROOT}" = "persistent" ] ; then
|
||||
echo "WWROOT=${WWROOT}: booting from DISK"
|
||||
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}"
|
||||
info "warewulf: using initial rootfs and invoking ${NEXT_INIT}..."
|
||||
exec "${NEXT_INIT}"
|
||||
else
|
||||
echo "ERROR: Unrecognized rootfs type requested: ${WWROOT}"
|
||||
echo "Rebooting in 1 minute..."
|
||||
sleep 60
|
||||
echo b > /proc/sysrq-trigger || /sbin/reboot -f
|
||||
mkdir /newroot
|
||||
info "warewulf: mounting ${WWROOT} at /newroot"
|
||||
if [ "${WWROOT}" = "ramfs" -o "${WWROOT}" = "tmpfs" ]; then
|
||||
mount wwroot /newroot -t ${WWROOT} -o mpol=interleave # mpol ignored for ramfs
|
||||
else
|
||||
mount ${WWROOT} /newroot
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "ERROR: wwinit encountered a problem."
|
||||
echo "Rebooting in 1 minute..."
|
||||
sleep 60
|
||||
echo b > /proc/sysrq-trigger || /sbin/reboot -f
|
||||
info "warewulf: copying image to /newroot..."
|
||||
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
|
||||
|
||||
info "warewulf: switching to /newroot and invoking ${NEXT_INIT}..."
|
||||
exec /sbin/switch_root /newroot "${NEXT_INIT}"
|
||||
fi
|
||||
|
||||
die "warewulf: ERROR: wwinit encountered a problem."
|
||||
|
||||
40
overlays/wwinit/rootfs/warewulf/run-init
Executable file
40
overlays/wwinit/rootfs/warewulf/run-init
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/bin/sh
|
||||
|
||||
if ! command -v info >/dev/null; then
|
||||
info() {
|
||||
printf '%s\n' "$*"
|
||||
}
|
||||
fi
|
||||
|
||||
. /warewulf/config
|
||||
|
||||
scriptdir=/warewulf/init.d
|
||||
echo "warewulf: running scripts in ${scriptdir}..."
|
||||
ls -1 "${scriptdir}/" | while read -r name; do
|
||||
info "warewulf: ${name}"
|
||||
sh "${scriptdir}/${name}"
|
||||
done
|
||||
|
||||
init="${WWINIT}"
|
||||
if [ -z "${init}" ]
|
||||
then
|
||||
for candidate in /sbin/init /etc/init /bin/init
|
||||
do
|
||||
if [ -x "${candidate}" ]
|
||||
then
|
||||
init="${candidate}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -n "${init}" ]
|
||||
then
|
||||
info "warewulf: invoking ${init}..."
|
||||
exec "${init}"
|
||||
else
|
||||
echo "ERROR: init not defined or not found."
|
||||
echo "Rebooting in 1 minute..."
|
||||
sleep 60
|
||||
echo b > /proc/sysrq-trigger || /sbin/reboot -f
|
||||
fi
|
||||
18
overlays/wwinit/rootfs/warewulf/run-wwinit.d
Executable file
18
overlays/wwinit/rootfs/warewulf/run-wwinit.d
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
|
||||
if ! command -v info >/dev/null; then
|
||||
info() {
|
||||
printf '%s\n' "$*"
|
||||
}
|
||||
fi
|
||||
|
||||
scriptdir="${PREFIX}/warewulf/wwinit.d"
|
||||
if [ -d "${scriptdir}" ]; then
|
||||
info "warewulf: running scripts in ${scriptdir}..."
|
||||
ls -1 "${scriptdir}/" | while read -r name; do
|
||||
info "warewulf: ${name}"
|
||||
PREFIX=$PREFIX sh "${scriptdir}/${name}"
|
||||
done
|
||||
else
|
||||
info "warewulf: ${scriptdir} not found"
|
||||
fi
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/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
|
||||
|
||||
init="${WWINIT}"
|
||||
if [ -z "${init}" ]
|
||||
then
|
||||
for candidate in /sbin/init /etc/init /bin/init
|
||||
do
|
||||
if [ -x "${candidate}" ]
|
||||
then
|
||||
init="${candidate}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -n "${init}" ]
|
||||
then
|
||||
echo
|
||||
echo "Running ${init}..."
|
||||
exec "${init}"
|
||||
else
|
||||
echo "ERROR: init not defined and not found."
|
||||
echo "Rebooting in 1 minute..."
|
||||
sleep 60
|
||||
echo b > /proc/sysrq-trigger || /sbin/reboot -f
|
||||
fi
|
||||
1
overlays/wwinit/rootfs/warewulf/wwprescripts
Symbolic link
1
overlays/wwinit/rootfs/warewulf/wwprescripts
Symbolic link
@@ -0,0 +1 @@
|
||||
run-init
|
||||
@@ -192,8 +192,15 @@ In particular:
|
||||
- Update PAM configuration to allow missing shadow entries
|
||||
- Relabel the file system for SELinux
|
||||
|
||||
Other overlays may place additional scripts in ``/warewulf/init.d/`` to affect
|
||||
node configuration in this pre-boot environment.
|
||||
Other overlays can place scripts in one of two locations for additional pre-init
|
||||
provisioning actions:
|
||||
|
||||
- **/warewulf/wwinit.d/:** executed in the initial root final system before the
|
||||
image is loaded into its final location. In a two-stage boot, these scripts
|
||||
are executed in the Dracut initramfs.
|
||||
|
||||
- **/warewulf/init.d/:** executed in the final root file system but before
|
||||
calling ``init``.
|
||||
|
||||
wwclient
|
||||
--------
|
||||
@@ -309,6 +316,36 @@ ignition
|
||||
--------
|
||||
|
||||
The **ignition** overlay defines partitions and file systems on local disks.
|
||||
Configuration may be provided via native disk, partition, and filesystem fields
|
||||
or via an ``ignition`` resource.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
ignition:
|
||||
storage:
|
||||
disks:
|
||||
- device: /dev/vda
|
||||
partitions:
|
||||
- label: scratch
|
||||
shouldExist: true
|
||||
wipePartitionEntry: true
|
||||
wipeTable: true
|
||||
filesystems:
|
||||
- device: /dev/disk/by-partlabel/scratch
|
||||
format: btrfs
|
||||
path: /scratch
|
||||
wipeFilesystem: false
|
||||
|
||||
If any disk/partition/filesystem configuration is provided for a node with
|
||||
explicit arguments to ``wwctl <node|profile> set``, the ``ignition`` resource is
|
||||
ignored.
|
||||
|
||||
To use ignition during Dracut (so that the root file system may be provisioned
|
||||
before the image is loaded) include Ignition in the Dracut image.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
wwctl image exec rockylinux-9 -- /usr/bin/dracut --force --no-hostonly --add wwinit --add ignition --regenerate-all
|
||||
|
||||
debug
|
||||
-----
|
||||
|
||||
@@ -148,7 +148,13 @@ symlink.
|
||||
IgnitionJson
|
||||
------------
|
||||
|
||||
Generates JSON suitable for use by Ignition to create
|
||||
Generates JSON suitable for use by Ignition to create partitions and file
|
||||
systems, from the data stored in a node's native ``disks`` and ``filesystems``
|
||||
fields.
|
||||
|
||||
.. code-block::
|
||||
|
||||
{{ IgnitionJson }}
|
||||
|
||||
abort
|
||||
-----
|
||||
@@ -174,10 +180,9 @@ nobackup
|
||||
UniqueField
|
||||
-----------
|
||||
|
||||
UniqueField returns a filtered version of a multi-line input string. input is
|
||||
expected to be a field-separated format with one record per line (terminated by
|
||||
`\n`). Order of lines is preserved, with the first matching line taking
|
||||
precedence.
|
||||
Returns a filtered version of a multi-line input string. input is expected to be
|
||||
a field-separated format with one record per line (terminated by `\n`). Order of
|
||||
lines is preserved, with the first matching line taking precedence.
|
||||
|
||||
For example, the following template snippet has been used in the ``syncuser`` overlay
|
||||
to generate a combined ``/etc/passwd``.
|
||||
@@ -191,6 +196,24 @@ to generate a combined ``/etc/passwd``.
|
||||
| UniqueField ":" 0 | trim
|
||||
}}
|
||||
|
||||
SystemdEscape
|
||||
-------------
|
||||
|
||||
Escapes a string for use in a systemd unit file.
|
||||
|
||||
Escape rules are documented at `systemd.unit. <https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#String%20Escaping%20for%20Inclusion%20in%20Unit%20Names>`_
|
||||
|
||||
SystemdEscapePath
|
||||
-----------------
|
||||
|
||||
Escapes a path for use in a systemd unit file.
|
||||
|
||||
.. code-block::
|
||||
|
||||
{{ file (print ($fs.path | SystemdEscapePath) ".mount") }}
|
||||
|
||||
Escape rules are documented at `systemd.unit. <https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#String%20Escaping%20for%20Inclusion%20in%20Unit%20Names>`_
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@ For example, the ``imgextract`` command can be `explicitly enabled`_.
|
||||
support for ZLIB and GZIP archive image formats.
|
||||
|
||||
Configuring Warewulf (>= v4.5.0)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In Warewulf v4.5.0, Warewulf can be configured to use these files using the
|
||||
``tftp.ipxe`` and ``paths.ipxesource`` configuration parameters in
|
||||
@@ -321,8 +321,8 @@ directly from the node's assigned image.
|
||||
|
||||
.. _booting with dracut:
|
||||
|
||||
Booting with dracut
|
||||
===================
|
||||
Two-stage boot: dracut
|
||||
======================
|
||||
|
||||
Some systems, typically due to limitations in their BIOS or EFI firmware, are
|
||||
unable to load image of a certain size directly with a traditional bootloader,
|
||||
@@ -337,21 +337,15 @@ must be installed in the image.
|
||||
With the ``warewulf-dracut`` package installed in the image, you can then build
|
||||
an initramfs inside the image.
|
||||
|
||||
EL installation
|
||||
---------------
|
||||
.. code-block:: shell
|
||||
|
||||
# Enterprise Linux
|
||||
wwctl image exec rockylinux-9 --build=false -- /usr/bin/dnf -y install https://github.com/warewulf/warewulf/releases/download/v4.6.1/warewulf-dracut-4.6.1-1.el9.noarch.rpm
|
||||
wwctl image exec rockylinux-9 -- /usr/bin/dracut --force --no-hostonly --add wwinit --regenerate-all
|
||||
|
||||
|
||||
SUSE installation
|
||||
-----------------
|
||||
.. code-block:: shell
|
||||
|
||||
zypper -y install warewulf-dracut
|
||||
dracut --force --no-hostonly --add wwinit --regenerate-all
|
||||
|
||||
# SUSE
|
||||
wwctl image exec leap-15 --build=false -- /usr/bin/zypper -y install https://github.com/warewulf/warewulf/releases/download/v4.6.1/warewulf-dracut-4.6.1-1.suse.lp155.noarch.rpm
|
||||
wwctl image exec leap-15 -- /usr/bin/dracut --force --no-hostonly --add wwinit --regenerate-all
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -403,42 +397,70 @@ to 50% of physical memory. This size limit may be adjusted using the kernel
|
||||
argument `wwinit.tmpfs.size`. (This parameter is passed to the `size` option
|
||||
during tmpfs mount. See ``tmpfs(5)`` for more details.)
|
||||
|
||||
Persistent installation
|
||||
========================
|
||||
With the `dracut` installation enabled warewulf can also install
|
||||
the node image to a harddrive. On the first boot of the node the
|
||||
compressed node image is simply dumped onto the configured partition.
|
||||
.. _provision to disk:
|
||||
|
||||
Provision to disk
|
||||
=================
|
||||
|
||||
*New in Warewulf v4.6.2*
|
||||
|
||||
As a tech preview, the Warewulf two-stage boot process can provision the node
|
||||
image to local storage.
|
||||
|
||||
.. warning::
|
||||
warewulf doesn't install the bootloader to the disk and add UEFI
|
||||
entries. In order to boot the node network booting is required and
|
||||
at every boot the kernel and the initrd is transfered over the network.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
The node image will be installed to the partition called `rootfs`. You
|
||||
can add add a rootfs with e.g. following command
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
wwctl node set n01 \
|
||||
--diskname /dev/vda --diskwipe \
|
||||
--partname rootfs --partcreate --partnumber 1 \
|
||||
--fsname rootfs --fsformat ext4 --fspath /rootfs
|
||||
|
||||
Now the node boot method has to be set to persitent with following command
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
wwctl node set n01 --root persistent
|
||||
|
||||
and the node rebooted.
|
||||
This functionality is a technology preview and should be used with care. Pay
|
||||
specific attention to ``wipeFilesytem`` (ignition) and ``overwrite`` (mkfs)
|
||||
settings.
|
||||
|
||||
.. note::
|
||||
|
||||
If the boot mode is perisiten the configured partion labeled `rootfs` will
|
||||
be mounted as `/`. With any other boot method mount point confiured (here `/roofs`)
|
||||
will be used.
|
||||
Warewulf doesn't install a bootloader to the disk or add UEFI entries. Nodes
|
||||
still request an image and configuration from the Warewulf server on every
|
||||
boot.
|
||||
|
||||
.. note::
|
||||
|
||||
While provisioning to disk should be possible during a single-stage boot, not
|
||||
all features are available:
|
||||
|
||||
- Warewulf does not perform hardware detection to ensure that necessary
|
||||
kernel modules are loaded prior to init.
|
||||
- Warewulf does not load udev to ensure that ``/dev/disk/by-*`` symlinks are
|
||||
available prior to init.
|
||||
|
||||
With Ignition
|
||||
-------------
|
||||
|
||||
Warewulf needs a prepared file system to deploy the image to. Warewulf can
|
||||
provision this file system using Ignition. To use Ignition, include ``ignition``
|
||||
in your system overlay. The ignition overlay provisions disks during init and,
|
||||
optionally, during the first stage of a two-stage boot. This allows the
|
||||
root file system to be provisioned before the image is loaded.
|
||||
|
||||
.. 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,
|
||||
Ignition must be included in the Dracut image.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
wwctl image exec rockylinux-9 -- /usr/bin/dracut --force --no-hostonly --add wwinit --add ignition --regenerate-all
|
||||
|
||||
The necessary file system may alternatively be prepared out-of-band.
|
||||
|
||||
|
||||
Configuring the root device
|
||||
---------------------------
|
||||
|
||||
Set the desired storage device for the node image using the ``--root``
|
||||
parameter.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
wwctl node set wwnode1 --root /dev/disk/by-partlabel/rootfs
|
||||
|
||||
Reference in New Issue
Block a user