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 }