18 "github.com/Sirupsen/logrus"
19 "github.com/iotbzh/xds-server/lib/common"
20 "github.com/iotbzh/xds-server/lib/xdsconfig"
21 "github.com/syncthing/syncthing/lib/config"
25 type SyncThing struct {
34 exitSTChan chan ExitChan
35 client *common.HTTPClient
39 // ExitChan Channel used for process exit
40 type ExitChan struct {
45 // NewSyncThing creates a new instance of Syncthing
46 func NewSyncThing(conf *xdsconfig.Config, log *logrus.Logger) *SyncThing {
47 var url, apiKey, home, binDir string
50 stCfg := conf.FileConf.SThgConf
52 url = stCfg.GuiAddress
53 apiKey = stCfg.GuiAPIKey
59 url = "http://localhost:8384"
61 if url[0:7] != "http://" {
70 if binDir, err = filepath.Abs(filepath.Dir(os.Args[0])); err != nil {
71 binDir = "/usr/local/bin"
80 logsDir: conf.FileConf.LogsDir,
87 // Start Starts syncthing process
88 func (s *SyncThing) startProc(exeName string, args []string, env []string, eChan *chan ExitChan) (*exec.Cmd, error) {
90 // Kill existing process (useful for debug ;-) )
91 if os.Getenv("DEBUG_MODE") != "" {
92 exec.Command("bash", "-c", "pkill -9 "+exeName).Output()
95 path, err := exec.LookPath(path.Join(s.binDir, exeName))
97 return nil, fmt.Errorf("Cannot find %s executable in %s", exeName, s.binDir)
99 cmd := exec.Command(path, args...)
100 cmd.Env = os.Environ()
101 for _, ev := range env {
102 cmd.Env = append(cmd.Env, ev)
107 logFilename := filepath.Join(s.logsDir, exeName+".log")
109 outfile, err := os.Create(logFilename)
111 return nil, fmt.Errorf("Cannot create log file %s", logFilename)
114 cmdOut, err := cmd.StdoutPipe()
116 return nil, fmt.Errorf("Pipe stdout error for : %s", err)
119 go io.Copy(outfile, cmdOut)
127 *eChan = make(chan ExitChan, 1)
128 go func(c *exec.Cmd, oF *os.File) {
130 sts, err := c.Process.Wait()
132 s := sts.Sys().(syscall.WaitStatus)
133 status = s.ExitStatus()
138 s.log.Debugf("%s exited with status %d, err %v", exeName, status, err)
140 *eChan <- ExitChan{status, err}
146 // Start Starts syncthing process
147 func (s *SyncThing) Start() (*exec.Cmd, error) {
150 s.log.Infof(" ST home=%s", s.Home)
151 s.log.Infof(" ST url=%s", s.BaseURL)
156 "--gui-address=" + s.BaseURL,
160 args = append(args, "-gui-apikey=\""+s.APIKey+"\"")
161 s.log.Infof(" ST apikey=%s", s.APIKey)
163 if s.log.Level == logrus.DebugLevel {
164 args = append(args, "-verbose")
168 "STNODEFAULTFOLDER=1",
171 s.STCmd, err = s.startProc("syncthing", args, env, &s.exitSTChan)
176 func (s *SyncThing) stopProc(pname string, proc *os.Process, exit chan ExitChan) {
177 if err := proc.Signal(os.Interrupt); err != nil {
178 s.log.Infof("Proc interrupt %s error: %s", pname, err.Error())
182 case <-time.After(time.Second):
183 // A bigger bonk on the head.
184 if err := proc.Signal(os.Kill); err != nil {
185 s.log.Infof("Proc term %s error: %s", pname, err.Error())
190 s.log.Infof("%s stopped (PID %d)", pname, proc.Pid)
193 // Stop Stops syncthing process
194 func (s *SyncThing) Stop() {
198 s.stopProc("syncthing", s.STCmd.Process, s.exitSTChan)
202 // Connect Establish HTTP connection with Syncthing
203 func (s *SyncThing) Connect() error {
205 s.client, err = common.HTTPNewClient(s.BaseURL,
206 common.HTTPClientConfig{
208 HeaderClientKeyName: "X-Syncthing-ID",
211 msg := ": " + err.Error()
212 if strings.Contains(err.Error(), "connection refused") {
213 msg = fmt.Sprintf("(url: %s)", s.BaseURL)
215 return fmt.Errorf("ERROR: cannot connect to Syncthing %s", msg)
218 return fmt.Errorf("ERROR: cannot connect to Syncthing (null client)")
223 // IDGet returns the Syncthing ID of Syncthing instance running locally
224 func (s *SyncThing) IDGet() (string, error) {
226 if err := s.client.HTTPGet("system/status", &data); err != nil {
229 status := make(map[string]interface{})
230 json.Unmarshal(data, &status)
231 return status["myID"].(string), nil
234 // ConfigGet returns the current Syncthing configuration
235 func (s *SyncThing) ConfigGet() (config.Configuration, error) {
237 config := config.Configuration{}
238 if err := s.client.HTTPGet("system/config", &data); err != nil {
241 err := json.Unmarshal(data, &config)
245 // ConfigSet set Syncthing configuration
246 func (s *SyncThing) ConfigSet(cfg config.Configuration) error {
247 body, err := json.Marshal(cfg)
251 return s.client.HTTPPost("system/config", string(body))