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