initrd/boot-v1.go
2024-01-20 16:41:54 +01:00

197 lines
4.5 KiB
Go

package main
import (
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
"github.com/rs/zerolog/log"
yaml "gopkg.in/yaml.v2"
"novit.tech/direktil/pkg/config/apply"
"novit.tech/direktil/pkg/sysfs"
)
var loopOffset = 0
func bootV1() {
log.Info().Msg("-- boot v1 --")
// find and mount /boot
bootMatch := param("boot", "")
bootMounted := false
if bootMatch != "" {
bootFS := param("boot.fs", "vfat")
for i := 0; ; i++ {
devNames := sysfs.DeviceByProperty("block", bootMatch)
if len(devNames) == 0 {
if i > 30 {
fatal("boot partition not found after 30s")
}
log.Info().Msg("boot partition not found, retrying")
time.Sleep(1 * time.Second)
continue
}
devFile := filepath.Join("/dev", devNames[0])
log.Info().Str("dev", devFile).Msg("boot partition found")
mount(devFile, "/boot", bootFS, bootMountFlags, "")
bootMounted = true
break
}
} else {
log.Info().Msg("Assuming /boot is already populated.")
}
// load config
cfgPath := param("config", "/boot/config.yaml")
layersDir = filepath.Join("/boot", bootVersion, "layers")
applyConfig(cfgPath, bootMounted)
finalizeBoot()
}
func applyConfig(cfgPath string, bootMounted bool) (cfg *configV1) {
cfgBytes, err := os.ReadFile(cfgPath)
if err != nil {
fatalf("failed to read %s: %v", cfgPath, err)
}
cfg = &configV1{}
if err := yaml.Unmarshal(cfgBytes, cfg); err != nil {
fatal("failed to load config: ", err)
}
// mount layers
if len(cfg.Layers) == 0 {
fatal("no layers configured!")
}
layersInMemory := paramBool("layers-in-mem", false)
log.Info().Strs("layers", cfg.Layers).Bool("in-memory", layersInMemory).Msg("mounting layers")
const layersInMemDir = "/layers-in-mem"
if layersInMemory {
mkdir(layersInMemDir, 0700)
mount("layers-mem", layersInMemDir, "tmpfs", 0, "")
}
lowers := make([]string, len(cfg.Layers))
for i, layer := range cfg.Layers {
log := log.With().Str("layer", layer).Logger()
path := layerPath(layer)
info, err := os.Stat(path)
if err != nil {
fatal(err)
}
log.Info().Int64("size", info.Size()).Msg("layer found")
if layersInMemory {
log.Info().Msg("copying to memory")
targetPath := filepath.Join(layersInMemDir, layer)
cp(path, targetPath)
path = targetPath
}
dir := "/layers/" + layer
lowers[i] = dir
mountSquahfs(path, dir)
}
// prepare system root
mount("mem", "/changes", "tmpfs", 0, "")
mkdir("/changes/workdir", 0755)
mkdir("/changes/upperdir", 0755)
mount("overlay", "/system", "overlay", rootMountFlags,
"lowerdir="+strings.Join(lowers, ":")+",upperdir=/changes/upperdir,workdir=/changes/workdir")
// make root rshared (default in systemd, required by Kubernetes 1.10+)
// equivalent to "mount --make-rshared /"
// see kernel's Documentation/sharedsubtree.txt (search rshared)
if err := syscall.Mount("", "/system", "", syscall.MS_SHARED|syscall.MS_REC, ""); err != nil {
fatalf("FATAL: mount --make-rshared / failed: %v", err)
}
if bootMounted {
if layersInMemory {
if err := syscall.Unmount("/boot", 0); err != nil {
log.Warn().Err(err).Msg("failed to unmount /boot")
time.Sleep(2 * time.Second)
}
} else {
mount("/boot", "/system/boot", "", syscall.MS_BIND, "")
}
}
// - write configuration
log.Info().Msg("writing /config.yaml")
if err := os.WriteFile("/system/config.yaml", cfgBytes, 0600); err != nil {
fatal("failed: ", err)
}
// - write files
apply.Files(cfg.Files, "/system")
// - groups
for _, group := range cfg.Groups {
logEvt := log.Info().Str("group", group.Name)
opts := make([]string, 0)
opts = append(opts /* chroot */, "/system", "groupadd", "-r")
if group.Gid != 0 {
opts = append(opts, "-g", strconv.Itoa(group.Gid))
logEvt.Int("gid", group.Gid)
}
opts = append(opts, group.Name)
logEvt.Msg("creating group")
run("chroot", opts...)
}
// - user
for _, user := range cfg.Users {
logEvt := log.Info().Str("user", user.Name)
opts := make([]string, 0)
opts = append(opts /* chroot */, "/system", "useradd", "-r")
if user.Gid != 0 {
opts = append(opts, "-g", strconv.Itoa(user.Gid))
logEvt.Int("gid", user.Gid)
}
if user.Uid != 0 {
opts = append(opts, "-u", strconv.Itoa(user.Uid))
logEvt.Int("uid", user.Uid)
}
opts = append(opts, user.Name)
logEvt.Msg("creating user")
run("chroot", opts...)
}
return
}
func finalizeBoot() {
// switch root
log.Info().Msg("switching root")
err := syscall.Exec("/sbin/switch_root", []string{"switch_root",
"-c", "/dev/console", "/system", "/sbin/init"}, os.Environ())
fatal("switch_root failed: ", err)
}