treewide: move build helper to more fitting places

Change-Id: I3d0cfe9283222d403ae369ec9db09201ad511e15
Reviewed-on: https://review.monogon.dev/c/monogon/+/3327
Reviewed-by: Serge Bazanski <serge@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/build/gotoolwrap/BUILD.bazel b/metropolis/build/gotoolwrap/BUILD.bazel
deleted file mode 100644
index 281f4b3..0000000
--- a/metropolis/build/gotoolwrap/BUILD.bazel
+++ /dev/null
@@ -1,14 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
-
-go_library(
-    name = "gotoolwrap_lib",
-    srcs = ["main.go"],
-    importpath = "source.monogon.dev/metropolis/build/gotoolwrap",
-    visibility = ["//visibility:private"],
-)
-
-go_binary(
-    name = "gotoolwrap",
-    embed = [":gotoolwrap_lib"],
-    visibility = ["//visibility:public"],
-)
diff --git a/metropolis/build/gotoolwrap/main.go b/metropolis/build/gotoolwrap/main.go
deleted file mode 100644
index 343b7ff..0000000
--- a/metropolis/build/gotoolwrap/main.go
+++ /dev/null
@@ -1,193 +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.
-
-// gotoolwrap is a tiny wrapper used to run executables that expect a standard
-// Go development environment setup to act on the monogon workspace. Notably,
-// it's used in Bazel rules to run tools like gofmt when invoked by some kinds
-// of code generation tools.
-//
-// Usage: ./gotoolwrap executable arg1 arg2
-//
-// gotoolwrap expects the following environment variables to be set (and unsets
-// them before calling the given executable):
-//  - GOTOOLWRAP_GOPATH: A synthetic GOPATH, eg. one generated by rules_go's
-//  				     go_path target.
-//  - GOTOOLWRAP_GOROOT: A Go SDK's GOROOT, eg. one from rules_go's GoSDK
-//                       provider.
-//  - GOTOOLWRAP_COPYOUT: Pairs of source:destination elements separated by ;.
-//                        Files given in this list will be copied from source
-//                        (relative to the GOPATH sources) to the destination
-//                        (absolute path). This is primarily useful when dealing
-//                        with code generators which insist on putting their
-//                        generated files within the GOPATH, and allows copying
-//                        of said files to a declared output file.
-//
-// gotoolwrap will set PATH to contain GOROOT/bin, and set GOPATH and GOROOT as
-// resolved, absolute paths. Absolute paths are expected by tools like 'gofmt'.
-
-package main
-
-import (
-	"errors"
-	"fmt"
-	"log"
-	"os"
-	"os/exec"
-	"path/filepath"
-	"strings"
-)
-
-func main() {
-	gopath := os.Getenv("GOTOOLWRAP_GOPATH")
-	if gopath == "" {
-		log.Fatal("GOTOOLWRAP_GOPATH must be set")
-	}
-
-	goroot := os.Getenv("GOTOOLWRAP_GOROOT")
-	if goroot == "" {
-		log.Fatal("GOTOOLWRAP_GOROOT must be set")
-	}
-
-	if len(os.Args) < 2 {
-		log.Fatalf("No command specified")
-	}
-
-	// Resolve gopath and goroot to absolute paths.
-	gopathAbs, err := filepath.Abs(gopath)
-	if err != nil {
-		log.Fatalf("Abs(%q): %v", gopath, err)
-	}
-	gorootAbs, err := filepath.Abs(goroot)
-	if err != nil {
-		log.Fatalf("Abs(%q): %v", goroot, err)
-	}
-
-	// Ensure the resolved GOROOT has a bin/go and bin/gofmt.
-	gorootBin := filepath.Join(gorootAbs, "bin")
-	stat, err := os.Stat(gorootBin)
-	if err != nil {
-		log.Fatalf("Could not stat $GOTOOLWRAP_GOROOT/bin (%q): %v", gorootBin, err)
-	}
-	if !stat.IsDir() {
-		log.Fatalf("$GOTOOLWRAP_GOROOT/bin (%q) is not a directory", gorootBin)
-	}
-	// We list all files inside so that we can print them to the user for
-	// debugging purposes if that's not the case.
-	binFiles := make(map[string]bool)
-	files, err := os.ReadDir(gorootBin)
-	if err != nil {
-		log.Fatalf("Could not read dir $GOTOOLWRAP_GOROOT/bin (%q): %v", gorootBin, err)
-	}
-	for _, f := range files {
-		if f.IsDir() {
-			continue
-		}
-		binFiles[f.Name()] = true
-	}
-	if !binFiles["go"] || !binFiles["gofmt"] {
-		msg := "no files"
-		if len(binFiles) > 0 {
-			var names []string
-			for name := range binFiles {
-				names = append(names, fmt.Sprintf("%q", name))
-			}
-			msg = fmt.Sprintf(": %s", strings.Join(names, ", "))
-		}
-		log.Fatalf("$GOTOOLWRAP_GOROOT/bin (%q) does not contain go and/or gofmt, found %s", gorootBin, msg)
-	}
-
-	// Make new PATH.
-	path := os.Getenv("PATH")
-	if path == "" {
-		path = gorootBin
-	} else {
-		path = fmt.Sprintf("%s:%s", gorootBin, path)
-	}
-
-	cmd := exec.Command(os.Args[1], os.Args[2:]...)
-
-	// Copy current env into command's env, filtering out GOTOOLWRAP env vars
-	// and PATH (which we set ourselves).
-	for _, v := range os.Environ() {
-		if strings.HasPrefix(v, "GOTOOLWRAP_GOROOT=") {
-			continue
-		}
-		if strings.HasPrefix(v, "GOTOOLWRAP_GOPATH=") {
-			continue
-		}
-		if strings.HasPrefix(v, "GOTOOLWRAP_COPYOUT=") {
-			continue
-		}
-		if strings.HasPrefix(v, "PATH=") {
-			continue
-		}
-		cmd.Env = append(cmd.Env, v)
-	}
-	// Create a temporary cache directory and remove everything afterwards.
-	// Based on code from
-	// @io_bazel_rules_go//go/tools/builders:stdliblist.go L174
-	tempDir, err := os.MkdirTemp("", "gocache")
-	if err != nil {
-		log.Fatalf("Cannot create temporary directory: %v", err)
-	}
-	defer os.RemoveAll(tempDir)
-
-	cmd.Env = append(cmd.Env,
-		"GOROOT="+gorootAbs,
-		"GOPATH="+gopathAbs,
-		"PATH="+path,
-		"GO111MODULE=off",
-		"GOCACHE="+tempDir,
-	)
-
-	// Run the command interactively.
-	cmd.Stdout = os.Stdout
-	cmd.Stderr = os.Stderr
-	cmd.Stdin = os.Stdin
-	if err := cmd.Run(); err != nil {
-		var exitErr *exec.ExitError
-		if errors.As(err, &exitErr) {
-			os.Exit(exitErr.ExitCode())
-		} else {
-			log.Fatalf("Could not run %q: %v", os.Args[1], err)
-		}
-	}
-
-	copyout := os.Getenv("GOTOOLWRAP_COPYOUT")
-	if len(copyout) != 0 {
-		for _, pair := range strings.Split(copyout, ";") {
-			parts := strings.Split(pair, ":")
-			if len(parts) != 2 {
-				log.Fatalf("GOTOOL_COPYOUT invalid pair: %q", pair)
-			}
-			from := filepath.Join(gopathAbs, "src", parts[0])
-			to := parts[1]
-			log.Printf("gotoolwrap: Copying %s to %s...", from, to)
-			data, err := os.ReadFile(from)
-			if err != nil {
-				log.Fatalf("gotoolwrap: read failed: %v", err)
-			}
-			err = os.MkdirAll(filepath.Dir(to), 0755)
-			if err != nil {
-				log.Fatalf("gotoolwrap: mkdir failed: %v", err)
-			}
-			err = os.WriteFile(to, data, 0644)
-			if err != nil {
-				log.Fatalf("gotoolwrap: write failed: %v", err)
-			}
-		}
-	}
-}
diff --git a/metropolis/build/kube-code-generator/boilerplate.go.txt b/metropolis/build/kube-code-generator/boilerplate.go.txt
index ef05f6f..c097ae1 100644
--- a/metropolis/build/kube-code-generator/boilerplate.go.txt
+++ b/metropolis/build/kube-code-generator/boilerplate.go.txt
@@ -1 +1 @@
-// Code generated by //metropolis/build/kubernetes-code-generator. Do not commit to source control.
+// Code generated by //osbase/build/kubernetes-code-generator. Do not commit to source control.
diff --git a/metropolis/build/kube-code-generator/defs.bzl b/metropolis/build/kube-code-generator/defs.bzl
index 7d431ef..2bc32bf 100644
--- a/metropolis/build/kube-code-generator/defs.bzl
+++ b/metropolis/build/kube-code-generator/defs.bzl
@@ -168,7 +168,7 @@
 )
 
 # _gotool_run is a helper function which runs an executable under
-# //metropolis/build/gotoolwrap, effectively setting up everything required to
+# //osbase/build/gotoolwrap, effectively setting up everything required to
 # use standard Go tooling on the monogon workspace (ie. GOPATH/GOROOT). This is
 # required by generators to run 'go fmt'.
 #
@@ -524,7 +524,7 @@
             default = "@io_bazel_rules_go//:go_context_data",
         ),
         "_gotoolwrap": attr.label(
-            default = Label("//metropolis/build/gotoolwrap"),
+            default = Label("//build/gotoolwrap"),
             allow_single_file = True,
             executable = True,
             cfg = "exec",
diff --git a/metropolis/installer/BUILD.bazel b/metropolis/installer/BUILD.bazel
index a0d1993..29eac44 100644
--- a/metropolis/installer/BUILD.bazel
+++ b/metropolis/installer/BUILD.bazel
@@ -1,7 +1,7 @@
 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")
+load("//osbase/build:def.bzl", "node_initramfs")
+load("//osbase/build/genosrelease:defs.bzl", "os_release")
+load("//osbase/build:efi.bzl", "efi_unified_kernel_image")
 
 go_library(
     name = "installer_lib",
@@ -15,8 +15,8 @@
     importpath = "source.monogon.dev/metropolis/installer",
     visibility = ["//visibility:private"],
     deps = [
-        "//metropolis/node/build/mkimage/osimage",
         "//osbase/blockdev",
+        "//osbase/build/mkimage/osimage",
         "//osbase/efivarfs",
         "//osbase/sysfs",
         "@org_golang_x_sys//unix",
@@ -35,7 +35,7 @@
         "//metropolis/installer": "/init",
     },
     fsspecs = [
-        "//metropolis/node/build:earlydev.fsspec",
+        "//osbase/build:earlydev.fsspec",
     ],
     visibility = ["//metropolis/installer/test:__pkg__"],
 )
diff --git a/metropolis/installer/main.go b/metropolis/installer/main.go
index bc428a0..e2d0d55 100644
--- a/metropolis/installer/main.go
+++ b/metropolis/installer/main.go
@@ -34,8 +34,8 @@
 
 	"golang.org/x/sys/unix"
 
-	"source.monogon.dev/metropolis/node/build/mkimage/osimage"
 	"source.monogon.dev/osbase/blockdev"
+	"source.monogon.dev/osbase/build/mkimage/osimage"
 	"source.monogon.dev/osbase/efivarfs"
 	"source.monogon.dev/osbase/sysfs"
 )
diff --git a/metropolis/installer/test/BUILD.bazel b/metropolis/installer/test/BUILD.bazel
index 3f74cde..9971209 100644
--- a/metropolis/installer/test/BUILD.bazel
+++ b/metropolis/installer/test/BUILD.bazel
@@ -1,5 +1,5 @@
 load("@io_bazel_rules_go//go:def.bzl", "go_test")
-load("//metropolis/node/build:efi.bzl", "efi_unified_kernel_image")
+load("//osbase/build:efi.bzl", "efi_unified_kernel_image")
 
 go_test(
     name = "test_test",
@@ -23,8 +23,8 @@
     },
     deps = [
         "//metropolis/cli/metroctl/core",
-        "//metropolis/node/build/mkimage/osimage",
         "//metropolis/proto/api",
+        "//osbase/build/mkimage/osimage",
         "//osbase/cmd",
         "@com_github_diskfs_go_diskfs//:go-diskfs",
         "@com_github_diskfs_go_diskfs//disk",
diff --git a/metropolis/installer/test/run_test.go b/metropolis/installer/test/run_test.go
index fef8e20..64fa210 100644
--- a/metropolis/installer/test/run_test.go
+++ b/metropolis/installer/test/run_test.go
@@ -38,7 +38,7 @@
 	"source.monogon.dev/metropolis/proto/api"
 
 	mctl "source.monogon.dev/metropolis/cli/metroctl/core"
-	"source.monogon.dev/metropolis/node/build/mkimage/osimage"
+	"source.monogon.dev/osbase/build/mkimage/osimage"
 	"source.monogon.dev/osbase/cmd"
 )
 
diff --git a/metropolis/installer/test/testos/BUILD.bazel b/metropolis/installer/test/testos/BUILD.bazel
index 8d5f267..d1ae0df 100644
--- a/metropolis/installer/test/testos/BUILD.bazel
+++ b/metropolis/installer/test/testos/BUILD.bazel
@@ -1,6 +1,6 @@
 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
-load("//metropolis/node/build:def.bzl", "erofs_image", "verity_image")
-load("//metropolis/node/build:efi.bzl", "efi_unified_kernel_image")
+load("//osbase/build:def.bzl", "erofs_image", "verity_image")
+load("//osbase/build:efi.bzl", "efi_unified_kernel_image")
 load("@rules_pkg//:pkg.bzl", "pkg_zip")
 
 erofs_image(
diff --git a/metropolis/node/BUILD.bazel b/metropolis/node/BUILD.bazel
index 6354edc..a0847f8 100644
--- a/metropolis/node/BUILD.bazel
+++ b/metropolis/node/BUILD.bazel
@@ -1,7 +1,7 @@
 load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
-load("//metropolis/node/build:def.bzl", "erofs_image", "verity_image")
-load("//metropolis/node/build:efi.bzl", "efi_unified_kernel_image")
-load("//metropolis/node/build/mkimage:def.bzl", "node_image")
+load("//osbase/build:def.bzl", "erofs_image", "verity_image")
+load("//osbase/build:efi.bzl", "efi_unified_kernel_image")
+load("//osbase/build/mkimage:def.bzl", "node_image")
 load("@rules_pkg//:pkg.bzl", "pkg_zip")
 
 go_library(
@@ -89,7 +89,7 @@
     },
     fsspecs = [
         ":erofs-layout.fsspec",
-        "//metropolis/node/build:earlydev.fsspec",
+        "//osbase/build:earlydev.fsspec",
         "//third_party:firmware",
     ],
     symlinks = {
@@ -135,7 +135,7 @@
     ],
 )
 
