Increase timeout for Syncthing startup.
[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         "strings"
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         MFolder     *model.Folder
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(1)
102 }
103
104 // XDS Server application main routine
105 func xdsApp(cliCtx *cli.Context) error {
106         var err error
107
108         // Create XDS server context
109         ctx := NewContext(cliCtx)
110
111         // Load config
112         cfg, err := xdsconfig.Init(ctx.Cli, ctx.Log)
113         if err != nil {
114                 return cli.NewExitError(err, 2)
115         }
116         ctx.Config = cfg
117
118         // TODO allow to redirect stdout/sterr into logs file
119         //logFilename := filepath.Join(ctx.Config.FileConf.LogsDir + "xds-server.log")
120
121         // FIXME - add a builder interface and support other builder type (eg. native)
122         builderType := "syncthing"
123
124         switch builderType {
125         case "syncthing":
126
127                 // Start local instance of Syncthing and Syncthing-notify
128                 ctx.SThg = st.NewSyncThing(ctx.Config, ctx.Log)
129
130                 ctx.Log.Infof("Starting Syncthing...")
131                 ctx.SThgCmd, err = ctx.SThg.Start()
132                 if err != nil {
133                         return cli.NewExitError(err, 2)
134                 }
135                 fmt.Printf("Syncthing started (PID %d)\n", ctx.SThgCmd.Process.Pid)
136
137                 ctx.Log.Infof("Starting Syncthing-inotify...")
138                 ctx.SThgInotCmd, err = ctx.SThg.StartInotify()
139                 if err != nil {
140                         return cli.NewExitError(err, 2)
141                 }
142                 fmt.Printf("Syncthing-inotify started (PID %d)\n", ctx.SThgInotCmd.Process.Pid)
143
144                 // Establish connection with local Syncthing (retry if connection fail)
145                 fmt.Printf("Establishing connection with Syncthing...\n")
146                 time.Sleep(2 * time.Second)
147                 maxRetry := 30
148                 retry := maxRetry
149                 err = nil
150                 for retry > 0 {
151                         if err = ctx.SThg.Connect(); err == nil {
152                                 break
153                         }
154                         ctx.Log.Warningf("Establishing connection to Syncthing (retry %d/%d)", retry, maxRetry)
155                         time.Sleep(time.Second)
156                         retry--
157                 }
158                 if err != nil || retry == 0 {
159                         return cli.NewExitError(err, 2)
160                 }
161
162                 // Retrieve Syncthing config
163                 id, err := ctx.SThg.IDGet()
164                 if err != nil {
165                         return cli.NewExitError(err, 2)
166                 }
167
168                 if ctx.Config.Builder, err = xdsconfig.NewBuilderConfig(id); err != nil {
169                         return cli.NewExitError(err, 2)
170                 }
171
172                 // Retrieve initial Syncthing config
173
174                 // FIXME: cannot retrieve default SDK, need to save on disk or somewhere
175                 // else all config to be able to restore it.
176                 defaultSdk := ""
177                 stCfg, err := ctx.SThg.ConfigGet()
178                 if err != nil {
179                         return cli.NewExitError(err, 2)
180                 }
181                 for _, stFld := range stCfg.Folders {
182                         relativePath := strings.TrimPrefix(stFld.RawPath, ctx.Config.ShareRootDir)
183                         if relativePath == "" {
184                                 relativePath = stFld.RawPath
185                         }
186
187                         newFld := xdsconfig.NewFolderConfig(stFld.ID, stFld.Label, ctx.Config.ShareRootDir, strings.TrimRight(relativePath, "/"), defaultSdk)
188                         ctx.Config.Folders = ctx.Config.Folders.Update(xdsconfig.FoldersConfig{newFld})
189                 }
190
191                 // Init model folder
192                 ctx.MFolder = model.NewFolder(ctx.Config, ctx.SThg)
193
194         default:
195                 err = fmt.Errorf("Unsupported builder type")
196                 return cli.NewExitError(err, 3)
197         }
198
199         // Init cross SDKs
200         ctx.SDKs, err = crosssdk.Init(ctx.Config, ctx.Log)
201         if err != nil {
202                 return cli.NewExitError(err, 2)
203         }
204
205         // Create and start Web Server
206         ctx.WWWServer = webserver.New(ctx.Config, ctx.MFolder, ctx.SDKs, ctx.Log)
207         if err = ctx.WWWServer.Serve(); err != nil {
208                 ctx.Log.Println(err)
209                 return cli.NewExitError(err, 3)
210         }
211
212         return cli.NewExitError("Program exited ", 4)
213 }
214
215 // main
216 func main() {
217
218         // Create a new instance of the logger
219         log := logrus.New()
220
221         // Create a new App instance
222         app := cli.NewApp()
223         app.Name = appName
224         app.Description = appDescription
225         app.Usage = appUsage
226         app.Version = AppVersion + " (" + AppSubVersion + ")"
227         app.Authors = appAuthors
228         app.Copyright = appCopyright
229         app.Metadata = make(map[string]interface{})
230         app.Metadata["version"] = AppVersion
231         app.Metadata["git-tag"] = AppSubVersion
232         app.Metadata["logger"] = log
233
234         app.Flags = []cli.Flag{
235                 cli.StringFlag{
236                         Name:   "config, c",
237                         Usage:  "JSON config file to use\n\t",
238                         EnvVar: "APP_CONFIG",
239                 },
240                 cli.StringFlag{
241                         Name:   "log, l",
242                         Value:  "error",
243                         Usage:  "logging level (supported levels: panic, fatal, error, warn, info, debug)\n\t",
244                         EnvVar: "LOG_LEVEL",
245                 },
246         }
247
248         // only one action: Web Server
249         app.Action = xdsApp
250
251         app.Run(os.Args)
252 }