mirror of
https://github.com/kairos-io/immucore.git
synced 2025-09-19 00:53:09 +00:00
8
internal/constants/constants.go
Normal file
8
internal/constants/constants.go
Normal 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"}
|
||||
}
|
@@ -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
|
||||
}
|
||||
|
@@ -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,
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user