Merge pull request #212 from mslacken/wwclient-systemd-integration
wwclient systemd integration
This commit is contained in:
22
cmd/wwclient/main.go
Normal file
22
cmd/wwclient/main.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/hpcng/warewulf/internal/app/wwclient"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
root := wwclient.GetRootCommand()
|
||||
|
||||
err := root.Execute()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
|
||||
if wwclient.DebugFlag {
|
||||
fmt.Printf("\nSTACK TRACE: %+v\n", err)
|
||||
}
|
||||
os.Exit(255)
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hpcng/warewulf/internal/pkg/warewulfconf"
|
||||
"github.com/hpcng/warewulf/internal/pkg/wwlog"
|
||||
"github.com/talos-systems/go-smbios/smbios"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if os.Args[0] == "/warewulf/bin/wwclient" {
|
||||
err := os.Chdir("/")
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "failed to change dir: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Printf("Updating live file system LIVE, cancel now if this is in error")
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
} else {
|
||||
fmt.Printf("Called via: %s\n", os.Args[0])
|
||||
fmt.Printf("Runtime overlay is being put in '/warewulf/wwclient-test' rather than '/'\n")
|
||||
err := os.MkdirAll("/warewulf/wwclient-test", 0755)
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "failed to create dir: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = os.Chdir("/warewulf/wwclient-test")
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "failed to change dir: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
conf, err := warewulfconf.New()
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Could not get Warewulf configuration: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
localTCPAddr := net.TCPAddr{}
|
||||
if conf.Warewulf.Secure {
|
||||
// Setup local port to something privileged (<1024)
|
||||
localTCPAddr.Port = 987
|
||||
wwlog.Printf(wwlog.INFO, "Running from trusted port\n")
|
||||
}
|
||||
|
||||
webclient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
LocalAddr: &localTCPAddr,
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
smbiosDump, err := smbios.New()
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Could not get SMBIOS info: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
sysinfoDump := smbiosDump.SystemInformation()
|
||||
localUUID, _ := sysinfoDump.UUID()
|
||||
x := smbiosDump.SystemEnclosure()
|
||||
tag := x.AssetTagNumber()
|
||||
|
||||
cmdline, err := ioutil.ReadFile("/proc/cmdline")
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Could not read from /proc/cmdline: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
wwid_tmp := strings.Split(string(cmdline), "wwid=")
|
||||
if len(wwid_tmp) < 2 {
|
||||
wwlog.Printf(wwlog.ERROR, "'wwid' is not defined in /proc/cmdline\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
wwid := strings.Split(wwid_tmp[1], " ")[0]
|
||||
|
||||
for {
|
||||
var resp *http.Response
|
||||
counter := 0
|
||||
|
||||
for {
|
||||
var err error
|
||||
|
||||
getString := fmt.Sprintf("http://%s:%d/overlay-runtime/%s?assetkey=%s&uuid=%s", conf.Ipaddr, conf.Warewulf.Port, wwid, tag, localUUID)
|
||||
resp, err = webclient.Get(getString)
|
||||
if err == nil {
|
||||
break
|
||||
} else {
|
||||
if counter > 60 {
|
||||
counter = 0
|
||||
}
|
||||
if counter == 0 {
|
||||
log.Println(err)
|
||||
}
|
||||
counter++
|
||||
}
|
||||
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
log.Printf("Not updating runtime overlay, got status code: %d\n", resp.StatusCode)
|
||||
time.Sleep(60000 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("Updating system\n")
|
||||
command := exec.Command("/bin/sh", "-c", "gzip -dc | cpio -iu")
|
||||
command.Stdin = resp.Body
|
||||
err := command.Run()
|
||||
if err != nil {
|
||||
log.Printf("ERROR: Failed running CPIO: %s\n", err)
|
||||
}
|
||||
|
||||
if conf.Warewulf.UpdateInterval > 0 {
|
||||
time.Sleep(time.Duration(conf.Warewulf.UpdateInterval*1000) * time.Millisecond)
|
||||
} else {
|
||||
time.Sleep(30000 * time.Millisecond * 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,8 @@ kernel --name kernel ${base}/kernel/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid}
|
||||
initrd --name container ${base}/container/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid} || goto reboot
|
||||
initrd --name kmods ${base}/kmods/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid} || goto reboot
|
||||
initrd --name system ${base}/overlay-system/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid} || goto reboot
|
||||
initrd --name runtime ${base}/overlay-runtime/{{.Hwaddr}}?assetkey=${asset}&uuid=${uuid} || goto reboot
|
||||
|
||||
boot kernel initrd=container initrd=kmods initrd=system initrd=runtime wwid={{.Hwaddr}} {{.KernelArgs}} || goto reboot
|
||||
boot kernel initrd=container initrd=kmods initrd=system wwid={{.Hwaddr}} {{.KernelArgs}} || goto reboot
|
||||
|
||||
:reboot
|
||||
echo
|
||||
|
||||
2
go.mod
2
go.mod
@@ -6,8 +6,10 @@ require (
|
||||
github.com/brotherpowers/ipsubnet v0.0.0-20170914094241-30bc98f0a5b1
|
||||
github.com/containers/image/v5 v5.7.0
|
||||
github.com/containers/storage v1.30.0
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
|
||||
github.com/creasty/defaults v1.5.2
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/google/uuid v1.1.2
|
||||
github.com/manifoldco/promptui v0.8.0
|
||||
github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6
|
||||
github.com/opencontainers/umoci v0.4.6
|
||||
|
||||
1
go.sum
1
go.sum
@@ -202,6 +202,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||
github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||
|
||||
@@ -5,7 +5,7 @@ After=network-online.target
|
||||
AssertFileIsExecutable=@BINDIR@/wwctl
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
Type=simple
|
||||
User=root
|
||||
Group=root
|
||||
|
||||
|
||||
213
internal/app/wwclient/root.go
Normal file
213
internal/app/wwclient/root.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package wwclient
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/talos-systems/go-smbios/smbios"
|
||||
"github.com/coreos/go-systemd/daemon"
|
||||
"github.com/hpcng/warewulf/internal/pkg/pidfile"
|
||||
"github.com/hpcng/warewulf/internal/pkg/warewulfconf"
|
||||
"github.com/hpcng/warewulf/internal/pkg/wwlog"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "wwclient",
|
||||
Short: "wwclient",
|
||||
Long: "wwclient fetches the runtime overlay and puts it on the disk",
|
||||
RunE: CobraRunE,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
DebugFlag bool
|
||||
PIDFile string
|
||||
Webclient *http.Client
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().BoolVarP(&DebugFlag, "debug", "d", false, "Run with debugging messages enabled.")
|
||||
rootCmd.PersistentFlags().StringVarP(&PIDFile, "pidfile", "p", "/var/run/wwclient.pid", "PIDFile to use")
|
||||
|
||||
}
|
||||
|
||||
// GetRootCommand returns the root cobra.Command for the application.
|
||||
func GetRootCommand() *cobra.Command {
|
||||
// Run cobra
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
conf, err := warewulfconf.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pid, err := pidfile.Write(PIDFile)
|
||||
if err != nil && pid == -1 {
|
||||
wwlog.Printf(wwlog.WARN, "%v. starting new wwclient", err)
|
||||
} else if err != nil && pid > 0 {
|
||||
return errors.New("found pidfile " + PIDFile + " not starting")
|
||||
}
|
||||
|
||||
if os.Args[0] == "/warewulf/bin/wwclient" {
|
||||
err := os.Chdir("/")
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "failed to change dir: %s", err)
|
||||
_ = os.Remove(PIDFile)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Printf("Updating live file system LIVE, cancel now if this is in error")
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
} else {
|
||||
fmt.Printf("Called via: %s\n", os.Args[0])
|
||||
fmt.Printf("Runtime overlay is being put in '/warewulf/wwclient-test' rather than '/'\n")
|
||||
err := os.MkdirAll("/warewulf/wwclient-test", 0755)
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "failed to create dir: %s", err)
|
||||
_ = os.Remove(PIDFile)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = os.Chdir("/warewulf/wwclient-test")
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "failed to change dir: %s", err)
|
||||
_ = os.Remove(PIDFile)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
localTCPAddr := net.TCPAddr{}
|
||||
if conf.Warewulf.Secure {
|
||||
// Setup local port to something privileged (<1024)
|
||||
localTCPAddr.Port = 987
|
||||
wwlog.Printf(wwlog.INFO, "Running from trusted port\n")
|
||||
}
|
||||
|
||||
Webclient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
LocalAddr: &localTCPAddr,
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
},
|
||||
}
|
||||
smbiosDump, err := smbios.New()
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Could not get SMBIOS info: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
sysinfoDump := smbiosDump.SystemInformation()
|
||||
localUUID, _ := sysinfoDump.UUID()
|
||||
x := smbiosDump.SystemEnclosure()
|
||||
tag := x.AssetTagNumber()
|
||||
|
||||
cmdline, err := ioutil.ReadFile("/proc/cmdline")
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "Could not read from /proc/cmdline: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
wwid_tmp := strings.Split(string(cmdline), "wwid=")
|
||||
if len(wwid_tmp) < 2 {
|
||||
wwlog.Printf(wwlog.ERROR, "'wwid' is not defined in /proc/cmdline\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
wwid := strings.Split(wwid_tmp[1], " ")[0]
|
||||
|
||||
|
||||
duration := 300
|
||||
if conf.Warewulf.UpdateInterval > 0 {
|
||||
duration = conf.Warewulf.UpdateInterval
|
||||
}
|
||||
stopTimer := time.NewTimer(time.Duration(duration) * time.Second)
|
||||
// listen on SIGHUP
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT)
|
||||
go func() {
|
||||
for {
|
||||
sig := <-sigs
|
||||
switch sig {
|
||||
case syscall.SIGHUP:
|
||||
log.Printf("Received SIGNAL: %s\n", sig)
|
||||
stopTimer.Stop()
|
||||
stopTimer.Reset(0)
|
||||
case syscall.SIGTERM, syscall.SIGINT:
|
||||
wwlog.Printf(wwlog.INFO, "termination wwclient!, %v", sig)
|
||||
cleanUp()
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
}()
|
||||
var finishedInitialSync bool = false
|
||||
for {
|
||||
updateSystem(conf.Ipaddr, conf.Warewulf.Port, wwid, tag, localUUID)
|
||||
if !finishedInitialSync {
|
||||
// ignore error and status here, as this wouldn't change anything
|
||||
_, _ = daemon.SdNotify(false, daemon.SdNotifyReady)
|
||||
finishedInitialSync = true
|
||||
}
|
||||
|
||||
<-stopTimer.C
|
||||
stopTimer.Reset(time.Duration(duration) * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func updateSystem(ipaddr string, port int, wwid string, tag string, localUUID uuid.UUID) {
|
||||
var resp *http.Response
|
||||
counter := 0
|
||||
for {
|
||||
var err error
|
||||
getString := fmt.Sprintf("http://%s:%d/overlay-runtime/%s?assetkey=%s&uuid=%s", ipaddr, port, wwid, tag, localUUID)
|
||||
resp, err = Webclient.Get(getString)
|
||||
if err == nil {
|
||||
break
|
||||
} else {
|
||||
if counter > 60 {
|
||||
counter = 0
|
||||
}
|
||||
if counter == 0 {
|
||||
log.Println(err)
|
||||
}
|
||||
counter++
|
||||
}
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
log.Printf("Not updating runtime overlay, got status code: %d\n", resp.StatusCode)
|
||||
time.Sleep(60000 * time.Millisecond)
|
||||
return
|
||||
}
|
||||
log.Printf("Updating system\n")
|
||||
command := exec.Command("/bin/sh", "-c", "gzip -dc | cpio -iu")
|
||||
command.Stdin = resp.Body
|
||||
err := command.Run()
|
||||
if err != nil {
|
||||
log.Printf("ERROR: Failed running CPIO: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func cleanUp() {
|
||||
err := pidfile.Remove(PIDFile)
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "could not remove pidfile: %s\n", err)
|
||||
}
|
||||
}
|
||||
@@ -262,6 +262,16 @@ func BuildOverlay(nodeInfo node.NodeInfo, overlayName string) error {
|
||||
|
||||
// } else if b, _ := regexp.MatchString(`\.ww[a-zA-Z0-9\-\._]*$`, location); b {
|
||||
// wwlog.Printf(wwlog.DEBUG, "Ignoring WW template file: %s\n", location)
|
||||
} else if info.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
wwlog.Printf(wwlog.DEBUG, "Found symlink %s\n", location)
|
||||
destination, err := os.Readlink(location)
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "%s\n", err)
|
||||
}
|
||||
err = os.Symlink(destination, path.Join(tmpDir, location))
|
||||
if err != nil {
|
||||
wwlog.Printf(wwlog.ERROR, "%s\n", err)
|
||||
}
|
||||
} else {
|
||||
|
||||
err := util.CopyFile(location, path.Join(tmpDir, location))
|
||||
|
||||
84
internal/pkg/pidfile/pidfile.go
Normal file
84
internal/pkg/pidfile/pidfile.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package pidfile
|
||||
|
||||
// based on original work found here, github.com/soellman/pidfile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrProcessRunning = errors.New("process is running")
|
||||
ErrFileStale = errors.New("pidfile exists but process is not running")
|
||||
ErrFileInvalid = errors.New("pidfile has invalid contents")
|
||||
)
|
||||
|
||||
// Remove a pidfile
|
||||
func Remove(filename string) error {
|
||||
return os.RemoveAll(filename)
|
||||
}
|
||||
|
||||
// Write writes a pidfile, returning an error
|
||||
// if the process is already running or pidfile is orphaned
|
||||
func Write(filename string) (int, error) {
|
||||
return WriteControl(filename, os.Getpid(), false)
|
||||
}
|
||||
|
||||
func WriteControl(filename string, pid int, overwrite bool) (int, error) {
|
||||
// Check for existing pid
|
||||
oldpid, err := pidfileContents(filename)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return oldpid, err
|
||||
}
|
||||
|
||||
// We have a pid
|
||||
if err == nil {
|
||||
if pidIsRunning(oldpid) {
|
||||
return oldpid, ErrProcessRunning
|
||||
}
|
||||
if !overwrite {
|
||||
return -1, ErrFileStale
|
||||
}
|
||||
}
|
||||
|
||||
// We're clear to (over)write the file
|
||||
return pid, ioutil.WriteFile(filename, []byte(fmt.Sprintf("%d\n", pid)), 0644)
|
||||
}
|
||||
|
||||
func pidfileContents(filename string) (int, error) {
|
||||
contents, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
pid, err := strconv.Atoi(strings.TrimSpace(string(contents)))
|
||||
if err != nil {
|
||||
return 0, ErrFileInvalid
|
||||
}
|
||||
|
||||
return pid, nil
|
||||
}
|
||||
|
||||
func pidIsRunning(pid int) bool {
|
||||
process, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
err = process.Signal(syscall.Signal(0))
|
||||
|
||||
if err != nil && err.Error() == "no such process" {
|
||||
return false
|
||||
}
|
||||
|
||||
if err != nil && err.Error() == "os: process already finished" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/etc/systemd/system/wwclient.service
|
||||
14
overlays/wwinit/etc/systemd/system/wwclient.service
Normal file
14
overlays/wwinit/etc/systemd/system/wwclient.service
Normal file
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=Warewulf node runtime overlay update
|
||||
After=network.target
|
||||
After=local-fs.target
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
ExecStart=/warewulf/bin/wwclient
|
||||
ExecReload=/bin/kill -s SIGHUP "$MAINPID"
|
||||
PIDFile=/var/run/wwclient.pid
|
||||
TimeoutSec=60
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /warewulf/config
|
||||
|
||||
# Only start if the systemd is no available
|
||||
test -e /usr/lib/systemd/systemd && exit 0
|
||||
echo "Starting wwclient"
|
||||
nohup /warewulf/bin/wwclient >/var/log/wwclient.log 2>&1 </dev/null &
|
||||
|
||||
Reference in New Issue
Block a user