-load("//metropolis/node/build/genosrelease:defs.bzl", "os_release")
+load("//osbase/build/genosrelease:defs.bzl", "os_release")
 
 os_release(
     name = "os-release-info",
diff --git a/metropolis/node/build/BUILD.bazel b/metropolis/node/build/BUILD.bazel
deleted file mode 100644
index 8eafa9d..0000000
--- a/metropolis/node/build/BUILD.bazel
+++ /dev/null
@@ -1 +0,0 @@
-exports_files(["earlydev.fsspec"])
diff --git a/metropolis/node/build/def.bzl b/metropolis/node/build/def.bzl
deleted file mode 100644
index d56d78a..0000000
--- a/metropolis/node/build/def.bzl
+++ /dev/null
@@ -1,403 +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.
-load("@bazel_skylib//lib:paths.bzl", "paths")
-
-def _build_pure_transition_impl(settings, attr):
-    """
-    Transition that enables pure, static build of Go binaries.
-    """
-    race = settings['@io_bazel_rules_go//go/config:race']
-    pure = not race
-
-    return {
-        "@io_bazel_rules_go//go/config:pure": pure,
-        "@io_bazel_rules_go//go/config:static": True,
-    }
-
-build_pure_transition = transition(
-    implementation = _build_pure_transition_impl,
-    inputs = [
-        "@io_bazel_rules_go//go/config:race",
-    ],
-    outputs = [
-        "@io_bazel_rules_go//go/config:pure",
-        "@io_bazel_rules_go//go/config:static",
-    ],
-)
-
-def _build_static_transition_impl(settings, attr):
-    """
-    Transition that enables static builds with CGo and musl for Go binaries.
-    """
-    return {
-        "@io_bazel_rules_go//go/config:static": True,
-        "//command_line_option:platforms": "//build/platforms:linux_amd64_static",
-    }
-
-build_static_transition = transition(
-    implementation = _build_static_transition_impl,
-    inputs = [],
-    outputs = [
-        "@io_bazel_rules_go//go/config:static",
-        "//command_line_option:platforms",
-    ],
-)
-
-FSSpecInfo = provider(
-    "Provides parts of an FSSpec used to assemble filesystem images",
-    fields = {
-        "spec": "File containing the partial FSSpec as prototext",
-        "referenced": "Files (potentially) referenced by the spec",
-    },
-)
-
-def _fsspec_core_impl(ctx, tool, output_file):
-    """
-    _fsspec_core_impl implements the core of an fsspec-based rule. It takes
-    input from the `files`,`files_cc`, `symlinks` and `fsspecs` attributes
-    and calls `tool` with the `-out` parameter pointing to `output_file`
-    and paths to all fsspecs as positional arguments.
-    """
-    fs_spec_name = ctx.label.name + ".prototxt"
-    fs_spec = ctx.actions.declare_file(fs_spec_name)
-
-    fs_files = []
-    inputs = []
-    for label, p in ctx.attr.files.items() + ctx.attr.files_cc.items():
-        if not p.startswith("/"):
-            fail("file {} invalid: must begin with /".format(p))
-
-        # Figure out if this is an executable.
-        is_executable = True
-
-        di = label[DefaultInfo]
-        if di.files_to_run.executable == None:
-            # Generated non-executable files will have DefaultInfo.files_to_run.executable == None
-            is_executable = False
-        elif di.files_to_run.executable.is_source:
-            # Source files will have executable.is_source == True
-            is_executable = False
-
-        # Ensure only single output is declared.
-        # If you hit this error, figure out a better logic to find what file you need, maybe looking at providers other
-        # than DefaultInfo.
-        files = di.files.to_list()
-        if len(files) > 1:
-            fail("file {} has more than one output: {}", p, files)
-        src = files[0]
-        inputs.append(src)
-
-        mode = 0o555 if is_executable else 0o444
-        fs_files.append(struct(path = p, source_path = src.path, mode = mode, uid = 0, gid = 0))
-
-    fs_symlinks = []
-    for target, p in ctx.attr.symlinks.items():
-        fs_symlinks.append(struct(path = p, target_path = target))
-
-    fs_spec_content = struct(file = fs_files, directory = [], symbolic_link = fs_symlinks)
-    ctx.actions.write(fs_spec, proto.encode_text(fs_spec_content))
-
-    extra_specs = []
-
-    for fsspec in ctx.attr.fsspecs:
-        if FSSpecInfo in fsspec:
-            fsspecInfo = fsspec[FSSpecInfo]
-            extra_specs.append(fsspecInfo.spec)
-            for f in fsspecInfo.referenced:
-                inputs.append(f)
-        else:
-            # Raw .fsspec prototext. No referenced data allowed.
-            di = fsspec[DefaultInfo]
-            extra_specs += di.files.to_list()
-
-    ctx.actions.run(
-        outputs = [output_file],
-        inputs = [fs_spec] + inputs + extra_specs,
-        tools = [tool],
-        executable = tool,
-        arguments = ["-out", output_file.path, fs_spec.path] + [s.path for s in extra_specs],
-    )
-    return
-
-def _node_initramfs_impl(ctx):
-    initramfs_name = ctx.label.name + ".cpio.zst"
-    initramfs = ctx.actions.declare_file(initramfs_name)
-
-    _fsspec_core_impl(ctx, ctx.executable._mkcpio, initramfs)
-
-    # TODO(q3k): Document why this is needed
-    return [DefaultInfo(runfiles = ctx.runfiles(files = [initramfs]), files = depset([initramfs]))]
-
-node_initramfs = rule(
-    implementation = _node_initramfs_impl,
-    doc = """
-        Build a node initramfs. The initramfs will contain a basic /dev directory and all the files specified by the
-        `files` attribute. Executable files will have their permissions set to 0755, non-executable files will have
-        their permissions set to 0444. All parent directories will be created with 0755 permissions.
-    """,
-    attrs = {
-        "files": attr.label_keyed_string_dict(
-            mandatory = True,
-            allow_files = True,
-            doc = """
-                Dictionary of Labels to String, placing a given Label's output file in the initramfs at the location
-                specified by the String value. The specified labels must only have a single output.
-            """,
-            # Attach pure transition to ensure all binaries added to the initramfs are pure/static binaries.
-            cfg = build_pure_transition,
-        ),
-        "files_cc": attr.label_keyed_string_dict(
-            allow_files = True,
-            doc = """
-                 Special case of 'files' for compilation targets that need to be built with the musl toolchain like
-                 go_binary targets which need cgo or cc_binary targets.
-            """,
-            # Attach static transition to all files_cc inputs to ensure they are built with musl and static.
-            cfg = build_static_transition,
-        ),
-        "symlinks": attr.string_dict(
-            default = {},
-            doc = """
-                Symbolic links to create. Similar format as in files and files_cc, so the target of the symlink is the
-                key and the value of it is the location of the symlink itself. Only raw strings are allowed as targets,
-                labels are not permitted. Include the file using files or files_cc, then symlink to its location.
-            """,
-        ),
-        "fsspecs": attr.label_list(
-            default = [],
-            doc = """
-                List of file system specs (metropolis.node.build.fsspec.FSSpec) to also include in the resulting image.
-                These will be merged with all other given attributes.
-            """,
-            providers = [FSSpecInfo],
-            allow_files = True,
-        ),
-
-        # Tool
-        "_mkcpio": attr.label(
-            default = Label("//metropolis/node/build/mkcpio"),
-            executable = True,
-            cfg = "exec",
-        ),
-    },
-)
-
-def _erofs_image_impl(ctx):
-    fs_name = ctx.label.name + ".img"
-    fs_out = ctx.actions.declare_file(fs_name)
-
-    _fsspec_core_impl(ctx, ctx.executable._mkerofs, fs_out)
-
-    return [DefaultInfo(files = depset([fs_out]))]
-
-erofs_image = rule(
-    implementation = _erofs_image_impl,
-    doc = """
-        Build an EROFS. All files specified in files, files_cc and all specified symlinks will be contained.
-        Executable files will have their permissions set to 0555, non-executable files will have
-        their permissions set to 0444. All parent directories will be created with 0555 permissions.
-    """,
-    attrs = {
-        "files": attr.label_keyed_string_dict(
-            mandatory = True,
-            allow_files = True,
-            doc = """
-                Dictionary of Labels to String, placing a given Label's output file in the EROFS at the location
-                specified by the String value. The specified labels must only have a single output.
-            """,
-            # Attach pure transition to ensure all binaries added to the initramfs are pure/static binaries.
-            cfg = build_pure_transition,
-        ),
-        "files_cc": attr.label_keyed_string_dict(
-            allow_files = True,
-            doc = """
-                 Special case of 'files' for compilation targets that need to be built with the musl toolchain like
-                 go_binary targets which need cgo or cc_binary targets.
-            """,
-            # Attach static transition to all files_cc inputs to ensure they are built with musl and static.
-            cfg = build_static_transition,
-        ),
-        "symlinks": attr.string_dict(
-            default = {},
-            doc = """
-                Symbolic links to create. Similar format as in files and files_cc, so the target of the symlink is the
-                key and the value of it is the location of the symlink itself. Only raw strings are allowed as targets,
-                labels are not permitted. Include the file using files or files_cc, then symlink to its location.
-          """,
-        ),
-        "fsspecs": attr.label_list(
-            default = [],
-            doc = """
-                List of file system specs (metropolis.node.build.fsspec.FSSpec) to also include in the resulting image.
-                These will be merged with all other given attributes.
-            """,
-            providers = [FSSpecInfo],
-            allow_files = True,
-        ),
-
-        # Tools, implicit dependencies.
-        "_mkerofs": attr.label(
-            default = Label("//metropolis/node/build/mkerofs"),
-            executable = True,
-            cfg = "host",
-        ),
-    },
-)
-
-# VerityConfig is emitted by verity_image, and contains a file enclosing a
-# singular dm-verity target table.
-VerityConfig = provider(
-    "Configuration necessary to mount a single dm-verity target.",
-    fields = {
-        "table": "A file containing the dm-verity target table. See: https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/verity.html",
-    },
-)
-
-def _verity_image_impl(ctx):
-    """
-    Create a new file containing the source image data together with the Verity
-    metadata appended to it, and provide an associated DeviceMapper Verity target
-    table in a separate file, through VerityConfig provider.
-    """
-
-    # Run mkverity.
-    image = ctx.actions.declare_file(ctx.attr.name + ".img")
-    table = ctx.actions.declare_file(ctx.attr.name + ".dmt")
-    ctx.actions.run(
-        mnemonic = "GenVerityImage",
-        progress_message = "Generating a dm-verity image",
-        inputs = [ctx.file.source],
-        outputs = [
-            image,
-            table,
-        ],
-        executable = ctx.file._mkverity,
-        arguments = [
-            "-input=" + ctx.file.source.path,
-            "-output=" + image.path,
-            "-table=" + table.path,
-            "-data_alias=" + ctx.attr.rootfs_partlabel,
-            "-hash_alias=" + ctx.attr.rootfs_partlabel,
-        ],
-    )
-
-    return [
-        DefaultInfo(
-            files = depset([image]),
-            runfiles = ctx.runfiles(files = [image]),
-        ),
-        VerityConfig(
-            table = table,
-        ),
-    ]
-
-verity_image = rule(
-    implementation = _verity_image_impl,
-    doc = """
-      Build a dm-verity target image by appending Verity metadata to the source
-      image. A corresponding dm-verity target table will be made available
-      through VerityConfig provider.
-  """,
-    attrs = {
-        "source": attr.label(
-            doc = "A source image.",
-            allow_single_file = True,
-        ),
-        "rootfs_partlabel": attr.string(
-            doc = "GPT partition label of the rootfs to be used with dm-mod.create.",
-            default = "PARTLABEL=METROPOLIS-SYSTEM-X",
-        ),
-        "_mkverity": attr.label(
-            doc = "The mkverity executable needed to generate the image.",
-            default = "//metropolis/node/build/mkverity",
-            allow_single_file = True,
-            executable = True,
-            cfg = "host",
-        ),
-    },
-)
-
-# From Aspect's bazel-lib under Apache 2.0
-def _transition_platform_impl(_, attr):
-    return {"//command_line_option:platforms": str(attr.target_platform)}
-
-# Transition from any input configuration to one that includes the
-# --platforms command-line flag.
-_transition_platform = transition(
-    implementation = _transition_platform_impl,
-    inputs = [],
-    outputs = ["//command_line_option:platforms"],
-)
-
-
-def _platform_transition_binary_impl(ctx):
-    # We need to forward the DefaultInfo provider from the underlying rule.
-    # Unfortunately, we can't do this directly, because Bazel requires that the executable to run
-    # is actually generated by this rule, so we need to symlink to it, and generate a synthetic
-    # forwarding DefaultInfo.
-
-    result = []
-    binary = ctx.attr.binary[0]
-
-    default_info = binary[DefaultInfo]
-    files = default_info.files
-    new_executable = None
-    original_executable = default_info.files_to_run.executable
-    runfiles = default_info.default_runfiles
-
-    if not original_executable:
-        fail("Cannot transition a 'binary' that is not executable")
-
-    new_executable_name = ctx.attr.basename if ctx.attr.basename else original_executable.basename
-
-    # In order for the symlink to have the same basename as the original
-    # executable (important in the case of proto plugins), put it in a
-    # subdirectory named after the label to prevent collisions.
-    new_executable = ctx.actions.declare_file(paths.join(ctx.label.name, new_executable_name))
-    ctx.actions.symlink(
-        output = new_executable,
-        target_file = original_executable,
-        is_executable = True,
-    )
-    files = depset(direct = [new_executable], transitive = [files])
-    runfiles = runfiles.merge(ctx.runfiles([new_executable]))
-
-    result.append(
-        DefaultInfo(
-            files = files,
-            runfiles = runfiles,
-            executable = new_executable,
-        ),
-    )
-
-    return result
-
-platform_transition_binary = rule(
-    implementation = _platform_transition_binary_impl,
-    attrs = {
-        "basename": attr.string(),
-        "binary": attr.label(allow_files = True, cfg = _transition_platform),
-        "target_platform": attr.label(
-            doc = "The target platform to transition the binary.",
-            mandatory = True,
-        ),
-        "_allowlist_function_transition": attr.label(
-            default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
-        ),
-    },
-    executable = True,
-    doc = "Transitions the binary to use the provided platform.",
-)
\ No newline at end of file
diff --git a/metropolis/node/build/earlydev.fsspec b/metropolis/node/build/earlydev.fsspec
deleted file mode 100644
index a7d2ea4..0000000
--- a/metropolis/node/build/earlydev.fsspec
+++ /dev/null
@@ -1,54 +0,0 @@
-# Critical /dev files which should be present as early as possible, ie. be baked
-# into filesystem images.
-
-# At least /dev/console and /dev/null are required to exist for Linux
-# to properly boot an init. Here we additionally include important device nodes
-# like /dev/kmsg and /dev/ptmx which might need to be available before a proper
-# device manager (ie. devtmpfs) is launched.
-special_file <
-    path: "/dev/console"
-    type: CHARACTER_DEV
-    major: 5 minor: 1
-    mode: 0600 uid: 0 gid: 0
->
-special_file <
-    path: "/dev/ptmx"
-    type: CHARACTER_DEV
-    major: 5 minor: 2
-    mode: 0644 uid: 0 gid: 0
->
-special_file <
-    path: "/dev/null"
-    type: CHARACTER_DEV
-    major: 1 minor: 3
-    mode: 0644 uid: 0 gid: 0
->
-special_file <
-    path: "/dev/kmsg"
-    type: CHARACTER_DEV
-    major: 1 minor: 11
-    mode: 0644 uid: 0 gid: 0
->
-
-
-# Metropolis core logs to /dev/ttyS{0,1} and /dev/tty0 by default, we want
-# these to also be present before devtmpfs is mounted so that minit can
-# log there, too.
-special_file <
-    path: "/dev/tty0"
-    type: CHARACTER_DEV
-    major: 4 minor: 0
-    mode: 0600 uid: 0 gid: 0
->
-special_file <
-    path: "/dev/ttyS0"
-    type: CHARACTER_DEV
-    major: 4 minor: 64
-    mode: 0660 uid: 0 gid: 0
->
-special_file <
-    path: "/dev/ttyS1"
-    type: CHARACTER_DEV
-    major: 4 minor: 65
-    mode: 0660 uid: 0 gid: 0
->
\ No newline at end of file
diff --git a/metropolis/node/build/efi.bzl b/metropolis/node/build/efi.bzl
deleted file mode 100644
index bf5ad26..0000000
--- a/metropolis/node/build/efi.bzl
+++ /dev/null
@@ -1,135 +0,0 @@
-"""Rules for generating EFI unified kernel images. These are EFI-bootable PE/COFF files containing a stub loader,
-a kernel, and optional commandline and initramfs in one file.
-See https://systemd.io/BOOT_LOADER_SPECIFICATION/#type-2-efi-unified-kernel-images for more information.
-"""
-
-load("//build/toolchain/llvm-efi:transition.bzl", "build_efi_transition")
-load("//metropolis/node/build:def.bzl", "VerityConfig")
-
-def _efi_unified_kernel_image_impl(ctx):
-    # Find the dependency paths to be passed to mkpayload.
-    deps = {
-        "linux": ctx.file.kernel,
-        "osrel": ctx.file.os_release,
-        "splash": ctx.file.splash,
-        "stub": ctx.file.stub,
-    }
-
-    # Since cmdline is a string attribute, put it into a file, then append
-    # that file to deps.
-    if ctx.attr.cmdline and ctx.attr.cmdline != "":
-        cmdline = ctx.actions.declare_file("cmdline")
-        ctx.actions.write(
-            output = cmdline,
-            content = ctx.attr.cmdline,
-        )
-        deps["cmdline"] = cmdline
-
-    # Get the dm-verity target table from VerityConfig provider.
-    if ctx.attr.verity:
-        deps["rootfs_dm_table"] = ctx.attr.verity[VerityConfig].table
-
-    # Format deps into command line arguments while keeping track of mkpayload
-    # runtime inputs.
-    args = []
-    inputs = []
-    for name, file in deps.items():
-        if file:
-            args.append("-{}={}".format(name, file.path))
-            inputs.append(file)
-
-    for file in ctx.files.initrd:
-        args.append("-initrd={}".format(file.path))
-        inputs.append(file)
-
-    # Append the output parameter separately, as it doesn't belong with the
-    # runtime inputs.
-    image = ctx.actions.declare_file(ctx.attr.name + ".efi")
-    args.append("-output={}".format(image.path))
-
-    # Append the objcopy parameter separately, as it's not of File type, and
-    # it does not constitute an input, since it's part of the toolchain.
-    objcopy = ctx.toolchains["@bazel_tools//tools/cpp:toolchain_type"].cc.objcopy_executable
-    args.append("-objcopy={}".format(objcopy))
-
-    # Run mkpayload.
-    ctx.actions.run(
-        mnemonic = "GenEFIKernelImage",
-        progress_message = "Generating EFI unified kernel image",
-        inputs = inputs,
-        outputs = [image],
-        executable = ctx.file._mkpayload,
-        arguments = args,
-    )
-
-    # Return the unified kernel image file.
-    return [DefaultInfo(files = depset([image]), runfiles = ctx.runfiles(files = [image]))]
-
-efi_unified_kernel_image = rule(
-    implementation = _efi_unified_kernel_image_impl,
-    attrs = {
-        "kernel": attr.label(
-            doc = "The Linux kernel executable bzImage. Needs to have EFI handover and EFI stub enabled.",
-            mandatory = True,
-            allow_single_file = True,
-        ),
-        "cmdline": attr.string(
-            doc = "The kernel commandline to be embedded.",
-        ),
-        "initrd": attr.label_list(
-            doc = """
-                List of payloads to concatenate and supply as the initrd parameter to Linux when it boots.
-                The name stems from the time Linux booted from an initial ram disk (initrd), but it's now
-                a catch-all for a bunch of different larger payload for early Linux initialization.
-
-                In Linux 5.15 this can first contain an arbitrary amount of uncompressed cpio archives
-                with directories being optional which is accessed by earlycpio. This is used for both
-                early microcode loading and ACPI table overrides. This can then be followed by an arbitrary
-                amount of compressed cpio archives (even with different compression methods) which will
-                together make up the initramfs. The initramfs is only booted into if it contains either
-                /init or whatever file is specified as init= in cmdline. Technically depending on kernel
-                flags you might be able to supply an actual initrd, i.e. an image of a disk loaded into
-                RAM, but that has been deprecated for nearly 2 decades and should really not be used.
-
-                For kernels designed to run on physical machines this should at least contain microcode,
-                optionally followed by a compressed initramfs. For kernels only used in virtualized
-                setups the microcode can be left out and if no initramfs is needed this option can
-                be omitted completely.
-                """,
-            allow_files = True,
-        ),
-        "os_release": attr.label(
-            doc = """
-                The os-release file identifying the operating system.
-                See https://www.freedesktop.org/software/systemd/man/os-release.html for format.
-            """,
-            allow_single_file = True,
-        ),
-        "splash": attr.label(
-            doc = "An image in BMP format which will be displayed as a splash screen until the kernel takes over.",
-            allow_single_file = True,
-        ),
-        "stub": attr.label(
-            doc = "The stub executable itself as a PE/COFF executable.",
-            default = "@efistub//:efistub",
-            allow_single_file = True,
-            executable = True,
-            cfg = build_efi_transition,
-        ),
-        "verity": attr.label(
-            doc = "The DeviceMapper Verity rootfs target table.",
-            allow_single_file = True,
-            providers = [DefaultInfo, VerityConfig],
-        ),
-        "_mkpayload": attr.label(
-            doc = "The mkpayload executable.",
-            default = "//metropolis/node/build/mkpayload",
-            allow_single_file = True,
-            executable = True,
-            cfg = "exec",
-        ),
-    },
-    toolchains = [
-        "@bazel_tools//tools/cpp:toolchain_type"
-    ],
-)
diff --git a/metropolis/node/build/fsspec/BUILD.bazel b/metropolis/node/build/fsspec/BUILD.bazel
deleted file mode 100644
index eeddda7..0000000
--- a/metropolis/node/build/fsspec/BUILD.bazel
+++ /dev/null
@@ -1,25 +0,0 @@
-load("@rules_proto//proto:defs.bzl", "proto_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
-
-proto_library(
-    name = "spec_proto",
-    srcs = ["spec.proto"],
-    visibility = ["//visibility:public"],
-)
-
-go_library(
-    name = "fsspec",
-    srcs = ["utils.go"],
-    embed = [":fsspec_go_proto"],
-    importpath = "source.monogon.dev/metropolis/node/build/fsspec",
-    visibility = ["//visibility:public"],
-    deps = ["@org_golang_google_protobuf//encoding/prototext"],
-)
-
-go_proto_library(
-    name = "fsspec_go_proto",
-    importpath = "source.monogon.dev/metropolis/node/build/fsspec",
-    proto = ":spec_proto",
-    visibility = ["//visibility:public"],
-)
diff --git a/metropolis/node/build/fsspec/spec.proto b/metropolis/node/build/fsspec/spec.proto
deleted file mode 100644
index 712de1c..0000000
--- a/metropolis/node/build/fsspec/spec.proto
+++ /dev/null
@@ -1,98 +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.
-
-syntax = "proto3";
-
-package metropolis.node.build.fsspec;
-option go_package = "source.monogon.dev/metropolis/node/build/fsspec";
-
-// FSSpec is the spec from which a filesystem is generated. It consists of files, directories and symbolic
-// links. Directories are also automatically inferred when required for the placement of files or symbolic
-// links. Inferred directories always have uid 0, gid 0 and permissions 0555. This can be overridden by
-// explicitly specifying a directory at a given path.
-message FSSpec {
-  repeated File file = 1;
-  repeated Directory directory = 2;
-  repeated SymbolicLink symbolic_link = 3;
-  repeated SpecialFile special_file = 4;
-}
-
-// For internal use only. Represents all supported inodes in a oneof.
-message Inode {
-  oneof type {
-    File file = 1;
-    Directory directory = 2;
-    SymbolicLink symbolic_link = 3;
-    SpecialFile special_file = 4;
-  }
-}
-
-message File {
-  // The path where the file ends up in the filesystem.
-  string path = 1;
-  // The path on the host filesystem where the file contents should be taken from.
-  string source_path = 2;
-  // Unix permission bits
-  uint32 mode = 3;
-  // Owner uid
-  uint32 uid = 4;
-  // Owner gid
-  uint32 gid = 5;
-}
-
-message Directory {
-  // The path where the directory ends up in the filesystem.
-  string path = 1;
-  // Unix permission bits
-  uint32 mode = 2;
-  // Owner uid
-  uint32 uid = 3;
-  // Owner gid
-  uint32 gid = 4;
-}
-
-message SymbolicLink {
-  // The path where the symbolic link ends up in the filesystem.
-  string path = 1;
-  // The path to which the symbolic link resolves to.
-  string target_path = 2;
-}
-
-message SpecialFile {
-  // The path where the special file ends up in the filesystem.
-  string path = 1;
-
-  enum Type {
-    CHARACTER_DEV = 0;
-    BLOCK_DEV = 1;
-    FIFO = 2;
-  }
-
-  // Type of special file.
-  Type type = 2;
-
-  // The major device number of the special file.
-  uint32 major = 3;
-  // The minor number of the special file. Ignored for FIFO-type special files.
-  uint32 minor = 4;
-
-  // Unix permission bits
-  uint32 mode = 5;
-  // Owner uid
-  uint32 uid = 6;
-  // Owner gid
-  uint32 gid = 7;
-}
\ No newline at end of file
diff --git a/metropolis/node/build/fsspec/utils.go b/metropolis/node/build/fsspec/utils.go
deleted file mode 100644
index f5a45e3..0000000
--- a/metropolis/node/build/fsspec/utils.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package fsspec
-
-import (
-	"fmt"
-	"os"
-
-	"google.golang.org/protobuf/encoding/prototext"
-)
-
-// ReadMergeSpecs reads FSSpecs from all files in paths and merges them into
-// a single FSSpec.
-func ReadMergeSpecs(paths []string) (*FSSpec, error) {
-	var mergedSpec FSSpec
-	for _, p := range paths {
-		specRaw, err := os.ReadFile(p)
-		if err != nil {
-			return nil, fmt.Errorf("failed to open spec: %w", err)
-		}
-
-		var spec FSSpec
-		if err := prototext.Unmarshal(specRaw, &spec); err != nil {
-			return nil, fmt.Errorf("failed to parse spec %q: %w", p, err)
-		}
-		mergedSpec.File = append(mergedSpec.File, spec.File...)
-		mergedSpec.Directory = append(mergedSpec.Directory, spec.Directory...)
-		mergedSpec.SymbolicLink = append(mergedSpec.SymbolicLink, spec.SymbolicLink...)
-		mergedSpec.SpecialFile = append(mergedSpec.SpecialFile, spec.SpecialFile...)
-	}
-	return &mergedSpec, nil
-}
diff --git a/metropolis/node/build/fwprune/BUILD.bazel b/metropolis/node/build/fwprune/BUILD.bazel
deleted file mode 100644
index e29ac7e..0000000
--- a/metropolis/node/build/fwprune/BUILD.bazel
+++ /dev/null
@@ -1,20 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
-
-go_library(
-    name = "fwprune_lib",
-    srcs = ["main.go"],
-    importpath = "source.monogon.dev/metropolis/node/build/fwprune",
-    visibility = ["//visibility:private"],
-    deps = [
-        "//metropolis/node/build/fsspec",
-        "//osbase/kmod",
-        "@org_golang_google_protobuf//encoding/prototext",
-        "@org_golang_google_protobuf//proto",
-    ],
-)
-
-go_binary(
-    name = "fwprune",
-    embed = [":fwprune_lib"],
-    visibility = ["//visibility:public"],
-)
diff --git a/metropolis/node/build/fwprune/def.bzl b/metropolis/node/build/fwprune/def.bzl
deleted file mode 100644
index 154009e..0000000
--- a/metropolis/node/build/fwprune/def.bzl
+++ /dev/null
@@ -1,76 +0,0 @@
-load("//metropolis/node/build:def.bzl", "FSSpecInfo")
-
-def _fsspec_linux_firmware(ctx):
-    fsspec_out = ctx.actions.declare_file(ctx.label.name + ".prototxt")
-
-    fwlist = ctx.actions.declare_file(ctx.label.name + "-fwlist.txt")
-    ctx.actions.write(
-        output = fwlist,
-        content = "\n".join([f.path for f in ctx.files.firmware_files]),
-    )
-
-    modinfo = ctx.attr.kernel[OutputGroupInfo].modinfo.to_list()[0]
-    modules = ctx.attr.kernel[OutputGroupInfo].modules.to_list()[0]
-
-    meta_out = ctx.actions.declare_file(ctx.label.name + "-meta.pb")
-
-    ctx.actions.run(
-        outputs = [fsspec_out, meta_out],
-        inputs = [fwlist, modinfo, modules, ctx.file.metadata] + ctx.files.firmware_files,
-        tools = [ctx.executable._fwprune],
-        executable = ctx.executable._fwprune,
-        arguments = [
-            "-modinfo",
-            modinfo.path,
-            "-modules",
-            modules.path,
-            "-firmware-file-list",
-            fwlist.path,
-            "-firmware-whence",
-            ctx.file.metadata.path,
-            "-out-meta",
-            meta_out.path,
-            "-out-fsspec",
-            fsspec_out.path,
-        ],
-    )
-
-    return [DefaultInfo(files = depset([fsspec_out])), FSSpecInfo(spec = fsspec_out, referenced = ctx.files.firmware_files + [modules, meta_out])]
-
-fsspec_linux_firmware = rule(
-    implementation = _fsspec_linux_firmware,
-    doc = """
-         Generates a partial filesystem spec containing all firmware files required by a given kernel at the
-         default firmware load path (/lib/firmware).
-    """,
-    attrs = {
-        "firmware_files": attr.label_list(
-            mandatory = True,
-            allow_files = True,
-            doc = """
-               List of firmware files. Generally at least a filegroup of the linux-firmware repository should
-               be in here.
-            """,
-        ),
-        "metadata": attr.label(
-            mandatory = True,
-            allow_single_file = True,
-            doc = """
-                The metadata file for the Linux firmware. Currently this is the WHENCE file at the root of the
-                linux-firmware repository. Used for resolving additional links.
-            """,
-        ),
-        "kernel": attr.label(
-            doc = """
-                Kernel for which firmware should be selected. Needs to have a modinfo OutputGroup.
-            """,
-        ),
-
-        # Tool
-        "_fwprune": attr.label(
-            default = Label("//metropolis/node/build/fwprune"),
-            executable = True,
-            cfg = "exec",
-        ),
-    },
-)
diff --git a/metropolis/node/build/fwprune/main.go b/metropolis/node/build/fwprune/main.go
deleted file mode 100644
index e76e5d0..0000000
--- a/metropolis/node/build/fwprune/main.go
+++ /dev/null
@@ -1,218 +0,0 @@
-// fwprune is a buildsystem utility that filters linux-firmware repository
-// contents to include only files required by the built-in kernel modules,
-// that are specified in modules.builtin.modinfo.
-// (see: https://www.kernel.org/doc/Documentation/kbuild/kbuild.txt)
-package main
-
-import (
-	"debug/elf"
-	"flag"
-	"io/fs"
-	"log"
-	"os"
-	"path"
-	"path/filepath"
-	"regexp"
-	"sort"
-	"strings"
-
-	"google.golang.org/protobuf/encoding/prototext"
-	"google.golang.org/protobuf/proto"
-
-	"source.monogon.dev/metropolis/node/build/fsspec"
-	"source.monogon.dev/osbase/kmod"
-)
-
-// linkRegexp parses the Link: lines in the WHENCE file. This does not have
-// an official grammar, the regexp has been written in an approximation of
-// the original parsing algorithm at @linux-firmware//:copy_firmware.sh.
-var linkRegexp = regexp.MustCompile(`(?m:^Link:\s*([^\s]+)\s+->\s+([^\s]+)\s*$)`)
-
-var (
-	modinfoPath      = flag.String("modinfo", "", "Path to the modules.builtin.modinfo file built with the kernel")
-	modulesPath      = flag.String("modules", "", "Path to the directory containing the dynamically loaded kernel modules (.ko files)")
-	firmwareListPath = flag.String("firmware-file-list", "", "Path to a file containing a newline-separated list of paths to firmware files")
-	whenceFilePath   = flag.String("firmware-whence", "", "Path to the linux-firmware WHENCE file containing aliases for firmware files")
-	outMetaPath      = flag.String("out-meta", "", "Path where the resulting module metadata protobuf file should be created")
-	outFSSpecPath    = flag.String("out-fsspec", "", "Path where the resulting fsspec should be created")
-)
-
-func main() {
-	flag.Parse()
-	if *modinfoPath == "" || *modulesPath == "" || *firmwareListPath == "" ||
-		*whenceFilePath == "" || *outMetaPath == "" || *outFSSpecPath == "" {
-		log.Fatal("all flags are required and need to be provided")
-	}
-
-	allFirmwareData, err := os.ReadFile(*firmwareListPath)
-	if err != nil {
-		log.Fatalf("Failed to read firmware source list: %v", err)
-	}
-	allFirmwarePaths := strings.Split(string(allFirmwareData), "\n")
-
-	// Create a look-up table of all possible suffixes to their full paths as
-	// this is much faster at O(n) than calling strings.HasSuffix for every
-	// possible combination which is O(n^2).
-	// For example a build output at out/a/b/c.bin will be entered into
-	// the suffix LUT as build as out/a/b/c.bin, a/b/c.bin, b/c.bin and c.bin.
-	// If the firmware then requests b/c.bin, the output path is contained in
-	// the suffix LUT.
-	suffixLUT := make(map[string]string)
-	for _, firmwarePath := range allFirmwarePaths {
-		pathParts := strings.Split(firmwarePath, string(os.PathSeparator))
-		for i := range pathParts {
-			suffixLUT[path.Join(pathParts[i:]...)] = firmwarePath
-		}
-	}
-
-	// The linux-firmware repo contains a WHENCE file which contains (among
-	// other information) aliases for firmware which should be symlinked.
-	// Open this file and create a map of aliases in it.
-	linkMap := make(map[string]string)
-	metadata, err := os.ReadFile(*whenceFilePath)
-	if err != nil {
-		log.Fatalf("Failed to read metadata file: %v", err)
-	}
-	linksRaw := linkRegexp.FindAllStringSubmatch(string(metadata), -1)
-	for _, link := range linksRaw {
-		// For links we know the exact path referenced by kernel drives so
-		// a suffix LUT is unnecessary.
-		linkMap[link[1]] = link[2]
-	}
-
-	// Collect module metadata (modinfo) from both built-in modules via the
-	// kbuild-generated metadata file as well as from the loadable modules by
-	// walking them.
-	var files []*fsspec.File
-	var symlinks []*fsspec.SymbolicLink
-
-	mi, err := os.Open(*modinfoPath)
-	if err != nil {
-		log.Fatalf("While reading modinfo: %v", err)
-	}
-	modMeta, err := kmod.GetBuiltinModulesInfo(mi)
-	if err != nil {
-		log.Fatalf("Failed to read modules modinfo data: %v", err)
-	}
-
-	err = filepath.WalkDir(*modulesPath, func(p string, d fs.DirEntry, err error) error {
-		if err != nil {
-			log.Fatal(err)
-		}
-		if d.IsDir() {
-			return nil
-		}
-		mod, err := elf.Open(p)
-		if err != nil {
-			log.Fatal(err)
-		}
-		defer mod.Close()
-		out, err := kmod.GetModuleInfo(mod)
-		if err != nil {
-			log.Fatal(err)
-		}
-		relPath, err := filepath.Rel(*modulesPath, p)
-		if err != nil {
-			return err
-		}
-		// Add path information for MakeMetaFromModuleInfo.
-		out["path"] = []string{relPath}
-		modMeta = append(modMeta, out)
-		files = append(files, &fsspec.File{
-			Path:       path.Join("/lib/modules", relPath),
-			SourcePath: filepath.Join(*modulesPath, relPath),
-			Mode:       0555,
-		})
-		return nil
-	})
-	if err != nil {
-		log.Fatalf("Error walking modules: %v", err)
-	}
-
-	// Generate loading metadata from all known modules.
-	meta, err := kmod.MakeMetaFromModuleInfo(modMeta)
-	if err != nil {
-		log.Fatal(err)
-	}
-	metaRaw, err := proto.Marshal(meta)
-	if err != nil {
-		log.Fatal(err)
-	}
-	if err := os.WriteFile(*outMetaPath, metaRaw, 0640); err != nil {
-		log.Fatal(err)
-	}
-	files = append(files, &fsspec.File{
-		Path:       "/lib/modules/meta.pb",
-		SourcePath: *outMetaPath,
-		Mode:       0444,
-	})
-
-	// Create set of all firmware paths required by modules
-	fwset := make(map[string]bool)
-	for _, m := range modMeta {
-		if len(m["path"]) == 0 && len(m.Firmware()) > 0 {
-			log.Fatalf("Module %v is built-in, but requires firmware. Linux does not support this in all configurations.", m.Name())
-		}
-		for _, fw := range m.Firmware() {
-			fwset[fw] = true
-		}
-	}
-
-	// Convert set to list and sort for determinism
-	fwp := make([]string, 0, len(fwset))
-	for p := range fwset {
-		fwp = append(fwp, p)
-	}
-	sort.Strings(fwp)
-
-	// This function is called for every requested firmware file and adds and
-	// resolves symlinks until it finds the target file and adds that too.
-	populatedPaths := make(map[string]bool)
-	var chaseReference func(string)
-	chaseReference = func(p string) {
-		if populatedPaths[p] {
-			// Bail if path is already populated. Because of the DAG-like
-			// property of links in filesystems everything transitively pointed
-			// to by anything at this path has already been included.
-			return
-		}
-		placedPath := path.Join("/lib/firmware", p)
-		if linkTarget := linkMap[p]; linkTarget != "" {
-			symlinks = append(symlinks, &fsspec.SymbolicLink{
-				Path:       placedPath,
-				TargetPath: linkTarget,
-			})
-			populatedPaths[p] = true
-			// Symlinks are relative to their place, resolve them to be relative
-			// to the firmware root directory.
-			chaseReference(path.Join(path.Dir(p), linkTarget))
-			return
-		}
-		sourcePath := suffixLUT[p]
-		if sourcePath == "" {
-			// This should not be fatal as sometimes linux-firmware cannot
-			// ship all firmware usable by the kernel for mostly legal reasons.
-			log.Printf("WARNING: Requested firmware %q not found", p)
-			return
-		}
-		files = append(files, &fsspec.File{
-			Path:       path.Join("/lib/firmware", p),
-			Mode:       0444,
-			SourcePath: sourcePath,
-		})
-		populatedPaths[p] = true
-	}
-
-	for _, p := range fwp {
-		chaseReference(p)
-	}
-	// Format output in a both human- and machine-readable form
-	marshalOpts := prototext.MarshalOptions{Multiline: true, Indent: "  "}
-	fsspecRaw, err := marshalOpts.Marshal(&fsspec.FSSpec{File: files, SymbolicLink: symlinks})
-	if err != nil {
-		log.Fatalf("failed to marshal fsspec: %v", err)
-	}
-	if err := os.WriteFile(*outFSSpecPath, fsspecRaw, 0644); err != nil {
-		log.Fatalf("failed writing output: %v", err)
-	}
-}
diff --git a/metropolis/node/build/genosrelease/BUILD.bazel b/metropolis/node/build/genosrelease/BUILD.bazel
deleted file mode 100644
index 5ac09d5..0000000
--- a/metropolis/node/build/genosrelease/BUILD.bazel
+++ /dev/null
@@ -1,18 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
-
-go_library(
-    name = "genosrelease_lib",
-    srcs = ["main.go"],
-    importpath = "source.monogon.dev/metropolis/node/build/genosrelease",
-    visibility = ["//visibility:private"],
-    deps = ["@com_github_joho_godotenv//:godotenv"],
-)
-
-go_binary(
-    name = "genosrelease",
-    embed = [":genosrelease_lib"],
-    visibility = [
-        "//metropolis/installer:__subpackages__",
-        "//metropolis/node:__subpackages__",
-    ],
-)
diff --git a/metropolis/node/build/genosrelease/defs.bzl b/metropolis/node/build/genosrelease/defs.bzl
deleted file mode 100644
index 61ce9e4..0000000
--- a/metropolis/node/build/genosrelease/defs.bzl
+++ /dev/null
@@ -1,54 +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.
-
-def _os_release_impl(ctx):
-    ctx.actions.run(
-        mnemonic = "GenOSRelease",
-        progress_message = "Generating os-release",
-        inputs = [ctx.info_file],
-        outputs = [ctx.outputs.out],
-        executable = ctx.executable._genosrelease,
-        arguments = [
-            "-status_file",
-            ctx.info_file.path,
-            "-out_file",
-            ctx.outputs.out.path,
-            "-stamp_var",
-            ctx.attr.stamp_var,
-            "-name",
-            ctx.attr.os_name,
-            "-id",
-            ctx.attr.os_id,
-        ],
-    )
-
-os_release = rule(
-    implementation = _os_release_impl,
-    attrs = {
-        "os_name": attr.string(mandatory = True),
-        "os_id": attr.string(mandatory = True),
-        "stamp_var": attr.string(mandatory = True),
-        "_genosrelease": attr.label(
-            default = Label("//metropolis/node/build/genosrelease"),
-            cfg = "host",
-            executable = True,
-            allow_files = True,
-        ),
-    },
-    outputs = {
-        "out": "os-release",
-    },
-)
diff --git a/metropolis/node/build/genosrelease/main.go b/metropolis/node/build/genosrelease/main.go
deleted file mode 100644
index adb8202..0000000
--- a/metropolis/node/build/genosrelease/main.go
+++ /dev/null
@@ -1,79 +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.
-
-// genosrelease provides rudimentary support to generate os-release files
-// following the freedesktop spec from arguments and stamping
-//
-// https://www.freedesktop.org/software/systemd/man/os-release.html
-package main
-
-import (
-	"flag"
-	"fmt"
-	"os"
-	"strings"
-
-	"github.com/joho/godotenv"
-)
-
-var (
-	flagStatusFile = flag.String("status_file", "", "path to bazel workspace status file")
-	flagOutFile    = flag.String("out_file", "os-release", "path to os-release output file")
-	flagStampVar   = flag.String("stamp_var", "", "variable to use as version from the workspace status file")
-	flagName       = flag.String("name", "", "name parameter (see freedesktop spec)")
-	flagID         = flag.String("id", "", "id parameter (see freedesktop spec)")
-)
-
-func main() {
-	flag.Parse()
-	statusFileContent, err := os.ReadFile(*flagStatusFile)
-	if err != nil {
-		fmt.Printf("Failed to open bazel workspace status file: %v\n", err)
-		os.Exit(1)
-	}
-	statusVars := make(map[string]string)
-	for _, line := range strings.Split(string(statusFileContent), "\n") {
-		line = strings.TrimSpace(line)
-		parts := strings.Fields(line)
-		if len(parts) != 2 {
-			continue
-		}
-		statusVars[parts[0]] = parts[1]
-	}
-
-	version, ok := statusVars[*flagStampVar]
-	if !ok {
-		fmt.Printf("%v key not set in bazel workspace status file\n", *flagStampVar)
-		os.Exit(1)
-	}
-	// As specified by https://www.freedesktop.org/software/systemd/man/os-release.html
-	osReleaseVars := map[string]string{
-		"NAME":        *flagName,
-		"ID":          *flagID,
-		"VERSION":     version,
-		"VERSION_ID":  version,
-		"PRETTY_NAME": *flagName + " " + version,
-	}
-	osReleaseContent, err := godotenv.Marshal(osReleaseVars)
-	if err != nil {
-		fmt.Printf("Failed to encode os-release file: %v\n", err)
-		os.Exit(1)
-	}
-	if err := os.WriteFile(*flagOutFile, []byte(osReleaseContent), 0644); err != nil {
-		fmt.Printf("Failed to write os-release file: %v\n", err)
-		os.Exit(1)
-	}
-}
diff --git a/metropolis/node/build/kconfig-patcher/BUILD.bazel b/metropolis/node/build/kconfig-patcher/BUILD.bazel
deleted file mode 100644
index 278db21..0000000
--- a/metropolis/node/build/kconfig-patcher/BUILD.bazel
+++ /dev/null
@@ -1,23 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
-
-go_library(
-    name = "kconfig-patcher_lib",
-    srcs = ["main.go"],
-    importpath = "source.monogon.dev/metropolis/node/build/kconfig-patcher",
-    visibility = ["//visibility:private"],
-)
-
-go_binary(
-    name = "kconfig-patcher",
-    embed = [":kconfig-patcher_lib"],
-    visibility = [
-        "//metropolis/node:__pkg__",
-        "//osbase/test/ktest:__pkg__",
-    ],
-)
-
-go_test(
-    name = "kconfig-patcher_test",
-    srcs = ["main_test.go"],
-    embed = [":kconfig-patcher_lib"],
-)
diff --git a/metropolis/node/build/kconfig-patcher/kconfig-patcher.bzl b/metropolis/node/build/kconfig-patcher/kconfig-patcher.bzl
deleted file mode 100644
index 337642e..0000000
--- a/metropolis/node/build/kconfig-patcher/kconfig-patcher.bzl
+++ /dev/null
@@ -1,33 +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.
-
-"""Override configs in a Linux kernel Kconfig
-"""
-
-def kconfig_patch(name, src, out, override_configs, **kwargs):
-    native.genrule(
-        name = name,
-        srcs = [src],
-        outs = [out],
-        tools = [
-            "//metropolis/node/build/kconfig-patcher",
-        ],
-        cmd = """
-        $(location //metropolis/node/build/kconfig-patcher) \
-            -in $< -out $@ '%s'
-        """ % struct(overrides = override_configs).to_json(),
-        **kwargs
-    )
diff --git a/metropolis/node/build/kconfig-patcher/main.go b/metropolis/node/build/kconfig-patcher/main.go
deleted file mode 100644
index 1b5f24f..0000000
--- a/metropolis/node/build/kconfig-patcher/main.go
+++ /dev/null
@@ -1,98 +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.
-
-package main
-
-import (
-	"bufio"
-	"encoding/json"
-	"flag"
-	"fmt"
-	"io"
-	"os"
-	"strings"
-)
-
-var (
-	inPath  = flag.String("in", "", "Path to input Kconfig")
-	outPath = flag.String("out", "", "Path to output Kconfig")
-)
-
-func main() {
-	flag.Parse()
-	if *inPath == "" || *outPath == "" {
-		flag.PrintDefaults()
-		os.Exit(2)
-	}
-	inFile, err := os.Open(*inPath)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Failed to open input Kconfig: %v\n", err)
-		os.Exit(1)
-	}
-	outFile, err := os.Create(*outPath)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Failed to create output Kconfig: %v\n", err)
-		os.Exit(1)
-	}
-	var config struct {
-		Overrides map[string]string `json:"overrides"`
-	}
-	if err := json.Unmarshal([]byte(flag.Arg(0)), &config); err != nil {
-		fmt.Fprintf(os.Stderr, "Failed to parse overrides: %v\n", err)
-		os.Exit(1)
-	}
-	err = patchKconfig(inFile, outFile, config.Overrides)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Failed to patch: %v\n", err)
-		os.Exit(1)
-	}
-}
-
-func patchKconfig(inFile io.Reader, outFile io.Writer, overrides map[string]string) error {
-	scanner := bufio.NewScanner(inFile)
-	for scanner.Scan() {
-		if scanner.Err() != nil {
-			return scanner.Err()
-		}
-		line := scanner.Text()
-		cleanLine := strings.TrimSpace(line)
-		if strings.HasPrefix(cleanLine, "#") || cleanLine == "" {
-			// Pass through comments and empty lines
-			fmt.Fprintln(outFile, line)
-		} else {
-			// Line contains a configuration option
-			parts := strings.SplitN(line, "=", 2)
-			keyName := parts[0]
-			if overrideVal, ok := overrides[strings.TrimSpace(keyName)]; ok {
-				// Override it
-				if overrideVal == "" {
-					fmt.Fprintf(outFile, "# %v is not set\n", keyName)
-				} else {
-					fmt.Fprintf(outFile, "%v=%v\n", keyName, overrideVal)
-				}
-				delete(overrides, keyName)
-			} else {
-				// Pass through unchanged
-				fmt.Fprintln(outFile, line)
-			}
-		}
-	}
-	// Process left over overrides
-	for key, val := range overrides {
-		fmt.Fprintf(outFile, "%v=%v\n", key, val)
-	}
-	return nil
-}
diff --git a/metropolis/node/build/kconfig-patcher/main_test.go b/metropolis/node/build/kconfig-patcher/main_test.go
deleted file mode 100644
index 11c7d84..0000000
--- a/metropolis/node/build/kconfig-patcher/main_test.go
+++ /dev/null
@@ -1,61 +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.
-
-package main
-
-import (
-	"bytes"
-	"strings"
-	"testing"
-)
-
-func Test_patchKconfig(t *testing.T) {
-	type args struct {
-		inFile    string
-		overrides map[string]string
-	}
-	tests := []struct {
-		name        string
-		args        args
-		wantOutFile string
-		wantErr     bool
-	}{
-		{
-			"passthroughExtend",
-			args{inFile: "# TEST=y\n\n", overrides: map[string]string{"TEST": "n"}},
-			"# TEST=y\n\nTEST=n\n",
-			false,
-		},
-		{
-			"patch",
-			args{inFile: "TEST=y\nTEST_NO=n\n", overrides: map[string]string{"TEST": "n"}},
-			"TEST=n\nTEST_NO=n\n",
-			false,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			outFile := &bytes.Buffer{}
-			if err := patchKconfig(strings.NewReader(tt.args.inFile), outFile, tt.args.overrides); (err != nil) != tt.wantErr {
-				t.Errorf("patchKconfig() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if gotOutFile := outFile.String(); gotOutFile != tt.wantOutFile {
-				t.Errorf("patchKconfig() = %v, want %v", gotOutFile, tt.wantOutFile)
-			}
-		})
-	}
-}
diff --git a/metropolis/node/build/mkcpio/BUILD.bazel b/metropolis/node/build/mkcpio/BUILD.bazel
deleted file mode 100644
index 3ea98ae..0000000
--- a/metropolis/node/build/mkcpio/BUILD.bazel
+++ /dev/null
@@ -1,20 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
-
-go_library(
-    name = "mkcpio_lib",
-    srcs = ["main.go"],
-    importpath = "source.monogon.dev/metropolis/node/build/mkcpio",
-    visibility = ["//visibility:private"],
-    deps = [
-        "//metropolis/node/build/fsspec",
-        "@com_github_cavaliergopher_cpio//:cpio",
-        "@com_github_klauspost_compress//zstd",
-        "@org_golang_x_sys//unix",
-    ],
-)
-
-go_binary(
-    name = "mkcpio",
-    embed = [":mkcpio_lib"],
-    visibility = ["//visibility:public"],
-)
diff --git a/metropolis/node/build/mkcpio/main.go b/metropolis/node/build/mkcpio/main.go
deleted file mode 100644
index b8f99b9..0000000
--- a/metropolis/node/build/mkcpio/main.go
+++ /dev/null
@@ -1,218 +0,0 @@
-package main
-
-import (
-	"flag"
-	"io"
-	"log"
-	"os"
-	"path"
-	"sort"
-	"strings"
-
-	"github.com/cavaliergopher/cpio"
-	"github.com/klauspost/compress/zstd"
-	"golang.org/x/sys/unix"
-
-	"source.monogon.dev/metropolis/node/build/fsspec"
-)
-
-var (
-	outPath = flag.String("out", "", "Output file path")
-)
-
-type placeEnum int
-
-const (
-	// placeNone implies that currently nothing is placed at that path.
-	// Can be overridden by everything.
-	placeNone placeEnum = 0
-	// placeDirImplicit means that there is currently a implied directory
-	// at the given path. It can be overridden by (and only by) an explicit
-	// directory.
-	placeDirImplicit placeEnum = 1
-	// placeDirExplicit means that there is an explicit (i.e. specified by
-	// the FSSpec) directory at the given path. Nothing else can override
-	// this.
-	placeDirExplicit placeEnum = 2
-	// placeNonDir means that there is a file-type resource (i.e a file, symlink
-	// or special_file) at the given path. Nothing else can override this.
-	placeNonDir placeEnum = 3
-)
-
-// place represents the state a given canonical path is in during metadata
-// construction. Its zero value is { State: placeNone, Inode: nil }.
-type place struct {
-	State placeEnum
-	// Inode contains one of the types inside an FSSpec (e.g. *fsspec.File)
-	Inode interface{}
-}
-
-// Usage: -out <out-path.cpio.zst> fsspec-path...
-func main() {
-	flag.Parse()
-	outFile, err := os.Create(*outPath)
-	if err != nil {
-		log.Fatalf("Failed to open CPIO output file: %v", err)
-	}
-	defer outFile.Close()
-	compressedOut, err := zstd.NewWriter(outFile)
-	if err != nil {
-		log.Fatalf("While initializing zstd writer: %v", err)
-	}
-	defer compressedOut.Close()
-	cpioWriter := cpio.NewWriter(compressedOut)
-	defer cpioWriter.Close()
-
-	spec, err := fsspec.ReadMergeSpecs(flag.Args())
-	if err != nil {
-		log.Fatalf("failed to load specs: %v", err)
-	}
-
-	// Map of paths to metadata for validation & implicit directory injection
-	places := make(map[string]place)
-
-	// The idea behind this machinery is that we try to place all files and
-	// directories into a map while creating the required parent directories
-	// on-the-fly as implicit directories. Overriding an implicit directory
-	// with an explicit one is allowed thus the actual order in which this
-	// structure is created does not matter. All non-directories cannot be
-	// overridden anyways so their insertion order does not matter.
-	// This also has the job of validating the FSSpec structure, ensuring that
-	// there are no duplicate paths and that there is nothing placed below a
-	// non-directory.
-	var placeInode func(p string, isDir bool, inode interface{})
-	placeInode = func(p string, isDir bool, inode interface{}) {
-		cleanPath := path.Clean(p)
-		if !isDir {
-			if places[cleanPath].State != placeNone {
-				log.Fatalf("Invalid FSSpec: Duplicate Inode at %q", cleanPath)
-			}
-			places[cleanPath] = place{
-				State: placeNonDir,
-				Inode: inode,
-			}
-		} else {
-			switch places[cleanPath].State {
-			case placeNone:
-				if inode != nil {
-					places[cleanPath] = place{
-						State: placeDirExplicit,
-						Inode: inode,
-					}
-				} else {
-					places[cleanPath] = place{
-						State: placeDirImplicit,
-						Inode: &fsspec.Directory{Path: cleanPath, Mode: 0555},
-					}
-				}
-			case placeDirImplicit:
-				if inode != nil {
-					places[cleanPath] = place{
-						State: placeDirExplicit,
-						Inode: inode,
-					}
-				}
-			case placeDirExplicit:
-				if inode != nil {
-					log.Fatalf("Invalid FSSpec: Conflicting explicit directories at %v", cleanPath)
-				}
-			case placeNonDir:
-				log.Fatalf("Invalid FSSpec: Trying to place inode below non-directory at #{cleanPath}")
-			default:
-				panic("unhandled placeEnum value")
-			}
-		}
-		parentPath, _ := path.Split(p)
-		parentPath = path.Clean(parentPath)
-		if parentPath == "/" || parentPath == p {
-			return
-		}
-		placeInode(parentPath, true, nil)
-	}
-	for _, d := range spec.Directory {
-		placeInode(d.Path, true, d)
-	}
-	for _, f := range spec.File {
-		placeInode(f.Path, false, f)
-	}
-	for _, s := range spec.SymbolicLink {
-		placeInode(s.Path, false, s)
-	}
-	for _, s := range spec.SpecialFile {
-		placeInode(s.Path, false, s)
-	}
-
-	var writeOrder []string
-	for path := range places {
-		writeOrder = append(writeOrder, path)
-	}
-	// Sorting a list of normalized paths representing a tree gives us Depth-
-	// first search (DFS) order which is the correct order for writing archives.
-	// This also makes the output reproducible.
-	sort.Strings(writeOrder)
-
-	for _, path := range writeOrder {
-		place := places[path]
-		switch i := place.Inode.(type) {
-		case *fsspec.File:
-			inF, err := os.Open(i.SourcePath)
-			if err != nil {
-				log.Fatalf("Failed to open source path for file %q: %v", i.Path, err)
-			}
-			inFStat, err := inF.Stat()
-			if err != nil {
-				log.Fatalf("Failed to stat source path for file %q: %v", i.Path, err)
-			}
-			if err := cpioWriter.WriteHeader(&cpio.Header{
-				Mode: cpio.FileMode(i.Mode),
-				Name: strings.TrimPrefix(i.Path, "/"),
-				Size: inFStat.Size(),
-			}); err != nil {
-				log.Fatalf("Failed to write cpio header for file %q: %v", i.Path, err)
-			}
-			if n, err := io.Copy(cpioWriter, inF); err != nil || n != inFStat.Size() {
-				log.Fatalf("Failed to copy file %q into cpio: %v", i.SourcePath, err)
-			}
-			inF.Close()
-		case *fsspec.Directory:
-			if err := cpioWriter.WriteHeader(&cpio.Header{
-				Mode: cpio.FileMode(i.Mode) | cpio.TypeDir,
-				Name: strings.TrimPrefix(i.Path, "/"),
-			}); err != nil {
-				log.Fatalf("Failed to write cpio header for directory %q: %v", i.Path, err)
-			}
-		case *fsspec.SymbolicLink:
-			if err := cpioWriter.WriteHeader(&cpio.Header{
-				// Symlinks are 0777 by definition (from man 7 symlink on Linux)
-				Mode: 0777 | cpio.TypeSymlink,
-				Name: strings.TrimPrefix(i.Path, "/"),
-				Size: int64(len(i.TargetPath)),
-			}); err != nil {
-				log.Fatalf("Failed to write cpio header for symlink %q: %v", i.Path, err)
-			}
-			if _, err := cpioWriter.Write([]byte(i.TargetPath)); err != nil {
-				log.Fatalf("Failed to write cpio symlink %q: %v", i.Path, err)
-			}
-		case *fsspec.SpecialFile:
-			mode := cpio.FileMode(i.Mode)
-			switch i.Type {
-			case fsspec.SpecialFile_CHARACTER_DEV:
-				mode |= cpio.TypeChar
-			case fsspec.SpecialFile_BLOCK_DEV:
-				mode |= cpio.TypeBlock
-			case fsspec.SpecialFile_FIFO:
-				mode |= cpio.TypeFifo
-			}
-
-			if err := cpioWriter.WriteHeader(&cpio.Header{
-				Mode:     mode,
-				Name:     strings.TrimPrefix(i.Path, "/"),
-				DeviceID: int(unix.Mkdev(i.Major, i.Minor)),
-			}); err != nil {
-				log.Fatalf("Failed to write CPIO header for special file %q: %v", i.Path, err)
-			}
-		default:
-			panic("inode type not handled")
-		}
-	}
-}
diff --git a/metropolis/node/build/mkerofs/BUILD.bazel b/metropolis/node/build/mkerofs/BUILD.bazel
deleted file mode 100644
index 0befb4c..0000000
--- a/metropolis/node/build/mkerofs/BUILD.bazel
+++ /dev/null
@@ -1,18 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
-
-go_library(
-    name = "mkerofs_lib",
-    srcs = ["main.go"],
-    importpath = "source.monogon.dev/metropolis/node/build/mkerofs",
-    visibility = ["//visibility:public"],
-    deps = [
-        "//metropolis/node/build/fsspec",
-        "//osbase/erofs",
-    ],
-)
-
-go_binary(
-    name = "mkerofs",
-    embed = [":mkerofs_lib"],
-    visibility = ["//visibility:public"],
-)
diff --git a/metropolis/node/build/mkerofs/main.go b/metropolis/node/build/mkerofs/main.go
deleted file mode 100644
index b5d5568..0000000
--- a/metropolis/node/build/mkerofs/main.go
+++ /dev/null
@@ -1,208 +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.
-
-// mkerofs takes a specification in the form of a prototext file (see fsspec
-// next to this) and assembles an EROFS filesystem according to it. The output
-// is fully reproducible.
-package main
-
-import (
-	"flag"
-	"fmt"
-	"io"
-	"log"
-	"os"
-	"path"
-	"sort"
-	"strings"
-
-	"source.monogon.dev/metropolis/node/build/fsspec"
-	"source.monogon.dev/osbase/erofs"
-)
-
-func (spec *entrySpec) writeRecursive(w *erofs.Writer, pathname string) {
-	switch inode := spec.data.Type.(type) {
-	case *fsspec.Inode_Directory:
-		// Sort children for reproducibility
-		var sortedChildren []string
-		for name := range spec.children {
-			sortedChildren = append(sortedChildren, name)
-		}
-		sort.Strings(sortedChildren)
-
-		err := w.Create(pathname, &erofs.Directory{
-			Base: erofs.Base{
-				Permissions: uint16(inode.Directory.Mode),
-				UID:         uint16(inode.Directory.Uid),
-				GID:         uint16(inode.Directory.Gid),
-			},
-			Children: sortedChildren,
-		})
-		if err != nil {
-			log.Fatalf("failed to write directory: %s", err)
-		}
-		for _, name := range sortedChildren {
-			spec.children[name].writeRecursive(w, path.Join(pathname, name))
-		}
-	case *fsspec.Inode_File:
-		iw := w.CreateFile(pathname, &erofs.FileMeta{
-			Base: erofs.Base{
-				Permissions: uint16(inode.File.Mode),
-				UID:         uint16(inode.File.Uid),
-				GID:         uint16(inode.File.Gid),
-			},
-		})
-
-		sourceFile, err := os.Open(inode.File.SourcePath)
-		if err != nil {
-			log.Fatalf("failed to open source file %s: %s", inode.File.SourcePath, err)
-		}
-
-		_, err = io.Copy(iw, sourceFile)
-		if err != nil {
-			log.Fatalf("failed to copy file into filesystem: %s", err)
-		}
-		sourceFile.Close()
-		if err := iw.Close(); err != nil {
-			log.Fatalf("failed to close target file: %s", err)
-		}
-	case *fsspec.Inode_SymbolicLink:
-		err := w.Create(pathname, &erofs.SymbolicLink{
-			Base: erofs.Base{
-				Permissions: 0777, // Nominal, Linux forces that mode anyways, see symlink(7)
-			},
-			Target: inode.SymbolicLink.TargetPath,
-		})
-		if err != nil {
-			log.Fatalf("failed to create symbolic link: %s", err)
-		}
-	case *fsspec.Inode_SpecialFile:
-		err := fmt.Errorf("unimplemented special file type %s", inode.SpecialFile.Type)
-		base := erofs.Base{
-			Permissions: uint16(inode.SpecialFile.Mode),
-			UID:         uint16(inode.SpecialFile.Uid),
-			GID:         uint16(inode.SpecialFile.Gid),
-		}
-		switch inode.SpecialFile.Type {
-		case fsspec.SpecialFile_FIFO:
-			err = w.Create(pathname, &erofs.FIFO{
-				Base: base,
-			})
-		case fsspec.SpecialFile_CHARACTER_DEV:
-			err = w.Create(pathname, &erofs.CharacterDevice{
-				Base:  base,
-				Major: inode.SpecialFile.Major,
-				Minor: inode.SpecialFile.Minor,
-			})
-		case fsspec.SpecialFile_BLOCK_DEV:
-			err = w.Create(pathname, &erofs.BlockDevice{
-				Base:  base,
-				Major: inode.SpecialFile.Major,
-				Minor: inode.SpecialFile.Minor,
-			})
-		}
-		if err != nil {
-			log.Fatalf("failed to make special file: %v", err)
-		}
-	}
-}
-
-// entrySpec is a recursive structure representing the filesystem tree
-type entrySpec struct {
-	data     fsspec.Inode
-	children map[string]*entrySpec
-}
-
-// pathRef gets the entrySpec at the leaf of the given path, inferring
-// directories if necessary
-func (spec *entrySpec) pathRef(p string) *entrySpec {
-	// This block gets a path array starting at the root of the filesystem. The
-	// root folder is the zero-length array.
-	pathParts := strings.Split(path.Clean("./"+p), "/")
-	if pathParts[0] == "." {
-		pathParts = pathParts[1:]
-	}
-
-	entryRef := spec
-	for _, part := range pathParts {
-		childRef, ok := entryRef.children[part]
-		if !ok {
-			childRef = &entrySpec{
-				data:     fsspec.Inode{Type: &fsspec.Inode_Directory{Directory: &fsspec.Directory{Mode: 0555}}},
-				children: make(map[string]*entrySpec),
-			}
-			entryRef.children[part] = childRef
-		}
-		entryRef = childRef
-	}
-	return entryRef
-}
-
-var (
-	outPath = flag.String("out", "", "Output file path")
-)
-
-func main() {
-	flag.Parse()
-
-	spec, err := fsspec.ReadMergeSpecs(flag.Args())
-	if err != nil {
-		log.Fatalf("failed to load specs: %v", err)
-	}
-
-	var fsRoot = &entrySpec{
-		data:     fsspec.Inode{Type: &fsspec.Inode_Directory{Directory: &fsspec.Directory{Mode: 0555}}},
-		children: make(map[string]*entrySpec),
-	}
-
-	for _, dir := range spec.Directory {
-		entryRef := fsRoot.pathRef(dir.Path)
-		entryRef.data.Type = &fsspec.Inode_Directory{Directory: dir}
-	}
-
-	for _, file := range spec.File {
-		entryRef := fsRoot.pathRef(file.Path)
-		entryRef.data.Type = &fsspec.Inode_File{File: file}
-	}
-
-	for _, symlink := range spec.SymbolicLink {
-		entryRef := fsRoot.pathRef(symlink.Path)
-		entryRef.data.Type = &fsspec.Inode_SymbolicLink{SymbolicLink: symlink}
-	}
-
-	for _, specialFile := range spec.SpecialFile {
-		entryRef := fsRoot.pathRef(specialFile.Path)
-		entryRef.data.Type = &fsspec.Inode_SpecialFile{SpecialFile: specialFile}
-	}
-
-	fs, err := os.Create(*outPath)
-	if err != nil {
-		log.Fatalf("failed to open output file: %v", err)
-	}
-	writer, err := erofs.NewWriter(fs)
-	if err != nil {
-		log.Fatalf("failed to initialize EROFS writer: %v", err)
-	}
-
-	fsRoot.writeRecursive(writer, ".")
-
-	if err := writer.Close(); err != nil {
-		panic(err)
-	}
-	if err := fs.Close(); err != nil {
-		panic(err)
-	}
-}
diff --git a/metropolis/node/build/mkimage/BUILD.bazel b/metropolis/node/build/mkimage/BUILD.bazel
deleted file mode 100644
index 41ce603..0000000
--- a/metropolis/node/build/mkimage/BUILD.bazel
+++ /dev/null
@@ -1,22 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
-
-go_library(
-    name = "mkimage_lib",
-    srcs = ["main.go"],
-    embedsrcs = [
-        "//metropolis/node/core/abloader",  #keep
-    ],
-    importpath = "source.monogon.dev/metropolis/node/build/mkimage",
-    visibility = ["//visibility:private"],
-    deps = [
-        "//metropolis/node/build/mkimage/osimage",
-        "//osbase/blkio",
-        "//osbase/blockdev",
-    ],
-)
-
-go_binary(
-    name = "mkimage",
-    embed = [":mkimage_lib"],
-    visibility = ["//metropolis/node:__pkg__"],
-)
diff --git a/metropolis/node/build/mkimage/def.bzl b/metropolis/node/build/mkimage/def.bzl
deleted file mode 100644
index efcebdc..0000000
--- a/metropolis/node/build/mkimage/def.bzl
+++ /dev/null
@@ -1,48 +0,0 @@
-def _node_image_impl(ctx):
-    img_file = ctx.actions.declare_file(ctx.label.name + ".img")
-    ctx.actions.run(
-        mnemonic = "MkImage",
-        executable = ctx.executable._mkimage,
-        arguments = [
-            "-efi",
-            ctx.file.kernel.path,
-            "-system",
-            ctx.file.system.path,
-            "-out",
-            img_file.path,
-        ],
-        inputs = [
-            ctx.file.kernel,
-            ctx.file.system,
-        ],
-        outputs = [img_file],
-    )
-
-    return [DefaultInfo(files = depset([img_file]), runfiles = ctx.runfiles(files = [img_file]))]
-
-node_image = rule(
-    implementation = _node_image_impl,
-    doc = """
-        Build a disk image from an EFI kernel payload and system partition
-        contents. See //metropolis/node/build/mkimage for more information.
-    """,
-    attrs = {
-        "kernel": attr.label(
-            doc = "EFI binary containing a kernel.",
-            mandatory = True,
-            allow_single_file = True,
-        ),
-        "system": attr.label(
-            doc = "Contents of the system partition.",
-            mandatory = True,
-            allow_single_file = True,
-        ),
-        "_mkimage": attr.label(
-            doc = "The mkimage executable.",
-            default = "//metropolis/node/build/mkimage",
-            allow_single_file = True,
-            executable = True,
-            cfg = "exec",
-        ),
-    },
-)
diff --git a/metropolis/node/build/mkimage/main.go b/metropolis/node/build/mkimage/main.go
deleted file mode 100644
index 3a68fc7..0000000
--- a/metropolis/node/build/mkimage/main.go
+++ /dev/null
@@ -1,106 +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.
-
-// mkimage is a tool to generate Metropolis node disk images.
-// It can be used both to initialize block devices and to create image
-// files.
-//
-// The tool takes a path to an EFI payload (--efi), and a path to a
-// Metropolis system image (--system) as its only required inputs. In
-// addition, an output path must be supplied (--out).
-// Node parameters file path (--node_parameters) may also be supplied, in
-// which case the file will be copied to the EFI system partition.
-// Partition sizes are fixed and may be overridden by command line flags.
-package main
-
-import (
-	"bytes"
-	_ "embed"
-	"flag"
-	"log"
-	"os"
-
-	"source.monogon.dev/metropolis/node/build/mkimage/osimage"
-	"source.monogon.dev/osbase/blkio"
-	"source.monogon.dev/osbase/blockdev"
-)
-
-//go:embed metropolis/node/core/abloader/abloader_bin.efi
-var abloader []byte
-
-func main() {
-	// Fill in the image parameters based on flags.
-	var (
-		efiPayload  string
-		systemImage string
-		nodeParams  string
-		outputPath  string
-		diskUUID    string
-		cfg         osimage.Params
-	)
-	flag.StringVar(&efiPayload, "efi", "", "Path to the UEFI payload used")
-	flag.StringVar(&systemImage, "system", "", "Path to the system partition image used")
-	flag.StringVar(&nodeParams, "node_parameters", "", "Path to Node Parameters to be written to the ESP (default: don't write Node Parameters)")
-	flag.StringVar(&outputPath, "out", "", "Path to the resulting disk image or block device")
-	flag.Int64Var(&cfg.PartitionSize.Data, "data_partition_size", 2048, "Override the data partition size (default 2048 MiB). Used only when generating image files.")
-	flag.Int64Var(&cfg.PartitionSize.ESP, "esp_partition_size", 128, "Override the ESP partition size (default: 128MiB)")
-	flag.Int64Var(&cfg.PartitionSize.System, "system_partition_size", 1024, "Override the System partition size (default: 1024MiB)")
-	flag.StringVar(&diskUUID, "GUID", "", "Disk GUID marked in the resulting image's partition table (default: randomly generated)")
-	flag.Parse()
-
-	// Open the input files for osimage.Create, fill in reader objects and
-	// metadata in osimage.Params.
-	// Start with the EFI Payload the OS will boot from.
-	p, err := blkio.NewFileReader(efiPayload)
-	if err != nil {
-		log.Fatalf("while opening the EFI payload at %q: %v", efiPayload, err)
-	}
-	cfg.EFIPayload = p
-
-	// Attempt to open the system image if its path is set. In case the path
-	// isn't set, the system partition will still be created, but no
-	// contents will be written into it.
-	if systemImage != "" {
-		img, err := os.Open(systemImage)
-		if err != nil {
-			log.Fatalf("while opening the system image at %q: %v", systemImage, err)
-		}
-		defer img.Close()
-		cfg.SystemImage = img
-	}
-
-	// Attempt to open the node parameters file if its path is set.
-	if nodeParams != "" {
-		np, err := blkio.NewFileReader(nodeParams)
-		if err != nil {
-			log.Fatalf("while opening node parameters at %q: %v", nodeParams, err)
-		}
-		cfg.NodeParameters = np
-	}
-
-	// TODO(#254): Build and use dynamically-grown block devices
-	cfg.Output, err = blockdev.CreateFile(outputPath, 512, 10*1024*1024)
-	if err != nil {
-		panic(err)
-	}
-
-	cfg.ABLoader = bytes.NewReader(abloader)
-
-	// Write the parametrized OS image.
-	if _, err := osimage.Write(&cfg); err != nil {
-		log.Fatalf("while creating a Metropolis OS image: %v", err)
-	}
-}
diff --git a/metropolis/node/build/mkimage/osimage/BUILD.bazel b/metropolis/node/build/mkimage/osimage/BUILD.bazel
deleted file mode 100644
index 9799b81..0000000
--- a/metropolis/node/build/mkimage/osimage/BUILD.bazel
+++ /dev/null
@@ -1,15 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-
-go_library(
-    name = "osimage",
-    srcs = ["osimage.go"],
-    importpath = "source.monogon.dev/metropolis/node/build/mkimage/osimage",
-    visibility = ["//visibility:public"],
-    deps = [
-        "//osbase/blockdev",
-        "//osbase/efivarfs",
-        "//osbase/fat32",
-        "//osbase/gpt",
-        "@com_github_google_uuid//:uuid",
-    ],
-)
diff --git a/metropolis/node/build/mkimage/osimage/osimage.go b/metropolis/node/build/mkimage/osimage/osimage.go
deleted file mode 100644
index d139d6b..0000000
--- a/metropolis/node/build/mkimage/osimage/osimage.go
+++ /dev/null
@@ -1,237 +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.
-
-// This package provides self-contained implementation used to generate
-// Metropolis disk images.
-package osimage
-
-import (
-	"fmt"
-	"io"
-	"strings"
-
-	"github.com/google/uuid"
-
-	"source.monogon.dev/osbase/blockdev"
-	"source.monogon.dev/osbase/efivarfs"
-	"source.monogon.dev/osbase/fat32"
-	"source.monogon.dev/osbase/gpt"
-)
-
-var (
-	SystemAType = uuid.MustParse("ee96054b-f6d0-4267-aaaa-724b2afea74c")
-	SystemBType = uuid.MustParse("ee96054b-f6d0-4267-bbbb-724b2afea74c")
-
-	DataType = uuid.MustParse("9eeec464-6885-414a-b278-4305c51f7966")
-)
-
-const (
-	SystemALabel = "METROPOLIS-SYSTEM-A"
-	SystemBLabel = "METROPOLIS-SYSTEM-B"
-	DataLabel    = "METROPOLIS-NODE-DATA"
-	ESPLabel     = "ESP"
-
-	EFIPayloadPath = "/EFI/BOOT/BOOTx64.EFI"
-	EFIBootAPath   = "/EFI/metropolis/boot-a.efi"
-	EFIBootBPath   = "/EFI/metropolis/boot-b.efi"
-	nodeParamsPath = "metropolis/parameters.pb"
-)
-
-// PartitionSizeInfo contains parameters used during partition table
-// initialization and, in case of image files, space allocation.
-type PartitionSizeInfo struct {
-	// Size of the EFI System Partition (ESP), in mebibytes. The size must
-	// not be zero.
-	ESP int64
-	// Size of the Metropolis system partition, in mebibytes. The partition
-	// won't be created if the size is zero.
-	System int64
-	// Size of the Metropolis data partition, in mebibytes. The partition
-	// won't be created if the size is zero. If the image is output to a
-	// block device, the partition will be extended to fill the remaining
-	// space.
-	Data int64
-}
-
-// Params contains parameters used by Plan or Write to build a Metropolis OS
-// image.
-type Params struct {
-	// Output is the block device to which the OS image is written.
-	Output blockdev.BlockDev
-	// ABLoader provides the A/B loader which then loads the EFI loader for the
-	// correct slot.
-	ABLoader fat32.SizedReader
-	// EFIPayload provides contents of the EFI payload file. It must not be
-	// nil. This gets put into boot slot A.
-	EFIPayload fat32.SizedReader
-	// SystemImage provides contents of the Metropolis system partition.
-	// If nil, no contents will be copied into the partition.
-	SystemImage io.Reader
-	// NodeParameters provides contents of the node parameters file. If nil,
-	// the node parameters file won't be created in the target ESP
-	// filesystem.
-	NodeParameters fat32.SizedReader
-	// DiskGUID is a unique identifier of the image and a part of Table
-	// header. It's optional and can be left blank if the identifier is
-	// to be randomly generated. Setting it to a predetermined value can
-	// help in implementing reproducible builds.
-	DiskGUID uuid.UUID
-	// PartitionSize specifies a size for the ESP, Metropolis System and
-	// Metropolis data partition.
-	PartitionSize PartitionSizeInfo
-}
-
-type plan struct {
-	*Params
-	rootInode        fat32.Inode
-	tbl              *gpt.Table
-	efiPartition     *gpt.Partition
-	systemPartitionA *gpt.Partition
-	systemPartitionB *gpt.Partition
-	dataPartition    *gpt.Partition
-}
-
-// Apply actually writes the planned installation to the blockdevice.
-func (i *plan) Apply() (*efivarfs.LoadOption, error) {
-	// Discard the entire device, we're going to write new data over it.
-	// Ignore errors, this is only advisory.
-	i.Output.Discard(0, i.Output.BlockCount()*i.Output.BlockSize())
-
-	if err := fat32.WriteFS(blockdev.NewRWS(i.efiPartition), i.rootInode, fat32.Options{
-		BlockSize:  uint16(i.efiPartition.BlockSize()),
-		BlockCount: uint32(i.efiPartition.BlockCount()),
-		Label:      "MNGN_BOOT",
-	}); err != nil {
-		return nil, fmt.Errorf("failed to write FAT32: %w", err)
-	}
-
-	if _, err := io.Copy(blockdev.NewRWS(i.systemPartitionA), i.SystemImage); err != nil {
-		return nil, fmt.Errorf("failed to write system partition A: %w", err)
-	}
-
-	if err := i.tbl.Write(); err != nil {
-		return nil, fmt.Errorf("failed to write Table: %w", err)
-	}
-
-	// Build an EFI boot entry pointing to the image's ESP.
-	return &efivarfs.LoadOption{
-		Description: "Metropolis",
-		FilePath: efivarfs.DevicePath{
-			&efivarfs.HardDrivePath{
-				PartitionNumber:     1,
-				PartitionStartBlock: i.efiPartition.FirstBlock,
-				PartitionSizeBlocks: i.efiPartition.SizeBlocks(),
-				PartitionMatch: efivarfs.PartitionGPT{
-					PartitionUUID: i.efiPartition.ID,
-				},
-			},
-			efivarfs.FilePath(EFIPayloadPath),
-		},
-	}, nil
-}
-
-// Plan allows to prepare an installation without modifying any data on the
-// system. To apply the planned installation, call Apply on the returned plan.
-func Plan(p *Params) (*plan, error) {
-	params := &plan{Params: p}
-
-	var err error
-	params.tbl, err = gpt.New(params.Output)
-	if err != nil {
-		return nil, fmt.Errorf("invalid block device: %w", err)
-	}
-
-	params.tbl.ID = params.DiskGUID
-	params.efiPartition = &gpt.Partition{
-		Type: gpt.PartitionTypeEFISystem,
-		Name: ESPLabel,
-	}
-
-	if err := params.tbl.AddPartition(params.efiPartition, params.PartitionSize.ESP*Mi); err != nil {
-		return nil, fmt.Errorf("failed to allocate ESP: %w", err)
-	}
-
-	params.rootInode = fat32.Inode{
-		Attrs: fat32.AttrDirectory,
-	}
-	if err := params.rootInode.PlaceFile(strings.TrimPrefix(EFIBootAPath, "/"), params.EFIPayload); err != nil {
-		return nil, err
-	}
-	// Place the A/B loader at the EFI bootloader autodiscovery path.
-	if err := params.rootInode.PlaceFile(strings.TrimPrefix(EFIPayloadPath, "/"), params.ABLoader); err != nil {
-		return nil, err
-	}
-	if params.NodeParameters != nil {
-		if err := params.rootInode.PlaceFile(nodeParamsPath, params.NodeParameters); err != nil {
-			return nil, err
-		}
-	}
-
-	// Try to layout the fat32 partition. If it detects that the disk is too
-	// small, an error will be returned.
-	if _, err := fat32.SizeFS(params.rootInode, fat32.Options{
-		BlockSize:  uint16(params.efiPartition.BlockSize()),
-		BlockCount: uint32(params.efiPartition.BlockCount()),
-		Label:      "MNGN_BOOT",
-	}); err != nil {
-		return nil, fmt.Errorf("failed to calculate size of FAT32: %w", err)
-	}
-
-	// Create the system partition only if its size is specified.
-	if params.PartitionSize.System != 0 && params.SystemImage != nil {
-		params.systemPartitionA = &gpt.Partition{
-			Type: SystemAType,
-			Name: SystemALabel,
-		}
-		if err := params.tbl.AddPartition(params.systemPartitionA, params.PartitionSize.System*Mi); err != nil {
-			return nil, fmt.Errorf("failed to allocate system partition A: %w", err)
-		}
-		params.systemPartitionB = &gpt.Partition{
-			Type: SystemBType,
-			Name: SystemBLabel,
-		}
-		if err := params.tbl.AddPartition(params.systemPartitionB, params.PartitionSize.System*Mi); err != nil {
-			return nil, fmt.Errorf("failed to allocate system partition B: %w", err)
-		}
-	} else if params.PartitionSize.System == 0 && params.SystemImage != nil {
-		// Safeguard against contradicting parameters.
-		return nil, fmt.Errorf("the system image parameter was passed while the associated partition size is zero")
-	}
-	// Create the data partition only if its size is specified.
-	if params.PartitionSize.Data != 0 {
-		params.dataPartition = &gpt.Partition{
-			Type: DataType,
-			Name: DataLabel,
-		}
-		if err := params.tbl.AddPartition(params.dataPartition, -1); err != nil {
-			return nil, fmt.Errorf("failed to allocate data partition: %w", err)
-		}
-	}
-
-	return params, nil
-}
-
-const Mi = 1024 * 1024
-
-// Write writes a Metropolis OS image to a block device.
-func Write(params *Params) (*efivarfs.LoadOption, error) {
-	p, err := Plan(params)
-	if err != nil {
-		return nil, err
-	}
-
-	return p.Apply()
-}
diff --git a/metropolis/node/build/mkpayload/BUILD.bazel b/metropolis/node/build/mkpayload/BUILD.bazel
deleted file mode 100644
index b092791..0000000
--- a/metropolis/node/build/mkpayload/BUILD.bazel
+++ /dev/null
@@ -1,14 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
-
-go_binary(
-    name = "mkpayload",
-    embed = [":mkpayload_lib"],
-    visibility = ["//visibility:public"],
-)
-
-go_library(
-    name = "mkpayload_lib",
-    srcs = ["mkpayload.go"],
-    importpath = "source.monogon.dev/metropolis/node/build/mkpayload",
-    visibility = ["//visibility:private"],
-)
diff --git a/metropolis/node/build/mkpayload/mkpayload.go b/metropolis/node/build/mkpayload/mkpayload.go
deleted file mode 100644
index a66d458..0000000
--- a/metropolis/node/build/mkpayload/mkpayload.go
+++ /dev/null
@@ -1,188 +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.
-
-// mkpayload is an objcopy wrapper that builds EFI unified kernel images. It
-// performs actions that can't be realized by either objcopy or the
-// buildsystem.
-package main
-
-import (
-	"errors"
-	"flag"
-	"fmt"
-	"io"
-	"log"
-	"os"
-	"os/exec"
-	"strings"
-)
-
-type stringList []string
-
-func (l *stringList) String() string {
-	if l == nil {
-		return ""
-	}
-	return strings.Join(*l, ", ")
-}
-
-func (l *stringList) Set(value string) error {
-	*l = append(*l, value)
-	return nil
-}
-
-var (
-	// sections contains VMAs and source files of the payload PE sections. The
-	// file path pointers will be filled in when the flags are parsed. It's used
-	// to generate objcopy command line arguments. Entries that are "required"
-	// will cause the program to stop and print usage information if not provided
-	// as command line parameters.
-	sections = map[string]struct {
-		descr    string
-		vma      string
-		required bool
-		file     *string
-	}{
-		"linux":   {"Linux kernel image", "0x2000000", true, nil},
-		"initrd":  {"initramfs", "0x5000000", false, nil},
-		"osrel":   {"OS release file in text format", "0x20000", false, nil},
-		"cmdline": {"a file containting additional kernel command line parameters", "0x30000", false, nil},
-		"splash":  {"a splash screen image in BMP format", "0x40000", false, nil},
-	}
-	initrdList      stringList
-	objcopy         = flag.String("objcopy", "", "objcopy executable")
-	stub            = flag.String("stub", "", "the EFI stub executable")
-	output          = flag.String("output", "", "objcopy output")
-	rootfs_dm_table = flag.String("rootfs_dm_table", "", "a text file containing the DeviceMapper rootfs target table")
-)
-
-func main() {
-	flag.Var(&initrdList, "initrd", "Path to initramfs, can be given multiple times")
-	// Register parameters related to the EFI payload sections, then parse the flags.
-	for k, v := range sections {
-		if k == "initrd" { // initrd is special because it accepts multiple payloads
-			continue
-		}
-		v.file = flag.String(k, "", v.descr)
-		sections[k] = v
-	}
-	flag.Parse()
-
-	// Ensure all the required parameters are filled in.
-	for n, s := range sections {
-		if s.required && *s.file == "" {
-			log.Fatalf("-%s parameter is missing.", n)
-		}
-	}
-	if *objcopy == "" {
-		log.Fatalf("-objcopy parameter is missing.")
-	}
-	if *stub == "" {
-		log.Fatalf("-stub parameter is missing.")
-	}
-	if *output == "" {
-		log.Fatalf("-output parameter is missing.")
-	}
-
-	// If a DeviceMapper table was passed, configure the kernel to boot from the
-	// device described by it, while keeping any other kernel command line
-	// parameters that might have been passed through "-cmdline".
-	if *rootfs_dm_table != "" {
-		var cmdline string
-		p := *sections["cmdline"].file
-		if p != "" {
-			c, err := os.ReadFile(p)
-			if err != nil {
-				log.Fatalf("%v", err)
-			}
-			cmdline = string(c[:])
-
-			if strings.Contains(cmdline, "root=") {
-				log.Fatalf("A DeviceMapper table was passed, however the kernel command line already contains a \"root=\" statement.")
-			}
-		}
-
-		vt, err := os.ReadFile(*rootfs_dm_table)
-		if err != nil {
-			log.Fatalf("%v", err)
-		}
-		cmdline += fmt.Sprintf(" dm-mod.create=\"rootfs,,,ro,%s\" root=/dev/dm-0", vt)
-
-		out, err := os.CreateTemp(".", "cmdline")
-		if err != nil {
-			log.Fatalf("%v", err)
-		}
-		defer os.Remove(out.Name())
-		if _, err = out.Write([]byte(cmdline[:])); err != nil {
-			log.Fatalf("%v", err)
-		}
-		out.Close()
-
-		*sections["cmdline"].file = out.Name()
-	}
-
-	var initrdPath string
-	if len(initrdList) > 0 {
-		initrd, err := os.CreateTemp(".", "initrd")
-		if err != nil {
-			log.Fatalf("Failed to create temporary initrd: %v", err)
-		}
-		defer os.Remove(initrd.Name())
-		for _, initrdPath := range initrdList {
-			initrdSrc, err := os.Open(initrdPath)
-			if err != nil {
-				log.Fatalf("Failed to open initrd file: %v", err)
-			}
-			if _, err := io.Copy(initrd, initrdSrc); err != nil {
-				initrdSrc.Close()
-				log.Fatalf("Failed concatinating initrd: %v", err)
-			}
-			initrdSrc.Close()
-		}
-		initrdPath = initrd.Name()
-	}
-	sec := sections["initrd"]
-	sec.file = &initrdPath
-	sections["initrd"] = sec
-
-	// Execute objcopy
-	var args []string
-	for name, c := range sections {
-		if *c.file != "" {
-			args = append(args, []string{
-				"--add-section", fmt.Sprintf(".%s=%s", name, *c.file),
-				"--change-section-vma", fmt.Sprintf(".%s=%s", name, c.vma),
-			}...)
-		}
-	}
-	args = append(args, []string{
-		*stub,
-		*output,
-	}...)
-	cmd := exec.Command(*objcopy, args...)
-	cmd.Stderr = os.Stderr
-	cmd.Stdout = os.Stdout
-	err := cmd.Run()
-	if err == nil {
-		return
-	}
-	// Exit with objcopy's return code.
-	var e *exec.ExitError
-	if errors.As(err, &e) {
-		os.Exit(e.ExitCode())
-	}
-	log.Fatalf("Could not start command: %v", err)
-}
diff --git a/metropolis/node/build/mkucode/BUILD.bazel b/metropolis/node/build/mkucode/BUILD.bazel
deleted file mode 100644
index c1f5667..0000000
--- a/metropolis/node/build/mkucode/BUILD.bazel
+++ /dev/null
@@ -1,19 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
-
-go_library(
-    name = "mkucode_lib",
-    srcs = ["main.go"],
-    importpath = "source.monogon.dev/metropolis/node/build/mkucode",
-    visibility = ["//visibility:private"],
-    deps = [
-        "//metropolis/node/build/mkucode/spec",
-        "@com_github_cavaliergopher_cpio//:cpio",
-        "@org_golang_google_protobuf//encoding/prototext",
-    ],
-)
-
-go_binary(
-    name = "mkucode",
-    embed = [":mkucode_lib"],
-    visibility = ["//visibility:public"],
-)
diff --git a/metropolis/node/build/mkucode/def.bzl b/metropolis/node/build/mkucode/def.bzl
deleted file mode 100644
index 3faa775..0000000
--- a/metropolis/node/build/mkucode/def.bzl
+++ /dev/null
@@ -1,45 +0,0 @@
-def _cpio_ucode_impl(ctx):
-    ucode_spec = ctx.actions.declare_file(ctx.label.name + "_spec.prototxt")
-
-    vendors = []
-    inputs = []
-    for label, vendor in ctx.attr.ucode.items():
-        files = label[DefaultInfo].files.to_list()
-        inputs += files
-        vendors.append(struct(id = vendor, file = [f.path for f in files]))
-
-    ctx.actions.write(ucode_spec, proto.encode_text(struct(vendor = vendors)))
-
-    output_file = ctx.actions.declare_file(ctx.label.name + ".cpio")
-    ctx.actions.run(
-        outputs = [output_file],
-        inputs = [ucode_spec] + inputs,
-        tools = [ctx.executable._mkucode],
-        executable = ctx.executable._mkucode,
-        arguments = ["-out", output_file.path, "-spec", ucode_spec.path],
-    )
-    return [DefaultInfo(files = depset([output_file]))]
-
-cpio_ucode = rule(
-    implementation = _cpio_ucode_impl,
-    doc = """
-        Builds a cpio archive with microcode for the Linux early microcode loader.
-    """,
-    attrs = {
-        "ucode": attr.label_keyed_string_dict(
-            mandatory = True,
-            allow_files = True,
-            doc = """
-                Dictionary of Labels to String. Each label is a list of microcode files and the string label
-                is the vendor ID corresponding to that microcode.
-            """,
-        ),
-
-        # Tool
-        "_mkucode": attr.label(
-            default = Label("//metropolis/node/build/mkucode"),
-            executable = True,
-            cfg = "exec",
-        ),
-    },
-)
diff --git a/metropolis/node/build/mkucode/main.go b/metropolis/node/build/mkucode/main.go
deleted file mode 100644
index 1cd8960..0000000
--- a/metropolis/node/build/mkucode/main.go
+++ /dev/null
@@ -1,71 +0,0 @@
-// This assembles standalone microcode files into the format expected by the
-// Linux microcode loader. See
-// https://www.kernel.org/doc/html/latest/x86/microcode.html for further
-// information.
-package main
-
-import (
-	"flag"
-	"io"
-	"log"
-	"os"
-
-	"github.com/cavaliergopher/cpio"
-	"google.golang.org/protobuf/encoding/prototext"
-
-	"source.monogon.dev/metropolis/node/build/mkucode/spec"
-)
-
-var (
-	specPath = flag.String("spec", "", "Path to prototext specification (metropolis.node.build.mkucode.UCode)")
-	outPath  = flag.String("out", "", "Output path for cpio to be prepend to initrd")
-)
-
-// Usage: -spec <ucode.prototxt> -out <ucode.cpio>
-func main() {
-	flag.Parse()
-	specRaw, err := os.ReadFile(*specPath)
-	if err != nil {
-		log.Fatalf("Failed to read spec: %v", err)
-	}
-	var ucodeSpec spec.UCode
-	if err := prototext.Unmarshal(specRaw, &ucodeSpec); err != nil {
-		log.Fatalf("Failed unmarshaling ucode spec: %v", err)
-	}
-	out, err := os.Create(*outPath)
-	if err != nil {
-		log.Fatalf("Failed to create cpio: %v", err)
-	}
-	defer out.Close()
-	cpioWriter := cpio.NewWriter(out)
-	for _, vendor := range ucodeSpec.Vendor {
-		var totalSize int64
-		for _, file := range vendor.File {
-			data, err := os.Stat(file)
-			if err != nil {
-				log.Fatalf("Failed to stat file for vendor %q: %v", vendor.Id, err)
-			}
-			totalSize += data.Size()
-		}
-		if err := cpioWriter.WriteHeader(&cpio.Header{
-			Mode: 0444,
-			Name: "kernel/x86/microcode/" + vendor.Id + ".bin",
-			Size: totalSize,
-		}); err != nil {
-			log.Fatalf("Failed to write cpio header for vendor %q: %v", vendor.Id, err)
-		}
-		for _, file := range vendor.File {
-			f, err := os.Open(file)
-			if err != nil {
-				log.Fatalf("Failed to open file for vendor %q: %v", vendor.Id, err)
-			}
-			if _, err := io.Copy(cpioWriter, f); err != nil {
-				log.Fatalf("Failed to copy data for file %q: %v", file, err)
-			}
-			f.Close()
-		}
-	}
-	if err := cpioWriter.Close(); err != nil {
-		log.Fatalf("Failed writing cpio: %v", err)
-	}
-}
diff --git a/metropolis/node/build/mkucode/spec/BUILD.bazel b/metropolis/node/build/mkucode/spec/BUILD.bazel
deleted file mode 100644
index 84e7c7b..0000000
--- a/metropolis/node/build/mkucode/spec/BUILD.bazel
+++ /dev/null
@@ -1,30 +0,0 @@
-load("@rules_proto//proto:defs.bzl", "proto_library")
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
-
-proto_library(
-    name = "mkucode_proto",
-    srcs = ["spec.proto"],
-    visibility = ["//visibility:public"],
-)
-
-go_proto_library(
-    name = "mkucode_go_proto",
-    importpath = "source.monogon.dev/metropolis/node/build/mkucode",
-    proto = ":mkucode_proto",
-    visibility = ["//visibility:public"],
-)
-
-go_library(
-    name = "spec",
-    embed = [":spec_go_proto"],
-    importpath = "source.monogon.dev/metropolis/node/build/mkucode/spec",
-    visibility = ["//visibility:public"],
-)
-
-go_proto_library(
-    name = "spec_go_proto",
-    importpath = "source.monogon.dev/metropolis/node/build/mkucode/spec",
-    proto = ":mkucode_proto",
-    visibility = ["//visibility:public"],
-)
diff --git a/metropolis/node/build/mkucode/spec/gomod-generated-placeholder.go b/metropolis/node/build/mkucode/spec/gomod-generated-placeholder.go
deleted file mode 100644
index f09cd57..0000000
--- a/metropolis/node/build/mkucode/spec/gomod-generated-placeholder.go
+++ /dev/null
@@ -1 +0,0 @@
-package spec
diff --git a/metropolis/node/build/mkucode/spec/spec.proto b/metropolis/node/build/mkucode/spec/spec.proto
deleted file mode 100644
index ed537c5..0000000
--- a/metropolis/node/build/mkucode/spec/spec.proto
+++ /dev/null
@@ -1,17 +0,0 @@
-syntax = "proto3";
-
-package metropolis.node.build.mkucode;
-option go_package = "source.monogon.dev/metropolis/node/build/mkucode/spec";
-
-message UCode {
-  repeated UCodeVendor vendor = 1;
-}
-
-message UCodeVendor {
-  // The vendor id (as given in cpuid) of the CPU the microcode is for, like
-  // GenuineIntel or AuthenticAMD.
-  string id = 1;
-
-  // List of paths to microcode files from for CPUs from the vendor.
-  repeated string file = 2;
-}
\ No newline at end of file
diff --git a/metropolis/node/build/mkverity/BUILD.bazel b/metropolis/node/build/mkverity/BUILD.bazel
deleted file mode 100644
index a748b86..0000000
--- a/metropolis/node/build/mkverity/BUILD.bazel
+++ /dev/null
@@ -1,19 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
-
-go_binary(
-    name = "mkverity",
-    embed = [":mkverity_lib"],
-    visibility = [
-        "//metropolis/installer/test/testos:__pkg__",
-        "//metropolis/node:__pkg__",
-        "//metropolis/node/core/update/e2e/testos:__pkg__",
-    ],
-)
-
-go_library(
-    name = "mkverity_lib",
-    srcs = ["mkverity.go"],
-    importpath = "source.monogon.dev/metropolis/node/build/mkverity",
-    visibility = ["//visibility:private"],
-    deps = ["//osbase/verity"],
-)
diff --git a/metropolis/node/build/mkverity/mkverity.go b/metropolis/node/build/mkverity/mkverity.go
deleted file mode 100644
index ff2807b..0000000
--- a/metropolis/node/build/mkverity/mkverity.go
+++ /dev/null
@@ -1,152 +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.
-
-// This package implements a command line tool that creates dm-verity hash
-// images at a selected path, given an existing data image. The tool
-// outputs a Verity mapping table on success.
-//
-// For more information, see:
-// - source.monogon.dev/osbase/verity
-// - https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity
-package main
-
-import (
-	"flag"
-	"fmt"
-	"io"
-	"log"
-	"os"
-
-	"source.monogon.dev/osbase/verity"
-)
-
-// createImage creates a dm-verity target image by combining the input image
-// with Verity metadata. Contents of the data image are copied to the output
-// image. Then, the same contents are verity-encoded and appended to the
-// output image. The verity superblock is written only if wsb is true. It
-// returns either a dm-verity target table, or an error.
-func createImage(dataImagePath, outputImagePath string, wsb bool) (*verity.MappingTable, error) {
-	// Hardcode both the data block size and the hash block size as 4096 bytes.
-	bs := uint32(4096)
-
-	// Open the data image for reading.
-	dataImage, err := os.Open(dataImagePath)
-	if err != nil {
-		return nil, fmt.Errorf("while opening the data image: %w", err)
-	}
-	defer dataImage.Close()
-
-	// Check that the data image is well-formed.
-	ds, err := dataImage.Stat()
-	if err != nil {
-		return nil, fmt.Errorf("while stat-ing the data image: %w", err)
-	}
-	if !ds.Mode().IsRegular() {
-		return nil, fmt.Errorf("the data image must be a regular file")
-	}
-	if ds.Size()%int64(bs) != 0 {
-		return nil, fmt.Errorf("the data image must end on a %d-byte block boundary", bs)
-	}
-
-	// Create an empty hash image file.
-	outputImage, err := os.OpenFile(outputImagePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0644)
-	if err != nil {
-		return nil, fmt.Errorf("while opening the hash image for writing: %w", err)
-	}
-	defer outputImage.Close()
-
-	// Copy the input data into the output file, then rewind dataImage to be read
-	// again by the Verity encoder.
-	_, err = io.Copy(outputImage, dataImage)
-	if err != nil {
-		return nil, err
-	}
-	_, err = dataImage.Seek(0, os.SEEK_SET)
-	if err != nil {
-		return nil, err
-	}
-
-	// Write outputImage contents. Start with initializing a verity encoder,
-	// seting outputImage as its output.
-	v, err := verity.NewEncoder(outputImage, bs, bs, wsb)
-	if err != nil {
-		return nil, fmt.Errorf("while initializing a verity encoder: %w", err)
-	}
-	// Hash the contents of dataImage, block by block.
-	_, err = io.Copy(v, dataImage)
-	if err != nil {
-		return nil, fmt.Errorf("while reading the data image: %w", err)
-	}
-	// The resulting hash tree won't be written until Close is called.
-	err = v.Close()
-	if err != nil {
-		return nil, fmt.Errorf("while writing the hash image: %w", err)
-	}
-
-	// Return an encoder-generated verity mapping table, containing the salt and
-	// the root hash. First, calculate the starting hash block by dividing the
-	// data image size by the encoder data block size.
-	hashStart := ds.Size() / int64(bs)
-	mt, err := v.MappingTable(dataImagePath, outputImagePath, hashStart)
-	if err != nil {
-		return nil, fmt.Errorf("while querying for the mapping table: %w", err)
-	}
-	return mt, nil
-}
-
-var (
-	input           = flag.String("input", "", "input disk image (required)")
-	output          = flag.String("output", "", "output disk image with Verity metadata appended (required)")
-	dataDeviceAlias = flag.String("data_alias", "", "data device alias used in the mapping table")
-	hashDeviceAlias = flag.String("hash_alias", "", "hash device alias used in the mapping table")
-	table           = flag.String("table", "", "a file the mapping table will be saved to; disables stdout")
-)
-
-func main() {
-	flag.Parse()
-
-	// Ensure that required parameters were provided before continuing.
-	if *input == "" {
-		log.Fatalf("-input must be set.")
-	}
-	if *output == "" {
-		log.Fatalf("-output must be set.")
-	}
-
-	// Build the image.
-	mt, err := createImage(*input, *output, false)
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	// Patch the device names, if alternatives were provided.
-	if *dataDeviceAlias != "" {
-		mt.DataDevicePath = *dataDeviceAlias
-	}
-	if *hashDeviceAlias != "" {
-		mt.HashDevicePath = *hashDeviceAlias
-	}
-
-	// Print a DeviceMapper target table, or save it to a file, if the table
-	// parameter was specified.
-	if *table != "" {
-		if err := os.WriteFile(*table, []byte(mt.String()), 0644); err != nil {
-			log.Fatal(err)
-		}
-	} else {
-		fmt.Println(mt)
-	}
-}
diff --git a/metropolis/node/core/abloader/BUILD.bazel b/metropolis/node/core/abloader/BUILD.bazel
index d56a391..e8c49ac 100644
--- a/metropolis/node/core/abloader/BUILD.bazel
+++ b/metropolis/node/core/abloader/BUILD.bazel
@@ -1,5 +1,5 @@
 load("@rules_rust//rust:defs.bzl", "rust_binary")
