From a31c319627a71ce2cdd5e3c34a3655ba80da0057 Mon Sep 17 00:00:00 2001 From: EnumDev Date: Sat, 22 Feb 2025 18:09:08 +0200 Subject: [PATCH] Initial Commit --- .gitignore | 5 + LICENSE | 21 +++ Makefile | 40 +++++ cmd/ectl/go.mod | 5 + cmd/ectl/go.sum | 2 + cmd/ectl/main.go | 192 +++++++++++++++++++++ cmd/enit/go.mod | 3 + cmd/enit/go.sum | 4 + cmd/enit/main.go | 177 ++++++++++++++++++++ cmd/esvm/go.mod | 5 + cmd/esvm/go.sum | 3 + cmd/esvm/main.go | 426 +++++++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 883 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 cmd/ectl/go.mod create mode 100644 cmd/ectl/go.sum create mode 100644 cmd/ectl/main.go create mode 100644 cmd/enit/go.mod create mode 100644 cmd/enit/go.sum create mode 100644 cmd/enit/main.go create mode 100644 cmd/esvm/go.mod create mode 100644 cmd/esvm/go.sum create mode 100644 cmd/esvm/main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..19da903 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Ignore build directory +build/ + +# Ignore IDE directories +.idea \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..782710c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 EnumDev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..633c2dd --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +SHELL := /bin/bash + +PREFIX ?= /usr/local +SBINDIR ?= $(PREFIX)/sbin +SYSCONFDIR ?= $(PREFIX)/etc +LOCALSTATEDIR ?= $(PREFIX)/var +RUNSTATEDIR ?= $(LOCALSTATEDIR)/run +GO ?= $(shell type -a -P go | head -n 1) + +# Set version variable +ifeq ($(VERSION),) + COMMIT := $(shell git rev-parse --short HEAD) + TAG_COMMIT := $(shell git rev-list --abbrev-commit --tags --max-count=1) + TAG := $(shell git describe --abbrev=0 --tags ${TAG_COMMIT} 2>/dev/null || true) + VERSION := $(COMMIT) + ifeq ($(COMMIT), $(TAG_COMMIT)) + VERSION := $(TAG) + endif + ifneq ($(shell git status --porcelain),) + VERSION := $(VERSION)-dirty + endif +endif + +build: + mkdir -p build + cd cmd/enit; $(GO) build -ldflags "-w -X main.version=$(VERSION)" -o ../../build/enit enit + cd cmd/esvm; $(GO) build -ldflags "-w -X main.version=$(VERSION)" -o ../../build/esvm esvm + cd cmd/ectl; $(GO) build -ldflags "-w -X main.version=$(VERSION) -X main.sysconfdir=$(SYSCONFDIR) -X main.runstatedir=$(RUNSTATEDIR)" -o ../../build/ectl ectl + +install: build/enit build/ectl + mkdir -p $(DESTDIR)$(SBINDIR) + mkdir -p $(DESTDIR)$(SYSCONFDIR)/esvm/services + cp build/enit $(DESTDIR)$(SBINDIR)/enit + cp build/enit $(DESTDIR)$(SBINDIR)/esvm + cp build/ectl $(DESTDIR)$(SBINDIR)/ectl + +clean: + rm -r build/ + +.PHONY: build diff --git a/cmd/ectl/go.mod b/cmd/ectl/go.mod new file mode 100644 index 0000000..097f73a --- /dev/null +++ b/cmd/ectl/go.mod @@ -0,0 +1,5 @@ +module ectl + +go 1.23.4 + +require rsc.io/getopt v0.0.0-20170811000552-20be20937449 // indirect diff --git a/cmd/ectl/go.sum b/cmd/ectl/go.sum new file mode 100644 index 0000000..5894729 --- /dev/null +++ b/cmd/ectl/go.sum @@ -0,0 +1,2 @@ +rsc.io/getopt v0.0.0-20170811000552-20be20937449 h1:UukjJOsjQH0DIuyyrcod6CXHS6cdaMMuJmrt+SN1j4A= +rsc.io/getopt v0.0.0-20170811000552-20be20937449/go.mod h1:dhCdeqAxkyt5u3/sKRkUXuHaMXUu1Pt13GTQAM2xnig= diff --git a/cmd/ectl/main.go b/cmd/ectl/main.go new file mode 100644 index 0000000..4272693 --- /dev/null +++ b/cmd/ectl/main.go @@ -0,0 +1,192 @@ +package main + +import ( + "flag" + "fmt" + "log" + "net" + "os" + "path" + "strconv" + "strings" + "syscall" + "time" +) + +// Build-time variables +var version = "dev" +var sysconfdir = "/etc/" +var runstatedir = "/var/run/" + +var socket net.Conn + +func main() { + + // Set and parse flags + printVersion := flag.Bool("version", false, "print version and exit") + flag.Parse() + + // Dial esvm socket + dialSocket() + defer socket.Close() + + if flag.NArg() < 1 { + printUsage() + os.Exit(1) + } + + if *printVersion || flag.Args()[0] == "version" { + fmt.Printf("Enit Control version %s\n", version) + return + } else if flag.Args()[0] == "help" { + printUsage() + return + } else if flag.Args()[0] == "shutdown" || flag.Args()[0] == "poweroff" || flag.Args()[0] == "halt" { + err := syscall.Kill(1, syscall.SIGUSR1) + if err != nil { + log.Fatalf("Could not send shutdown signal! Error: %s\n", err) + } + return + } else if flag.Args()[0] == "reboot" || flag.Args()[0] == "restart" || flag.Args()[0] == "reset" { + err := syscall.Kill(1, syscall.SIGTERM) + if err != nil { + log.Fatalf("Could not send shutdown signal! Error: %s\n", err) + } + return + } else if flag.Args()[0] == "service" || flag.Args()[0] == "sv" { + if len(flag.Args()) <= 1 { + fmt.Println("Usage: ectl service [service]") + return + } else if flag.Args()[1] == "list" { + fmt.Println("list") + return + } else if len(flag.Args()) <= 2 { + fmt.Printf("Usage: ectl service %s \n", flag.Args()[1]) + return + } else if flag.Args()[1] == "start" { + flag.Args()[2] = strings.TrimSuffix(flag.Args()[2], ".esv") + if _, err := os.Stat(path.Join(sysconfdir, "esvm/services/", flag.Args()[2]+".esv")); err != nil { + log.Fatalf("Could not start service! Error: %s\n", err) + } + + _, err := socket.Write([]byte("start " + flag.Args()[2])) + if err != nil { + log.Fatalf("Could not start service! Error: %s\n", err) + } + + buf := make([]byte, 1024) + n, err := socket.Read(buf) + if err != nil { + log.Fatalf("Could not start service! Error: %s\n", err) + } + if string(buf[:n]) != "ok" { + log.Fatalf("Could not start service! Error: expcted 'ok' got '%s'\n", string(buf)) + } + + fmt.Println("Service started successfully!") + return + } else if flag.Args()[1] == "stop" { + flag.Args()[2] = strings.TrimSuffix(flag.Args()[2], ".esv") + if _, err := os.Stat(path.Join(sysconfdir, "esvm/services/", flag.Args()[2]+".esv")); err != nil { + log.Fatalf("Could not stop service! Error: %s\n", err) + } + + _, err := socket.Write([]byte("stop " + flag.Args()[2])) + if err != nil { + log.Fatalf("Could not stop service! Error: %s\n", err) + } + + buf := make([]byte, 1024) + n, err := socket.Read(buf) + if err != nil { + log.Fatalf("Could not stop service! Error: %s\n", err) + } + if string(buf[:n]) != "ok" { + log.Fatalf("Could not stop service! Error: expcted 'ok' got '%s'\n", string(buf)) + } + fmt.Println("Service stopped successfully!") + return + } else if flag.Args()[1] == "restart" || flag.Args()[1] == "reload" { + flag.Args()[2] = strings.TrimSuffix(flag.Args()[2], ".esv") + if _, err := os.Stat(path.Join(sysconfdir, "esvm/services/", flag.Args()[2]+".esv")); err != nil { + log.Fatalf("Could not stop service! Error: %s\n", err) + } + + _, err := socket.Write([]byte("restart " + flag.Args()[2])) + if err != nil { + log.Fatalf("Could not restart service! Error: %s\n", err) + } + + buf := make([]byte, 1024) + n, err := socket.Read(buf) + if err != nil { + log.Fatalf("Could not restart service! Error: %s\n", err) + } + if string(buf[:n]) != "ok" { + log.Fatalf("Could not restart service! Error: expcted 'ok' got '%s'\n", string(buf)) + } + fmt.Println("Service restarted successfully!") + return + } else if flag.Args()[1] == "status" { + flag.Args()[2] = strings.TrimSuffix(flag.Args()[2], ".esv") + if _, err := os.Stat(path.Join(sysconfdir, "esvm/services/", flag.Args()[2]+".esv")); err != nil { + log.Fatalf("Could not stop service! Error: %s\n", err) + } + + var state uint64 + bytes, err := os.ReadFile(path.Join(runstatedir, "esvm", flag.Args()[2], "state")) + if err != nil { + state = 0 + } + state, err = strconv.ParseUint(string(bytes), 10, 8) + + fmt.Println("Service name: " + flag.Args()[2]) + switch state { + case 0: + fmt.Println("Service state: Unknown") + case 1: + fmt.Println("Service state: Unloaded") + case 2: + fmt.Println("Service state: Running") + case 3: + fmt.Println("Service state: Stopped") + case 4: + fmt.Println("Service state: Crashed") + } + return + } + } + + printUsage() + os.Exit(1) +} + +func printUsage() { + fmt.Println("Available sucommands:") + fmt.Println("ectl version | Show enit version") + fmt.Println("ectl shutdown/poweroff/halt | Shutdown the system") + fmt.Println("ectl reboot/restart | Reboot the system") + fmt.Println("ectl help | Show command explanations") + fmt.Println("ectl sv/service start | Start a service") + fmt.Println("ectl sv/service stop | Stop a service") + fmt.Println("ectl sv/service enable | Enable a service at startup") + fmt.Println("ectl sv/service disable | Disable a service at startup") + fmt.Println("ectl sv/service status | Show service status") + fmt.Println("ectl sv/service list | Show all enabled services") +} + +func dialSocket() { + if _, err := os.Stat(path.Join(runstatedir, "esvm/esvm.sock")); err != nil { + log.Fatalf("Could not find esvm.sock! Error: %s\n", err) + } + + var err error + socket, err = net.Dial("unix", path.Join(runstatedir, "esvm/esvm.sock")) + if err != nil { + log.Fatalf("Failed to connect to esvm.sock! Error: %s\n", err) + } + + if err := socket.SetDeadline(time.Now().Add(5 * time.Second)); err != nil { + log.Fatalf("Failed to set write deadline! Error: %s\n", err) + } +} diff --git a/cmd/enit/go.mod b/cmd/enit/go.mod new file mode 100644 index 0000000..89a00dc --- /dev/null +++ b/cmd/enit/go.mod @@ -0,0 +1,3 @@ +module enit + +go 1.23.4 \ No newline at end of file diff --git a/cmd/enit/go.sum b/cmd/enit/go.sum new file mode 100644 index 0000000..a62c313 --- /dev/null +++ b/cmd/enit/go.sum @@ -0,0 +1,4 @@ +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/cmd/enit/main.go b/cmd/enit/main.go new file mode 100644 index 0000000..e3d5196 --- /dev/null +++ b/cmd/enit/main.go @@ -0,0 +1,177 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "os/exec" + "os/signal" + "path" + "strconv" + "syscall" +) + +// Build-time variables +var version = "dev" +var sysconfdir = "/etc/" +var runstatedir = "/var/run/" + +var serviceManagerPid int + +func main() { + // Parse flags + printVersion := flag.Bool("version", false, "print version and exit") + flag.Parse() + + if *printVersion { + fmt.Printf("Enit version %s\n", version) + os.Exit(0) + } + + if os.Getpid() != 1 { + fmt.Println("Enit must be run as PID 1!") + os.Exit(1) + } + + fmt.Println("Starting Enit...") + + // Mount virtual filesystems + mountVirtualFilesystems() + // Mount filesystems in fstab + mountFilesystems() + // Start service manager + startServiceManager() + + // Run function once to wait zombie processes created by initcpio + waitZombieProcesses() + + fmt.Println() + startTerminal() + + // Catch signals + catchSignals() +} + +func mountVirtualFilesystems() { + fmt.Print("Mounting virtual filesystems... ") + + if err := os.Mkdir("/dev/pts", 0755); err != nil { + panic(err) + } + if err := syscall.Mount("none", "/dev/pts", "devpts", syscall.MS_NOSUID|syscall.MS_NOEXEC, ""); err != nil { + panic(err) + } + + fmt.Println("Done.") +} + +func mountFilesystems() { + fmt.Print("Mounting filesystems... ") + + cmd := exec.Command("/bin/mount", "-a") + err := cmd.Run() + + if err != nil { + log.Println("Could not mount fstab entries!") + panic(err) + } + + fmt.Println("Done.") +} + +func startServiceManager() { + fmt.Print("Initializing service manager... ") + + cmd := exec.Command("/sbin/esvm", path.Join(runstatedir, "esvm"), path.Join(sysconfdir, "esvm")) + err := cmd.Start() + if err != nil { + log.Println("Could not initialize service manager!") + panic(err) + } + serviceManagerPid = cmd.Process.Pid + + fmt.Println("Done") +} + +func waitZombieProcesses() { + for { + if wpid, _ := syscall.Wait4(-1, nil, syscall.WNOHANG, nil); wpid <= 0 { + break + } + } +} + +func startTerminal() { + for i := 1; i < 6; i++ { + cmd := exec.Command("/sbin/agetty", "--noclear", "tty"+strconv.Itoa(i)) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Start() + + if err != nil { + log.Println("Could not start agetty terminal on tty" + strconv.Itoa(i) + "!") + panic(err) + } + } +} + +func catchSignals() { + sigc := make(chan os.Signal, 1) + signal.Notify(sigc, syscall.SIGUSR1, syscall.SIGTERM, syscall.SIGINT, syscall.SIGCHLD) + defer close(sigc) + defer signal.Stop(sigc) + for { + switch <-sigc { + case syscall.SIGUSR1: + close(sigc) + signal.Stop(sigc) + shutdownSystem() + case syscall.SIGTERM, syscall.SIGINT: + close(sigc) + signal.Stop(sigc) + rebootSystem() + case syscall.SIGCHLD: + waitZombieProcesses() + } + } +} + +func shutdownSystem() { + fmt.Println("Shutting down...") + + fmt.Println("Stopping services... ") + err := syscall.Kill(serviceManagerPid, syscall.SIGTERM) + if err != nil { + log.Println("Could not stop service manager!") + panic(err) + } + fmt.Print("Done.") + + fmt.Println("Sending shutdown syscall...") + syscall.Sync() + err = syscall.Reboot(syscall.LINUX_REBOOT_CMD_POWER_OFF) + if err != nil { + panic(err) + } +} + +func rebootSystem() { + fmt.Println("Rebooting...") + + fmt.Println("Stopping service manager... ") + err := syscall.Kill(serviceManagerPid, syscall.SIGTERM) + if err != nil { + log.Println("Could not stop service manager!") + panic(err) + } + fmt.Print("Done.") + + fmt.Println("Sending reboot syscall...") + syscall.Sync() + err = syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART) + if err != nil { + panic(err) + } +} diff --git a/cmd/esvm/go.mod b/cmd/esvm/go.mod new file mode 100644 index 0000000..8e7b3d6 --- /dev/null +++ b/cmd/esvm/go.mod @@ -0,0 +1,5 @@ +module esvm + +go 1.23.4 + +require gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/cmd/esvm/go.sum b/cmd/esvm/go.sum new file mode 100644 index 0000000..4bc0337 --- /dev/null +++ b/cmd/esvm/go.sum @@ -0,0 +1,3 @@ +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/cmd/esvm/main.go b/cmd/esvm/main.go new file mode 100644 index 0000000..2aa5f3a --- /dev/null +++ b/cmd/esvm/main.go @@ -0,0 +1,426 @@ +package main + +import ( + "flag" + "fmt" + "gopkg.in/yaml.v3" + "io" + "log" + "net" + "os" + "os/exec" + "os/signal" + "path" + "strconv" + "strings" + "syscall" +) + +type EnitServiceState uint8 + +const ( + EnitServiceUnknown EnitServiceState = iota + EnitServiceUnloaded + EnitServiceRunning + EnitServiceStopped + EnitServiceCrashed + EnitServiceCompleted +) + +type EnitService struct { + Name string `yaml:"name"` + Description string `yaml:"description,omitempty"` + Type string `yaml:"type"` + StartCmd string `yaml:"start_cmd"` + ExitMethod string `yaml:"exit_method"` + StopCmd string `yaml:"stop_cmd,omitempty"` + ServiceRunPath string + stopChannel chan bool +} + +// Build-time variables +var version = "dev" + +var runtimeServiceDir string +var serviceConfigDir string + +var Services = make([]EnitService, 0) + +var socket net.Listener + +func main() { + // Parse flags + printVersion := flag.Bool("version", false, "print version and exit") + flag.Parse() + + if *printVersion || flag.NArg() != 2 { + fmt.Printf("Enit Service Manager version %s\n", version) + os.Exit(0) + } + + // Set directory variables + runtimeServiceDir = flag.Arg(0) + serviceConfigDir = flag.Arg(1) + + if os.Getppid() != 1 { + fmt.Println("Enit must be run by PID 1!") + os.Exit(1) + } + + err := Init() + if err != nil { + log.Fatalf("Could not initialize esvm! Error: %s", err) + } + + sigc := make(chan os.Signal, 1) + signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) + go func() { + <-sigc + Destroy() + os.Exit(0) + }() + + for { + listenToSocket() + } +} + +func Init() error { + if _, err := os.Stat(runtimeServiceDir); err == nil { + return fmt.Errorf("runtime service directory %s already exists", runtimeServiceDir) + } + + err := os.MkdirAll(runtimeServiceDir, 0755) + if err != nil { + return err + } + + socket, err = net.Listen("unix", path.Join(runtimeServiceDir, "esvm.sock")) + if err != nil { + return err + } + + if stat, err := os.Stat(serviceConfigDir); err != nil || !stat.IsDir() { + return nil + } + + dirEntries, err := os.ReadDir(path.Join(serviceConfigDir, "services")) + if err != nil { + return err + } + + for _, entry := range dirEntries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".esv") { + bytes, err := os.ReadFile(path.Join(serviceConfigDir, "services", entry.Name())) + if err != nil { + log.Printf("Could not read service file at %s!", path.Join(serviceConfigDir, "services", entry.Name())) + } + + service := EnitService{ + StopCmd: "", + stopChannel: make(chan bool), + } + if err := yaml.Unmarshal(bytes, &service); err != nil { + log.Printf("Could not read service file at %s!", path.Join(serviceConfigDir, "services", entry.Name())) + } + + switch service.Type { + case "simple", "background": + default: + return fmt.Errorf("unknown service type: %s", service.Type) + } + + switch service.ExitMethod { + case "stop_command", "kill": + default: + return fmt.Errorf("unknown exit method: %s", service.ExitMethod) + } + + service.ServiceRunPath = path.Join(runtimeServiceDir, service.Name) + err = os.MkdirAll(path.Join(service.ServiceRunPath), 0755) + if err != nil { + return err + } + + err = service.setCurrentState(EnitServiceUnloaded) + if err != nil { + return err + } + + Services = append(Services, service) + + if err := service.StartService(); err != nil { + log.Printf("Could not start service %s: %s\n", service.Name, err) + } + } + } + + return nil +} + +func Destroy() { + for _, service := range Services { + if err := service.StopService(); err != nil { + log.Printf("Error stopping service %s: %s\n", service.Name, err) + } + } +} + +func GetServiceByName(name string) *EnitService { + for _, service := range Services { + if service.Name == name { + return &service + } + } + return nil +} + +func (service *EnitService) GetProcess() *os.Process { + bytes, err := os.ReadFile(path.Join(service.ServiceRunPath, "process")) + if err != nil { + return nil + } + + pid, err := strconv.Atoi(strings.TrimSpace(string(bytes))) + if err != nil { + return nil + } + + process, err := os.FindProcess(pid) + if err != nil { + return nil + } + + return process +} + +func (service *EnitService) setProcessID(pid int) error { + if err := os.WriteFile(path.Join(service.ServiceRunPath, "process"), []byte(strconv.Itoa(pid)), 0644); err != nil { + return err + } + return nil +} + +func (service *EnitService) GetCurrentState() EnitServiceState { + bytes, err := os.ReadFile(path.Join(service.ServiceRunPath, "state")) + if err != nil { + return EnitServiceUnknown + } + + state, err := strconv.Atoi(strings.TrimSpace(string(bytes))) + if err != nil { + return EnitServiceUnknown + } + return EnitServiceState(state) +} + +func (service *EnitService) setCurrentState(state EnitServiceState) error { + if err := os.WriteFile(path.Join(service.ServiceRunPath, "state"), []byte(strconv.Itoa(int(state))), 0644); err != nil { + return err + } + return nil +} + +func (service *EnitService) StartService() error { + if service == nil { + return nil + } + if service.GetCurrentState() == EnitServiceRunning { + return nil + } + + cmd := exec.Command("/bin/sh", "-c", service.StartCmd) + if err := cmd.Start(); err != nil { + return err + } + + go func() { + err := cmd.Wait() + select { + case <-service.stopChannel: + _ = service.setCurrentState(EnitServiceStopped) + default: + if service.Type == "simple" && err == nil { + _ = service.setCurrentState(EnitServiceCompleted) + } + _ = service.setCurrentState(EnitServiceCrashed) + } + + }() + + err := service.setProcessID(cmd.Process.Pid) + if err != nil { + return err + } + + err = service.setCurrentState(EnitServiceRunning) + if err != nil { + return err + } + + return nil +} + +func (service *EnitService) StopService() error { + if service.GetCurrentState() != EnitServiceRunning { + return nil + } + + if service.ExitMethod == "kill" { + if service.GetProcess() == nil { + return nil + } + go func() { service.stopChannel <- true }() + err := service.GetProcess().Kill() + if err != nil { + return err + } + } else { + cmd := exec.Command("/bin/sh", "-c", service.StopCmd) + if err := cmd.Run(); err != nil { + return err + } + } + + err := service.setCurrentState(EnitServiceStopped) + if err != nil { + return err + } + + err = service.setProcessID(0) + if err != nil { + return err + } + + return nil +} + +func (service *EnitService) RestartService() error { + if err := service.StopService(); err != nil { + return err + } + + if err := service.StartService(); err != nil { + return err + } + + return nil +} + +func checkForServiceCommand() { + for _, service := range Services { + if _, err := os.Stat(path.Join(service.ServiceRunPath, "start")); err == nil { + err := service.StartService() + if err != nil { + return + } + err = os.Remove(path.Join(service.ServiceRunPath, "start")) + if err != nil { + return + } + } else if _, err := os.Stat(path.Join(service.ServiceRunPath, "stop")); err == nil { + err := service.StopService() + if err != nil { + return + } + err = os.Remove(path.Join(service.ServiceRunPath, "stop")) + if err != nil { + return + } + } else if _, err := os.Stat(path.Join(service.ServiceRunPath, "restart")); err == nil { + err := service.RestartService() + if err != nil { + return + } + err = os.Remove(path.Join(service.ServiceRunPath, "restart")) + if err != nil { + return + } + } + } +} + +func listenToSocket() { + conn, err := socket.Accept() + if err != nil { + log.Println("Could not accept socket connection!") + panic(err) + } + + // Handle the connection in a separate goroutine. + go func(conn net.Conn) { + defer conn.Close() + // Create a buffer for incoming data. + buf := make([]byte, 4096) + + // Read data from the connection. + n, err := conn.Read(buf) + if err == io.EOF { + return + } + if err != nil { + log.Fatal(err) + } + + command := string(buf[:n]) + commandSplit := strings.Split(command, " ") + + if len(commandSplit) >= 2 { + if commandSplit[0] == "start" { + service := GetServiceByName(commandSplit[1]) + if service == nil { + _, err := conn.Write([]byte("service not found")) + if err != nil { + return + } + } + if err := service.StartService(); err != nil { + _, err := conn.Write([]byte("could not start service")) + if err != nil { + return + } + } + _, err := conn.Write([]byte("ok")) + if err != nil { + return + } + } else if commandSplit[0] == "stop" { + service := GetServiceByName(commandSplit[1]) + if service == nil { + _, err := conn.Write([]byte("service not found")) + if err != nil { + return + } + } + if err := service.StopService(); err != nil { + _, err := conn.Write([]byte("could not stop service")) + if err != nil { + return + } + } + _, err := conn.Write([]byte("ok")) + if err != nil { + return + } + } else if commandSplit[0] == "restart" { + service := GetServiceByName(commandSplit[1]) + if service == nil { + _, err := conn.Write([]byte("service not found")) + if err != nil { + return + } + } + if err := service.RestartService(); err != nil { + _, err := conn.Write([]byte("could not restart service")) + if err != nil { + return + } + } + _, err := conn.Write([]byte("ok")) + if err != nil { + return + } + } + } + }(conn) +}