Auto start Syncthing and Syncthing-inotify.
[src/xds/xds-server.git] / lib / xdsconfig / fileconfig.go
1 package xdsconfig
2
3 import (
4         "encoding/json"
5         "fmt"
6         "os"
7         "os/user"
8         "path"
9         "path/filepath"
10         "regexp"
11         "strings"
12 )
13
14 type SyncThingConf struct {
15         BinDir     string `json:"binDir"`
16         Home       string `json:"home"`
17         GuiAddress string `json:"gui-address"`
18         GuiAPIKey  string `json:"gui-apikey"`
19 }
20
21 type FileConfig struct {
22         WebAppDir    string         `json:"webAppDir"`
23         ShareRootDir string         `json:"shareRootDir"`
24         HTTPPort     string         `json:"httpPort"`
25         SThgConf     *SyncThingConf `json:"syncthing"`
26         LogsDir      string         `json:"logsDir"`
27 }
28
29 // getConfigFromFile reads configuration from a config file.
30 // Order to determine which config file is used:
31 //  1/ from command line option: "--config myConfig.json"
32 //  2/ $HOME/.xds/config.json file
33 //  3/ <current_dir>/config.json file
34 //  4/ <xds-server executable dir>/config.json file
35
36 func updateConfigFromFile(c *Config, confFile string) error {
37
38         searchIn := make([]string, 0, 3)
39         if confFile != "" {
40                 searchIn = append(searchIn, confFile)
41         }
42         if usr, err := user.Current(); err == nil {
43                 searchIn = append(searchIn, path.Join(usr.HomeDir, ".xds", "config.json"))
44         }
45         cwd, err := os.Getwd()
46         if err == nil {
47                 searchIn = append(searchIn, path.Join(cwd, "config.json"))
48         }
49         exePath, err := filepath.Abs(filepath.Dir(os.Args[0]))
50         if err == nil {
51                 searchIn = append(searchIn, path.Join(exePath, "config.json"))
52         }
53
54         var cFile *string
55         for _, p := range searchIn {
56                 if _, err := os.Stat(p); err == nil {
57                         cFile = &p
58                         break
59                 }
60         }
61         if cFile == nil {
62                 // No config file found
63                 return nil
64         }
65
66         c.Log.Infof("Use config file: %s", *cFile)
67
68         // TODO move on viper package to support comments in JSON and also
69         // bind with flags (command line options)
70         // see https://github.com/spf13/viper#working-with-flags
71
72         fd, _ := os.Open(*cFile)
73         defer fd.Close()
74         fCfg := FileConfig{}
75         if err := json.NewDecoder(fd).Decode(&fCfg); err != nil {
76                 return err
77         }
78         c.FileConf = fCfg
79
80         // Support environment variables (IOW ${MY_ENV_VAR} syntax) in config.json
81         // TODO: better to use reflect package to iterate on fields and be more generic
82         var rep string
83         if rep, err = resolveEnvVar(fCfg.WebAppDir); err != nil {
84                 return err
85         }
86         fCfg.WebAppDir = path.Clean(rep)
87
88         if rep, err = resolveEnvVar(fCfg.ShareRootDir); err != nil {
89                 return err
90         }
91         fCfg.ShareRootDir = path.Clean(rep)
92
93         if rep, err = resolveEnvVar(fCfg.SThgConf.Home); err != nil {
94                 return err
95         }
96         fCfg.SThgConf.Home = path.Clean(rep)
97
98         // Config file settings overwrite default config
99
100         if fCfg.WebAppDir != "" {
101                 c.WebAppDir = strings.Trim(fCfg.WebAppDir, " ")
102         }
103         // Is it a full path ?
104         if !strings.HasPrefix(c.WebAppDir, "/") && exePath != "" {
105                 // Check first from current directory
106                 for _, rootD := range []string{cwd, exePath} {
107                         ff := path.Join(rootD, c.WebAppDir, "index.html")
108                         if exists(ff) {
109                                 c.WebAppDir = path.Join(rootD, c.WebAppDir)
110                                 break
111                         }
112                 }
113         }
114
115         if fCfg.ShareRootDir != "" {
116                 c.ShareRootDir = fCfg.ShareRootDir
117         }
118
119         if fCfg.HTTPPort != "" {
120                 c.HTTPPort = fCfg.HTTPPort
121         }
122
123         return nil
124 }
125
126 // resolveEnvVar Resolved environment variable regarding the syntax ${MYVAR}
127 func resolveEnvVar(s string) (string, error) {
128         re := regexp.MustCompile("\\${(.*)}")
129         vars := re.FindAllStringSubmatch(s, -1)
130         res := s
131         for _, v := range vars {
132                 val := os.Getenv(v[1])
133                 if val == "" {
134                         return res, fmt.Errorf("ERROR: %s env variable not defined", v[1])
135                 }
136
137                 rer := regexp.MustCompile("\\${" + v[1] + "}")
138                 res = rer.ReplaceAllString(res, val)
139         }
140
141         return res, nil
142 }
143
144 // exists returns whether the given file or directory exists or not
145 func exists(path string) bool {
146         _, err := os.Stat(path)
147         if err == nil {
148                 return true
149         }
150         if os.IsNotExist(err) {
151                 return false
152         }
153         return true
154 }