Compare commits
No commits in common. "main" and "v1.0.1" have entirely different histories.
15
CHANGELOG-LATEST.md
Normal file
15
CHANGELOG-LATEST.md
Normal file
@ -0,0 +1,15 @@
|
||||
## v1.0.1 (2026-05-12)
|
||||
|
||||
### 🚀 Features
|
||||
- **Secure IPC**: SHA256 token-based authentication for Unix Domain Sockets.
|
||||
- **Precise Signaling**: `kill <svc_name> <signal_num>` command.
|
||||
- **Trace ID Propagation**: Shared Trace ID for service startup logs.
|
||||
- **Enhanced `status`**: Secured detailed health reporting.
|
||||
|
||||
### 🧹 Cleanup
|
||||
- **Minimalist API**: `Register`, `Run`, `AddCommand`, `SetAppInfo`.
|
||||
- **Automated PID**: System temp directory placement.
|
||||
|
||||
### 🛠 Improvements
|
||||
- **Race Condition Fix**: Delayed IPC server activation until startup completion.
|
||||
- **Infrastructure Alignment**: `cast.To[T]`, `timer.Retry`, and `id`.
|
||||
142
CODE-SUMMARY.md
Normal file
142
CODE-SUMMARY.md
Normal file
@ -0,0 +1,142 @@
|
||||
### starter > starter.go
|
||||
```go
|
||||
|
||||
|
||||
|
||||
var (
|
||||
// Default configuration
|
||||
appName = filepath.Base(os.Args[0])
|
||||
appVersion = "1.0.1"
|
||||
|
||||
// Internal state
|
||||
commands = make(map[string]*command)
|
||||
|
||||
// New Service registry
|
||||
services = make(map[int][]*managedService)
|
||||
startedPriorities []int
|
||||
|
||||
// Flags
|
||||
flagSet = flag.NewFlagSet(appName, flag.ContinueOnError)
|
||||
|
||||
// IPC Security
|
||||
ipcSecret = "apigo-starter-secret-2026"
|
||||
)
|
||||
|
||||
// Service defines the lifecycle of a component managed by the starter.
|
||||
type Service interface {
|
||||
// Start starts the service. It should block until the service is ready.
|
||||
Start(ctx context.Context, logger *log.Logger) error
|
||||
// Stop stops the service. It should block until the service is cleaned up.
|
||||
Stop(ctx context.Context) error
|
||||
// Health returns the health status of the service.
|
||||
Health() error
|
||||
}
|
||||
|
||||
// Reloader defines an optional interface for services that support configuration reloading.
|
||||
type Reloader interface {
|
||||
Reload() error
|
||||
}
|
||||
|
||||
// UserSignalHandler defines an optional interface for services that handle custom user signals.
|
||||
type UserSignalHandler interface {
|
||||
// HandleUserSignal handles a custom signal. Return true if the signal was handled.
|
||||
HandleUserSignal(sig os.Signal) bool
|
||||
}
|
||||
|
||||
type managedService struct {
|
||||
Name string
|
||||
svc Service
|
||||
priority int
|
||||
startTimeout time.Duration
|
||||
stopTimeout time.Duration
|
||||
}
|
||||
|
||||
type command struct {
|
||||
name string
|
||||
desc string
|
||||
fn func()
|
||||
}
|
||||
|
||||
func init()
|
||||
|
||||
|
||||
// Register adds a service to be managed by the starter.
|
||||
func Register(name string, svc Service, priority int, startTimeout, stopTimeout time.Duration)
|
||||
|
||||
|
||||
// SetAppInfo sets the application name and version.
|
||||
func SetAppInfo(name, version string)
|
||||
|
||||
|
||||
// AddCommand adds a custom command.
|
||||
func AddCommand(name, desc string, fn func())
|
||||
|
||||
|
||||
// Run parses arguments and executes the service.
|
||||
func Run()
|
||||
|
||||
|
||||
func showHelp()
|
||||
|
||||
|
||||
func runForeground()
|
||||
|
||||
|
||||
func startServices(ctx context.Context) error
|
||||
|
||||
|
||||
func stopServices()
|
||||
|
||||
|
||||
func reloadServices()
|
||||
|
||||
|
||||
func handleUserSignal(svcName *string, sig os.Signal) bool
|
||||
|
||||
|
||||
func serveIPC(l net.Listener)
|
||||
|
||||
|
||||
func getInternalStatus() string
|
||||
|
||||
|
||||
func startCmd()
|
||||
|
||||
|
||||
func stopCmd()
|
||||
|
||||
|
||||
func restartCmd()
|
||||
|
||||
|
||||
func statusCmd()
|
||||
|
||||
|
||||
func killCmd()
|
||||
|
||||
|
||||
func callIPC(pid int, cmd string) (string, error)
|
||||
|
||||
|
||||
func getIPCToken(pid int) string
|
||||
|
||||
|
||||
func getPidPath() string
|
||||
|
||||
|
||||
func getSockPath() string
|
||||
|
||||
|
||||
func savePid(p int)
|
||||
|
||||
|
||||
func loadPid() int
|
||||
|
||||
|
||||
func removePid()
|
||||
|
||||
|
||||
func isProcessRunning(p int) bool
|
||||
|
||||
```
|
||||
|
||||
@ -64,7 +64,6 @@ func main() {
|
||||
- **Trace ID Propagation**: Automatically generates a shared Trace ID for all services during startup, ensuring log correlation.
|
||||
- **Secure IPC**: Token-based Unix Domain Socket for `status` and `kill` commands.
|
||||
- **Zero Configuration PID**: Automatic PID management in the system temporary directory.
|
||||
- **Embedded Log Writer**: Automatically registered log writer service with high priority (-100).
|
||||
|
||||
## Commands
|
||||
|
||||
|
||||
24
go.mod
24
go.mod
@ -3,21 +3,11 @@ module apigo.cc/go/starter
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
apigo.cc/go/cast v1.3.3
|
||||
apigo.cc/go/crypto v1.3.1
|
||||
apigo.cc/go/file v1.3.2
|
||||
apigo.cc/go/id v1.3.1
|
||||
apigo.cc/go/log v1.3.4
|
||||
apigo.cc/go/shell v1.3.1
|
||||
apigo.cc/go/timer v1.3.1
|
||||
)
|
||||
|
||||
require (
|
||||
apigo.cc/go/config v1.3.1 // indirect
|
||||
apigo.cc/go/encoding v1.3.1 // indirect
|
||||
apigo.cc/go/rand v1.3.1 // indirect
|
||||
apigo.cc/go/safe v1.3.1 // indirect
|
||||
golang.org/x/crypto v0.51.0 // indirect
|
||||
golang.org/x/sys v0.44.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
apigo.cc/go/cast v1.3.0
|
||||
apigo.cc/go/crypto v1.3.0
|
||||
apigo.cc/go/file v1.3.0
|
||||
apigo.cc/go/id v1.3.0
|
||||
apigo.cc/go/log v1.3.0
|
||||
apigo.cc/go/shell v1.3.0
|
||||
apigo.cc/go/timer v1.3.0
|
||||
)
|
||||
|
||||
37
go.sum
37
go.sum
@ -1,37 +0,0 @@
|
||||
apigo.cc/go/cast v1.3.3 h1:aln5eDR5DZVWVzZ/y5SJh1gQNgWv2sT82I25NaO9g34=
|
||||
apigo.cc/go/cast v1.3.3/go.mod h1:lGlwImiOvHxG7buyMWhFzcdvQzmSaoKbmr7bcDfUpHk=
|
||||
apigo.cc/go/config v1.3.1 h1:wZzUh4oL+fGD6SayVgX6prLPMsniM25etWFcEH8XzIE=
|
||||
apigo.cc/go/config v1.3.1/go.mod h1:7KHz/1WmtBLM762Lln/TaXh2dmlMvJTLhnlk33zbS3U=
|
||||
apigo.cc/go/crypto v1.3.1 h1:ulQ2zX9bUWirk0sEacx1Srsjs2Jow7HlZq7ED7msNcg=
|
||||
apigo.cc/go/crypto v1.3.1/go.mod h1:SwHlBFDPddttWgFFtzsEMla8CM/rcFy9nvdsJjW4CIs=
|
||||
apigo.cc/go/encoding v1.3.1 h1:y8O58KYAyulkThg1O2ji2BqjnFoSvk42sit9I3z+K7Y=
|
||||
apigo.cc/go/encoding v1.3.1/go.mod h1:xAJk5b83VZ31mXMTnyp0dfMoBKfT/AHDn0u+cQfojgY=
|
||||
apigo.cc/go/file v1.3.2 h1:pu4oiDyiqgj3/eykfnJf+/6+A9v/Z0b3ClP5XK+lwG4=
|
||||
apigo.cc/go/file v1.3.2/go.mod h1:vci4h0Pz94mV6dkniQkuyBYERVYeq7/LX4jJVuCg9hs=
|
||||
apigo.cc/go/id v1.3.1 h1:pkqi6VeWyQoHuIu0Zbx/RRxIAdM61Js0j6cY1M9XVCk=
|
||||
apigo.cc/go/id v1.3.1/go.mod h1:P2/vl3tyW3US+ayOFSMoPIOCulNLBngNYPhXJC/Z7J4=
|
||||
apigo.cc/go/log v1.3.4 h1:UT8Neb9r4QjjbCFbTzw+ZeTxd+DmdmR5gNExeR4Cj+g=
|
||||
apigo.cc/go/log v1.3.4/go.mod h1:/Q/2r51xWSsrS4QN5U9jLiTw8n6qNC8kG9nuVHweY20=
|
||||
apigo.cc/go/rand v1.3.1 h1:7FvsI6PtQ5XrWER0dTiLVo0p7GIxRidT/TBKhVy93j8=
|
||||
apigo.cc/go/rand v1.3.1/go.mod h1:mZ/4Soa3bk+XvDaqPWJuUe1bfEi4eThBj1XmEAuYxsk=
|
||||
apigo.cc/go/safe v1.3.1 h1:irTCqPAC97gGsX/Lw5AzLelDt1xXLEZIAaVhLELWe9Q=
|
||||
apigo.cc/go/safe v1.3.1/go.mod h1:XdOpBhN2vkImalaykYXXmEpczqWa1y3ah6/Q72cdRqE=
|
||||
apigo.cc/go/shell v1.3.1 h1:M8oD0b2HcJuCC6frQFx11b3UTcTx3lATX8XK+YXSVm8=
|
||||
apigo.cc/go/shell v1.3.1/go.mod h1:ZMdJjpCpWdvsHKUXlelh/AxsV/nWdkH/k3lISfzMdUw=
|
||||
apigo.cc/go/timer v1.3.1 h1:YMSusF1LfJYOf6tAW94Yipj3pHrX6QhfP7Rk3nGFT8k=
|
||||
apigo.cc/go/timer v1.3.1/go.mod h1:kOnqTTX+zA4AH7SfC+LpUm4ZvS+DVyWWMqul/V5QWJs=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
28
service.go
28
service.go
@ -1,28 +0,0 @@
|
||||
package starter
|
||||
|
||||
import (
|
||||
"apigo.cc/go/log"
|
||||
"context"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Service defines the lifecycle of a component managed by the starter.
|
||||
type Service interface {
|
||||
// Start starts the service. It should block until the service is ready.
|
||||
Start(ctx context.Context, logger *log.Logger) error
|
||||
// Stop stops the service. It should block until the service is cleaned up.
|
||||
Stop(ctx context.Context) error
|
||||
// Status returns the current status and health of the service.
|
||||
Status() (string, error)
|
||||
}
|
||||
|
||||
// Reloader defines an optional interface for services that support configuration reloading.
|
||||
type Reloader interface {
|
||||
Reload() error
|
||||
}
|
||||
|
||||
// UserSignalHandler defines an optional interface for services that handle custom user signals.
|
||||
type UserSignalHandler interface {
|
||||
// HandleUserSignal handles a custom signal. Return true if the signal was handled.
|
||||
HandleUserSignal(sig os.Signal) bool
|
||||
}
|
||||
@ -77,11 +77,8 @@ func (s *mockService) Stop(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *mockService) Status() (string, error) {
|
||||
if s.healthErr != nil {
|
||||
return "unhealthy", s.healthErr
|
||||
}
|
||||
return "ok", nil
|
||||
func (s *mockService) Health() error {
|
||||
return s.healthErr
|
||||
}
|
||||
|
||||
func (s *mockService) Reload() error {
|
||||
|
||||
60
starter.go
60
starter.go
@ -29,7 +29,6 @@ var (
|
||||
// Default configuration
|
||||
appName = filepath.Base(os.Args[0])
|
||||
appVersion = "1.0.1"
|
||||
appUsage = ""
|
||||
|
||||
// Internal state
|
||||
commands = make(map[string]*command)
|
||||
@ -45,6 +44,27 @@ var (
|
||||
ipcSecret = "apigo-starter-secret-2026"
|
||||
)
|
||||
|
||||
// Service defines the lifecycle of a component managed by the starter.
|
||||
type Service interface {
|
||||
// Start starts the service. It should block until the service is ready.
|
||||
Start(ctx context.Context, logger *log.Logger) error
|
||||
// Stop stops the service. It should block until the service is cleaned up.
|
||||
Stop(ctx context.Context) error
|
||||
// Health returns the health status of the service.
|
||||
Health() error
|
||||
}
|
||||
|
||||
// Reloader defines an optional interface for services that support configuration reloading.
|
||||
type Reloader interface {
|
||||
Reload() error
|
||||
}
|
||||
|
||||
// UserSignalHandler defines an optional interface for services that handle custom user signals.
|
||||
type UserSignalHandler interface {
|
||||
// HandleUserSignal handles a custom signal. Return true if the signal was handled.
|
||||
HandleUserSignal(sig os.Signal) bool
|
||||
}
|
||||
|
||||
type managedService struct {
|
||||
Name string
|
||||
svc Service
|
||||
@ -65,9 +85,6 @@ func init() {
|
||||
AddCommand("restart", "Restart the service", restartCmd)
|
||||
AddCommand("status", "Show service status", statusCmd)
|
||||
AddCommand("kill", "Send signal to a specific service: kill <svc_name> <signal_num>", killCmd)
|
||||
|
||||
// Auto-register log writer service with high priority
|
||||
Register("log-writer", log.WriterService, -100, 0, 0)
|
||||
}
|
||||
|
||||
// Register adds a service to be managed by the starter.
|
||||
@ -87,26 +104,11 @@ func SetAppInfo(name, version string) {
|
||||
appVersion = version
|
||||
}
|
||||
|
||||
// SetInfo is an alias for SetAppInfo.
|
||||
func SetInfo(name, version string) {
|
||||
SetAppInfo(name, version)
|
||||
}
|
||||
|
||||
// SetUsage sets custom usage text to be displayed in help.
|
||||
func SetUsage(text string) {
|
||||
appUsage = text
|
||||
}
|
||||
|
||||
// AddCommand adds a custom command.
|
||||
func AddCommand(name, desc string, fn func()) {
|
||||
commands[name] = &command{name: name, desc: desc, fn: fn}
|
||||
}
|
||||
|
||||
// AddCmd is an alias for AddCommand.
|
||||
func AddCmd(name, desc string, fn func()) {
|
||||
AddCommand(name, desc, fn)
|
||||
}
|
||||
|
||||
// Run parses arguments and executes the service.
|
||||
func Run() {
|
||||
flagSet.Usage = showHelp
|
||||
@ -136,11 +138,8 @@ func Run() {
|
||||
}
|
||||
|
||||
func showHelp() {
|
||||
fmt.Printf("%s (%s)\n\n", appName, appVersion)
|
||||
if appUsage != "" {
|
||||
fmt.Printf("%s\n\n", appUsage)
|
||||
}
|
||||
fmt.Printf("Usage:\n %s [command] [options]\n\nCommands:\n", filepath.Base(os.Args[0]))
|
||||
fmt.Printf("%s (%s)\n\nUsage:\n %s [command] [options]\n\nCommands:\n",
|
||||
appName, appVersion, filepath.Base(os.Args[0]))
|
||||
|
||||
var names []string
|
||||
for cmdName := range commands {
|
||||
@ -377,16 +376,11 @@ func getInternalStatus() string {
|
||||
|
||||
for _, p := range priorities {
|
||||
for _, ms := range services[p] {
|
||||
statusMsg, err := ms.svc.Status()
|
||||
indicator := shell.Green("OK")
|
||||
if err != nil {
|
||||
indicator = shell.Red(fmt.Sprintf("FAIL (%v)", err))
|
||||
}
|
||||
if statusMsg != "" {
|
||||
out += fmt.Sprintf("[%d] %-20s %s (%s)\n", p, ms.Name, indicator, statusMsg)
|
||||
} else {
|
||||
out += fmt.Sprintf("[%d] %-20s %s\n", p, ms.Name, indicator)
|
||||
status := shell.Green("OK")
|
||||
if err := ms.svc.Health(); err != nil {
|
||||
status = shell.Red(fmt.Sprintf("FAIL (%v)", err))
|
||||
}
|
||||
out += fmt.Sprintf("[%d] %-20s %s\n", p, ms.Name, status)
|
||||
}
|
||||
}
|
||||
return out
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user