import (
"encoding/json"
+ "io"
"io/ioutil"
"os"
"path"
+ "path/filepath"
+ "regexp"
"strings"
"syscall"
"time"
"os/exec"
"github.com/Sirupsen/logrus"
- "github.com/iotbzh/xds-agent/lib/common"
"github.com/iotbzh/xds-agent/lib/xdsconfig"
+ common "github.com/iotbzh/xds-common/golib"
"github.com/syncthing/syncthing/lib/config"
)
// SyncThing .
type SyncThing struct {
BaseURL string
- ApiKey string
+ APIKey string
Home string
STCmd *exec.Cmd
STICmd *exec.Cmd
// Private fields
binDir string
+ logsDir string
exitSTChan chan ExitChan
exitSTIChan chan ExitChan
client *common.HTTPClient
log *logrus.Logger
}
-// Monitor process exit
+// ExitChan Channel used for process exit
type ExitChan struct {
status int
err error
}
// NewSyncThing creates a new instance of Syncthing
-//func NewSyncThing(url string, apiKey string, home string, log *logrus.Logger) *SyncThing {
-func NewSyncThing(conf *xdsconfig.SyncThingConf, log *logrus.Logger) *SyncThing {
- url := conf.GuiAddress
- apiKey := conf.GuiAPIKey
- home := conf.Home
+func NewSyncThing(conf *xdsconfig.Config, log *logrus.Logger) *SyncThing {
+ var url, apiKey, home, binDir string
+
+ stCfg := conf.FileConf.SThgConf
+ if stCfg != nil {
+ url = stCfg.GuiAddress
+ apiKey = stCfg.GuiAPIKey
+ home = stCfg.Home
+ binDir = stCfg.BinDir
+ }
+
+ if url == "" {
+ url = "http://localhost:8386"
+ }
+ if url[0:7] != "http://" {
+ url = "http://" + url
+ }
+
+ if home == "" {
+ panic("home parameter must be set")
+ }
s := SyncThing{
BaseURL: url,
- ApiKey: apiKey,
+ APIKey: apiKey,
Home: home,
- binDir: conf.BinDir,
+ binDir: binDir,
+ logsDir: conf.FileConf.LogsDir,
log: log,
}
- if s.BaseURL == "" {
- s.BaseURL = "http://localhost:8384"
- }
- if s.BaseURL[0:7] != "http://" {
- s.BaseURL = "http://" + s.BaseURL
- }
-
return &s
}
// Start Starts syncthing process
func (s *SyncThing) startProc(exeName string, args []string, env []string, eChan *chan ExitChan) (*exec.Cmd, error) {
+ var err error
+ var exePath string
// Kill existing process (useful for debug ;-) )
if os.Getenv("DEBUG_MODE") != "" {
exec.Command("bash", "-c", "pkill -9 "+exeName).Output()
}
- path, err := exec.LookPath(path.Join(s.binDir, exeName))
+ // When not set (or set to '.') set bin to path of xds-agent executable
+ bdir := s.binDir
+ if bdir == "" || bdir == "." {
+ exe, _ := os.Executable()
+ if exeAbsPath, err := filepath.Abs(exe); err == nil {
+ if exePath, err := filepath.EvalSymlinks(exeAbsPath); err == nil {
+ bdir = filepath.Dir(exePath)
+ }
+ }
+ }
+
+ exePath, err = exec.LookPath(path.Join(bdir, exeName))
if err != nil {
- return nil, fmt.Errorf("Cannot find %s executable in %s", exeName, s.binDir)
+ // Let's try in /opt/AGL/bin
+ exePath, err = exec.LookPath(path.Join("opt", "AGL", "bin", exeName))
+ if err != nil {
+ return nil, fmt.Errorf("Cannot find %s executable in %s", exeName, bdir)
+ }
}
- cmd := exec.Command(path, args...)
+ cmd := exec.Command(exePath, args...)
cmd.Env = os.Environ()
for _, ev := range env {
cmd.Env = append(cmd.Env, ev)
}
+ // open log file
+ var outfile *os.File
+ logFilename := filepath.Join(s.logsDir, exeName+".log")
+ if s.logsDir != "" {
+ outfile, err := os.Create(logFilename)
+ if err != nil {
+ return nil, fmt.Errorf("Cannot create log file %s", logFilename)
+ }
+
+ cmdOut, err := cmd.StdoutPipe()
+ if err != nil {
+ return nil, fmt.Errorf("Pipe stdout error for : %s", err)
+ }
+
+ go io.Copy(outfile, cmdOut)
+ }
+
err = cmd.Start()
if err != nil {
return nil, err
}
*eChan = make(chan ExitChan, 1)
- go func(c *exec.Cmd) {
+ go func(c *exec.Cmd, oF *os.File) {
status := 0
- cmdOut, err := c.StdoutPipe()
- if err == nil {
- s.log.Errorf("Pipe stdout error for : %s", err)
- } else if cmdOut != nil {
- stdOutput, _ := ioutil.ReadAll(cmdOut)
- fmt.Printf("STDOUT: %s\n", stdOutput)
- }
sts, err := c.Process.Wait()
if !sts.Success() {
s := sts.Sys().(syscall.WaitStatus)
status = s.ExitStatus()
}
+ if oF != nil {
+ oF.Close()
+ }
+ s.log.Debugf("%s exited with status %d, err %v", exeName, status, err)
+
*eChan <- ExitChan{status, err}
- }(cmd)
+ }(cmd, outfile)
return cmd, nil
}
// Start Starts syncthing process
func (s *SyncThing) Start() (*exec.Cmd, error) {
var err error
+
+ s.log.Infof(" ST home=%s", s.Home)
+ s.log.Infof(" ST url=%s", s.BaseURL)
+
args := []string{
"--home=" + s.Home,
"-no-browser",
"--gui-address=" + s.BaseURL,
}
- if s.ApiKey != "" {
- args = append(args, "-gui-apikey=\""+s.ApiKey+"\"")
+ if s.APIKey != "" {
+ args = append(args, "-gui-apikey=\""+s.APIKey+"\"")
+ s.log.Infof(" ST apikey=%s", s.APIKey)
}
if s.log.Level == logrus.DebugLevel {
args = append(args, "-verbose")
env := []string{
"STNODEFAULTFOLDER=1",
+ "STNOUPGRADE=1",
+ }
+
+ // XXX - temporary hack because -gui-apikey seems to correctly handle by
+ // syncthing the early first time
+ stConfigFile := filepath.Join(s.Home, "config.xml")
+ if s.APIKey != "" && !common.Exists(stConfigFile) {
+ s.log.Infof("Stop and restart Syncthing (hack for apikey setting)")
+ s.STCmd, err = s.startProc("syncthing", args, env, &s.exitSTChan)
+ tmo := 20
+ for ; tmo > 0; tmo-- {
+ s.log.Debugf("Waiting Syncthing config.xml creation (%v)\n", tmo)
+ time.Sleep(500 * time.Millisecond)
+ if common.Exists(stConfigFile) {
+ break
+ }
+ }
+ if tmo <= 0 {
+ return nil, fmt.Errorf("Cannot start Syncthing for config file creation")
+ }
+ s.Stop()
+ read, err := ioutil.ReadFile(stConfigFile)
+ if err != nil {
+ return nil, fmt.Errorf("Cannot read Syncthing config file for apikey setting")
+ }
+ re := regexp.MustCompile(`<apikey>.*</apikey>`)
+ newContents := re.ReplaceAllString(string(read), "<apikey>"+s.APIKey+"</apikey>")
+ err = ioutil.WriteFile(stConfigFile, []byte(newContents), 0)
+ if err != nil {
+ return nil, fmt.Errorf("Cannot write Syncthing config file to set apikey")
+ }
}
s.STCmd, err = s.startProc("syncthing", args, env, &s.exitSTChan)
+ // Use autogenerated apikey if not set by config.json
+ if s.APIKey == "" {
+ if fd, err := os.Open(stConfigFile); err == nil {
+ defer fd.Close()
+ if b, err := ioutil.ReadAll(fd); err == nil {
+ re := regexp.MustCompile("<apikey>(.*)</apikey>")
+ key := re.FindStringSubmatch(string(b))
+ if len(key) >= 1 {
+ s.APIKey = key[1]
+ }
+ }
+ }
+ }
+
return s.STCmd, err
}
// StartInotify Starts syncthing-inotify process
func (s *SyncThing) StartInotify() (*exec.Cmd, error) {
var err error
+ exeName := "syncthing-inotify"
+
+ s.log.Infof(" STI url=%s", s.BaseURL)
args := []string{
- "--home=" + s.Home,
"-target=" + s.BaseURL,
}
+ if s.APIKey != "" {
+ args = append(args, "-api="+s.APIKey)
+ s.log.Infof("%s uses apikey=%s", exeName, s.APIKey)
+ }
if s.log.Level == logrus.DebugLevel {
args = append(args, "-verbosity=4")
}
env := []string{}
- s.STICmd, err = s.startProc("syncthing-inotify", args, env, &s.exitSTIChan)
+ s.STICmd, err = s.startProc(exeName, args, env, &s.exitSTIChan)
return s.STICmd, err
}
func (s *SyncThing) stopProc(pname string, proc *os.Process, exit chan ExitChan) {
if err := proc.Signal(os.Interrupt); err != nil {
- s.log.Errorf("Proc interrupt %s error: %s", pname, err.Error())
+ s.log.Infof("Proc interrupt %s error: %s", pname, err.Error())
select {
case <-exit:
case <-time.After(time.Second):
// A bigger bonk on the head.
if err := proc.Signal(os.Kill); err != nil {
- s.log.Errorf("Proc term %s error: %s", pname, err.Error())
+ s.log.Infof("Proc term %s error: %s", pname, err.Error())
}
<-exit
}
if s.client == nil {
return fmt.Errorf("ERROR: cannot connect to Syncthing (null client)")
}
+
+ s.client.SetLogLevel(s.log.Level.String())
+ s.client.LoggerPrefix = "SYNCTHING: "
+ s.client.LoggerOut = s.log.Out
+
return nil
}