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) }