metropolis/installer: migrate to bringup package

Change-Id: Ib6215bc51fdef45476198eceffbd0fd1fd362f1b
Reviewed-on: https://review.monogon.dev/c/monogon/+/3393
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/installer/BUILD.bazel b/metropolis/installer/BUILD.bazel
index 29eac44..4fcd58c 100644
--- a/metropolis/installer/BUILD.bazel
+++ b/metropolis/installer/BUILD.bazel
@@ -5,10 +5,7 @@
 
 go_library(
     name = "installer_lib",
-    srcs = [
-        "log.go",
-        "main.go",
-    ],
+    srcs = ["main.go"],
     embedsrcs = [
         "//metropolis/node/core/abloader",  #keep
     ],
@@ -16,8 +13,10 @@
     visibility = ["//visibility:private"],
     deps = [
         "//osbase/blockdev",
+        "//osbase/bringup",
         "//osbase/build/mkimage/osimage",
         "//osbase/efivarfs",
+        "//osbase/supervisor",
         "//osbase/sysfs",
         "@org_golang_x_sys//unix",
     ],
diff --git a/metropolis/installer/log.go b/metropolis/installer/log.go
deleted file mode 100644
index cb7dee6..0000000
--- a/metropolis/installer/log.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package main
-
-import (
-	"fmt"
-	"os"
-)
-
-var logC = make(chan string)
-
-// logPiper pipes log entries submitted via logf and panicf into whatever
-// consoles are available to the system.
-func logPiper() {
-	var consoles []*os.File
-	for _, p := range []string{"/dev/tty0", "/dev/ttyS0"} {
-		f, err := os.OpenFile(p, os.O_WRONLY, 0)
-		if err != nil {
-			continue
-		}
-		consoles = append(consoles, f)
-	}
-
-	for {
-		s := <-logC
-		for _, c := range consoles {
-			fmt.Fprintf(c, "%s\n", s)
-		}
-	}
-}
-
-// logf logs some format/args into the active consoles.
-func logf(format string, args ...any) {
-	s := fmt.Sprintf(format, args...)
-	logC <- s
-}
-
-// panicf aborts the installation process with a given format/args.
-func panicf(format string, args ...any) {
-	s := fmt.Sprintf(format, args...)
-	// We don't need to print `s` here, as it's gonna get printed by the recovery
-	// code in main.
-	panic(s)
-}
diff --git a/metropolis/installer/main.go b/metropolis/installer/main.go
index e2d0d55..0863808 100644
--- a/metropolis/installer/main.go
+++ b/metropolis/installer/main.go
@@ -22,6 +22,7 @@
 import (
 	"archive/zip"
 	"bytes"
+	"context"
 	_ "embed"
 	"errors"
 	"fmt"
@@ -29,14 +30,15 @@
 	"os"
 	"path/filepath"
 	"strings"
-	"syscall"
 	"time"
 
 	"golang.org/x/sys/unix"
 
 	"source.monogon.dev/osbase/blockdev"
+	"source.monogon.dev/osbase/bringup"
 	"source.monogon.dev/osbase/build/mkimage/osimage"
 	"source.monogon.dev/osbase/efivarfs"
+	"source.monogon.dev/osbase/supervisor"
 	"source.monogon.dev/osbase/sysfs"
 )
 
@@ -45,28 +47,6 @@
 
 const mib = 1024 * 1024
 
-// mountPseudoFS mounts efivarfs, devtmpfs and sysfs, used by the installer in
-// the block device discovery stage.
-func mountPseudoFS() error {
-	for _, m := range []struct {
-		dir   string
-		fs    string
-		flags uintptr
-	}{
-		{"/sys", "sysfs", unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV},
-		{efivarfs.Path, "efivarfs", unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV},
-		{"/dev", "devtmpfs", unix.MS_NOEXEC | unix.MS_NOSUID},
-	} {
-		if err := unix.Mkdir(m.dir, 0700); err != nil && !os.IsExist(err) {
-			return fmt.Errorf("couldn't create the mountpoint at %q: %w", m.dir, err)
-		}
-		if err := unix.Mount(m.fs, m.dir, m.fs, m.flags, ""); err != nil {
-			return fmt.Errorf("couldn't mount %q at %q: %w", m.fs, m.dir, err)
-		}
-	}
-	return nil
-}
-
 // mountInstallerESP mounts the filesystem the installer was loaded from based
 // on espPath, which must point to the appropriate partition block device. The
 // filesystem is mounted at /installer.
@@ -153,30 +133,26 @@
 }
 
 func main() {
-	// Reboot on panic after a delay. The error string will have been printed
-	// before recover is called.
-	defer func() {
-		if r := recover(); r != nil {
-			logf("Fatal error: %v", r)
-			logf("The installation could not be finalized. Please reboot to continue.")
-			syscall.Pause()
-		}
-	}()
+	bringup.Runnable(installerRunnable).Run()
+}
 
