From e904beb65720b753078f49b05902ba2b0c87e5e0 Mon Sep 17 00:00:00 2001 From: Darius klein Date: Sun, 20 Jul 2025 00:47:03 +0200 Subject: [PATCH] Added template service launcher --- README.md | 6 ++ commands/templateCommand/commands.go | 9 ++- .../{template.go => baseTemplate.go} | 2 +- .../subcommands/startServiceTemplate.go | 23 +++++++ .../subcommands/stopServiceTemplate.go | 39 ++++++++++++ common/constants.go | 3 + common/files.go | 18 ++++++ common/interrupt.go | 17 ++++++ common/socketPaths.go | 12 ++++ go.work | 6 ++ services/example/go.mod | 3 + services/example/main.go | 60 +++++++++++++++++++ services/linuxServices.go | 10 ++++ services/services.go | 38 ++++++++++++ services/windowsServices.go | 10 ++++ 15 files changed, 252 insertions(+), 4 deletions(-) create mode 100644 README.md rename commands/templateCommand/subcommands/{template.go => baseTemplate.go} (96%) create mode 100644 commands/templateCommand/subcommands/startServiceTemplate.go create mode 100644 commands/templateCommand/subcommands/stopServiceTemplate.go create mode 100644 common/constants.go create mode 100644 common/files.go create mode 100644 common/interrupt.go create mode 100644 common/socketPaths.go create mode 100644 go.work create mode 100644 services/example/go.mod create mode 100644 services/example/main.go create mode 100644 services/linuxServices.go create mode 100644 services/services.go create mode 100644 services/windowsServices.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba41202 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +## windows +- GOOS=windows go generate ./services +- GOOS=windows go build . +## linux +- GOOS=linux go generate ./services +- GOOS=linux go build . \ No newline at end of file diff --git a/commands/templateCommand/commands.go b/commands/templateCommand/commands.go index 3e1095c..d081761 100644 --- a/commands/templateCommand/commands.go +++ b/commands/templateCommand/commands.go @@ -34,12 +34,15 @@ func Action(context context.Context, c *cli.Command) error { // PLACEHOLDER sub-category of CATEGORY NAME Category func subCategory() *cli.Command { return &cli.Command{ - Name: "sub-category-template", - Usage: "commands for sub-category-template", - Action: Action, + Name: "sub-category-template", + Aliases: []string{"category"}, + Usage: "commands for sub-category-template", + Action: Action, Commands: []*cli.Command{ //commands or sub-category here subcommands.Template(), + subcommands.StartServiceTemplate(), + subcommands.StopServiceTemplate(), }, HideHelpCommand: true, } diff --git a/commands/templateCommand/subcommands/template.go b/commands/templateCommand/subcommands/baseTemplate.go similarity index 96% rename from commands/templateCommand/subcommands/template.go rename to commands/templateCommand/subcommands/baseTemplate.go index 57afa0c..46555cd 100644 --- a/commands/templateCommand/subcommands/template.go +++ b/commands/templateCommand/subcommands/baseTemplate.go @@ -12,7 +12,7 @@ var templateVar bool // Template Command func Template() *cli.Command { return &cli.Command{ - Name: "template command", + Name: "basic", Usage: "template command usage", Action: templateAction, Flags: templateFlags(), diff --git a/commands/templateCommand/subcommands/startServiceTemplate.go b/commands/templateCommand/subcommands/startServiceTemplate.go new file mode 100644 index 0000000..f742ded --- /dev/null +++ b/commands/templateCommand/subcommands/startServiceTemplate.go @@ -0,0 +1,23 @@ +package subcommands + +import ( + "context" + "github.com/DariusKlein/kleinCommand/services" + "github.com/urfave/cli/v3" +) + +// StartServiceTemplate Command +func StartServiceTemplate() *cli.Command { + return &cli.Command{ + Name: "start service", + Aliases: []string{"start"}, + Usage: "template command usage", + Action: startServiceTemplateAction, + ArgsUsage: "args usage", + } +} + +// startServiceTemplateAction logic for StartServiceTemplate +func startServiceTemplateAction(context context.Context, c *cli.Command) error { + return services.RunExampleService() +} diff --git a/commands/templateCommand/subcommands/stopServiceTemplate.go b/commands/templateCommand/subcommands/stopServiceTemplate.go new file mode 100644 index 0000000..2d6a68e --- /dev/null +++ b/commands/templateCommand/subcommands/stopServiceTemplate.go @@ -0,0 +1,39 @@ +package subcommands + +import ( + "context" + "github.com/DariusKlein/kleinCommand/common" + "github.com/urfave/cli/v3" + "log/slog" + "net" +) + +// StopServiceTemplate Command +func StopServiceTemplate() *cli.Command { + return &cli.Command{ + Name: "stop service", + Aliases: []string{"stop"}, + Usage: "template command usage", + Action: stopServiceTemplateAction, + ArgsUsage: "args usage", + } +} + +// templateAction logic for Template +func stopServiceTemplateAction(context context.Context, c *cli.Command) error { + conn, err := net.Dial("unix", common.ExampleServiceSocketPath) + if err != nil { + slog.ErrorContext(context, err.Error()) + return err + } + defer conn.Close() + + // Send the shutdown command. + _, err = conn.Write([]byte("shutdown\n")) + if err != nil { + slog.ErrorContext(context, err.Error()) + return err + } + + return nil +} diff --git a/common/constants.go b/common/constants.go new file mode 100644 index 0000000..1e7569d --- /dev/null +++ b/common/constants.go @@ -0,0 +1,3 @@ +package common + +const ExampleServiceName = "exampleService" diff --git a/common/files.go b/common/files.go new file mode 100644 index 0000000..449c214 --- /dev/null +++ b/common/files.go @@ -0,0 +1,18 @@ +package common + +import ( + "os" +) + +func FileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} + +func DeleteSelf() { + path, _ := os.Executable() + os.Remove(path) +} diff --git a/common/interrupt.go b/common/interrupt.go new file mode 100644 index 0000000..c4d6530 --- /dev/null +++ b/common/interrupt.go @@ -0,0 +1,17 @@ +package common + +import ( + "os" + "os/signal" + "syscall" +) + +func CatchInterrupt(logic func()) { + cmd := make(chan os.Signal, 1) + signal.Notify(cmd, os.Interrupt, syscall.SIGTERM) + go func() { + <-cmd + logic() + os.Exit(0) + }() +} diff --git a/common/socketPaths.go b/common/socketPaths.go new file mode 100644 index 0000000..029fa4e --- /dev/null +++ b/common/socketPaths.go @@ -0,0 +1,12 @@ +package common + +import ( + "os" + "path/filepath" +) + +var ExampleServiceSocketPath = GetSocketPath(ExampleServiceName) + +func GetSocketPath(serviceName string) string { + return filepath.Join(os.TempDir(), serviceName+".sock") +} diff --git a/go.work b/go.work new file mode 100644 index 0000000..e0caca6 --- /dev/null +++ b/go.work @@ -0,0 +1,6 @@ +go 1.24.4 + +use ( + . + services/example +) diff --git a/services/example/go.mod b/services/example/go.mod new file mode 100644 index 0000000..edf06d5 --- /dev/null +++ b/services/example/go.mod @@ -0,0 +1,3 @@ +module exampleService + +go 1.24.4 diff --git a/services/example/main.go b/services/example/main.go new file mode 100644 index 0000000..9333b77 --- /dev/null +++ b/services/example/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "bufio" + "github.com/DariusKlein/kleinCommand/common" + "log" + "net" + "os" +) + +var socketPath = common.ExampleServiceSocketPath + +func main() { + common.CatchInterrupt(func() { + os.Remove(socketPath) + }) + + if common.FileExists(socketPath) { + log.Fatal("Socket file exists.") + } + + // Create the UNIX socket listener. + listener, err := net.Listen("unix", socketPath) + if err != nil { + log.Fatalf("Failed to listen on socket: %v", err) + } + + defer listener.Close() + + log.Println("Service started, listening on", socketPath) + + go func() { + conn, err := listener.Accept() + if err != nil { + log.Printf("Accept error: %v", err) + return + } + defer conn.Close() + + reader := bufio.NewReader(conn) + command, err := reader.ReadString('\n') + if err != nil { + log.Printf("Read error: %v", err) + return + } + + if command == "shutdown\n" { + log.Println("Shutdown command received, exiting.") + // This will cause the listener.Accept() to unblock and the program to exit. + listener.Close() + } + }() + + for { + if _, err := os.Stat(socketPath); os.IsNotExist(err) { + break + } + } + common.DeleteSelf() +} diff --git a/services/linuxServices.go b/services/linuxServices.go new file mode 100644 index 0000000..c85455a --- /dev/null +++ b/services/linuxServices.go @@ -0,0 +1,10 @@ +//go:build linux + +package services + +//go:generate go build ./example + +import _ "embed" + +//go:embed exampleService +var exampleService []byte diff --git a/services/services.go b/services/services.go new file mode 100644 index 0000000..233f890 --- /dev/null +++ b/services/services.go @@ -0,0 +1,38 @@ +package services + +import ( + "github.com/DariusKlein/kleinCommand/common" + "os" + "os/exec" + "syscall" +) + +func RunExampleService() error { + return runService(common.ExampleServiceName, exampleService) +} + +func runService(name string, file []byte) error { + tempFile, err := os.CreateTemp("", name) + if err != nil { + return err + } + + if _, err = tempFile.Write(file); err != nil { + tempFile.Close() + return err + } + if err = tempFile.Close(); err != nil { + return err + } + if err = os.Chmod(tempFile.Name(), 0777); err != nil { + return err + } + + cmd := exec.Command(tempFile.Name()) + cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} + if err = cmd.Start(); err != nil { + return err + } + + return nil +} diff --git a/services/windowsServices.go b/services/windowsServices.go new file mode 100644 index 0000000..c4b27c4 --- /dev/null +++ b/services/windowsServices.go @@ -0,0 +1,10 @@ +//go:build windows + +package services + +//go:generate go build ./example + +import _ "embed" + +//go:embed exampleService.exe +var exampleService []byte