2022-02-04 18:59:42 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2023-12-04 12:59:37 +00:00
|
|
|
"strconv"
|
2022-02-04 18:59:42 +00:00
|
|
|
"strings"
|
|
|
|
"syscall"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
yaml "gopkg.in/yaml.v2"
|
2022-04-04 08:29:28 +00:00
|
|
|
"novit.tech/direktil/pkg/sysfs"
|
2022-02-04 18:59:42 +00:00
|
|
|
)
|
|
|
|
|
2022-03-08 10:45:56 +00:00
|
|
|
var loopOffset = 0
|
|
|
|
|
2022-02-04 18:59:42 +00:00
|
|
|
func bootV1() {
|
2022-03-08 10:45:56 +00:00
|
|
|
log.Print("-- boot v1 --")
|
|
|
|
|
2022-02-04 18:59:42 +00:00
|
|
|
// 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")
|
|
|
|
|
2022-03-08 10:45:56 +00:00
|
|
|
layersDir = filepath.Join("/boot", bootVersion, "layers")
|
|
|
|
applyConfig(cfgPath, bootMounted)
|
|
|
|
|
|
|
|
finalizeBoot()
|
|
|
|
}
|
|
|
|
|
|
|
|
func applyConfig(cfgPath string, bootMounted bool) (cfg *configV1) {
|
2023-11-27 13:08:44 +00:00
|
|
|
cfgBytes, err := os.ReadFile(cfgPath)
|
2022-02-04 18:59:42 +00:00
|
|
|
if err != nil {
|
|
|
|
fatalf("failed to read %s: %v", cfgPath, err)
|
|
|
|
}
|
|
|
|
|
2022-03-08 10:45:56 +00:00
|
|
|
cfg = &configV1{}
|
2022-02-04 18:59:42 +00:00
|
|
|
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
|
|
|
|
|
2022-03-08 10:45:56 +00:00
|
|
|
loopDev := fmt.Sprintf("/dev/loop%d", i+loopOffset)
|
2022-02-04 18:59:42 +00:00
|
|
|
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")
|
2023-11-27 13:08:44 +00:00
|
|
|
if err := os.WriteFile("/system/config.yaml", cfgBytes, 0600); err != nil {
|
2022-02-04 18:59:42 +00:00
|
|
|
fatal("failed: ", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// - write files
|
|
|
|
for _, fileDef := range cfg.Files {
|
|
|
|
log.Print("writing ", fileDef.Path)
|
|
|
|
|
|
|
|
filePath := filepath.Join("/system", fileDef.Path)
|
|
|
|
|
2023-11-27 13:08:44 +00:00
|
|
|
if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil {
|
|
|
|
log.Printf("failed to create dir %s: %v", filepath.Dir(fileDef.Path), err)
|
|
|
|
}
|
|
|
|
|
2023-11-27 15:08:09 +00:00
|
|
|
mode := fileDef.Mode
|
|
|
|
if mode == 0 {
|
|
|
|
mode = 0644
|
|
|
|
}
|
|
|
|
|
|
|
|
err = os.WriteFile(filePath, []byte(fileDef.Content), mode)
|
2023-11-27 13:08:44 +00:00
|
|
|
if err != nil {
|
|
|
|
fatalf("failed to write %s: %v", fileDef.Path, err)
|
|
|
|
}
|
2022-02-04 18:59:42 +00:00
|
|
|
}
|
|
|
|
|
2023-12-04 12:59:37 +00:00
|
|
|
// - 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 <<EOF\nroot:"+passwordHash+"\nEOF")
|
|
|
|
}
|
|
|
|
|
|
|
|
// - groups
|
|
|
|
for _, group := range cfg.Groups {
|
|
|
|
log.Print("creating 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))
|
|
|
|
}
|
|
|
|
opts = append(opts, group.Name)
|
|
|
|
|
|
|
|
run("chroot", opts...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// - user
|
|
|
|
for _, user := range cfg.Users {
|
|
|
|
log.Print("creating 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))
|
|
|
|
}
|
|
|
|
if user.Uid != 0 {
|
|
|
|
opts = append(opts, "-u", strconv.Itoa(user.Uid))
|
|
|
|
}
|
|
|
|
opts = append(opts, user.Name)
|
|
|
|
|
|
|
|
run("chroot", opts...)
|
|
|
|
}
|
|
|
|
|
2022-03-08 10:45:56 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func finalizeBoot() {
|
2022-02-04 18:59:42 +00:00
|
|
|
// clean zombies
|
|
|
|
cleanZombies()
|
|
|
|
|
|
|
|
// switch root
|
|
|
|
log.Print("switching root")
|
2022-03-08 10:45:56 +00:00
|
|
|
err := syscall.Exec("/sbin/switch_root", []string{"switch_root",
|
2022-02-04 18:59:42 +00:00
|
|
|
"-c", "/dev/console", "/system", "/sbin/init"}, os.Environ())
|
|
|
|
fatal("switch_root failed: ", err)
|
|
|
|
}
|