global command
This commit is contained in:
parent
9e597e8a4d
commit
7741051b20
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
|
module novit.nc/direktil/inits
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7
|
||||||
github.com/sparrc/go-ping v0.0.0-20160208162908-416e72114cd1
|
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
|
golang.org/x/sys v0.0.0-20180709060233-1b2967e3c290
|
||||||
gopkg.in/yaml.v2 v2.2.1
|
gopkg.in/yaml.v2 v2.2.1
|
||||||
novit.nc/direktil/pkg v0.0.0-20180706230842-852aa03280f9
|
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/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=
|
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/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=
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"novit.nc/direktil/inits/pkg/vars"
|
"novit.nc/direktil/inits/pkg/vars"
|
||||||
"novit.nc/direktil/pkg/config"
|
"novit.nc/direktil/pkg/config"
|
||||||
dlog "novit.nc/direktil/pkg/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -17,14 +17,14 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Files writes the files from the given config
|
// 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 }
|
accept := func(n string) bool { return true }
|
||||||
|
|
||||||
if len(filters) > 0 {
|
if len(filters) > 0 {
|
||||||
accept = func(n string) bool {
|
accept = func(n string) bool {
|
||||||
for _, filter := range filters {
|
for _, filter := range filters {
|
||||||
if matched, err := filepath.Match(filter, n); err != nil {
|
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 {
|
} else if matched {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -37,7 +37,7 @@ func Files(cfg *config.Config, log *dlog.Log, filters ...string) (err error) {
|
|||||||
err = writeFile(
|
err = writeFile(
|
||||||
authorizedKeysPath,
|
authorizedKeysPath,
|
||||||
[]byte(strings.Join(cfg.RootUser.AuthorizedKeys, "\n")),
|
[]byte(strings.Join(cfg.RootUser.AuthorizedKeys, "\n")),
|
||||||
0600, 0700, cfg, log,
|
0600, 0700, cfg,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -63,7 +63,6 @@ func Files(cfg *config.Config, log *dlog.Log, filters ...string) (err error) {
|
|||||||
mode,
|
mode,
|
||||||
0755,
|
0755,
|
||||||
cfg,
|
cfg,
|
||||||
log,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -74,9 +73,7 @@ func Files(cfg *config.Config, log *dlog.Log, filters ...string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeFile(path string, content []byte, fileMode, dirMode os.FileMode,
|
func writeFile(path string, content []byte, fileMode, dirMode os.FileMode, cfg *config.Config) (err error) {
|
||||||
cfg *config.Config, log *dlog.Log) (err error) {
|
|
||||||
|
|
||||||
if err = os.MkdirAll(filepath.Dir(path), dirMode); err != nil {
|
if err = os.MkdirAll(filepath.Dir(path), dirMode); err != nil {
|
||||||
return
|
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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user