-load("//metropolis/node/build:def.bzl", "platform_transition_binary")
+load("//osbase/build:def.bzl", "platform_transition_binary")
 
 rust_binary(
     name = "abloader_bin",
diff --git a/metropolis/node/core/update/BUILD.bazel b/metropolis/node/core/update/BUILD.bazel
index 30ca20b..f60ba40 100644
--- a/metropolis/node/core/update/BUILD.bazel
+++ b/metropolis/node/core/update/BUILD.bazel
@@ -9,9 +9,9 @@
     importpath = "source.monogon.dev/metropolis/node/core/update",
     visibility = ["//visibility:public"],
     deps = [
-        "//metropolis/node/build/mkimage/osimage",
         "//metropolis/node/core/abloader/spec",
         "//osbase/blockdev",
+        "//osbase/build/mkimage/osimage",
         "//osbase/efivarfs",
         "//osbase/gpt",
         "//osbase/kexec",
diff --git a/metropolis/node/core/update/e2e/BUILD.bazel b/metropolis/node/core/update/e2e/BUILD.bazel
index abf3ba1..3443ecf 100644
--- a/metropolis/node/core/update/e2e/BUILD.bazel
+++ b/metropolis/node/core/update/e2e/BUILD.bazel
@@ -26,9 +26,9 @@
         "xAbloaderPath": "_main/metropolis/node/core/abloader/abloader_bin.efi",
     },
     deps = [
-        "//metropolis/node/build/mkimage/osimage",
         "//osbase/blkio",
         "//osbase/blockdev",
+        "//osbase/build/mkimage/osimage",
         "@io_bazel_rules_go//go/runfiles:go_default_library",
     ],
 )
