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  }