Added template service launcher

This commit is contained in:
Darius klein 2025-07-20 00:47:03 +02:00
parent 8c2eebc438
commit e904beb657
15 changed files with 252 additions and 4 deletions

6
README.md Normal file
View File

@ -0,0 +1,6 @@
## windows
- GOOS=windows go generate ./services
- GOOS=windows go build .
## linux
- GOOS=linux go generate ./services
- GOOS=linux go build .

View File

@ -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,
}

View File

@ -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(),

View File

@ -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()
}

View File

@ -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
}

3
common/constants.go Normal file
View File

@ -0,0 +1,3 @@
package common
const ExampleServiceName = "exampleService"

18
common/files.go Normal file
View File

@ -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)
}

17
common/interrupt.go Normal file
View File

@ -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)
}()
}

12
common/socketPaths.go Normal file
View File

@ -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")
}

6
go.work Normal file
View File

@ -0,0 +1,6 @@
go 1.24.4
use (
.
services/example
)

3
services/example/go.mod Normal file
View File

@ -0,0 +1,3 @@
module exampleService
go 1.24.4

60
services/example/main.go Normal file
View File

@ -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()
}

10
services/linuxServices.go Normal file
View File

@ -0,0 +1,10 @@
//go:build linux
package services
//go:generate go build ./example
import _ "embed"
//go:embed exampleService
var exampleService []byte

38
services/services.go Normal file
View File

@ -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
}

View File

@ -0,0 +1,10 @@
//go:build windows
package services
//go:generate go build ./example
import _ "embed"
//go:embed exampleService.exe
var exampleService []byte