diff --git a/metropolis/node/core/update/e2e/e2e_test.go b/metropolis/node/core/update/e2e/e2e_test.go
index 68e55e0..7679a69 100644
--- a/metropolis/node/core/update/e2e/e2e_test.go
+++ b/metropolis/node/core/update/e2e/e2e_test.go
@@ -18,9 +18,9 @@
 
 	"github.com/bazelbuild/rules_go/go/runfiles"
 
-	"source.monogon.dev/metropolis/node/build/mkimage/osimage"
 	"source.monogon.dev/osbase/blkio"
 	"source.monogon.dev/osbase/blockdev"
+	"source.monogon.dev/osbase/build/mkimage/osimage"
 )
 
 var (
diff --git a/metropolis/node/core/update/e2e/testos/BUILD.bazel b/metropolis/node/core/update/e2e/testos/BUILD.bazel
index 275a44c..8a4b122 100644
--- a/metropolis/node/core/update/e2e/testos/BUILD.bazel
+++ b/metropolis/node/core/update/e2e/testos/BUILD.bazel
@@ -13,10 +13,10 @@
     importpath = "source.monogon.dev/metropolis/node/core/update/e2e/testos",
     visibility = ["//visibility:private"],
     deps = [
-        "//metropolis/node/build/mkimage/osimage",
         "//metropolis/node/core/network",
         "//metropolis/node/core/update",
         "//osbase/blockdev",
+        "//osbase/build/mkimage/osimage",
         "//osbase/gpt",
         "//osbase/logtree",
         "//osbase/supervisor",
diff --git a/metropolis/node/core/update/e2e/testos/main.go b/metropolis/node/core/update/e2e/testos/main.go
index e77a4e2..69897eb 100644
--- a/metropolis/node/core/update/e2e/testos/main.go
+++ b/metropolis/node/core/update/e2e/testos/main.go
@@ -8,10 +8,10 @@
 
 	"golang.org/x/sys/unix"
 
-	"source.monogon.dev/metropolis/node/build/mkimage/osimage"
 	"source.monogon.dev/metropolis/node/core/network"
 	"source.monogon.dev/metropolis/node/core/update"
 	"source.monogon.dev/osbase/blockdev"
+	"source.monogon.dev/osbase/build/mkimage/osimage"
 	"source.monogon.dev/osbase/gpt"
 	"source.monogon.dev/osbase/logtree"
 	"source.monogon.dev/osbase/supervisor"
diff --git a/metropolis/node/core/update/e2e/testos/testos.bzl b/metropolis/node/core/update/e2e/testos/testos.bzl
index 29e218f..2ce3e31 100644
--- a/metropolis/node/core/update/e2e/testos/testos.bzl
+++ b/metropolis/node/core/update/e2e/testos/testos.bzl
@@ -1,6 +1,6 @@
 load("@io_bazel_rules_go//go:def.bzl", "go_binary")
-load("//metropolis/node/build:def.bzl", "erofs_image", "verity_image")
-load("//metropolis/node/build:efi.bzl", "efi_unified_kernel_image")
+load("//osbase/build:def.bzl", "erofs_image", "verity_image")
+load("//osbase/build:efi.bzl", "efi_unified_kernel_image")
 load("@rules_pkg//:pkg.bzl", "pkg_zip")
 load("@rules_pkg//:mappings.bzl", "pkg_files")
 
@@ -14,7 +14,7 @@
             "@com_github_coredns_coredns//:coredns": "/kubernetes/bin/coredns",
         },
         fsspecs = [
-            "//metropolis/node/build:earlydev.fsspec",
+            "//osbase/build:earlydev.fsspec",
             ":rootfs.fsspec",
         ],
     )
