2023-02-15 21:30:08 +00:00
|
|
|
package mount
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2023-03-02 15:46:25 +00:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"syscall"
|
|
|
|
"time"
|
|
|
|
|
2023-02-15 21:30:08 +00:00
|
|
|
"github.com/hashicorp/go-multierror"
|
|
|
|
cnst "github.com/kairos-io/immucore/internal/constants"
|
|
|
|
internalUtils "github.com/kairos-io/immucore/internal/utils"
|
2023-04-28 07:30:56 +00:00
|
|
|
"github.com/kairos-io/kairos-sdk/state"
|
|
|
|
"github.com/kairos-io/kairos-sdk/utils"
|
2023-05-04 16:58:26 +00:00
|
|
|
kcrypt "github.com/kairos-io/kcrypt/pkg/lib"
|
2023-03-01 10:42:46 +00:00
|
|
|
"github.com/mudler/go-kdetect"
|
2023-02-15 21:30:08 +00:00
|
|
|
"github.com/spectrocloud-labs/herd"
|
2023-03-01 10:42:46 +00:00
|
|
|
"golang.org/x/sys/unix"
|
2023-02-15 21:30:08 +00:00
|
|
|
)
|
|
|
|
|
2023-03-02 15:46:25 +00:00
|
|
|
// MountTmpfsDagStep adds the step to mount /tmp .
|
2023-02-15 21:30:08 +00:00
|
|
|
func (s *State) MountTmpfsDagStep(g *herd.Graph) error {
|
|
|
|
return g.Add(cnst.OpMountTmpfs, herd.WithCallback(s.MountOP("tmpfs", "/tmp", "tmpfs", []string{"rw"}, 10*time.Second)))
|
|
|
|
}
|
|
|
|
|
|
|
|
// MountRootDagStep will add the step to mount the Rootdir for the system
|
|
|
|
// 1 - mount the state partition to find the images (active/passive/recovery)
|
|
|
|
// 2 - mount the image as a loop device
|
2023-03-02 15:46:25 +00:00
|
|
|
// 3 - Mount the labels as /sysroot .
|
2023-02-15 21:30:08 +00:00
|
|
|
func (s *State) MountRootDagStep(g *herd.Graph) error {
|
|
|
|
var err error
|
2023-02-17 08:54:23 +00:00
|
|
|
|
2023-02-15 21:30:08 +00:00
|
|
|
// 1 - mount the state partition to find the images (active/passive/recovery)
|
|
|
|
err = g.Add(cnst.OpMountState,
|
|
|
|
herd.WithCallback(
|
|
|
|
s.MountOP(
|
2023-02-17 10:53:26 +00:00
|
|
|
internalUtils.GetState(),
|
2023-02-15 21:30:08 +00:00
|
|
|
s.path("/run/initramfs/cos-state"),
|
2023-02-17 10:53:26 +00:00
|
|
|
internalUtils.DiskFSType(internalUtils.GetState()),
|
2023-02-15 21:30:08 +00:00
|
|
|
[]string{
|
2023-02-17 08:54:23 +00:00
|
|
|
s.RootMountMode,
|
2023-02-15 21:30:08 +00:00
|
|
|
}, 60*time.Second),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
if err != nil {
|
2023-03-01 10:42:46 +00:00
|
|
|
internalUtils.Log.Err(err).Send()
|
2023-02-15 21:30:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 2 - mount the image as a loop device
|
|
|
|
err = g.Add(cnst.OpDiscoverState,
|
|
|
|
herd.WithDeps(cnst.OpMountState),
|
|
|
|
herd.WithCallback(
|
|
|
|
func(ctx context.Context) error {
|
2023-02-17 10:53:26 +00:00
|
|
|
// Check if loop device is mounted already
|
|
|
|
if internalUtils.IsMounted(s.TargetDevice) {
|
2023-03-01 10:42:46 +00:00
|
|
|
internalUtils.Log.Debug().Str("targetImage", s.TargetImage).Str("path", s.Rootdir).Str("TargetDevice", s.TargetDevice).Msg("Not mounting loop, already mounted")
|
2023-02-15 21:30:08 +00:00
|
|
|
return nil
|
|
|
|
}
|
2023-02-17 15:15:26 +00:00
|
|
|
_ = internalUtils.Fsck(s.path("/run/initramfs/cos-state", s.TargetImage))
|
2023-05-30 19:51:03 +00:00
|
|
|
cmd := fmt.Sprintf("losetup -f %s", s.path("/run/initramfs/cos-state", s.TargetImage))
|
2023-02-15 21:30:08 +00:00
|
|
|
_, err := utils.SH(cmd)
|
2023-02-16 09:41:02 +00:00
|
|
|
s.LogIfError(err, "losetup")
|
|
|
|
// Trigger udevadm
|
|
|
|
// On some systems the COS_ACTIVE/PASSIVE label is automatically shown as soon as we mount the device
|
|
|
|
// But on other it seems like it won't trigger which causes the sysroot to not be mounted as we cant find
|
|
|
|
// the block device by the target label. Make sure we run this after mounting so we refresh the devices.
|
2023-02-17 08:54:23 +00:00
|
|
|
sh, _ := utils.SH("udevadm trigger")
|
2023-03-01 10:42:46 +00:00
|
|
|
internalUtils.Log.Debug().Str("output", sh).Msg("udevadm trigger")
|
|
|
|
internalUtils.Log.Debug().Str("targetImage", s.TargetImage).Str("path", s.Rootdir).Str("TargetDevice", s.TargetDevice).Msg("mount done")
|
2023-02-15 21:30:08 +00:00
|
|
|
return err
|
|
|
|
},
|
|
|
|
))
|
|
|
|
if err != nil {
|
2023-03-01 10:42:46 +00:00
|
|
|
internalUtils.Log.Err(err).Send()
|
2023-02-15 21:30:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 3 - Mount the labels as Rootdir
|
|
|
|
err = g.Add(cnst.OpMountRoot,
|
|
|
|
herd.WithDeps(cnst.OpDiscoverState),
|
|
|
|
herd.WithCallback(
|
|
|
|
s.MountOP(
|
2023-02-17 10:53:26 +00:00
|
|
|
s.TargetDevice,
|
2023-02-15 21:30:08 +00:00
|
|
|
s.Rootdir,
|
2023-03-02 15:46:25 +00:00
|
|
|
"ext4", // TODO: Get this just in time? Currently if using DiskFSType is run immediately which is bad because its not mounted
|
2023-02-15 21:30:08 +00:00
|
|
|
[]string{
|
2023-02-17 08:54:23 +00:00
|
|
|
s.RootMountMode,
|
2023-02-15 21:30:08 +00:00
|
|
|
"suid",
|
|
|
|
"dev",
|
|
|
|
"exec",
|
|
|
|
"async",
|
|
|
|
}, 10*time.Second),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
if err != nil {
|
2023-03-01 10:42:46 +00:00
|
|
|
internalUtils.Log.Err(err).Send()
|
2023-02-15 21:30:08 +00:00
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// RootfsStageDagStep will add the rootfs stage.
|
2023-04-12 14:19:21 +00:00
|
|
|
func (s *State) RootfsStageDagStep(g *herd.Graph, opts ...herd.OpOption) error {
|
|
|
|
return g.Add(cnst.OpRootfsHook, append(opts, herd.WithCallback(s.RunStageOp("rootfs")))...)
|
2023-02-15 21:30:08 +00:00
|
|
|
}
|
|
|
|
|
2023-03-01 10:42:46 +00:00
|
|
|
// InitramfsStageDagStep will add the rootfs stage.
|
2023-04-12 14:19:21 +00:00
|
|
|
func (s *State) InitramfsStageDagStep(g *herd.Graph, opts ...herd.OpOption) error {
|
|
|
|
return g.Add(cnst.OpInitramfsHook, append(opts, herd.WithCallback(s.RunStageOp("initramfs")))...)
|
2023-03-01 10:42:46 +00:00
|
|
|
}
|
|
|
|
|
2023-03-02 15:46:25 +00:00
|
|
|
// LoadEnvLayoutDagStep will add the stage to load from cos-layout.env and fill the proper CustomMounts, OverlayDirs and BindMounts.
|
2023-09-22 12:56:26 +00:00
|
|
|
func (s *State) LoadEnvLayoutDagStep(g *herd.Graph, opts ...herd.OpOption) error {
|
2023-02-15 21:30:08 +00:00
|
|
|
return g.Add(cnst.OpLoadConfig,
|
2023-09-22 12:56:26 +00:00
|
|
|
append(opts, herd.WithDeps(cnst.OpRootfsHook),
|
|
|
|
herd.WithCallback(func(ctx context.Context) error {
|
|
|
|
if s.CustomMounts == nil {
|
|
|
|
s.CustomMounts = map[string]string{}
|
|
|
|
}
|
2023-02-15 21:30:08 +00:00
|
|
|
|
2023-09-22 12:56:26 +00:00
|
|
|
env, err := internalUtils.ReadEnv("/run/cos/cos-layout.env")
|
|
|
|
if err != nil {
|
|
|
|
internalUtils.Log.Err(err).Msg("Reading env")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// populate from env here
|
|
|
|
s.OverlayDirs = internalUtils.CleanupSlice(strings.Split(env["RW_PATHS"], " "))
|
|
|
|
// Append default RW_Paths if list is empty, otherwise we won't boot properly
|
|
|
|
if len(s.OverlayDirs) == 0 {
|
|
|
|
s.OverlayDirs = cnst.DefaultRWPaths()
|
|
|
|
}
|
2023-02-22 10:26:59 +00:00
|
|
|
|
2023-09-22 12:56:26 +00:00
|
|
|
// Remove any duplicates
|
|
|
|
s.OverlayDirs = internalUtils.UniqueSlice(internalUtils.CleanupSlice(s.OverlayDirs))
|
2023-02-15 21:30:08 +00:00
|
|
|
|
2023-09-22 12:56:26 +00:00
|
|
|
s.BindMounts = strings.Split(env["PERSISTENT_STATE_PATHS"], " ")
|
|
|
|
// Add custom bind mounts
|
|
|
|
s.BindMounts = append(s.BindMounts, strings.Split(env["CUSTOM_BIND_MOUNTS"], " ")...)
|
|
|
|
// Remove any duplicates
|
|
|
|
s.BindMounts = internalUtils.UniqueSlice(internalUtils.CleanupSlice(s.BindMounts))
|
2023-02-15 21:30:08 +00:00
|
|
|
|
2023-09-22 12:56:26 +00:00
|
|
|
// Load Overlay config
|
|
|
|
overlayConfig := env["OVERLAY"]
|
|
|
|
if overlayConfig != "" {
|
|
|
|
s.OverlayBase = overlayConfig
|
|
|
|
}
|
2023-03-02 09:44:54 +00:00
|
|
|
|
2023-09-22 12:56:26 +00:00
|
|
|
s.StateDir = env["PERSISTENT_STATE_TARGET"]
|
|
|
|
if s.StateDir == "" {
|
|
|
|
s.StateDir = cnst.PersistentStateTarget
|
|
|
|
}
|
2023-02-15 21:30:08 +00:00
|
|
|
|
2023-09-22 12:56:26 +00:00
|
|
|
addLine := func(d string) {
|
|
|
|
dat := strings.Split(d, ":")
|
|
|
|
if len(dat) == 2 {
|
|
|
|
disk := dat[0]
|
|
|
|
path := dat[1]
|
|
|
|
s.CustomMounts[disk] = path
|
|
|
|
}
|
2023-02-15 21:30:08 +00:00
|
|
|
}
|
2023-09-22 12:56:26 +00:00
|
|
|
// Parse custom mounts also from cmdline (rd.cos.mount=)
|
|
|
|
// Parse custom mounts also from cmdline (rd.immucore.mount=)
|
|
|
|
// Parse custom mounts also from env file (VOLUMES)
|
2023-03-08 10:45:11 +00:00
|
|
|
|
2023-09-22 12:56:26 +00:00
|
|
|
for _, v := range append(append(internalUtils.ReadCMDLineArg("rd.cos.mount="), internalUtils.ReadCMDLineArg("rd.immucore.mount=")...), strings.Split(env["VOLUMES"], " ")...) {
|
|
|
|
addLine(internalUtils.ParseMount(v))
|
|
|
|
}
|
2023-02-15 21:30:08 +00:00
|
|
|
|
2023-09-22 12:56:26 +00:00
|
|
|
return nil
|
|
|
|
}))...)
|
2023-02-15 21:30:08 +00:00
|
|
|
}
|
|
|
|
|
2023-03-02 15:46:25 +00:00
|
|
|
// MountOemDagStep will add mounting COS_OEM partition under s.Rootdir + /oem .
|
2023-09-22 12:56:26 +00:00
|
|
|
func (s *State) MountOemDagStep(g *herd.Graph, opts ...herd.OpOption) error {
|
2023-02-15 21:30:08 +00:00
|
|
|
return g.Add(cnst.OpMountOEM,
|
2023-09-22 12:56:26 +00:00
|
|
|
append(opts,
|
|
|
|
herd.EnableIf(func() bool {
|
|
|
|
runtime, _ := state.NewRuntime()
|
|
|
|
switch runtime.BootState {
|
|
|
|
// Don't run this on LiveCD/Netboot
|
|
|
|
case state.LiveCD:
|
|
|
|
return false
|
|
|
|
default:
|
|
|
|
return internalUtils.GetOemLabel() != ""
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
herd.WithCallback(func(ctx context.Context) error {
|
|
|
|
if internalUtils.IsUKI() {
|
|
|
|
_, err := os.Stat("/dev/disk/by-label/UKI_ISO_INSTALL")
|
|
|
|
if err == nil {
|
|
|
|
// Do nothing during uki install
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
op := s.MountOP(
|
|
|
|
fmt.Sprintf("/dev/disk/by-label/%s", internalUtils.GetOemLabel()),
|
|
|
|
s.path("/oem"),
|
|
|
|
internalUtils.DiskFSType(fmt.Sprintf("/dev/disk/by-label/%s", internalUtils.GetOemLabel())),
|
|
|
|
[]string{
|
|
|
|
"rw",
|
|
|
|
"suid",
|
|
|
|
"dev",
|
|
|
|
"exec",
|
|
|
|
"async",
|
|
|
|
}, time.Duration(internalUtils.GetOemTimeout())*time.Second)
|
|
|
|
return op(ctx)
|
|
|
|
}))...)
|
2023-02-15 21:30:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// MountBaseOverlayDagStep will add mounting /run/overlay as an overlay dir
|
2023-03-02 15:46:25 +00:00
|
|
|
// Requires the config-load step because some parameters can come from there.
|
2023-09-22 12:56:26 +00:00
|
|
|
func (s *State) MountBaseOverlayDagStep(g *herd.Graph, opts ...herd.OpOption) error {
|
2023-02-15 21:30:08 +00:00
|
|
|
return g.Add(cnst.OpMountBaseOverlay,
|
2023-09-22 12:56:26 +00:00
|
|
|
append(opts, herd.WithDeps(cnst.OpLoadConfig),
|
|
|
|
herd.WithCallback(
|
|
|
|
func(ctx context.Context) error {
|
|
|
|
op, err := baseOverlay(Overlay{
|
|
|
|
Base: "/run/overlay",
|
|
|
|
BackingBase: s.OverlayBase,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err2 := op.run()
|
|
|
|
// No error, add fstab
|
|
|
|
if err2 == nil {
|
|
|
|
s.fstabs = append(s.fstabs, &op.FstabEntry)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// Error but its already mounted error, dont add fstab but dont return error
|
|
|
|
if err2 != nil && errors.Is(err2, cnst.ErrAlreadyMounted) {
|
|
|
|
return nil
|
|
|
|
}
|
2023-02-15 21:30:08 +00:00
|
|
|
|
2023-09-22 12:56:26 +00:00
|
|
|
return err2
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)...)
|
2023-02-15 21:30:08 +00:00
|
|
|
}
|
|
|
|
|
2023-03-02 15:46:25 +00:00
|
|
|
// MountCustomOverlayDagStep will add mounting s.OverlayDirs under /run/overlay .
|
2023-09-22 12:56:26 +00:00
|
|
|
func (s *State) MountCustomOverlayDagStep(g *herd.Graph, opts ...herd.OpOption) error {
|
2023-02-15 21:30:08 +00:00
|
|
|
return g.Add(cnst.OpOverlayMount,
|
2023-09-22 12:56:26 +00:00
|
|
|
append(opts, herd.WithDeps(cnst.OpLoadConfig, cnst.OpMountBaseOverlay),
|
|
|
|
herd.WithCallback(
|
|
|
|
func(ctx context.Context) error {
|
|
|
|
var multierr *multierror.Error
|
|
|
|
internalUtils.Log.Debug().Strs("dirs", s.OverlayDirs).Msg("Mounting overlays")
|
|
|
|
for _, p := range s.OverlayDirs {
|
|
|
|
internalUtils.Log.Debug().Str("what", p).Msg("Overlay mount start")
|
|
|
|
op := mountWithBaseOverlay(p, s.Rootdir, "/run/overlay")
|
|
|
|
err := op.run()
|
|
|
|
// Append to errors only if it's not an already mounted error
|
|
|
|
if err != nil && !errors.Is(err, cnst.ErrAlreadyMounted) {
|
|
|
|
internalUtils.Log.Err(err).Msg("overlay mount")
|
|
|
|
multierr = multierror.Append(multierr, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
s.fstabs = append(s.fstabs, &op.FstabEntry)
|
|
|
|
internalUtils.Log.Debug().Str("what", p).Msg("Overlay mount done")
|
2023-02-15 21:30:08 +00:00
|
|
|
}
|
2023-09-22 12:56:26 +00:00
|
|
|
return multierr.ErrorOrNil()
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)...)
|
2023-02-15 21:30:08 +00:00
|
|
|
}
|
|
|
|
|
2023-03-02 15:46:25 +00:00
|
|
|
// MountCustomMountsDagStep will add mounting s.CustomMounts .
|
2023-09-22 12:56:26 +00:00
|
|
|
func (s *State) MountCustomMountsDagStep(g *herd.Graph, opts ...herd.OpOption) error {
|
|
|
|
return g.Add(cnst.OpCustomMounts, append(opts, herd.WithDeps(cnst.OpLoadConfig),
|
2023-02-15 21:30:08 +00:00
|
|
|
herd.WithCallback(func(ctx context.Context) error {
|
|
|
|
var err *multierror.Error
|
2023-03-08 10:45:11 +00:00
|
|
|
internalUtils.Log.Debug().Interface("mounts", s.CustomMounts).Msg("Mounting custom mounts")
|
2023-02-15 21:30:08 +00:00
|
|
|
|
|
|
|
for what, where := range s.CustomMounts {
|
2023-03-21 09:58:15 +00:00
|
|
|
internalUtils.Log.Debug().Str("what", what).Str("where", where).Msg("Custom mount start")
|
2023-02-15 21:30:08 +00:00
|
|
|
// TODO: scan for the custom mount disk to know the underlying fs and set it proper
|
|
|
|
fstype := "ext4"
|
|
|
|
mountOptions := []string{"ro"}
|
2023-03-01 10:42:46 +00:00
|
|
|
// TODO: Are custom mounts always rw?ro?depends? Clarify.
|
2023-02-15 21:30:08 +00:00
|
|
|
// Persistent needs to be RW
|
|
|
|
if strings.Contains(what, "COS_PERSISTENT") {
|
|
|
|
mountOptions = []string{"rw"}
|
|
|
|
}
|
2023-03-01 10:42:46 +00:00
|
|
|
err2 := s.MountOP(
|
2023-02-15 21:30:08 +00:00
|
|
|
what,
|
|
|
|
s.path(where),
|
|
|
|
fstype,
|
|
|
|
mountOptions,
|
2023-03-01 10:42:46 +00:00
|
|
|
3*time.Second,
|
|
|
|
)(ctx)
|
2023-02-15 21:30:08 +00:00
|
|
|
|
2023-03-01 10:42:46 +00:00
|
|
|
// If its COS_OEM and it fails then we can safely ignore, as it's not mandatory to have COS_OEM
|
|
|
|
if err2 != nil && !strings.Contains(what, "COS_OEM") {
|
|
|
|
err = multierror.Append(err, err2)
|
|
|
|
}
|
2023-03-21 09:58:15 +00:00
|
|
|
internalUtils.Log.Debug().Str("what", what).Str("where", where).Msg("Custom mount done")
|
2023-02-15 21:30:08 +00:00
|
|
|
}
|
2023-05-08 13:20:05 +00:00
|
|
|
internalUtils.Log.Warn().Err(err.ErrorOrNil()).Send()
|
2023-02-15 21:30:08 +00:00
|
|
|
|
|
|
|
return err.ErrorOrNil()
|
|
|
|
}),
|
2023-09-22 12:56:26 +00:00
|
|
|
)...)
|
2023-02-15 21:30:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// MountCustomBindsDagStep will add mounting s.BindMounts
|
2023-03-02 15:46:25 +00:00
|
|
|
// mount state is defined over a custom mount (/usr/local/.state for instance, needs to be mounted over a device).
|
2023-09-22 12:56:26 +00:00
|
|
|
func (s *State) MountCustomBindsDagStep(g *herd.Graph, opts ...herd.OpOption) error {
|
2023-02-15 21:30:08 +00:00
|
|
|
return g.Add(cnst.OpMountBind,
|
2023-09-22 12:56:26 +00:00
|
|
|
append(opts, herd.WithDeps(cnst.OpOverlayMount, cnst.OpCustomMounts, cnst.OpLoadConfig),
|
|
|
|
herd.WithCallback(
|
|
|
|
func(ctx context.Context) error {
|
|
|
|
var err *multierror.Error
|
|
|
|
internalUtils.Log.Debug().Strs("mounts", s.BindMounts).Msg("Mounting binds")
|
|
|
|
|
|
|
|
for _, p := range s.SortedBindMounts() {
|
|
|
|
internalUtils.Log.Debug().Str("what", p).Msg("Bind mount start")
|
|
|
|
op := mountBind(p, s.Rootdir, s.StateDir)
|
|
|
|
err2 := op.run()
|
|
|
|
if err2 == nil {
|
|
|
|
// Only append to fstabs if there was no error, otherwise we will try to mount it after switch_root
|
|
|
|
s.fstabs = append(s.fstabs, &op.FstabEntry)
|
|
|
|
}
|
|
|
|
// Append to errors only if it's not an already mounted error
|
|
|
|
if err2 != nil && !errors.Is(err2, cnst.ErrAlreadyMounted) {
|
|
|
|
internalUtils.Log.Err(err2).Send()
|
|
|
|
err = multierror.Append(err, err2)
|
|
|
|
}
|
|
|
|
internalUtils.Log.Debug().Str("what", p).Msg("Bind mount end")
|
2023-02-15 21:30:08 +00:00
|
|
|
}
|
2023-09-22 12:56:26 +00:00
|
|
|
internalUtils.Log.Warn().Err(err.ErrorOrNil()).Send()
|
|
|
|
return err.ErrorOrNil()
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)...)
|
2023-02-15 21:30:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// WriteFstabDagStep will add writing the final fstab file with all the mounts
|
2023-03-02 15:46:25 +00:00
|
|
|
// Depends on everything but weak, so it will still try to write.
|
2023-02-15 21:30:08 +00:00
|
|
|
func (s *State) WriteFstabDagStep(g *herd.Graph) error {
|
|
|
|
return g.Add(cnst.OpWriteFstab,
|
2023-03-08 19:05:57 +00:00
|
|
|
herd.WithDeps(cnst.OpMountRoot, cnst.OpDiscoverState, cnst.OpLoadConfig),
|
|
|
|
herd.WithWeakDeps(cnst.OpMountOEM, cnst.OpCustomMounts, cnst.OpMountBind, cnst.OpOverlayMount),
|
2023-02-15 21:30:08 +00:00
|
|
|
herd.WithCallback(s.WriteFstab(s.path("/etc/fstab"))))
|
|
|
|
}
|
|
|
|
|
|
|
|
// WriteSentinelDagStep sets the sentinel file to identify the boot mode.
|
2023-03-02 15:46:25 +00:00
|
|
|
// This is used by several things to know in which state they are, for example cloud configs.
|
2023-03-14 09:33:38 +00:00
|
|
|
func (s *State) WriteSentinelDagStep(g *herd.Graph, deps ...string) error {
|
2023-02-15 21:30:08 +00:00
|
|
|
return g.Add(cnst.OpSentinel,
|
2023-03-14 09:33:38 +00:00
|
|
|
herd.WithDeps(deps...),
|
2023-02-15 21:30:08 +00:00
|
|
|
herd.WithCallback(func(ctx context.Context) error {
|
|
|
|
var sentinel string
|
|
|
|
|
|
|
|
err := internalUtils.CreateIfNotExists("/run/cos/")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
runtime, err := state.NewRuntime()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch runtime.BootState {
|
|
|
|
case state.Active:
|
|
|
|
sentinel = "active_mode"
|
|
|
|
case state.Passive:
|
|
|
|
sentinel = "passive_mode"
|
|
|
|
case state.Recovery:
|
|
|
|
sentinel = "recovery_mode"
|
|
|
|
case state.LiveCD:
|
|
|
|
sentinel = "live_mode"
|
|
|
|
default:
|
|
|
|
sentinel = string(state.Unknown)
|
|
|
|
}
|
2023-02-16 14:30:54 +00:00
|
|
|
|
2023-03-01 10:42:46 +00:00
|
|
|
internalUtils.Log.Info().Str("to", sentinel).Msg("Setting sentinel file")
|
2023-02-15 21:30:08 +00:00
|
|
|
err = os.WriteFile(filepath.Join("/run/cos/", sentinel), []byte("1"), os.ModePerm)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-03-01 10:42:46 +00:00
|
|
|
|
|
|
|
// Lets add a uki sentinel as well!
|
2023-03-09 18:45:24 +00:00
|
|
|
cmdline, _ := os.ReadFile(internalUtils.GetHostProcCmdline())
|
|
|
|
if strings.Contains(string(cmdline), "rd.immucore.uki") {
|
2023-03-01 10:42:46 +00:00
|
|
|
err = os.WriteFile("/run/cos/uki_mode", []byte("1"), os.ModePerm)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-15 21:30:08 +00:00
|
|
|
return nil
|
|
|
|
}))
|
|
|
|
}
|
2023-03-01 10:42:46 +00:00
|
|
|
|
2023-03-14 09:33:38 +00:00
|
|
|
func (s *State) UKIMountBaseSystem(g *herd.Graph) error {
|
|
|
|
type mount struct {
|
|
|
|
where string
|
|
|
|
what string
|
|
|
|
fs string
|
|
|
|
flags uintptr
|
|
|
|
data string
|
|
|
|
}
|
|
|
|
|
|
|
|
return g.Add(
|
|
|
|
cnst.OpUkiBaseMounts,
|
|
|
|
herd.WithCallback(
|
|
|
|
func(ctx context.Context) error {
|
|
|
|
var err error
|
|
|
|
mounts := []mount{
|
|
|
|
{
|
|
|
|
"/sys",
|
|
|
|
"sysfs",
|
|
|
|
"sysfs",
|
|
|
|
syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_NOEXEC | syscall.MS_RELATIME,
|
|
|
|
"",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"/dev",
|
|
|
|
"devtmpfs",
|
|
|
|
"devtmpfs",
|
|
|
|
syscall.MS_NOSUID,
|
|
|
|
"mode=755",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"/tmp",
|
|
|
|
"tmpfs",
|
|
|
|
"tmpfs",
|
|
|
|
syscall.MS_NOSUID | syscall.MS_NODEV,
|
|
|
|
"",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, m := range mounts {
|
|
|
|
e := os.MkdirAll(m.where, 0755)
|
|
|
|
if e != nil {
|
|
|
|
err = multierror.Append(err, e)
|
|
|
|
internalUtils.Log.Err(e).Msg("Creating dir")
|
|
|
|
}
|
2023-09-19 07:51:57 +00:00
|
|
|
|
2023-03-14 09:33:38 +00:00
|
|
|
e = syscall.Mount(m.what, m.where, m.fs, m.flags, m.data)
|
|
|
|
if e != nil {
|
|
|
|
err = multierror.Append(err, e)
|
|
|
|
internalUtils.Log.Err(e).Str("what", m.what).Str("where", m.where).Str("type", m.fs).Msg("Mounting")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-03-01 10:42:46 +00:00
|
|
|
// UKIBootInitDagStep tries to launch /sbin/init in root and pass over the system
|
|
|
|
// booting to the real init process
|
2023-03-02 15:46:25 +00:00
|
|
|
// Drops to emergency if not able to. Panic if it cant even launch emergency.
|
2023-03-14 09:33:38 +00:00
|
|
|
func (s *State) UKIBootInitDagStep(g *herd.Graph) error {
|
2023-03-01 10:42:46 +00:00
|
|
|
return g.Add(cnst.OpUkiInit,
|
2023-09-22 12:56:26 +00:00
|
|
|
herd.WeakDeps,
|
2023-03-14 09:33:38 +00:00
|
|
|
herd.WithWeakDeps(cnst.OpRemountRootRO, cnst.OpRootfsHook, cnst.OpInitramfsHook, cnst.OpWriteFstab),
|
2023-03-01 10:42:46 +00:00
|
|
|
herd.WithCallback(func(ctx context.Context) error {
|
|
|
|
// Print dag before exit, otherwise its never printed as we never exit the program
|
|
|
|
internalUtils.Log.Info().Msg(s.WriteDAG(g))
|
|
|
|
internalUtils.Log.Debug().Msg("Executing init callback!")
|
|
|
|
internalUtils.CloseLogFiles()
|
|
|
|
if err := unix.Exec("/sbin/init", []string{"/sbin/init", "--system"}, os.Environ()); err != nil {
|
|
|
|
internalUtils.Log.Err(err).Msg("running init")
|
|
|
|
// drop to emergency shell
|
|
|
|
if err := unix.Exec("/bin/bash", []string{"/bin/bash"}, os.Environ()); err != nil {
|
|
|
|
internalUtils.Log.Fatal().Msg("Could not drop to emergency shell")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
2023-03-02 15:46:25 +00:00
|
|
|
// UKIRemountRootRODagStep remount root read only.
|
2023-03-14 09:33:38 +00:00
|
|
|
func (s *State) UKIRemountRootRODagStep(g *herd.Graph) error {
|
2023-03-01 10:42:46 +00:00
|
|
|
return g.Add(cnst.OpRemountRootRO,
|
2023-03-14 09:33:38 +00:00
|
|
|
herd.WithDeps(cnst.OpRootfsHook),
|
2023-03-01 10:42:46 +00:00
|
|
|
herd.WithCallback(func(ctx context.Context) error {
|
2023-03-14 09:33:38 +00:00
|
|
|
var err error
|
|
|
|
for i := 1; i < 5; i++ {
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
// Should we try to stop udev here?
|
|
|
|
err = syscall.Mount("", "/", "", syscall.MS_REMOUNT|syscall.MS_RDONLY, "")
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return err
|
2023-03-01 10:42:46 +00:00
|
|
|
}),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// UKIUdevDaemon launches the udevd daemon and triggers+settles in order to discover devices
|
|
|
|
// Needed if we expect to find devices by label...
|
|
|
|
func (s *State) UKIUdevDaemon(g *herd.Graph) error {
|
|
|
|
return g.Add(cnst.OpUkiUdev,
|
2023-03-14 09:33:38 +00:00
|
|
|
herd.WithDeps(cnst.OpUkiBaseMounts, cnst.OpUkiKernelModules),
|
2023-03-01 10:42:46 +00:00
|
|
|
herd.WithCallback(func(ctx context.Context) error {
|
|
|
|
// Should probably figure out other udevd binaries....
|
|
|
|
var udevBin string
|
|
|
|
if _, err := os.Stat("/usr/lib/systemd/systemd-udevd"); !os.IsNotExist(err) {
|
|
|
|
udevBin = "/usr/lib/systemd/systemd-udevd"
|
|
|
|
}
|
|
|
|
cmd := fmt.Sprintf("%s --daemon", udevBin)
|
|
|
|
out, err := internalUtils.CommandWithPath(cmd)
|
|
|
|
internalUtils.Log.Debug().Str("out", out).Str("cmd", cmd).Msg("Udev daemon")
|
|
|
|
if err != nil {
|
|
|
|
internalUtils.Log.Err(err).Msg("Udev daemon")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
out, err = internalUtils.CommandWithPath("udevadm trigger")
|
|
|
|
internalUtils.Log.Debug().Str("out", out).Msg("Udev trigger")
|
|
|
|
if err != nil {
|
|
|
|
internalUtils.Log.Err(err).Msg("Udev trigger")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
out, err = internalUtils.CommandWithPath("udevadm settle")
|
|
|
|
internalUtils.Log.Debug().Str("out", out).Msg("Udev settle")
|
|
|
|
if err != nil {
|
|
|
|
internalUtils.Log.Err(err).Msg("Udev settle")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadKernelModules loads kernel modules needed during uki boot to load the disks for.
|
|
|
|
// Mainly block devices and net devices
|
2023-03-02 15:46:25 +00:00
|
|
|
// probably others down the line.
|
2023-03-01 10:42:46 +00:00
|
|
|
func (s *State) LoadKernelModules(g *herd.Graph) error {
|
2023-03-14 09:33:38 +00:00
|
|
|
return g.Add(cnst.OpUkiKernelModules,
|
|
|
|
herd.WithDeps(cnst.OpUkiBaseMounts),
|
2023-03-01 10:42:46 +00:00
|
|
|
herd.WithCallback(func(ctx context.Context) error {
|
|
|
|
drivers, err := kdetect.ProbeKernelModules("")
|
|
|
|
if err != nil {
|
|
|
|
internalUtils.Log.Err(err).Msg("Detecting needed modules")
|
|
|
|
}
|
2023-09-22 12:56:26 +00:00
|
|
|
drivers = append(drivers, cnst.GenericKernelDrivers()...)
|
2023-03-01 10:42:46 +00:00
|
|
|
internalUtils.Log.Debug().Strs("drivers", drivers).Msg("Detecting needed modules")
|
|
|
|
for _, driver := range drivers {
|
|
|
|
cmd := fmt.Sprintf("modprobe %s", driver)
|
|
|
|
out, err := internalUtils.CommandWithPath(cmd)
|
|
|
|
if err != nil {
|
|
|
|
internalUtils.Log.Err(err).Str("out", out).Msg("modprobe")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
}
|
2023-03-08 10:45:11 +00:00
|
|
|
|
|
|
|
// WaitForSysrootDagStep waits for the s.Rootdir and s.Rootdir/system paths to be there
|
|
|
|
// Useful for livecd/netboot as we want to run steps after s.Rootdir is ready but we don't mount it ourselves.
|
|
|
|
func (s *State) WaitForSysrootDagStep(g *herd.Graph) error {
|
|
|
|
return g.Add(cnst.OpWaitForSysroot,
|
|
|
|
herd.WithCallback(func(ctx context.Context) error {
|
|
|
|
cc := time.After(60 * time.Second)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
default:
|
|
|
|
time.Sleep(2 * time.Second)
|
|
|
|
_, err := os.Stat(s.Rootdir)
|
|
|
|
if err != nil {
|
|
|
|
internalUtils.Log.Debug().Str("what", s.Rootdir).Msg("Checking path existence")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
_, err = os.Stat(filepath.Join(s.Rootdir, "system"))
|
|
|
|
if err != nil {
|
|
|
|
internalUtils.Log.Debug().Str("what", filepath.Join(s.Rootdir, "system")).Msg("Checking path existence")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
case <-ctx.Done():
|
|
|
|
e := fmt.Errorf("context canceled")
|
|
|
|
internalUtils.Log.Err(e).Str("what", s.Rootdir).Msg("filepath check canceled")
|
|
|
|
return e
|
|
|
|
case <-cc:
|
|
|
|
e := fmt.Errorf("timeout exhausted")
|
|
|
|
internalUtils.Log.Err(e).Str("what", s.Rootdir).Msg("filepath check timeout")
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
}
|
2023-04-19 14:23:51 +00:00
|
|
|
|
2023-05-04 16:58:26 +00:00
|
|
|
// LVMActivation will try to activate lvm volumes/groups on the system.
|
2023-04-19 14:23:51 +00:00
|
|
|
func (s *State) LVMActivation(g *herd.Graph) error {
|
|
|
|
return g.Add(cnst.OpLvmActivate, herd.WithCallback(func(ctx context.Context) error {
|
|
|
|
return internalUtils.ActivateLVM()
|
|
|
|
}))
|
|
|
|
}
|
2023-05-04 16:58:26 +00:00
|
|
|
|
|
|
|
// RunKcrypt will run the UnlockAll method of kcrypt to unlock the encrypted partitions
|
|
|
|
// Requires sysroot to be mounted as the kcrypt-challenger binary is not injected in the initramfs.
|
|
|
|
func (s *State) RunKcrypt(g *herd.Graph, opts ...herd.OpOption) error {
|
|
|
|
return g.Add(cnst.OpKcryptUnlock, append(opts, herd.WithCallback(func(ctx context.Context) error { return kcrypt.UnlockAll() }))...)
|
|
|
|
}
|
2023-05-08 07:44:43 +00:00
|
|
|
|
|
|
|
// RunKcryptUpgrade will upgrade encrypted partitions created with 1.x to the new 2.x format, where
|
|
|
|
// we inspect the uuid of the partition directly to know which label to use for the key
|
|
|
|
// As those old installs have an old agent the only way to do it is during the first boot after the upgrade to the newest immucore.
|
|
|
|
func (s *State) RunKcryptUpgrade(g *herd.Graph, opts ...herd.OpOption) error {
|
|
|
|
return g.Add(cnst.OpKcryptUpgrade, append(opts, herd.WithCallback(func(ctx context.Context) error {
|
|
|
|
return internalUtils.UpgradeKcryptPartitions()
|
|
|
|
}))...)
|
|
|
|
}
|