package main import ( "fmt" "log" "os" "path/filepath" "strconv" "strings" "syscall" "time" yaml "gopkg.in/yaml.v2" "novit.tech/direktil/pkg/sysfs" ) var loopOffset = 0 func bootV1() { log.Print("-- 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.Print("boot partition not found, retrying") time.Sleep(1 * time.Second) continue } devFile := filepath.Join("/dev", devNames[0]) log.Print("boot partition found: ", devFile) mount(devFile, "/boot", bootFS, bootMountFlags, "") bootMounted = true break } } else { log.Print("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!") } log.Printf("wanted layers: %q", cfg.Layers) layersInMemory := paramBool("layers-in-mem", false) 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 { path := layerPath(layer) info, err := os.Stat(path) if err != nil { fatal(err) } log.Printf("layer %s found (%d bytes)", layer, info.Size()) if layersInMemory { log.Print(" copying to memory...") targetPath := filepath.Join(layersInMemDir, layer) cp(path, targetPath) path = targetPath } dir := "/layers/" + layer lowers[i] = dir loopDev := fmt.Sprintf("/dev/loop%d", i+loopOffset) losetup(loopDev, path) mount(loopDev, dir, "squashfs", layerMountFlags, "") } // 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") if bootMounted { if layersInMemory { if err := syscall.Unmount("/boot", 0); err != nil { log.Print("WARNING: failed to unmount /boot: ", err) time.Sleep(2 * time.Second) } } else { mount("/boot", "/system/boot", "", syscall.MS_BIND, "") } } // - write configuration log.Print("writing /config.yaml") if err := os.WriteFile("/system/config.yaml", cfgBytes, 0600); err != nil { fatal("failed: ", err) } // - write files for _, fileDef := range cfg.Files { log.Print("writing ", fileDef.Path) filePath := filepath.Join("/system", fileDef.Path) if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil { log.Printf("failed to create dir %s: %v", filepath.Dir(fileDef.Path), err) } mode := fileDef.Mode if mode == 0 { mode = 0644 } err = os.WriteFile(filePath, []byte(fileDef.Content), mode) if err != nil { fatalf("failed to write %s: %v", fileDef.Path, err) } } // - setup root user if passwordHash := cfg.RootUser.PasswordHash; passwordHash == "" { log.Print("deleting root password") run("chroot", "/system", "passwd", "-d", "root") } else { log.Print("setting root password") run("chroot", "/system", "sh", "-c", "chpasswd --encrypted <