Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55c3f60d1c | ||
|
|
5002b1d9fa | ||
|
|
904f2b29d2 |
21
CHANGELOG.md
21
CHANGELOG.md
@ -1,5 +1,26 @@
|
|||||||
# Changelog: @go/starter
|
# Changelog: @go/starter
|
||||||
|
|
||||||
|
## v1.5.3 (2026-06-07)
|
||||||
|
- **重大架构更迭: 引入 Start/Wait 编排模式与生命周期钩子**:
|
||||||
|
- **废弃 `Run()`**: 彻底移除黑盒式的 `Run()` 方法,改为显式的 `Start()` (解析与非阻塞启动) 与 `Wait()` (阻塞与信号处理) 模式。
|
||||||
|
- **生命周期钩子**: `Register` 现返回 `*managedService` 指针,支持链式注册 `OnStarting`, `OnStarted`, `OnStopping`, `OnStopped` 回调。
|
||||||
|
- **中间状态编排**: 允许开发者在 `Start()` 之后、`Wait()` 之前执行自定义初始化逻辑(如跨服务的业务对齐)。
|
||||||
|
|
||||||
|
## v1.5.2 (2026-06-05)
|
||||||
|
- **可观测性升级: 中心化生命周期审计与 TraceID 链路追踪**:
|
||||||
|
- **TraceID 分层**: 为 `starter` 编排层引入了独立的长短 ID 体系。
|
||||||
|
- Starter 级(8位短 ID):代表整个启动/停止批次的编排上下文。
|
||||||
|
- 服务级(10位长 ID):自动派发给每个子服务的 `Start` 方法,确保服务内部日志能与 Starter 状态精准关联。
|
||||||
|
- **状态聚合**: 所有的 `service [name] starting / started / stopping / stopped` 日志现由 `starter` 统一负责,并显式记录跨层级的 `trace` ID。
|
||||||
|
- **语义统一**: 所有 Starter 核心消息增加 `[starter]` 前缀标识。
|
||||||
|
- **架构清理**: 移除了内部对 `log-writer` 的自动注册逻辑,将核心组件的启停权责彻底交还给应用开发者。
|
||||||
|
|
||||||
|
## v1.5.1 (2026-06-04)
|
||||||
|
- **依赖升级**: 全面对接 `@go` 基础设施 v1.5.1。
|
||||||
|
|
||||||
|
## v1.5.0 (2026-05-10)
|
||||||
|
- **基础设施对齐**: 全局对齐至 v1.5.0。
|
||||||
|
|
||||||
## v1.0.1 (2026-05-12)
|
## v1.0.1 (2026-05-12)
|
||||||
|
|
||||||
### 🚀 Features
|
### 🚀 Features
|
||||||
|
|||||||
18
README.md
18
README.md
@ -45,21 +45,31 @@ func (s *MyService) Reload() error {
|
|||||||
func main() {
|
func main() {
|
||||||
starter.SetAppInfo("myapp", "1.0.0")
|
starter.SetAppInfo("myapp", "1.0.0")
|
||||||
|
|
||||||
// Register with priority 1
|
// Register with priority 1 and optional hooks
|
||||||
starter.Register("myservice", &MyService{}, 1, 0, 0)
|
starter.Register("myservice", &MyService{}, 1, 0, 0).OnStarted(func() {
|
||||||
|
fmt.Println("MyService is ready!")
|
||||||
|
})
|
||||||
|
|
||||||
starter.Run()
|
if err := starter.Start(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom initialization logic here
|
||||||
|
|
||||||
|
starter.Wait()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Interfaces
|
## Interfaces
|
||||||
|
|
||||||
- **`Service`**: Core lifecycle interface (`Start`, `Stop`, `Health`).
|
- **`Service`**: Core lifecycle interface (`Start`, `Stop`, `Status`).
|
||||||
- **`Reloader`**: Optional interface for services that support `Reload()` (triggered by `SIGHUP`).
|
- **`Reloader`**: Optional interface for services that support `Reload()` (triggered by `SIGHUP`).
|
||||||
- **`UserSignalHandler`**: Optional interface for services that handle custom user signals (`SIGUSR1`, `SIGUSR2` or via `kill` command).
|
- **`UserSignalHandler`**: Optional interface for services that handle custom user signals (`SIGUSR1`, `SIGUSR2` or via `kill` command).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
- **Lifecycle Hooks**: `OnStarting`, `OnStarted`, `OnStopping`, `OnStopped` hooks for precise coordination.
|
||||||
|
- **Start/Wait Pattern**: Replaces `Run()` to allow custom logic between service startup and blocking.
|
||||||
- **Tiered Startup/Shutdown**: Concurrent execution within priority levels, serial execution across them.
|
- **Tiered Startup/Shutdown**: Concurrent execution within priority levels, serial execution across them.
|
||||||
- **Trace ID Propagation**: Automatically generates a shared Trace ID for all services during startup, ensuring log correlation.
|
- **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.
|
- **Secure IPC**: Token-based Unix Domain Socket for `status` and `kill` commands.
|
||||||
|
|||||||
15
go.mod
15
go.mod
@ -3,23 +3,22 @@ module apigo.cc/go/starter
|
|||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
apigo.cc/go/cast v1.5.0
|
|
||||||
apigo.cc/go/crypto v1.5.0
|
apigo.cc/go/crypto v1.5.0
|
||||||
apigo.cc/go/file v1.5.0
|
|
||||||
apigo.cc/go/id v1.5.0
|
apigo.cc/go/id v1.5.0
|
||||||
apigo.cc/go/log v1.5.0
|
apigo.cc/go/log v1.5.5
|
||||||
apigo.cc/go/shell v1.5.0
|
|
||||||
apigo.cc/go/timer v1.5.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require apigo.cc/go/jsmod v1.5.0 // indirect
|
require apigo.cc/go/jsmod v1.5.0 // indirect
|
||||||
|
|
||||||
require (
|
require (
|
||||||
apigo.cc/go/config v1.5.0 // indirect
|
apigo.cc/go/cast v1.5.0 // indirect
|
||||||
|
apigo.cc/go/config v1.5.1 // indirect
|
||||||
apigo.cc/go/encoding v1.5.0 // indirect
|
apigo.cc/go/encoding v1.5.0 // indirect
|
||||||
|
apigo.cc/go/file v1.5.0 // indirect
|
||||||
apigo.cc/go/rand v1.5.0 // indirect
|
apigo.cc/go/rand v1.5.0 // indirect
|
||||||
apigo.cc/go/safe v1.5.0 // indirect
|
apigo.cc/go/safe v1.5.0 // indirect
|
||||||
golang.org/x/crypto v0.51.0 // indirect
|
apigo.cc/go/shell v1.5.0 // indirect
|
||||||
golang.org/x/sys v0.44.0 // indirect
|
golang.org/x/crypto v0.52.0 // indirect
|
||||||
|
golang.org/x/sys v0.45.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
17
go.sum
17
go.sum
@ -1,7 +1,7 @@
|
|||||||
apigo.cc/go/cast v1.5.0 h1:UBGJtFQ8eJPMQXs37cUgqd7YQo1zI9opuSDBDmn2/pE=
|
apigo.cc/go/cast v1.5.0 h1:UBGJtFQ8eJPMQXs37cUgqd7YQo1zI9opuSDBDmn2/pE=
|
||||||
apigo.cc/go/cast v1.5.0/go.mod h1:z2GW5p5WCZGEqVVIJUdhl232vRbLf2Qu4EDlEakX/D8=
|
apigo.cc/go/cast v1.5.0/go.mod h1:z2GW5p5WCZGEqVVIJUdhl232vRbLf2Qu4EDlEakX/D8=
|
||||||
apigo.cc/go/config v1.5.0 h1:Yuz9QEb11XXG4XkhDi/ueT2M1T3Q9PElE5tiakvjehs=
|
apigo.cc/go/config v1.5.1 h1:rpj7oCzlsDV3f2/YK3Pb+CHbfr2DL5Vyyv6VNkobJP4=
|
||||||
apigo.cc/go/config v1.5.0/go.mod h1:jdMiDLPa9gzB8/FFZvm9jOopUqdxb7XSX+0OeWcZZUM=
|
apigo.cc/go/config v1.5.1/go.mod h1:jdMiDLPa9gzB8/FFZvm9jOopUqdxb7XSX+0OeWcZZUM=
|
||||||
apigo.cc/go/crypto v1.5.0 h1:Nxz7a6VKCdvaF258IU0NkjQyureOLxfR308Sy2iftUI=
|
apigo.cc/go/crypto v1.5.0 h1:Nxz7a6VKCdvaF258IU0NkjQyureOLxfR308Sy2iftUI=
|
||||||
apigo.cc/go/crypto v1.5.0/go.mod h1:F9M6nXv+5328r1ZwbTvI6fcr8VdgqHVzALOcsdv6ntE=
|
apigo.cc/go/crypto v1.5.0/go.mod h1:F9M6nXv+5328r1ZwbTvI6fcr8VdgqHVzALOcsdv6ntE=
|
||||||
apigo.cc/go/encoding v1.5.0 h1:EJNdRVDOMoI2DAvZwQNQTbYuqB/6zsEzvg7lS5pQI+I=
|
apigo.cc/go/encoding v1.5.0 h1:EJNdRVDOMoI2DAvZwQNQTbYuqB/6zsEzvg7lS5pQI+I=
|
||||||
@ -12,26 +12,23 @@ apigo.cc/go/id v1.5.0 h1:MjNWPhBhDsoXaLeJDv/0wfJmVMU9EvOs8pWYfsTQ6e8=
|
|||||||
apigo.cc/go/id v1.5.0/go.mod h1:qhu4a1/KLc/XcBpcsRu+mXZt7U7Wvd9zMcPs4VspuPA=
|
apigo.cc/go/id v1.5.0/go.mod h1:qhu4a1/KLc/XcBpcsRu+mXZt7U7Wvd9zMcPs4VspuPA=
|
||||||
apigo.cc/go/jsmod v1.5.0 h1:JgQtJNiJWy1NOP9AzE8NX5VXJkpO/x3GqLsCCSny5Ec=
|
apigo.cc/go/jsmod v1.5.0 h1:JgQtJNiJWy1NOP9AzE8NX5VXJkpO/x3GqLsCCSny5Ec=
|
||||||
apigo.cc/go/jsmod v1.5.0/go.mod h1:bmyeZtOAP/j5am+YRnaiM89smysK24K7ebk0koFtsSw=
|
apigo.cc/go/jsmod v1.5.0/go.mod h1:bmyeZtOAP/j5am+YRnaiM89smysK24K7ebk0koFtsSw=
|
||||||
apigo.cc/go/log v1.5.0 h1:kQuLLtbt33mEuc/xJVcy8NODXkso/QKSZWNclKrSpsI=
|
apigo.cc/go/log v1.5.5 h1:AFU7d7AQxkpgDHl7SnlEwd6yzGSFAlnrrjbrNDQnQHI=
|
||||||
apigo.cc/go/log v1.5.0/go.mod h1:Djy+I5aLhGB/EjwRz4KHqkVEz584IAD55FAFiIfInuo=
|
|
||||||
apigo.cc/go/rand v1.5.0 h1:1o8hh8fhdBuk1/h02IvugvamuT3dkWbVJrqEJVQKB2E=
|
apigo.cc/go/rand v1.5.0 h1:1o8hh8fhdBuk1/h02IvugvamuT3dkWbVJrqEJVQKB2E=
|
||||||
apigo.cc/go/rand v1.5.0/go.mod h1:Lh98S2dm9UY0X+M+kNQQEKyXHG5pcCKSFPyXN0QCGdk=
|
apigo.cc/go/rand v1.5.0/go.mod h1:Lh98S2dm9UY0X+M+kNQQEKyXHG5pcCKSFPyXN0QCGdk=
|
||||||
apigo.cc/go/safe v1.5.0 h1:W1NblmcU8cex1f9Y5z8mNLUJOzZTE1s6fszb3FbhGnk=
|
apigo.cc/go/safe v1.5.0 h1:W1NblmcU8cex1f9Y5z8mNLUJOzZTE1s6fszb3FbhGnk=
|
||||||
apigo.cc/go/safe v1.5.0/go.mod h1:OfQ5d6COePSGEuPvMeOk6KagX2sezw7nvKh7exj9SeM=
|
apigo.cc/go/safe v1.5.0/go.mod h1:OfQ5d6COePSGEuPvMeOk6KagX2sezw7nvKh7exj9SeM=
|
||||||
apigo.cc/go/shell v1.5.0 h1:WLDMMqUU0INeaBDmQsTPr0h/NfB2RknAtiJ5NL467+Q=
|
apigo.cc/go/shell v1.5.0 h1:WLDMMqUU0INeaBDmQsTPr0h/NfB2RknAtiJ5NL467+Q=
|
||||||
apigo.cc/go/shell v1.5.0/go.mod h1:rYHA77d5hEsQHcJrbAWf1pHy0sxayeJ0gU55LA/JWQk=
|
apigo.cc/go/shell v1.5.0/go.mod h1:rYHA77d5hEsQHcJrbAWf1pHy0sxayeJ0gU55LA/JWQk=
|
||||||
apigo.cc/go/timer v1.5.0 h1:iPo/IQn+iuhBRI1/MR1txwZnamef/RBBfOiIlBiqkgk=
|
|
||||||
apigo.cc/go/timer v1.5.0/go.mod h1:kOnqTTX+zA4AH7SfC+LpUm4ZvS+DVyWWMqul/V5QWJs=
|
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
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 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
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.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|||||||
390
starter.go
390
starter.go
@ -11,18 +11,13 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"apigo.cc/go/cast"
|
|
||||||
"apigo.cc/go/crypto"
|
"apigo.cc/go/crypto"
|
||||||
"apigo.cc/go/file"
|
|
||||||
"apigo.cc/go/id"
|
"apigo.cc/go/id"
|
||||||
"apigo.cc/go/log"
|
"apigo.cc/go/log"
|
||||||
"apigo.cc/go/shell"
|
|
||||||
"apigo.cc/go/timer"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -43,6 +38,13 @@ var (
|
|||||||
|
|
||||||
// IPC Security
|
// IPC Security
|
||||||
ipcSecret = "apigo-starter-secret-2026"
|
ipcSecret = "apigo-starter-secret-2026"
|
||||||
|
|
||||||
|
// Starter Logger
|
||||||
|
starterLogger *log.Logger
|
||||||
|
|
||||||
|
// Signal handling
|
||||||
|
sigs chan os.Signal
|
||||||
|
cancelCtx context.CancelFunc
|
||||||
)
|
)
|
||||||
|
|
||||||
type managedService struct {
|
type managedService struct {
|
||||||
@ -51,6 +53,30 @@ type managedService struct {
|
|||||||
priority int
|
priority int
|
||||||
startTimeout time.Duration
|
startTimeout time.Duration
|
||||||
stopTimeout time.Duration
|
stopTimeout time.Duration
|
||||||
|
onStarting []func()
|
||||||
|
onStarted []func()
|
||||||
|
onStopping []func()
|
||||||
|
onStopped []func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *managedService) OnStarting(fn func()) *managedService {
|
||||||
|
ms.onStarting = append(ms.onStarting, fn)
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *managedService) OnStarted(fn func()) *managedService {
|
||||||
|
ms.onStarted = append(ms.onStarted, fn)
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *managedService) OnStopping(fn func()) *managedService {
|
||||||
|
ms.onStopping = append(ms.onStopping, fn)
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *managedService) OnStopped(fn func()) *managedService {
|
||||||
|
ms.onStopped = append(ms.onStopped, fn)
|
||||||
|
return ms
|
||||||
}
|
}
|
||||||
|
|
||||||
type command struct {
|
type command struct {
|
||||||
@ -65,20 +91,27 @@ func init() {
|
|||||||
AddCommand("restart", "Restart the service", restartCmd)
|
AddCommand("restart", "Restart the service", restartCmd)
|
||||||
AddCommand("status", "Show service status", statusCmd)
|
AddCommand("status", "Show service status", statusCmd)
|
||||||
AddCommand("kill", "Send signal to a specific service: kill <svc_name> <signal_num>", killCmd)
|
AddCommand("kill", "Send signal to a specific service: kill <svc_name> <signal_num>", killCmd)
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-register log writer service with high priority
|
// getStarterLogger returns the singleton logger for the starter context.
|
||||||
Register("log-writer", log.WriterService, -100, 0, 0)
|
func getStarterLogger() *log.Logger {
|
||||||
|
if starterLogger == nil {
|
||||||
|
starterLogger = log.DefaultLogger.New(id.Get8Bytes4KPerSecond())
|
||||||
|
}
|
||||||
|
return starterLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register adds a service to be managed by the starter.
|
// Register adds a service to be managed by the starter.
|
||||||
func Register(name string, svc Service, priority int, startTimeout, stopTimeout time.Duration) {
|
func Register(name string, svc Service, priority int, startTimeout, stopTimeout time.Duration) *managedService {
|
||||||
services[priority] = append(services[priority], &managedService{
|
ms := &managedService{
|
||||||
Name: name,
|
Name: name,
|
||||||
svc: svc,
|
svc: svc,
|
||||||
priority: priority,
|
priority: priority,
|
||||||
startTimeout: startTimeout,
|
startTimeout: startTimeout,
|
||||||
stopTimeout: stopTimeout,
|
stopTimeout: stopTimeout,
|
||||||
})
|
}
|
||||||
|
services[priority] = append(services[priority], ms)
|
||||||
|
return ms
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAppInfo sets the application name and version.
|
// SetAppInfo sets the application name and version.
|
||||||
@ -87,11 +120,6 @@ func SetAppInfo(name, version string) {
|
|||||||
appVersion = version
|
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.
|
// SetUsage sets custom usage text to be displayed in help.
|
||||||
func SetUsage(text string) {
|
func SetUsage(text string) {
|
||||||
appUsage = text
|
appUsage = text
|
||||||
@ -102,13 +130,10 @@ func AddCommand(name, desc string, fn func()) {
|
|||||||
commands[name] = &command{name: name, desc: desc, fn: fn}
|
commands[name] = &command{name: name, desc: desc, fn: fn}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCmd is an alias for AddCommand.
|
// TODO 使用 Start / Wait 代替 Run,方便在启动后做初始化操作,或者支持注册某服务的 OnStarting / OnStarted / OnStopping / OnStopped 事件
|
||||||
func AddCmd(name, desc string, fn func()) {
|
|
||||||
AddCommand(name, desc, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run parses arguments and executes the service.
|
// Start parses arguments and starts the services.
|
||||||
func Run() {
|
func Start() error {
|
||||||
flagSet.Usage = showHelp
|
flagSet.Usage = showHelp
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
arg := os.Args[1]
|
arg := os.Args[1]
|
||||||
@ -116,87 +141,46 @@ func Run() {
|
|||||||
// Subcommand detected, parse flags after the command
|
// Subcommand detected, parse flags after the command
|
||||||
_ = flagSet.Parse(os.Args[2:])
|
_ = flagSet.Parse(os.Args[2:])
|
||||||
cmd.fn()
|
cmd.fn()
|
||||||
return
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for help/version
|
// Check for help/version
|
||||||
switch arg {
|
switch arg {
|
||||||
case "-h", "--help", "help":
|
case "-h", "--help", "help":
|
||||||
showHelp()
|
showHelp()
|
||||||
return
|
os.Exit(0)
|
||||||
case "-v", "--version", "version":
|
case "-v", "--version", "version":
|
||||||
fmt.Printf("%s version %s\n", appName, appVersion)
|
fmt.Printf("%s version %s\n", appName, appVersion)
|
||||||
return
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No starter command, treat all as app flags
|
// No starter command, treat all as app flags
|
||||||
_ = flagSet.Parse(os.Args[1:])
|
_ = flagSet.Parse(os.Args[1:])
|
||||||
runForeground()
|
|
||||||
}
|
|
||||||
|
|
||||||
func showHelp() {
|
// Setup signal handling for graceful shutdown
|
||||||
fmt.Printf("%s (%s)\n\n", appName, appVersion)
|
sigs = make(chan os.Signal, 1)
|
||||||
if appUsage != "" {
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGUSR1, syscall.SIGUSR2)
|
||||||
fmt.Printf("%s\n\n", appUsage)
|
|
||||||
}
|
|
||||||
fmt.Printf("Usage:\n %s [command] [options]\n\nCommands:\n", filepath.Base(os.Args[0]))
|
|
||||||
|
|
||||||
var names []string
|
var ctx context.Context
|
||||||
for cmdName := range commands {
|
ctx, cancelCtx = context.WithCancel(context.Background())
|
||||||
names = append(names, cmdName)
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
|
|
||||||
for _, cmdName := range names {
|
|
||||||
fmt.Printf(" %-10s %s\n", cmdName, commands[cmdName].desc)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("\nOptions:")
|
|
||||||
flagSet.PrintDefaults()
|
|
||||||
fmt.Println("\nIf no command is provided, the service runs in the foreground.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func runForeground() {
|
|
||||||
pid := os.Getpid()
|
|
||||||
savePid(pid)
|
|
||||||
defer removePid()
|
|
||||||
|
|
||||||
// Prepare IPC listener but don't serve yet to avoid race conditions during startup
|
|
||||||
sockPath := getSockPath()
|
|
||||||
_ = os.Remove(sockPath)
|
|
||||||
l, err := net.Listen("unix", sockPath)
|
|
||||||
if err == nil {
|
|
||||||
defer func() {
|
|
||||||
_ = l.Close()
|
|
||||||
_ = os.Remove(sockPath)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Setup signal handling
|
|
||||||
sigChan := make(chan os.Signal, 10)
|
|
||||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGUSR1, syscall.SIGUSR2)
|
|
||||||
|
|
||||||
// Start registered services
|
|
||||||
if err := startServices(ctx); err != nil {
|
if err := startServices(ctx); err != nil {
|
||||||
log.DefaultLogger.Error(fmt.Sprintf("Start services failed: %v", err))
|
getStarterLogger().Error(fmt.Sprintf("[starter] start services failed: %v", err))
|
||||||
stopServices()
|
return err
|
||||||
log.DefaultLogger.Error("Service failed to start, exiting.")
|
}
|
||||||
os.Exit(1)
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait blocks until a termination signal is received.
|
||||||
|
func Wait() {
|
||||||
|
if sigs == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Service started successfully, now expose IPC
|
for {
|
||||||
if l != nil {
|
sig := <-sigs
|
||||||
go serveIPC(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
for sig := range sigChan {
|
|
||||||
if sig == syscall.SIGHUP {
|
if sig == syscall.SIGHUP {
|
||||||
log.DefaultLogger.Info("Received SIGHUP. Reloading...")
|
|
||||||
reloadServices()
|
reloadServices()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -204,18 +188,20 @@ func runForeground() {
|
|||||||
// Handle user custom signals
|
// Handle user custom signals
|
||||||
if sig == syscall.SIGUSR1 || sig == syscall.SIGUSR2 {
|
if sig == syscall.SIGUSR1 || sig == syscall.SIGUSR2 {
|
||||||
if !handleUserSignal(nil, sig) {
|
if !handleUserSignal(nil, sig) {
|
||||||
log.DefaultLogger.Info(fmt.Sprintf("Received signal %v, but no service handled it.", sig))
|
getStarterLogger().Info(fmt.Sprintf("[starter] received signal %v, but no service handled it.", sig))
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.DefaultLogger.Info(fmt.Sprintf("Received signal: %v. Shutting down...", sig))
|
getStarterLogger().Info(fmt.Sprintf("[starter] received signal: %v, shutting down...", sig))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel() // Trigger context cancellation
|
if cancelCtx != nil {
|
||||||
|
cancelCtx() // Trigger context cancellation
|
||||||
|
}
|
||||||
stopServices()
|
stopServices()
|
||||||
log.DefaultLogger.Info("Shutdown complete.")
|
getStarterLogger().Info("[starter] shutdown complete")
|
||||||
}
|
}
|
||||||
|
|
||||||
func startServices(ctx context.Context) error {
|
func startServices(ctx context.Context) error {
|
||||||
@ -225,9 +211,6 @@ func startServices(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
sort.Ints(priorities)
|
sort.Ints(priorities)
|
||||||
|
|
||||||
// Generate a shared logger with trace ID for all services startup
|
|
||||||
logger := log.DefaultLogger.New(id.Get8Bytes4KPerSecond())
|
|
||||||
|
|
||||||
for _, p := range priorities {
|
for _, p := range priorities {
|
||||||
svcs := services[p]
|
svcs := services[p]
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
@ -243,8 +226,23 @@ func startServices(ctx context.Context) error {
|
|||||||
sctx, cancel = context.WithTimeout(ctx, ms.startTimeout)
|
sctx, cancel = context.WithTimeout(ctx, ms.startTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
}
|
}
|
||||||
if err := ms.svc.Start(sctx, logger); err != nil {
|
|
||||||
|
// Each service gets its own unique 10-byte trace ID for its internal logs
|
||||||
|
serviceTraceId := id.Get10Bytes14MPerSecond()
|
||||||
|
serviceLogger := log.DefaultLogger.New(serviceTraceId)
|
||||||
|
|
||||||
|
// Log using starter's logger (8-byte ID) but include service's trace ID in extra fields
|
||||||
|
getStarterLogger().Info(fmt.Sprintf("service [%s] starting", ms.Name), "trace", serviceTraceId)
|
||||||
|
for _, fn := range ms.onStarting {
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
if err := ms.svc.Start(sctx, serviceLogger); err != nil {
|
||||||
errChan <- fmt.Errorf("service [%s] start error: %w", ms.Name, err)
|
errChan <- fmt.Errorf("service [%s] start error: %w", ms.Name, err)
|
||||||
|
} else {
|
||||||
|
getStarterLogger().Info(fmt.Sprintf("service [%s] started", ms.Name), "trace", serviceTraceId)
|
||||||
|
for _, fn := range ms.onStarted {
|
||||||
|
fn()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}(ms)
|
}(ms)
|
||||||
}
|
}
|
||||||
@ -264,6 +262,7 @@ func stopServices() {
|
|||||||
sort.Slice(startedPriorities, func(i, j int) bool {
|
sort.Slice(startedPriorities, func(i, j int) bool {
|
||||||
return startedPriorities[i] > startedPriorities[j]
|
return startedPriorities[i] > startedPriorities[j]
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, p := range startedPriorities {
|
for _, p := range startedPriorities {
|
||||||
svcs := services[p]
|
svcs := services[p]
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
@ -277,8 +276,17 @@ func stopServices() {
|
|||||||
sctx, cancel = context.WithTimeout(sctx, ms.stopTimeout)
|
sctx, cancel = context.WithTimeout(sctx, ms.stopTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
}
|
}
|
||||||
|
getStarterLogger().Info(fmt.Sprintf("service [%s] stopping", ms.Name))
|
||||||
|
for _, fn := range ms.onStopping {
|
||||||
|
fn()
|
||||||
|
}
|
||||||
if err := ms.svc.Stop(sctx); err != nil {
|
if err := ms.svc.Stop(sctx); err != nil {
|
||||||
log.DefaultLogger.Error(fmt.Sprintf("service [%s] stop error: %v", ms.Name, err))
|
getStarterLogger().Error(fmt.Sprintf("service [%s] stop error: %v", ms.Name, err))
|
||||||
|
} else {
|
||||||
|
getStarterLogger().Info(fmt.Sprintf("service [%s] stopped", ms.Name))
|
||||||
|
for _, fn := range ms.onStopped {
|
||||||
|
fn()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}(ms)
|
}(ms)
|
||||||
}
|
}
|
||||||
@ -288,11 +296,12 @@ func stopServices() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func reloadServices() {
|
func reloadServices() {
|
||||||
|
getStarterLogger().Info("[starter] reloading all services...")
|
||||||
for _, p := range startedPriorities {
|
for _, p := range startedPriorities {
|
||||||
for _, ms := range services[p] {
|
for _, ms := range services[p] {
|
||||||
if r, ok := ms.svc.(Reloader); ok {
|
if r, ok := ms.svc.(Reloader); ok {
|
||||||
if err := r.Reload(); err != nil {
|
if err := r.Reload(); err != nil {
|
||||||
log.DefaultLogger.Error(fmt.Sprintf("service [%s] reload error: %v", ms.Name, err))
|
getStarterLogger().Error(fmt.Sprintf("service [%s] reload error: %v", ms.Name, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -303,7 +312,7 @@ func handleUserSignal(svcName *string, sig os.Signal) bool {
|
|||||||
handled := false
|
handled := false
|
||||||
for _, p := range startedPriorities {
|
for _, p := range startedPriorities {
|
||||||
for _, ms := range services[p] {
|
for _, ms := range services[p] {
|
||||||
if svcName != nil && ms.Name != *svcName {
|
if svcName != nil && *svcName != ms.Name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if h, ok := ms.svc.(UserSignalHandler); ok {
|
if h, ok := ms.svc.(UserSignalHandler); ok {
|
||||||
@ -316,168 +325,103 @@ func handleUserSignal(svcName *string, sig os.Signal) bool {
|
|||||||
return handled
|
return handled
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveIPC(l net.Listener) {
|
func showHelp() {
|
||||||
for {
|
fmt.Printf("%s (%s)\n\n", appName, appVersion)
|
||||||
conn, err := l.Accept()
|
if appUsage != "" {
|
||||||
if err != nil {
|
fmt.Printf("%s\n\n", appUsage)
|
||||||
return
|
|
||||||
}
|
|
||||||
go func(c net.Conn) {
|
|
||||||
defer c.Close()
|
|
||||||
data := make([]byte, 4096)
|
|
||||||
n, err := c.Read(data)
|
|
||||||
if err != nil || n == 0 {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
fmt.Printf("Usage:\n %s [command] [options]\n\nCommands:\n", filepath.Base(os.Args[0]))
|
||||||
|
|
||||||
// Protocol: TOKEN COMMAND ARGS...
|
var names []string
|
||||||
parts := strings.Split(string(data[:n]), " ")
|
for cmdName := range commands {
|
||||||
if len(parts) < 2 {
|
names = append(names, cmdName)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
|
||||||
token := parts[0]
|
for _, name := range names {
|
||||||
if token != getIPCToken(os.Getpid()) {
|
fmt.Printf(" %-10s %s\n", name, commands[name].desc)
|
||||||
_, _ = c.Write([]byte("Error: Unauthorized"))
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
fmt.Println()
|
||||||
cmd := parts[1]
|
|
||||||
args := parts[2:]
|
|
||||||
|
|
||||||
switch cmd {
|
|
||||||
case "status":
|
|
||||||
_, _ = c.Write([]byte(getInternalStatus()))
|
|
||||||
case "kill":
|
|
||||||
if len(args) < 2 {
|
|
||||||
_, _ = c.Write([]byte("Error: Missing arguments for kill"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
svcName := args[0]
|
|
||||||
sigNum := cast.Int(args[1])
|
|
||||||
if handleUserSignal(&svcName, syscall.Signal(sigNum)) {
|
|
||||||
_, _ = c.Write([]byte(fmt.Sprintf("Signal %d sent to %s", sigNum, svcName)))
|
|
||||||
} else {
|
|
||||||
_, _ = c.Write([]byte(fmt.Sprintf("Error: Service %s not found or didn't handle signal", svcName)))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
_, _ = c.Write([]byte("Error: Unknown command"))
|
|
||||||
}
|
|
||||||
}(conn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getInternalStatus() string {
|
|
||||||
var out string
|
|
||||||
var priorities []int
|
|
||||||
for p := range services {
|
|
||||||
priorities = append(priorities, p)
|
|
||||||
}
|
|
||||||
sort.Ints(priorities)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func startCmd() {
|
func startCmd() {
|
||||||
pid := loadPid()
|
pid := getPid()
|
||||||
if pid > 0 && isProcessRunning(pid) {
|
if pid != 0 && isProcessRunning(pid) {
|
||||||
log.DefaultLogger.Info(fmt.Sprintf("%s is already running (PID %d)", appName, pid))
|
getStarterLogger().Info(fmt.Sprintf("[starter] %s is already running (PID %d)", appName, pid))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{}
|
cmd := exec.Command(os.Args[0])
|
||||||
for i := 1; i < len(os.Args); i++ {
|
cmd.Args = append([]string{os.Args[0]}, flagSet.Args()...)
|
||||||
if os.Args[i] != "start" {
|
cmd.Stdout = nil
|
||||||
args = append(args, os.Args[i])
|
cmd.Stderr = nil
|
||||||
}
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
getStarterLogger().Error(fmt.Sprintf("[starter] failed to start %s: %v", appName, err))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(os.Args[0], args...)
|
_ = os.WriteFile(getPidPath(), []byte(fmt.Sprintf("%d", cmd.Process.Pid)), 0644)
|
||||||
err := cmd.Start()
|
getStarterLogger().Info(fmt.Sprintf("[starter] %s started (PID %d)", appName, cmd.Process.Pid))
|
||||||
if err != nil {
|
|
||||||
log.DefaultLogger.Error(fmt.Sprintf("Failed to start %s: %v", appName, err))
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.DefaultLogger.Info(fmt.Sprintf("%s started (PID %d)", appName, cmd.Process.Pid))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopCmd() {
|
func stopCmd() {
|
||||||
pid := loadPid()
|
pid := getPid()
|
||||||
if pid <= 0 || !isProcessRunning(pid) {
|
if pid == 0 || !isProcessRunning(pid) {
|
||||||
log.DefaultLogger.Info(fmt.Sprintf("%s is not running", appName))
|
getStarterLogger().Info(fmt.Sprintf("[starter] %s is not running", appName))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
process, _ := os.FindProcess(pid)
|
getStarterLogger().Info(fmt.Sprintf("[starter] stopping %s (PID %d)...", appName, pid))
|
||||||
log.DefaultLogger.Info(fmt.Sprintf("Stopping %s (PID %d)...", appName, pid))
|
_ = syscall.Kill(pid, syscall.SIGTERM)
|
||||||
_ = process.Signal(syscall.SIGTERM)
|
|
||||||
|
|
||||||
err := timer.Retry(func() error {
|
for i := 0; i < 30; i++ {
|
||||||
if isProcessRunning(pid) {
|
if !isProcessRunning(pid) {
|
||||||
return fmt.Errorf("still running")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, timer.WithMaxRetries(25), timer.WithBackoff(200*time.Millisecond, 1.0))
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
log.DefaultLogger.Info("Stopped OK")
|
|
||||||
removePid()
|
removePid()
|
||||||
|
getStarterLogger().Info(fmt.Sprintf("[starter] %s stopped", appName))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
log.DefaultLogger.Info("Stop timeout, killing...")
|
}
|
||||||
_ = process.Kill()
|
getStarterLogger().Warning(fmt.Sprintf("[starter] %s failed to stop gracefully, killing...", appName))
|
||||||
|
_ = syscall.Kill(pid, syscall.SIGKILL)
|
||||||
removePid()
|
removePid()
|
||||||
}
|
}
|
||||||
|
|
||||||
func restartCmd() {
|
func restartCmd() {
|
||||||
stopCmd()
|
stopCmd()
|
||||||
_ = timer.Retry(func() error { return nil }, timer.WithMaxRetries(1), timer.WithBackoff(500*time.Millisecond, 1.0))
|
|
||||||
startCmd()
|
startCmd()
|
||||||
}
|
}
|
||||||
|
|
||||||
func statusCmd() {
|
func statusCmd() {
|
||||||
pid := loadPid()
|
pid := getPid()
|
||||||
isRunning := pid > 0 && isProcessRunning(pid)
|
if pid == 0 || !isProcessRunning(pid) {
|
||||||
if isRunning {
|
fmt.Printf("%s is NOT running\n", appName)
|
||||||
fmt.Printf("%s is %s (PID %d)\n", appName, shell.Green("running"), pid)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s is running (PID %d)\n\n", appName, pid)
|
||||||
res, err := callIPC(pid, "status")
|
res, err := callIPC(pid, "status")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Println("\nServices:")
|
fmt.Println("Services Status:")
|
||||||
fmt.Print(res)
|
fmt.Println(res)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Printf("%s is %s\n", appName, shell.Red("not running"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func killCmd() {
|
func killCmd() {
|
||||||
if len(flagSet.Args()) < 2 {
|
if flagSet.NArg() < 2 {
|
||||||
fmt.Println("Usage: kill <service_name> <signal_num>")
|
fmt.Println("Usage: kill <service_name> <signal_num>")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pid := loadPid()
|
|
||||||
if pid <= 0 || !isProcessRunning(pid) {
|
|
||||||
fmt.Println("Error: process not running")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
svcName := flagSet.Arg(0)
|
svcName := flagSet.Arg(0)
|
||||||
sigNum := flagSet.Arg(1)
|
sigNum := flagSet.Arg(1)
|
||||||
|
pid := getPid()
|
||||||
|
if pid == 0 || !isProcessRunning(pid) {
|
||||||
|
fmt.Println("Application is not running")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
res, err := callIPC(pid, fmt.Sprintf("kill %s %s", svcName, sigNum))
|
res, err := callIPC(pid, fmt.Sprintf("kill %s %s", svcName, sigNum))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error: %v\n", err)
|
fmt.Printf("Error: %v\n", err)
|
||||||
@ -487,24 +431,24 @@ func killCmd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func callIPC(pid int, cmd string) (string, error) {
|
func callIPC(pid int, cmd string) (string, error) {
|
||||||
conn, err := net.Dial("unix", getSockPath())
|
sockPath := getSockPath()
|
||||||
|
conn, err := net.Dial("unix", sockPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
token := getIPCToken(pid)
|
token := generateToken(pid)
|
||||||
_, _ = conn.Write([]byte(fmt.Sprintf("%s %s", token, cmd)))
|
_, _ = conn.Write([]byte(fmt.Sprintf("%s %s", token, cmd)))
|
||||||
|
|
||||||
data, err := io.ReadAll(conn)
|
buf, err := io.ReadAll(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return string(data), nil
|
return string(buf), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIPCToken(pid int) string {
|
func generateToken(pid int) string {
|
||||||
// Use Sha256 for better security
|
|
||||||
return crypto.Sha256ToHex([]byte(fmt.Sprintf("%s:%d", ipcSecret, pid)))
|
return crypto.Sha256ToHex([]byte(fmt.Sprintf("%s:%d", ipcSecret, pid)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -516,16 +460,14 @@ func getSockPath() string {
|
|||||||
return filepath.Join(os.TempDir(), fmt.Sprintf("%s-%s.sock", appName, appVersion))
|
return filepath.Join(os.TempDir(), fmt.Sprintf("%s-%s.sock", appName, appVersion))
|
||||||
}
|
}
|
||||||
|
|
||||||
func savePid(p int) {
|
func getPid() int {
|
||||||
_ = file.Write(getPidPath(), cast.To[string](p))
|
data, err := os.ReadFile(getPidPath())
|
||||||
}
|
|
||||||
|
|
||||||
func loadPid() int {
|
|
||||||
data, err := file.Read(getPidPath())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return cast.To[int](data)
|
var pid int
|
||||||
|
fmt.Sscanf(string(data), "%d", &pid)
|
||||||
|
return pid
|
||||||
}
|
}
|
||||||
|
|
||||||
func removePid() {
|
func removePid() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user