Separate go files in esvm

This commit is contained in:
2025-08-26 14:15:31 +03:00
parent 634e0271f5
commit 1d4a51abe0
3 changed files with 413 additions and 388 deletions
+1 -388
View File
@@ -3,15 +3,12 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"io"
"log" "log"
"net" "net"
"os" "os"
"os/exec"
"os/signal" "os/signal"
"path" "path"
"slices" "slices"
"strconv"
"strings" "strings"
"syscall" "syscall"
"time" "time"
@@ -19,33 +16,6 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
type EnitServiceState uint8
const (
EnitServiceUnknown EnitServiceState = iota
EnitServiceUnloaded
EnitServiceRunning
EnitServiceStopped
EnitServiceCrashed
EnitServiceCompleted
)
type EnitService struct {
Name string `yaml:"name"`
Description string `yaml:"description,omitempty"`
Dependencies []string `yaml:"dependencies,omitempty"`
Type string `yaml:"type"`
StartCmd string `yaml:"start_cmd"`
ExitMethod string `yaml:"exit_method"`
CrashOnSafeExit bool `yaml:"crash_on_safe_exit"`
StopCmd string `yaml:"stop_cmd,omitempty"`
Restart string `yaml:"restart,omitempty"`
LogOutput bool `yaml:"log_output,omitempty"`
ServiceRunPath string
restartCount int
stopChannel chan bool
}
// Build-time variables // Build-time variables
var version = "dev" var version = "dev"
@@ -145,7 +115,7 @@ func Init() {
logger.Fatalf("Could not initialize ESVM! Error: %s", err) logger.Fatalf("Could not initialize ESVM! Error: %s", err)
} }
socket, err = net.Listen("unix", path.Join(runtimeServiceDir, "esvm.sock")) socket, err = initSocket()
if err != nil { if err != nil {
logger.Fatalf("Could not initialize ESVM! Error: %s", err) logger.Fatalf("Could not initialize ESVM! Error: %s", err)
} }
@@ -315,360 +285,3 @@ func GetServiceByName(name string) *EnitService {
} }
return nil return nil
} }
func (service *EnitService) GetUnmetDependencies() (missingDependencies []string) {
for _, dependency := range service.Dependencies {
if strings.HasPrefix(dependency, "/") {
// File dependency
if _, err := os.Stat(dependency); err != nil {
missingDependencies = append(missingDependencies, dependency)
}
} else {
// Service dependency
depService := GetServiceByName(dependency)
if depService == nil {
missingDependencies = append(missingDependencies, dependency)
}
}
}
return missingDependencies
}
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) GetLogFile() (file *os.File, err error) {
// Create esvm log directory
err = os.MkdirAll("/var/log/esvm", 0755)
if err != nil {
return nil, err
}
// Create esvm old log directory
err = os.MkdirAll("/var/log/esvm/old", 0755)
if err != nil {
return nil, err
}
// Move old log file
if _, err := os.Stat(path.Join("/var/log/esvm/", service.Name+".log")); err == nil {
os.Rename(path.Join("/var/log/esvm/", service.Name+".log"), path.Join("/var/log/esvm/old", service.Name+".log"))
}
// Open new log file
file, err = os.OpenFile(path.Join("/var/log/esvm/", service.Name+".log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return nil, err
}
// Initialize logger and print a header line
_, err = file.WriteString("------ " + time.Now().Format(time.UnixDate) + " ------\n")
if err != nil {
file.Close()
return nil, err
}
return file, nil
}
func (service *EnitService) StartService() error {
if service == nil {
return nil
}
if service.GetCurrentState() == EnitServiceRunning {
return nil
}
logger.Printf("Starting service (%s)...\n", service.Name)
// Get log file if service logs output
var logFile *os.File
if service.LogOutput {
var err error
logFile, err = service.GetLogFile()
if err != nil {
return err
}
}
cmd := exec.Command("/bin/sh", "-c", "exec "+service.StartCmd)
if logFile != nil {
cmd.Stdout = logFile
cmd.Stderr = logFile
}
if err := cmd.Start(); err != nil {
// Close log file if not nil
if logFile != nil {
logFile.Close()
}
return err
}
err := service.setProcessID(cmd.Process.Pid)
if err != nil {
// Close log file if not nil
if logFile != nil {
logFile.Close()
}
return err
}
err = service.setCurrentState(EnitServiceRunning)
if err != nil {
// Close log file if not nil
if logFile != nil {
logFile.Close()
}
return err
}
go func() {
err := cmd.Wait()
// Close log file if not nil
if logFile != nil {
logFile.Close()
}
select {
case <-service.stopChannel:
service.restartCount = 0
_ = service.setCurrentState(EnitServiceStopped)
default:
if service.Type == "simple" && err == nil {
service.restartCount = 0
_ = service.setCurrentState(EnitServiceCompleted)
return
}
if !service.CrashOnSafeExit {
logger.Printf("Service (%s) has exited\n", service.Name)
_ = service.setCurrentState(EnitServiceStopped)
} else {
logger.Printf("Service (%s) has crashed!\n", service.Name)
_ = service.setCurrentState(EnitServiceCrashed)
}
if service.Restart == "always" {
_ = service.StartService()
} else if service.Restart == "true" && service.restartCount < 5 {
service.restartCount++
_ = service.StartService()
}
}
}()
logger.Printf("Service (%s) has started!\n", service.Name)
return nil
}
func (service *EnitService) StopService() error {
if service.GetCurrentState() != EnitServiceRunning {
return nil
}
logger.Printf("Stopping service (%s)...\n", service.Name)
if service.ExitMethod == "kill" {
process := service.GetProcess()
if err := process.Signal(syscall.Signal(0)); err != nil {
logger.Printf("Service (%s) has stopped. (Process already dead)\n", service.Name)
return nil
}
go func() { service.stopChannel <- true }()
err := service.GetProcess().Signal(syscall.SIGTERM)
if err != nil {
return err
}
exit := false
for timeout := time.After(5 * time.Second); ; {
if exit {
break
}
select {
case <-timeout:
logger.Println("Process took too long to finish. Forcefully killing process...")
err := service.GetProcess().Kill()
if err != nil {
return err
}
exit = true
default:
if process == nil {
exit = true
break
}
err = process.Signal(syscall.Signal(0))
if err != nil {
exit = true
}
}
}
} 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
}
logger.Printf("Service (%s) has stopped!\n", service.Name)
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 listenToSocket() {
conn, err := socket.Accept()
if err != nil {
logger.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 {
logger.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)
}
+310
View File
@@ -0,0 +1,310 @@
package main
import (
"os"
"os/exec"
"path"
"strconv"
"strings"
"syscall"
"time"
)
type EnitServiceState uint8
const (
EnitServiceUnknown EnitServiceState = iota
EnitServiceUnloaded
EnitServiceRunning
EnitServiceStopped
EnitServiceCrashed
EnitServiceCompleted
)
type EnitService struct {
Name string `yaml:"name"`
Description string `yaml:"description,omitempty"`
Dependencies []string `yaml:"dependencies,omitempty"`
Type string `yaml:"type"`
StartCmd string `yaml:"start_cmd"`
ExitMethod string `yaml:"exit_method"`
CrashOnSafeExit bool `yaml:"crash_on_safe_exit"`
StopCmd string `yaml:"stop_cmd,omitempty"`
Restart string `yaml:"restart,omitempty"`
LogOutput bool `yaml:"log_output,omitempty"`
ServiceRunPath string
restartCount int
stopChannel chan bool
}
func (service *EnitService) GetUnmetDependencies() (missingDependencies []string) {
for _, dependency := range service.Dependencies {
if strings.HasPrefix(dependency, "/") {
// File dependency
if _, err := os.Stat(dependency); err != nil {
missingDependencies = append(missingDependencies, dependency)
}
} else {
// Service dependency
depService := GetServiceByName(dependency)
if depService == nil {
missingDependencies = append(missingDependencies, dependency)
}
}
}
return missingDependencies
}
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) GetLogFile() (file *os.File, err error) {
// Create esvm log directory
err = os.MkdirAll("/var/log/esvm", 0755)
if err != nil {
return nil, err
}
// Create esvm old log directory
err = os.MkdirAll("/var/log/esvm/old", 0755)
if err != nil {
return nil, err
}
// Move old log file
if _, err := os.Stat(path.Join("/var/log/esvm/", service.Name+".log")); err == nil {
os.Rename(path.Join("/var/log/esvm/", service.Name+".log"), path.Join("/var/log/esvm/old", service.Name+".log"))
}
// Open new log file
file, err = os.OpenFile(path.Join("/var/log/esvm/", service.Name+".log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return nil, err
}
// Initialize logger and print a header line
_, err = file.WriteString("------ " + time.Now().Format(time.UnixDate) + " ------\n")
if err != nil {
file.Close()
return nil, err
}
return file, nil
}
func (service *EnitService) StartService() error {
if service == nil {
return nil
}
if service.GetCurrentState() == EnitServiceRunning {
return nil
}
logger.Printf("Starting service (%s)...\n", service.Name)
// Get log file if service logs output
var logFile *os.File
if service.LogOutput {
var err error
logFile, err = service.GetLogFile()
if err != nil {
return err
}
}
cmd := exec.Command("/bin/sh", "-c", "exec "+service.StartCmd)
if logFile != nil {
cmd.Stdout = logFile
cmd.Stderr = logFile
}
if err := cmd.Start(); err != nil {
// Close log file if not nil
if logFile != nil {
logFile.Close()
}
return err
}
err := service.setProcessID(cmd.Process.Pid)
if err != nil {
// Close log file if not nil
if logFile != nil {
logFile.Close()
}
return err
}
err = service.setCurrentState(EnitServiceRunning)
if err != nil {
// Close log file if not nil
if logFile != nil {
logFile.Close()
}
return err
}
go func() {
err := cmd.Wait()
// Close log file if not nil
if logFile != nil {
logFile.Close()
}
select {
case <-service.stopChannel:
service.restartCount = 0
_ = service.setCurrentState(EnitServiceStopped)
default:
if service.Type == "simple" && err == nil {
service.restartCount = 0
_ = service.setCurrentState(EnitServiceCompleted)
return
}
if !service.CrashOnSafeExit {
logger.Printf("Service (%s) has exited\n", service.Name)
_ = service.setCurrentState(EnitServiceStopped)
} else {
logger.Printf("Service (%s) has crashed!\n", service.Name)
_ = service.setCurrentState(EnitServiceCrashed)
}
if service.Restart == "always" {
_ = service.StartService()
} else if service.Restart == "true" && service.restartCount < 5 {
service.restartCount++
_ = service.StartService()
}
}
}()
logger.Printf("Service (%s) has started!\n", service.Name)
return nil
}
func (service *EnitService) StopService() error {
if service.GetCurrentState() != EnitServiceRunning {
return nil
}
logger.Printf("Stopping service (%s)...\n", service.Name)
if service.ExitMethod == "kill" {
process := service.GetProcess()
if err := process.Signal(syscall.Signal(0)); err != nil {
logger.Printf("Service (%s) has stopped. (Process already dead)\n", service.Name)
return nil
}
go func() { service.stopChannel <- true }()
err := service.GetProcess().Signal(syscall.SIGTERM)
if err != nil {
return err
}
exit := false
for timeout := time.After(5 * time.Second); ; {
if exit {
break
}
select {
case <-timeout:
logger.Println("Process took too long to finish. Forcefully killing process...")
err := service.GetProcess().Kill()
if err != nil {
return err
}
exit = true
default:
if process == nil {
exit = true
break
}
err = process.Signal(syscall.Signal(0))
if err != nil {
exit = true
}
}
}
} 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
}
logger.Printf("Service (%s) has stopped!\n", service.Name)
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
}
+102
View File
@@ -0,0 +1,102 @@
package main
import (
"io"
"net"
"path"
"strings"
)
func initSocket() (socket net.Listener, err error) {
socket, err = net.Listen("unix", path.Join(runtimeServiceDir, "esvm.sock"))
if err != nil {
return nil, err
}
return socket, nil
}
func listenToSocket() {
conn, err := socket.Accept()
if err != nil {
logger.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 {
logger.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)
}