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/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
 }