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