tconsole: init

This introduces the 'tconsole' (terminal console), the default
interface to show in /dev/tty1 on a Metropolis node.

Currently it just shows some basic status in a single page. Upcoming
changes will reintroduce a simple log dump on a different page, as well
as entirely new features like supervision tree inspection.

To iterate quickly on the console, a 'standalone' target is added which
exercises the console on the user's terminal with fake node data.
However only the actual console in Linux displays colours as intended.

Change-Id: I5cfba2bdb320daa080a073e76bf0494aeab6a4d4
Reviewed-on: https://review.monogon.dev/c/monogon/+/3371
Reviewed-by: Leopold Schabel <leo@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/node/core/main.go b/metropolis/node/core/main.go
index 3d2969f..d103693 100644
--- a/metropolis/node/core/main.go
+++ b/metropolis/node/core/main.go
@@ -33,6 +33,7 @@
 	"source.monogon.dev/metropolis/node/core/network"
 	"source.monogon.dev/metropolis/node/core/roleserve"
 	"source.monogon.dev/metropolis/node/core/rpc/resolver"
+	"source.monogon.dev/metropolis/node/core/tconsole"
 	timesvc "source.monogon.dev/metropolis/node/core/time"
 	"source.monogon.dev/metropolis/node/core/update"
 	mversion "source.monogon.dev/metropolis/version"
@@ -52,13 +53,8 @@
 	// Root system logtree.
 	lt := logtree.New()
 
-	// Set up logger for Metropolis. Currently logs everything to /dev/tty0 and
-	// /dev/ttyS{0,1}.
-	consoles := []*console{
-		{
-			path:     "/dev/tty0",
-			maxWidth: 80,
-		},
+	// Set up logger for Metropolis. Currently logs everything to /dev/ttyS{0,1}.
+	serialConsoles := []*console{
 		{
 			path:     "/dev/ttyS0",
 			maxWidth: 120,
@@ -73,7 +69,7 @@
 	crash := make(chan string)
 
 	// Open up consoles and set up logging from logtree and crash channel.
-	for _, c := range consoles {
+	for _, c := range serialConsoles {
 		f, err := os.OpenFile(c.path, os.O_WRONLY, 0)
 		if err != nil {
 			continue
@@ -99,7 +95,7 @@
 	}
 
 	// Initialize persistent panic handler early
-	initPanicHandler(lt, consoles)
+	initPanicHandler(lt, serialConsoles)
 
 	// Initial logger. Used until we get to a supervisor.
 	logger := lt.MustLeveledFor("init")
@@ -215,6 +211,19 @@
 			return fmt.Errorf("when starting debug service: %w", err)
 		}
 
+		// Initialize interactive consoles.
+		interactiveConsoles := []string{"/dev/tty0"}
+		for _, c := range interactiveConsoles {
+			console, err := tconsole.New(tconsole.TerminalLinux, c, &networkSvc.Status, &rs.LocalRoles, &rs.CuratorConnection)
+			if err != nil {
+				logger.Info("Failed to initialize interactive console at %s: %v", c, err)
+				// TODO: fall back to logger
+			} else {
+				logger.Info("Started interactive console at %s", c)
+				supervisor.Run(ctx, "console-"+c, console.Run)
+			}
+		}
+
 		// Start cluster manager. This kicks off cluster membership machinery,
 		// which will either start a new cluster, enroll into one or join one.
 		m := cluster.NewManager(root, networkSvc, rs, updateSvc, nodeParams, haveTPM)
@@ -249,7 +258,7 @@
 	ctxC()
 	time.Sleep(time.Second)
 	// After a bit, kill all console log readers.
-	for _, c := range consoles {
+	for _, c := range serialConsoles {
 		if c.reader == nil {
 			continue
 		}