global command
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
/tmp
 | 
			
		||||
@ -1,33 +0,0 @@
 | 
			
		||||
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)
 | 
			
		||||
}
 | 
			
		||||
@ -1,267 +0,0 @@
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
@ -1,36 +0,0 @@
 | 
			
		||||
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 {}
 | 
			
		||||
}
 | 
			
		||||
@ -1,49 +0,0 @@
 | 
			
		||||
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)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -1,167 +0,0 @@
 | 
			
		||||
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")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// wait the device link
 | 
			
		||||
	devPath := "/dev/storage/" + volume.Name
 | 
			
		||||
	for i := 0; i < 300; i++ {
 | 
			
		||||
		_, err := os.Stat(devPath)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		time.Sleep(100 * time.Millisecond)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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, devPath)...)
 | 
			
		||||
}
 | 
			
		||||
@ -1,225 +0,0 @@
 | 
			
		||||
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()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -1,44 +0,0 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/signal"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/sys/unix"
 | 
			
		||||
	"novit.nc/direktil/pkg/log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var reapLock = sync.RWMutex{}
 | 
			
		||||
 | 
			
		||||
func handleChildren() {
 | 
			
		||||
	sigchld := make(chan os.Signal, 2048)
 | 
			
		||||
	signal.Notify(sigchld, syscall.SIGCHLD)
 | 
			
		||||
 | 
			
		||||
	// set us as a sub-reaper
 | 
			
		||||
	if err := unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0); err != nil {
 | 
			
		||||
		initLog.Taintf(log.Error, "reaper: failed to set myself a child sub-reaper: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for range sigchld {
 | 
			
		||||
		reapChildren()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func reapChildren() {
 | 
			
		||||
	reapLock.Lock()
 | 
			
		||||
	defer reapLock.Unlock()
 | 
			
		||||
	for {
 | 
			
		||||
		pid, err := syscall.Wait4(-1, nil, syscall.WNOHANG, nil)
 | 
			
		||||
		if err != nil && err != syscall.ECHILD {
 | 
			
		||||
			initLog.Taintf(log.Warning, "reaper: wait4 failed: %v", err)
 | 
			
		||||
			fmt.Printf("reaper: wait4 failed: %v\n", err)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if pid <= 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -1,44 +0,0 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func _TestReap(t *testing.T) {
 | 
			
		||||
	truePath, err := exec.LookPath("true")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Log("true binary not found, ignoring this test.")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go handleChildren()
 | 
			
		||||
 | 
			
		||||
	count := 1000
 | 
			
		||||
 | 
			
		||||
	wg := &sync.WaitGroup{}
 | 
			
		||||
	wg.Add(count)
 | 
			
		||||
	for i := 0; i < count; i++ {
 | 
			
		||||
		i := i
 | 
			
		||||
		go func() {
 | 
			
		||||
			cmd := exec.Command(truePath)
 | 
			
		||||
			cmd.Stdout = os.Stdout
 | 
			
		||||
			cmd.Stderr = os.Stderr
 | 
			
		||||
			cmd.Stdin = os.Stdin
 | 
			
		||||
			if err := cmd.Run(); err != nil {
 | 
			
		||||
				t.Errorf("[%d] %v", i, err)
 | 
			
		||||
			}
 | 
			
		||||
			wg.Done()
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	wg.Wait()
 | 
			
		||||
 | 
			
		||||
	cmd := exec.Command("sh", "-c", "ps aux |grep Z")
 | 
			
		||||
	cmd.Stdout = os.Stdout
 | 
			
		||||
	cmd.Run()
 | 
			
		||||
 | 
			
		||||
	t.Fail()
 | 
			
		||||
}
 | 
			
		||||
@ -1,28 +0,0 @@
 | 
			
		||||
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"},
 | 
			
		||||
		},
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
@ -1,111 +0,0 @@
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
@ -1,185 +0,0 @@
 | 
			
		||||
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)
 | 
			
		||||
}
 | 
			
		||||
@ -1,30 +0,0 @@
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
@ -1,151 +0,0 @@
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
@ -1,21 +0,0 @@
 | 
			
		||||
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"),
 | 
			
		||||
    }...)
 | 
			
		||||
}
 | 
			
		||||
// */
 | 
			
		||||
@ -1,19 +0,0 @@
 | 
			
		||||
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()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -1,23 +0,0 @@
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
@ -1,36 +0,0 @@
 | 
			
		||||
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"},
 | 
			
		||||
		},
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
@ -1,105 +0,0 @@
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								cmd/dkl/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								cmd/dkl/main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"log"
 | 
			
		||||
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	cmdinit "novit.nc/direktil/inits/pkg/cmd/init"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	root := &cobra.Command{}
 | 
			
		||||
 | 
			
		||||
	root.AddCommand(cmdinit.Command())
 | 
			
		||||
 | 
			
		||||
	if err := root.Execute(); err != nil {
 | 
			
		||||
		log.Fatal("error: ", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.mod
									
									
									
									
									
								
							@ -1,8 +1,12 @@
 | 
			
		||||
module novit.nc/direktil/inits
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/fsnotify/fsnotify v1.4.7
 | 
			
		||||
	github.com/sparrc/go-ping v0.0.0-20160208162908-416e72114cd1
 | 
			
		||||
	golang.org/x/net v0.0.0-20180706051357-32a936f46389 // indirect
 | 
			
		||||
	github.com/spf13/cobra v0.0.3
 | 
			
		||||
	github.com/spf13/pflag v1.0.3 // indirect
 | 
			
		||||
	github.com/ulikunitz/xz v0.5.4
 | 
			
		||||
	golang.org/x/net v0.0.0-20180706051357-32a936f46389
 | 
			
		||||
	golang.org/x/sys v0.0.0-20180709060233-1b2967e3c290
 | 
			
		||||
	gopkg.in/yaml.v2 v2.2.1
 | 
			
		||||
	novit.nc/direktil/pkg v0.0.0-20180706230842-852aa03280f9
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.sum
									
									
									
									
									
								
							@ -1,4 +1,10 @@
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 | 
			
		||||
github.com/sparrc/go-ping v0.0.0-20160208162908-416e72114cd1/go.mod h1:eMyUVp6f/5jnzM+3zahzl7q6UXLbgSc3MKg/+ow9QW0=
 | 
			
		||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
 | 
			
		||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 | 
			
		||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
 | 
			
		||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 | 
			
		||||
github.com/ulikunitz/xz v0.5.4/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
 | 
			
		||||
golang.org/x/net v0.0.0-20180706051357-32a936f46389/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180709060233-1b2967e3c290/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										15
									
								
								layer/etc/init.d/dkl-boot
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										15
									
								
								layer/etc/init.d/dkl-boot
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
#!/sbin/openrc-run
 | 
			
		||||
 | 
			
		||||
description="Direktil boot operations."
 | 
			
		||||
 | 
			
		||||
depend()
 | 
			
		||||
{
 | 
			
		||||
	provide net
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
start()
 | 
			
		||||
{
 | 
			
		||||
	ebegin "Running Direktil boot operations"
 | 
			
		||||
	/sbin/dkl init boot
 | 
			
		||||
	eend $? "Direktil boot operations failed"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								layer/etc/init.d/dkl-default
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										16
									
								
								layer/etc/init.d/dkl-default
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
#!/sbin/openrc-run
 | 
			
		||||
 | 
			
		||||
description="Direktil default operations."
 | 
			
		||||
 | 
			
		||||
depend()
 | 
			
		||||
{
 | 
			
		||||
    # need anything?
 | 
			
		||||
    :
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
start()
 | 
			
		||||
{
 | 
			
		||||
	ebegin "Running Direktil default operations"
 | 
			
		||||
	/sbin/dkl init default
 | 
			
		||||
	eend $? "Direktil default operations failed"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								layer/etc/init.d/dkl-user-services
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										30
									
								
								layer/etc/init.d/dkl-user-services
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
#!/sbin/openrc-run
 | 
			
		||||
 | 
			
		||||
description="Direktil user services."
 | 
			
		||||
 | 
			
		||||
pidfile=/run/direktil/services.pid
 | 
			
		||||
 | 
			
		||||
depend()
 | 
			
		||||
{
 | 
			
		||||
    need dkl-default
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
start()
 | 
			
		||||
{
 | 
			
		||||
    mkdir -p $(dirname $pidfile)
 | 
			
		||||
 | 
			
		||||
    einfo "Starting Direktil services manager"
 | 
			
		||||
    start-stop-daemon --start \
 | 
			
		||||
        --pidfile $pidfile \
 | 
			
		||||
        --background \
 | 
			
		||||
        --exec "/sbin/dkl" \
 | 
			
		||||
        -- init services
 | 
			
		||||
    eend $? "Failed to start Direktil services manager"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
stop()
 | 
			
		||||
{
 | 
			
		||||
    einfo "Stopping Direktil services manager"
 | 
			
		||||
    start-stop-daemon --stop --pidfile $pidfile
 | 
			
		||||
    eend $? "Failed to stop Direktil services manager"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								layer/etc/inittab
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								layer/etc/inittab
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
			
		||||
#
 | 
			
		||||
# /etc/inittab:  This file describes how the INIT process should set up
 | 
			
		||||
#                the system in a certain run-level.
 | 
			
		||||
#
 | 
			
		||||
# Author:  Miquel van Smoorenburg, <miquels@cistron.nl>
 | 
			
		||||
# Modified by:  Patrick J. Volkerding, <volkerdi@ftp.cdrom.com>
 | 
			
		||||
# Modified by:  Daniel Robbins, <drobbins@gentoo.org>
 | 
			
		||||
# Modified by:  Martin Schlemmer, <azarah@gentoo.org>
 | 
			
		||||
# Modified by:  Mike Frysinger, <vapier@gentoo.org>
 | 
			
		||||
# Modified by:  Robin H. Johnson, <robbat2@gentoo.org>
 | 
			
		||||
# Modified by:  William Hubbs, <williamh@gentoo.org>
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# Default runlevel.
 | 
			
		||||
id:3:initdefault:
 | 
			
		||||
 | 
			
		||||
# System initialization, mount local filesystems, etc.
 | 
			
		||||
si::sysinit:/sbin/openrc sysinit
 | 
			
		||||
 | 
			
		||||
# Further system initialization, brings up the boot runlevel.
 | 
			
		||||
rc::bootwait:/sbin/openrc boot
 | 
			
		||||
 | 
			
		||||
l0u:0:wait:/sbin/telinit u
 | 
			
		||||
l0:0:wait:/sbin/openrc shutdown
 | 
			
		||||
l0s:0:wait:/sbin/halt -dhnp
 | 
			
		||||
l1:1:wait:/sbin/openrc single
 | 
			
		||||
l2:2:wait:/sbin/openrc nonetwork
 | 
			
		||||
l3:3:wait:/sbin/openrc default
 | 
			
		||||
l4:4:wait:/sbin/openrc default
 | 
			
		||||
l5:5:wait:/sbin/openrc default
 | 
			
		||||
l6u:6:wait:/sbin/telinit u
 | 
			
		||||
l6:6:wait:/sbin/openrc reboot
 | 
			
		||||
l6r:6:wait:/sbin/reboot -dkn
 | 
			
		||||
#z6:6:respawn:/sbin/sulogin
 | 
			
		||||
 | 
			
		||||
# new-style single-user
 | 
			
		||||
su0:S:wait:/sbin/openrc single
 | 
			
		||||
su1:S:wait:/sbin/sulogin
 | 
			
		||||
 | 
			
		||||
# TERMINALS
 | 
			
		||||
#x1:12345:respawn:/sbin/agetty 38400 console linux
 | 
			
		||||
c1:12345:respawn:/sbin/agetty --noclear 38400 tty1 linux
 | 
			
		||||
c2:2345:respawn:/sbin/agetty 38400 tty2 linux
 | 
			
		||||
c3:2345:respawn:/sbin/agetty 38400 tty3 linux
 | 
			
		||||
c4:2345:respawn:/sbin/agetty 38400 tty4 linux
 | 
			
		||||
c5:2345:respawn:/sbin/agetty 38400 tty5 linux
 | 
			
		||||
c6:2345:respawn:/sbin/agetty 38400 tty6 linux
 | 
			
		||||
 | 
			
		||||
# SERIAL CONSOLES
 | 
			
		||||
s0:12345:respawn:/sbin/agetty --noclear -L 115200 ttyS0 vt100
 | 
			
		||||
#s1:12345:respawn:/sbin/agetty -L 115200 ttyS1 vt100
 | 
			
		||||
 | 
			
		||||
# What to do at the "Three Finger Salute".
 | 
			
		||||
ca:12345:ctrlaltdel:/sbin/shutdown -r now
 | 
			
		||||
							
								
								
									
										1
									
								
								layer/etc/runlevels/boot/dkl-boot
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								layer/etc/runlevels/boot/dkl-boot
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
../../init.d/dkl-boot
 | 
			
		||||
							
								
								
									
										1
									
								
								layer/etc/runlevels/default/dkl-default
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								layer/etc/runlevels/default/dkl-default
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
../../init.d/dkl-default
 | 
			
		||||
							
								
								
									
										1
									
								
								layer/etc/runlevels/default/dkl-user-services
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								layer/etc/runlevels/default/dkl-user-services
									
									
									
									
									
										Symbolic link
									
								
							@ -0,0 +1 @@
 | 
			
		||||
../../init.d/dkl-user-services
 | 
			
		||||
							
								
								
									
										6
									
								
								modd.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								modd.conf
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
**/*.go layer/**/* test-vm update-test-data {
 | 
			
		||||
  prep: go test ./pkg/... ./cmd/dkl
 | 
			
		||||
  prep: CGO_ENABLED=0 go build ./cmd/dkl
 | 
			
		||||
  prep: ./update-test-data
 | 
			
		||||
  daemon: ./test-vm
 | 
			
		||||
}
 | 
			
		||||
@ -3,13 +3,13 @@ package apply
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"novit.nc/direktil/inits/pkg/vars"
 | 
			
		||||
	"novit.nc/direktil/pkg/config"
 | 
			
		||||
	dlog "novit.nc/direktil/pkg/log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
@ -17,14 +17,14 @@ const (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Files writes the files from the given config
 | 
			
		||||
func Files(cfg *config.Config, log *dlog.Log, filters ...string) (err error) {
 | 
			
		||||
func Files(cfg *config.Config, filters ...string) (err error) {
 | 
			
		||||
	accept := func(n string) bool { return true }
 | 
			
		||||
 | 
			
		||||
	if len(filters) > 0 {
 | 
			
		||||
		accept = func(n string) bool {
 | 
			
		||||
			for _, filter := range filters {
 | 
			
		||||
				if matched, err := filepath.Match(filter, n); err != nil {
 | 
			
		||||
					log.Taintf(dlog.Error, "bad filter ignored: %q: %v", filter, err)
 | 
			
		||||
					log.Printf("ERROR: bad filter ignored: %q: %v", filter, err)
 | 
			
		||||
				} else if matched {
 | 
			
		||||
					return true
 | 
			
		||||
				}
 | 
			
		||||
@ -37,7 +37,7 @@ func Files(cfg *config.Config, log *dlog.Log, filters ...string) (err error) {
 | 
			
		||||
		err = writeFile(
 | 
			
		||||
			authorizedKeysPath,
 | 
			
		||||
			[]byte(strings.Join(cfg.RootUser.AuthorizedKeys, "\n")),
 | 
			
		||||
			0600, 0700, cfg, log,
 | 
			
		||||
			0600, 0700, cfg,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@ -63,7 +63,6 @@ func Files(cfg *config.Config, log *dlog.Log, filters ...string) (err error) {
 | 
			
		||||
			mode,
 | 
			
		||||
			0755,
 | 
			
		||||
			cfg,
 | 
			
		||||
			log,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@ -74,9 +73,7 @@ func Files(cfg *config.Config, log *dlog.Log, filters ...string) (err error) {
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func writeFile(path string, content []byte, fileMode, dirMode os.FileMode,
 | 
			
		||||
	cfg *config.Config, log *dlog.Log) (err error) {
 | 
			
		||||
 | 
			
		||||
func writeFile(path string, content []byte, fileMode, dirMode os.FileMode, cfg *config.Config) (err error) {
 | 
			
		||||
	if err = os.MkdirAll(filepath.Dir(path), dirMode); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										43
									
								
								pkg/cmd/init/boot/boot.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								pkg/cmd/init/boot/boot.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
			
		||||
package initboot
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"log"
 | 
			
		||||
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	"novit.nc/direktil/inits/pkg/sys"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	doNetwork bool
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Command() (c *cobra.Command) {
 | 
			
		||||
	c = &cobra.Command{
 | 
			
		||||
		Use:   "boot",
 | 
			
		||||
		Short: "boot stage",
 | 
			
		||||
		Run:   run,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Flags().BoolVar(&doNetwork, "do-network", true, "setup network")
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func run(c *cobra.Command, args []string) {
 | 
			
		||||
	setupFiles()
 | 
			
		||||
	setupModules()
 | 
			
		||||
 | 
			
		||||
	if doNetwork {
 | 
			
		||||
		setupNetworking()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	setupLVM()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setupModules() {
 | 
			
		||||
	for _, mod := range sys.Config().Modules {
 | 
			
		||||
		log.Print("loading module ", mod)
 | 
			
		||||
		sys.Run("modprobe", mod)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								pkg/cmd/init/boot/files.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								pkg/cmd/init/boot/files.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
			
		||||
package initboot
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"log"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"syscall"
 | 
			
		||||
 | 
			
		||||
	"novit.nc/direktil/inits/pkg/apply"
 | 
			
		||||
	"novit.nc/direktil/inits/pkg/sys"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func setupFiles() {
 | 
			
		||||
	cfg := sys.Config()
 | 
			
		||||
 | 
			
		||||
	// 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 {
 | 
			
		||||
		log.Fatalf("FATAL: mount --make-rshared / failed: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// - setup root user
 | 
			
		||||
	if passwordHash := cfg.RootUser.PasswordHash; passwordHash == "" {
 | 
			
		||||
		sys.MustRun("/usr/bin/passwd", "-d", "root")
 | 
			
		||||
	} else {
 | 
			
		||||
		sys.MustRun("/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)
 | 
			
		||||
 | 
			
		||||
		sys.MustRun("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)
 | 
			
		||||
 | 
			
		||||
		sys.MustRun("useradd", opts...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// - files
 | 
			
		||||
	if err := apply.Files(cfg); err != nil {
 | 
			
		||||
		log.Fatal("FATAL: ", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										186
									
								
								pkg/cmd/init/boot/lvm.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								pkg/cmd/init/boot/lvm.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,186 @@
 | 
			
		||||
package initboot
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"novit.nc/direktil/pkg/config"
 | 
			
		||||
 | 
			
		||||
	"novit.nc/direktil/inits/pkg/sys"
 | 
			
		||||
	"novit.nc/direktil/inits/pkg/vars"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func setupLVM() {
 | 
			
		||||
	if !dmInProc() {
 | 
			
		||||
		sys.MustRun("modprobe", "dm-mod")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// start lvmetad
 | 
			
		||||
	sys.Mkdir("/run/lvm", 0700)
 | 
			
		||||
	sys.Mkdir("/run/lock/lvm", 0700)
 | 
			
		||||
	sys.Run("lvmetad")
 | 
			
		||||
 | 
			
		||||
	sys.WaitFile("/run/lvm/lvmetad.socket", time.After(30*time.Second))
 | 
			
		||||
 | 
			
		||||
	// scan devices
 | 
			
		||||
	sys.Run("lvm", "pvscan")
 | 
			
		||||
	sys.Run("lvm", "vgscan", "--mknodes")
 | 
			
		||||
	sys.Run("lvm", "vgchange", "--sysinit", "-a", "ly")
 | 
			
		||||
 | 
			
		||||
	cfg := sys.Config()
 | 
			
		||||
 | 
			
		||||
	// setup storage
 | 
			
		||||
	log.Print("checking storage")
 | 
			
		||||
	if err := exec.Command("vgdisplay", "storage").Run(); err != nil {
 | 
			
		||||
		log.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 {
 | 
			
		||||
			log.Fatal("failed to stat ", dev, ": ", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		log.Print("- removing LV ", name)
 | 
			
		||||
		cmd := exec.Command("lvremove", "-f", "storage/"+name)
 | 
			
		||||
		cmd.Stderr = os.Stderr
 | 
			
		||||
		if err := cmd.Run(); err != nil {
 | 
			
		||||
			log.Fatal("failed to remove LV ", name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// setup volumes
 | 
			
		||||
	for _, volume := range cfg.Storage.Volumes {
 | 
			
		||||
		if err := exec.Command("lvdisplay", "storage/"+volume.Name).Run(); err != nil {
 | 
			
		||||
			log.Print("- creating LV ", volume.Name)
 | 
			
		||||
			setupLV(volume)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		dev := "/dev/storage/" + volume.Name
 | 
			
		||||
 | 
			
		||||
		sys.WaitFile(dev, time.After(30*time.Second))
 | 
			
		||||
 | 
			
		||||
		log.Printf("checking filesystem on %s", dev)
 | 
			
		||||
		sys.MustRun("fsck", "-p", dev)
 | 
			
		||||
 | 
			
		||||
		sys.Mount(dev, volume.Mount.Path, volume.FS,
 | 
			
		||||
			syscall.MS_NOATIME|syscall.MS_RELATIME,
 | 
			
		||||
			volume.Mount.Options)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func dmInProc() bool {
 | 
			
		||||
	for _, f := range []string{"devices", "misc"} {
 | 
			
		||||
		c, err := ioutil.ReadFile("/proc/" + f)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatalf("failed to read %s: %v", f, err)
 | 
			
		||||
		}
 | 
			
		||||
		if !bytes.Contains(c, []byte("device-mapper")) {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setupVG(udevMatch string) {
 | 
			
		||||
	const pDevName = "DEVNAME="
 | 
			
		||||
 | 
			
		||||
	dev := ""
 | 
			
		||||
	try := 0
 | 
			
		||||
 | 
			
		||||
retry:
 | 
			
		||||
	paths, err := filepath.Glob("/sys/class/block/*")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.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 {
 | 
			
		||||
			log.Printf("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 matched, err := filepath.Match(udevMatch, line); err != nil {
 | 
			
		||||
				log.Fatalf("FATAL: invalid match: %q: %v", udevMatch, err)
 | 
			
		||||
 | 
			
		||||
			} else if matched {
 | 
			
		||||
				matches = true
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if devPath != "" && matches {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if devPath != "" && matches {
 | 
			
		||||
			dev = devPath
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if dev == "" {
 | 
			
		||||
		time.Sleep(1 * time.Second)
 | 
			
		||||
		try++
 | 
			
		||||
		if try > 30 {
 | 
			
		||||
			log.Fatal("FATAL: storage device not found after 30s: ", udevMatch)
 | 
			
		||||
		}
 | 
			
		||||
		goto retry
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Print("found storage device at ", dev)
 | 
			
		||||
 | 
			
		||||
	sys.MustRun("pvcreate", dev)
 | 
			
		||||
	sys.MustRun("vgcreate", "storage", dev)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setupLV(volume config.VolumeDef) {
 | 
			
		||||
	if volume.Extents != "" {
 | 
			
		||||
		sys.MustRun("lvcreate", "-l", volume.Extents, "-n", volume.Name, "storage")
 | 
			
		||||
	} else {
 | 
			
		||||
		sys.MustRun("lvcreate", "-L", volume.Size, "-n", volume.Name, "storage")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// wait the device link
 | 
			
		||||
	devPath := "/dev/storage/" + volume.Name
 | 
			
		||||
	sys.WaitFile(devPath, time.After(30*time.Second))
 | 
			
		||||
 | 
			
		||||
	args := make([]string, 0)
 | 
			
		||||
 | 
			
		||||
	switch volume.FS {
 | 
			
		||||
	case "btrfs":
 | 
			
		||||
		args = append(args, "-f")
 | 
			
		||||
	case "ext4":
 | 
			
		||||
		args = append(args, "-F")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sys.MustRun("mkfs."+volume.FS, append(args, devPath)...)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										155
									
								
								pkg/cmd/init/boot/network.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								pkg/cmd/init/boot/network.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,155 @@
 | 
			
		||||
package initboot
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	ping "github.com/sparrc/go-ping"
 | 
			
		||||
	"novit.nc/direktil/pkg/config"
 | 
			
		||||
 | 
			
		||||
	"novit.nc/direktil/inits/pkg/sys"
 | 
			
		||||
	"novit.nc/direktil/inits/pkg/vars"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var networkStarted = map[string]bool{}
 | 
			
		||||
 | 
			
		||||
func setupNetworking() {
 | 
			
		||||
	cfg := sys.Config()
 | 
			
		||||
	for idx, network := range cfg.Networks {
 | 
			
		||||
		setupNetwork(idx, network)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setupNetwork(idx int, network config.NetworkDef) {
 | 
			
		||||
	tries := 0
 | 
			
		||||
retry:
 | 
			
		||||
	ifaces, err := net.Interfaces()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("FATAL: 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 {
 | 
			
		||||
				log.Fatalf("FATAL: network[%d] name match error: %v", idx, err)
 | 
			
		||||
			} else if !ok {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if network.Match.Ping != nil {
 | 
			
		||||
			log.Printf("network[%d] ping check on %s", idx, iface.Name)
 | 
			
		||||
 | 
			
		||||
			if ok, err := networkPingCheck(iface.Name, network); err != nil {
 | 
			
		||||
				log.Printf("ERROR: network[%d] ping check failed: %v", idx, err)
 | 
			
		||||
 | 
			
		||||
			} else if !ok {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		log.Printf("network[%d] matches interface %s", idx, iface.Name)
 | 
			
		||||
		match = true
 | 
			
		||||
 | 
			
		||||
		startNetwork(iface.Name, idx, network)
 | 
			
		||||
 | 
			
		||||
		if !network.Match.All {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !match {
 | 
			
		||||
		log.Printf("WARNING: network[%d] did not match any interface", idx)
 | 
			
		||||
 | 
			
		||||
		tries++
 | 
			
		||||
		if network.Optional && tries > 3 {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		time.Sleep(1 * time.Second)
 | 
			
		||||
		log.Printf("WARNING: network[%d] retrying (try: %d)", idx, tries)
 | 
			
		||||
		goto retry
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func startNetwork(ifaceName string, idx int, network config.NetworkDef) {
 | 
			
		||||
	cfg := sys.Config()
 | 
			
		||||
 | 
			
		||||
	log.Printf("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()
 | 
			
		||||
		log.Fatalf("FATAL: network setup failed (link list below): %v\n%s", err, string(links))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	networkStarted[ifaceName] = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func networkPingCheck(ifName string, network config.NetworkDef) (b bool, err error) {
 | 
			
		||||
	check := network.Match.Ping
 | 
			
		||||
 | 
			
		||||
	source := string(vars.Substitute([]byte(check.Source), sys.Config()))
 | 
			
		||||
 | 
			
		||||
	if err = sys.Run("ip", "addr", "add", source, "dev", ifName); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if err = sys.Run("ip", "link", "set", ifName, "up"); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer func() {
 | 
			
		||||
		sys.MustRun("ip", "link", "set", ifName, "down")
 | 
			
		||||
		sys.MustRun("ip", "addr", "del", source, "dev", ifName)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	count := 3
 | 
			
		||||
	if check.Count != 0 {
 | 
			
		||||
		count = check.Count
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for n := 0; n < count; n++ {
 | 
			
		||||
		// TODO probably better to use golang.org/x/net/icmp directly
 | 
			
		||||
		pinger, e := ping.NewPinger(network.Match.Ping.Target)
 | 
			
		||||
		if e != nil {
 | 
			
		||||
			err = e
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pinger.Count = 1
 | 
			
		||||
 | 
			
		||||
		pinger.Timeout = 1 * time.Second
 | 
			
		||||
		if check.Timeout > 0 {
 | 
			
		||||
			pinger.Timeout = time.Duration(check.Timeout) * time.Second
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pinger.SetPrivileged(true)
 | 
			
		||||
		pinger.Run()
 | 
			
		||||
 | 
			
		||||
		if pinger.Statistics().PacketsRecv > 0 {
 | 
			
		||||
			b = true
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								pkg/cmd/init/default/default.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								pkg/cmd/init/default/default.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
package initdefault
 | 
			
		||||
 | 
			
		||||
import "github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
func Command() (c *cobra.Command) {
 | 
			
		||||
	c = &cobra.Command{
 | 
			
		||||
		Use:   "default",
 | 
			
		||||
		Short: "default stage",
 | 
			
		||||
		Run:   run,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func run(c *cobra.Command, args []string) {
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								pkg/cmd/init/init.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								pkg/cmd/init/init.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
			
		||||
package cmdinit
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	initboot "novit.nc/direktil/inits/pkg/cmd/init/boot"
 | 
			
		||||
	initdefault "novit.nc/direktil/inits/pkg/cmd/init/default"
 | 
			
		||||
	initservice "novit.nc/direktil/inits/pkg/cmd/init/service"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Command() (c *cobra.Command) {
 | 
			
		||||
	c = &cobra.Command{
 | 
			
		||||
		Use:   "init",
 | 
			
		||||
		Short: "init stages",
 | 
			
		||||
 | 
			
		||||
		PersistentPreRun: func(_ *cobra.Command, _ []string) {
 | 
			
		||||
			// set a reasonable path
 | 
			
		||||
			os.Setenv("PATH", strings.Join([]string{
 | 
			
		||||
				"/usr/local/bin:/usr/local/sbin",
 | 
			
		||||
				"/usr/bin:/usr/sbin",
 | 
			
		||||
				"/bin:/sbin",
 | 
			
		||||
			}, ":"))
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.AddCommand(initboot.Command())
 | 
			
		||||
	c.AddCommand(initdefault.Command())
 | 
			
		||||
	c.AddCommand(initservice.Command())
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										90
									
								
								pkg/cmd/init/service/service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								pkg/cmd/init/service/service.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,90 @@
 | 
			
		||||
package initservices
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"log"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	plog "novit.nc/direktil/pkg/log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	delays = []time.Duration{
 | 
			
		||||
		1 * time.Second,
 | 
			
		||||
		2 * time.Second,
 | 
			
		||||
		4 * time.Second,
 | 
			
		||||
		8 * time.Second,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	crashForgiveDelay = 10 * time.Minute
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Command() (c *cobra.Command) {
 | 
			
		||||
	c = &cobra.Command{
 | 
			
		||||
		Use:   "services",
 | 
			
		||||
		Short: "run user services",
 | 
			
		||||
		Run:   run,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func run(c *cobra.Command, args []string) {
 | 
			
		||||
	paths, err := filepath.Glob("/etc/direktil/services/*")
 | 
			
		||||
 | 
			
		||||
	if err != nil && !os.IsNotExist(err) {
 | 
			
		||||
		log.Fatal("failed to list services: ", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, path := range paths {
 | 
			
		||||
		stat, err := os.Stat(path)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatalf("failed to stat %s: %v", path, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if stat.Mode()&0100 == 0 {
 | 
			
		||||
			// not executable
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		go runService(path)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	select {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runService(svcPath string) {
 | 
			
		||||
	svc := filepath.Base(svcPath)
 | 
			
		||||
 | 
			
		||||
	logger := plog.Get(svc)
 | 
			
		||||
	plog.EnableFiles()
 | 
			
		||||
 | 
			
		||||
	n := 0
 | 
			
		||||
	for {
 | 
			
		||||
		lastStart := time.Now()
 | 
			
		||||
 | 
			
		||||
		cmd := exec.Command(svcPath)
 | 
			
		||||
		cmd.Stdout = logger
 | 
			
		||||
		cmd.Stderr = logger
 | 
			
		||||
		err := cmd.Run()
 | 
			
		||||
 | 
			
		||||
		if time.Since(lastStart) > crashForgiveDelay {
 | 
			
		||||
			n = 0
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			logger.Taintf(plog.Error, "service exited (%v), waiting %v", err, delays[n])
 | 
			
		||||
		} else {
 | 
			
		||||
			logger.Taintf(plog.Error, "service exited on error (%v), waiting %v", err, delays[n])
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		time.Sleep(delays[n])
 | 
			
		||||
 | 
			
		||||
		if n+1 < len(delays) {
 | 
			
		||||
			n++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								pkg/sys/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								pkg/sys/config.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
			
		||||
package sys
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"log"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"novit.nc/direktil/pkg/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const cfgPath = "/config.yaml"
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	cfg     *config.Config
 | 
			
		||||
	cfgLock sync.Mutex
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Config() *config.Config {
 | 
			
		||||
	if cfg != nil {
 | 
			
		||||
		return cfg
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cfgLock.Lock()
 | 
			
		||||
	defer cfgLock.Unlock()
 | 
			
		||||
 | 
			
		||||
	if cfg != nil {
 | 
			
		||||
		return cfg
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c, err := config.Load(cfgPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal("FATAL: failed to load config: ", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cfg = c
 | 
			
		||||
	return cfg
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								pkg/sys/mount.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								pkg/sys/mount.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
package sys
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"log"
 | 
			
		||||
	"os"
 | 
			
		||||
	"syscall"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
		log.Fatalf("FATAL: mount %q %q -t %q -o %q failed: %v", source, target, fstype, data, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Printf("mounted %q", target)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										87
									
								
								pkg/sys/sys.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								pkg/sys/sys.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,87 @@
 | 
			
		||||
package sys
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"log"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/fsnotify/fsnotify"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Run(cmd string, args ...string) (err error) {
 | 
			
		||||
	c := exec.Command(cmd, args...)
 | 
			
		||||
	c.Stdout = os.Stdout
 | 
			
		||||
	c.Stderr = os.Stderr
 | 
			
		||||
 | 
			
		||||
	if err = c.Run(); err != nil {
 | 
			
		||||
		log.Printf("command %s %q failed: %v", cmd, args, err)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func MustRun(cmd string, args ...string) {
 | 
			
		||||
	if err := Run(cmd, args...); err != nil {
 | 
			
		||||
		log.Fatal("FATAL: mandatory command did not succeed")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Mkdir(dir string, mode os.FileMode) {
 | 
			
		||||
	if err := os.MkdirAll(dir, mode); err != nil {
 | 
			
		||||
		log.Fatalf("FATAL: mkdir %q failed: %v", dir, err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func FileExists(path string) bool {
 | 
			
		||||
	_, err := os.Stat(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if !os.IsNotExist(err) {
 | 
			
		||||
			log.Printf("WARNING: failed to stat %q, assuming not exist: %v", path, err)
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func WaitFile(path string, timeout <-chan time.Time) {
 | 
			
		||||
	if FileExists(path) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	watch, err := fsnotify.NewWatcher()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal("FATAL: fsnotify: failed to create: ", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer watch.Close()
 | 
			
		||||
 | 
			
		||||
	dir := filepath.Dir(path)
 | 
			
		||||
 | 
			
		||||
	if err = watch.Add(dir); err != nil {
 | 
			
		||||
		log.Fatalf("FATAL: fsnotify: failed to add %s: %v", dir, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for err := range watch.Errors {
 | 
			
		||||
			log.Fatal("FATAL: fsnotify: error: ", err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	timedOut := false
 | 
			
		||||
	for !timedOut {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-watch.Events:
 | 
			
		||||
			// skip
 | 
			
		||||
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			timedOut = true
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if FileExists(path) {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Fatal("FATAL: timed out waiting for ", path)
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user