diff --git a/metropolis/node/core/update/update.go b/metropolis/node/core/update/update.go
index 28b2381..b6d2ce4 100644
--- a/metropolis/node/core/update/update.go
+++ b/metropolis/node/core/update/update.go
@@ -23,9 +23,9 @@
 	"google.golang.org/grpc/status"
 	"google.golang.org/protobuf/proto"
 
-	"source.monogon.dev/metropolis/node/build/mkimage/osimage"
 	abloaderpb "source.monogon.dev/metropolis/node/core/abloader/spec"
 	"source.monogon.dev/osbase/blockdev"
+	"source.monogon.dev/osbase/build/mkimage/osimage"
 	"source.monogon.dev/osbase/efivarfs"
 	"source.monogon.dev/osbase/gpt"
 	"source.monogon.dev/osbase/kexec"
diff --git a/metropolis/test/nanoswitch/BUILD.bazel b/metropolis/test/nanoswitch/BUILD.bazel
index 4954480..323d53e 100644
--- a/metropolis/test/nanoswitch/BUILD.bazel
+++ b/metropolis/test/nanoswitch/BUILD.bazel
@@ -1,5 +1,5 @@
 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
-load("//metropolis/node/build:def.bzl", "node_initramfs")
+load("//osbase/build:def.bzl", "node_initramfs")
 
 go_library(
     name = "nanoswitch_lib",
@@ -42,7 +42,7 @@
         "@cacerts//file": "/etc/ssl/cert.pem",
     },
     fsspecs = [
-        "//metropolis/node/build:earlydev.fsspec",
+        "//osbase/build:earlydev.fsspec",
     ],
     visibility = ["//metropolis/test:__subpackages__"],
 )
diff --git a/metropolis/vm/smoketest/BUILD.bazel b/metropolis/vm/smoketest/BUILD.bazel
index 7d075d8..6225e2d 100644
--- a/metropolis/vm/smoketest/BUILD.bazel
+++ b/metropolis/vm/smoketest/BUILD.bazel
@@ -1,5 +1,5 @@
 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
-load("//metropolis/node/build:def.bzl", "node_initramfs")
+load("//osbase/build:def.bzl", "node_initramfs")
 
 go_library(
     name = "smoketest_lib",
@@ -21,7 +21,7 @@
         "//metropolis/vm/smoketest/payload": "/init",
     },
     fsspecs = [
-        "//metropolis/node/build:earlydev.fsspec",
+        "//osbase/build:earlydev.fsspec",
     ],
 )