2024-03-20 10:48:51 +00:00
|
|
|
package state
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2024-04-09 08:40:28 +00:00
|
|
|
"strconv"
|
2024-03-20 10:48:51 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
cnst "github.com/kairos-io/immucore/internal/constants"
|
|
|
|
internalUtils "github.com/kairos-io/immucore/internal/utils"
|
|
|
|
"github.com/kairos-io/immucore/pkg/op"
|
|
|
|
"github.com/kairos-io/kairos-sdk/utils"
|
|
|
|
kcrypt "github.com/kairos-io/kcrypt/pkg/lib"
|
|
|
|
"github.com/spectrocloud-labs/herd"
|
|
|
|
)
|
|
|
|
|
|
|
|
// MountTmpfsDagStep adds the step to mount /tmp .
|
|
|
|
func (s *State) MountTmpfsDagStep(g *herd.Graph) error {
|
|
|
|
return g.Add(cnst.OpMountTmpfs, herd.WithCallback(
|
|
|
|
func(_ context.Context) error {
|
|
|
|
fstab, err := op.MountOPWithFstab("tmpfs", "/tmp", "tmpfs", []string{"rw"}, 10*time.Second)
|
|
|
|
for _, f := range fstab {
|
|
|
|
s.fstabs = append(s.fstabs, f)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
// 3 - Mount the labels as /sysroot .
|
|
|
|
func (s *State) MountRootDagStep(g *herd.Graph) error {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
// 1 - mount the state partition to find the images (active/passive/recovery)
|
|
|
|
err = g.Add(cnst.OpMountState,
|
|
|
|
herd.WithCallback(
|
|
|
|
func(_ context.Context) error {
|
|
|
|
fstab, err := op.MountOPWithFstab(
|
|
|
|
internalUtils.GetState(),
|
|
|
|
s.path("/run/initramfs/cos-state"),
|
|
|
|
internalUtils.DiskFSType(internalUtils.GetState()),
|
|
|
|
[]string{
|
|
|
|
s.RootMountMode,
|
|
|
|
}, 60*time.Second)
|
|
|
|
for _, f := range fstab {
|
|
|
|
s.fstabs = append(s.fstabs, f)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
internalUtils.Log.Err(err).Send()
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2 - mount the image as a loop device
|
|
|
|
err = g.Add(cnst.OpDiscoverState,
|
|
|
|
herd.WithDeps(cnst.OpMountState),
|
|
|
|
herd.WithCallback(
|
|
|
|
func(_ context.Context) error {
|
|
|
|
// Check if loop device is mounted already
|
|
|
|
if internalUtils.IsMounted(s.TargetDevice) {
|
|
|
|
internalUtils.Log.Debug().Str("targetImage", s.TargetImage).Str("path", s.Rootdir).Str("TargetDevice", s.TargetDevice).Msg("Not mounting loop, already mounted")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
_ = internalUtils.Fsck(s.path("/run/initramfs/cos-state", s.TargetImage))
|
|
|
|
cmd := fmt.Sprintf("losetup -f %s", s.path("/run/initramfs/cos-state", s.TargetImage))
|
|
|
|
_, err := utils.SH(cmd)
|
|
|
|
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.
|
|
|
|
sh, _ := utils.SH("udevadm trigger")
|
|
|
|
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")
|
|
|
|
return err
|
|
|
|
},
|
|
|
|
))
|
|
|
|
if err != nil {
|
|
|
|
internalUtils.Log.Err(err).Send()
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3 - Mount the labels as Rootdir
|
|
|
|
err = g.Add(cnst.OpMountRoot,
|
|
|
|
herd.WithDeps(cnst.OpDiscoverState),
|
|
|
|
herd.WithCallback(
|
|
|
|
func(_ context.Context) error {
|
|
|
|
fstab, err := op.MountOPWithFstab(
|
|
|
|
s.TargetDevice,
|
|
|
|
s.Rootdir,
|
|
|
|
"ext4", // TODO: Get this just in time? Currently if using DiskFSType is run immediately which is bad because its not mounted
|
|
|
|
[]string{
|
|
|
|
s.RootMountMode,
|
|
|
|
"suid",
|
|
|
|
"dev",
|
|
|
|
"exec",
|
|
|
|
"async",
|
|
|
|
}, 10*time.Second)
|
|
|
|
for _, f := range fstab {
|
|
|
|
s.fstabs = append(s.fstabs, f)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
internalUtils.Log.Err(err).Send()
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2024-04-09 08:40:28 +00:00
|
|
|
var timeout = 60 * time.Second
|
|
|
|
timeoutArg := internalUtils.CleanupSlice(internalUtils.ReadCMDLineArg("rd.immucore.sysrootwait="))
|
|
|
|
if len(timeoutArg) > 0 {
|
|
|
|
atoi, err := strconv.Atoi(timeoutArg[0])
|
|
|
|
if err == nil && atoi > 0 {
|
|
|
|
timeout = time.Duration(atoi) * time.Second
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internalUtils.Log.Debug().Str("timeout", timeout.String()).Msg("Waiting for sysroot")
|
|
|
|
|
|
|
|
cc := time.After(timeout)
|
2024-03-20 10:48:51 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
// LVMActivation will try to activate lvm volumes/groups on the system.
|
|
|
|
func (s *State) LVMActivation(g *herd.Graph) error {
|
|
|
|
return g.Add(cnst.OpLvmActivate, herd.WithCallback(func(_ context.Context) error {
|
|
|
|
return internalUtils.ActivateLVM()
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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(_ context.Context) error {
|
|
|
|
internalUtils.Log.Debug().Msg("Unlocking with kcrypt")
|
|
|
|
return kcrypt.UnlockAllWithLogger(false, internalUtils.Log)
|
|
|
|
}))...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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(_ context.Context) error {
|
|
|
|
return internalUtils.UpgradeKcryptPartitions()
|
|
|
|
}))...)
|
|
|
|
}
|