Files
immucore/pkg/mount/mount.go

257 lines
5.3 KiB
Go
Raw Normal View History

2023-01-12 19:10:10 +01:00
package mount
import (
2023-02-01 18:01:58 +01:00
"context"
2023-01-12 19:10:10 +01:00
"fmt"
"os"
"path/filepath"
2023-02-01 18:01:58 +01:00
"time"
2023-01-12 19:10:10 +01:00
"github.com/containerd/containerd/mount"
"github.com/deniswernert/go-fstab"
"github.com/kairos-io/immucore/pkg/profile"
2023-02-01 18:01:58 +01:00
"github.com/kairos-io/kairos/pkg/utils"
"github.com/spectrocloud-labs/herd"
2023-01-12 19:10:10 +01:00
)
2023-02-01 18:01:58 +01:00
type State struct {
2023-02-01 19:06:51 +01:00
Rootdir string
TargetImage string // e.g. /cOS/active.img
OverlayDir []string // e.g. /var
BindMounts []string // e.g. /etc/kubernetes
2023-02-01 22:33:44 +01:00
StateDir string // e.g. "/usr/local/.state"
2023-02-01 19:06:51 +01:00
CustomMounts map[string]string // e.g. diskid : mountpoint
fstabs []*fstab.Mount
2023-02-01 18:01:58 +01:00
}
2023-02-01 19:27:01 +01:00
func genOpreferenceName(op, s string) string {
return fmt.Sprintf("%s-%s", op, s)
}
func genOpreferenceFromMap(op string, m map[string]string) (res []string) {
values := []string{}
for _, n := range m {
values = append(values, n)
}
res = genOpreference(op, values)
return
}
func genOpreference(op string, s []string) (res []string) {
for _, n := range s {
res = append(res, genOpreferenceName(op, n))
}
return
}
const (
opCustomMounts = "custom-mount"
)
2023-02-01 18:01:58 +01:00
func (s *State) Register(g *herd.Graph) error {
2023-02-01 19:27:01 +01:00
// TODO: add, hooks, fstab, systemd compat
2023-02-01 18:01:58 +01:00
g.Add("discover-mount",
herd.WithDeps("mount-cos-state"),
herd.WithCallback(
func(ctx context.Context) error {
2023-02-01 19:06:51 +01:00
_, err := utils.SH(fmt.Sprintf("losetup --show -f /run/initramfs/cos-state%s", s.TargetImage))
return err
2023-02-01 18:01:58 +01:00
},
))
g.Add("mount-cos-state",
herd.WithCallback(
s.MountOP(
"/dev/disk/by-label/COS_STATE",
s.path("/run/initramfs/cos-state"),
"auto",
[]string{
"ro", // or rw
}, 60*time.Second),
),
)
2023-02-01 22:33:44 +01:00
// overlay mount start
if rootFSType(s.Rootdir) != "overlay" {
g.Add("mount-overlay-base",
herd.WithCallback(
func(ctx context.Context) error {
op, err := baseOverlay(profile.Overlay{
Base: "/run/overlay",
BackingBase: "tmpfs:20%",
})
if err != nil {
return err
}
s.fstabs = append(s.fstabs, &op.FstabEntry)
return op.run()
},
),
)
}
overlayCondition := herd.ConditionalOption(func() bool { return rootFSType(s.Rootdir) != "overlay" }, herd.WithDeps("mount-overlay-base"))
2023-02-01 19:06:51 +01:00
// TODO: Add fsck
// mount overlay
for _, p := range s.OverlayDir {
g.Add("mount-overlays-base",
2023-02-01 22:33:44 +01:00
overlayCondition,
2023-02-01 19:06:51 +01:00
herd.WithCallback(
func(ctx context.Context) error {
op, err := mountWithBaseOverlay(p, s.Rootdir, "/run/overlay")
if err != nil {
return err
}
s.fstabs = append(s.fstabs, &op.FstabEntry)
2023-02-01 22:33:44 +01:00
return op.run()
2023-02-01 19:06:51 +01:00
},
),
)
}
// custom mounts TODO: disk/path
for id, mountpoint := range s.CustomMounts {
2023-02-01 19:27:01 +01:00
g.Add(
genOpreferenceName(opCustomMounts, mountpoint),
2023-02-01 22:33:44 +01:00
overlayCondition,
2023-02-01 19:06:51 +01:00
herd.WithCallback(
s.MountOP(
id,
s.path(mountpoint),
"auto",
[]string{
"ro", // or rw
}, 60*time.Second),
),
)
}
// mount state
2023-02-01 19:27:01 +01:00
// mount state is defined over a custom mount (/usr/local/.state for instance, needs to be mounted over a device)
2023-02-01 19:06:51 +01:00
for _, p := range s.BindMounts {
2023-02-01 19:27:01 +01:00
g.Add(
genOpreferenceName("mount-state", p),
2023-02-01 22:33:44 +01:00
overlayCondition,
2023-02-01 19:27:01 +01:00
herd.WithDeps(genOpreferenceFromMap(opCustomMounts, s.CustomMounts)...),
2023-02-01 19:06:51 +01:00
herd.WithCallback(
func(ctx context.Context) error {
2023-02-01 22:33:44 +01:00
op, err := mountBind(p, s.Rootdir, s.StateDir)
2023-02-01 19:06:51 +01:00
if err != nil {
return err
}
s.fstabs = append(s.fstabs, &op.FstabEntry)
2023-02-01 22:33:44 +01:00
return op.run()
2023-02-01 19:06:51 +01:00
},
),
)
}
2023-02-01 22:33:44 +01:00
// overlay mount end
2023-02-01 18:01:58 +01:00
g.Add("mount-sysroot",
herd.WithCallback(
s.MountOP(
"/dev/disk/by-label/COS_ACTIVE",
2023-02-01 22:33:44 +01:00
s.Rootdir,
2023-02-01 18:01:58 +01:00
"auto",
[]string{
"ro", // or rw
"suid",
"dev",
"exec",
"auto",
"nouser",
"async",
}, 60*time.Second),
),
)
g.Add("mount-oem",
herd.WithCallback(
s.MountOP(
"/dev/disk/by-label/COS_OEM",
2023-02-01 22:33:44 +01:00
s.path("/oem"),
2023-02-01 18:01:58 +01:00
"auto",
[]string{
"rw",
"suid",
"dev",
"exec",
"noauto",
"nouser",
"async",
}, 60*time.Second),
),
)
g.Add("write-fstab", herd.WithCallback(s.WriteFstab("foo")))
return nil
}
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 {
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
}
defer f.Close()
if _, err := f.WriteString(fmt.Sprintf("%s\n", fst.String())); err != nil {
return err
}
}
}
return nil
}
}
func (s *State) MountOP(what, where, t string, options []string, timeout time.Duration) func(context.Context) error {
return func(c context.Context) error {
for {
select {
default:
time.Sleep(1 * time.Second)
mountPoint := mount.Mount{
Type: t,
Source: what,
Options: options,
}
fstab := mountToStab(mountPoint)
fstab.File = where
2023-02-01 22:33:44 +01:00
op := mountOperation{
2023-02-01 18:01:58 +01:00
MountOption: mountPoint,
FstabEntry: *fstab,
Target: where,
}
2023-02-01 22:33:44 +01:00
err := op.run()
2023-02-01 18:01:58 +01:00
if err != nil {
continue
}
s.fstabs = append(s.fstabs, fstab)
return nil
case <-c.Done():
return fmt.Errorf("context canceled")
case <-time.After(timeout):
return fmt.Errorf("timeout exhausted")
}
}
}
}