mirror of
https://github.com/kairos-io/immucore.git
synced 2025-08-02 06:52:03 +00:00
This makes immucore run more in parallel rather than block everything else. We just tell sysroot.mount that eventually it will be mounted and to wait for a bit. This allows us to be more flexible where to run and run in parallel in cases like cdrom in which we may do things but we need the sysroot to be mounted already but not from us. Also adds the initramfs stage directly in immucore and merges all the dracut config into one Dont create sysroot, just add a timeout override so it waits for us Dont block on the service, just make sure to finish before initrd.target Fix mounts from cmdline More proper log Store logs under the /run/immucore dir Store rootfs and initramfs logs separated Do not log the full stages in INFO level Run initramfs stage in immucore directly on boot and cd/netboot Drop systemd requirement from dracut module Signed-off-by: Itxaka itxaka.garcia@spectrocloud.com
228 lines
6.7 KiB
Go
228 lines
6.7 KiB
Go
package mount
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/containerd/containerd/mount"
|
|
"github.com/deniswernert/go-fstab"
|
|
"github.com/kairos-io/immucore/internal/constants"
|
|
internalUtils "github.com/kairos-io/immucore/internal/utils"
|
|
"github.com/kairos-io/kairos/pkg/utils"
|
|
"github.com/rs/zerolog"
|
|
"github.com/spectrocloud-labs/herd"
|
|
)
|
|
|
|
type State struct {
|
|
Rootdir string // where to mount the root partition e.g. /sysroot inside initrd with pivot, / with nopivot
|
|
TargetImage string // image from the state partition to mount as loop device e.g. /cOS/active.img
|
|
TargetDevice string // e.g. /dev/disk/by-label/COS_ACTIVE
|
|
RootMountMode string // How to mount the root partition e.g. ro or rw
|
|
|
|
// /run/cos-layout.env (different!)
|
|
OverlayDirs []string // e.g. /var
|
|
BindMounts []string // e.g. /etc/kubernetes
|
|
CustomMounts map[string]string // e.g. diskid : mountpoint
|
|
|
|
OverlayBase string // Overlay config, defaults to tmpfs:20%
|
|
OemTimout int // Time to wait for the oem to time out if not found, defaults to 5s
|
|
|
|
StateDir string // e.g. "/usr/local/.state"
|
|
fstabs []*fstab.Mount
|
|
}
|
|
|
|
func (s *State) path(p ...string) string {
|
|
return filepath.Join(append([]string{s.Rootdir}, p...)...)
|
|
}
|
|
|
|
func (s *State) WriteFstab(fstabFile string) func(context.Context) error {
|
|
return func(ctx context.Context) error {
|
|
// Create the file first, override if something is there, we don't care, we are on initramfs
|
|
f, err := os.Create(fstabFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f.Close()
|
|
for _, fst := range s.fstabs {
|
|
select {
|
|
case <-ctx.Done():
|
|
default:
|
|
f, err := os.OpenFile(fstabFile,
|
|
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := f.WriteString(fmt.Sprintf("%s\n", fst.String())); err != nil {
|
|
_ = f.Close()
|
|
return err
|
|
}
|
|
_ = f.Close()
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// RunStageOp runs elemental run-stage stage. If its rootfs its special as it needs som symlinks
|
|
// If its uki we don't symlink as we already have everything in the sysroot.
|
|
func (s *State) RunStageOp(stage string) func(context.Context) error {
|
|
return func(ctx context.Context) error {
|
|
cmd := fmt.Sprintf("elemental run-stage %s", stage)
|
|
// If we set the level to debug, also call elemental with debug
|
|
if internalUtils.Log.GetLevel() == zerolog.DebugLevel {
|
|
cmd = fmt.Sprintf("%s --debug", cmd)
|
|
}
|
|
|
|
switch stage {
|
|
case "rootfs":
|
|
if !internalUtils.IsUKI() {
|
|
if _, err := os.Stat("/system"); os.IsNotExist(err) {
|
|
err = os.Symlink("/sysroot/system", "/system")
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg("creating symlink")
|
|
}
|
|
}
|
|
if _, err := os.Stat("/oem"); os.IsNotExist(err) {
|
|
err = os.Symlink("/sysroot/oem", "/oem")
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg("creating symlink")
|
|
}
|
|
}
|
|
}
|
|
output, err := utils.SH(cmd)
|
|
internalUtils.Log.Info().Msg("Running rootfs stage")
|
|
internalUtils.Log.Info().Msg(output)
|
|
f, ferr := os.Create(filepath.Join(constants.LogDir, "rootfs_stage.log"))
|
|
if ferr == nil {
|
|
_, _ = f.WriteString(output)
|
|
_ = f.Close()
|
|
}
|
|
return err
|
|
case "initramfs":
|
|
// Not sure if it will work under UKI where the s.Rootdir is the current root already
|
|
chroot := internalUtils.NewChroot(s.Rootdir)
|
|
output, err := chroot.Run(cmd)
|
|
internalUtils.Log.Info().Msg("Running initramfs stage")
|
|
internalUtils.Log.Info().Msg(output)
|
|
f, ferr := os.Create(filepath.Join(constants.LogDir, "initramfs_stage.log"))
|
|
if ferr == nil {
|
|
_, _ = f.WriteString(output)
|
|
_ = f.Close()
|
|
}
|
|
return err
|
|
default:
|
|
return errors.New("no stage that we know off")
|
|
}
|
|
}
|
|
}
|
|
|
|
// MountOP creates and executes a mount operation.
|
|
func (s *State) MountOP(what, where, t string, options []string, timeout time.Duration) func(context.Context) error {
|
|
l := internalUtils.Log.With().Str("what", what).Str("where", where).Str("type", t).Strs("options", options).Logger().Level(zerolog.InfoLevel)
|
|
// Not sure why this defaults to debuglevel when creating a sublogger, so make sure we set it properly
|
|
debug := len(internalUtils.ReadCMDLineArg("rd.immucore.debug")) > 0
|
|
if debug {
|
|
l.Level(zerolog.DebugLevel)
|
|
}
|
|
|
|
return func(c context.Context) error {
|
|
cc := time.After(timeout)
|
|
for {
|
|
select {
|
|
default:
|
|
err := internalUtils.CreateIfNotExists(where)
|
|
if err != nil {
|
|
l.Err(err).Msg("Creating dir")
|
|
continue
|
|
}
|
|
time.Sleep(1 * time.Second)
|
|
mountPoint := mount.Mount{
|
|
Type: t,
|
|
Source: what,
|
|
Options: options,
|
|
}
|
|
tmpFstab := internalUtils.MountToFstab(mountPoint)
|
|
tmpFstab.File = internalUtils.CleanSysrootForFstab(where)
|
|
op := mountOperation{
|
|
MountOption: mountPoint,
|
|
FstabEntry: *tmpFstab,
|
|
Target: where,
|
|
PrepareCallback: func() error {
|
|
_ = internalUtils.Fsck(what)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
err = op.run()
|
|
|
|
if err == nil {
|
|
s.fstabs = append(s.fstabs, tmpFstab)
|
|
}
|
|
|
|
// only continue the loop if it's an error and not an already mounted error
|
|
if err != nil && !errors.Is(err, constants.ErrAlreadyMounted) {
|
|
l.Err(err).Send()
|
|
continue
|
|
}
|
|
l.Info().Msg("mount done")
|
|
return nil
|
|
case <-c.Done():
|
|
e := fmt.Errorf("context canceled")
|
|
l.Err(e).Msg("mount canceled")
|
|
return e
|
|
case <-cc:
|
|
e := fmt.Errorf("timeout exhausted")
|
|
l.Err(e).Msg("Mount timeout")
|
|
return e
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// WriteDAG writes the dag.
|
|
func (s *State) WriteDAG(g *herd.Graph) (out string) {
|
|
for i, layer := range g.Analyze() {
|
|
out += fmt.Sprintf("%d.\n", i+1)
|
|
for _, op := range layer {
|
|
if op.Error != nil {
|
|
out += fmt.Sprintf(" <%s> (error: %s) (background: %t) (weak: %t) (run: %t)\n", op.Name, op.Error.Error(), op.Background, op.WeakDeps, op.Executed)
|
|
} else {
|
|
out += fmt.Sprintf(" <%s> (background: %t) (weak: %t) (run: %t)\n", op.Name, op.Background, op.WeakDeps, op.Executed)
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// LogIfError will log if there is an error with the given context as message
|
|
// Context can be empty.
|
|
func (s *State) LogIfError(e error, msgContext string) {
|
|
if e != nil {
|
|
internalUtils.Log.Err(e).Msg(msgContext)
|
|
}
|
|
}
|
|
|
|
// LogIfErrorAndReturn will log if there is an error with the given context as message
|
|
// Context can be empty
|
|
// Will also return the error.
|
|
func (s *State) LogIfErrorAndReturn(e error, msgContext string) error {
|
|
if e != nil {
|
|
internalUtils.Log.Err(e).Msg(msgContext)
|
|
}
|
|
return e
|
|
}
|
|
|
|
// LogIfErrorAndPanic will log if there is an error with the given context as message
|
|
// Context can be empty
|
|
// Will also panic.
|
|
func (s *State) LogIfErrorAndPanic(e error, msgContext string) {
|
|
if e != nil {
|
|
internalUtils.Log.Err(e).Msg(msgContext)
|
|
internalUtils.Log.Fatal().Msg(e.Error())
|
|
}
|
|
}
|