Skip to content

Commit

Permalink
00
Browse files Browse the repository at this point in the history
  • Loading branch information
lei006 committed Jan 4, 2021
1 parent b991878 commit 43f6892
Show file tree
Hide file tree
Showing 37 changed files with 2,675 additions and 20 deletions.
14 changes: 14 additions & 0 deletions daemon/Anydev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[Unit]
Description=Anydev
After=network.target

[Service]
Type=simple
ExecStart=/root/anydev/anydev start
ExecReload=/root/anydev/anydev reload
ExecStop=/root/anydev/anydev stop
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

101 changes: 101 additions & 0 deletions daemon/daemon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Package daemon program is installed as a system service to achieve process daemon
package daemon

import (
"errors"

"github.com/sohaha/zlsgo/zarray"
)

const (
optionKeepAlive = "KeepAlive"
optionKeepAliveDefault = true
optionRunAtLoad = "RunAtLoad"
optionRunAtLoadDefault = true
optionUserService = "UserService"
optionUserServiceDefault = false
optionSessionCreate = "SessionCreate"
optionSessionCreateDefault = false
optionRunWait = "RunWait"
)

type (
// ServiceIfe represents a service that can be run or controlled
ServiceIfe interface {
Run() error
Start() error
Stop() error
Restart() error
Install() error
Uninstall() error
Status() string
String() string
}
Ife interface {
Start(s ServiceIfe) error
Stop(s ServiceIfe) error
}
SystemIfe interface {
String() string
Interactive() bool
Detect() bool
New(i Ife, c *Config) (ServiceIfe, error)
}
// Config provides the setup for a ServiceIfe. The Name field is required.
Config struct {
Name string
DisplayName string
Description string
UserName string
Arguments []string
Executable string
WorkingDir string
RootDir string
// System specific options
// * OS X
// - KeepAlive bool (true)
// - RunAtLoad bool (true)
// - UserService bool (false) - Install as a current user service.
// - SessionCreate bool (false) - Create a full user session.
// * POSIX
// - RunWait func() (wait for SIGNAL) - Do not install signal but wait for this function to return.
// - ReloadSignal string () [USR1, ...] - Signal to send on reaload.
// - PIDFile string () [/run/prog.pid] - Location of the PID file.
Option zarray.DefData
}
)

var (
system SystemIfe
systemRegistry []SystemIfe
ErrNameFieldRequired = errors.New("config.name field is required")
ErrNoServiceSystemDetected = errors.New("no service system detected")
ErrNotAnRootUser = errors.New("need to execute with sudo permission")
ErrNotAnAdministrator = errors.New("please operate with administrator rights")
)

// New creates a new service based on a service interface and configuration
func New(i Ife, c *Config) (ServiceIfe, error) {
if len(c.Name) == 0 {
return nil, ErrNameFieldRequired
}
if system == nil {
return nil, ErrNoServiceSystemDetected
}
return system.New(i, c)
}

func newSystem() SystemIfe {
for _, choice := range systemRegistry {
if !choice.Detect() {
continue
}
return choice
}
return nil
}

func chooseSystem(a ...SystemIfe) {
systemRegistry = a
system = newSystem()
}
246 changes: 246 additions & 0 deletions daemon/daemon_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
package daemon

import (
"errors"
"fmt"
"os"
"os/signal"
"os/user"
"path/filepath"
"strings"
"syscall"
"text/template"
"time"

"github.com/sohaha/zlsgo/zutil"
)

type (
darwinSystem struct{}
darwinLaunchdService struct {
i Ife
*Config
userService bool
}
)

const version = "darwin-launchd"

var interactive = false

func (darwinSystem) String() string {
return version
}

func (darwinSystem) Detect() bool {
return true
}

func (darwinSystem) Interactive() bool {
return interactive
}

func (darwinSystem) New(i Ife, c *Config) (s ServiceIfe, err error) {
userService := c.Option.Bool(optionUserService, optionUserServiceDefault)
s = &darwinLaunchdService{
i: i,
Config: c,
userService: userService,
}
if !userService {
err = isSudo()
}
return s, err
}

func init() {
var err error
chooseSystem(darwinSystem{})
interactive, err = isInteractive()
zutil.CheckErr(err, true)
}

func isInteractive() (bool, error) {
return os.Getppid() != 1, nil
}

func (s *darwinLaunchdService) String() string {
if len(s.DisplayName) > 0 {
return s.DisplayName
}
return s.Name
}