-	// Mount sysfs, devtmpfs and efivarfs.
-	if err := mountPseudoFS(); err != nil {
-		panicf("While mounting pseudo-filesystems: %v", err)
+func installerRunnable(ctx context.Context) error {
+	l := supervisor.Logger(ctx)
+
+	l.Info("Metropolis Installer")
+	l.Info("Copyright (c) 2024 The Monogon Project Authors")
+	l.Info("")
+
+	// Validate we are running via EFI.
+	if _, err := os.Stat("/sys/firmware/efi"); os.IsNotExist(err) {
+		//nolint:ST1005
+		return errors.New("Monogon OS can only be installed on EFI-booted machines, this one is not")
 	}
 
-	go logPiper()
-	logf("Metropolis Installer")
-	logf("Copyright (c) 2023 The Monogon Project Authors")
-	logf("")
-
 	// Read the installer ESP UUID from efivarfs.
 	espUuid, err := efivarfs.ReadLoaderDevicePartUUID()
 	if err != nil {
-		panicf("While reading the installer ESP UUID: %v", err)
+		return fmt.Errorf("while reading the installer ESP UUID: %w", err)
 	}
 	// Wait for up to 30 tries @ 1s (30s) for the ESP to show up
 	var espDev string
@@ -190,34 +166,34 @@
 			time.Sleep(1 * time.Second)
 			retries--
 		} else {
-			panicf("While resolving the installer device handle: %v", err)
+			return fmt.Errorf("while resolving the installer device handle: %w", err)
 		}
 	}
 	espPath := filepath.Join("/dev", espDev)
 	// Mount the installer partition. The installer bundle will be read from it.
 	if err := mountInstallerESP(espPath); err != nil {
-		panicf("While mounting the installer ESP: %v", err)
+		return fmt.Errorf("while mounting the installer ESP: %w", err)
 	}
 
 	nodeParameters, err := os.Open("/installer/metropolis-installer/nodeparams.pb")
 	if err != nil {
-		panicf("Failed to open node parameters from ESP: %v", err)
+		return fmt.Errorf("failed to open node parameters from ESP: %w", err)
 	}
 
 	// TODO(lorenz): Replace with proper bundles
 	bundle, err := zip.OpenReader("/installer/metropolis-installer/bundle.bin")
 	if err != nil {
-		panicf("Failed to open node bundle from ESP: %v", err)
+		return fmt.Errorf("failed to open node bundle from ESP: %w", err)
 	}
 	defer bundle.Close()
 	efiPayload, err := bundle.Open("kernel_efi.efi")
 	if err != nil {
-		panicf("Cannot open EFI payload in bundle: %v", err)
+		return fmt.Errorf("cannot open EFI payload in bundle: %w", err)
 	}
 	defer efiPayload.Close()
 	systemImage, err := bundle.Open("verity_rootfs.img")
 	if err != nil {
-		panicf("Cannot open system image in bundle: %v", err)
+		return fmt.Errorf("cannot open system image in bundle: %w", err)
 	}
 	defer systemImage.Close()
 
@@ -247,10 +223,10 @@
 	// Look for suitable block devices, given the minimum size.
 	blkDevs, err := findInstallableBlockDevices(espDev, minSize)
 	if err != nil {
-		panicf(err.Error())
+		return fmt.Errorf(err.Error())
 	}
 	if len(blkDevs) == 0 {
-		panicf("Couldn't find a suitable block device.")
+		return fmt.Errorf("couldn't find a suitable block device")
 	}
 	// Set the first suitable block device found as the installation target.
 	tgtBlkdevName := blkDevs[0]
@@ -259,31 +235,32 @@
 
 	tgtBlockDev, err := blockdev.Open(tgtBlkdevPath)
 	if err != nil {
-		panicf("error opening target device: %v", err)
+		return fmt.Errorf("error opening target device: %w", err)
 	}
 	installParams.Output = tgtBlockDev
 
 	// Use osimage to partition the target block device and set up its ESP.
 	// Write will return an EFI boot entry on success.
-	logf("Installing to %s...", tgtBlkdevPath)
+	l.Infof("Installing to %s...", tgtBlkdevPath)
 	be, err := osimage.Write(&installParams)
 	if err != nil {
-		panicf("While installing: %v", err)
+		return fmt.Errorf("while installing: %w", err)
 	}
 
 	// Create an EFI boot entry for Metropolis.
 	en, err := efivarfs.AddBootEntry(be)
 	if err != nil {
-		panicf("While creating a boot entry: %v", err)
+		return fmt.Errorf("while creating a boot entry: %w", err)
 	}
 	// Erase the preexisting boot order, leaving Metropolis as the only option.
 	if err := efivarfs.SetBootOrder(efivarfs.BootOrder{uint16(en)}); err != nil {
-		panicf("While adjusting the boot order: %v", err)
+		return fmt.Errorf("while adjusting the boot order: %w", err)
 	}
 
 	// Reboot.
 	tgtBlockDev.Close()
 	unix.Sync()
-	logf("Installation completed. Rebooting.")
+	l.Info("Installation completed. Rebooting.")
 	unix.Reboot(unix.LINUX_REBOOT_CMD_RESTART)
+	return nil
 }
diff --git a/metropolis/installer/test/run_test.go b/metropolis/installer/test/run_test.go
index 64fa210..538ef8f 100644
--- a/metropolis/installer/test/run_test.go
+++ b/metropolis/installer/test/run_test.go
@@ -213,7 +213,7 @@
 	// No block devices are passed to QEMU aside from the install medium. Expect
 	// the installer to fail at the device probe stage rather than attempting to
 	// use the medium as the target device.
-	expectedOutput := "Couldn't find a suitable block device"
+	expectedOutput := "couldn't find a suitable block device"
 	result, err := runQemuWithInstaller(ctx, nil, expectedOutput)
 	if err != nil {
 		t.Error(err.Error())
@@ -236,7 +236,7 @@
 	}
 
 	// Run QEMU. Expect the installer to fail with a predefined error string.
-	expectedOutput := "Couldn't find a suitable block device"
+	expectedOutput := "couldn't find a suitable block device"
 	result, err := runQemuWithInstaller(ctx, qemuDriveParam(imagePath), expectedOutput)
 	if err != nil {
 		t.Error(err.Error())