Signed-off-by: Itxaka <itxaka@spectrocloud.com>
This commit is contained in:
Itxaka
2023-02-09 10:12:11 +01:00
parent ed6614b6c7
commit 0807fe08f5
5 changed files with 126 additions and 121 deletions

View File

@@ -0,0 +1,8 @@
package constants
const PersistentStateTarget = "/usr/local/.state"
func DefaultRWPaths() []string {
// Default RW_PATHS to mount if there are none defined
return []string{"/etc", "/root", "/home", "/opt", "/srv", "/usr/local", "/var"}
}

View File

@@ -1,11 +1,13 @@
package utils
import (
"github.com/joho/godotenv"
"github.com/kairos-io/kairos/sdk/state"
"os"
"strings"
)
// BootedFromCD tells us if we are currently runnig of the LiveCD
func BootedFromCD() (bool, error) {
runtime, err := state.NewRuntime()
if err != nil {
@@ -15,6 +17,7 @@ func BootedFromCD() (bool, error) {
return runtime.BootState == state.LiveCD, nil
}
// BootStateToLabel lets us know the label we need to mount sysroot on
func BootStateToLabel() string {
runtime, err := state.NewRuntime()
if err != nil {
@@ -32,6 +35,7 @@ func BootStateToLabel() string {
}
}
// IsRecovery lets us know if we are in the recovery
func IsRecovery() bool {
runtime, err := state.NewRuntime()
if err != nil {
@@ -45,6 +49,8 @@ func IsRecovery() bool {
}
}
// GetRootDir returns the proper dir to mount all the stuff
// Useful if we want to move to a no-pivot boot
func GetRootDir() string {
cmdline, _ := os.ReadFile("/proc/cmdline")
switch {
@@ -68,3 +74,33 @@ func UniqueSlice(slice []string) []string {
}
return list
}
// ReadEnv will reaed an env file (key=value) and return a nice map
func ReadEnv(file string) (map[string]string, error) {
var envMap map[string]string
var err error
f, err := os.Open(file)
if err != nil {
return envMap, err
}
defer func(f *os.File) {
_ = f.Close()
}(f)
envMap, err = godotenv.Parse(f)
if err != nil {
return envMap, err
}
return envMap, err
}
// CreateIfNotExists will check if a path exists and create it if needed
func CreateIfNotExists(path string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return os.MkdirAll(path, os.ModePerm)
}
return nil
}

View File

@@ -2,13 +2,17 @@ package utils
import (
"fmt"
"github.com/containerd/containerd/mount"
"github.com/deniswernert/go-fstab"
"github.com/kairos-io/kairos/pkg/utils"
"os"
"os/exec"
"strings"
)
// https://github.com/kairos-io/packages/blob/7c3581a8ba6371e5ce10c3a98bae54fde6a505af/packages/system/dracut/immutable-rootfs/30cos-immutable-rootfs/cos-mount-layout.sh#L58
// ParseMount will return a proper full disk path based on UUID or LABEL given
// input: LABEL=FOO:/mount
// output: /dev/disk...:/mount
func ParseMount(s string) string {
@@ -24,6 +28,7 @@ func ParseMount(s string) string {
}
}
// ReadCMDLineArg will return the pair of arg=value for a given arg if it was passed on the cmdline
func ReadCMDLineArg(arg string) []string {
cmdLine, err := os.ReadFile("/proc/cmdline")
if err != nil {
@@ -40,7 +45,51 @@ func ReadCMDLineArg(arg string) []string {
return res
}
// IsMountedByLabel lets us know if the given label is currently mounted
func IsMountedByLabel(label string) bool {
_, err := utils.SH(fmt.Sprintf("findmnt /dev/disk/by-label/%s", label))
return err == nil
}
// DiskFSType will return the FS type for a given disk
// Needs to be mounted
// Needs full path so either /dev/sda1 or /dev/disk/by-{label,uuid}/{label,uuid}
func DiskFSType(s string) string {
out, _ := utils.SH(fmt.Sprintf("findmnt -rno FSTYPE %s", s))
return out
}
// SyncState will rsync source into destination. Useful for Bind mounts.
func SyncState(src, dst string) error {
return exec.Command("rsync", "-aqAX", src, dst).Run()
}
// AppendSlash it's in the name. Appends a slash.
func AppendSlash(path string) string {
if !strings.HasSuffix(path, "/") {
return fmt.Sprintf("%s/", path)
}
return path
}
// MountToFstab transforms a mount.Mount into a fstab.Mount so we can transform existing mounts into the fstab format
func MountToFstab(m mount.Mount) *fstab.Mount {
opts := map[string]string{}
for _, o := range m.Options {
if strings.Contains(o, "=") {
dat := strings.Split(o, "=")
key := dat[0]
value := dat[1]
opts[key] = value
} else {
opts[o] = ""
}
}
return &fstab.Mount{
Spec: m.Source,
VfsType: m.Type,
MntOps: opts,
Freq: 0,
PassNo: 0,
}
}

View File

@@ -3,36 +3,14 @@ package mount
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/containerd/containerd/mount"
"github.com/deniswernert/go-fstab"
"github.com/kairos-io/kairos/pkg/utils"
internalUtils "github.com/kairos-io/immucore/internal/utils"
"github.com/moby/sys/mountinfo"
)
func rootFSType(s string) string {
out, _ := utils.SH(fmt.Sprintf("findmnt -rno FSTYPE %s", s))
return out
}
func createIfNotExists(path string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return os.MkdirAll(path, os.ModePerm)
}
return nil
}
func appendSlash(path string) string {
if !strings.HasSuffix(path, "/") {
return fmt.Sprintf("%s/", path)
}
return path
}
// https://github.com/kairos-io/packages/blob/94aa3bef3d1330cb6c6905ae164f5004b6a58b8c/packages/system/dracut/immutable-rootfs/30cos-immutable-rootfs/cos-mount-layout.sh#L129
func baseOverlay(overlay Overlay) (mountOperation, error) {
if err := os.MkdirAll(overlay.Base, 0700); err != nil {
@@ -50,7 +28,7 @@ func baseOverlay(overlay Overlay) (mountOperation, error) {
case "tmpfs":
tmpMount := mount.Mount{Type: "tmpfs", Source: "tmpfs", Options: []string{fmt.Sprintf("size=%s", dat[1])}}
err := mount.All([]mount.Mount{tmpMount}, overlay.Base)
tmpFstab := mountToStab(tmpMount)
tmpFstab := internalUtils.MountToFstab(tmpMount)
tmpFstab.File = overlay.BackingBase
return mountOperation{
MountOption: tmpMount,
@@ -61,7 +39,7 @@ func baseOverlay(overlay Overlay) (mountOperation, error) {
blockMount := mount.Mount{Type: "auto", Source: dat[1]}
err := mount.All([]mount.Mount{blockMount}, overlay.Base)
tmpFstab := mountToStab(blockMount)
tmpFstab := internalUtils.MountToFstab(blockMount)
tmpFstab.File = overlay.BackingBase
tmpFstab.MntOps["default"] = ""
@@ -75,27 +53,6 @@ func baseOverlay(overlay Overlay) (mountOperation, error) {
}
}
func mountToStab(m mount.Mount) *fstab.Mount {
opts := map[string]string{}
for _, o := range m.Options {
if strings.Contains(o, "=") {
dat := strings.Split(o, "=")
key := dat[0]
value := dat[1]
opts[key] = value
} else {
opts[o] = ""
}
}
return &fstab.Mount{
Spec: m.Source,
VfsType: m.Type,
MntOps: opts,
Freq: 0,
PassNo: 0,
}
}
// https://github.com/kairos-io/packages/blob/94aa3bef3d1330cb6c6905ae164f5004b6a58b8c/packages/system/dracut/immutable-rootfs/30cos-immutable-rootfs/cos-mount-layout.sh#L183
func mountBind(mountpoint, root, stateTarget string) mountOperation {
mountpoint = strings.TrimLeft(mountpoint, "/") // normalize, remove / upfront as we are going to re-use it in subdirs
@@ -113,7 +70,7 @@ func mountBind(mountpoint, root, stateTarget string) mountOperation {
},
}
tmpFstab := mountToStab(tmpMount)
tmpFstab := internalUtils.MountToFstab(tmpMount)
tmpFstab.File = fmt.Sprintf("/%s", mountpoint)
tmpFstab.Spec = strings.ReplaceAll(tmpFstab.Spec, root, "")
return mountOperation{
@@ -121,29 +78,26 @@ func mountBind(mountpoint, root, stateTarget string) mountOperation {
FstabEntry: *tmpFstab,
Target: rootMount,
PrepareCallback: func() error {
if err := createIfNotExists(rootMount); err != nil {
if err := internalUtils.CreateIfNotExists(rootMount); err != nil {
return err
}
if err := createIfNotExists(stateDir); err != nil {
if err := internalUtils.CreateIfNotExists(stateDir); err != nil {
return err
}
return syncState(appendSlash(rootMount), appendSlash(stateDir))
return internalUtils.SyncState(internalUtils.AppendSlash(rootMount), internalUtils.AppendSlash(stateDir))
},
}
}
func syncState(src, dst string) error {
return exec.Command("rsync", "-aqAX", src, dst).Run()
}
// https://github.com/kairos-io/packages/blob/94aa3bef3d1330cb6c6905ae164f5004b6a58b8c/packages/system/dracut/immutable-rootfs/30cos-immutable-rootfs/cos-mount-layout.sh#L145
func mountWithBaseOverlay(mountpoint, root, base string) (mountOperation, error) {
mountpoint = strings.TrimLeft(mountpoint, "/") // normalize, remove / upfront as we are going to re-use it in subdirs
rootMount := filepath.Join(root, mountpoint)
bindMountPath := strings.ReplaceAll(mountpoint, "/", "-")
createIfNotExists(rootMount)
// TODO: Should we error out if we cant create the target to mount to?
_ = internalUtils.CreateIfNotExists(rootMount)
if mounted, _ := mountinfo.Mounted(rootMount); !mounted {
upperdir := filepath.Join(base, bindMountPath, ".overlay", "upper")
workdir := filepath.Join(base, bindMountPath, ".overlay", "work")
@@ -159,7 +113,7 @@ func mountWithBaseOverlay(mountpoint, root, base string) (mountOperation, error)
},
}
tmpFstab := mountToStab(tmpMount)
tmpFstab := internalUtils.MountToFstab(tmpMount)
tmpFstab.File = rootMount
// TODO: update fstab with x-systemd info

View File

@@ -3,6 +3,7 @@ package mount
import (
"context"
"fmt"
"github.com/kairos-io/immucore/internal/constants"
"os"
"path/filepath"
"strings"
@@ -11,7 +12,6 @@ import (
"github.com/containerd/containerd/mount"
"github.com/deniswernert/go-fstab"
"github.com/hashicorp/go-multierror"
"github.com/joho/godotenv"
internalUtils "github.com/kairos-io/immucore/internal/utils"
"github.com/kairos-io/kairos/pkg/utils"
"github.com/kairos-io/kairos/sdk/state"
@@ -69,14 +69,15 @@ func (s *State) WriteFstab(fstabFile string) func(context.Context) error {
if err != nil {
return err
}
defer f.Close()
// As we mount on /sysroot during initramfs but the fstab file is for the real init process, we need to remove
// Any mentions to /sysroot from the fstab lines, otherwise they wont work
// Any mentions to /sysroot from the fstab lines, otherwise they won't work
fstCleaned := strings.ReplaceAll(fst.String(), "/sysroot", "")
toWrite := fmt.Sprintf("%s\n", fstCleaned)
if _, err := f.WriteString(toWrite); err != nil {
_ = f.Close()
return err
}
_ = f.Close()
}
}
return nil
@@ -115,12 +116,10 @@ func (s *State) MountOP(what, where, t string, options []string, timeout time.Du
for {
select {
default:
if _, err := os.Stat(where); os.IsNotExist(err) {
err = os.MkdirAll(where, os.ModeDir|os.ModePerm)
if err != nil {
log.Logger.Debug().Str("what", what).Str("where", where).Str("type", t).Strs("options", options).Err(err).Msg("Creating dir")
continue
}
err := internalUtils.CreateIfNotExists(where)
if err != nil {
log.Logger.Debug().Str("what", what).Str("where", where).Str("type", t).Strs("options", options).Err(err).Msg("Creating dir")
continue
}
time.Sleep(1 * time.Second)
mountPoint := mount.Mount{
@@ -128,7 +127,7 @@ func (s *State) MountOP(what, where, t string, options []string, timeout time.Du
Source: what,
Options: options,
}
tmpFstab := mountToStab(mountPoint)
tmpFstab := internalUtils.MountToFstab(mountPoint)
tmpFstab.File = where
op := mountOperation{
MountOption: mountPoint,
@@ -136,7 +135,7 @@ func (s *State) MountOP(what, where, t string, options []string, timeout time.Du
Target: where,
}
err := op.run()
err = op.run()
if err != nil {
continue
}
@@ -158,7 +157,7 @@ func (s *State) MountOP(what, where, t string, options []string, timeout time.Du
func (s *State) WriteDAG(g *herd.Graph) (out string) {
for i, layer := range g.Analyze() {
out += fmt.Sprintf("%d.\n", (i + 1))
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)\n", op.Name, op.Error.Error(), op.Background, op.WeakDeps)
@@ -170,32 +169,9 @@ func (s *State) WriteDAG(g *herd.Graph) (out string) {
return
}
func readEnv(file string) (map[string]string, error) {
var envMap map[string]string
var err error
f, err := os.Open(file)
if err != nil {
return envMap, err
}
defer func(f *os.File) {
_ = f.Close()
}(f)
envMap, err = godotenv.Parse(f)
if err != nil {
return envMap, err
}
return envMap, err
}
func (s *State) Register(g *herd.Graph) error {
var err error
// Default RW_PATHS to mount ALWAYS
s.OverlayDirs = []string{"/etc", "/root", "/home", "/opt", "/srv", "/usr/local", "/var"}
runtime, err := state.NewRuntime()
if err != nil {
s.Logger.Debug().Err(err).Msg("")
@@ -299,32 +275,29 @@ func (s *State) Register(g *herd.Graph) error {
s.CustomMounts = map[string]string{}
}
env, err := readEnv("/run/cos/cos-layout.env")
env, err := internalUtils.ReadEnv("/run/cos/cos-layout.env")
if err != nil {
log.Logger.Err(err).Msg("Reading env")
return err
}
// populate from env here
s.OverlayDirs = append(s.OverlayDirs, strings.Split(env["RW_PATHS"], " ")...)
s.OverlayDirs = strings.Split(env["RW_PATHS"], " ")
// If empty, then set defaults
if len(s.OverlayDirs) == 0 {
s.OverlayDirs = constants.DefaultRWPaths()
}
// Remove any duplicates
s.OverlayDirs = internalUtils.UniqueSlice(s.OverlayDirs)
// TODO: PERSISTENT_STATE_TARGET /usr/local/.state
s.BindMounts = strings.Split(env["PERSISTENT_STATE_PATHS"], " ")
// Remove any duplicates
s.BindMounts = internalUtils.UniqueSlice(s.BindMounts)
s.StateDir = env["PERSISTENT_STATE_TARGET"]
if s.StateDir == "" {
s.StateDir = "/usr/local/.state"
s.StateDir = constants.PersistentStateTarget
}
// s.CustomMounts is special:
// It gets parsed by the cmdline (TODO)
// and from the env var
// https://github.com/kairos-io/packages/blob/7c3581a8ba6371e5ce10c3a98bae54fde6a505af/packages/system/dracut/immutable-rootfs/30cos-immutable-rootfs/cos-generator.sh#L71
// https://github.com/kairos-io/packages/blob/7c3581a8ba6371e5ce10c3a98bae54fde6a505af/packages/system/dracut/immutable-rootfs/30cos-immutable-rootfs/cos-mount-layout.sh#L80
addLine := func(d string) {
dat := strings.Split(d, ":")
if len(dat) == 2 {
@@ -333,6 +306,8 @@ func (s *State) Register(g *herd.Graph) error {
s.CustomMounts[disk] = path
}
}
// Parse custom mounts also from cmdline (rd.cos.mount=)
// Parse custom mounts also from env file (VOLUMES)
for _, v := range append(internalUtils.ReadCMDLineArg("rd.cos.mount="), strings.Split(env["VOLUMES"], " ")...) {
addLine(internalUtils.ParseMount(v))
}
@@ -345,7 +320,7 @@ func (s *State) Register(g *herd.Graph) error {
// end sysroot mount
// overlay mount start
if rootFSType(s.Rootdir) != "overlay" {
if internalUtils.DiskFSType(s.Rootdir) != "overlay" {
err = g.Add(opMountBaseOverlay,
herd.WithCallback(
func(ctx context.Context) error {
@@ -366,7 +341,7 @@ func (s *State) Register(g *herd.Graph) error {
}
}
overlayCondition := herd.ConditionalOption(func() bool { return rootFSType(s.Rootdir) != "overlay" }, herd.WithDeps(opMountBaseOverlay))
overlayCondition := herd.ConditionalOption(func() bool { return internalUtils.DiskFSType(s.Rootdir) != "overlay" }, herd.WithDeps(opMountBaseOverlay))
// TODO: Add fsck
// mount overlay
err = g.Add(
@@ -439,24 +414,7 @@ func (s *State) Register(g *herd.Graph) error {
func(ctx context.Context) error {
var err *multierror.Error
for _, p := range s.BindMounts {
// TODO: Check why p can be empty, Example:
/*
3:12PM DBG Mounting bind binds=[
"/etc/systemd","/etc/modprobe.d",
"/etc/rancher","/etc/sysconfig",
"/etc/runlevels","/etc/ssh",
"/etc/ssl/certs","/etc/iscsi",
"", <----- HERE
"/etc/cni","/etc/kubernetes",
"/home","/opt","/root","/snap",
"/var/snap","/usr/libexec",
"/var/log","/var/lib/rancher",
"/var/lib/kubelet","/var/lib/snapd"
,"/var/lib/wicked","/var/lib/longhorn"
,"/var/lib/cni","/usr/share/pki/trust"
,"/usr/share/pki/trust/anchors",
"/var/lib/ca-certificates"]
*/
// Ignore empty values that can get there by having extra spaces in the cos-layout file
if p == "" {
continue
}