github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/cmd/installer/main.go (about)

     1  // +build linux
     2  
     3  package main
     4  
     5  import (
     6  	"flag"
     7  	"fmt"
     8  	stdlog "log"
     9  	"os"
    10  	"path/filepath"
    11  	"sync"
    12  	"syscall"
    13  	"time"
    14  
    15  	"github.com/Cloud-Foundations/Dominator/lib/constants"
    16  	"github.com/Cloud-Foundations/Dominator/lib/flags/loadflags"
    17  	"github.com/Cloud-Foundations/Dominator/lib/fsutil"
    18  	"github.com/Cloud-Foundations/Dominator/lib/log"
    19  	"github.com/Cloud-Foundations/Dominator/lib/log/debuglogger"
    20  	"github.com/Cloud-Foundations/Dominator/lib/logbuf"
    21  	"github.com/Cloud-Foundations/Dominator/lib/srpc/setupserver"
    22  	"github.com/Cloud-Foundations/tricorder/go/tricorder"
    23  )
    24  
    25  const logfile = "/var/log/installer/latest"
    26  
    27  type flusher interface {
    28  	Flush() error
    29  }
    30  
    31  type Rebooter interface {
    32  	Reboot() error
    33  	String() string
    34  }
    35  
    36  var (
    37  	dryRun = flag.Bool("dryRun", ifUnprivileged(),
    38  		"If true, do not make changes")
    39  	mountPoint = flag.String("mountPoint", "/mnt",
    40  		"Mount point for new root file-system")
    41  	objectsDirectory = flag.String("objectsDirectory", "/objects",
    42  		"Directory where cached objects will be written")
    43  	logDebugLevel = flag.Int("logDebugLevel", -1, "Debug log level")
    44  	portNum       = flag.Uint("portNum", constants.InstallerPortNumber,
    45  		"Port number to allocate and listen on for HTTP/RPC")
    46  	procDirectory = flag.String("procDirectory", "/proc",
    47  		"Directory where procfs is mounted")
    48  	skipNetwork = flag.Bool("skipNetwork", false,
    49  		"If true, do not update target network configuration")
    50  	skipStorage = flag.Bool("skipStorage", false,
    51  		"If true, do not update storage")
    52  	sysfsDirectory = flag.String("sysfsDirectory", "/sys",
    53  		"Directory where sysfs is mounted")
    54  	tftpDirectory = flag.String("tftpDirectory", "/tftpdata",
    55  		"Directory containing (possibly injected) TFTP data")
    56  	tmpRoot = flag.String("tmpRoot", "/tmproot",
    57  		"Mount point for temporary (tmpfs) root file-system")
    58  )
    59  
    60  func copyLogs(logFlusher flusher) error {
    61  	logFlusher.Flush()
    62  	logdir := filepath.Join(*mountPoint, "var", "log", "installer")
    63  	return fsutil.CopyFile(filepath.Join(logdir, "log"), logfile,
    64  		fsutil.PublicFilePerms)
    65  }
    66  
    67  func createLogger() (*logbuf.LogBuffer, log.DebugLogger) {
    68  	os.MkdirAll("/var/log/installer", fsutil.DirPerms)
    69  	options := logbuf.GetStandardOptions()
    70  	options.AlsoLogToStderr = true
    71  	logBuffer := logbuf.NewWithOptions(options)
    72  	logger := debuglogger.New(stdlog.New(logBuffer, "", 0))
    73  	logger.SetLevel(int16(*logDebugLevel))
    74  	return logBuffer, logger
    75  }
    76  
    77  func ifUnprivileged() bool {
    78  	if os.Geteuid() != 0 {
    79  		return true
    80  	}
    81  	return false
    82  }
    83  
    84  func install(updateHwClock bool, logFlusher flusher,
    85  	logger log.DebugLogger) (Rebooter, error) {
    86  	var rebooter Rebooter
    87  	machineInfo, interfaces, err := configureLocalNetwork(logger)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	if !*skipStorage {
    93  		rebooter, err = configureStorage(*machineInfo, logger)
    94  		if err != nil {
    95  			return nil, err
    96  		}
    97  		if !*dryRun && updateHwClock {
    98  			if err := run("hwclock", *tmpRoot, logger, "-w"); err != nil {
    99  				logger.Printf("Error updating hardware clock: %s\n", err)
   100  			} else {
   101  				logger.Println("Updated hardware clock from system clock")
   102  			}
   103  		}
   104  	}
   105  	if !*skipNetwork {
   106  		err := configureNetwork(*machineInfo, interfaces, logger)
   107  		if err != nil {
   108  			return nil, err
   109  		}
   110  	}
   111  	if err := copyLogs(logFlusher); err != nil {
   112  		return nil, fmt.Errorf("error copying logs: %s", err)
   113  	}
   114  	if err := unmountStorage(logger); err != nil {
   115  		return nil, fmt.Errorf("error unmounting: %s", err)
   116  	}
   117  	return rebooter, nil
   118  }
   119  
   120  func printAndWait(initialTimeoutString, waitTimeoutString string,
   121  	waitGroup *sync.WaitGroup, rebooterName string, logger log.Logger) {
   122  	initialTimeout, _ := time.ParseDuration(initialTimeoutString)
   123  	if initialTimeout < time.Second {
   124  		initialTimeout = time.Second
   125  		initialTimeoutString = "1s"
   126  	}
   127  	logger.Printf("waiting %s before rebooting with %s rebooter\n",
   128  		initialTimeoutString, rebooterName)
   129  	time.Sleep(initialTimeout - time.Second)
   130  	waitChannel := make(chan struct{})
   131  	go func() {
   132  		waitGroup.Wait()
   133  		waitChannel <- struct{}{}
   134  	}()
   135  	timer := time.NewTimer(time.Second)
   136  	select {
   137  	case <-timer.C:
   138  	case <-waitChannel:
   139  		return
   140  	}
   141  	logger.Printf(
   142  		"waiting %s for remote shells to terminate before rebooting\n",
   143  		waitTimeoutString)
   144  	waitTimeout, _ := time.ParseDuration(waitTimeoutString)
   145  	timer.Reset(waitTimeout)
   146  	select {
   147  	case <-timer.C:
   148  	case <-waitChannel:
   149  	}
   150  }
   151  
   152  func doMain() error {
   153  	var timeLogMessage string
   154  	if fi, err := os.Stat("/build-timestamp"); err != nil {
   155  		return err
   156  	} else {
   157  		now := time.Now()
   158  		if fi.ModTime().After(now) {
   159  			timeval := syscall.Timeval{Sec: fi.ModTime().Unix()}
   160  			if err := syscall.Settimeofday(&timeval); err != nil {
   161  				return err
   162  			}
   163  			timeLogMessage = fmt.Sprintf("System time: %s is earlier than build time: %s.\nAdvancing to build time",
   164  				now, fi.ModTime())
   165  		}
   166  	}
   167  	if err := loadflags.LoadForDaemon("installer"); err != nil {
   168  		return err
   169  	}
   170  	flag.Parse()
   171  	tricorder.RegisterFlags()
   172  	logBuffer, logger := createLogger()
   173  	defer logBuffer.Flush()
   174  	if timeLogMessage != "" {
   175  		logger.Println(timeLogMessage)
   176  	}
   177  	go runShellOnConsole(logger)
   178  	AddHtmlWriter(logBuffer)
   179  	if err := setupserver.SetupTls(); err != nil {
   180  		logger.Println(err)
   181  	}
   182  	waitGroup := &sync.WaitGroup{}
   183  	if newLogger, err := startServer(*portNum, waitGroup, logger); err != nil {
   184  		logger.Printf("cannot start server: %s\n", err)
   185  	} else {
   186  		logger = newLogger
   187  	}
   188  	rebooter, err := install(timeLogMessage != "", logBuffer, logger)
   189  	rebooterName := "default"
   190  	if rebooter != nil {
   191  		rebooterName = rebooter.String()
   192  	}
   193  	if err != nil {
   194  		logger.Println(err)
   195  		printAndWait("5m", "1h", waitGroup, rebooterName, logger)
   196  	} else {
   197  		printAndWait("5s", "5m", waitGroup, rebooterName, logger)
   198  	}
   199  	syscall.Sync()
   200  	if rebooter != nil {
   201  		if err := rebooter.Reboot(); err != nil {
   202  			logger.Printf("error calling %s rebooter: %s\n", rebooterName, err)
   203  			logger.Println("falling back to default rebooter after 5 minutes")
   204  			time.Sleep(time.Minute * 5)
   205  		}
   206  	}
   207  	if err := syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART); err != nil {
   208  		logger.Fatalf("error rebooting: %s\n", err)
   209  	}
   210  	return nil
   211  }
   212  
   213  func main() {
   214  	if err := doMain(); err != nil {
   215  		fmt.Fprintln(os.Stderr, err)
   216  		os.Exit(1)
   217  	}
   218  }