From 93a2eeaf2c02a0c38f5a39b607944d84deb9fa92 Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Tue, 23 Feb 2021 19:52:11 -0800 Subject: [PATCH 01/30] Sleep and reboot on PXE error --- etc/ipxe/default.ipxe | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/etc/ipxe/default.ipxe b/etc/ipxe/default.ipxe index 42daae76..5cd3a227 100644 --- a/etc/ipxe/default.ipxe +++ b/etc/ipxe/default.ipxe @@ -11,8 +11,16 @@ echo set base http://{{.Ipaddr}}:{{.Port}} -kernel ${base}/kernel/{{.Hwaddr}} {{.KernelArgs}} || reboot -initrd ${base}/container/{{.Hwaddr}} || reboot -initrd ${base}/kmods/{{.Hwaddr}} || reboot -initrd ${base}/overlay-system/{{.Hwaddr}} || reboot -boot || reboot +kernel ${base}/kernel/{{.Hwaddr}} {{.KernelArgs}} || goto reboot +initrd ${base}/container/{{.Hwaddr}} || goto reboot +initrd ${base}/kmods/{{.Hwaddr}} || goto reboot +initrd ${base}/overlay-system/{{.Hwaddr}} || goto reboot +boot || goto reboot + + +:reboot +echo +echo There was an error, rebooting in 15s... +echo +sleep 15 +reboot From 33e68eaeac757a3b3490cfc0edbd19af677d6c00 Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Tue, 23 Feb 2021 19:52:39 -0800 Subject: [PATCH 02/30] Minor fixups to daemon process and reloading the database on configuring a new node --- internal/pkg/warewulfd/ipxe.go | 13 ++++++++++++- internal/pkg/warewulfd/nodedb.go | 1 - internal/pkg/warewulfd/util.go | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/internal/pkg/warewulfd/ipxe.go b/internal/pkg/warewulfd/ipxe.go index ab87f665..08ce0ee9 100644 --- a/internal/pkg/warewulfd/ipxe.go +++ b/internal/pkg/warewulfd/ipxe.go @@ -27,6 +27,7 @@ type iPxeTemplate struct { } func IpxeSend(w http.ResponseWriter, req *http.Request) { + url := strings.Split(req.URL.Path, "/") var unconfiguredNode bool @@ -45,6 +46,8 @@ func IpxeSend(w http.ResponseWriter, req *http.Request) { return } + log.Printf("IPXEREQ: %s\n", req.URL.Path) + nodeobj, err := GetNode(hwaddr) if err != nil { @@ -58,7 +61,7 @@ func IpxeSend(w http.ResponseWriter, req *http.Request) { return } - wwlog.Printf(wwlog.VERBOSE, "Node was not found, looking for discoverable nodes...\n") + wwlog.Printf(wwlog.INFO, "Node was not found, looking for discoverable nodes...\n") n, netdev, err := nodeDB.FindDiscoverableNode() if err != nil { @@ -85,6 +88,14 @@ func IpxeSend(w http.ResponseWriter, req *http.Request) { _ = overlay.BuildSystemOverlay([]node.NodeInfo{n}) wwlog.Printf(wwlog.INFO, "Building Runtime Overlay:\n") _ = overlay.BuildRuntimeOverlay([]node.NodeInfo{n}) + + wwlog.Printf(wwlog.INFO, "Added node %s\n", nodeobj.Id.Get()) + + err := LoadNodeDB() + if err != nil { + wwlog.Printf(wwlog.WARN, "Could not reload database: %s\n", err) + } + } } } diff --git a/internal/pkg/warewulfd/nodedb.go b/internal/pkg/warewulfd/nodedb.go index b2feddab..bea0368f 100644 --- a/internal/pkg/warewulfd/nodedb.go +++ b/internal/pkg/warewulfd/nodedb.go @@ -53,7 +53,6 @@ func GetNode(val string) (node.NodeInfo, error) { defer db.lock.RUnlock() if _, ok := db.NodeInfo[val]; ok { - wwlog.Printf(wwlog.DEBUG, "Found node:\n%+v\n", db.NodeInfo[val]) return db.NodeInfo[val], nil } diff --git a/internal/pkg/warewulfd/util.go b/internal/pkg/warewulfd/util.go index 9cdba891..09e5d638 100644 --- a/internal/pkg/warewulfd/util.go +++ b/internal/pkg/warewulfd/util.go @@ -19,7 +19,7 @@ func getSanity(req *http.Request) (node.NodeInfo, error) { nodeobj, err := GetNode(hwaddr) if err != nil { var ret node.NodeInfo - return ret, errors.New("Could not find node by HW address") + return ret, errors.New("Could not find node by HW address: " + req.URL.Path) } log.Printf("REQ: %15s: %s\n", nodeobj.Id.Get(), req.URL.Path) From 64d1a24cd922933e032d4f3dfc454efe348f1b8b Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Wed, 24 Feb 2021 20:59:41 -0800 Subject: [PATCH 03/30] Added container command alias for 'vnfs' --- internal/app/wwctl/container/root.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/app/wwctl/container/root.go b/internal/app/wwctl/container/root.go index d85be76f..cf209b6f 100644 --- a/internal/app/wwctl/container/root.go +++ b/internal/app/wwctl/container/root.go @@ -16,6 +16,7 @@ var ( Long: "Starting with version 4, Warewulf uses containers to build the bootable VNFS\n" + "images for nodes to boot. These commands will help you import, management, and\n" + "transform containers into bootable Warewulf VNFS images.", + Aliases: []string{"vnfs"}, } test bool ) From 3998dff0210ecf59c8fbd2683e3ef27b254af5f4 Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Wed, 24 Feb 2021 21:00:15 -0800 Subject: [PATCH 04/30] Allowed undescore character in container name --- internal/pkg/container/vnfs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/container/vnfs.go b/internal/pkg/container/vnfs.go index 731f67cb..a9effc37 100644 --- a/internal/pkg/container/vnfs.go +++ b/internal/pkg/container/vnfs.go @@ -12,7 +12,7 @@ import ( ) func ValidName(name string) bool { - if util.ValidString(name, "^[a-zA-Z0-9.:-]+$") == false { + if util.ValidString(name, "^[a-zA-Z0-9.:-_]+$") == false { wwlog.Printf(wwlog.WARN, "VNFS name has illegal characters: %s\n", name) return false } From 8f049f9ba48297f71dfa22a379a0213cead3ed2e Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Wed, 24 Feb 2021 21:00:41 -0800 Subject: [PATCH 05/30] Added function for recursive file copy --- internal/pkg/util/util.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/internal/pkg/util/util.go b/internal/pkg/util/util.go index 36f12118..6b2a0ab5 100644 --- a/internal/pkg/util/util.go +++ b/internal/pkg/util/util.go @@ -8,6 +8,7 @@ import ( "math/rand" "os" "os/exec" + "path" "path/filepath" "regexp" "time" @@ -88,6 +89,40 @@ func CopyFile(source string, dest string) error { return destFD.Close() } +func CopyFiles(source string, dest string) error { + err := filepath.Walk(source, func(location string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + wwlog.Printf(wwlog.DEBUG, "Creating directory: %s\n", location) + + err := os.MkdirAll(path.Join(dest, location), info.Mode()) + if err != nil { + return err + } + + } else { + wwlog.Printf(wwlog.DEBUG, "Writing file: %s\n", location) + + err := CopyFile(location, path.Join(dest, location)) + if err != nil { + return err + } + + } + + return nil + }) + + if err != nil { + return err + } + + return nil +} + //TODO: func CopyRecursive ... func IsDir(path string) bool { From 5a07ac2e14a34b5a202d9f55cf8ceade288420bf Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Wed, 24 Feb 2021 21:01:35 -0800 Subject: [PATCH 06/30] Cleaned container.Build() return --- internal/app/wwctl/container/build/main.go | 4 ++-- internal/pkg/container/build.go | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/internal/app/wwctl/container/build/main.go b/internal/app/wwctl/container/build/main.go index b0f9e692..3ce09c93 100644 --- a/internal/app/wwctl/container/build/main.go +++ b/internal/app/wwctl/container/build/main.go @@ -30,12 +30,12 @@ func CobraRunE(cmd *cobra.Command, args []string) error { os.Exit(1) } - output, err := container.Build(c, BuildForce) + err := container.Build(c, BuildForce) if err != nil { wwlog.Printf(wwlog.ERROR, "Could not build container %s: %s\n", c, err) os.Exit(1) } else { - fmt.Printf("%-20s: %s\n", c, output) + //fmt.Printf("%-20s: %s\n", c, output) } } diff --git a/internal/pkg/container/build.go b/internal/pkg/container/build.go index 0b4e5256..87c6ae3e 100644 --- a/internal/pkg/container/build.go +++ b/internal/pkg/container/build.go @@ -11,40 +11,41 @@ import ( "github.com/hpcng/warewulf/internal/pkg/wwlog" ) -func Build(name string, buildForce bool) (string, error) { +func Build(name string, buildForce bool) error { rootfsPath := RootFsDir(name) imagePath := ImageFile(name) if ValidSource(name) == false { - return "", errors.New("Container does not exist") + return errors.New("Container does not exist") } if buildForce == false { wwlog.Printf(wwlog.DEBUG, "Checking if there have been any updates to the VNFS directory\n") if util.PathIsNewer(rootfsPath, imagePath) { - return "Skipping (VNFS is current)", nil + + return errors.New("Skipping (VNFS is current)") } } wwlog.Printf(wwlog.DEBUG, "Making parent directory for: %s\n", name) err := os.MkdirAll(path.Dir(imagePath), 0755) if err != nil { - return "Failed creating directory", err + return errors.New("Failed creating directory") } wwlog.Printf(wwlog.DEBUG, "Making parent directory for: %s\n", rootfsPath) err = os.MkdirAll(path.Dir(rootfsPath), 0755) if err != nil { - return "Failed creating directory", err + return errors.New("Failed creating directory") } wwlog.Printf(wwlog.DEBUG, "Building VNFS image: '%s' -> '%s'\n", rootfsPath, imagePath) cmd := fmt.Sprintf("cd %s; find . | cpio --quiet -o -H newc | gzip -c > \"%s\"", rootfsPath, imagePath) err = exec.Command("/bin/sh", "-c", cmd).Run() if err != nil { - return "Failed building VNFS", err + return errors.New("Failed building VNFS") } - return "Done", nil + return nil } From 6357e3143886f3f3bc693563cc0e840c829f77c2 Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Wed, 24 Feb 2021 21:01:56 -0800 Subject: [PATCH 07/30] Support container import of chroot directory --- internal/app/wwctl/container/exec/main.go | 4 +-- internal/app/wwctl/container/imprt/main.go | 33 +++++++++++++------- internal/pkg/container/{pull.go => imprt.go} | 28 ++++++++++++++++- 3 files changed, 50 insertions(+), 15 deletions(-) rename internal/pkg/container/{pull.go => imprt.go} (60%) diff --git a/internal/app/wwctl/container/exec/main.go b/internal/app/wwctl/container/exec/main.go index 0e818d85..1ad9ff03 100644 --- a/internal/app/wwctl/container/exec/main.go +++ b/internal/app/wwctl/container/exec/main.go @@ -38,12 +38,12 @@ func CobraRunE(cmd *cobra.Command, args []string) error { } fmt.Printf("Rebuilding container...\n") - output, err := container.Build(containerName, false) + err := container.Build(containerName, false) if err != nil { wwlog.Printf(wwlog.ERROR, "Could not build container %s: %s\n", containerName, err) os.Exit(1) } else { - fmt.Printf("%s\n", output) + //fmt.Printf("%s\n", output) } return nil diff --git a/internal/app/wwctl/container/imprt/main.go b/internal/app/wwctl/container/imprt/main.go index 180980fb..3a3ef37f 100644 --- a/internal/app/wwctl/container/imprt/main.go +++ b/internal/app/wwctl/container/imprt/main.go @@ -5,6 +5,7 @@ import ( "os" "path" "strconv" + "strings" "github.com/containers/image/v5/types" "github.com/hpcng/warewulf/internal/pkg/container" @@ -85,38 +86,46 @@ func CobraRunE(cmd *cobra.Command, args []string) error { if util.IsDir(fullPath) == true { if SetForce == true { - wwlog.Printf(wwlog.WARN, "Overwriting existing VNFS\n") + fmt.Printf("Overwriting existing VNFS\n") err := os.RemoveAll(fullPath) if err != nil { wwlog.Printf(wwlog.ERROR, "%s\n", err) os.Exit(1) } } else if SetUpdate == true { - wwlog.Printf(wwlog.WARN, "Updating existing VNFS\n") + fmt.Printf("Updating existing VNFS\n") } else { wwlog.Printf(wwlog.ERROR, "VNFS Name exists, specify --force, --update, or choose a different name: %s\n", name) os.Exit(1) } } - sCtx, err := getSystemContext() - if err != nil { - wwlog.Printf(wwlog.ERROR, "%s\n", err) - } + if strings.HasPrefix(uri, "docker://") || strings.HasPrefix(uri, "docker-daemon://") { + sCtx, err := getSystemContext() + if err != nil { + wwlog.Printf(wwlog.ERROR, "%s\n", err) + } - err = container.PullURI(uri, name, sCtx) - if err != nil { - wwlog.Printf(wwlog.ERROR, "Could not pull image: %s\n", err) - os.Exit(1) + err = container.ImportDocker(uri, name, sCtx) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not import image: %s\n", err) + os.Exit(1) + } + } else if util.IsDir(uri) { + err := container.ImportDirectory(uri, name) + if err != nil { + wwlog.Printf(wwlog.ERROR, "Could not import image: %s\n", err) + os.Exit(1) + } } fmt.Printf("Building container: %s\n", name) - output, err := container.Build(name, true) + err := container.Build(name, true) if err != nil { wwlog.Printf(wwlog.ERROR, "Could not build container %s: %s\n", name, err) os.Exit(1) } else { - fmt.Printf("%s: %s\n", name, output) + //fmt.Printf("%s: %s\n", name, output) } if SetDefault == true { diff --git a/internal/pkg/container/pull.go b/internal/pkg/container/imprt.go similarity index 60% rename from internal/pkg/container/pull.go rename to internal/pkg/container/imprt.go index 26a4680b..fa33141d 100644 --- a/internal/pkg/container/pull.go +++ b/internal/pkg/container/imprt.go @@ -2,7 +2,9 @@ package container import ( "context" + "github.com/hpcng/warewulf/internal/pkg/util" "os" + "path" "github.com/containers/image/v5/types" "github.com/hpcng/warewulf/internal/pkg/config" @@ -10,7 +12,7 @@ import ( "github.com/hpcng/warewulf/internal/pkg/oci" ) -func PullURI(uri string, name string, sCtx *types.SystemContext) error { +func ImportDocker(uri string, name string, sCtx *types.SystemContext) error { OciBlobCacheDir := config.LocalStateDir + "/oci/blobs" err := os.MkdirAll(OciBlobCacheDir, 0755) @@ -47,3 +49,27 @@ func PullURI(uri string, name string, sCtx *types.SystemContext) error { return nil } + +func ImportDirectory(uri string, name string) error { + fullPath := RootFsDir(name) + + err := os.MkdirAll(fullPath, 0755) + if err != nil { + return err + } + + if !util.IsDir(uri) { + return errors.New("Import directory does not exist: " + uri) + } + + if !util.IsFile(path.Join(fullPath, "/bin/sh")) { + return errors.New("Source directory has no /bin/sh: " + uri) + } + + err = util.CopyFiles(uri, fullPath) + if err != nil { + return err + } + + return nil +} From b4a6e45b35da1d52e5dde33f303bef7b6a2c0e25 Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Sun, 28 Feb 2021 21:02:13 -0800 Subject: [PATCH 08/30] Fixed bug reported by John Linford on `wwctl node list -n` output --- internal/app/wwctl/node/list/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/app/wwctl/node/list/main.go b/internal/app/wwctl/node/list/main.go index 017c68bc..ce856c8b 100644 --- a/internal/app/wwctl/node/list/main.go +++ b/internal/app/wwctl/node/list/main.go @@ -80,7 +80,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { for _, node := range nodes { if len(node.NetDevs) > 0 { for name, dev := range node.NetDevs { - fmt.Printf("%-22s %-6s %-18s %-15s %-15s\n", node.Id.Get(), name, dev.Hwaddr, dev.Ipaddr, dev.Gateway) + fmt.Printf("%-22s %-6s %-18s %-15s %-15s\n", node.Id.Get(), name, dev.Hwaddr.Print(), dev.Ipaddr.Print(), dev.Gateway.Print()) } } else { fmt.Printf("%-22s %-6s %-18s %-15s %-15s\n", node.Id.Get(), "--", "--", "--", "--") @@ -110,7 +110,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { var netdevs []string if len(node.NetDevs) > 0 { for name, dev := range node.NetDevs { - netdevs = append(netdevs, fmt.Sprintf("%s:%s", name, dev.Ipaddr.Get())) + netdevs = append(netdevs, fmt.Sprintf("%s:%s", name, dev.Ipaddr.Print())) } } sort.Strings(netdevs) From 4867bdb67242a40416a0f67e653139b2e5c7140c Mon Sep 17 00:00:00 2001 From: Brian Clemens Date: Wed, 3 Mar 2021 07:55:23 +0900 Subject: [PATCH 09/30] add docusaurus docs --- docs/.gitignore | 20 +++ docs/README.md | 33 ++++ docs/babel.config.js | 3 + docs/docs/about/architecture.md | 75 ++++++++ docs/docs/about/introduction.md | 39 +++++ docs/docs/about/security.md | 4 + docs/docs/about/templating.md | 4 + docs/docs/appendix/glossary.md | 22 +++ docs/docs/contributing/contributing.md | 104 ++++++++++++ .../development-environment-kvm.md | 127 ++++++++++++++ .../development-environment-vbox.md | 147 ++++++++++++++++ docs/docs/contributing/documentation.md | 22 +++ docs/docs/getting-started/quickstart-el7.md | 138 +++++++++++++++ docs/docs/getting-started/quickstart-el8.md | 160 ++++++++++++++++++ docs/docs/prerequisites/hardware.md | 4 + docs/docs/prerequisites/software.md | 4 + docs/docs/wwctl/configure.md | 48 ++++++ docs/docs/wwctl/container.md | 35 ++++ docs/docs/wwctl/controller.md | 23 +++ docs/docs/wwctl/kernel.md | 19 +++ docs/docs/wwctl/node.md | 79 +++++++++ docs/docs/wwctl/overlay.md | 76 +++++++++ docs/docs/wwctl/power.md | 22 +++ docs/docs/wwctl/profile.md | 46 +++++ docs/docs/wwctl/ready.md | 6 + docs/docs/wwctl/server.md | 20 +++ docs/docs/wwctl/wwctl.md | 30 ++++ docs/docusaurus.config.js | 97 +++++++++++ docs/news/2021-02-25-4.0.0.md | 11 ++ docs/package.json | 34 ++++ docs/sidebars.js | 40 +++++ docs/src/css/custom.css | 25 +++ docs/src/pages/index.js | 94 ++++++++++ docs/src/pages/styles.module.css | 37 ++++ docs/static/.nojekyll | 0 docs/static/img/favicon.png | Bin 0 -> 18706 bytes docs/static/img/logo.png | Bin 0 -> 51914 bytes 37 files changed, 1648 insertions(+) create mode 100644 docs/.gitignore create mode 100644 docs/README.md create mode 100644 docs/babel.config.js create mode 100644 docs/docs/about/architecture.md create mode 100644 docs/docs/about/introduction.md create mode 100644 docs/docs/about/security.md create mode 100644 docs/docs/about/templating.md create mode 100644 docs/docs/appendix/glossary.md create mode 100644 docs/docs/contributing/contributing.md create mode 100644 docs/docs/contributing/development-environment-kvm.md create mode 100644 docs/docs/contributing/development-environment-vbox.md create mode 100644 docs/docs/contributing/documentation.md create mode 100644 docs/docs/getting-started/quickstart-el7.md create mode 100644 docs/docs/getting-started/quickstart-el8.md create mode 100644 docs/docs/prerequisites/hardware.md create mode 100644 docs/docs/prerequisites/software.md create mode 100644 docs/docs/wwctl/configure.md create mode 100644 docs/docs/wwctl/container.md create mode 100644 docs/docs/wwctl/controller.md create mode 100644 docs/docs/wwctl/kernel.md create mode 100644 docs/docs/wwctl/node.md create mode 100644 docs/docs/wwctl/overlay.md create mode 100644 docs/docs/wwctl/power.md create mode 100644 docs/docs/wwctl/profile.md create mode 100644 docs/docs/wwctl/ready.md create mode 100644 docs/docs/wwctl/server.md create mode 100644 docs/docs/wwctl/wwctl.md create mode 100644 docs/docusaurus.config.js create mode 100644 docs/news/2021-02-25-4.0.0.md create mode 100644 docs/package.json create mode 100644 docs/sidebars.js create mode 100644 docs/src/css/custom.css create mode 100644 docs/src/pages/index.js create mode 100644 docs/src/pages/styles.module.css create mode 100644 docs/static/.nojekyll create mode 100644 docs/static/img/favicon.png create mode 100644 docs/static/img/logo.png diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..b2d6de30 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,20 @@ +# Dependencies +/node_modules + +# Production +/build + +# Generated files +.docusaurus +.cache-loader + +# Misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..8960fa2a --- /dev/null +++ b/docs/README.md @@ -0,0 +1,33 @@ +# Website + +This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator. + +## Installation + +```console +yarn install +``` + +## Local Development + +```console +yarn start +``` + +This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server. + +## Build + +```console +yarn build +``` + +This command generates static content into the `build` directory and can be served using any static contents hosting service. + +## Deployment + +```console +GIT_USER= USE_SSH=true yarn deploy +``` + +If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. diff --git a/docs/babel.config.js b/docs/babel.config.js new file mode 100644 index 00000000..e00595da --- /dev/null +++ b/docs/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: [require.resolve('@docusaurus/core/lib/babel/preset')], +}; diff --git a/docs/docs/about/architecture.md b/docs/docs/about/architecture.md new file mode 100644 index 00000000..92d04e7e --- /dev/null +++ b/docs/docs/about/architecture.md @@ -0,0 +1,75 @@ +--- +id: architecture +title: Architecture +--- + +Warewulf's primary design goal is to facilitate software installation, configuration and management on the most common architecture for an HPC cluster nowadays. This popular cluster design originated 25 years ago with the seminal commodity-off-the-shelf (COTS) architecture commonly known as the "Beowulf". + +## The Beowulf + +The original [Beowulf Cluster](https://en.wikipedia.org/wiki/Beowulf_cluster) was developed in 1996 by Dr. Thomas Sterling and Dr. Donald Becker at NASA. The architecture is defined as a group of similar compute worker nodes all connected together using standard commodity equipment on a private network segment. The control node is dual homed (has two network interface cards) with one of these network interface cards attached to the upstream network and the other connected to the same private network which connects the compute worker nodes (as seen in the figure below). + +.. image:: /images/beowulf_architecture.png + +This architecture is very advantageous for creating a scalable HPC cluster resource, but it is an overly simple portrayal of the cluster ecosystem. To this thumbnail sketch we must add storage, scheduling and resource management, monitoring, interactive systems, and as the system grows, it may need to have groups of nodes with different features, architectures, vintages, memory configurations, GPUs or interconnects. As the system grows more complex, the need for a scalable control surface also increases which can result in even more complexity. + +## Provisioning + +Warewulf is designed to support the simplest of clusters, but also the most complicated, largest, and specialized resources that are in demand today. But when implementing all the various configurations, we rely on the basic architecture illustrated above. We can rely on such a simple schematic because Warewulf is designed to "own" the network broadcast domain and manage how these worker nodes boot. + +As mentioned before, Warewulf is designed first and foremost to be a stateless provisioning subsystem. This means that the worker nodes maintain no state about their configuration, operating system or purpose when they are powered off. So, when these systems boot, they retain no knowledge of who they are, what they are supposed to do or how they got there. For these reasons, the worker nodes will need to boot via [PXE](https://en.wikipedia.org/wiki/Preboot_Execution_Environment) in order to retrieve a personality and a purpose. + +## PXE + +Most servers will have network interface cards that support PXE by default. In a nutshell, PXE will allow the network card to be seen by the BIOS as a bootable device. This means that the boot order may need to be configured in the system's BIOS on the worker nodes to allow it to boot. If there is also another bootable device on these systems, it might be necessary to set the network interface card to boot first. + +When the system boots via PXE, it will begin a chain reaction of events: + +1. The network card will register an option ROM into the BIOS +2. The BIOS will run through all of its functions and finish with boot devices +3. The boot devices are attempted to be booted in the defined order +4. When it gets to the network boot device, PXE is run from the firmware on the network card +5. PXE will request a BOOTP/DHCP address on the network which is handled by the controller node +6. If the DHCP response includes a boot file name, it will download this file (iPXE boot image) over TFTP +7. Once iPXE is downloaded, it is loaded by the network card and it will request a configuration from the + controller node +8. The configuration will tell iPXE what to download and load + +Warewulf manages the entire process once the worker node's network device has begun the PXE process. + +## Warewulf Server + +Warewulf will configure the controller's DHCP and TFTP services and put all the required files into place. But once the iPXE file has been sent and loaded on the worker nodes, all network communication from this point on is handled by the Warewulf server on the controller node over HTTP. The order of events from this point on is as follows: + +1. iPXE requests its configuration and Warewulf generates this on demand from the configured template +1. The default iPXE template tells iPXE to request a kernel, VNFS image, runtime kernel modules and a system overlay +1. Each of the requested files will be sent to the worker node + +## Post PXE + +Once the worker node has received all the required files, the kernel will boot, and the runtime components will be loaded into the kernel's initial RAM file system (initramfs). The order of these processes is important because each layer is dependent on the previous. The layers are implemented in the following order: + +1. VNFS/Container image +1. Kernel modules +1. System Overlay +1. Runtime Overlay (repeated at a given interval) + +As part of the provisioning process, the system boots after the System Overlay has been provisioned, which means that the Runtime Overlay occurs after `/sbin/init` has been called on the system. This delineation is important because it clearly defines what should be in the system versus runtime overlay. More on overlays in the next section. + +Before calling `/sbin/init` (or `init` to override in the node configuration file), Warewulf must set up the system. Again, this process occurs after the System Overlay has been provisioned. This is where the system initializes and prepares for booting the runtime OS. Depending on the configuration, the system might boot directly in the initramfs file system as it stands, or it could migrate the root file system to a different mount point (e.g. `tmpfs`or at some point, hard drives). + +Other things that get done at this stage are setting up and enabling SELinux, IPMI, making any needed changes or configurations to the file system before booting it and starting up the `wwclient` which is responsible for loading the runtime overlay. + +Lastly, the `init` process is executed with PID 1 and thus "boots" the VNFS container. This is where SysVInit, Upstart, or Systemd takes over. + +## Warewulf Overlays + +As described above, Warewulf uses layers to provision worker nodes in phases. The first layer is static across any number of nodes, but each node may require some custom configurations, for example, network. This means there must be a method for leveraging a base file system (VNFS) that can be shared by many nodes and also be able to configure these file systems with custom per-node options at a large scale. + +Typically, there are two major times this configuration needs to be done-- pre "boot" and post "boot". In this case, as described above, we can consider the call to `/sbin/init` the delineation point and the proper way to consider the two configurable overlays: system and runtime. + +**System Overlay**: The system overlay is what will be present before `/sbin/init` is called. This gives the administrator the ability to control the configuration of the booting system itself. For example, network configuration must be addressed on every node, but each node must have a slightly different network configuration otherwise the IP addresses will clash. This must be set before Systemd brings up the network device, so the Warewulf system overlay is the right place to configure this. + +**Runtime Overlays**: Some configurations happen after the system boots and continuously at periodic intervals. For example, user and group accounts. You probably don't want to reprovision a node, let alone hundreds of nodes, to add a user or change a runtime configuration, and this is where you should use the runtime overlay. + +Both overlays leverage a similar file system template structure. Each overlay (you can create any number of them) can include text files, directories, links and templates. Templates allow you to dynamically customize any of the content within an overlay for each node that will be leveraging that template. diff --git a/docs/docs/about/introduction.md b/docs/docs/about/introduction.md new file mode 100644 index 00000000..59279998 --- /dev/null +++ b/docs/docs/about/introduction.md @@ -0,0 +1,39 @@ +--- +id: introduction +title: Introduction +slug: / +--- + +## Summary + +Warewulf is an operating system provisioning system for Linux that is designed to produce simple, turnkey HPC deployment solutions that maintain flexibility and configurability at scale. + +Since its initial release in 2001, Warewulf has become the most popular open source and vendor-agnostic provisioning system within the global HPC community. Warewulf is known for its massive scalability and simple management of stateless (disk optional) provisioning. + +In a nutshell, cluster node operating system images are containers. These containers are built into a bootable format called a "Virtual Node File System" (VNFS) image which is provisioned out to nodes when they boot. A VNFS and kernel pair can be distributed to any number of nodes, which means you could have all of your nodes booting the exact same container VNFS image. To avoid the administrative headache of too many customized VNFS images, subtle differences between node operating system configurations are handled with "overlays". + +On boot, each node receives, in the following order: + +1. iPXE boot image (tftp) +1. Linux kernel (http) +1. VNFS image (http) +1. Kernel module overlay (http) +1. System overlay (http) + +Once the node has booted, it will request a runtime overlay (privileged http) at a periodic interval. + +All the images which are transferred for provisioning are prebuilt static components and the per node connection processing is minimal. This means that Warewulf is as scalable as the physical infrastructure. + +## Warewulf Design Tenants + +To enable simple, scalable and flexible operating system management at scale. + +- **Lightweight**: Warewulf needs to do its job and stay out of the way. There should be no underlying system dependencies, changes or "stack" for the controller or worker nodes. + +- **Simple**: Warewulf is used by hobbyists, researchers, scientists, engineers and systems administrators. This means that Warewulf must be simple to use and understand. + +- **Flexible**: Warewulf is highly flexible and can address the needs of any environment-- from a computer lab with graphical workstations, to under-the-desk clusters, to massive supercomputing centers providing traditional HPC capabilities to thousands of users. + +- **Agnostic**: From the Linux distribution to the underlying hardware, Warewulf should be as agnostic and standards compliant as possible. From ARM to x86, Atos to Dell, Warewulf can provision it all equally well with no favorites. + +- **Open Source**: It is imperative that Warewulf be and remain absolutely Open Source. diff --git a/docs/docs/about/security.md b/docs/docs/about/security.md new file mode 100644 index 00000000..6d52799c --- /dev/null +++ b/docs/docs/about/security.md @@ -0,0 +1,4 @@ +--- +id: security +title: Security +--- diff --git a/docs/docs/about/templating.md b/docs/docs/about/templating.md new file mode 100644 index 00000000..edc22337 --- /dev/null +++ b/docs/docs/about/templating.md @@ -0,0 +1,4 @@ +--- +id: templating +title: Templating +--- diff --git a/docs/docs/appendix/glossary.md b/docs/docs/appendix/glossary.md new file mode 100644 index 00000000..8b6d2c2e --- /dev/null +++ b/docs/docs/appendix/glossary.md @@ -0,0 +1,22 @@ +--- +id: glossary +title: Glossary +--- + +### Container + +Containers are used by Warewulf as the template for the VNFS image. Warewulf containers can be any type of OCI or Singularity standard image formats but maintained on disk as an "OCI bundle". Warewulf integrates with Docker, Docker Hub, any OCI registery, Singularity, standard chroots, etc. + +### Controller +The controller node(s) are the resources responsible for management, control, and administration of the cluster. Historically these systems have been called "master", "head", or "administrative" nodes, but we feel the term "controller" is more appropriate and descriptive of the role of this system. + +### Initramfs + +### Kernel + +### Overlays + +### Virtual Node File System (VNFS) + +### Workers +Worker nodes are the systems that are being provisioned by Warewulf. The roles of these systems could be "compute", "storage", "GPU", "IO", etc. which would typically be used as a prefix, for example: "**compute worker node**" diff --git a/docs/docs/contributing/contributing.md b/docs/docs/contributing/contributing.md new file mode 100644 index 00000000..690dfc5a --- /dev/null +++ b/docs/docs/contributing/contributing.md @@ -0,0 +1,104 @@ +--- +id: contributing +title: Contributing +--- + +Warewulf is an open source project, meaning we have the challenge of limited resources. We are grateful for any support that you can offer. Helping other users, raising issues, helping write documentation, or contributing code are all ways to help! + +## Join the community + +This is a huge endeavor, and your help would be greatly appreciated! Post to online communities about Warewulf, and request that your distribution vendor, service provider, and system administrators include Warewulf for you! + +### Warewulf Google Group + +If you have been using Warewulf and having good luck with it, join our [Google Group](https://groups.google.com/a/ctrliq.com/forum/#!forum/warewulf) and help out other users! + +### Warewulf on Slack + +Many of our users come to Slack for quick help with an issue. You can find us at [HPCng](https://join.slack.com/t/hpcng/shared_invite/zt-k9atb2dj-rnlPjSh5Os3Ks6vlDEOyAA). + +## Raise an Issue + +For general bugs/issues, you can open an issue [at the GitHub repo](https://github.com/ctrliq/warewulf/issues/new). However, if you find a security related issue/problem, please email Ctrl IQ directly at [security@ctrliq.com](mailto:security@ctrliq.com). More information about the Ctrl IQ security policies and procedures can be found [here](https://ctrliq.com/warewulf/security-policy). + +## Contribute to the code + +We use the traditional [GitHub Flow](https://guides.github.com/introduction/flow) to develop. This means that you fork the main repo, create a new branch to make changes, and submit a pull request (PR) to the master branch. + +Check out our official [CONTRIBUTING.md](https://github.com/ctrliq/warewulf/blob/master/CONTRIBUTING.md) document, which also includes a [code of conduct](https://github.com/ctrliq/warewulf/blob/master/CONTRIBUTING.md#code-of-conduct). + + +### Step 1. Fork the repo + +To contribute to Warewulf, you should obtain a GitHub account and fork the [Warewulf](https://github.com/ctrliq/warewulf) repository. Once forked, clone your fork of the repo to your computer. (Obviously, you should replace `your-username` with your GitHub username.) + +```bash +git clone https://github.com/your-username/warewulf.git +cd warewulf +``` + +### Step 2. Checkout a new branch + +[Branches](https://guides.github.com/introduction/flow) are a way of +isolating your features from the main branch. Given that we’ve just cloned the +repo, we will probably want to make a new branch from master in which to work on +our new feature. Lets call that branch `new-feature`: + +```bash +git checkout master +git checkout -b new-feature +``` + +:::note +You can always check which branch you are in by running ``git branch``. +::: + +### Step 3. Make your changes + +On your new branch, go nuts! Make changes, test them, and when you are happy commit the changes to the branch: + +```bash +git add file-changed1 file-changed2... +git commit -m "what changed?" +``` + +This commit message is important - it should describe exactly the changes that you have made. Good commit messages read like so: + +```bash +git commit -m "changed function getConfig in functions.go to output csv to fix #2" +git commit -m "updated docs about shell to close #10" +``` + +The tags `close #10` and `fix #2` are referencing issues that are posted on the upstream repo where you will direct your pull request. When your PR is merged into the master branch, these messages will automatically close the issues, and further, they will link your commits directly to the issues they intend to fix. This will help future maintainers understand your contribution, or (hopefully not) revert the code back to a previous version if necessary. + +### Step 4. Push your branch to your fork + +When you are done with your commits, you should push your branch to your fork (and you can also continuously push commits here as you work): + +```bash +git push origin new-feature +``` + +Note that you should always check the status of your branches to see what has been pushed (or not): + +```bash +git status +``` + +### Step 5. Submit a Pull Request +Once you have pushed your branch, then you can go to your fork (in the web GUI on GitHub) and [submit a Pull Request](https://help.github.com/articles/creating-a-pull-request). Regardless of the name of your branch, your PR should be submitted to the Ctrl IQ `master` branch. Submitting your PR will open a conversation thread for the maintainers of Warewulf to discuss your contribution. At this time, the continuous integration that is linked with the code base will also be executed. If there is an issue, or if the maintainers suggest changes, you can continue to push commits to your branch and they will update the Pull Request. + +### Step 6. Keep your branch in sync +Cloning the repo will create an exact copy of the Warewulf repository at that moment. As you work, your branch may become out of date as others merge changesinto the upstream master. In the event that you need to update a branch, you will need to follow the next steps: + +```bash +# add a new remote named "upstream" +git remote add upstream https://github.com/ctrliq/warewulf.git +# or another branch to be updated +git checkout master +git pull upstream master +# to update your fork +git push origin master +git checkout new-feature +git merge master +``` diff --git a/docs/docs/contributing/development-environment-kvm.md b/docs/docs/contributing/development-environment-kvm.md new file mode 100644 index 00000000..7c91aebf --- /dev/null +++ b/docs/docs/contributing/development-environment-kvm.md @@ -0,0 +1,127 @@ +--- +id: development-environment-kvm +title: Development Environment (KVM) +--- + +1. Create CentOS 7 development virtual machine under KVM + +```bash +# KVM is running on server called master1 which is not my desktop +ssh -X master1 + +# On master1 server +wget -P /global/downloads/centos http://mirror.mobap.edu/centos/7.8.2003/isos/x86_64/CentOS-7-x86_64-Everything-2003.iso + +qemu-img create -o preallocation=metadata -f qcow2 /global/images/centos-7.qcow2 32G + +# install wwdev Centos 7 development VM +sudo virt-install --virt-type kvm --name centos7-wwdev --ram 8192 \ + --disk /global/images/centos-7.qcow2,format=qcow2 \ + --network network=default \ + --graphics vnc,listen=0.0.0.0 --noautoconsole \ + --os-type=linux --os-variant=rhel7.0 \ + --location=/global/downloads/centos/CentOS-7-x86_64-Everything-2003.iso + +# Complete installation using virt-manager + +# To start virt-manager on non-local server +ssh -X master1 + +sudo -E virt-manager + +# Login to VM and install @development group and go language +ssh root@wwdev + +# Disable selinux by modifying /etc/sysconfig/selinux +vi /etc/sysconfig/selinux + + SELINUX=disabled + +# disable firewall +systemctl stop firewalld +systemctl disable firewalld +``` + +2. Turn off default network dhcp on server master1 + +```bash +# shutdown all VMs +sudo virsh net-destroy default + +sudo virsh net-edit default + + # remove dhcp lines from XML + +sudo virsh net-start default +``` + + +3. Build and install warewulf on wwdev + +```bash +ssh wwdev + +# Fedora prerequisites +sudo dnf -y install tftp-server tftp +sudo dnf -y install dhcp +sudo dnf -y install ipmitool +sudo dnf install singularity +sudo dnf install gpgme-devel +sudo dnf install libassuan.x86_64 libassuan-devel.x86_64 +sudo dnf golang +sudo dnf nfs-utils + +# Centos prerequisites +sudo yum -y install tftp-server tftp +sudo yum -y install dhcp +sudo yum -y install ipmitool +sudo yum install http://repo.ctrliq.com/packages/rhel7/ctrl-release.rpm +sudo yum install singularityplus +sudo yum install gpgme-devel +sudo yum install libassuan.x86_64 libassuan-devel.x86_64 +sudo yum install https://packages.endpoint.com/rhel/7/os/x86_64/endpoint-repo-1.7-1.x86_64.rpm +sudo yum install golang +sudo yum install nfs-utils + +# Install Warewulf and dependencies +git clone https://github.com/ctrliq/warewulf.git +cd warewulf + +make all +sudo make install + +# Configure the controller +Edit the file /etc/warewulf/warewulf.conf and ensure that you've ser the approprite configuration parameters + +# Configure system service automatically +sudo wwctl configure dhcp # Create the default dhcpd.conf file and start/enable service +sudo wwctl configure tftp # Install the base tftp/PXE boot files and start/enable service +sudo wwctl configure nfs # Configure the exports and create an fstab in the default system overlay +sudo wwctl configure ssh # Build the basic ssh keys to be included by the default system overlay + +# Pull and build the VNFS container and kernel +sudo wwctl container import docker://warewulf/centos-8 centos-8 --setdefault +sudo wwctl kernel import build $(uname -r) --setdefault + +# Set up the default node profile +sudo wwctl profile set default -K $(uname -r) -C centos-7 +sudo wwctl profile set default --netdev eth0 -M WW_server_subnet_mask -G WW_server_ip +sudo wwctl profile list + +# Add a node and build node specific overlays +sudo wwctl node add n0000.cluster --netdev eth0 -I n0000_ip --discoverable +sudo wwctl node list -a n0000 + +# Review Warewulf overlays +sudo wwctl overlay list -l +sudo wwctl overlay list -ls +sudo wwctl overlay edit default /etc/hello_world.ww +sudo wwctl overlay build -a + +# Start the Warewulf daemon +sudo wwctl ready +sudo wwctl server start +sudo wwctl server status +``` + +4. Boot your node and watch the console and the output of the Warewulfd process diff --git a/docs/docs/contributing/development-environment-vbox.md b/docs/docs/contributing/development-environment-vbox.md new file mode 100644 index 00000000..58dfe75b --- /dev/null +++ b/docs/docs/contributing/development-environment-vbox.md @@ -0,0 +1,147 @@ +--- +id: development-environment-vbox +title: Development Environment (VirtualBox) +--- + +I have VirtualBox running on my desktop. + +1. Create a NAT Network (a private vlan) to be used for the Warewlf Server and compute nodes inside the VirtualBox. Make sure to turnoff DHCP service within this NAT Network. + +```bash +# On the host with VirtualBox execute below. In my example using 10.0.8.0/24 as the private vlan for my experiment with Warewulf + +VBoxManage natnetwork add --netname wwnatnetwork --network "10.0.8.0/24" --enable --dhcp off +``` + +2. Create a Centos 7 development Virtual machine (wwdev) to be used as the Warewulf Server. Enable two Network adapters one with a standard NAT and SSH port mapping such that you can access this VM from the host machine. Assign the second network adapter to the NAT Network created in step #1. Assign sufficient memory (e.g: 4GB) to the VM. + +```bash +# Download a Centos7 or SL7 ISO and mount it to the optical drive to boot and install OS for the wwdev VM. +# Attach Network adapter #1 of the wwdev VM to the standard NAT via VM Settings -> Network option. +# By default VirtualBox puts the Network Adapter into 10.0.2.0/24 network and assigns 10.0.2.15 IP address. + +# Also add a rule to the port forwarding table under the standard NAT configuration to allow SSH +# from localhost (127.0.0.1) some high port e.g 2222 to the guest IP 10.0.2.15 port 22 such that +# you can SSH from your host/desktop to the wwdev VM. + +# Next attach the second Network adapter #2 to the NAT Network and you should be able to choose +# the 'wwnatnetwork' created above in step #1 from the drop down list. +``` + +3. Build and install warewulf on wwdev + +```bash +# Login to wwdev VM and install @development group and go language + +ssh localhost -p 2222 #(should prompt for a user account password on wwdev VM) + +# Disable selinux by modifying /etc/sysconfig/selinux +vi /etc/sysconfig/selinux + + SELINUX=disabled + +# Disable firewall +systemctl stop firewalld +systemctl disable firewalld + +# Centos prerequisites +sudo yum -y install tftp-server tftp +sudo yum -y install dhcp +sudo yum -y install ipmitool +sudo yum install http://repo.ctrliq.com/packages/rhel7/ctrl-release.rpm +sudo yum install singularityplus +sudo yum install gpgme-devel +sudo yum install libassuan.x86_64 libassuan-devel.x86_64 + +# Upgrade git to v2+ +sudo yum install https://packages.endpoint.com/rhel/7/os/x86_64/endpoint-repo-1.7-1.x86_64.rpm +sudo yum install git +sudo yum install golang +sudo yum install nfs-utils + +# Install Warewulf and dependencies +git clone https://github.com/ctrliq/warewulf.git +cd warewulf + +make all +sudo make install + +# Static assign an IP to adapter #2 which is in the wwnatnetwork. +$ Edit the file /etc/sysconfig/networking-scripts/ifcfg-enp0s9 # adapter name at the end might be different for you +# Add lines like to below to assign an ip in 10.0.8.0/24 wwnatnetwork, I choose 10.0.8.4 +BOOTPROTO=static +ONBOOT=yes +NAME=enp0s9 +DEVICE=enp0s9 +IPADDR=10.0.8.4 +NETMASK=255.255.255.0 +GATEWAY=10.0.8.1 +# Bring the enp0s9 interface online and verify ip assignment + +# Configure the Warewulf controller +$ Edit the file /etc/warewulf/warewulf.conf and ensure that you've set the approprite configuration parameters. +# My conf file looks like below: + ipaddr: 10.0.8.4 + netmask: 255.255.255.0 + warewulf: + port: 9873 + secure: true + update interval: 60 + dhcp: + enabled: true + range start: 10.0.8.150 + range end: 10.0.8.200 + template: default + systemd name: dhcpd + tftp: + enabled: true + tftproot: /var/lib/tftpboot + systemd name: tftp + nfs: + systemd name: nfs-server + exports: + - /home + - /var/warewulf + +# Configure system service automatically +sudo wwctl configure dhcp --persist # Create the default dhcpd.conf file and start/enable service +sudo wwctl configure tftp --persist # Install the base tftp/PXE boot files and start/enable service +sudo wwctl configure nfs --persist # Configure the exports and create an fstab in the default system overlay +sudo wwctl configure ssh --persist # Build the basic ssh keys to be included by the default system overlay + +# Pull and build the VNFS container and kernel +sudo wwctl container import docker://warewulf/centos-7 centos-7 --setdefault +sudo wwctl kernel import build $(uname -r) --setdefault + +# Set up the default node profile +sudo wwctl profile set default -K $(uname -r) -C centos-7 +sudo wwctl profile set default --netdev eth0 -M 255.255.255.0 -G 10.0.8.4 +sudo wwctl profile list + +# Add a node and build node specific overlays +# IP address of my nodes start from 150 as set in the warewulf.conf file above +sudo wwctl node add n0000.cluster --netdev eth0 -I 10.0.8.150 --discoverable +sudo wwctl node list -a n0000 + +# Review Warewulf overlays +sudo wwctl overlay list -l +sudo wwctl overlay list -ls +sudo wwctl overlay edit default /etc/hello_world.ww +sudo wwctl overlay build -a + +# Start the Warewulf daemon +sudo wwctl ready +sudo wwctl server start +sudo wwctl server status +``` + +4. Create a new guest VM instance inside the VirtualBox to be the warewulf client/compute node. Under the system configuration make sure to select the optical and network options only for the boot order. The default iPXE used by VirtualBox does not come with bzImage capability which is needed for warewulf. Download the ipxe.iso available at ipxe.org and mount the ipxe.iso to the optical drive. Enable one Network adapter for this VM and assign it to the NAT Network created in step #1 above. + +```bash +# Download ipxe.so available at http://boot.ipxe.org/ipxe.iso +# VM Settings -> System disable Floppy, Hard Disk from Boot order. Enable Optical and Network options. +# VM Settings -> Storage and mount the above download ipxe.so to the Optical Drive. +# VM Settings -> Network Enable adapter #1, attach to 'Nat Network' and choose 'wwnatnetwork' from the drop down list. +``` + +5. Boot your node and watch the console and the output of the Warewulfd process diff --git a/docs/docs/contributing/documentation.md b/docs/docs/contributing/documentation.md new file mode 100644 index 00000000..7d91e841 --- /dev/null +++ b/docs/docs/contributing/documentation.md @@ -0,0 +1,22 @@ +--- +id: documentation +title: Documentation +--- + +We (like almost all open source software providers) have a documentation dilemma… We tend to focus on the code features and functionality before working on documentation. And there is very good reason for this: we want to share the love so nobody feels left out! + +You can contribute to the documentation by [raising an issue to suggest an improvement](https://github.com/ctrliq/warewulf-docs/issues/new) or by sending a [pull request](https://github.com/ctrliq/warewulf-docs/compare) on [our repository for documentation](https://github.com/ctrliq/warewulf-docs). + +The current documentation is generated with: + +- [reStructured Text (RST)](http://docutils.sourceforge.net/rst.html) and [ReadTheDocs](https://readthedocs.org). + +Other dependencies include: + +- [Python 3.5 or newer](https://www.python.org/downloads) + +- [Sphinx](https://pypi.org/project/Sphinx) + +More information about contributing to the documentation, instructions on how to install the dependencies, and how to generate the files can be obtained [here](https://github.com/ctrliq/warewulf-docs/blob/master/README.md). + +For more information on using Git and GitHub to create a pull request suggesting additions and edits to the docs, see the [section on contributing to the code](contributing). The procedure is identical for contributions to the documentation and the code base. diff --git a/docs/docs/getting-started/quickstart-el7.md b/docs/docs/getting-started/quickstart-el7.md new file mode 100644 index 00000000..f2da5240 --- /dev/null +++ b/docs/docs/getting-started/quickstart-el7.md @@ -0,0 +1,138 @@ +--- +id: quickstart-el7 +title: Quick Start for RHEL and CentOS 7 +--- + +Install Warewulf and dependencies +================================= + +.. code-block:: bash + + $ sudo yum install epel-release + $ sudo yum install golang tftp-server dhcp nfs-utils + + $ sudo systemctl stop firewalld + $ sudo systemctl disable firewalld + + $ git clone https://github.com/ctrliq/warewulf.git + $ cd warewulf + $ make all + $ sudo make install + +Configure the controller +======================== + +Edit the file ``/etc/warewulf/warewulf.conf`` and ensure that you've set the appropriate configuration paramaters. Here are some of the defaults for reference: + +.. code-block:: yaml + + ipaddr: 192.168.1.1 + netmask: 255.255.255.0 + warewulf: + port: 9873 + secure: true + update interval: 60 + dhcp: + enabled: true + range start: 192.168.1.150 + range end: 192.168.1.200 + template: default + systemd name: dhcpd + tftp: + enabled: true + tftproot: /var/lib/tftpboot + systemd name: tftp + nfs: + systemd name: nfs-server + exports: + - /home + - /var/warewulf + +Configure system services automatically +======================================= + +.. code-block:: bash + + $ sudo wwctl configure dhcp # Create the default dhcpd.conf file and start/enable service + $ sudo wwctl configure tftp # Install the base tftp/PXE boot files and start/enable service + $ sudo wwctl configure nfs # Configure the exports and create an fstab in the default system overlay + $ sudo wwctl configure ssh # Build the basic ssh keys to be included by the default system overlay + +Pull and build the VNFS container and kernel +============================================ + +This will pull a basic VNFS container from Docker Hub and import the default running kernel from the controller node and set both in the "default" node profile. + +.. code-block:: bash + + $ sudo wwctl container import docker://warewulf/centos-7 centos-7 --setdefault + $ sudo wwctl kernel import $(uname -r) --setdefault + +Set up the default node profile +=============================== + +The ``--setdefault`` arguments above will automatically set those entries in the default profile, but if you wanted to set them by hand to something different, you can do the following: + +.. code-block:: bash + + $ sudo wwctl profile set default -K $(uname -r) -C centos-7 + +Next we set some default networking configurations for the first ethernet device. On modern Linux distributions, the name of the device is not critical, as it will be setup according to the HW address. Because all nodes will share the netmask and gateway configuration, we can set them in the default profile as follows: + +.. code-block:: bash + + $ sudo wwctl profile set default --netdev eth0 -M 255.255.255.0 -G 192.168.1.1 + $ sudo wwctl profile list + +Add a node and build node specific overlays +=========================================== + +Adding nodes can be done while setting configurations in one command. Here we are setting the IP address of ``eth0`` and setting this node to be discoverable, which will then automatically have the HW address added to the configuration as the node boots. + +Node names must be unique. If you have node groups and/or multiple clusters, designate them using dot notation. + +Note that the full node configuration comes from both cascading profiles and node configurations which always supersede profile configurations. + +.. code-block:: bash + + $ sudo wwctl node add n0000.cluster --netdev eth0 -I 192.168.1.100 --discoverable + $ sudo wwctl node list -a n0000 + +Warewulf Overlays +================= + +There are two types of overlays: system and runtime overlays. + +System overlays are provisioned to the node before ``/sbin/init`` is called. This enables us to prepopulate node configurations with content that is node specific like networking and service configurations. + +Runtime overlays are provisioned after the node has booted and periodically during the normal runtime of the node. Because these overlays are provisioned at periodic intervals, they are very useful for content that changes, like users and groups. + +Overlays are generated from a template structure that is viewed using the ``wwctl overlay`` commands. Files that end in the ``.ww`` suffix are templates and abide by standard text/template rules. This supports loops, arrays, variables, and functions making overlays extremely flexible. + +.. note:: + When using the overlay subsystem, system overlays are never shown by default. So when running ``overlay`` commands, you are always looking at runtime overlays unless the ``-s`` option is passed. + +All overlays are compiled before being provisioned. This accelerates the provisioning process because there is less to do when nodes are being managed at scale. + +Here are some of the common ``overlay`` commands: + +.. code-block:: bash + + $ sudo wwctl overlay list -l + $ sudo wwctl overlay list -ls + $ sudo wwctl overlay edit default /etc/hello_world.ww + $ sudo wwctl overlay build -a + +Start the Warewulf daemon +------------------------- + +Once the above provisioning images are built, you can check the provisioning "rediness" and then begin booting nodes. + +.. code-block:: bash + + $ sudo wwctl ready + $ sudo wwctl server start + $ sudo wwctl server status + +Boot your compute node and watch it boot +---------------------------------------- diff --git a/docs/docs/getting-started/quickstart-el8.md b/docs/docs/getting-started/quickstart-el8.md new file mode 100644 index 00000000..eec46625 --- /dev/null +++ b/docs/docs/getting-started/quickstart-el8.md @@ -0,0 +1,160 @@ +--- +id: quickstart-el8 +title: Quick Start for RHEL, CentOS, and Rocky 8 +--- + +Install Warewulf and dependencies +================================= + +.. code-block:: bash + + $ sudo dnf groupinstall "Development Tools" + $ sudo dnf install epel-release + $ sudo dnf install golang tftp-server dhcp-server nfs-utils + + $ sudo systemctl stop firewalld + $ sudo systemctl disable firewalld + + $ git clone https://github.com/ctrliq/warewulf.git + $ cd warewulf + $ make all + $ sudo make install + +Configure the controller +========================= + +Edit the file ``/etc/warewulf/warewulf.conf`` and ensure that you've set the +appropriate configuration paramaters. Here are some of the defaults for reference: + +.. code-block:: yaml + + ipaddr: 192.168.1.1 + netmask: 255.255.255.0 + warewulf: + port: 9873 + secure: true + update interval: 60 + dhcp: + enabled: true + range start: 192.168.1.150 + range end: 192.168.1.200 + template: default + systemd name: dhcpd + tftp: + enabled: true + tftproot: /var/lib/tftpboot + systemd name: tftp + nfs: + systemd name: nfs-server + exports: + - /home + - /var/warewulf + +Configure system services automatically +======================================= + +.. code-block:: bash + + $ sudo wwctl configure dhcp # Create the default dhcpd.conf file and start/enable service + $ sudo wwctl configure tftp # Install the base tftp/PXE boot files and start/enable service + $ sudo wwctl configure nfs # Configure the exports and create an fstab in the default system overlay + $ sudo wwctl configure ssh # Build the basic ssh keys to be included by the default system overlay + + +Pull and build the VNFS container and kernel +============================================ + +This will pull a basic VNFS container from Docker Hub and import the default running +kernel from the controller node and set both in the "default" node profile. + +.. code-block:: bash + + $ sudo wwctl container import docker://warewulf/centos-8 centos-8 --setdefault + $ sudo wwctl kernel import $(uname -r) --setdefault + +Set up the default node profile +=============================== + +The ``--setdefault`` arguments above will automatically set those entries in the default +profile, but if you wanted to set them by hand to something different, you can do the +following: + +.. code-block:: bash + + $ sudo wwctl profile set default -K $(uname -r) -C centos-7 + +Next we set some default networking configurations for the first ethernet device. On +modern Linux distributions, the name of the device is not critical, as it will be setup +according to the HW address. Because all nodes will share the netmask and gateway +configuration, we can set them in the default profile as follows: + +.. code-block:: bash + + $ sudo wwctl profile set default --netdev eth0 -M 255.255.255.0 -G 192.168.1.1 + $ sudo wwctl profile list + +Add a node and build node specific overlays +=========================================== + +Adding nodes can be done while setting configurations in one command. Here we are setting +the IP address of ``eth0`` and setting this node to be discoverable, which will then +automatically have the HW address added to the configuration as the node boots. + +Node names must be unique. If you have node groups and/or multiple clusters, designate +them using dot notation. + +Note that the full node configuration comes from both cascading profiles and node +configurations which always supersede profile configurations. + +.. code-block:: bash + + $ sudo wwctl node add n0000.cluster --netdev eth0 -I 192.168.1.100 --discoverable + $ sudo wwctl node list -a n0000 + +Warewulf Overlays +================= + +There are two types of overlays: system and runtime overlays. + +System overlays are provisioned to the node before ``/sbin/init`` is called. This enables us +to prepopulate node configurations with content that is node specific like networking and +service configurations. + +Runtime overlays are provisioned after the node has booted and periodically during the +normal runtime of the node. Because these overlays are provisioned at periodic intervals, +they are very useful for content that changes, like users and groups. + +Overlays are generated from a template structure that is viewed using the ``wwctl overlay`` +commands. Files that end in the ``.ww`` suffix are templates and abide by standard +text/template rules. This supports loops, arrays, variables, and functions making overlays +extremely flexible. + +.. note:: + When using the overlay subsystem, system overlays are never shown by default. So when running ``overlay`` commands, you are always looking at runtime overlays unless the ``-s`` option is passed. + +All overlays are compiled before being provisioned. This accelerates the provisioning +process because there is less to do when nodes are being managed at scale. + +Here are some of the common ``overlay`` commands: + +.. code-block:: bash + + $ sudo wwctl overlay list -l + $ sudo wwctl overlay list -ls + $ sudo wwctl overlay edit default /etc/hello_world.ww + $ sudo wwctl overlay build -a + +Start the Warewulf daemon +------------------------- + +Once the above provisioning images are built, you can check the provisioning "rediness" +and then begin booting nodes. + +.. code-block:: bash + + $ sudo wwctl ready + $ sudo wwctl server start + $ sudo wwctl server status + +Boot your compute node and watch it boot +---------------------------------------- diff --git a/docs/docs/prerequisites/hardware.md b/docs/docs/prerequisites/hardware.md new file mode 100644 index 00000000..cea84032 --- /dev/null +++ b/docs/docs/prerequisites/hardware.md @@ -0,0 +1,4 @@ +--- +id: hardware +title: Hardware +--- diff --git a/docs/docs/prerequisites/software.md b/docs/docs/prerequisites/software.md new file mode 100644 index 00000000..d455b162 --- /dev/null +++ b/docs/docs/prerequisites/software.md @@ -0,0 +1,4 @@ +--- +id: software +title: Software +--- diff --git a/docs/docs/wwctl/configure.md b/docs/docs/wwctl/configure.md new file mode 100644 index 00000000..fbbfea2a --- /dev/null +++ b/docs/docs/wwctl/configure.md @@ -0,0 +1,48 @@ +--- +id: configure +title: wwctl configure +--- + +Commands in this command group manage and initialize services Warewulf depends on, based on the configuration in `warewulf.conf`. + +## dhcp +DHCP is a dependent service to Warewulf. This command will configure DHCP as defined. + +### -s, --show +Show configuration (don't update) + +### --persist +Persist the configuration and initialize the service + +## hosts +Write out the /etc/hosts file based on the Warewulf template (hosts.tmpl) in the Warewulf configuration directory. + +### -s, --show +Show configuration (don't update) + +### --persist +Persist the configuration and initialize the service + +## nfs +NFS is an optional dependent service of Warewulf, this tool will automatically configure NFS as per the configuration in the warewulf.conf file. + +### -s, --show +Show configuration (don't update) + +### --persist +Persist the configuration and initialize the service + +## ssh +SSH is an optionally dependent service for Warewulf, this tool will automatically setup the ssh keys nodes using the 'default' system overlay as well as user owned keys. + +### --persist +Persist the configuration and initialize the service + +## tftp +TFTP is a dependent service of Warewulf, this tool will enable the tftp services on your Warewulf master. + +### -s, --show +Show configuration (don't update) + +### --persist +Persist the configuration and initialize the service diff --git a/docs/docs/wwctl/container.md b/docs/docs/wwctl/container.md new file mode 100644 index 00000000..b952c899 --- /dev/null +++ b/docs/docs/wwctl/container.md @@ -0,0 +1,35 @@ +--- +id: container +title: wwctl container +--- + +Starting with version 4, Warewulf uses containers to build the bootable VNFS images for nodes to boot. These commands will help you import, management, and transform containers into bootable Warewulf VNFS images. + +build +~~~~~ +This command will build a bootable VNFS image from an imported container image. + +-a, --all (re)Build all VNFS images for all nodes +-f, --force Force rebuild, even if it isn't necessary +--setdefault Set this container for the default profile + +delete +~~~~~~ +This command will delete a container that has been imported into Warewulf. + +exec +~~~~ +This command will allow you to run any command inside of a given warewulf container. This is commonly used with an interactive shell such as ``/bin/bash`` to run a virtual environment within the container. + +imprt +~~~~~ +This command will pull and import a container into Warewulf so it can be used as a source to create a bootable VNFS image. + +-f, --force Force overwrite of an existing container +-u, --update Update and overwrite an existing container +-b, --build Build container when after pulling +--setdefault Set this container for the default profile + +list, ls +~~~~~~~~ +This command will show you the containers that are imported into Warewulf. diff --git a/docs/docs/wwctl/controller.md b/docs/docs/wwctl/controller.md new file mode 100644 index 00000000..1fb86e5f --- /dev/null +++ b/docs/docs/wwctl/controller.md @@ -0,0 +1,23 @@ +--- +id: controller +title: wwctl controller +--- + +Management of group settings and power management + +add +~~~ + +delete +~~~~~~ + +list +~~~~ +-a, --all Show all node configurations + +set +~~~ +-a, --all Set all controllers +-I, --ipaddr Set the controller's IP address +-F, --fqdn Set the controller's FQDN +-C, --comment Comments describing this controller diff --git a/docs/docs/wwctl/kernel.md b/docs/docs/wwctl/kernel.md new file mode 100644 index 00000000..36c72381 --- /dev/null +++ b/docs/docs/wwctl/kernel.md @@ -0,0 +1,19 @@ +--- +id: kernel +title: wwctl kernel +--- + +This command is for management of Warewulf Kernels to be used for bootstrapping nodes. + +imprt +~~~~~ +This will import a Kernel version from the control node into Warewulf for nodes to be configured to boot on. + +-a, --all Build all overlays (runtime and system) +-n, --node Build overlay for a particular node(s) +-r, --root Import kernel from root (chroot) directory +--setdefault Set this kernel for the default profile + +list, ls +~~~~~~~~ +This command will list the kernels that have been imported into Warewulf. diff --git a/docs/docs/wwctl/node.md b/docs/docs/wwctl/node.md new file mode 100644 index 00000000..c6464919 --- /dev/null +++ b/docs/docs/wwctl/node.md @@ -0,0 +1,79 @@ +--- +id: node +title: wwctl node +--- + +Management of node settings + +add +~~~ +This command will add a new node to Warewulf. + +-g, --group Group to add nodes to +-c, --controller Controller to add nodes to +-N, --netdevDefine the network device to configure +-I, --ipaddrSet the node's network device IP address +-M, --netmaskSet the node's network device netmask +-G, --gatewaySet the node's network device gateway +-H, --hwaddrSet the node's network device HW address +--discoverable Make this node discoverable + +console +~~~~~~~ +Start IPMI console for a singe node. + +delete +~~~~~~ +This command will remove a node from the Warewulf node configuration. + +-f, --force Force node delete +-g, --group Set group to delete nodes from +-c, --controller Controller to add nodes to + +list +~~~~ +This command will show you configured nodes. + +-n, --net Show node network configurations +-i, --ipmi Show node IPMI configurations +-a, --all Show all node configurations +-l, --long Show long or wide format + +sensors +~~~~~~~ +Show IPMI sensors for a single node. +-F, --full show detailed output + +set +~~~ +This command will allow you to set configuration properties for nodes. + +--comment Set a comment for this node +-C, --container Set the container (VNFS) for this node +-K, --kernel Set Kernel version for nodes +-A, --kernelargs Set Kernel argument for nodes +-c, --cluster Set the node's cluster group +-P, --ipxe Set the node's iPXE template name +-i, --init Define the init process to boot the container +--root Define the rootfs +-R, --runtime Set the node's runtime overlay +-S, --system Set the node's system overlay +--ipmi Set the node's IPMI IP address +--ipminetmask Set the node's IPMI netmask +--ipmigateway Set the node's IPMI gateway +--ipmiuser Set the node's IPMI username +--ipmipass Set the node's IPMI password +-p, --addprofile Add Profile(s) to node +-r, --delprofile Remove Profile(s) to node +-N, --netdev Define the network device to configure +-I, --ipaddr Set the node's network device IP address +-M, --netmask Set the node's network device netmask +-G, --gateway Set the node's network device gateway +-H, --hwaddr Set the node's network device HW address +--netdel Delete the node's network device +--netdefault Set this network to be default +-a, --all Set all nodes +-y, --yes Set 'yes' to all questions asked +-f, --force Force configuration (even on error) +--discoverable Make this node discoverable +--undiscoverable Remove the discoverable flag diff --git a/docs/docs/wwctl/overlay.md b/docs/docs/wwctl/overlay.md new file mode 100644 index 00000000..06b5e603 --- /dev/null +++ b/docs/docs/wwctl/overlay.md @@ -0,0 +1,76 @@ +--- +id: overlay +title: wwctl overlay +--- + +Management interface for Warewulf overlays + +build +~~~~~ +This command will build a system or runtime overlay. + +-s, --system Show System Overlays as well +-a, --all Build all overlays (runtime and system) + +chmod +~~~~~ +This command will allow you to change the permissions of a file within an overlay. + +-s, --system Show System Overlays as well +-n, --noupdate Don't update overlays + +create +~~~~~~ +This command will create a new empty overlay. + +-s, --system Show System Overlays as well +-n, --noupdate Don't update overlays + +delete +~~~~~~ +This command will delete files within an overlay or an entire overlay if no files are given to remove (use with caution). + +-s, --system Show system overlays instead of runtime +-f, --force Force deletion of a non-empty overlay +-p, --parents Remove empty parent directories +-n, --noupdate Don't update overlays + +edit +~~~~ +This command will allow you to edit or create a new file within a given overlay. Note: when creating files ending in a '.ww' suffix this will always be parsed as a Warewulf template file, and the suffix will be removed automatically + +-s, --system Show system overlays instead of runtime +-f, --files List files contained within a given overlay +-p, --parents Create any necessary parent directories +-m, --mode Permission mode for directory +-n, --noupdate Don't update overlays + +imprt +~~~~~ +This command will import a file into a given Warewulf overlay. + +-s, --system Show system overlays instead of runtime +-m, --mode Permission mode for directory +-n, --noupdate Don't update overlays + +list +~~~~ +This command will show you information about Warewulf overlays and the files contained within. + +-s, --system Show system overlays instead of runtime +-a, --all List the contents of overlays +-l, --long List 'long' of all overlay contents + +mkdir +~~~~~ +This command will allow you to create a new file within a given Warewulf overlay. + +-s, --system Show System Overlays as well +-m, --mode Permission mode for directory +-n, --noupdate Don't update overlays + +show +~~~~ +This command will output the contents of a file within a given + +-s, --system Show System Overlays as well diff --git a/docs/docs/wwctl/power.md b/docs/docs/wwctl/power.md new file mode 100644 index 00000000..10749b8c --- /dev/null +++ b/docs/docs/wwctl/power.md @@ -0,0 +1,22 @@ +--- +id: power +title: wwctl power +--- + +This command can control the power state of nodes. + +cycle +~~~~~ +This command will cycle the power for a given set of nodes. + +off +~~~ +This command will shutdown the power to a given set of nodes. + +on +~~ +This command will power on a given set of nodes. + +status +~~~~~~ +Show power status for the given node(s) diff --git a/docs/docs/wwctl/profile.md b/docs/docs/wwctl/profile.md new file mode 100644 index 00000000..256eb0ae --- /dev/null +++ b/docs/docs/wwctl/profile.md @@ -0,0 +1,46 @@ +--- +id: profile +title: wwctl profile +--- + +Management of node profile settings + +add +~~~ +This command will add a new node profile. + +delete +~~~~~~ +This command will delete a node profile. + +list +~~~~ +This command will list and show the profile configurations. + +set +~~~ +This command will allow you to set configuration properties for node profiles. + +--comment Set a comment for this node +-C, --container Set the container (VNFS) for this node +-K, --kernel Set Kernel version for nodes +-A, --kernelargs Set Kernel argument for nodes +-c, --cluster Set the node's cluster group +-P, --ipxe Set the node's iPXE template name +-i, --init Define the init process to boot the container +--root Define the rootfs +-R, --runtime Set the node's runtime overlay +-S, --system Set the node's system overlay +--ipminetmask Set the node's IPMI netmask +--ipmigateway Set the node's IPMI gateway +--ipmiuser Set the node's IPMI username +--ipmipass Set the node's IPMI password +-N, --netdev Define the network device to configure +-I, --ipaddr Set the node's network device IP address +-M, --netmask Set the node's network device netmask +-G, --gateway Set the node's network device gateway +-H, --hwaddr Set the node's network device HW address +--netdel Delete the node's network device +--netdefault Set this network to be default +-a, --all Set all profiles +-f, --force Force configuration (even on error) diff --git a/docs/docs/wwctl/ready.md b/docs/docs/wwctl/ready.md new file mode 100644 index 00000000..68d9bc39 --- /dev/null +++ b/docs/docs/wwctl/ready.md @@ -0,0 +1,6 @@ +--- +id: ready +title: wwctl ready +--- + +Warewulf Status Check diff --git a/docs/docs/wwctl/server.md b/docs/docs/wwctl/server.md new file mode 100644 index 00000000..387ef162 --- /dev/null +++ b/docs/docs/wwctl/server.md @@ -0,0 +1,20 @@ +--- +id: server +title: wwctl server +--- + +This command will allow you to control the Warewulf daemon process. + +start +~~~~~ +Start Warewulf server + +-f, --foreground Run daemon process in the foreground + +status +~~~~~~ +Warewulf server status + +stop +~~~~ +Stop Warewulf server diff --git a/docs/docs/wwctl/wwctl.md b/docs/docs/wwctl/wwctl.md new file mode 100644 index 00000000..05890f6a --- /dev/null +++ b/docs/docs/wwctl/wwctl.md @@ -0,0 +1,30 @@ +--- +id: wwctl +title: wwctl +slug: /wwctl +--- + +Control interface to the Cluster Warewulf Provisioning System + +## Synopsis + +`wwctl [GLOBAL OPTIONS] [SUBCOMMAND]` + +## Options + +### -d, --debug +Run with debugging messages enabled + +### -v, --verbose +Run with increased verbosity + +## Environment + +### WAREWULF_OCI_NOHTTPS +Skip TLS verification when connecting to an OCI registry + +### WAREWULF_OCI_PASSWORD +OCI registry password used in `wwctl container imprt` + +### WAREWULF_OCI_USERNAME +OCI registry username used in `wwctl container imprt` diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js new file mode 100644 index 00000000..45d43722 --- /dev/null +++ b/docs/docusaurus.config.js @@ -0,0 +1,97 @@ +module.exports = { + title: 'Warewulf', + tagline: 'A stateless and diskless container operating system provisioning system for large clusters of bare metal and/or virtual systems.', + url: 'https://ctrliq.github.io', + baseUrl: '/warewulf/', + onBrokenLinks: 'throw', + onBrokenMarkdownLinks: 'warn', + favicon: 'img/favicon.png', + organizationName: 'ctrliq', + projectName: 'warewulf', + themeConfig: { + navbar: { + title: 'Warewulf', + logo: { + alt: 'Warewulf Logo', + src: 'img/logo.png', + }, + items: [ + { + to: 'docs/', + activeBasePath: 'docs', + label: 'Docs', + position: 'left', + }, + {to: 'news', label: 'News', position: 'left'}, + { + href: 'https://github.com/ctrliq/warewulf', + label: 'GitHub', + position: 'right', + }, + ], + }, + footer: { + style: 'light', + links: [ + { + title: 'Docs', + items: [ + { + label: 'Getting Started', + to: 'docs', + }, + ], + }, + { + title: 'Community', + items: [ + { + label: 'Slack', + href: 'https://join.slack.com/t/hpcng/shared_invite/zt-ll5c3ofb-XhvMPbXUhTVrHlutQz2jbA', + }, + { + label: 'YouTube', + href: 'https://www.youtube.com/channel/UCQbKq1vIffqRAMUDPfHcU0w', + }, + ], + }, + { + title: 'More', + items: [ + { + label: 'News', + to: 'news', + }, + { + label: 'GitHub', + href: 'https://github.com/ctrliq/warewulf', + }, + ], + }, + ], + copyright: `Copyright © ${new Date().getFullYear()} Ctrl IQ, Inc. - All Rights Reserved.`, + }, + }, + presets: [ + [ + '@docusaurus/preset-classic', + { + docs: { + sidebarPath: require.resolve('./sidebars.js'), + editUrl: + 'https://github.com/ctrliq/warewulf-docs/edit/master/', + }, + blog: { + showReadingTime: true, + editUrl: + 'https://github.com/ctrliq/warewulf-docs/edit/master/blog/', + routeBasePath: 'news', + path: './news', + }, + theme: { + customCss: require.resolve('./src/css/custom.css'), + }, + }, + ], + ], +}; diff --git a/docs/news/2021-02-25-4.0.0.md b/docs/news/2021-02-25-4.0.0.md new file mode 100644 index 00000000..af61e69e --- /dev/null +++ b/docs/news/2021-02-25-4.0.0.md @@ -0,0 +1,11 @@ +--- +slug: 4.0.0 +title: 4.0.0 +author: Brian Clemens +author_title: Solutions Architect @ Ctrl IQ +author_url: https://github.com/brianclemens +author_image_url: https://avatars.githubusercontent.com/u/13581364?s=400&v=4 +tags: [changelog, release] +--- + +This is a placeholder for the announcement that will come with the first release of Warewulf v4 diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 00000000..678c543f --- /dev/null +++ b/docs/package.json @@ -0,0 +1,34 @@ +{ + "name": "warewulf-docs", + "version": "0.0.0", + "private": true, + "scripts": { + "docusaurus": "docusaurus", + "start": "docusaurus start", + "build": "docusaurus build", + "swizzle": "docusaurus swizzle", + "deploy": "docusaurus deploy", + "serve": "docusaurus serve", + "clear": "docusaurus clear" + }, + "dependencies": { + "@docusaurus/core": "2.0.0-alpha.70", + "@docusaurus/preset-classic": "2.0.0-alpha.70", + "@mdx-js/react": "^1.6.21", + "clsx": "^1.1.1", + "react": "^16.8.4", + "react-dom": "^16.8.4" + }, + "browserslist": { + "production": [ + ">0.5%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} \ No newline at end of file diff --git a/docs/sidebars.js b/docs/sidebars.js new file mode 100644 index 00000000..3b403ace --- /dev/null +++ b/docs/sidebars.js @@ -0,0 +1,40 @@ +module.exports = { + someSidebar: { + 'About': [ + 'about/introduction', + 'about/architecture', + 'about/security', + 'about/templating', + ], + 'Prerequisites': [ + 'prerequisites/hardware', + 'prerequisites/software', + ], + 'Getting Started': [ + 'getting-started/quickstart-el7', + 'getting-started/quickstart-el8', + ], + 'wwctl': [ + 'wwctl/configure', + 'wwctl/container', + 'wwctl/controller', + 'wwctl/kernel', + 'wwctl/node', + 'wwctl/overlay', + 'wwctl/power', + 'wwctl/profile', + 'wwctl/ready', + 'wwctl/server', + 'wwctl/wwctl', + ], + 'Contributing': [ + 'contributing/contributing', + 'contributing/documentation', + 'contributing/development-environment-kvm', + 'contributing/development-environment-vbox', + ], + 'Appendix': [ + 'appendix/glossary', + ], + }, +}; diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css new file mode 100644 index 00000000..39c4b44c --- /dev/null +++ b/docs/src/css/custom.css @@ -0,0 +1,25 @@ +/* stylelint-disable docusaurus/copyright-header */ +/** + * Any CSS included here will be global. The classic template + * bundles Infima by default. Infima is a CSS framework designed to + * work well for content-centric websites. + */ + +/* You can override the default Infima variables here. */ +:root { + --ifm-color-primary: #229529; + --ifm-color-primary-dark: #1f8625; + --ifm-color-primary-darker: #1d7f23; + --ifm-color-primary-darkest: #18681d; + --ifm-color-primary-light: #25a42d; + --ifm-color-primary-lighter: #27ab2f; + --ifm-color-primary-lightest: #2cc235; + --ifm-code-font-size: 95%; +} + +.docusaurus-highlight-code-line { + background-color: rgb(72, 77, 91); + display: block; + margin: 0 calc(-1 * var(--ifm-pre-padding)); + padding: 0 var(--ifm-pre-padding); +} diff --git a/docs/src/pages/index.js b/docs/src/pages/index.js new file mode 100644 index 00000000..b4ebb1e0 --- /dev/null +++ b/docs/src/pages/index.js @@ -0,0 +1,94 @@ +import React from 'react'; +import clsx from 'clsx'; +import Layout from '@theme/Layout'; +import Link from '@docusaurus/Link'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import useBaseUrl from '@docusaurus/useBaseUrl'; +import styles from './styles.module.css'; + +const features = [ + { + title: 'Lightweight', + imageUrl: 'img/logo.png', + description: ( + <> + Warewulf needs to do its job and stay out of the way. There should be no underlying system dependencies, changes or “stack” for the controller or worker nodes. + + ), + }, + { + title: 'Simple', + imageUrl: 'img/logo.png', + description: ( + <> + Warewulf is used by hobbyists, researchers, scientists, engineers and systems administrators. This means that Warewulf must be simple to use and understand. + + ), + }, + { + title: 'Flexible', + imageUrl: 'img/logo.png', + description: ( + <> + Warewulf is highly flexible and can address the needs of any environment– from a computer lab with graphical workstations, to under-the-desk clusters, to massive supercomputing centers providing traditional HPC capabilities to thousands of users. + + ), + }, +]; + +function Feature({imageUrl, title, description}) { + const imgUrl = useBaseUrl(imageUrl); + return ( +
+ {imgUrl && ( +
+ {title} +
+ )} +

