Files
enit/src/esvm/main.go
T

269 lines
6.1 KiB
Go

package main
import (
"crypto/sha256"
"flag"
"fmt"
"io"
"log"
"maps"
"net"
"os"
"os/signal"
"path"
"slices"
"strings"
"syscall"
"time"
"gopkg.in/yaml.v3"
)
// Build-time variables
var version = "dev"
var runtimeServiceDir string
var serviceConfigDir string
var logger *log.Logger
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)
}
if os.Getppid() != 1 {
fmt.Println("Esvm must be run by PID 1!")
os.Exit(1)
}
// Setup main logger
err := setupESVMLogger()
if err != nil {
log.Printf("Error: could not setup main ESVM logger: %s\n", err)
logger = log.Default()
}
// Set directory variables
runtimeServiceDir = flag.Arg(0)
serviceConfigDir = flag.Arg(1)
Init()
if err != nil {
}
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigc
Destroy()
os.Exit(0)
}()
for {
listenToSocket()
}
}
func setupESVMLogger() error {
// Create esvm log directory
err := os.MkdirAll("/var/log/esvm", 0755)
if err != nil {
return err
}
// Create esvm old log directory
err = os.MkdirAll("/var/log/esvm/old", 0755)
if err != nil {
return err
}
// Move old log file
if _, err := os.Stat("/var/log/esvm/esvm.log"); err == nil {
os.Rename("/var/log/esvm/esvm.log", "/var/log/esvm/old/esvm.log")
}
// Open new log file
loggerFile, err := os.OpenFile("/var/log/esvm/esvm.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return err
}
// Setup multiwriter
w := io.MultiWriter(loggerFile, os.Stderr)
// Initialize logger and print a header line
logger = log.New(w, "[ESVM] ", log.Lshortfile|log.LstdFlags)
_, err = loggerFile.WriteString("------ " + time.Now().Format(time.UnixDate) + " ------\n")
return nil
}
func Init() {
logger.Println("Initializing ESVM...")
if _, err := os.Stat(runtimeServiceDir); err == nil {
logger.Fatalf("Error: could not initialize ESVM: %s", fmt.Errorf("runtime service directory %s already exists", runtimeServiceDir))
}
err := os.MkdirAll(runtimeServiceDir, 0755)
if err != nil {
logger.Fatalf("Error: could not initialize ESVM: %s", err)
}
socket, err = initSocket()
if err != nil {
logger.Fatalf("Error: could not initialize ESVM: %s", err)
}
if stat, err := os.Stat(serviceConfigDir); err != nil || !stat.IsDir() {
logger.Println("ESVM initialized successfully!")
return
}
dirEntries, err := os.ReadDir(path.Join(serviceConfigDir, "services"))
if err != nil {
logger.Fatalf("Error: Could not initialize ESVM: %s", err)
}
// Read and initialize service files
for _, entry := range dirEntries {
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".esv") {
logger.Printf("Initializing service (%s)...\n", entry.Name())
bytes, err := os.ReadFile(path.Join(serviceConfigDir, "services", entry.Name()))
if err != nil {
logger.Printf("Error: Could not read service file (%s)", path.Join(serviceConfigDir, "services", entry.Name()))
continue
}
service := EnitService{
Name: "",
Description: "",
Type: "",
StartCmd: "",
ExitMethod: "",
StopCmd: "",
Restart: "",
Setpgid: true,
CrashOnSafeExit: true,
LogOutput: true,
Filepath: path.Join(serviceConfigDir, "services", entry.Name()),
filepathChecksum: sha256.Sum256(bytes),
restartCount: 0,
stopChannel: make(chan bool),
state: EnitServiceUnloaded,
}
if err := yaml.Unmarshal(bytes, &service); err != nil {
logger.Printf("Error: could not read service file %s", path.Join(serviceConfigDir, "services", entry.Name()))
continue
}
for _, sv := range Services {
if sv.Name == service.Name {
logger.Printf("Error: service with name (%s) has already been initialized", service.Name)
}
}
switch service.Type {
case "simple", "background":
default:
logger.Printf("Error: unknown service type (%s)", service.Type)
continue
}
switch service.ExitMethod {
case "stop_command", "kill":
default:
logger.Printf("Error: unknown exit method (%s)\n", service.ExitMethod)
continue
}
switch service.Restart {
case "true", "always":
default:
service.Restart = "false"
}
Services = append(Services, &service)
logger.Printf("Service (%s) has been initialized!\n", service.Name)
}
}
// Read enabled services
EnabledServices := ReadEnabledServices()
// Start enabled services
stages := slices.Collect(maps.Keys(EnabledServices))
slices.Sort(stages)
for stage := 1; stage <= stages[len(stages)-1]; stage++ {
logger.Printf("Starting stage %d services...", stage)
services := EnabledServices[stage]
remainingServices := len(services)
for remainingServices != 0 {
for _, serviceName := range services {
service := GetServiceByName(serviceName)
if service == nil {
remainingServices--
continue
}
err := service.StartService()
if err != nil {
logger.Printf("Error: could not start service (%s): %s", service.Name, err)
}
remainingServices--
}
}
}
logger.Println("ESVM initialized successfully!")
}
func Reload() {
logger.Println("Reloading all ESVM services...")
for _, service := range Services {
service.ReloadService()
}
logger.Println("All ESVM services have been reloaded!")
}
func Destroy() {
logger.Println("Stopping all ESVM services...")
// Loop through all started services in reverse
for i := len(startedServicesOrder) - 1; i >= 0; i-- {
// Get service by name
service := GetServiceByName(startedServicesOrder[i])
if service == nil {
continue
}
// Stop service
if err := service.StopService(); err != nil {
logger.Printf("Error: could not stop service (%s): %s", service.Name, err)
}
}
logger.Println("All ESVM services have stopped!")
}
func GetServiceByName(name string) *EnitService {
for _, service := range Services {
if service.Name == name {
return service
}
}
return nil
}