Cleanup and improvements
[src/xds/xds-server.git] / lib / xdsconfig / fileconfig.go
1 package xdsconfig
2
3 import (
4         "encoding/json"
5         "os"
6         "os/user"
7         "path"
8         "path/filepath"
9         "strings"
10
11         common "github.com/iotbzh/xds-common/golib"
12 )
13
14 const (
15         // ConfigDir Directory in user HOME directory where xds config will be saved
16         ConfigDir = ".xds-server"
17         // GlobalConfigFilename Global config filename
18         GlobalConfigFilename = "config.json"
19         // ServerDataFilename Server data filename
20         ServerDataFilename = "server-data.xml"
21         // FoldersConfigFilename Folders config filename
22         FoldersConfigFilename = "server-config_folders.xml"
23 )
24
25 // SyncThingConf definition
26 type SyncThingConf struct {
27         BinDir          string `json:"binDir"`
28         Home            string `json:"home"`
29         GuiAddress      string `json:"gui-address"`
30         GuiAPIKey       string `json:"gui-apikey"`
31         RescanIntervalS int    `json:"rescanIntervalS"`
32 }
33
34 // FileConfig is the JSON structure of xds-server config file (config.json)
35 type FileConfig struct {
36         WebAppDir    string         `json:"webAppDir"`
37         ShareRootDir string         `json:"shareRootDir"`
38         SdkRootDir   string         `json:"sdkRootDir"`
39         HTTPPort     string         `json:"httpPort"`
40         SThgConf     *SyncThingConf `json:"syncthing"`
41         LogsDir      string         `json:"logsDir"`
42 }
43
44 // readGlobalConfig reads configuration from a config file.
45 // Order to determine which config file is used:
46 //  1/ from command line option: "--config myConfig.json"
47 //  2/ $HOME/.xds-server/config.json file
48 //  3/ /etc/xds-server/config.json file
49 //  4/ <xds-server executable dir>/config.json file
50 func readGlobalConfig(c *Config, confFile string) error {
51
52         searchIn := make([]string, 0, 3)
53         if confFile != "" {
54                 searchIn = append(searchIn, confFile)
55         }
56         if usr, err := user.Current(); err == nil {
57                 searchIn = append(searchIn, path.Join(usr.HomeDir, ConfigDir,
58                         GlobalConfigFilename))
59         }
60
61         searchIn = append(searchIn, "/etc/xds-server/config.json")
62
63         exePath := os.Args[0]
64         ee, _ := os.Executable()
65         exeAbsPath, err := filepath.Abs(ee)
66         if err == nil {
67                 exePath, err = filepath.EvalSymlinks(exeAbsPath)
68                 if err == nil {
69                         exePath = filepath.Dir(ee)
70                 } else {
71                         exePath = filepath.Dir(exeAbsPath)
72                 }
73         }
74         searchIn = append(searchIn, path.Join(exePath, "config.json"))
75
76         var cFile *string
77         for _, p := range searchIn {
78                 if _, err := os.Stat(p); err == nil {
79                         cFile = &p
80                         break
81                 }
82         }
83         if cFile == nil {
84                 // No config file found
85                 return nil
86         }
87         c.Log.Infof("Use config file: %s", *cFile)
88
89         // TODO move on viper package to support comments in JSON and also
90         // bind with flags (command line options)
91         // see https://github.com/spf13/viper#working-with-flags
92         fd, _ := os.Open(*cFile)
93         defer fd.Close()
94         fCfg := FileConfig{}
95         if err := json.NewDecoder(fd).Decode(&fCfg); err != nil {
96                 return err
97         }
98
99         // Support environment variables (IOW ${MY_ENV_VAR} syntax) in config.json
100         vars := []*string{
101                 &fCfg.WebAppDir,
102                 &fCfg.ShareRootDir,
103                 &fCfg.SdkRootDir,
104                 &fCfg.LogsDir}
105         if fCfg.SThgConf != nil {
106                 vars = append(vars, &fCfg.SThgConf.Home, &fCfg.SThgConf.BinDir)
107         }
108         for _, field := range vars {
109                 var err error
110                 if *field, err = common.ResolveEnvVar(*field); err != nil {
111                         return err
112                 }
113         }
114
115         // Use config file settings else use default config
116         if fCfg.WebAppDir == "" {
117                 fCfg.WebAppDir = c.FileConf.WebAppDir
118         }
119         if fCfg.ShareRootDir == "" {
120                 fCfg.ShareRootDir = c.FileConf.ShareRootDir
121         }
122         if fCfg.SdkRootDir == "" {
123                 fCfg.SdkRootDir = c.FileConf.SdkRootDir
124         }
125         if fCfg.HTTPPort == "" {
126                 fCfg.HTTPPort = c.FileConf.HTTPPort
127         }
128         if fCfg.LogsDir == "" {
129                 fCfg.LogsDir = c.FileConf.LogsDir
130         }
131
132         // Resolve webapp dir (support relative or full path)
133         fCfg.WebAppDir = strings.Trim(fCfg.WebAppDir, " ")
134         if !strings.HasPrefix(fCfg.WebAppDir, "/") && exePath != "" {
135                 cwd, _ := os.Getwd()
136
137                 // Check first from current directory
138                 for _, rootD := range []string{exePath, cwd} {
139                         ff := path.Join(rootD, fCfg.WebAppDir, "index.html")
140                         if common.Exists(ff) {
141                                 fCfg.WebAppDir = path.Join(rootD, fCfg.WebAppDir)
142                                 break
143                         }
144                 }
145         }
146
147         c.FileConf = fCfg
148         return nil
149 }
150
151 func configFilenameGet(cfgFile string) (string, error) {
152         usr, err := user.Current()
153         if err != nil {
154                 return "", err
155         }
156         return path.Join(usr.HomeDir, ConfigDir, cfgFile), nil
157 }
158
159 // FoldersConfigFilenameGet
160 func FoldersConfigFilenameGet() (string, error) {
161         return configFilenameGet(FoldersConfigFilename)
162 }
163
164 // ServerDataFilenameGet
165 func ServerDataFilenameGet() (string, error) {
166         return configFilenameGet(ServerDataFilename)
167 }