{title}

+

{description}

+
+ ); +} + +function Home() { + const context = useDocusaurusContext(); + const {siteConfig = {}} = context; + return ( + +
+
+

{siteConfig.title}

+

{siteConfig.tagline}

+
+ + Get Started + +
+
+
+
+ {features && features.length > 0 && ( +
+
+
+ {features.map((props, idx) => ( + + ))} +
+
+
+ )} +
+
+ ); +} + +export default Home; diff --git a/docs/src/pages/styles.module.css b/docs/src/pages/styles.module.css new file mode 100644 index 00000000..c1aa8512 --- /dev/null +++ b/docs/src/pages/styles.module.css @@ -0,0 +1,37 @@ +/* stylelint-disable docusaurus/copyright-header */ + +/** + * CSS files with the .module.css suffix will be treated as CSS modules + * and scoped locally. + */ + +.heroBanner { + padding: 4rem 0; + text-align: center; + position: relative; + overflow: hidden; +} + +@media screen and (max-width: 966px) { + .heroBanner { + padding: 2rem; + } +} + +.buttons { + display: flex; + align-items: center; + justify-content: center; +} + +.features { + display: flex; + align-items: center; + padding: 2rem 0; + width: 100%; +} + +.featureImage { + height: 200px; + width: 200px; +} diff --git a/docs/static/.nojekyll b/docs/static/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/docs/static/img/favicon.png b/docs/static/img/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..e67fff16a333531e343301eddf8438682da9ea18 GIT binary patch literal 18706 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 D8&e8Z literal 0 HcmV?d00001 diff --git a/docs/static/img/logo.png b/docs/static/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f4f62649caa93105df4efd2d17f6f4a84aa6de9f GIT binary patch literal 51914 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 z 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 literal 0 HcmV?d00001 From 3a4b83224d6b3c8bccc962e2c025ff731c68b933 Mon Sep 17 00:00:00 2001 From: Brian Clemens Date: Wed, 3 Mar 2021 07:56:48 +0900 Subject: [PATCH 10/30] fixup! add docusaurus docs --- .github/workflows/documentation.yml | 66 +++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/documentation.yml diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 00000000..044c8abd --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,66 @@ +--- +name: documentation + +on: + pull_request: + branches: [main] + push: + branches: [main] + +jobs: + checks: + if: github.event_name != 'push' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: '12.x' + - name: Test Build + run: | + cd docs + if [ -e yarn.lock ]; then + yarn install --frozen-lockfile + elif [ -e package-lock.json ]; then + npm ci + else + npm i + fi + npm run build + gh-release: + if: github.event_name != 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: '12.x' + - name: Add key to allow access to repository + env: + SSH_AUTH_SOCK: /tmp/ssh_agent.sock + run: | + mkdir -p ~/.ssh + ssh-keyscan github.com >> ~/.ssh/known_hosts + echo "${{ secrets.GH_PAGES_DEPLOY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + cat <> ~/.ssh/config + Host github.com + HostName github.com + IdentityFile ~/.ssh/id_rsa + EOT + - name: Release to GitHub Pages + env: + USE_SSH: true + GIT_USER: git + run: | + git config --global user.email "actions@gihub.com" + git config --global user.name "gh-actions" + cd docs + if [ -e yarn.lock ]; then + yarn install --frozen-lockfile + elif [ -e package-lock.json ]; then + npm ci + else + npm i + fi + npx docusaurus deploy From 03b09b922181b4183eb5b27c0162d8c58a11965d Mon Sep 17 00:00:00 2001 From: Brian Clemens Date: Wed, 3 Mar 2021 13:46:50 +0900 Subject: [PATCH 11/30] correct repo url --- docs/docusaurus.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 45d43722..4b254958 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -79,12 +79,12 @@ module.exports = { docs: { sidebarPath: require.resolve('./sidebars.js'), editUrl: - 'https://github.com/ctrliq/warewulf-docs/edit/master/', + 'https://github.com/ctrliq/warewulf/edit/main/docs/', }, blog: { showReadingTime: true, editUrl: - 'https://github.com/ctrliq/warewulf-docs/edit/master/blog/', + 'https://github.com/ctrliq/warewulf/edit/main/docs/blog/', routeBasePath: 'news', path: './news', }, From 8c969f06b3d2f6ebdd6d686797dd33600ce528bd Mon Sep 17 00:00:00 2001 From: Brian Clemens Date: Wed, 3 Mar 2021 22:24:09 +0900 Subject: [PATCH 12/30] reformat el7 quickstart --- docs/docs/getting-started/quickstart-el7.md | 178 ++++++++++---------- docs/package.json | 2 +- 2 files changed, 87 insertions(+), 93 deletions(-) diff --git a/docs/docs/getting-started/quickstart-el7.md b/docs/docs/getting-started/quickstart-el7.md index f2da5240..16af545b 100644 --- a/docs/docs/getting-started/quickstart-el7.md +++ b/docs/docs/getting-started/quickstart-el7.md @@ -3,136 +3,130 @@ id: quickstart-el7 title: Quick Start for RHEL and CentOS 7 --- -Install Warewulf and dependencies -================================= +## Install Warewulf and dependencies -.. code-block:: bash +```bash +$ sudo yum install epel-release +$ sudo yum install golang tftp-server dhcp nfs-utils - $ sudo yum install epel-release - $ sudo yum install golang tftp-server dhcp nfs-utils +$ sudo systemctl stop firewalld +$ sudo systemctl disable firewalld - $ sudo systemctl stop firewalld - $ sudo systemctl disable firewalld +$ git clone https://github.com/ctrliq/warewulf.git +$ cd warewulf +$ make all +$ sudo make install +``` - $ git clone https://github.com/ctrliq/warewulf.git - $ cd warewulf - $ make all - $ sudo make install +## Configure the controller -Configure the controller -======================== +Edit the file `/etc/warewulf/warewulf.conf` and ensure that you've set the appropriate configuration paramaters. Here are some of the defaults for reference: -Edit the file ``/etc/warewulf/warewulf.conf`` and ensure that you've set the appropriate configuration paramaters. Here are some of the defaults for reference: +```yaml +ipaddr: 192.168.1.1 +netmask: 255.255.255.0 +warewulf: + port: 9873 + secure: true + update interval: 60 +dhcp: + enabled: true + range start: 192.168.1.150 + range end: 192.168.1.200 + template: default + systemd name: dhcpd +tftp: + enabled: true + tftproot: /var/lib/tftpboot + systemd name: tftp +nfs: + systemd name: nfs-server + exports: + - /home + - /var/warewulf +``` -.. code-block:: yaml +## Configure system services automatically - ipaddr: 192.168.1.1 - netmask: 255.255.255.0 - warewulf: - port: 9873 - secure: true - update interval: 60 - dhcp: - enabled: true - range start: 192.168.1.150 - range end: 192.168.1.200 - template: default - systemd name: dhcpd - tftp: - enabled: true - tftproot: /var/lib/tftpboot - systemd name: tftp - nfs: - systemd name: nfs-server - exports: - - /home - - /var/warewulf +```bash +$ sudo wwctl configure dhcp # Create the default dhcpd.conf file and start/enable service +$ sudo wwctl configure tftp # Install the base tftp/PXE boot files and start/enable service +$ sudo wwctl configure nfs # Configure the exports and create an fstab in the default system overlay +$ sudo wwctl configure ssh # Build the basic ssh keys to be included by the default system overlay +``` -Configure system services automatically -======================================= - -.. code-block:: bash - - $ sudo wwctl configure dhcp # Create the default dhcpd.conf file and start/enable service - $ sudo wwctl configure tftp # Install the base tftp/PXE boot files and start/enable service - $ sudo wwctl configure nfs # Configure the exports and create an fstab in the default system overlay - $ sudo wwctl configure ssh # Build the basic ssh keys to be included by the default system overlay - -Pull and build the VNFS container and kernel -============================================ +## Pull and build the VNFS container and kernel This will pull a basic VNFS container from Docker Hub and import the default running kernel from the controller node and set both in the "default" node profile. -.. code-block:: bash +```bash +$ sudo wwctl container import docker://warewulf/centos-7 centos-7 --setdefault +$ sudo wwctl kernel import $(uname -r) --setdefault +``` - $ sudo wwctl container import docker://warewulf/centos-7 centos-7 --setdefault - $ sudo wwctl kernel import $(uname -r) --setdefault +## Set up the default node profile -Set up the default node profile -=============================== +The `--setdefault` arguments above will automatically set those entries in the default profile, but if you wanted to set them by hand to something different, you can do the following: -The ``--setdefault`` arguments above will automatically set those entries in the default profile, but if you wanted to set them by hand to something different, you can do the following: - -.. code-block:: bash - - $ sudo wwctl profile set default -K $(uname -r) -C centos-7 +```bash +$ sudo wwctl profile set default -K $(uname -r) -C centos-7 +``` Next we set some default networking configurations for the first ethernet device. On modern Linux distributions, the name of the device is not critical, as it will be setup according to the HW address. Because all nodes will share the netmask and gateway configuration, we can set them in the default profile as follows: -.. code-block:: bash +```bash +$ sudo wwctl profile set default --netdev eth0 -M 255.255.255.0 -G 192.168.1.1 +$ sudo wwctl profile list +``` - $ sudo wwctl profile set default --netdev eth0 -M 255.255.255.0 -G 192.168.1.1 - $ sudo wwctl profile list +## Add a node and build node specific overlays -Add a node and build node specific overlays -=========================================== - -Adding nodes can be done while setting configurations in one command. Here we are setting the IP address of ``eth0`` and setting this node to be discoverable, which will then automatically have the HW address added to the configuration as the node boots. +Adding nodes can be done while setting configurations in one command. Here we are setting the IP address of `eth0` and setting this node to be discoverable, which will then automatically have the HW address added to the configuration as the node boots. Node names must be unique. If you have node groups and/or multiple clusters, designate them using dot notation. Note that the full node configuration comes from both cascading profiles and node configurations which always supersede profile configurations. -.. code-block:: bash +```bash +$ sudo wwctl node add n0000.cluster --netdev eth0 -I 192.168.1.100 --discoverable +$ sudo wwctl node list -a n0000 +``` - $ sudo wwctl node add n0000.cluster --netdev eth0 -I 192.168.1.100 --discoverable - $ sudo wwctl node list -a n0000 - -Warewulf Overlays -================= +## Warewulf Overlays There are two types of overlays: system and runtime overlays. -System overlays are provisioned to the node before ``/sbin/init`` is called. This enables us to prepopulate node configurations with content that is node specific like networking and service configurations. +System overlays are provisioned to the node before `/sbin/init` is called. This enables us to prepopulate node configurations with content that is node specific like networking and service configurations. Runtime overlays are provisioned after the node has booted and periodically during the normal runtime of the node. Because these overlays are provisioned at periodic intervals, they are very useful for content that changes, like users and groups. -Overlays are generated from a template structure that is viewed using the ``wwctl overlay`` commands. Files that end in the ``.ww`` suffix are templates and abide by standard text/template rules. This supports loops, arrays, variables, and functions making overlays extremely flexible. +Overlays are generated from a template structure that is viewed using the `wwctl overlay` commands. Files that end in the ``.ww`` suffix are templates and abide by standard text/template rules. This supports loops, arrays, variables, and functions making overlays extremely flexible. -.. note:: - When using the overlay subsystem, system overlays are never shown by default. So when running ``overlay`` commands, you are always looking at runtime overlays unless the ``-s`` option is passed. +::: note + +When using the overlay subsystem, system overlays are never shown by default. So when running `overlay` commands, you are always looking at runtime overlays unless the `-s` option is passed. + +::: All overlays are compiled before being provisioned. This accelerates the provisioning process because there is less to do when nodes are being managed at scale. -Here are some of the common ``overlay`` commands: +Here are some of the common `overlay` commands: -.. code-block:: bash +```bash +$ sudo wwctl overlay list -l +$ sudo wwctl overlay list -ls +$ sudo wwctl overlay edit default /etc/hello_world.ww +$ sudo wwctl overlay build -a +``` - $ sudo wwctl overlay list -l - $ sudo wwctl overlay list -ls - $ sudo wwctl overlay edit default /etc/hello_world.ww - $ sudo wwctl overlay build -a +## Start the Warewulf daemon -Start the Warewulf daemon -------------------------- +Once the above provisioning images are built, you can check the provisioning readiness and then begin booting nodes. -Once the above provisioning images are built, you can check the provisioning "rediness" and then begin booting nodes. +```bash +$ sudo wwctl ready +$ sudo wwctl server start +$ sudo wwctl server status +``` -.. code-block:: bash - - $ sudo wwctl ready - $ sudo wwctl server start - $ sudo wwctl server status - -Boot your compute node and watch it boot ----------------------------------------- +## Boot your compute node and watch it boot diff --git a/docs/package.json b/docs/package.json index 678c543f..e881cd4b 100644 --- a/docs/package.json +++ b/docs/package.json @@ -31,4 +31,4 @@ "last 1 safari version" ] } -} \ No newline at end of file +} From 7063b66352ae4256a3e044ffed67610797697d14 Mon Sep 17 00:00:00 2001 From: Brian Clemens Date: Wed, 3 Mar 2021 22:28:07 +0900 Subject: [PATCH 13/30] fixup! reformat el7 quickstart --- docs/docs/getting-started/quickstart-el7.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/docs/getting-started/quickstart-el7.md b/docs/docs/getting-started/quickstart-el7.md index 16af545b..463745a8 100644 --- a/docs/docs/getting-started/quickstart-el7.md +++ b/docs/docs/getting-started/quickstart-el7.md @@ -102,10 +102,8 @@ Runtime overlays are provisioned after the node has booted and periodically duri Overlays are generated from a template structure that is viewed using the `wwctl overlay` commands. Files that end in the ``.ww`` suffix are templates and abide by standard text/template rules. This supports loops, arrays, variables, and functions making overlays extremely flexible. -::: note - +:::note When using the overlay subsystem, system overlays are never shown by default. So when running `overlay` commands, you are always looking at runtime overlays unless the `-s` option is passed. - ::: All overlays are compiled before being provisioned. This accelerates the provisioning process because there is less to do when nodes are being managed at scale. From 547dd0cabd3517ec6d77e6d159a94a805e7247e0 Mon Sep 17 00:00:00 2001 From: Brian Clemens Date: Wed, 3 Mar 2021 22:31:47 +0900 Subject: [PATCH 14/30] trigger document generation only when docs modified --- .github/workflows/documentation.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 044c8abd..fddbdf40 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -3,9 +3,15 @@ name: documentation on: pull_request: - branches: [main] + branches: + - main + paths: + - 'docs/**' push: - branches: [main] + branches: + - main + paths: + - 'docs/**' jobs: checks: From c9d6e1ceca400cd000680573f5786bc7585bf481 Mon Sep 17 00:00:00 2001 From: Brian Clemens Date: Wed, 3 Mar 2021 22:37:29 +0900 Subject: [PATCH 15/30] limit unnecessary testing --- .github/workflows/documentation.yml | 6 ++---- .github/workflows/test.yml | 8 ++++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index fddbdf40..40e224cd 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -3,13 +3,11 @@ name: documentation on: pull_request: - branches: - - main + branches: [main] paths: - 'docs/**' push: - branches: - - main + branches: [main] paths: - 'docs/**' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1f412750..d3df6f94 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,9 +3,13 @@ name: test on: push: - branches: [ main ] + branches: [main] + paths-ignore: + - 'docs/**' pull_request: - branches: [ main ] + branches: [main] + paths-ignore: + - 'docs/**' jobs: From 70ebfdbf7047b2dacef48c00fe2e08738e3525ec Mon Sep 17 00:00:00 2001 From: Brian Clemens Date: Wed, 3 Mar 2021 23:04:39 +0900 Subject: [PATCH 16/30] reformat el8 quickstart --- docs/docs/getting-started/quickstart-el8.md | 162 ++++++++++---------- 1 file changed, 77 insertions(+), 85 deletions(-) diff --git a/docs/docs/getting-started/quickstart-el8.md b/docs/docs/getting-started/quickstart-el8.md index eec46625..a63e416f 100644 --- a/docs/docs/getting-started/quickstart-el8.md +++ b/docs/docs/getting-started/quickstart-el8.md @@ -3,98 +3,92 @@ id: quickstart-el8 title: Quick Start for RHEL, CentOS, and Rocky 8 --- -Install Warewulf and dependencies -================================= +## Install Warewulf and dependencies -.. code-block:: bash +```bash +$ sudo dnf groupinstall "Development Tools" +$ sudo dnf install epel-release +$ sudo dnf install golang tftp-server dhcp-server nfs-utils - $ sudo dnf groupinstall "Development Tools" - $ sudo dnf install epel-release - $ sudo dnf install golang tftp-server dhcp-server nfs-utils +$ sudo systemctl stop firewalld +$ sudo systemctl disable firewalld - $ sudo systemctl stop firewalld - $ sudo systemctl disable firewalld +$ git clone https://github.com/ctrliq/warewulf.git +$ cd warewulf +$ make all +$ sudo make install +``` - $ git clone https://github.com/ctrliq/warewulf.git - $ cd warewulf - $ make all - $ sudo make install - -Configure the controller -========================= +## Configure the controller Edit the file ``/etc/warewulf/warewulf.conf`` and ensure that you've set the appropriate configuration paramaters. Here are some of the defaults for reference: -.. code-block:: yaml +```yaml +ipaddr: 192.168.1.1 +netmask: 255.255.255.0 +warewulf: + port: 9873 + secure: true + update interval: 60 +dhcp: + enabled: true + range start: 192.168.1.150 + range end: 192.168.1.200 + template: default + systemd name: dhcpd +tftp: + enabled: true + tftproot: /var/lib/tftpboot + systemd name: tftp +nfs: + systemd name: nfs-server + exports: + - /home + - /var/warewulf +``` - ipaddr: 192.168.1.1 - netmask: 255.255.255.0 - warewulf: - port: 9873 - secure: true - update interval: 60 - dhcp: - enabled: true - range start: 192.168.1.150 - range end: 192.168.1.200 - template: default - systemd name: dhcpd - tftp: - enabled: true - tftproot: /var/lib/tftpboot - systemd name: tftp - nfs: - systemd name: nfs-server - exports: - - /home - - /var/warewulf +## Configure system services automatically -Configure system services automatically -======================================= - -.. code-block:: bash - - $ sudo wwctl configure dhcp # Create the default dhcpd.conf file and start/enable service - $ sudo wwctl configure tftp # Install the base tftp/PXE boot files and start/enable service - $ sudo wwctl configure nfs # Configure the exports and create an fstab in the default system overlay - $ sudo wwctl configure ssh # Build the basic ssh keys to be included by the default system overlay +```bash +$ sudo wwctl configure dhcp # Create the default dhcpd.conf file and start/enable service +$ sudo wwctl configure tftp # Install the base tftp/PXE boot files and start/enable service +$ sudo wwctl configure nfs # Configure the exports and create an fstab in the default system overlay +$ sudo wwctl configure ssh # Build the basic ssh keys to be included by the default system overlay +``` -Pull and build the VNFS container and kernel -============================================ +## Pull and build the VNFS container and kernel This will pull a basic VNFS container from Docker Hub and import the default running kernel from the controller node and set both in the "default" node profile. -.. code-block:: bash +```bash +$ sudo wwctl container import docker://warewulf/centos-8 centos-8 --setdefault +$ sudo wwctl kernel import $(uname -r) --setdefault +``` - $ sudo wwctl container import docker://warewulf/centos-8 centos-8 --setdefault - $ sudo wwctl kernel import $(uname -r) --setdefault - -Set up the default node profile -=============================== +## Set up the default node profile The ``--setdefault`` arguments above will automatically set those entries in the default profile, but if you wanted to set them by hand to something different, you can do the following: -.. code-block:: bash - - $ sudo wwctl profile set default -K $(uname -r) -C centos-7 +```bash +$ sudo wwctl profile set default -K $(uname -r) -C centos-7 +``` Next we set some default networking configurations for the first ethernet device. On modern Linux distributions, the name of the device is not critical, as it will be setup according to the HW address. Because all nodes will share the netmask and gateway configuration, we can set them in the default profile as follows: -.. code-block:: bash +```bash +$ sudo wwctl profile set default --netdev eth0 -M 255.255.255.0 -G 192.168.1.1 +$ sudo wwctl profile list +``` - $ sudo wwctl profile set default --netdev eth0 -M 255.255.255.0 -G 192.168.1.1 - $ sudo wwctl profile list - -Add a node and build node specific overlays -=========================================== +## Add a node and build node specific overlays Adding nodes can be done while setting configurations in one command. Here we are setting the IP address of ``eth0`` and setting this node to be discoverable, which will then @@ -106,13 +100,12 @@ them using dot notation. Note that the full node configuration comes from both cascading profiles and node configurations which always supersede profile configurations. -.. code-block:: bash +```bash +$ sudo wwctl node add n0000.cluster --netdev eth0 -I 192.168.1.100 --discoverable +$ sudo wwctl node list -a n0000 +``` - $ sudo wwctl node add n0000.cluster --netdev eth0 -I 192.168.1.100 --discoverable - $ sudo wwctl node list -a n0000 - -Warewulf Overlays -================= +## Warewulf Overlays There are two types of overlays: system and runtime overlays. @@ -129,32 +122,31 @@ commands. Files that end in the ``.ww`` suffix are templates and abide by standa text/template rules. This supports loops, arrays, variables, and functions making overlays extremely flexible. -.. note:: +:::note When using the overlay subsystem, system overlays are never shown by default. So when running ``overlay`` commands, you are always looking at runtime overlays unless the ``-s`` option is passed. +::: All overlays are compiled before being provisioned. This accelerates the provisioning process because there is less to do when nodes are being managed at scale. Here are some of the common ``overlay`` commands: -.. code-block:: bash +```bash +$ sudo wwctl overlay list -l +$ sudo wwctl overlay list -ls +$ sudo wwctl overlay edit default /etc/hello_world.ww +$ sudo wwctl overlay build -a +``` - $ sudo wwctl overlay list -l - $ sudo wwctl overlay list -ls - $ sudo wwctl overlay edit default /etc/hello_world.ww - $ sudo wwctl overlay build -a - -Start the Warewulf daemon -------------------------- +## Start the Warewulf daemon Once the above provisioning images are built, you can check the provisioning "rediness" and then begin booting nodes. -.. code-block:: bash +```bash +$ sudo wwctl ready +$ sudo wwctl server start +$ sudo wwctl server status +``` - $ sudo wwctl ready - $ sudo wwctl server start - $ sudo wwctl server status - -Boot your compute node and watch it boot ----------------------------------------- +## Boot your compute node and watch it boot From 8094e95eee8b8041419864971aeb79152d7cf78a Mon Sep 17 00:00:00 2001 From: Brian Clemens Date: Thu, 4 Mar 2021 00:13:21 +0900 Subject: [PATCH 17/30] add download page --- docs/src/pages/download.md | 27 +++++++++++++++++++++++++++ docs/src/pages/index.js | 8 ++++++++ 2 files changed, 35 insertions(+) create mode 100644 docs/src/pages/download.md diff --git a/docs/src/pages/download.md b/docs/src/pages/download.md new file mode 100644 index 00000000..40f6d36b --- /dev/null +++ b/docs/src/pages/download.md @@ -0,0 +1,27 @@ +--- +title: Download +hide_table_of_contents: false +--- +# Download +Warewulf is available in the [Ctrl IQ Repository](https://repo.ctrliq.com) for several RPM based Linux distributions. For convenience, direct download links are also listed here. + +## Enterprise Linux 7 +Compatible with RHEL, CentOS, and Oracle Linux + +| Architecture | Version | Download +| --- | --- | --- +| x86_64 | 4.0.0 | https://repo.ctrliq.com/rhel/7/stable/x86_64/warewulf-4.0.0-1.el7.x86_64.rpm +| aarch64 | 4.0.0 | Available soon + +## Enterprise Linux 8 +Compatible with RHEL, CentOS, Oracle, and Rocky Linux + +| Architecture | Version | Download +| --- | --- | --- +| x86_64 | 4.0.0 | https://repo.ctrliq.com/rhel/8/stable/x86_64/warewulf-4.0.0-1.el7.x86_64.rpm +| aarch64 | 4.0.0 | Available soon + + +--- + +Ctrl IQ packages are signed with the Ctrl IQ release key, [E97E 8F85 D71A B47F](https://repo.ctrliq.com/gpg). diff --git a/docs/src/pages/index.js b/docs/src/pages/index.js index b4ebb1e0..94dcf315 100644 --- a/docs/src/pages/index.js +++ b/docs/src/pages/index.js @@ -71,6 +71,14 @@ function Home() { to={useBaseUrl('docs/')}> Get Started + + Download + From da192d95b662b6f789d286cd9d309b82b7fc4eee Mon Sep 17 00:00:00 2001 From: Brian Clemens Date: Thu, 4 Mar 2021 19:03:45 +0900 Subject: [PATCH 18/30] Fix systemd unit failing because of missing warewulf group --- Makefile | 5 +++-- include/systemd/warewulfd.service | 2 +- warewulf.spec.in | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 5eb72180..61f27956 100644 --- a/Makefile +++ b/Makefile @@ -65,8 +65,9 @@ files: all # restorecon -r /var/lib/tftpboot/warewulf services: files -# sudo systemctl enable tftp -# sudo systemctl restart tftp + groupadd -r warewulf + systemctl daemon-reload + systemctl enable --now warewulfd wwctl: cd cmd/wwctl; go build -mod vendor -tags "$(WW_BUILD_GO_BUILD_TAGS)" -o ../../wwctl diff --git a/include/systemd/warewulfd.service b/include/systemd/warewulfd.service index 83e6954c..54fc4458 100644 --- a/include/systemd/warewulfd.service +++ b/include/systemd/warewulfd.service @@ -1,6 +1,6 @@ [Unit] Description=Warewulf cluster management daemon -Documentation=https://warewulf.readthedocs.io +Documentation=https://docs.ctrliq.com/warewulf After=network-online.target AssertFileIsExecutable=/usr/bin/wwctl diff --git a/warewulf.spec.in b/warewulf.spec.in index d41ccfec..8f8ff836 100644 --- a/warewulf.spec.in +++ b/warewulf.spec.in @@ -24,7 +24,7 @@ make all %{__make} install DESTDIR=$RPM_BUILD_ROOT %{?mflags_install} #%pre -#groupadd -r warewulf >/dev/null 2>&1 || : +groupadd -r warewulf >/dev/null 2>&1 || : %post %if %{?rhel}%{!?rhel:0} >= 7 From 3283f4de587b11193d173698cd47c0493e68eec9 Mon Sep 17 00:00:00 2001 From: Brian Clemens Date: Fri, 5 Mar 2021 23:50:57 +0900 Subject: [PATCH 19/30] Use forking systemd service --- include/systemd/warewulfd.service | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/systemd/warewulfd.service b/include/systemd/warewulfd.service index 54fc4458..992f3614 100644 --- a/include/systemd/warewulfd.service +++ b/include/systemd/warewulfd.service @@ -5,10 +5,12 @@ After=network-online.target AssertFileIsExecutable=/usr/bin/wwctl [Service] +Type=forking User=root Group=warewulf -ExecStart=/usr/bin/wwctl server start --foreground +ExecStart=/usr/bin/wwctl server start +PIDFile=/tmp/warewulfd.pid Restart=always [Install] From 6cdf15abb5d9a5783d86dd583a97019099e8dc94 Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Sat, 6 Mar 2021 17:45:14 -0800 Subject: [PATCH 20/30] Fix for issue #36. Debug option always takes precedence over verbose. --- internal/app/wwctl/root.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/app/wwctl/root.go b/internal/app/wwctl/root.go index 430938bf..e355cd37 100644 --- a/internal/app/wwctl/root.go +++ b/internal/app/wwctl/root.go @@ -50,10 +50,10 @@ func GetRootCommand() *cobra.Command { } func rootPersistentPreRunE(cmd *cobra.Command, args []string) error { - if verboseArg == true { - wwlog.SetLevel(wwlog.VERBOSE) - } else if debugArg == true { + if debugArg == true { wwlog.SetLevel(wwlog.DEBUG) + } else if verboseArg == true { + wwlog.SetLevel(wwlog.VERBOSE) } else { wwlog.SetLevel(wwlog.INFO) } From 78824d6e2907d183d81011d8681dbd4663d70c8b Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Sat, 6 Mar 2021 17:46:55 -0800 Subject: [PATCH 21/30] Fix for issue #30. Kernel version != kernel args. --- internal/app/wwctl/profile/set/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/wwctl/profile/set/root.go b/internal/app/wwctl/profile/set/root.go index d24533a6..5562ccb3 100644 --- a/internal/app/wwctl/profile/set/root.go +++ b/internal/app/wwctl/profile/set/root.go @@ -39,7 +39,7 @@ func init() { baseCmd.PersistentFlags().StringVar(&SetComment, "comment", "", "Set a comment for this node") baseCmd.PersistentFlags().StringVarP(&SetContainer, "container", "C", "", "Set the container (VNFS) for this node") baseCmd.PersistentFlags().StringVarP(&SetKernel, "kernel", "K", "", "Set Kernel version for nodes") - baseCmd.PersistentFlags().StringVarP(&SetKernel, "kernelargs", "A", "", "Set Kernel argument for nodes") + baseCmd.PersistentFlags().StringVarP(&SetKernelArgs, "kernelargs", "A", "", "Set Kernel argument for nodes") baseCmd.PersistentFlags().StringVarP(&SetClusterName, "cluster", "c", "", "Set the node's cluster group") baseCmd.PersistentFlags().StringVarP(&SetIpxe, "ipxe", "P", "", "Set the node's iPXE template name") baseCmd.PersistentFlags().StringVarP(&SetInit, "init", "i", "", "Define the init process to boot the container") From 8d24496e97036af75f53b226ed2c512c0ec48928 Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Sat, 6 Mar 2021 18:02:25 -0800 Subject: [PATCH 22/30] Fixing some packaging issues, make should start services. --- Makefile | 7 +------ warewulf.spec.in | 6 ++++-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 61f27956..f9573a43 100644 --- a/Makefile +++ b/Makefile @@ -64,11 +64,6 @@ files: all # cp -r tftpboot/* /var/lib/tftpboot/warewulf/ipxe/ # restorecon -r /var/lib/tftpboot/warewulf -services: files - groupadd -r warewulf - systemctl daemon-reload - systemctl enable --now warewulfd - wwctl: cd cmd/wwctl; go build -mod vendor -tags "$(WW_BUILD_GO_BUILD_TAGS)" -o ../../wwctl @@ -89,4 +84,4 @@ clean: rm -rf _dist rm -f warewulf-$(VERSION).tar.gz -install: files services +install: files diff --git a/warewulf.spec.in b/warewulf.spec.in index 8f8ff836..0f76c3c2 100644 --- a/warewulf.spec.in +++ b/warewulf.spec.in @@ -28,8 +28,10 @@ groupadd -r warewulf >/dev/null 2>&1 || : %post %if %{?rhel}%{!?rhel:0} >= 7 -systemctl start warewulfd >/dev/null 2>&1 || : -systemctl enable warewulfd >/dev/null 2>&1 || : + groupadd -r warewulf >/dev/null 2>&1 || : + systemctl daemon-reload >/dev/null 2>&1 || : + systemctl start warewulfd >/dev/null 2>&1 || : + systemctl enable warewulfd >/dev/null 2>&1 || : %endif %clean From e149afd580c6bfc885dfbbb313943a88ed19c4df Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Sat, 6 Mar 2021 18:03:49 -0800 Subject: [PATCH 23/30] Disabling the warewulfd automatic start as part of RPM (anaconda install!?) --- warewulf.spec.in | 2 -- 1 file changed, 2 deletions(-) diff --git a/warewulf.spec.in b/warewulf.spec.in index 0f76c3c2..64b8cd69 100644 --- a/warewulf.spec.in +++ b/warewulf.spec.in @@ -30,8 +30,6 @@ groupadd -r warewulf >/dev/null 2>&1 || : %if %{?rhel}%{!?rhel:0} >= 7 groupadd -r warewulf >/dev/null 2>&1 || : systemctl daemon-reload >/dev/null 2>&1 || : - systemctl start warewulfd >/dev/null 2>&1 || : - systemctl enable warewulfd >/dev/null 2>&1 || : %endif %clean From 455848463ffe93bcee81e24303b1c88103c9b647 Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Sat, 6 Mar 2021 18:18:05 -0800 Subject: [PATCH 24/30] If container import fails, delete the installed container dir (issue #28) --- internal/app/wwctl/container/imprt/main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/app/wwctl/container/imprt/main.go b/internal/app/wwctl/container/imprt/main.go index 3a3ef37f..03d9cc2e 100644 --- a/internal/app/wwctl/container/imprt/main.go +++ b/internal/app/wwctl/container/imprt/main.go @@ -109,12 +109,14 @@ func CobraRunE(cmd *cobra.Command, args []string) error { err = container.ImportDocker(uri, name, sCtx) if err != nil { wwlog.Printf(wwlog.ERROR, "Could not import image: %s\n", err) + _ = container.DeleteSource(uri) os.Exit(1) } } else if util.IsDir(uri) { err := container.ImportDirectory(uri, name) if err != nil { wwlog.Printf(wwlog.ERROR, "Could not import image: %s\n", err) + _ = container.DeleteSource(uri) os.Exit(1) } } From 431c6d68912d1903228b011c5d6b17aea23e9487 Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Sat, 6 Mar 2021 18:27:21 -0800 Subject: [PATCH 25/30] Renamed vnfs.go to util.go --- internal/pkg/container/{vnfs.go => util.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename internal/pkg/container/{vnfs.go => util.go} (100%) diff --git a/internal/pkg/container/vnfs.go b/internal/pkg/container/util.go similarity index 100% rename from internal/pkg/container/vnfs.go rename to internal/pkg/container/util.go From dcea5ece28701fde089c63012f4b46dd37cc9c9b Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Sat, 6 Mar 2021 18:32:18 -0800 Subject: [PATCH 26/30] Minor bug with removing container name if import fails. Fixed. --- internal/app/wwctl/container/imprt/main.go | 4 ++-- internal/pkg/container/util.go | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/app/wwctl/container/imprt/main.go b/internal/app/wwctl/container/imprt/main.go index 03d9cc2e..929404a4 100644 --- a/internal/app/wwctl/container/imprt/main.go +++ b/internal/app/wwctl/container/imprt/main.go @@ -109,14 +109,14 @@ func CobraRunE(cmd *cobra.Command, args []string) error { err = container.ImportDocker(uri, name, sCtx) if err != nil { wwlog.Printf(wwlog.ERROR, "Could not import image: %s\n", err) - _ = container.DeleteSource(uri) + _ = container.DeleteSource(name) os.Exit(1) } } else if util.IsDir(uri) { err := container.ImportDirectory(uri, name) if err != nil { wwlog.Printf(wwlog.ERROR, "Could not import image: %s\n", err) - _ = container.DeleteSource(uri) + _ = container.DeleteSource(name) os.Exit(1) } } diff --git a/internal/pkg/container/util.go b/internal/pkg/container/util.go index a9effc37..a138b313 100644 --- a/internal/pkg/container/util.go +++ b/internal/pkg/container/util.go @@ -88,5 +88,6 @@ func ValidSource(name string) bool { func DeleteSource(name string) error { fullPath := SourceDir(name) + wwlog.Printf(wwlog.VERBOSE, "Removing path: %s\n", fullPath) return os.RemoveAll(fullPath) } From 902c6974c8b904176d78813e6bd87ef3f8710947 Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Sat, 6 Mar 2021 18:56:15 -0800 Subject: [PATCH 27/30] added "UNSET" and "UNDEF" (WW3 compat) to node object sets --- internal/pkg/node/methods.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/internal/pkg/node/methods.go b/internal/pkg/node/methods.go index 1fcbd7a7..6f49c692 100644 --- a/internal/pkg/node/methods.go +++ b/internal/pkg/node/methods.go @@ -1,9 +1,5 @@ package node -import ( - "strings" -) - /********** * * Sets @@ -15,7 +11,7 @@ func (self *Entry) Set(val string) { return } - if strings.ToUpper(val) == "DELETE" { + if val == "UNDEF" || val == "DELETE" || val == "UNSET" { self.value = "" } else { self.value = val From af681eabf501049cdf4d912b476d8a657dfaeca0 Mon Sep 17 00:00:00 2001 From: Brian Clemens Date: Mon, 8 Mar 2021 20:51:06 +0900 Subject: [PATCH 28/30] Document e26a05e debug option --- docs/.gitignore | 1 + docs/docs/wwctl/wwctl.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/.gitignore b/docs/.gitignore index b2d6de30..9c0d0c36 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -7,6 +7,7 @@ # Generated files .docusaurus .cache-loader +package-lock.json # Misc .DS_Store diff --git a/docs/docs/wwctl/wwctl.md b/docs/docs/wwctl/wwctl.md index 05890f6a..daa0e0d7 100644 --- a/docs/docs/wwctl/wwctl.md +++ b/docs/docs/wwctl/wwctl.md @@ -13,7 +13,7 @@ Control interface to the Cluster Warewulf Provisioning System ## Options ### -d, --debug -Run with debugging messages enabled +Run with increased verbosity and debug messages enabled ### -v, --verbose Run with increased verbosity From 73e8265c381a2456bcff5cb8efa0051c1c71f445 Mon Sep 17 00:00:00 2001 From: Brian Clemens Date: Mon, 8 Mar 2021 21:04:50 +0900 Subject: [PATCH 29/30] Document 98e3547 create group and start service in source-installed quickstarts --- docs/docs/getting-started/quickstart-el7.md | 13 +++++++++++++ docs/docs/getting-started/quickstart-el8.md | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/docs/docs/getting-started/quickstart-el7.md b/docs/docs/getting-started/quickstart-el7.md index 463745a8..69202f7f 100644 --- a/docs/docs/getting-started/quickstart-el7.md +++ b/docs/docs/getting-started/quickstart-el7.md @@ -46,6 +46,19 @@ nfs: - /var/warewulf ``` +## Start and enable the Warewulf service + +```bash +# Create the group the warewulfd service will run as +$ sudo groupadd -r warewulf + +# Reload system services +$ sudo systemctl daemon-reload + +# Start and enable the warewulfd service +$ sudo systemctl enable --now warewulfd +``` + ## Configure system services automatically ```bash diff --git a/docs/docs/getting-started/quickstart-el8.md b/docs/docs/getting-started/quickstart-el8.md index a63e416f..12b1751a 100644 --- a/docs/docs/getting-started/quickstart-el8.md +++ b/docs/docs/getting-started/quickstart-el8.md @@ -48,6 +48,19 @@ nfs: - /var/warewulf ``` +## Start and enable the Warewulf service + +```bash +# Create the group the warewulfd service will run as +$ sudo groupadd -r warewulf + +# Reload system services +$ sudo systemctl daemon-reload + +# Start and enable the warewulfd service +$ sudo systemctl enable --now warewulfd +``` + ## Configure system services automatically ```bash From 02163769b12c422d2a6b8ddc6b99489a8ae89335 Mon Sep 17 00:00:00 2001 From: Gregory Kurtzer Date: Wed, 10 Mar 2021 15:09:06 -0800 Subject: [PATCH 30/30] Testing an iPXE fix/suggestion from Ben Allen. (Thanks!) --- etc/ipxe/default.ipxe | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/etc/ipxe/default.ipxe b/etc/ipxe/default.ipxe index 5cd3a227..543797f8 100644 --- a/etc/ipxe/default.ipxe +++ b/etc/ipxe/default.ipxe @@ -11,12 +11,12 @@ echo set base http://{{.Ipaddr}}:{{.Port}} -kernel ${base}/kernel/{{.Hwaddr}} {{.KernelArgs}} || goto reboot -initrd ${base}/container/{{.Hwaddr}} || goto reboot -initrd ${base}/kmods/{{.Hwaddr}} || goto reboot -initrd ${base}/overlay-system/{{.Hwaddr}} || goto reboot -boot || goto reboot +kernel --name kernel ${base}/kernel/{{.Hwaddr}} || goto reboot +initrd --name container ${base}/container/{{.Hwaddr}} || goto reboot +initrd --name kmods ${base}/kmods/{{.Hwaddr}} || goto reboot +initrd --name system ${base}/overlay-system/{{.Hwaddr}} || goto reboot +boot kernel initrd=container initrd=kmods initrd=system {{.KernelArgs}} || goto reboot :reboot echo