With this change, you can specify a port number for wwclient.
If
wwclient:
port: NUMBER
is specified in warewulf.conf, wwclient will bind to the specified local
port NUMBER. If no port is specified, wwclient will use any available
port, or port 987 if secure is true.
Port 987 is in the default port range used by the Linux NFS client
(665-1023, see linux/include/linux/sunrpc/xprtsock.h). Changing
the port can avoid failures when port 987 is already in use.
Signed-off-by: Tobias Poschwatta <poschwatta@zib.de>
259 lines
7.2 KiB
Go
259 lines
7.2 KiB
Go
package wwclient
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"path"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/coreos/go-systemd/daemon"
|
|
"github.com/google/uuid"
|
|
"github.com/spf13/cobra"
|
|
"github.com/talos-systems/go-smbios/smbios"
|
|
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
|
"github.com/warewulf/warewulf/internal/pkg/pidfile"
|
|
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
|
)
|
|
|
|
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
|
|
WarewulfConfArg string
|
|
)
|
|
|
|
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")
|
|
rootCmd.PersistentFlags().StringVar(&WarewulfConfArg, "warewulfconf", "", "Set the warewulf configuration file")
|
|
|
|
}
|
|
|
|
// GetRootCommand returns the root cobra.Command for the application.
|
|
func GetRootCommand() *cobra.Command {
|
|
// Run cobra
|
|
return rootCmd
|
|
}
|
|
|
|
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
|
|
conf := warewulfconf.Get()
|
|
if WarewulfConfArg != "" {
|
|
err = conf.Read(WarewulfConfArg)
|
|
} else if os.Getenv("WAREWULFCONF") != "" {
|
|
err = conf.Read(os.Getenv("WAREWULFCONF"))
|
|
} else {
|
|
err = conf.Read(warewulfconf.ConfigFile)
|
|
}
|
|
if err != nil {
|
|
return
|
|
}
|
|
pid, err := pidfile.Write(PIDFile)
|
|
if err != nil && pid == -1 {
|
|
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] == path.Join(conf.Paths.WWClientdir, "wwclient") {
|
|
err := os.Chdir("/")
|
|
if err != nil {
|
|
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")
|
|
fmt.Printf("For full functionality call with: %s\n", path.Join(conf.Paths.WWClientdir, "wwclient"))
|
|
err := os.MkdirAll("/warewulf/wwclient-test", 0755)
|
|
if err != nil {
|
|
wwlog.Error("failed to create dir: %s", err)
|
|
_ = os.Remove(PIDFile)
|
|
os.Exit(1)
|
|
}
|
|
|
|
err = os.Chdir("/warewulf/wwclient-test")
|
|
if err != nil {
|
|
wwlog.Error("failed to change dir: %s", err)
|
|
_ = os.Remove(PIDFile)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
localTCPAddr := net.TCPAddr{}
|
|
if conf.WWClient != nil && conf.WWClient.Port > 0 {
|
|
localTCPAddr.Port = int(conf.WWClient.Port)
|
|
wwlog.Info("Running from configured port %d", conf.WWClient.Port)
|
|
} else if conf.Warewulf.Secure {
|
|
// Setup local port to something privileged (<1024)
|
|
localTCPAddr.Port = 987
|
|
wwlog.Info("Running from trusted port")
|
|
}
|
|
|
|
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,
|
|
},
|
|
}
|
|
var localUUID uuid.UUID
|
|
var tag string
|
|
smbiosDump, smbiosErr := smbios.New()
|
|
if smbiosErr == nil {
|
|
sysinfoDump := smbiosDump.SystemInformation()
|
|
localUUID, _ = sysinfoDump.UUID()
|
|
x := smbiosDump.SystemEnclosure()
|
|
tag = strings.ReplaceAll(x.AssetTagNumber(), " ", "_")
|
|
if tag == "Unknown" {
|
|
dmiOut, err := exec.Command("dmidecode", "-s", "chassis-asset-tag").Output()
|
|
if err == nil {
|
|
chassisAssetTag := strings.TrimSpace(string(dmiOut))
|
|
if chassisAssetTag != "" {
|
|
tag = chassisAssetTag
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Raspberry Pi serial and DUID locations
|
|
// /sys/firmware/devicetree/base/serial-number
|
|
// /sys/firmware/devicetree/base/chosen/rpi-duid
|
|
piSerial, err := os.ReadFile("/sys/firmware/devicetree/base/serial-number")
|
|
if err != nil {
|
|
wwlog.Error("Could not get SMBIOS info: %s", smbiosErr)
|
|
os.Exit(1)
|
|
}
|
|
localUUID = uuid.NewSHA1(uuid.NameSpaceURL, []byte("http://raspberrypi.com/serial-number/"+string(piSerial)))
|
|
tag = "Unknown"
|
|
}
|
|
|
|
cmdline, err := os.ReadFile("/proc/cmdline")
|
|
if err != nil {
|
|
wwlog.Error("Could not read from /proc/cmdline: %s", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
wwid_tmp := strings.Split(string(cmdline), "wwid=")
|
|
if len(wwid_tmp) < 2 {
|
|
wwlog.Error("'wwid' is not defined in /proc/cmdline")
|
|
os.Exit(1)
|
|
}
|
|
|
|
wwid := strings.Split(wwid_tmp[1], " ")[0]
|
|
wwid = strings.TrimSuffix(wwid, "\n")
|
|
|
|
// Dereference wwid from [interface] for cases that cannot have /proc/cmdline set by bootloader
|
|
if string(wwid[0]) == "[" {
|
|
iface := wwid[1 : len(wwid)-1]
|
|
wwid_tmp, err := os.ReadFile(fmt.Sprintf("/sys/class/net/%s/address", iface))
|
|
if err != nil {
|
|
wwlog.Error("'wwid' cannot be dereferenced from /sys/class/net", iface)
|
|
os.Exit(1)
|
|
}
|
|
wwid = strings.TrimSuffix(string(wwid_tmp), "\n")
|
|
wwlog.Info("Dereferencing wwid from [%s] to %s", iface, wwid)
|
|
}
|
|
|
|
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.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/provision/%s?assetkey=%s&uuid=%s&stage=runtime&compress=gz", ipaddr, port, wwid, tag, localUUID)
|
|
wwlog.Debug("Making request: %s", getString)
|
|
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.Error("could not remove pidfile: %s", err)
|
|
}
|
|
}
|