github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/cmd/installer/server.go (about) 1 // +build linux 2 3 package main 4 5 import ( 6 "bufio" 7 "fmt" 8 "io" 9 stdlog "log" 10 "net" 11 "net/http" 12 "os" 13 "os/exec" 14 "sync" 15 "syscall" 16 17 "github.com/Cloud-Foundations/Dominator/lib/html" 18 "github.com/Cloud-Foundations/Dominator/lib/log" 19 "github.com/Cloud-Foundations/Dominator/lib/log/debuglogger" 20 "github.com/Cloud-Foundations/Dominator/lib/log/teelogger" 21 "github.com/Cloud-Foundations/Dominator/lib/srpc" 22 ) 23 24 type HtmlWriter interface { 25 WriteHtml(writer io.Writer) 26 } 27 28 type state struct { 29 logger log.DebugLogger 30 } 31 32 type srpcType struct { 33 remoteShellWaitGroup *sync.WaitGroup 34 logger log.DebugLogger 35 mutex sync.RWMutex 36 connections map[*srpc.Conn]struct{} 37 } 38 39 var htmlWriters []HtmlWriter 40 41 func startServer(portNum uint, remoteShellWaitGroup *sync.WaitGroup, 42 logger log.DebugLogger) (log.DebugLogger, error) { 43 listener, err := net.Listen("tcp", fmt.Sprintf(":%d", portNum)) 44 if err != nil { 45 return nil, err 46 } 47 myState := state{logger} 48 html.HandleFunc("/", myState.statusHandler) 49 srpcObj := &srpcType{ 50 remoteShellWaitGroup: remoteShellWaitGroup, 51 logger: logger, 52 connections: make(map[*srpc.Conn]struct{}), 53 } 54 if err := srpc.RegisterName("Installer", srpcObj); err != nil { 55 logger.Printf("error registering SRPC receiver: %s\n", err) 56 } 57 sprayLogger := debuglogger.New(stdlog.New(srpcObj, "", 0)) 58 sprayLogger.SetLevel(int16(*logDebugLevel)) 59 go http.Serve(listener, nil) 60 return teelogger.New(logger, sprayLogger), nil 61 } 62 63 func (s state) statusHandler(w http.ResponseWriter, req *http.Request) { 64 if req.URL.Path != "/" { 65 http.NotFound(w, req) 66 return 67 } 68 writer := bufio.NewWriter(w) 69 defer writer.Flush() 70 fmt.Fprintln(writer, "<title>installer status page</title>") 71 fmt.Fprintln(writer, `<style> 72 table, th, td { 73 border-collapse: collapse; 74 } 75 </style>`) 76 fmt.Fprintln(writer, "<body>") 77 fmt.Fprintln(writer, "<center>") 78 fmt.Fprintln(writer, "<h1>installer status page</h1>") 79 fmt.Fprintln(writer, "</center>") 80 html.WriteHeaderWithRequest(writer, req) 81 fmt.Fprintln(writer, "<h3>") 82 s.writeDashboard(writer) 83 for _, htmlWriter := range htmlWriters { 84 htmlWriter.WriteHtml(writer) 85 } 86 fmt.Fprintln(writer, "</h3>") 87 fmt.Fprintln(writer, "<hr>") 88 html.WriteFooter(writer) 89 fmt.Fprintln(writer, "</body>") 90 } 91 92 func AddHtmlWriter(htmlWriter HtmlWriter) { 93 htmlWriters = append(htmlWriters, htmlWriter) 94 } 95 96 func (s state) writeDashboard(writer io.Writer) { 97 } 98 99 func (t *srpcType) Shell(conn *srpc.Conn) error { 100 t.logger.Println("starting shell on SRPC connection") 101 t.remoteShellWaitGroup.Add(1) 102 defer t.remoteShellWaitGroup.Done() 103 pty, tty, err := openPty() 104 if err != nil { 105 return err 106 } 107 defer pty.Close() 108 defer tty.Close() 109 if file, err := os.Open("/var/log/installer/latest"); err != nil { 110 t.logger.Println(err) 111 } else { 112 fmt.Fprintln(conn, "Logs so far:\r") 113 // Need to inject carriage returns for each line, so have to do this the 114 // hard way. 115 reader := bufio.NewReader(file) 116 for { 117 if chunk, isPrefix, err := reader.ReadLine(); err != nil { 118 break 119 } else { 120 conn.Write(chunk) 121 if !isPrefix { 122 conn.Write([]byte("\r\n")) 123 } 124 } 125 } 126 file.Close() 127 conn.Flush() 128 } 129 cmd := exec.Command("/bin/busybox", "sh", "-i") 130 cmd.Env = make([]string, 0) 131 cmd.Stdin = tty 132 cmd.Stdout = tty 133 cmd.Stderr = tty 134 cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true, Setctty: true} 135 if err := cmd.Start(); err != nil { 136 return err 137 } 138 fmt.Fprintln(conn, "Starting shell...\r") 139 conn.Flush() 140 killed := false 141 go func() { // Read from pty until killed. 142 for { 143 t.mutex.Lock() 144 t.connections[conn] = struct{}{} 145 t.mutex.Unlock() 146 buffer := make([]byte, 256) 147 if nRead, err := pty.Read(buffer); err != nil { 148 if killed { 149 break 150 } 151 t.logger.Printf("error reading from pty: %s", err) 152 break 153 } else if _, err := conn.Write(buffer[:nRead]); err != nil { 154 t.logger.Printf("error writing to connection: %s\n", err) 155 break 156 } 157 if err := conn.Flush(); err != nil { 158 t.logger.Printf("error flushing connection: %s\n", err) 159 break 160 } 161 } 162 t.mutex.Lock() 163 delete(t.connections, conn) 164 t.mutex.Unlock() 165 }() 166 // Read from connection, write to pty. 167 for { 168 buffer := make([]byte, 256) 169 if nRead, err := conn.Read(buffer); err != nil { 170 if err == io.EOF { 171 break 172 } 173 return err 174 } else { 175 if _, err := pty.Write(buffer[:nRead]); err != nil { 176 return err 177 } 178 } 179 } 180 killed = true 181 cmd.Process.Kill() 182 cmd.Wait() 183 t.logger.Println("shell for SRPC connection exited") 184 return nil 185 } 186 187 func (t *srpcType) Write(p []byte) (int, error) { 188 buffer := make([]byte, 0, len(p)+1) 189 for _, ch := range p { // First add a carriage return for each newline. 190 if ch == '\n' { 191 buffer = append(buffer, '\r') 192 } 193 buffer = append(buffer, ch) 194 } 195 t.mutex.RLock() 196 defer t.mutex.RUnlock() 197 for conn := range t.connections { 198 conn.Write(buffer) 199 conn.Flush() 200 } 201 return len(p), nil 202 }