m/{t,n}/installer: move to m/installer
Change-Id: I8fd15f1fa1e151369df251d1469e84cfeffd26fd
Reviewed-on: https://review.monogon.dev/c/monogon/+/510
Reviewed-by: Sergiusz Bazanski <serge@monogon.tech>
diff --git a/metropolis/node/build/genosrelease/BUILD.bazel b/metropolis/node/build/genosrelease/BUILD.bazel
index bae7f1d..b48b602 100644
--- a/metropolis/node/build/genosrelease/BUILD.bazel
+++ b/metropolis/node/build/genosrelease/BUILD.bazel
@@ -11,5 +11,8 @@
go_binary(
name = "genosrelease",
embed = [":go_default_library"],
- visibility = ["//metropolis/node:__subpackages__"],
+ visibility = [
+ "//metropolis/installer:__subpackages__",
+ "//metropolis/node:__subpackages__",
+ ],
)
diff --git a/metropolis/node/installer/BUILD.bazel b/metropolis/node/installer/BUILD.bazel
deleted file mode 100644
index 9e1a36a..0000000
--- a/metropolis/node/installer/BUILD.bazel
+++ /dev/null
@@ -1,46 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
-load("//metropolis/node/build:def.bzl", "node_initramfs")
-load("//metropolis/node/build/genosrelease:defs.bzl", "os_release")
-load("//metropolis/node/build:efi.bzl", "efi_unified_kernel_image")
-
-go_library(
- name = "go_default_library",
- srcs = ["main.go"],
- importpath = "source.monogon.dev/metropolis/node/installer",
- visibility = ["//visibility:private"],
- deps = [
- "//metropolis/node/build/mkimage/osimage:go_default_library",
- "//metropolis/pkg/efivarfs:go_default_library",
- "//metropolis/pkg/sysfs:go_default_library",
- "@org_golang_x_sys//unix:go_default_library",
- ],
-)
-
-go_binary(
- name = "installer",
- embed = [":go_default_library"],
- visibility = ["//visibility:private"],
-)
-
-node_initramfs(
- name = "initramfs",
- files = {
- "//metropolis/node/installer": "/init",
- },
- visibility = ["//metropolis/test/installer:__pkg__"],
-)
-
-os_release(
- name = "installer-release-info",
- os_id = "metropolis-installer",
- os_name = "Metropolis Installer",
- stamp_var = "STABLE_METROPOLIS_version",
-)
-
-efi_unified_kernel_image(
- name = "kernel",
- initramfs = ":initramfs",
- kernel = "//third_party/linux",
- os_release = ":installer-release-info",
- visibility = ["//visibility:public"],
-)
diff --git a/metropolis/node/installer/main.go b/metropolis/node/installer/main.go
deleted file mode 100644
index e3a4896..0000000
--- a/metropolis/node/installer/main.go
+++ /dev/null
@@ -1,320 +0,0 @@
-// Copyright 2020 The Monogon Project Authors.
-//
-// SPDX-License-Identifier: Apache-2.0
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Installer creates a Metropolis image at a suitable block device based on the
-// installer bundle present in the installation medium's ESP, after which it
-// reboots. It's meant to be used as an init process.
-package main
-
-import (
- "archive/zip"
- "fmt"
- "io"
- "os"
- "path/filepath"
- "strings"
- "syscall"
-
- "golang.org/x/sys/unix"
-
- "source.monogon.dev/metropolis/node/build/mkimage/osimage"
- "source.monogon.dev/metropolis/pkg/efivarfs"
- "source.monogon.dev/metropolis/pkg/sysfs"
-)
-
-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.
-func mountInstallerESP(espPath string) error {
- // Create the mountpoint.
- if err := unix.Mkdir("/installer", 0700); err != nil {
- return fmt.Errorf("couldn't create the installer mountpoint: %w", err)
- }
- // Mount the filesystem.
- if err := unix.Mount(espPath, "/installer", "vfat", unix.MS_NOEXEC|unix.MS_RDONLY, ""); err != nil {
- return fmt.Errorf("couldn't mount the installer ESP (%q -> %q): %w", espPath, "/installer", err)
- }
- return nil
-}
-
-// findInstallableBlockDevices returns names of all the block devices suitable
-// for hosting a Metropolis installation, limited by the size expressed in
-// bytes minSize. The install medium espDev will be excluded from the result.
-func findInstallableBlockDevices(espDev string, minSize uint64) ([]string, error) {
- // Use the partition's name to find and return the name of its parent
- // device. It will be excluded from the list of suitable target devices.
- srcDev, err := sysfs.ParentBlockDevice(espDev)
- // Build the exclusion list containing forbidden handle prefixes.
- exclude := []string{"dm-", "zram", "ram", "loop", srcDev}
-
- // Get the block device handles by looking up directory contents.
- const blkDirPath = "/sys/class/block"
- blkDevs, err := os.ReadDir(blkDirPath)
- if err != nil {
- return nil, fmt.Errorf("couldn't read %q: %w", blkDirPath, err)
- }
- // Iterate over the handles, skipping any block device that either points to
- // a partition, matches the exclusion list, or is smaller than minSize.
- var suitable []string
-probeLoop:
- for _, devInfo := range blkDevs {
- // Skip devices according to the exclusion list.
- for _, prefix := range exclude {
- if strings.HasPrefix(devInfo.Name(), prefix) {
- continue probeLoop
- }
- }
-
- // Skip partition symlinks.
- if _, err := os.Stat(filepath.Join(blkDirPath, devInfo.Name(), "partition")); err == nil {
- continue
- } else if !os.IsNotExist(err) {
- return nil, fmt.Errorf("while probing sysfs: %w", err)
- }
-
- // Skip devices of insufficient size.
- devPath := filepath.Join("/dev", devInfo.Name())
- dev, err := os.Open(devPath)
- if err != nil {
- return nil, fmt.Errorf("couldn't open a block device at %q: %w", devPath, err)
- }
- size, err := unix.IoctlGetInt(int(dev.Fd()), unix.BLKGETSIZE64)
- dev.Close()
- if err != nil {
- return nil, fmt.Errorf("couldn't probe the size of %q: %w", devPath, err)
- }
- if uint64(size) < minSize {
- continue
- }
-
- suitable = append(suitable, devInfo.Name())
- }
- return suitable, nil
-}
-
-// rereadPartitionTable causes the kernel to read the partition table present
-// in the block device at blkdevPath. It may return an error.
-func rereadPartitionTable(blkdevPath string) error {
- dev, err := os.Open(blkdevPath)
- if err != nil {
- return fmt.Errorf("couldn't open the block device at %q: %w", blkdevPath, err)
- }
- defer dev.Close()
- ret, err := unix.IoctlRetInt(int(dev.Fd()), unix.BLKRRPART)
- if err != nil {
- return fmt.Errorf("while doing an ioctl: %w", err)
- }
- if syscall.Errno(ret) == unix.EINVAL {
- return fmt.Errorf("got an EINVAL from BLKRRPART ioctl")
- }
- return nil
-}
-
-// initializeSystemPartition writes image contents to the node's system
-// partition using the block device abstraction layer as opposed to slower
-// go-diskfs. tgtBlkdev must contain a path pointing to the block device
-// associated with the system partition. It may return an error.
-func initializeSystemPartition(image io.Reader, tgtBlkdev string) error {
- // Check that tgtBlkdev points at an actual block device.
- info, err := os.Stat(tgtBlkdev)
- if err != nil {
- return fmt.Errorf("couldn't stat the system partition at %q: %w", tgtBlkdev, err)
- }
- if info.Mode()&os.ModeDevice == 0 {
- return fmt.Errorf("system partition path %q doesn't point to a block device", tgtBlkdev)
- }
-
- // Get the system partition's file descriptor.
- sys, err := os.OpenFile(tgtBlkdev, os.O_WRONLY, 0600)
- if err != nil {
- return fmt.Errorf("couldn't open the system partition at %q: %w", tgtBlkdev, err)
- }
- defer sys.Close()
- // Copy the system partition contents. Use a bigger buffer to optimize disk
- // writes.
- buf := make([]byte, mib)
- if _, err := io.CopyBuffer(sys, image, buf); err != nil {
- return fmt.Errorf("couldn't copy partition contents: %w", err)
- }
- return nil
-}
-
-// panicf is a replacement for log.panicf that doesn't print the error message
-// before calling panic.
-func panicf(format string, v ...interface{}) {
- s := fmt.Sprintf(format, v...)
- panic(s)
-}
-
-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 {
- fmt.Println(r)
- fmt.Println("The installation could not be finalized. Please reboot to continue.")
- syscall.Pause()
- }
- }()
-
- // Mount sysfs, devtmpfs and efivarfs.
- if err := mountPseudoFS(); err != nil {
- panicf("While mounting pseudo-filesystems: %v", err)
- }
- // Read the installer ESP UUID from efivarfs.
- espUuid, err := efivarfs.ReadLoaderDevicePartUUID()
- if err != nil {
- panicf("While reading the installer ESP UUID: %v", err)
- }
- // Look up the installer partition based on espUuid.
- espDev, err := sysfs.DeviceByPartUUID(espUuid)
- espPath := filepath.Join("/dev", espDev)
- if err != nil {
- panicf("While resolving the installer device handle: %v", err)
- }
- // 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)
- }
-
- nodeParameters, err := os.Open("/installer/metropolis-installer/nodeparams.pb")
- if err != nil {
- panicf("Failed to open node parameters from ESP: %v", 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)
- }
- defer bundle.Close()
- efiPayload, err := bundle.Open("kernel_efi.efi")
- if err != nil {
- panicf("Cannot open EFI payload in bundle: %v", err)
- }
- defer efiPayload.Close()
- systemImage, err := bundle.Open("rootfs.img")
- if err != nil {
- panicf("Cannot open system image in bundle: %v", err)
- }
- defer systemImage.Close()
-
- // Build the osimage parameters.
- installParams := osimage.Params{
- PartitionSize: osimage.PartitionSizeInfo{
- // ESP is the size of the node ESP partition, expressed in mebibytes.
- ESP: 128,
- // System is the size of the node system partition, expressed in
- // mebibytes.
- System: 4096,
- // Data must be nonzero in order for the data partition to be created.
- // osimage will extend the data partition to fill all the available space
- // whenever it's writing to block devices, such as now.
- Data: 128,
- },
- // Due to a bug in go-diskfs causing slow writes, SystemImage is explicitly
- // marked unused here, as system partition contents will be written using
- // a workaround below instead.
- // TODO(mateusz@monogon.tech): Address that bug either by patching go-diskfs
- // or rewriting osimage.
- SystemImage: nil,
-
- EFIPayload: efiPayload,
- NodeParameters: nodeParameters,
- }
- // Calculate the minimum target size based on the installation parameters.
- minSize := uint64((installParams.PartitionSize.ESP +
- installParams.PartitionSize.System +
- installParams.PartitionSize.Data + 1) * mib)
-
- // Look for suitable block devices, given the minimum size.
- blkDevs, err := findInstallableBlockDevices(espDev, minSize)
- if err != nil {
- panicf(err.Error())
- }
- if len(blkDevs) == 0 {
- panicf("Couldn't find a suitable block device.")
- }
- // Set the first suitable block device found as the installation target.
- tgtBlkdevName := blkDevs[0]
- // Update the osimage parameters with a path pointing at the target device.
- tgtBlkdevPath := filepath.Join("/dev", tgtBlkdevName)
- installParams.OutputPath = tgtBlkdevPath
-
- // Use osimage to partition the target block device and set up its ESP.
- // Create will return an EFI boot entry on success.
- fmt.Printf("Installing to %s\n", tgtBlkdevPath)
- be, err := osimage.Create(&installParams)
- if err != nil {
- panicf("While installing: %v", err)
- }
- // The target device's partition table has just been updated. Re-read it to
- // make the node system partition reachable through /dev.
- if err := rereadPartitionTable(tgtBlkdevPath); err != nil {
- panicf("While re-reading the partition table of %q: %v", tgtBlkdevPath, err)
- }
- // Look up the node's system partition path to be later used in the
- // initialization step. It's always the second partition, right after
- // the ESP.
- sysBlkdevName, err := sysfs.PartitionBlockDevice(tgtBlkdevName, 2)
- if err != nil {
- panicf("While looking up the system partition: %v", err)
- }
- sysBlkdevPath := filepath.Join("/dev", sysBlkdevName)
- // Copy the system partition contents.
- if err := initializeSystemPartition(systemImage, sysBlkdevPath); err != nil {
- panicf("While initializing the system partition at %q: %v", sysBlkdevPath, err)
- }
-
- // Create an EFI boot entry for Metropolis.
- en, err := efivarfs.CreateBootEntry(be)
- if err != nil {
- panicf("While creating a boot entry: %v", 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)
- }
-
- // Reboot.
- unix.Sync()
- fmt.Println("Installation completed. Rebooting.")
- unix.Reboot(unix.LINUX_REBOOT_CMD_RESTART)
-}