Initial commit
This commit is contained in:
		
							
								
								
									
										31
									
								
								cmd/dkl-apply-config/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								cmd/dkl-apply-config/main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"novit.nc/direktil/inits/pkg/apply"
 | 
				
			||||||
 | 
						"novit.nc/direktil/pkg/config"
 | 
				
			||||||
 | 
						dlog "novit.nc/direktil/pkg/log"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						log = dlog.Get("dkl-apply-config")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						configPath := flag.String("config", "config.yaml", "config to load")
 | 
				
			||||||
 | 
						doFiles := flag.Bool("files", false, "apply files")
 | 
				
			||||||
 | 
						flag.Parse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.SetConsole(os.Stderr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cfg, err := config.Load(*configPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Print("failed to load config: ", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if *doFiles {
 | 
				
			||||||
 | 
							apply.Files(cfg, log)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										190
									
								
								cmd/dkl-initrd-init/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								cmd/dkl-initrd-init/main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,190 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/exec"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						yaml "gopkg.in/yaml.v2"
 | 
				
			||||||
 | 
						"novit.nc/direktil/pkg/sysfs"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// VERSION is the current version of init
 | 
				
			||||||
 | 
						VERSION = "Direktil init v0.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rootMountFlags  = 0
 | 
				
			||||||
 | 
						bootMountFlags  = syscall.MS_NOEXEC | syscall.MS_NODEV | syscall.MS_NOSUID | syscall.MS_RDONLY
 | 
				
			||||||
 | 
						layerMountFlags = syscall.MS_RDONLY
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						bootVersion string
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type config struct {
 | 
				
			||||||
 | 
						Layers []string `yaml:"layers"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						log.Print("Welcome to ", VERSION)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// essential mounts
 | 
				
			||||||
 | 
						mount("none", "/proc", "proc", 0, "")
 | 
				
			||||||
 | 
						mount("none", "/sys", "sysfs", 0, "")
 | 
				
			||||||
 | 
						mount("none", "/dev", "devtmpfs", 0, "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// get the "boot version"
 | 
				
			||||||
 | 
						bootVersion = param("version", "current")
 | 
				
			||||||
 | 
						log.Printf("booting system %q", bootVersion)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// find and mount /boot
 | 
				
			||||||
 | 
						bootMatch := param("boot", "")
 | 
				
			||||||
 | 
						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, "")
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							log.Print("Assuming /boot is already populated.")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// load config
 | 
				
			||||||
 | 
						cfgPath := param("config", "/boot/config.yaml")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cfgBytes, err := ioutil.ReadFile(cfgPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fatalf("failed to read %s: %v", cfgPath, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cfg := &config{}
 | 
				
			||||||
 | 
						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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							dir := "/layers/" + layer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							lowers[i] = dir
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							loopDev := fmt.Sprintf("/dev/loop%d", i)
 | 
				
			||||||
 | 
							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")
 | 
				
			||||||
 | 
						mount("/boot", "/system/boot", "", syscall.MS_BIND, "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// - write configuration
 | 
				
			||||||
 | 
						log.Print("writing /config.yaml")
 | 
				
			||||||
 | 
						if err := ioutil.WriteFile("/system/config.yaml", cfgBytes, 0600); err != nil {
 | 
				
			||||||
 | 
							fatal("failed: ", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// clean zombies
 | 
				
			||||||
 | 
						cleanZombies()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// switch root
 | 
				
			||||||
 | 
						log.Print("switching root")
 | 
				
			||||||
 | 
						err = syscall.Exec("/sbin/switch_root", []string{"switch_root", "/system", "/sbin/init"}, os.Environ())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Print("switch_root failed: ", err)
 | 
				
			||||||
 | 
							select {}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func layerPath(name string) string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("/boot/%s/layers/%s.fs", bootVersion, name)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fatal(v ...interface{}) {
 | 
				
			||||||
 | 
						log.Print("*** FATAL ***")
 | 
				
			||||||
 | 
						log.Print(v...)
 | 
				
			||||||
 | 
						dropToShell()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fatalf(pattern string, v ...interface{}) {
 | 
				
			||||||
 | 
						log.Print("*** FATAL ***")
 | 
				
			||||||
 | 
						log.Printf(pattern, v...)
 | 
				
			||||||
 | 
						dropToShell()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func dropToShell() {
 | 
				
			||||||
 | 
						err := syscall.Exec("/bin/sh", []string{"/bin/sh"}, os.Environ())
 | 
				
			||||||
 | 
						log.Print("shell drop failed: ", err)
 | 
				
			||||||
 | 
						select {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func losetup(dev, file string) {
 | 
				
			||||||
 | 
						run("/sbin/losetup", dev, file)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func run(cmd string, args ...string) {
 | 
				
			||||||
 | 
						if output, err := exec.Command(cmd, args...).CombinedOutput(); err != nil {
 | 
				
			||||||
 | 
							fatalf("command %s %q failed: %v\n%s", cmd, args, err, string(output))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func mkdir(dir string, mode os.FileMode) {
 | 
				
			||||||
 | 
						if err := os.MkdirAll(dir, mode); err != nil {
 | 
				
			||||||
 | 
							fatalf("mkdir %q failed: %v", dir, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func mount(source, target, fstype string, flags uintptr, data string) {
 | 
				
			||||||
 | 
						if _, err := os.Stat(target); os.IsNotExist(err) {
 | 
				
			||||||
 | 
							mkdir(target, 0755)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := syscall.Mount(source, target, fstype, flags, data); err != nil {
 | 
				
			||||||
 | 
							fatalf("mount %q %q -t %q -o %q failed: %v", source, target, fstype, data, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						log.Printf("mounted %q", target)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								cmd/dkl-initrd-init/params.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								cmd/dkl-initrd-init/params.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func param(name, defaultValue string) (value string) {
 | 
				
			||||||
 | 
						ba, err := ioutil.ReadFile("/proc/cmdline")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fatal("could not read /proc/cmdline: ", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						prefix := "direktil." + name + "="
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, part := range strings.Split(string(ba), " ") {
 | 
				
			||||||
 | 
							if strings.HasPrefix(part, prefix) {
 | 
				
			||||||
 | 
								return strings.TrimSpace(part[len(prefix):])
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return defaultValue
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										24
									
								
								cmd/dkl-initrd-init/zombies.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								cmd/dkl-initrd-init/zombies.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func cleanZombies() {
 | 
				
			||||||
 | 
						var wstatus syscall.WaitStatus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							pid, err := syscall.Wait4(-1, &wstatus, 0, nil)
 | 
				
			||||||
 | 
							switch err {
 | 
				
			||||||
 | 
							case nil:
 | 
				
			||||||
 | 
								log.Printf("collected PID %v", pid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case syscall.ECHILD:
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								log.Printf("unknown error: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										33
									
								
								cmd/dkl-system-init/bootstrap.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								cmd/dkl-system-init/bootstrap.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func bootstrap() {
 | 
				
			||||||
 | 
						mount("proc", "/proc", "proc", 0, "")
 | 
				
			||||||
 | 
						mount("sys", "/sys", "sysfs", 0, "")
 | 
				
			||||||
 | 
						mount("dev", "/dev", "devtmpfs", syscall.MS_NOSUID, "mode=0755,size=10M")
 | 
				
			||||||
 | 
						mount("run", "/run", "tmpfs", 0, "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mount("/run", "/var/run", "", syscall.MS_BIND, "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mkdir("/run/lock", 0775)
 | 
				
			||||||
 | 
						log.Print("/run/lock: correcting owner")
 | 
				
			||||||
 | 
						if err := os.Chown("/run/lock", 0, 14); err != nil {
 | 
				
			||||||
 | 
							fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func mount(source, target, fstype string, flags uintptr, data string) {
 | 
				
			||||||
 | 
						if _, err := os.Stat(target); os.IsNotExist(err) {
 | 
				
			||||||
 | 
							mkdir(target, 0755)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := syscall.Mount(source, target, fstype, flags, data); err != nil {
 | 
				
			||||||
 | 
							fatalf("mount %q %q -t %q -o %q failed: %v", source, target, fstype, data, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						log.Printf("mounted %q", target)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										267
									
								
								cmd/dkl-system-init/configure.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										267
									
								
								cmd/dkl-system-init/configure.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,267 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"net"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/exec"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/sparrc/go-ping"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"novit.nc/direktil/inits/pkg/apply"
 | 
				
			||||||
 | 
						"novit.nc/direktil/inits/pkg/vars"
 | 
				
			||||||
 | 
						"novit.nc/direktil/pkg/config"
 | 
				
			||||||
 | 
						"novit.nc/direktil/pkg/log"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						services.Register(configure{})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type configure struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (_ configure) GetName() string {
 | 
				
			||||||
 | 
						return "configure"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (_ configure) CanStart() bool {
 | 
				
			||||||
 | 
						return services.HasFlag("service:lvm", "service:udev trigger")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (_ configure) Run(_ func()) error {
 | 
				
			||||||
 | 
						// 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("", "/", "", syscall.MS_SHARED|syscall.MS_REC, ""); err != nil {
 | 
				
			||||||
 | 
							fatalf("mount --make-rshared / failed: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// - setup root user
 | 
				
			||||||
 | 
						if passwordHash := cfg.RootUser.PasswordHash; passwordHash == "" {
 | 
				
			||||||
 | 
							run("/usr/bin/passwd", "-d", "root")
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							run("/bin/sh", "-c", "chpasswd --encrypted <<EOF\nroot:"+passwordHash+"\nEOF")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// - groups
 | 
				
			||||||
 | 
						for _, group := range cfg.Groups {
 | 
				
			||||||
 | 
							opts := make([]string, 0)
 | 
				
			||||||
 | 
							opts = append(opts, "-r")
 | 
				
			||||||
 | 
							if group.Gid != 0 {
 | 
				
			||||||
 | 
								opts = append(opts, "-g", strconv.Itoa(group.Gid))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							opts = append(opts, group.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							run("groupadd", opts...)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// - user
 | 
				
			||||||
 | 
						for _, user := range cfg.Users {
 | 
				
			||||||
 | 
							opts := make([]string, 0)
 | 
				
			||||||
 | 
							opts = append(opts, "-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("useradd", opts...)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// - files
 | 
				
			||||||
 | 
						if err := apply.Files(cfg, initLog); err != nil {
 | 
				
			||||||
 | 
							fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						services.SetFlag("files-written")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// - hostname
 | 
				
			||||||
 | 
						initLog.Taint(log.Info, "setting hostname")
 | 
				
			||||||
 | 
						run("hostname", "-F", "/etc/hostname")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// - modules
 | 
				
			||||||
 | 
						for _, module := range cfg.Modules {
 | 
				
			||||||
 | 
							initLog.Taint(log.Info, "loading module ", module)
 | 
				
			||||||
 | 
							run("modprobe", module)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// - networks
 | 
				
			||||||
 | 
						for idx, network := range cfg.Networks {
 | 
				
			||||||
 | 
							setupNetwork(idx, network)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						services.SetFlag("network-up")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// - setup storage
 | 
				
			||||||
 | 
						initLog.Print("checking storage")
 | 
				
			||||||
 | 
						if err := exec.Command("vgdisplay", "storage").Run(); err != nil {
 | 
				
			||||||
 | 
							initLog.Print("creating VG storage")
 | 
				
			||||||
 | 
							setupVG(vars.BootArgValue("storage", cfg.Storage.UdevMatch))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, name := range cfg.Storage.RemoveVolumes {
 | 
				
			||||||
 | 
							dev := "/dev/storage/" + name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if _, err := os.Stat(dev); os.IsNotExist(err) {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							} else if err != nil {
 | 
				
			||||||
 | 
								fatal("failed to stat ", dev, ": ", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							initLog.Print("removing LV ", name)
 | 
				
			||||||
 | 
							cmd := exec.Command("lvremove", "-f", "storage/"+name)
 | 
				
			||||||
 | 
							cmd.Stderr = os.Stderr
 | 
				
			||||||
 | 
							if err := cmd.Run(); err != nil {
 | 
				
			||||||
 | 
								fatal("failed to remove LV ", name)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, volume := range cfg.Storage.Volumes {
 | 
				
			||||||
 | 
							if err := exec.Command("lvdisplay", "storage/"+volume.Name).Run(); err != nil {
 | 
				
			||||||
 | 
								initLog.Print("creating LV ", volume.Name)
 | 
				
			||||||
 | 
								setupLV(volume)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							dev := "/dev/storage/" + volume.Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							initLog.Printf("checking filesystem on %s", dev)
 | 
				
			||||||
 | 
							run("fsck", "-p", dev)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							mount(dev, volume.Mount.Path, volume.FS,
 | 
				
			||||||
 | 
								syscall.MS_NOATIME|syscall.MS_RELATIME,
 | 
				
			||||||
 | 
								volume.Mount.Options)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// finished configuring :-)
 | 
				
			||||||
 | 
						log.EnableFiles()
 | 
				
			||||||
 | 
						services.SetFlag("configured")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// load user services
 | 
				
			||||||
 | 
						go loadUserServices()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (_ configure) Stop() {
 | 
				
			||||||
 | 
						// no-op
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var networkStarted = map[string]bool{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func setupNetwork(idx int, network config.NetworkDef) {
 | 
				
			||||||
 | 
						tries := 0
 | 
				
			||||||
 | 
					retry:
 | 
				
			||||||
 | 
						ifaces, err := net.Interfaces()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fatalf("failed to get network interfaces: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						match := false
 | 
				
			||||||
 | 
						for _, iface := range ifaces {
 | 
				
			||||||
 | 
							if networkStarted[iface.Name] {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if network.Match.Name != "" {
 | 
				
			||||||
 | 
								if ok, err := filepath.Match(network.Match.Name, iface.Name); err != nil {
 | 
				
			||||||
 | 
									fatalf("network[%d] name match error: %v", idx, err)
 | 
				
			||||||
 | 
								} else if !ok {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if network.Match.Ping != nil {
 | 
				
			||||||
 | 
								initLog.Printf("network[%d] ping check on %s", idx, iface.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if ok, err := networkPingCheck(iface.Name, network); err != nil {
 | 
				
			||||||
 | 
									initLog.Taintf(log.Error, "network[%d] ping check failed: %v",
 | 
				
			||||||
 | 
										idx, err)
 | 
				
			||||||
 | 
								} else if !ok {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							initLog.Printf("network[%d] matches interface %s", idx, iface.Name)
 | 
				
			||||||
 | 
							match = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							startNetwork(iface.Name, idx, network)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if !network.Match.All {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !match {
 | 
				
			||||||
 | 
							initLog.Taintf(log.Warning, "network[%d] did not match any interface", idx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							tries++
 | 
				
			||||||
 | 
							if network.Optional && tries > 3 {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							time.Sleep(1 * time.Second)
 | 
				
			||||||
 | 
							initLog.Taintf(log.Warning, "network[%d] retrying (try: %d)", idx, tries)
 | 
				
			||||||
 | 
							goto retry
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func startNetwork(ifaceName string, idx int, network config.NetworkDef) {
 | 
				
			||||||
 | 
						initLog.Taintf(log.Info, "starting network[%d]", idx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						script := vars.Substitute([]byte(network.Script), cfg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c := exec.Command("/bin/sh")
 | 
				
			||||||
 | 
						c.Stdin = bytes.NewBuffer(script)
 | 
				
			||||||
 | 
						c.Stdout = os.Stdout
 | 
				
			||||||
 | 
						c.Stderr = os.Stderr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO doc
 | 
				
			||||||
 | 
						c.Env = append(append(make([]string, 0), os.Environ()...), "IFNAME="+ifaceName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := c.Run(); err != nil {
 | 
				
			||||||
 | 
							links, _ := exec.Command("ip", "link", "ls").CombinedOutput()
 | 
				
			||||||
 | 
							fatalf("network setup failed (link list below): %v\n%s", err, string(links))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						networkStarted[ifaceName] = true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func networkPingCheck(ifName string, network config.NetworkDef) (bool, error) {
 | 
				
			||||||
 | 
						check := network.Match.Ping
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						source := string(vars.Substitute([]byte(check.Source), cfg))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						run("ip", "addr", "add", source, "dev", ifName)
 | 
				
			||||||
 | 
						run("ip", "link", "set", ifName, "up")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							run("ip", "link", "set", ifName, "down")
 | 
				
			||||||
 | 
							run("ip", "addr", "del", source, "dev", ifName)
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pinger, err := ping.NewPinger(network.Match.Ping.Target)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pinger.Count = 3
 | 
				
			||||||
 | 
						if check.Count > 0 {
 | 
				
			||||||
 | 
							pinger.Count = check.Count
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pinger.Timeout = 1 * time.Second
 | 
				
			||||||
 | 
						if check.Timeout > 0 {
 | 
				
			||||||
 | 
							pinger.Timeout = time.Duration(check.Timeout) * time.Second
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pinger.SetPrivileged(true)
 | 
				
			||||||
 | 
						pinger.Run()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return pinger.Statistics().PacketsRecv > 0, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										8
									
								
								cmd/dkl-system-init/dumb-init-bridge.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								cmd/dkl-system-init/dumb-init-bridge.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// void handleSignals();
 | 
				
			||||||
 | 
					import "C"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func handleChildren() {
 | 
				
			||||||
 | 
						C.handleSignals()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										139
									
								
								cmd/dkl-system-init/dumb-init.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								cmd/dkl-system-init/dumb-init.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,139 @@
 | 
				
			|||||||
 | 
					// extracted from https://raw.githubusercontent.com/Yelp/dumb-init/master/dumb-init.c
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <assert.h>
 | 
				
			||||||
 | 
					#include <errno.h>
 | 
				
			||||||
 | 
					#include <getopt.h>
 | 
				
			||||||
 | 
					#include <signal.h>
 | 
				
			||||||
 | 
					#include <stdio.h>
 | 
				
			||||||
 | 
					#include <stdlib.h>
 | 
				
			||||||
 | 
					#include <string.h>
 | 
				
			||||||
 | 
					#include <sys/ioctl.h>
 | 
				
			||||||
 | 
					#include <sys/types.h>
 | 
				
			||||||
 | 
					#include <sys/wait.h>
 | 
				
			||||||
 | 
					#include <unistd.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define PRINTERR(...) do { \
 | 
				
			||||||
 | 
					    fprintf(stderr, "[dumb-init] " __VA_ARGS__); \
 | 
				
			||||||
 | 
					} while (0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define DEBUG(...) do { \
 | 
				
			||||||
 | 
					    if (debug) { \
 | 
				
			||||||
 | 
					        PRINTERR(__VA_ARGS__); \
 | 
				
			||||||
 | 
					    } \
 | 
				
			||||||
 | 
					} while (0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Signals we care about are numbered from 1 to 31, inclusive.
 | 
				
			||||||
 | 
					// (32 and above are real-time signals.)
 | 
				
			||||||
 | 
					// TODO: this is likely not portable outside of Linux, or on strange architectures
 | 
				
			||||||
 | 
					#define MAXSIG 31
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Indices are one-indexed (signal 1 is at index 1). Index zero is unused.
 | 
				
			||||||
 | 
					int signal_rewrite[MAXSIG + 1] = {[0 ... MAXSIG] = -1};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pid_t child_pid = -1;
 | 
				
			||||||
 | 
					char debug = 0;
 | 
				
			||||||
 | 
					char use_setsid = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int translate_signal(int signum) {
 | 
				
			||||||
 | 
					    if (signum <= 0 || signum > MAXSIG) {
 | 
				
			||||||
 | 
					        return signum;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        int translated = signal_rewrite[signum];
 | 
				
			||||||
 | 
					        if (translated == -1) {
 | 
				
			||||||
 | 
					            return signum;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            DEBUG("Translating signal %d to %d.\n", signum, translated);
 | 
				
			||||||
 | 
					            return translated;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void forward_signal(int signum) {
 | 
				
			||||||
 | 
					    signum = translate_signal(signum);
 | 
				
			||||||
 | 
					    if (signum != 0) {
 | 
				
			||||||
 | 
					        kill(use_setsid ? -child_pid : child_pid, signum);
 | 
				
			||||||
 | 
					        DEBUG("Forwarded signal %d to children.\n", signum);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        DEBUG("Not forwarding signal %d to children (ignored).\n", signum);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * The dumb-init signal handler.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The main job of this signal handler is to forward signals along to our child
 | 
				
			||||||
 | 
					 * process(es). In setsid mode, this means signaling the entire process group
 | 
				
			||||||
 | 
					 * rooted at our child. In non-setsid mode, this is just signaling the primary
 | 
				
			||||||
 | 
					 * child.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * In most cases, simply proxying the received signal is sufficient. If we
 | 
				
			||||||
 | 
					 * receive a job control signal, however, we should not only forward it, but
 | 
				
			||||||
 | 
					 * also sleep dumb-init itself.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This allows users to run foreground processes using dumb-init and to
 | 
				
			||||||
 | 
					 * control them using normal shell job control features (e.g. Ctrl-Z to
 | 
				
			||||||
 | 
					 * generate a SIGTSTP and suspend the process).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The libc manual is useful:
 | 
				
			||||||
 | 
					 * https://www.gnu.org/software/libc/manual/html_node/Job-Control-Signals.html
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					void handle_signal(int signum) {
 | 
				
			||||||
 | 
					    DEBUG("Received signal %d.\n", signum);
 | 
				
			||||||
 | 
					    if (signum == SIGCHLD) {
 | 
				
			||||||
 | 
					        int status, exit_status;
 | 
				
			||||||
 | 
					        pid_t killed_pid;
 | 
				
			||||||
 | 
					        while ((killed_pid = waitpid(-1, &status, WNOHANG)) > 0) {
 | 
				
			||||||
 | 
					            if (WIFEXITED(status)) {
 | 
				
			||||||
 | 
					                exit_status = WEXITSTATUS(status);
 | 
				
			||||||
 | 
					                DEBUG("A child with PID %d exited with exit status %d.\n", killed_pid, exit_status);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                assert(WIFSIGNALED(status));
 | 
				
			||||||
 | 
					                exit_status = 128 + WTERMSIG(status);
 | 
				
			||||||
 | 
					                DEBUG("A child with PID %d was terminated by signal %d.\n", killed_pid, exit_status - 128);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (killed_pid == child_pid) {
 | 
				
			||||||
 | 
					                forward_signal(SIGTERM);  // send SIGTERM to any remaining children
 | 
				
			||||||
 | 
					                DEBUG("Child exited with status %d. Goodbye.\n", exit_status);
 | 
				
			||||||
 | 
					                exit(exit_status);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        forward_signal(signum);
 | 
				
			||||||
 | 
					        if (signum == SIGTSTP || signum == SIGTTOU || signum == SIGTTIN) {
 | 
				
			||||||
 | 
					            DEBUG("Suspending self due to TTY signal.\n");
 | 
				
			||||||
 | 
					            kill(getpid(), SIGSTOP);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void set_rewrite_to_sigstop_if_not_defined(int signum) {
 | 
				
			||||||
 | 
					    if (signal_rewrite[signum] == -1)
 | 
				
			||||||
 | 
					        signal_rewrite[signum] = SIGSTOP;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// A dummy signal handler used for signals we care about.
 | 
				
			||||||
 | 
					// On the FreeBSD kernel, ignored signals cannot be waited on by `sigwait` (but
 | 
				
			||||||
 | 
					// they can be on Linux). We must provide a dummy handler.
 | 
				
			||||||
 | 
					// https://lists.freebsd.org/pipermail/freebsd-ports/2009-October/057340.html
 | 
				
			||||||
 | 
					void dummy(int signum) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ------------------------------------------------------------------------
 | 
				
			||||||
 | 
					// Go entry point
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					void handleSignals() {
 | 
				
			||||||
 | 
					   sigset_t all_signals;
 | 
				
			||||||
 | 
					   sigfillset(&all_signals);
 | 
				
			||||||
 | 
					   sigprocmask(SIG_BLOCK, &all_signals, NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   int i = 0;
 | 
				
			||||||
 | 
					   for (i = 1; i <= MAXSIG; i++)
 | 
				
			||||||
 | 
					       signal(i, dummy);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   for (;;) {
 | 
				
			||||||
 | 
					       int signum;
 | 
				
			||||||
 | 
					       sigwait(&all_signals, &signum);
 | 
				
			||||||
 | 
					       handle_signal(signum);
 | 
				
			||||||
 | 
					   }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										36
									
								
								cmd/dkl-system-init/errors.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								cmd/dkl-system-init/errors.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"novit.nc/direktil/pkg/color"
 | 
				
			||||||
 | 
						"novit.nc/direktil/pkg/log"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						endOfInitMessage = `
 | 
				
			||||||
 | 
					.---- END OF INIT -----.
 | 
				
			||||||
 | 
					| init process failed. |
 | 
				
			||||||
 | 
					 ----------------------
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fatal(v ...interface{}) {
 | 
				
			||||||
 | 
						initLog.Taint(log.Fatal, v...)
 | 
				
			||||||
 | 
						os.Stderr.Write([]byte(color.Red + endOfInitMessage + color.Reset))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						services.SetFlag("boot-failed")
 | 
				
			||||||
 | 
						endOfProcess()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fatalf(pattern string, v ...interface{}) {
 | 
				
			||||||
 | 
						initLog.Taintf(log.Fatal, pattern, v...)
 | 
				
			||||||
 | 
						os.Stderr.Write([]byte(color.Red + endOfInitMessage + color.Reset))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						services.SetFlag("boot-failed")
 | 
				
			||||||
 | 
						endOfProcess()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func endOfProcess() {
 | 
				
			||||||
 | 
						select {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										49
									
								
								cmd/dkl-system-init/initctl.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								cmd/dkl-system-init/initctl.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func listenInitctl() {
 | 
				
			||||||
 | 
						const f = "/run/initctl"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := syscall.Mkfifo(f, 0700); err != nil {
 | 
				
			||||||
 | 
							fatal("can't create "+f+": ", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							func() {
 | 
				
			||||||
 | 
								fifo, err := os.Open(f)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									fatal("can't open "+f+": ", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								defer fifo.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								r := bufio.NewReader(fifo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for {
 | 
				
			||||||
 | 
									s, err := r.ReadString('\n')
 | 
				
			||||||
 | 
									if err == io.EOF {
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										initLog.Print(f+": read error: ", err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									switch s {
 | 
				
			||||||
 | 
									case "prepare-shutdown\n":
 | 
				
			||||||
 | 
										prepareShutdown()
 | 
				
			||||||
 | 
									case "poweroff\n", "shutdown\n":
 | 
				
			||||||
 | 
										poweroff()
 | 
				
			||||||
 | 
									case "reboot\n":
 | 
				
			||||||
 | 
										reboot()
 | 
				
			||||||
 | 
									default:
 | 
				
			||||||
 | 
										initLog.Printf(f+": unknown command: %q", s)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										157
									
								
								cmd/dkl-system-init/lvm.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								cmd/dkl-system-init/lvm.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,157 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/exec"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"novit.nc/direktil/pkg/config"
 | 
				
			||||||
 | 
						"novit.nc/direktil/pkg/log"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						pDevName = "DEVNAME="
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						go services.WaitPath("/run/lvm/lvmetad.socket")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						services.Register(
 | 
				
			||||||
 | 
							&CommandService{
 | 
				
			||||||
 | 
								Name:    "lvmetad",
 | 
				
			||||||
 | 
								Restart: StdRestart,
 | 
				
			||||||
 | 
								Needs:   []string{"service:devfs"},
 | 
				
			||||||
 | 
								Command: []string{"lvmetad", "-f"},
 | 
				
			||||||
 | 
								PreExec: func() error {
 | 
				
			||||||
 | 
									mkdir("/run/lvm", 0700)
 | 
				
			||||||
 | 
									mkdir("/run/lock/lvm", 0700)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if !dmInProc() {
 | 
				
			||||||
 | 
										run("modprobe", "dm-mod")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							&CommandService{
 | 
				
			||||||
 | 
								Name:  "lvm",
 | 
				
			||||||
 | 
								Needs: []string{"file:/run/lvm/lvmetad.socket"},
 | 
				
			||||||
 | 
								Command: []string{"/bin/sh", "-c", `set -ex
 | 
				
			||||||
 | 
					/sbin/lvm pvscan
 | 
				
			||||||
 | 
					/sbin/lvm vgscan --mknodes
 | 
				
			||||||
 | 
					/sbin/lvm vgchange --sysinit -a ly
 | 
				
			||||||
 | 
					`},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func isDir(path string) bool {
 | 
				
			||||||
 | 
						s, err := os.Stat(path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if os.IsNotExist(err) {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							fatal("failed to query ", path, ": ", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return s.IsDir()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func dmInProc() bool {
 | 
				
			||||||
 | 
						for _, f := range []string{"devices", "misc"} {
 | 
				
			||||||
 | 
							c, err := ioutil.ReadFile("/proc/" + f)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								fatalf("failed to read %s: %v", f, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !bytes.Contains(c, []byte("device-mapper")) {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func setupVG(udevMatch string) {
 | 
				
			||||||
 | 
						dev := ""
 | 
				
			||||||
 | 
						try := 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					retry:
 | 
				
			||||||
 | 
						paths, err := filepath.Glob("/sys/class/block/*")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fatal("failed to list block devices: ", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, path := range paths {
 | 
				
			||||||
 | 
							// ignore loop devices
 | 
				
			||||||
 | 
							if strings.HasPrefix("loop", filepath.Base(path)) {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// fetch udev informations
 | 
				
			||||||
 | 
							out, err := exec.Command("udevadm", "info", "-q", "property", path).CombinedOutput()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								initLog.Taintf(log.Warning, "udev query of %q failed: %v\n%s", path, err, string(out))
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							propertyLines := strings.Split(strings.TrimSpace(string(out)), "\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							devPath := ""
 | 
				
			||||||
 | 
							matches := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for _, line := range propertyLines {
 | 
				
			||||||
 | 
								if strings.HasPrefix(line, pDevName) {
 | 
				
			||||||
 | 
									devPath = line[len(pDevName):]
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if line == udevMatch {
 | 
				
			||||||
 | 
									matches = true
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if devPath != "" && matches {
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if devPath != "" && matches {
 | 
				
			||||||
 | 
								dev = devPath
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if dev == "" {
 | 
				
			||||||
 | 
							time.Sleep(1 * time.Second)
 | 
				
			||||||
 | 
							try++
 | 
				
			||||||
 | 
							if try > 30 {
 | 
				
			||||||
 | 
								fatal("storage device not found after 30s, failing.")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							goto retry
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						initLog.Taint(log.Info, "found storage device at ", dev)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						run("pvcreate", dev)
 | 
				
			||||||
 | 
						run("vgcreate", "storage", dev)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func setupLV(volume config.VolumeDef) {
 | 
				
			||||||
 | 
						if volume.Extents != "" {
 | 
				
			||||||
 | 
							run("lvcreate", "-l", volume.Extents, "-n", volume.Name, "storage")
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							run("lvcreate", "-L", volume.Size, "-n", volume.Name, "storage")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						args := make([]string, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch volume.FS {
 | 
				
			||||||
 | 
						case "btrfs":
 | 
				
			||||||
 | 
							args = append(args, "-f")
 | 
				
			||||||
 | 
						case "ext4":
 | 
				
			||||||
 | 
							args = append(args, "-F")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						run("mkfs."+volume.FS, append(args, "/dev/storage/"+volume.Name)...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										225
									
								
								cmd/dkl-system-init/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								cmd/dkl-system-init/main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,225 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/exec"
 | 
				
			||||||
 | 
						"os/signal"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"novit.nc/direktil/pkg/color"
 | 
				
			||||||
 | 
						"novit.nc/direktil/pkg/config"
 | 
				
			||||||
 | 
						"novit.nc/direktil/pkg/log"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cfgPath = "/config.yaml"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						bootVarPrefix = []byte("direktil.var.")
 | 
				
			||||||
 | 
						cfg           *config.Config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						initLog = log.Get("init")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						switch filepath.Base(os.Args[0]) {
 | 
				
			||||||
 | 
						case "poweroff":
 | 
				
			||||||
 | 
							initCommand("poweroff\n")
 | 
				
			||||||
 | 
						case "reboot":
 | 
				
			||||||
 | 
							initCommand("reboot\n")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(os.Args) > 1 {
 | 
				
			||||||
 | 
							switch os.Args[1] {
 | 
				
			||||||
 | 
							case "0":
 | 
				
			||||||
 | 
								initCommand("poweroff\n")
 | 
				
			||||||
 | 
							case "6":
 | 
				
			||||||
 | 
								initCommand("reboot\n")
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								fmt.Fprintf(os.Stderr, "unknown args: %v\n", os.Args)
 | 
				
			||||||
 | 
								os.Exit(1)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if os.Getpid() != 1 {
 | 
				
			||||||
 | 
							fmt.Println("not PID 1")
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						color.Write(os.Stderr, color.Cyan, "Direktil system starting\n")
 | 
				
			||||||
 | 
						initLog.SetConsole(os.Stderr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go handleChildren()
 | 
				
			||||||
 | 
						go handleSignals()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// handle abnormal ends
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							if err := recover(); err != nil {
 | 
				
			||||||
 | 
								fatal("FATAL: panic in main: ", err)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								fatal("FATAL: exited from main")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// set a reasonable path
 | 
				
			||||||
 | 
						os.Setenv("PATH", strings.Join([]string{
 | 
				
			||||||
 | 
							"/usr/local/bin:/usr/local/sbin",
 | 
				
			||||||
 | 
							"/usr/bin:/usr/sbin",
 | 
				
			||||||
 | 
							"/bin:/sbin",
 | 
				
			||||||
 | 
						}, ":"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// load the configuration
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							c, err := config.Load(cfgPath)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								fatal("failed to load config: ", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := os.Remove(cfgPath); err != nil {
 | 
				
			||||||
 | 
								initLog.Taint(log.Warning, "failed to remove config: ", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							cfg = c
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// bootstrap the basic things
 | 
				
			||||||
 | 
						bootstrap()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go listenInitctl()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// start the services
 | 
				
			||||||
 | 
						services.Start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Wait for configuration, but timeout to always give a login
 | 
				
			||||||
 | 
						ch := make(chan int, 1)
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							services.Wait(func() bool {
 | 
				
			||||||
 | 
								return services.HasFlag("configured") ||
 | 
				
			||||||
 | 
									services.HasFlag("boot-failed")
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							close(ch)
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-time.After(1 * time.Minute):
 | 
				
			||||||
 | 
							initLog.Taint(log.Warning, "configuration took too long, allowing login anyway.")
 | 
				
			||||||
 | 
						case <-ch:
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Handle CAD command (ctrl+alt+del)
 | 
				
			||||||
 | 
						intCh := make(chan os.Signal, 1)
 | 
				
			||||||
 | 
						signal.Notify(intCh, syscall.SIGINT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						syscall.Reboot(syscall.LINUX_REBOOT_CMD_CAD_ON)
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							<-intCh
 | 
				
			||||||
 | 
							initLog.Taint(log.Warning, "received ctrl+alt+del, rebooting...")
 | 
				
			||||||
 | 
							reboot()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Allow login now
 | 
				
			||||||
 | 
						go allowLogin()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Wait all services
 | 
				
			||||||
 | 
						services.WaitAll()
 | 
				
			||||||
 | 
						initLog.Taint(log.OK, "all services are started")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						endOfProcess()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func initCommand(c string) {
 | 
				
			||||||
 | 
						err := ioutil.WriteFile("/run/initctl", []byte(c), 0600)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						os.Exit(0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func mkdir(dir string, mode os.FileMode) {
 | 
				
			||||||
 | 
						if err := os.MkdirAll(dir, mode); err != nil {
 | 
				
			||||||
 | 
							fatalf("mkdir %q failed: %v", dir, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func run(cmd string, args ...string) {
 | 
				
			||||||
 | 
						c := exec.Command(cmd, args...)
 | 
				
			||||||
 | 
						c.Stdout = os.Stdout
 | 
				
			||||||
 | 
						c.Stderr = os.Stderr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := c.Run(); err != nil {
 | 
				
			||||||
 | 
							fatalf("command %s %q failed: %v", cmd, args, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func touch(path string) {
 | 
				
			||||||
 | 
						run("touch", path)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func poweroff() {
 | 
				
			||||||
 | 
						prepareShutdown()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						initLog.Print("final sync")
 | 
				
			||||||
 | 
						syscall.Sync()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						syscall.Reboot(syscall.LINUX_REBOOT_CMD_POWER_OFF)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func reboot() {
 | 
				
			||||||
 | 
						prepareShutdown()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						initLog.Print("final sync")
 | 
				
			||||||
 | 
						syscall.Sync()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func prepareShutdown() {
 | 
				
			||||||
 | 
						services.Stop()
 | 
				
			||||||
 | 
						initLog.Taint(log.Info, "services stopped")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.DisableFiles()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for try := 0; try < 5; try++ {
 | 
				
			||||||
 | 
							initLog.Taint(log.Info, "unmounting filesystems")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// FIXME: filesystem list should be build from non "nodev" lines in /proc/filesystems
 | 
				
			||||||
 | 
							c := exec.Command("umount", "-a", "-t", "ext2,ext3,ext4,vfat,msdos,xfs,btrfs")
 | 
				
			||||||
 | 
							c.Stdout = initLog
 | 
				
			||||||
 | 
							c.Stderr = initLog
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := c.Run(); err != nil {
 | 
				
			||||||
 | 
								initLog.Taint(log.Warning, "umounting failed: ", err)
 | 
				
			||||||
 | 
								time.Sleep(time.Duration(2*try) * time.Second)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							break
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						initLog.Taint(log.Info, "sync'ing")
 | 
				
			||||||
 | 
						exec.Command("sync").Run()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func allowLogin() {
 | 
				
			||||||
 | 
						b := make([]byte, 1)
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							os.Stdout.Write([]byte("\n" + color.Yellow + "[press enter to login]" + color.Reset + "\n\n"))
 | 
				
			||||||
 | 
							for {
 | 
				
			||||||
 | 
								os.Stdin.Read(b)
 | 
				
			||||||
 | 
								if b[0] == '\n' {
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							c := exec.Command("/sbin/agetty", "--noclear", "--noissue", "console", "linux")
 | 
				
			||||||
 | 
							c.Stdin = os.Stdin
 | 
				
			||||||
 | 
							c.Stdout = os.Stdout
 | 
				
			||||||
 | 
							c.Stderr = os.Stderr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							c.Run()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										28
									
								
								cmd/dkl-system-init/runlevel-default.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								cmd/dkl-system-init/runlevel-default.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						services.Register(
 | 
				
			||||||
 | 
							&CommandService{
 | 
				
			||||||
 | 
								Name:    "sysctl",
 | 
				
			||||||
 | 
								Needs:   []string{"configured"},
 | 
				
			||||||
 | 
								Command: []string{"/usr/sbin/sysctl", "--system"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							&CommandService{
 | 
				
			||||||
 | 
								Name:    "ssh-keygen",
 | 
				
			||||||
 | 
								Needs:   []string{"files-written"},
 | 
				
			||||||
 | 
								Command: []string{"/usr/bin/ssh-keygen", "-A"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							&CommandService{
 | 
				
			||||||
 | 
								Name:    "sshd",
 | 
				
			||||||
 | 
								Restart: StdRestart,
 | 
				
			||||||
 | 
								Needs:   []string{"service:ssh-keygen"},
 | 
				
			||||||
 | 
								Command: []string{"/usr/sbin/sshd", "-D"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							&CommandService{
 | 
				
			||||||
 | 
								Name:    "chrony",
 | 
				
			||||||
 | 
								Restart: StdRestart,
 | 
				
			||||||
 | 
								Needs:   []string{"configured"},
 | 
				
			||||||
 | 
								Command: []string{"chronyd", "-d"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										111
									
								
								cmd/dkl-system-init/runlevel-sysinit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								cmd/dkl-system-init/runlevel-sysinit.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,111 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						msSysfs = syscall.MS_NODEV | syscall.MS_NOEXEC | syscall.MS_NOSUID
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						services.Register(
 | 
				
			||||||
 | 
							&CommandService{
 | 
				
			||||||
 | 
								Name: "kmod-static-nodes",
 | 
				
			||||||
 | 
								PreExec: func() error {
 | 
				
			||||||
 | 
									mkdir("/run/tmpfiles.d", 0755)
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Command: []string{"kmod", "static-nodes", "--format=tmpfiles",
 | 
				
			||||||
 | 
									"--output=/run/tmpfiles.d/kmod.conf"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							&Oneshot{
 | 
				
			||||||
 | 
								Name: "devfs",
 | 
				
			||||||
 | 
								Func: func() error {
 | 
				
			||||||
 | 
									for _, mount := range []struct {
 | 
				
			||||||
 | 
										fstype string
 | 
				
			||||||
 | 
										target string
 | 
				
			||||||
 | 
										mode   os.FileMode
 | 
				
			||||||
 | 
										flags  uintptr
 | 
				
			||||||
 | 
										data   string
 | 
				
			||||||
 | 
										source string
 | 
				
			||||||
 | 
									}{
 | 
				
			||||||
 | 
										{"mqueue", "/dev/mqueue", 01777, syscall.MS_NODEV, "", "mqueue"},
 | 
				
			||||||
 | 
										{"devpts", "/dev/pts", 0755, 0, "gid=5", "devpts"},
 | 
				
			||||||
 | 
										{"tmpfs", "/dev/shm", 01777, syscall.MS_NODEV, "mode=1777", "shm"},
 | 
				
			||||||
 | 
										{"tmpfs", "/sys/fs/cgroup", 0755, 0, "mode=755,size=10m", "cgroup"},
 | 
				
			||||||
 | 
									} {
 | 
				
			||||||
 | 
										initLog.Print("mounting ", mount.target)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										flags := syscall.MS_NOEXEC | syscall.MS_NOSUID | mount.flags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										mkdir(mount.target, mount.mode)
 | 
				
			||||||
 | 
										err := syscall.Mount(mount.source, mount.target, mount.fstype, flags, mount.data)
 | 
				
			||||||
 | 
										if err != nil {
 | 
				
			||||||
 | 
											fatalf("mount failed: %v", err)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// mount cgroup controllers
 | 
				
			||||||
 | 
									for line := range readLines("/proc/cgroups") {
 | 
				
			||||||
 | 
										parts := strings.Split(line, "\t")
 | 
				
			||||||
 | 
										name, enabled := parts[0], parts[3]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if enabled != "1" {
 | 
				
			||||||
 | 
											continue
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										initLog.Print("mounting cgroup fs for controller ", name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										mp := "/sys/fs/cgroup/" + name
 | 
				
			||||||
 | 
										mkdir(mp, 0755)
 | 
				
			||||||
 | 
										mount(name, mp, "cgroup", msSysfs, name)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if err := ioutil.WriteFile("/sys/fs/cgroup/memory/memory.use_hierarchy",
 | 
				
			||||||
 | 
										[]byte{'1'}, 0644); err != nil {
 | 
				
			||||||
 | 
										initLog.Print("failed to enable use_hierarchy in memory cgroup: ", err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							&CommandService{
 | 
				
			||||||
 | 
								Name:    "dmesg",
 | 
				
			||||||
 | 
								Command: []string{"dmesg", "-n", "warn"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func readLines(path string) chan string {
 | 
				
			||||||
 | 
						f, err := os.Open(path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fatalf("failed to open %s: %v", path, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bf := bufio.NewReader(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ch := make(chan string, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							defer f.Close()
 | 
				
			||||||
 | 
							defer close(ch)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for {
 | 
				
			||||||
 | 
								line, err := bf.ReadString('\n')
 | 
				
			||||||
 | 
								if err == io.EOF {
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								} else if err != nil {
 | 
				
			||||||
 | 
									fatalf("error while reading %s: %v", path, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								ch <- line[:len(line)-1]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ch
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										185
									
								
								cmd/dkl-system-init/service-registry.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								cmd/dkl-system-init/service-registry.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,185 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"novit.nc/direktil/pkg/log"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						services = ServiceRegistry{
 | 
				
			||||||
 | 
							rw:       &sync.RWMutex{},
 | 
				
			||||||
 | 
							cond:     sync.NewCond(&sync.Mutex{}),
 | 
				
			||||||
 | 
							services: make(map[string]Service),
 | 
				
			||||||
 | 
							statuses: make(map[string]ServiceStatus),
 | 
				
			||||||
 | 
							flags:    make(map[string]bool),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ServiceStatus int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						Pending ServiceStatus = iota
 | 
				
			||||||
 | 
						Starting
 | 
				
			||||||
 | 
						Running
 | 
				
			||||||
 | 
						Failed
 | 
				
			||||||
 | 
						Exited
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ServiceRegistry struct {
 | 
				
			||||||
 | 
						rw       *sync.RWMutex
 | 
				
			||||||
 | 
						cond     *sync.Cond
 | 
				
			||||||
 | 
						services map[string]Service
 | 
				
			||||||
 | 
						statuses map[string]ServiceStatus
 | 
				
			||||||
 | 
						flags    map[string]bool
 | 
				
			||||||
 | 
						started  bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (sr *ServiceRegistry) Register(services ...Service) {
 | 
				
			||||||
 | 
						for _, service := range services {
 | 
				
			||||||
 | 
							name := service.GetName()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if _, ok := sr.services[name]; ok {
 | 
				
			||||||
 | 
								fatalf("duplicated service name: %s", name)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							sr.services[name] = service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if sr.started {
 | 
				
			||||||
 | 
								go sr.startService(name, service)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (sr *ServiceRegistry) Start() {
 | 
				
			||||||
 | 
						initLog.Taintf(log.Info, "starting service registry")
 | 
				
			||||||
 | 
						sr.started = true
 | 
				
			||||||
 | 
						for name, service := range sr.services {
 | 
				
			||||||
 | 
							go sr.startService(name, service)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (sr *ServiceRegistry) startService(name string, service Service) {
 | 
				
			||||||
 | 
						sr.Set(name, Pending)
 | 
				
			||||||
 | 
						sr.Wait(service.CanStart)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sr.Set(name, Starting)
 | 
				
			||||||
 | 
						initLog.Taintf(log.Info, "starting service %s", name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := service.Run(func() {
 | 
				
			||||||
 | 
							sr.Set(name, Running)
 | 
				
			||||||
 | 
							sr.SetFlag("service:" + name)
 | 
				
			||||||
 | 
						}); err == nil {
 | 
				
			||||||
 | 
							initLog.Taintf(log.OK, "service %s finished.", name)
 | 
				
			||||||
 | 
							sr.Set(name, Exited)
 | 
				
			||||||
 | 
							sr.SetFlag("service:" + name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							initLog.Taintf(log.Error, "service %s failed: %v", name, err)
 | 
				
			||||||
 | 
							sr.Set(name, Failed)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (sr *ServiceRegistry) Stop() {
 | 
				
			||||||
 | 
						wg := sync.WaitGroup{}
 | 
				
			||||||
 | 
						wg.Add(len(sr.services))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for name, service := range sr.services {
 | 
				
			||||||
 | 
							name, service := name, service
 | 
				
			||||||
 | 
							go func() {
 | 
				
			||||||
 | 
								initLog.Taintf(log.Info, "stopping %s", name)
 | 
				
			||||||
 | 
								service.Stop()
 | 
				
			||||||
 | 
								wg.Done()
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wg.Wait()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (sr *ServiceRegistry) Wait(check func() bool) {
 | 
				
			||||||
 | 
						sr.cond.L.Lock()
 | 
				
			||||||
 | 
						defer sr.cond.L.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							if check() {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							sr.cond.Wait()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (sr *ServiceRegistry) WaitAll() {
 | 
				
			||||||
 | 
						flags := make([]string, 0, len(sr.services))
 | 
				
			||||||
 | 
						for name, _ := range sr.services {
 | 
				
			||||||
 | 
							flags = append(flags, "service:"+name)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sr.Wait(sr.HasFlagCond(flags...))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (sr *ServiceRegistry) Set(serviceName string, status ServiceStatus) {
 | 
				
			||||||
 | 
						sr.cond.L.Lock()
 | 
				
			||||||
 | 
						sr.rw.Lock()
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							sr.rw.Unlock()
 | 
				
			||||||
 | 
							sr.cond.L.Unlock()
 | 
				
			||||||
 | 
							sr.cond.Broadcast()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sr.statuses[serviceName] = status
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (sr *ServiceRegistry) HasStatus(status ServiceStatus, serviceNames ...string) bool {
 | 
				
			||||||
 | 
						sr.rw.RLock()
 | 
				
			||||||
 | 
						defer sr.rw.RUnlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, name := range serviceNames {
 | 
				
			||||||
 | 
							if sr.statuses[name] != status {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (sr *ServiceRegistry) SetFlag(flag string) {
 | 
				
			||||||
 | 
						sr.cond.L.Lock()
 | 
				
			||||||
 | 
						sr.rw.Lock()
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							sr.rw.Unlock()
 | 
				
			||||||
 | 
							sr.cond.L.Unlock()
 | 
				
			||||||
 | 
							sr.cond.Broadcast()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sr.flags[flag] = true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (sr *ServiceRegistry) HasFlag(flags ...string) bool {
 | 
				
			||||||
 | 
						sr.rw.RLock()
 | 
				
			||||||
 | 
						defer sr.rw.RUnlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, flag := range flags {
 | 
				
			||||||
 | 
							if !sr.flags[flag] {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (sr *ServiceRegistry) HasFlagCond(flags ...string) func() bool {
 | 
				
			||||||
 | 
						return func() bool {
 | 
				
			||||||
 | 
							return sr.HasFlag(flags...)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (sr *ServiceRegistry) WaitPath(path string) {
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							if _, err := os.Stat(path); err == nil {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							time.Sleep(100 * time.Millisecond)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sr.SetFlag("file:" + path)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										30
									
								
								cmd/dkl-system-init/service-rules.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								cmd/dkl-system-init/service-rules.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ServiceRules struct {
 | 
				
			||||||
 | 
						flags []string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewServiceRules() *ServiceRules {
 | 
				
			||||||
 | 
						return &ServiceRules{make([]string, 0)}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *ServiceRules) Flags(flags ...string) *ServiceRules {
 | 
				
			||||||
 | 
						r.flags = append(r.flags, flags...)
 | 
				
			||||||
 | 
						return r
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *ServiceRules) Services(serviceNames ...string) *ServiceRules {
 | 
				
			||||||
 | 
						flags := make([]string, len(serviceNames))
 | 
				
			||||||
 | 
						for i, name := range serviceNames {
 | 
				
			||||||
 | 
							flags[i] = "service:" + name
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return r.Flags(flags...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r ServiceRules) Check() bool {
 | 
				
			||||||
 | 
						if !services.HasFlag(r.flags...) {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										151
									
								
								cmd/dkl-system-init/service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								cmd/dkl-system-init/service.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,151 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"os/exec"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"novit.nc/direktil/pkg/log"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						// StdRestart is a wait duration between restarts of a service, if you have no inspiration.
 | 
				
			||||||
 | 
						StdRestart = 1 * time.Second
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						killDelay = 30 * time.Second
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Service represents a service to run as part of the init process.
 | 
				
			||||||
 | 
					type Service interface {
 | 
				
			||||||
 | 
						GetName() string
 | 
				
			||||||
 | 
						CanStart() bool
 | 
				
			||||||
 | 
						Run(notify func()) error
 | 
				
			||||||
 | 
						Stop()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type CommandService struct {
 | 
				
			||||||
 | 
						Name     string
 | 
				
			||||||
 | 
						Command  []string
 | 
				
			||||||
 | 
						Restart  time.Duration
 | 
				
			||||||
 | 
						Needs    []string
 | 
				
			||||||
 | 
						Provides []string
 | 
				
			||||||
 | 
						PreExec  func() error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log     *log.Log
 | 
				
			||||||
 | 
						stop    bool
 | 
				
			||||||
 | 
						command *exec.Cmd
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ Service = &CommandService{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *CommandService) GetName() string {
 | 
				
			||||||
 | 
						return s.Name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CanStart is part of the Service interface
 | 
				
			||||||
 | 
					func (s *CommandService) CanStart() bool {
 | 
				
			||||||
 | 
						return services.HasFlag(s.Needs...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *CommandService) Stop() {
 | 
				
			||||||
 | 
						stopped := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s.stop = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c := s.command
 | 
				
			||||||
 | 
						if c == nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.Process.Signal(syscall.SIGTERM)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							time.Sleep(killDelay)
 | 
				
			||||||
 | 
							if !stopped {
 | 
				
			||||||
 | 
								c.Process.Signal(syscall.SIGKILL)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.Wait()
 | 
				
			||||||
 | 
						stopped = true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *CommandService) Run(notify func()) error {
 | 
				
			||||||
 | 
						s.stop = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if s.log == nil {
 | 
				
			||||||
 | 
							s.log = log.Get(s.Name)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						isOneshot := s.Restart == time.Duration(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						myNotify := func() {
 | 
				
			||||||
 | 
							for _, provide := range s.Provides {
 | 
				
			||||||
 | 
								services.SetFlag(provide)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							notify()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if s.PreExec != nil {
 | 
				
			||||||
 | 
							if err := s.PreExec(); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Starting
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					retry:
 | 
				
			||||||
 | 
						if isOneshot {
 | 
				
			||||||
 | 
							// oneshot services are only active after exit
 | 
				
			||||||
 | 
							err = s.exec(func() {})
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							err = s.exec(myNotify)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if s.stop {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if isOneshot {
 | 
				
			||||||
 | 
							myNotify()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// auto-restart service
 | 
				
			||||||
 | 
							services.Set(s.Name, Failed)
 | 
				
			||||||
 | 
							time.Sleep(s.Restart)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							s.log.Taintf(log.Warning, "-- restarting --")
 | 
				
			||||||
 | 
							services.Set(s.Name, Starting)
 | 
				
			||||||
 | 
							goto retry
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *CommandService) exec(notify func()) error {
 | 
				
			||||||
 | 
						c := exec.Command(s.Command[0], s.Command[1:]...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s.command = c
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							s.command = nil
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.Stdout = s.log
 | 
				
			||||||
 | 
						c.Stderr = s.log
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := c.Start(); err != nil {
 | 
				
			||||||
 | 
							s.log.Taintf(log.Error, "failed to start: %v", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						notify()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := c.Wait(); err != nil {
 | 
				
			||||||
 | 
							s.log.Taintf(log.Error, "failed: %v", err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								cmd/dkl-system-init/services-to-migrate.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								cmd/dkl-system-init/services-to-migrate.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						legacyService := func(name string, needs ...string) *Service {
 | 
				
			||||||
 | 
							return &Service{
 | 
				
			||||||
 | 
								Name:    name,
 | 
				
			||||||
 | 
								PreCond: NewServiceRules().Services(needs...).Check,
 | 
				
			||||||
 | 
								Run: func(notify func()) bool {
 | 
				
			||||||
 | 
									return Exec(func() {}, "/etc/init.d/"+name, "start", "--nodeps")
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    services.Register([]*Service{
 | 
				
			||||||
 | 
					        legacyService("modules-load"),
 | 
				
			||||||
 | 
					        legacyService("modules", "modules-load"),
 | 
				
			||||||
 | 
					        legacyService("dmesg", "udev", "modules"),
 | 
				
			||||||
 | 
					    }...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					// */
 | 
				
			||||||
							
								
								
									
										19
									
								
								cmd/dkl-system-init/signal.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								cmd/dkl-system-init/signal.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/signal"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func handleSignals() {
 | 
				
			||||||
 | 
						c := make(chan os.Signal, 1)
 | 
				
			||||||
 | 
						signal.Notify(c, syscall.SIGPWR)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for sig := range c {
 | 
				
			||||||
 | 
							switch sig {
 | 
				
			||||||
 | 
							case syscall.SIGPWR:
 | 
				
			||||||
 | 
								poweroff()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								cmd/dkl-system-init/simple-service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								cmd/dkl-system-init/simple-service.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Oneshot struct {
 | 
				
			||||||
 | 
						Name  string
 | 
				
			||||||
 | 
						Needs []string
 | 
				
			||||||
 | 
						Func  func() error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s Oneshot) GetName() string {
 | 
				
			||||||
 | 
						return s.Name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s Oneshot) CanStart() bool {
 | 
				
			||||||
 | 
						return services.HasFlag(s.Needs...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s Oneshot) Run(_ func()) error {
 | 
				
			||||||
 | 
						return s.Func()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s Oneshot) Stop() {
 | 
				
			||||||
 | 
						// no-op
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										36
									
								
								cmd/dkl-system-init/udev.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								cmd/dkl-system-init/udev.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						services.Register(
 | 
				
			||||||
 | 
							&CommandService{
 | 
				
			||||||
 | 
								Name:    "udev",
 | 
				
			||||||
 | 
								Restart: StdRestart,
 | 
				
			||||||
 | 
								Needs:   []string{"service:devfs", "service:dmesg", "service:lvm"},
 | 
				
			||||||
 | 
								PreExec: func() error {
 | 
				
			||||||
 | 
									if _, err := os.Stat("/proc/net/unix"); os.IsNotExist(err) {
 | 
				
			||||||
 | 
										run("modprobe", "unix")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if _, err := os.Stat("/proc/sys/kernel/hotplug"); err == nil {
 | 
				
			||||||
 | 
										ioutil.WriteFile("/proc/sys/kernel/hotplug", []byte{}, 0644)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Command: []string{"/lib/systemd/systemd-udevd"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							&CommandService{
 | 
				
			||||||
 | 
								Name:    "udev trigger",
 | 
				
			||||||
 | 
								Needs:   []string{"service:udev"},
 | 
				
			||||||
 | 
								Command: []string{"udevadm", "trigger"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							&CommandService{
 | 
				
			||||||
 | 
								Name:    "udev settle",
 | 
				
			||||||
 | 
								Needs:   []string{"service:udev trigger"},
 | 
				
			||||||
 | 
								Command: []string{"udevadm", "settle"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										105
									
								
								cmd/dkl-system-init/user-service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								cmd/dkl-system-init/user-service.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,105 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						yaml "gopkg.in/yaml.v2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"novit.nc/direktil/pkg/log"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						reYamlStart = regexp.MustCompile("^#\\s+---\\s*$")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type UserService struct {
 | 
				
			||||||
 | 
						Restart  int
 | 
				
			||||||
 | 
						Needs    []string
 | 
				
			||||||
 | 
						Provides []string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func loadUserServices() {
 | 
				
			||||||
 | 
					retry:
 | 
				
			||||||
 | 
						files, err := filepath.Glob("/etc/direktil/services/*")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							initLog.Taint(log.Error, "failed to load user services: ", err)
 | 
				
			||||||
 | 
							time.Sleep(10 * time.Second)
 | 
				
			||||||
 | 
							goto retry
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, path := range files {
 | 
				
			||||||
 | 
							path := path
 | 
				
			||||||
 | 
							go func() {
 | 
				
			||||||
 | 
								for {
 | 
				
			||||||
 | 
									if err := loadUserService(path); err != nil {
 | 
				
			||||||
 | 
										initLog.Taintf(log.Error, "failed to load %s: %v", path, err)
 | 
				
			||||||
 | 
										time.Sleep(10 * time.Second)
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func loadUserService(path string) error {
 | 
				
			||||||
 | 
						f, err := os.Open(path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer f.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r := bufio.NewReader(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						yamlBuf := &bytes.Buffer{}
 | 
				
			||||||
 | 
						inYaml := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							line, err := r.ReadString('\n')
 | 
				
			||||||
 | 
							if err == io.EOF {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							} else if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if inYaml {
 | 
				
			||||||
 | 
								if !strings.HasPrefix(line, "# ") {
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								yamlBuf.WriteString(line[2:])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							} else if reYamlStart.MatchString(line) {
 | 
				
			||||||
 | 
								inYaml = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						spec := &UserService{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if inYaml {
 | 
				
			||||||
 | 
							if err := yaml.Unmarshal(yamlBuf.Bytes(), &spec); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						svc := &CommandService{
 | 
				
			||||||
 | 
							Name:     filepath.Base(path),
 | 
				
			||||||
 | 
							Restart:  time.Duration(spec.Restart) * time.Second,
 | 
				
			||||||
 | 
							Needs:    spec.Needs,
 | 
				
			||||||
 | 
							Provides: spec.Provides,
 | 
				
			||||||
 | 
							Command:  []string{path},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						initLog.Taintf(log.OK, "user service: %s", path)
 | 
				
			||||||
 | 
						services.Register(svc)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										67
									
								
								pkg/apply/files.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								pkg/apply/files.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					package apply
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"novit.nc/direktil/inits/pkg/vars"
 | 
				
			||||||
 | 
						"novit.nc/direktil/pkg/config"
 | 
				
			||||||
 | 
						dlog "novit.nc/direktil/pkg/log"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Files writes the files from the given config
 | 
				
			||||||
 | 
					func Files(cfg *config.Config, log *dlog.Log) (err error) {
 | 
				
			||||||
 | 
						err = writeFile(
 | 
				
			||||||
 | 
							"/root/.ssh/authorized_keys",
 | 
				
			||||||
 | 
							[]byte(strings.Join(cfg.RootUser.AuthorizedKeys, "\n")),
 | 
				
			||||||
 | 
							0600, 0700, cfg, log,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, file := range cfg.Files {
 | 
				
			||||||
 | 
							mode := file.Mode
 | 
				
			||||||
 | 
							if mode == 0 {
 | 
				
			||||||
 | 
								mode = 0644
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							content := []byte(file.Content)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = writeFile(
 | 
				
			||||||
 | 
								file.Path,
 | 
				
			||||||
 | 
								content,
 | 
				
			||||||
 | 
								mode,
 | 
				
			||||||
 | 
								0755,
 | 
				
			||||||
 | 
								cfg,
 | 
				
			||||||
 | 
								log,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func writeFile(path string, content []byte, fileMode, dirMode os.FileMode,
 | 
				
			||||||
 | 
						cfg *config.Config, log *dlog.Log) (err error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = os.MkdirAll(filepath.Dir(path), dirMode); err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						content = vars.Substitute(content, cfg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Printf("writing %q, mode %04o, %d bytes", path, fileMode, len(content))
 | 
				
			||||||
 | 
						if err = ioutil.WriteFile(path, content, fileMode); err != nil {
 | 
				
			||||||
 | 
							err = fmt.Errorf("failed to write %s: %v", path, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										32
									
								
								pkg/vars/boot.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								pkg/vars/boot.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					package vars
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						bootVarPrefix = []byte("direktil.var.")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func BootArgs() [][]byte {
 | 
				
			||||||
 | 
						ba, err := ioutil.ReadFile("/proc/cmdline")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							// should not happen
 | 
				
			||||||
 | 
							panic(fmt.Errorf("failed to read /proc/cmdline: ", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return bytes.Split(ba, []byte{' '})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func BootArgValue(prefix, defaultValue string) string {
 | 
				
			||||||
 | 
						prefixB := []byte("direktil." + prefix + "=")
 | 
				
			||||||
 | 
						for _, ba := range BootArgs() {
 | 
				
			||||||
 | 
							if bytes.HasPrefix(ba, prefixB) {
 | 
				
			||||||
 | 
								return string(ba[len(prefixB):])
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return defaultValue
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										60
									
								
								pkg/vars/vars.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								pkg/vars/vars.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					package vars
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"novit.nc/direktil/pkg/config"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Var struct {
 | 
				
			||||||
 | 
						Template []byte
 | 
				
			||||||
 | 
						Value    []byte
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Vars(cfg *config.Config) []Var {
 | 
				
			||||||
 | 
						res := make([]Var, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, arg := range BootArgs() {
 | 
				
			||||||
 | 
							if !bytes.HasPrefix(arg, bootVarPrefix) {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							parts := bytes.SplitN(arg[len(bootVarPrefix):], []byte{'='}, 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							res = append(res, Var{
 | 
				
			||||||
 | 
								Template: append(append([]byte("$(var:"), parts[0]...), ')'),
 | 
				
			||||||
 | 
								Value:    parts[1],
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					configVarsLoop:
 | 
				
			||||||
 | 
						for _, v := range cfg.Vars {
 | 
				
			||||||
 | 
							t := []byte("$(var:" + v.Name + ")")
 | 
				
			||||||
 | 
							for _, prev := range res {
 | 
				
			||||||
 | 
								if bytes.Equal(prev.Template, t) {
 | 
				
			||||||
 | 
									continue configVarsLoop
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							res = append(res, Var{t, []byte(v.Default)})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Substitute variables in src
 | 
				
			||||||
 | 
					func Substitute(src []byte, cfg *config.Config) (dst []byte) {
 | 
				
			||||||
 | 
						dst = src
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, bv := range Vars(cfg) {
 | 
				
			||||||
 | 
							if !bytes.Contains(dst, bv.Template) {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							v := bytes.TrimSpace(bv.Value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							dst = bytes.Replace(dst, bv.Template, v, -1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user