1// Copyright 2015 Daniel Theophanes. 2// Use of this source code is governed by a zlib-style 3// license that can be found in the LICENSE file. 4 5// Simple service that only works by printing a log message every few seconds. 6package main 7 8import ( 9 "encoding/json" 10 "flag" 11 "fmt" 12 "log" 13 "os" 14 "os/exec" 15 "path/filepath" 16 17 "github.com/kardianos/service" 18) 19 20// Config is the runner app config structure. 21type Config struct { 22 Name, DisplayName, Description string 23 24 Dir string 25 Exec string 26 Args []string 27 Env []string 28 29 Stderr, Stdout string 30} 31 32var logger service.Logger 33 34type program struct { 35 exit chan struct{} 36 service service.Service 37 38 *Config 39 40 cmd *exec.Cmd 41} 42 43func (p *program) Start(s service.Service) error { 44 // Look for exec. 45 // Verify home directory. 46 fullExec, err := exec.LookPath(p.Exec) 47 if err != nil { 48 return fmt.Errorf("Failed to find executable %q: %v", p.Exec, err) 49 } 50 51 p.cmd = exec.Command(fullExec, p.Args...) 52 p.cmd.Dir = p.Dir 53 p.cmd.Env = append(os.Environ(), p.Env...) 54 55 go p.run() 56 return nil 57} 58func (p *program) run() { 59 logger.Info("Starting ", p.DisplayName) 60 defer func() { 61 if service.Interactive() { 62 p.Stop(p.service) 63 } else { 64 p.service.Stop() 65 } 66 }() 67 68 if p.Stderr != "" { 69 f, err := os.OpenFile(p.Stderr, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777) 70 if err != nil { 71 logger.Warningf("Failed to open std err %q: %v", p.Stderr, err) 72 return 73 } 74 defer f.Close() 75 p.cmd.Stderr = f 76 } 77 if p.Stdout != "" { 78 f, err := os.OpenFile(p.Stdout, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777) 79 if err != nil { 80 logger.Warningf("Failed to open std out %q: %v", p.Stdout, err) 81 return 82 } 83 defer f.Close() 84 p.cmd.Stdout = f 85 } 86 87 err := p.cmd.Run() 88 if err != nil { 89 logger.Warningf("Error running: %v", err) 90 } 91 92 return 93} 94func (p *program) Stop(s service.Service) error { 95 close(p.exit) 96 logger.Info("Stopping ", p.DisplayName) 97 if p.cmd.Process != nil { 98 p.cmd.Process.Kill() 99 } 100 if service.Interactive() { 101 os.Exit(0) 102 } 103 return nil 104} 105 106func getConfigPath() (string, error) { 107 fullexecpath, err := os.Executable() 108 if err != nil { 109 return "", err 110 } 111 112 dir, execname := filepath.Split(fullexecpath) 113 ext := filepath.Ext(execname) 114 name := execname[:len(execname)-len(ext)] 115 116 return filepath.Join(dir, name+".json"), nil 117} 118 119func getConfig(path string) (*Config, error) { 120 f, err := os.Open(path) 121 if err != nil { 122 return nil, err 123 } 124 defer f.Close() 125 126 conf := &Config{} 127 128 r := json.NewDecoder(f) 129 err = r.Decode(&conf) 130 if err != nil { 131 return nil, err 132 } 133 return conf, nil 134} 135 136func main() { 137 svcFlag := flag.String("service", "", "Control the system service.") 138 flag.Parse() 139 140 configPath, err := getConfigPath() 141 if err != nil { 142 log.Fatal(err) 143 } 144 config, err := getConfig(configPath) 145 if err != nil { 146 log.Fatal(err) 147 } 148 149 svcConfig := &service.Config{ 150 Name: config.Name, 151 DisplayName: config.DisplayName, 152 Description: config.Description, 153 } 154 155 prg := &program{ 156 exit: make(chan struct{}), 157 158 Config: config, 159 } 160 s, err := service.New(prg, svcConfig) 161 if err != nil { 162 log.Fatal(err) 163 } 164 prg.service = s 165 166 errs := make(chan error, 5) 167 logger, err = s.Logger(errs) 168 if err != nil { 169 log.Fatal(err) 170 } 171 172 go func() { 173 for { 174 err := <-errs 175 if err != nil { 176 log.Print(err) 177 } 178 } 179 }() 180 181 if len(*svcFlag) != 0 { 182 err := service.Control(s, *svcFlag) 183 if err != nil { 184 log.Printf("Valid actions: %q\n", service.ControlAction) 185 log.Fatal(err) 186 } 187 return 188 } 189 err = s.Run() 190 if err != nil { 191 logger.Error(err) 192 } 193} 194