From e898428b9f63084667335552bfe39ab1f6e21f06 Mon Sep 17 00:00:00 2001 From: Hedzr Yeh Date: Sat, 25 May 2019 18:42:50 +0800 Subject: [PATCH] add daemon; hooks before/after xref-building; bugs; --- README.md | 37 +++++++- command.go | 5 + def.go | 4 +- examples/demo/demo/entry.go | 4 + examples/demo/demo/root_cmd.go | 61 +----------- examples/demo/svr/server.go | 65 +++++++++++++ exec.go | 22 +++-- go.mod | 9 +- go.sum | 44 ++------- options.watch.go | 4 +- plugin/daemon/cmd.go | 96 +++++++++++++++++++ plugin/daemon/daemon.go | 169 +++++++++++++++++++++++++++++++++ plugin/daemon/daemon_ctx.go | 49 ++++++++++ plugin/daemon/def.go | 21 ++++ plugin/daemon/handlers.go | 34 +++++++ plugin/daemon/installer.go | 53 +++++++++++ plugin/daemon/main.c.txt | 50 ++++++++++ plugin/daemon/pidfile.go | 29 ++++++ 18 files changed, 646 insertions(+), 110 deletions(-) create mode 100644 examples/demo/svr/server.go create mode 100644 plugin/daemon/cmd.go create mode 100644 plugin/daemon/daemon.go create mode 100644 plugin/daemon/daemon_ctx.go create mode 100644 plugin/daemon/def.go create mode 100644 plugin/daemon/handlers.go create mode 100644 plugin/daemon/installer.go create mode 100644 plugin/daemon/main.c.txt create mode 100644 plugin/daemon/pidfile.go diff --git a/README.md b/README.md index e1a4b4c..1c4cdb5 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,37 @@ A getopt-like parser of command-line options, compatible with the [getopt_long]( - Uses `WalkAllCommands(walker)` to loop commands. +- Daemon + + > Uses daemon feature with `go-daemon` + + ```golang + import "github.com/hedzr/cmdr/plugin/daemon" + func main() { + daemon.Enable(NewDaemon()) + if err := cmdr.Exec(rootCmd); err != nil { + log.Fatal("Error:", err) + } + } + func NewDaemon() daemon.Daemon { + return &DaemonImpl{} + } + ``` + + See full codes in [demo](./examples/demo/) app. + + ```bash + bin/demo server [start|stop|status|restart|install|uninstall] + ``` + + `install`/`uninstall` sub-commands could install `demo` app as a systemd service. + +- `ExecWith(rootCmd *RootCommand, beforeXrefBuilding_, afterXrefBuilt_ HookXrefFunc) (err error)` + + `AddOnBeforeXrefBuilding(cb)` + + `AddOnAfterXrefBuilt(cb)` + - More... @@ -142,9 +173,13 @@ A getopt-like parser of command-line options, compatible with the [getopt_long]( +## Contrib + +*Feel free to issue me bug reports and fixes. Many thanks to all contributors.* + -## LICENSE +## License MIT. diff --git a/command.go b/command.go index f32d842..2ec12ee 100644 --- a/command.go +++ b/command.go @@ -29,6 +29,11 @@ func (c *Command) IsRoot() bool { return c == &c.root.Command } +// GetHitStr returns the matched command string +func (c *Command) GetHitStr() string { + return c.strHit +} + // // HasParent detects whether owner is available or not // func (c *BaseOpt) HasParent() bool { // return c.owner != nil diff --git a/def.go b/def.go index 6e9ca6c..7da0ae8 100644 --- a/def.go +++ b/def.go @@ -169,8 +169,8 @@ var ( globalShowVersion func() globalShowBuildInfo func() - beforeXrefBuilding HookXrefFunc - afterXrefBuilt HookXrefFunc + beforeXrefBuilding []HookXrefFunc + afterXrefBuilt []HookXrefFunc // ErrShouldBeStopException tips `Exec()` cancelled the following actions after `PreAction()` ErrShouldBeStopException = errors.New("should be stop right now") diff --git a/examples/demo/demo/entry.go b/examples/demo/demo/entry.go index 54f3054..8c92b86 100644 --- a/examples/demo/demo/entry.go +++ b/examples/demo/demo/entry.go @@ -6,6 +6,8 @@ package demo import ( "github.com/hedzr/cmdr" + "github.com/hedzr/cmdr/examples/demo/svr" + "github.com/hedzr/cmdr/plugin/daemon" "github.com/sirupsen/logrus" ) @@ -21,6 +23,8 @@ func Entry() { // cmdr.EnableHelpCommands = false // cmdr.EnableGenerateCommands = false + daemon.Enable(svr.NewDaemon()) + if err := cmdr.Exec(rootCmd); err != nil { logrus.Errorf("Error: %v", err) } diff --git a/examples/demo/demo/root_cmd.go b/examples/demo/demo/root_cmd.go index 0a421d5..5a7eb48 100644 --- a/examples/demo/demo/root_cmd.go +++ b/examples/demo/demo/root_cmd.go @@ -36,7 +36,7 @@ var ( }, SubCommands: []*cmdr.Command{ // generatorCommands, - serverCommands, + // serverCommands, msCommands, }, }, @@ -48,65 +48,6 @@ var ( Author: "Hedzr Yeh ", } - serverCommands = &cmdr.Command{ - BaseOpt: cmdr.BaseOpt{ - // Name: "server", - Short: "s", - Full: "server", - Aliases: []string{"serve", "svr"}, - Description: "server ops: for linux service/daemon.", - }, - SubCommands: []*cmdr.Command{ - { - BaseOpt: cmdr.BaseOpt{ - Short: "s", - Full: "start", - Aliases: []string{"run", "startup"}, - Description: "startup this system service/daemon.", - }, - }, - { - BaseOpt: cmdr.BaseOpt{ - Short: "t", - Full: "stop", - Aliases: []string{"stp", "halt", "pause"}, - Description: "stop this system service/daemon.", - }, - }, - { - BaseOpt: cmdr.BaseOpt{ - Short: "r", - Full: "restart", - Aliases: []string{"reload"}, - Description: "restart this system service/daemon.", - }, - }, - { - BaseOpt: cmdr.BaseOpt{ - Full: "status", - Aliases: []string{"st"}, - Description: "display its running status as a system service/daemon.", - }, - }, - { - BaseOpt: cmdr.BaseOpt{ - Short: "i", - Full: "install", - Aliases: []string{"setup"}, - Description: "install as a system service/daemon.", - }, - }, - { - BaseOpt: cmdr.BaseOpt{ - Short: "u", - Full: "uninstall", - Aliases: []string{"remove"}, - Description: "remove from a system service/daemon.", - }, - }, - }, - } - msCommands = &cmdr.Command{ BaseOpt: cmdr.BaseOpt{ Name: "microservices", diff --git a/examples/demo/svr/server.go b/examples/demo/svr/server.go new file mode 100644 index 0000000..c7fd28e --- /dev/null +++ b/examples/demo/svr/server.go @@ -0,0 +1,65 @@ +/* + * Copyright © 2019 Hedzr Yeh. + */ + +package svr + +import ( + "fmt" + "github.com/hedzr/cmdr" + "github.com/hedzr/cmdr/plugin/daemon" + "github.com/sirupsen/logrus" + "os" + "time" +) + +func NewDaemon() daemon.Daemon { + return &DaemonImpl{} +} + +type DaemonImpl struct { +} + +func (*DaemonImpl) OnRun(cmd *cmdr.Command, args []string, stopCh, doneCh chan struct{}) (err error) { + logrus.Debugf("demo daemon OnRun, pid = %v, ppid = %v", os.Getpid(), os.Getppid()) + go worker(stopCh, doneCh) + return +} + +func worker(stopCh, doneCh chan struct{}) { +LOOP: + for { + time.Sleep(time.Second) // this is work to be done by worker. + select { + case <-stopCh: + break LOOP + default: + } + } + doneCh <- struct{}{} +} + +func (*DaemonImpl) OnStop(cmd *cmdr.Command, args []string) (err error) { + logrus.Debugf("demo daemon OnStop") + return +} + +func (*DaemonImpl) OnReload() { + logrus.Debugf("demo daemon OnReload") +} + +func (*DaemonImpl) OnStatus(cxt *daemon.Context, cmd *cmdr.Command, p *os.Process) (err error) { + fmt.Printf("%v v%v\n", cmd.GetRoot().AppName, cmd.GetRoot().Version) + fmt.Printf("PID=%v\nLOG=%v\n", cxt.PidFileName, cxt.LogFileName) + return +} + +func (*DaemonImpl) OnInstall(cxt *daemon.Context, cmd *cmdr.Command, args []string) (err error) { + logrus.Debugf("demo daemon OnInstall") + panic("implement me") +} + +func (*DaemonImpl) OnUninstall(cxt *daemon.Context, cmd *cmdr.Command, args []string) (err error) { + logrus.Debugf("demo daemon OnUninstall") + panic("implement me") +} diff --git a/exec.go b/exec.go index 7a026d1..a17ed33 100644 --- a/exec.go +++ b/exec.go @@ -24,8 +24,8 @@ func Exec(rootCmd *RootCommand) (err error) { // ExecWith is main entry of `cmdr`. func ExecWith(rootCmd *RootCommand, beforeXrefBuilding_, afterXrefBuilt_ HookXrefFunc) (err error) { - beforeXrefBuilding = beforeXrefBuilding_ - afterXrefBuilt = afterXrefBuilt_ + beforeXrefBuilding = append(beforeXrefBuilding, beforeXrefBuilding_) + afterXrefBuilt = append(afterXrefBuilt, afterXrefBuilt_) err = InternalExecFor(rootCmd, os.Args) return } @@ -52,6 +52,16 @@ func buildRefs(rootCmd *RootCommand) (err error) { return } +// AddOnBeforeXrefBuilding add hook func +func AddOnBeforeXrefBuilding(cb HookXrefFunc) { + beforeXrefBuilding = append(beforeXrefBuilding, cb) +} + +// AddOnAfterXrefBuilt add hook func +func AddOnAfterXrefBuilt(cb HookXrefFunc) { + afterXrefBuilt = append(afterXrefBuilt, cb) +} + // InternalExecFor is an internal helper, esp for debugging func InternalExecFor(rootCmd *RootCommand, args []string) (err error) { var ( @@ -70,8 +80,8 @@ func InternalExecFor(rootCmd *RootCommand, args []string) (err error) { _ = rootCmd.oerr.Flush() }() - if beforeXrefBuilding != nil { - beforeXrefBuilding(rootCmd, args) + for _, x := range beforeXrefBuilding { + x(rootCmd, args) } err = buildRefs(rootCmd) @@ -79,8 +89,8 @@ func InternalExecFor(rootCmd *RootCommand, args []string) (err error) { return } - if afterXrefBuilt != nil { - afterXrefBuilt(rootCmd, args) + for _, x := range afterXrefBuilt { + x(rootCmd, args) } for pkg.i = 1; pkg.i < len(args); pkg.i++ { diff --git a/go.mod b/go.mod index 3e26b2d..701ae67 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,12 @@ module github.com/hedzr/cmdr go 1.12 require ( - github.com/BurntSushi/toml v0.3.1 // indirect github.com/fsnotify/fsnotify v1.4.7 - github.com/sirupsen/logrus v1.4.1 - github.com/spf13/viper v1.3.2 + github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect + github.com/sevlyar/go-daemon v0.1.5 + github.com/sirupsen/logrus v1.4.2 + golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5 // indirect gopkg.in/yaml.v2 v2.2.2 ) + +exclude github.com/sirupsen/logrus v1.4.1 diff --git a/go.sum b/go.sum index dcb1610..4fd443e 100644 --- a/go.sum +++ b/go.sum @@ -1,49 +1,23 @@ -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/sevlyar/go-daemon v0.1.5 h1:Zy/6jLbM8CfqJ4x4RPr7MJlSKt90f00kNM1D401C+Qk= +github.com/sevlyar/go-daemon v0.1.5/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5 h1:sM3evRHxE/1RuMe1FYAL3j7C7fUfIjkbE+NiDAYUF8U= +golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/options.watch.go b/options.watch.go index a52e616..a9825cc 100644 --- a/options.watch.go +++ b/options.watch.go @@ -11,8 +11,6 @@ import ( "log" "path" - // log "github.com/sirupsen/logrus" - "github.com/spf13/viper" "gopkg.in/yaml.v2" "io" "io/ioutil" @@ -203,7 +201,7 @@ func (s *Options) watchConfigDir(configDir string) { if err != nil { log.Printf("ERROR: os.Open() returned %v\n", err) } else { - err = viper.MergeConfig(bufio.NewReader(file)) + err = s.mergeConfigFile(bufio.NewReader(file)) if err != nil { log.Printf("ERROR: os.Open() returned %v\n", err) } diff --git a/plugin/daemon/cmd.go b/plugin/daemon/cmd.go new file mode 100644 index 0000000..5bf5281 --- /dev/null +++ b/plugin/daemon/cmd.go @@ -0,0 +1,96 @@ +/* + * Copyright © 2019 Hedzr Yeh. + */ + +package daemon + +import "github.com/hedzr/cmdr" + +var ( + DaemonServerCommands = &cmdr.Command{ + BaseOpt: cmdr.BaseOpt{ + // Name: "server", + Short: "s", + Full: "server", + Aliases: []string{"serve", "svr", "daemon"}, + Description: "server ops: for linux daemon.", + }, + SubCommands: []*cmdr.Command{ + { + BaseOpt: cmdr.BaseOpt{ + Short: "s", + Full: "start", + Aliases: []string{"run", "startup"}, + Description: "startup this system service/daemon.", + Action: daemonStart, + Flags: []*cmdr.Flag{ + { + BaseOpt: cmdr.BaseOpt{ + Short: "f", + Full: "foreground", + Aliases: []string{"fg"}, + Description: "run on foreground, NOT daemonized.", + }, + DefaultValue: false, + }, + }, + }, + }, + { + BaseOpt: cmdr.BaseOpt{ + Short: "t", + Full: "stop", + Aliases: []string{"stp", "halt", "pause"}, + Description: "stop this system service/daemon.", + Action: daemonStop, + }, + }, + { + BaseOpt: cmdr.BaseOpt{ + Short: "re", + Full: "restart", + Aliases: []string{"reload"}, + Description: "restart this system service/daemon.", + Action: daemonRestart, + }, + }, + { + BaseOpt: cmdr.BaseOpt{ + Full: "status", + Aliases: []string{"st"}, + Description: "display its running status as a system service/daemon.", + Action: daemonStatus, + }, + }, + { + BaseOpt: cmdr.BaseOpt{ + Short: "i", + Full: "install", + Aliases: []string{"setup"}, + Description: "install as a system service/daemon.", + Action: daemonInstall, + Flags: []*cmdr.Flag{ + { + BaseOpt: cmdr.BaseOpt{ + Short: "s", + Full: "systemd", + Aliases: []string{"sys"}, + Description: "install as a systemd service.", + }, + DefaultValue: true, + }, + }, + }, + }, + { + BaseOpt: cmdr.BaseOpt{ + Short: "u", + Full: "uninstall", + Aliases: []string{"remove"}, + Description: "remove from a system service/daemon.", + Action: daemonUninstall, + }, + }, + }, + } +) diff --git a/plugin/daemon/daemon.go b/plugin/daemon/daemon.go new file mode 100644 index 0000000..7a6e3ed --- /dev/null +++ b/plugin/daemon/daemon.go @@ -0,0 +1,169 @@ +/* + * Copyright © 2019 Hedzr Yeh. + */ + +package daemon + +import ( + "fmt" + "github.com/hedzr/cmdr" + "github.com/sevlyar/go-daemon" + "log" + "os" + "strings" + "syscall" +) + +func Enable(daemonImpl_ Daemon) { + daemonImpl = daemonImpl_ + + cmdr.AddOnBeforeXrefBuilding(func(root *cmdr.RootCommand, args []string) { + root.SubCommands = append(root.SubCommands, DaemonServerCommands) + if root.PreAction != nil { + savedPreAction := root.PreAction + root.PreAction = func(cmd *cmdr.Command, args []string) (err error) { + pidfile.Create(cmd) + logger.Setup(cmd) + err = savedPreAction(cmd, args) + return + } + } + if root.PostAction != nil { + savedPostAction := root.PostAction + root.PostAction = func(cmd *cmdr.Command, args []string) { + pidfile.Destroy() + savedPostAction(cmd, args) + return + } + } + }) +} + +var prefix = strings.Join(append(cmdr.RxxtPrefix, "server"), ".") + +func daemonStart(cmd *cmdr.Command, args []string) (err error) { + // logrus.Debugf("daemon start") + if cmdr.GetBoolP(prefix, "foreground") { + err = run(cmd, args) + } else if cmd.GetHitStr() == "run" { + err = run(cmd, args) + } else { + err = runAsDaemon(cmd, args) + } + return +} + +func runAsDaemon(cmd *cmdr.Command, args []string) (err error) { + cxt := getContext(cmd, args) + child, e := cxt.Reborn() + if e != nil { + log.Fatalln(e) + } + if child != nil { + fmt.Println("Daemon started. parent stopped.") + return + } + + defer cxt.Release() + err = run(cmd, args) + return +} + +func run(cmd *cmdr.Command, args []string) (err error) { + daemon.SetSigHandler(termHandler, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGABRT, syscall.SIGINT) + daemon.SetSigHandler(reloadHandler, syscall.SIGHUP) + + if daemonImpl != nil { + err = daemonImpl.OnRun(cmd, args, stop, done) + } + + log.Printf("daemon ServeSignals, pid = %v", os.Getpid()) + err = daemon.ServeSignals() + if err != nil { + log.Printf("Error:", err) + } + + if daemonImpl != nil { + err = daemonImpl.OnStop(cmd, args) + } + + log.Println("daemon terminated") + return +} + +func daemonStop(cmd *cmdr.Command, args []string) (err error) { + // logrus.Debugf("daemon stop") + getContext(cmd, args) + + p, err := daemonCtx.Search() + if err != nil { + fmt.Printf("%v is stopped.\n", cmd.GetRoot().AppName) + return + } + + if err = p.Signal(syscall.SIGTERM); err != nil { + return + } + return +} + +func daemonRestart(cmd *cmdr.Command, args []string) (err error) { + // logrus.Debugf("daemon reload") + getContext(cmd, args) + + p, err := daemonCtx.Search() + if err != nil { + // logrus.Fatalln("Unable send signal to the daemon:", err) + fmt.Printf("%v is stopped.\n", cmd.GetRoot().AppName) + } + + if err = p.Signal(syscall.SIGHUP); err != nil { + return + } + return +} + +func daemonStatus(cmd *cmdr.Command, args []string) (err error) { + // logrus.Debugf("daemon status") + getContext(cmd, args) + + p, err := daemonCtx.Search() + if err != nil { + fmt.Printf("%v is stopped.\n", cmd.GetRoot().AppName) + } else { + fmt.Printf("%v is running as %v.\n", cmd.GetRoot().AppName, p.Pid) + } + + if daemonImpl != nil { + err = daemonImpl.OnStatus(&Context{Context: *daemonCtx}, cmd, p) + } + return +} + +func daemonInstall(cmd *cmdr.Command, args []string) (err error) { + // logrus.Debugf("daemon install") + getContext(cmd, args) + + err = runInstaller(cmd, args) + if err != nil { + return + } + if daemonImpl != nil { + err = daemonImpl.OnInstall(&Context{Context: *daemonCtx}, cmd, args) + } + return +} + +func daemonUninstall(cmd *cmdr.Command, args []string) (err error) { + // logrus.Debugf("daemon uninstall") + getContext(cmd, args) + + err = runUninstaller(cmd, args) + if err != nil { + return + } + if daemonImpl != nil { + err = daemonImpl.OnUninstall(&Context{Context: *daemonCtx}, cmd, args) + } + return +} diff --git a/plugin/daemon/daemon_ctx.go b/plugin/daemon/daemon_ctx.go new file mode 100644 index 0000000..dc69ea5 --- /dev/null +++ b/plugin/daemon/daemon_ctx.go @@ -0,0 +1,49 @@ +/* + * Copyright © 2019 Hedzr Yeh. + */ + +package daemon + +import ( + "fmt" + "github.com/hedzr/cmdr" + "github.com/sevlyar/go-daemon" + "path" +) + +type Context struct { + daemon.Context +} + +var daemonCtx *daemon.Context + +func getContext(cmd *cmdr.Command, args []string) *daemon.Context { + var pidpath, logpath string + + for _, x := range []string{"/var/log/%s/%s.log", "/tmp/%s.log"} { + xx := fmt.Sprintf(x, cmd.GetRoot().AppName) + if cmdr.FileExists(path.Dir(xx)) { + logpath = xx + break + } + } + + for _, x := range []string{"/var/run/%s/%s.pid", "/tmp/%s.pid"} { + xx := fmt.Sprintf(x, cmd.GetRoot().AppName) + if cmdr.FileExists(path.Dir(xx)) { + pidpath = xx + break + } + } + + daemonCtx = &daemon.Context{ + PidFileName: pidpath, + PidFilePerm: 0644, + LogFileName: logpath, + LogFilePerm: 0640, + WorkDir: "./", + Umask: 027, + Args: args, + } + return daemonCtx +} diff --git a/plugin/daemon/def.go b/plugin/daemon/def.go new file mode 100644 index 0000000..820472e --- /dev/null +++ b/plugin/daemon/def.go @@ -0,0 +1,21 @@ +/* + * Copyright © 2019 Hedzr Yeh. + */ + +package daemon + +import ( + "github.com/hedzr/cmdr" + "os" +) + +type Daemon interface { + OnRun(cmd *cmdr.Command, args []string, stopCh, doneCh chan struct{}) (err error) + OnStop(cmd *cmdr.Command, args []string) (err error) + OnReload() + OnStatus(cxt *Context, cmd *cmdr.Command, p *os.Process) (err error) + OnInstall(cxt *Context, cmd *cmdr.Command, args []string) (err error) + OnUninstall(cxt *Context, cmd *cmdr.Command, args []string) (err error) +} + +var daemonImpl Daemon diff --git a/plugin/daemon/handlers.go b/plugin/daemon/handlers.go new file mode 100644 index 0000000..2de6bef --- /dev/null +++ b/plugin/daemon/handlers.go @@ -0,0 +1,34 @@ +/* + * Copyright © 2019 Hedzr Yeh. + */ + +package daemon + +import ( + "github.com/sevlyar/go-daemon" + "log" + "os" + "syscall" +) + +var ( + stop = make(chan struct{}) + done = make(chan struct{}) +) + +func termHandler(sig os.Signal) error { + log.Println("terminating...") + stop <- struct{}{} + if sig == syscall.SIGQUIT { + <-done + } + return daemon.ErrStop +} + +func reloadHandler(sig os.Signal) error { + log.Println("configuration reloaded") + if daemonImpl != nil { + daemonImpl.OnReload() + } + return nil +} diff --git a/plugin/daemon/installer.go b/plugin/daemon/installer.go new file mode 100644 index 0000000..778a9b2 --- /dev/null +++ b/plugin/daemon/installer.go @@ -0,0 +1,53 @@ +/* + * Copyright © 2019 Hedzr Yeh. + */ + +package daemon + +import ( + "errors" + "github.com/hedzr/cmdr" + "log" + "os" + "os/exec" +) + +func runInstaller(cmd *cmdr.Command, args []string) (err error) { + if !isRoot() { + log.Fatal("This program must be run as root! (sudo)") + } + + return +} + +func runUninstaller(cmd *cmdr.Command, args []string) (err error) { + if !isRoot() { + log.Fatal("This program must be run as root! (sudo)") + } + + return +} + +func isRoot() bool { + return os.Getuid() == 0 +} + +// Exec executes a command setting both standard input, output and error. +func Exec(cmd string, args ...string) error { + c := exec.Command(cmd, args...) + c.Stdin = os.Stdin + c.Stdout = os.Stdout + c.Stderr = os.Stderr + + if err := c.Run(); err != nil { + return err + } + return nil +} + +// ExecSudo executes a command under "sudo". +func ExecSudo(cmd string, args ...string) error { + return Exec("sudo", append([]string{cmd}, args...)...) +} + +var ErrNoRoot = errors.New("MUST have administrator privileges") diff --git a/plugin/daemon/main.c.txt b/plugin/daemon/main.c.txt new file mode 100644 index 0000000..c8c2eb9 --- /dev/null +++ b/plugin/daemon/main.c.txt @@ -0,0 +1,50 @@ + +int init_daemon(void){ + + pid_t pid; + int i; + + + /* parent exits , child continues */ + if((pid = fork()) < 0) + return -1; + else if(pid != 0) + exit(0); + + setsid(); /* become session leader */ + + for(i=0;i< NOFILE ;++i) /* close STDOUT, STDIN, STDERR, */ + close(i); + + umask(0); /* clear file mode creation mask */ + return 0; +} + +void sig_term(int signo){ + if(signo == SIGTERM) /* catched signal sent by kill(1) command */ + { + wsio_logit("", "wsiod stopped\n"); + exit(0); + } +} + +/* main program of daemon */ +int main(void){ + if(init_daemon() == -1){ + printf("can't fork self\n"); + exit(0); + } + + wsio_logit("", "wsiod started\n"); + + signal(SIGTERM, sig_term); /* arrange to catch the signal */ + + while (1) { + + // Do what you want here + // … … + + } + + exit(0); +} diff --git a/plugin/daemon/pidfile.go b/plugin/daemon/pidfile.go new file mode 100644 index 0000000..5f81a4b --- /dev/null +++ b/plugin/daemon/pidfile.go @@ -0,0 +1,29 @@ +/* + * Copyright © 2019 Hedzr Yeh. + */ + +package daemon + +import "github.com/hedzr/cmdr" + +type pidFileStruct struct { +} + +var pidfile = &pidFileStruct{} + +func (pf *pidFileStruct) Create(cmd *cmdr.Command) { + // +} + +func (pf *pidFileStruct) Destroy() { + // +} + +type loggerStruct struct { +} + +var logger = &loggerStruct{} + +func (l *loggerStruct) Setup(cmd *cmdr.Command) { + // +}