func (s *darwinLaunchdService) getHomeDir() (string, error) {
u, err := user.Current()
if err == nil {
return u.HomeDir, nil
}

homeDir := os.Getenv("HOME")
if homeDir == "" {
return "", errors.New("user home directory not found")
}
return homeDir, nil
}

func (s *darwinLaunchdService) getServiceFilePath() (string, error) {
if s.userService {
homeDir, err := s.getHomeDir()
if err != nil {
return "", err
}
return homeDir + "/Library/LaunchAgents/" + s.Name + ".plist", nil
}
return "/Library/LaunchDaemons/" + s.Name + ".plist", nil
}

func (s *darwinLaunchdService) Install() error {
confPath, err := s.getServiceFilePath()
if err != nil {
return err
}
_, err = os.Stat(confPath)
if err == nil {
return fmt.Errorf("init already exists: %s", confPath)
}

if s.userService {
// ~/Library/LaunchAgents exists
err = os.MkdirAll(filepath.Dir(confPath), 0700)
if err != nil {
return err
}
}

f, err := os.Create(confPath)
if err != nil {
return err
}
defer f.Close()
path := s.execPath()
to := &struct {
*Config
Path string

KeepAlive, RunAtLoad bool
SessionCreate bool
}{
Config: s.Config,
Path: path,
KeepAlive: s.Option.Bool(optionKeepAlive, optionKeepAliveDefault),
RunAtLoad: s.Option.Bool(optionRunAtLoad, optionRunAtLoadDefault),
SessionCreate: s.Option.Bool(optionSessionCreate, optionSessionCreateDefault),
}

functions := template.FuncMap{
"bool": func(v bool) string {
if v {
return "true"
}
return "false"
},
}
t := template.Must(template.New("launchdConfig").Funcs(functions).Parse(launchdConfig))
return t.Execute(f, to)
}

func (s *darwinLaunchdService) Uninstall() error {
var (
err error
confPath string
)
if err = s.Stop(); err != nil {
return err
}
if confPath, err = s.getServiceFilePath(); err != nil {
return err
}
return os.Remove(confPath)
}

func (s *darwinLaunchdService) Start() error {
confPath, err := s.getServiceFilePath()
if err != nil {
return err
}
err = run("launchctl", "load", confPath)

return err
}

func (s *darwinLaunchdService) Stop() error {
confPath, err := s.getServiceFilePath()
if err != nil {
return err
}
_ = run("launchctl", "stop", confPath)
for {
err = run("launchctl", "unload", confPath)
if err == nil || (strings.Contains(err.Error(), "Could not find specified service") || !strings.Contains(err.Error(), "Operation now in progress")) {
err = nil
break
}
time.Sleep(500 * time.Millisecond)
}
return err
}

func (s *darwinLaunchdService) Status() string {
res, _ := runGrep(s.Name+"$", "launchctl", "list")
if res != "" {
return "Running"
}
return "Stop"
}

func (s *darwinLaunchdService) Restart() error {
err := s.Stop()
if err != nil {
return err
}
time.Sleep(50 * time.Millisecond)
return s.Start()
}

func (s *darwinLaunchdService) Run() error {
err := s.i.Start(s)
if err != nil {
return err
}
s.Option.FuncSingle(optionRunWait, func() {
var sigChan = make(chan os.Signal, 3)
signal.Notify(sigChan, os.Interrupt, os.Kill, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
<-sigChan
})()
return s.i.Stop(s)
}

var launchdConfig = `<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd" >
<plist version='1.0'>
<dict>
<key>Label</key><string>{{html .Name}}</string>
<key>ProgramArguments</key>
<array>
<string>{{html .Path}}</string>
{{range .Config.Arguments}}
<string>{{html .}}</string>
{{end}}
</array>
{{if .UserName}}<key>UserName</key><string>{{html .UserName}}</string>{{end}}
{{if .RootDir}}<key>RootDirectory</key><string>{{html .RootDir}}</string>{{end}}
{{if .WorkingDir}}<key>WorkingDirectory</key><string>{{html .WorkingDir}}</string>{{end}}
<key>SessionCreate</key><{{bool .SessionCreate}}/>
<key>KeepAlive</key><{{bool .KeepAlive}}/>
<key>RunAtLoad</key><{{bool .RunAtLoad}}/>
<key>Disabled</key><false/>
</dict>
</plist>
`

// <key>StandardOutPath</key>
// <string>/tmp/zlsgo/{{html .Name}}.log</string>
// <key>StandardErrorPath</key>
// <string>/tmp/zlsgo/{{html .Name}}.err</string>
Loading

0 comments on commit 43f6892

Please sign in to comment.