跨平台 Go 服务(守护进程)框架,统一 Windows、Linux(systemd/Upstart/SysV/OpenRC)、macOS launchd 的安装、启动、停止和运行接口,可自动识别交互/服务模式。
NewFromRunner 即可将 Runner 实现适配为 ServiceRunner,无需手动包装。config、factory、runner、interface 等职责拆分,减少单文件膨胀,便于测试与审查。go get github.com/darkit/daemon@latestgo run ./example/simple实现 Runner 接口的类型会被框架自动包装成 ServiceRunner,无需手动创建。这是最简洁的方式,适合大多数场景。
package main
import (
"context"
"log"
"time"
"github.com/darkit/daemon"
)
type myService struct{}
func (m *myService) Run(ctx context.Context) error {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
log.Println("服务运行中...")
case <-ctx.Done():
return nil
}
}
}
func main() {
cfg := &service.Config{
Name: "demo",
DisplayName: "Demo Service",
Description: "跨平台 demo 服务",
}
svc, err := service.NewFromRunner(&myService{}, cfg)
if err != nil {
log.Fatal(err)
}
if err := svc.Run(); err != nil {
log.Fatal(err)
}
}
直接使用 NewServiceRunner 创建函数式服务,无需定义类型。适合快速开发和原型验证。
package main
import (
"context"
"log"
"time"
"github.com/darkit/daemon"
)
func main() {
runner := service.NewServiceRunner(func(ctx context.Context) error {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
log.Println("服务运行中...")
case <-ctx.Done():
return nil
}
}
})
cfg := &service.Config{
Name: "demo",
DisplayName: "Demo Service",
Description: "跨平台 demo 服务",
}
svc, err := service.New(runner, cfg)
if err != nil {
log.Fatal(err)
}
if err := svc.Run(); err != nil {
log.Fatal(err)
}
}
传统的 Interface 方式,需要手动管理 goroutine 和退出信号。保留用于遗留代码或需要精细控制启动/停止逻辑的场景。
package main
import (
"log"
"time"
"github.com/darkit/daemon"
)
type program struct{ quit chan struct{} }
func (p *program) Start(s service.Service) error {
p.quit = make(chan struct{})
go func() {
for {
select {
case <-p.quit:
return
default:
time.Sleep(time.Second)
}
}
}()
return nil
}
func (p *program) Stop(service.Service) error {
close(p.quit)
return nil
}
func main() {
cfg := &service.Config{
Name: "demo",
DisplayName: "Demo Service",
Description: "跨平台 demo 服务",
}
svc, err := service.New(&program{}, cfg)
if err != nil {
log.Fatal(err)
}
if err := svc.Run(); err != nil {
log.Fatal(err)
}
}
以下表格对比三种实现方式的特点:
| 对比维度 | Runner 接口 | ServiceRunner 函数式 | Interface 传统方式 |
|---|---|---|---|
| 代码量 | ~10 行 | ~5 行 | ~30 行 |
| 学习曲线 | 低(仅需理解 context) | 极低(闭包即可) | 中(需手动管理生命周期) |
| goroutine 管理 | 自动 | 自动 | 手动 |
| context 传播 | 自动 | 自动 | 手动 |
| panic 恢复 | 自动 | 自动 | 手动 |
| 超时控制 | 支持 | 支持(可配置) | 手动实现 |
| 适用场景 | 大多数新项目 | 快速原型、简单服务 | 遗留代码、复杂启动逻辑 |
| 向后兼容 | ✅ | ✅ | ✅ |
传统方式(~30 行):
type program struct{ quit chan struct{} }
func (p *program) Start(s service.Service) error {
p.quit = make(chan struct{})
go func() {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
log.Println("运行中...")
case <-p.quit:
return
}
}
}()
return nil
}
func (p *program) Stop(service.Service) error {
close(p.quit)
time.Sleep(100 * time.Millisecond) // 等待 goroutine 退出
return nil
}
Runner 方式(~10 行):
type program struct{}
func (p *program) Run(ctx context.Context) error {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
log.Println("运行中...")
case <-ctx.Done():
return nil
}
}
}
ServiceRunner 方式(~5 行):
runner := service.NewServiceRunner(func(ctx context.Context) error {
// 业务逻辑
<-ctx.Done()
return nil
})
说明:✅ 完整支持;◑ 部分支持;⏳ 实验性;— 暂不支持。
| 能力/平台 | Windows | Linux systemd | Linux Upstart | Linux SysV | Linux OpenRC | macOS launchd |
|---|---|---|---|---|---|---|
| StructuredDeps | ◑(after/require/want,before 忽略并告警) | ✅ | ◑(after/require) | ✅ | ✅ | ⏳(after/require 软等待脚本) |
| TimeoutConfig | ✅(Start/Stop) | ✅(Start/Stop/RestartSec) | ◑(Restart 间隔) | — | — | — |
| 重启策略配置 | ✅(OnFailure/Delay) | ✅(Restart/StartLimit) | ◑(respawn 策略) | ◑(LSB retry) | ◑(respawn/rc-service) | — |
| 生命周期钩子 | — | ✅(ExecStartPre/Post、ExecStopPost) | ✅(pre-start/post-stop) | — | — | — |
| 日志配置样板 | ✅(EventLog 脚本) | ✅(journald + logrotate) | ✅(logrotate) | ✅ | ✅ | ✅(logrotate) |
以下示例均复用最小实现:
package main
import "github.com/darkit/daemon"
type program struct{}
func (p *program) Start(service.Service) error { return nil }
func (p *program) Stop(service.Service) error { return nil }
package main
import "github.com/darkit/daemon"
func main() {
cfg := &service.Config{
Name: "myapp",
StructuredDeps: []service.Dependency{
{Name: "network.target", Type: service.DependencyAfter},
{Name: "postgresql", Type: service.DependencyRequire},
{Name: "logger", Type: service.DependencyBefore},
},
}
svc, _ := service.New(&program{}, cfg)
_ = svc.Run()
}
package main
import (
"time"
"github.com/darkit/daemon"
)
func main() {
cfg := &service.Config{
Name: "myapp",
Timeout: service.TimeoutConfig{
Start: 10 * time.Second,
Stop: 20 * time.Second,
Restart: 5 * time.Second,
},
}
svc, _ := service.New(&program{}, cfg)
_ = svc.Run()
}
package main
import "github.com/darkit/daemon"
func main() {
cfg := &service.Config{
Name: "myapp",
Option: service.KeyValue{
service.OptionExecStartPre: []string{"/usr/local/bin/prepare.sh"},
service.OptionExecStartPost: "echo started",
service.OptionExecStopPost: "rm -f /var/run/myapp.pid",
service.OptionOnFailureUnit: []string{"alert@%n.service"},
},
}
svc, _ := service.New(&program{}, cfg)
_ = svc.Run()
}
// 生成 logrotate、journald、Windows EventLog 配置文件内容
files := map[string]string{
"logrotate.conf": service.GenerateLogrotateConfig("myapp", "/var/log/myapp.log"),
"journald.conf": service.GenerateSystemdJournalConfig("myapp"),
"windows-eventlog.ps1": service.GenerateWindowsEventLogConfig("Application", "MyApp"),
}
更多细节见 LOGGING.md。