Cosmetic fix
[src/xds/xds-server.git] / main.go
1 // TODO add Doc
2 //
3 package main
4
5 import (
6         "fmt"
7         "os"
8         "os/exec"
9         "os/signal"
10         "path/filepath"
11         "syscall"
12         "time"
13
14         "github.com/Sirupsen/logrus"
15         "github.com/codegangsta/cli"
16         "github.com/iotbzh/xds-server/lib/crosssdk"
17         "github.com/iotbzh/xds-server/lib/model"
18         "github.com/iotbzh/xds-server/lib/syncthing"
19         "github.com/iotbzh/xds-server/lib/webserver"
20         "github.com/iotbzh/xds-server/lib/xdsconfig"
21 )
22
23 const (
24         appName        = "xds-server"
25         appDescription = "X(cross) Development System Server is a web server that allows to remotely cross build applications."
26         appCopyright   = "Apache-2.0"
27         appUsage       = "X(cross) Development System Server"
28 )
29
30 var appAuthors = []cli.Author{
31         cli.Author{Name: "Sebastien Douheret", Email: "sebastien@iot.bzh"},
32 }
33
34 // AppVersion is the version of this application
35 var AppVersion = "?.?.?"
36
37 // AppSubVersion is the git tag id added to version string
38 // Should be set by compilation -ldflags "-X main.AppSubVersion=xxx"
39 var AppSubVersion = "unknown-dev"
40
41 // Context holds the XDS server context
42 type Context struct {
43         ProgName    string
44         Cli         *cli.Context
45         Config      *xdsconfig.Config
46         Log         *logrus.Logger
47         SThg        *st.SyncThing
48         SThgCmd     *exec.Cmd
49         SThgInotCmd *exec.Cmd
50         MFolders    *model.Folders
51         SDKs        *crosssdk.SDKs
52         WWWServer   *webserver.Server
53         Exit        chan os.Signal
54 }
55
56 // NewContext Create a new instance of XDS server
57 func NewContext(cliCtx *cli.Context) *Context {
58         var err error
59
60         // Set logger level and formatter
61         log := cliCtx.App.Metadata["logger"].(*logrus.Logger)
62
63         logLevel := cliCtx.GlobalString("log")
64         if logLevel == "" {
65                 logLevel = "error" // FIXME get from Config DefaultLogLevel
66         }
67         if log.Level, err = logrus.ParseLevel(logLevel); err != nil {
68                 fmt.Printf("Invalid log level : \"%v\"\n", logLevel)
69                 os.Exit(1)
70         }
71         log.Formatter = &logrus.TextFormatter{}
72
73         // Define default configuration
74         ctx := Context{
75                 ProgName: cliCtx.App.Name,
76                 Cli:      cliCtx,
77                 Log:      log,
78                 Exit:     make(chan os.Signal, 1),
79         }
80
81         // register handler on SIGTERM / exit
82         signal.Notify(ctx.Exit, os.Interrupt, syscall.SIGTERM)
83         go handlerSigTerm(&ctx)
84
85         return &ctx
86 }
87
88 // Handle exit and properly stop/close all stuff
89 func handlerSigTerm(ctx *Context) {
90         <-ctx.Exit
91         if ctx.SThg != nil {
92                 ctx.Log.Infof("Stoping Syncthing... (PID %d)", ctx.SThgCmd.Process.Pid)
93                 ctx.SThg.Stop()
94                 ctx.Log.Infof("Stoping Syncthing-inotify... (PID %d)", ctx.SThgInotCmd.Process.Pid)
95                 ctx.SThg.StopInotify()
96         }
97         if ctx.WWWServer != nil {
98                 ctx.Log.Infof("Stoping Web server...")
99                 ctx.WWWServer.Stop()
100         }
101         os.Exit(0)
102 }
103
104 // Helper function to log message on both stdout and logger
105 func logPrint(ctx *Context, format string, args ...interface{}) {
106         fmt.Printf(format, args...)
107         if ctx.Log.Out != os.Stdout {
108                 ctx.Log.Infof(format, args...)
109         }
110 }
111
112 // XDS Server application main routine
113 func xdsApp(cliCtx *cli.Context) error {
114         var err error
115
116         // Create XDS server context
117         ctx := NewContext(cliCtx)
118
119         // Load config
120         cfg, err := xdsconfig.Init(ctx.Cli, ctx.Log)
121         if err != nil {
122                 return cli.NewExitError(err, -2)
123         }
124         ctx.Config = cfg
125
126         // Logs redirected into a file when logfile option or logsDir config is set
127         ctx.Config.LogVerboseOut = os.Stderr
128         if ctx.Config.FileConf.LogsDir != "" {
129                 if ctx.Config.Options.LogFile != "stdout" {
130                         logFile := ctx.Config.Options.LogFile
131
132                         fdL, err := os.OpenFile(logFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
133                         if err != nil {
134                                 msgErr := fmt.Sprintf("Cannot create log file %s", logFile)
135                                 return cli.NewExitError(msgErr, int(syscall.EPERM))
136                         }
137                         ctx.Log.Out = fdL
138
139                         logPrint(ctx, "Logging file: %s\n", logFile)
140                 }
141
142                 logFileHTTPReq := filepath.Join(ctx.Config.FileConf.LogsDir, "xds-server-verbose.log")
143                 fdLH, err := os.OpenFile(logFileHTTPReq, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
144                 if err != nil {
145                         msgErr := fmt.Sprintf("Cannot create log file %s", logFileHTTPReq)
146                         return cli.NewExitError(msgErr, int(syscall.EPERM))
147                 }
148                 ctx.Config.LogVerboseOut = fdLH
149
150                 logPrint(ctx, "Logging file for HTTP requests: %s\n", logFileHTTPReq)
151         }
152
153         // Create syncthing instance when section "syncthing" is present in config.json
154         if ctx.Config.FileConf.SThgConf != nil {
155                 ctx.SThg = st.NewSyncThing(ctx.Config, ctx.Log)
156         }
157
158         // Start local instance of Syncthing and Syncthing-notify
159         if ctx.SThg != nil {
160                 ctx.Log.Infof("Starting Syncthing...")
161                 ctx.SThgCmd, err = ctx.SThg.Start()
162                 if err != nil {
163                         return cli.NewExitError(err, -4)
164                 }
165                 logPrint(ctx, "Syncthing started (PID %d)\n", ctx.SThgCmd.Process.Pid)
166
167                 ctx.Log.Infof("Starting Syncthing-inotify...")
168                 ctx.SThgInotCmd, err = ctx.SThg.StartInotify()
169                 if err != nil {
170                         return cli.NewExitError(err, -4)
171                 }
172                 logPrint(ctx, "Syncthing-inotify started (PID %d)\n", ctx.SThgInotCmd.Process.Pid)
173
174                 // Establish connection with local Syncthing (retry if connection fail)
175                 logPrint(ctx, "Establishing connection with Syncthing...\n")
176                 time.Sleep(2 * time.Second)
177                 maxRetry := 30
178                 retry := maxRetry
179                 err = nil
180                 for retry > 0 {
181                         if err = ctx.SThg.Connect(); err == nil {
182                                 break
183                         }
184                         ctx.Log.Warningf("Establishing connection to Syncthing (retry %d/%d)", retry, maxRetry)
185                         time.Sleep(time.Second)
186                         retry--
187                 }
188                 if err != nil || retry == 0 {
189                         return cli.NewExitError(err, -4)
190                 }
191
192                 // FIXME: do we still need Builder notion ? if no cleanup
193                 if ctx.Config.Builder, err = xdsconfig.NewBuilderConfig(ctx.SThg.MyID); err != nil {
194                         return cli.NewExitError(err, -4)
195                 }
196         }
197
198         // Init model folder
199         ctx.MFolders = model.FoldersNew(ctx.Config, ctx.SThg)
200
201         // Load initial folders config from disk
202         if err := ctx.MFolders.LoadConfig(); err != nil {
203                 return cli.NewExitError(err, -5)
204         }
205
206         // Init cross SDKs
207         ctx.SDKs, err = crosssdk.Init(ctx.Config, ctx.Log)
208         if err != nil {
209                 return cli.NewExitError(err, -6)
210         }
211
212         // Create Web Server
213         ctx.WWWServer = webserver.New(ctx.Config, ctx.MFolders, ctx.SDKs, ctx.Log)
214
215         // Run Web Server until exit requested (blocking call)
216         if err = ctx.WWWServer.Serve(); err != nil {
217                 ctx.Log.Println(err)
218                 return cli.NewExitError(err, -7)
219         }
220
221         return cli.NewExitError("Program exited ", -99)
222 }
223
224 // main
225 func main() {
226
227         // Create a new instance of the logger
228         log := logrus.New()
229
230         // Create a new App instance
231         app := cli.NewApp()
232         app.Name = appName
233         app.Description = appDescription
234         app.Usage = appUsage
235         app.Version = AppVersion + " (" + AppSubVersion + ")"
236         app.Authors = appAuthors
237         app.Copyright = appCopyright
238         app.Metadata = make(map[string]interface{})
239         app.Metadata["version"] = AppVersion
240         app.Metadata["git-tag"] = AppSubVersion
241         app.Metadata["logger"] = log
242
243         app.Flags = []cli.Flag{
244                 cli.StringFlag{
245                         Name:   "config, c",
246                         Usage:  "JSON config file to use\n\t",
247                         EnvVar: "APP_CONFIG",
248                 },
249                 cli.StringFlag{
250                         Name:   "log, l",
251                         Value:  "error",
252                         Usage:  "logging level (supported levels: panic, fatal, error, warn, info, debug)\n\t",
253                         EnvVar: "LOG_LEVEL",
254                 },
255                 cli.StringFlag{
256                         Name:   "logfile",
257                         Value:  "stdout",
258                         Usage:  "filename where logs will be redirected (default stdout)\n\t",
259                         EnvVar: "LOG_FILENAME",
260                 },
261                 cli.BoolFlag{
262                         Name:   "no-folderconfig, nfc",
263                         Usage:  fmt.Sprintf("Do not read folder config file (%s)\n\t", xdsconfig.FoldersConfigFilename),
264                         EnvVar: "NO_FOLDERCONFIG",
265                 },
266         }
267
268         // only one action: Web Server
269         app.Action = xdsApp
270
271         app.Run(os.Args)
272 }