github.com/dmmcquay/sia@v1.3.1-0.20180712220038-9f8d535311b9/cmd/siad/server.go (about) 1 package main 2 3 import ( 4 "archive/zip" 5 "bytes" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "math/big" 12 "net" 13 "net/http" 14 "os" 15 "path" 16 "path/filepath" 17 "runtime" 18 "sort" 19 "strings" 20 "sync" 21 "syscall" 22 "time" 23 24 "github.com/NebulousLabs/Sia/build" 25 "github.com/NebulousLabs/Sia/modules" 26 "github.com/NebulousLabs/Sia/modules/consensus" 27 "github.com/NebulousLabs/Sia/modules/explorer" 28 "github.com/NebulousLabs/Sia/modules/gateway" 29 "github.com/NebulousLabs/Sia/modules/host" 30 "github.com/NebulousLabs/Sia/modules/miner" 31 "github.com/NebulousLabs/Sia/modules/renter" 32 "github.com/NebulousLabs/Sia/modules/transactionpool" 33 "github.com/NebulousLabs/Sia/modules/wallet" 34 "github.com/NebulousLabs/Sia/node/api" 35 "github.com/NebulousLabs/Sia/types" 36 37 "github.com/inconshreveable/go-update" 38 "github.com/julienschmidt/httprouter" 39 "github.com/kardianos/osext" 40 ) 41 42 var errEmptyUpdateResponse = errors.New("API call to https://api.github.com/repos/NebulousLabs/Sia/releases/latest is returning an empty response") 43 44 type ( 45 // Server creates and serves a HTTP server that offers communication with a 46 // Sia API. 47 Server struct { 48 httpServer *http.Server 49 listener net.Listener 50 config Config 51 moduleClosers []moduleCloser 52 api http.Handler 53 mu sync.Mutex 54 } 55 56 // moduleCloser defines a struct that closes modules, defined by a name and 57 // an underlying io.Closer. 58 moduleCloser struct { 59 name string 60 io.Closer 61 } 62 63 // SiaConstants is a struct listing all of the constants in use. 64 SiaConstants struct { 65 BlockFrequency types.BlockHeight `json:"blockfrequency"` 66 BlockSizeLimit uint64 `json:"blocksizelimit"` 67 ExtremeFutureThreshold types.Timestamp `json:"extremefuturethreshold"` 68 FutureThreshold types.Timestamp `json:"futurethreshold"` 69 GenesisTimestamp types.Timestamp `json:"genesistimestamp"` 70 MaturityDelay types.BlockHeight `json:"maturitydelay"` 71 MedianTimestampWindow uint64 `json:"mediantimestampwindow"` 72 SiafundCount types.Currency `json:"siafundcount"` 73 SiafundPortion *big.Rat `json:"siafundportion"` 74 TargetWindow types.BlockHeight `json:"targetwindow"` 75 76 InitialCoinbase uint64 `json:"initialcoinbase"` 77 MinimumCoinbase uint64 `json:"minimumcoinbase"` 78 79 RootTarget types.Target `json:"roottarget"` 80 RootDepth types.Target `json:"rootdepth"` 81 82 // DEPRECATED: same values as MaxTargetAdjustmentUp and 83 // MaxTargetAdjustmentDown. 84 MaxAdjustmentUp *big.Rat `json:"maxadjustmentup"` 85 MaxAdjustmentDown *big.Rat `json:"maxadjustmentdown"` 86 87 MaxTargetAdjustmentUp *big.Rat `json:"maxtargetadjustmentup"` 88 MaxTargetAdjustmentDown *big.Rat `json:"maxtargetadjustmentdown"` 89 90 SiacoinPrecision types.Currency `json:"siacoinprecision"` 91 } 92 93 // DaemonVersion holds the version information for siad 94 DaemonVersion struct { 95 Version string `json:"version"` 96 GitRevision string `json:"gitrevision"` 97 BuildTime string `json:"buildtime"` 98 } 99 // UpdateInfo indicates whether an update is available, and to what 100 // version. 101 UpdateInfo struct { 102 Available bool `json:"available"` 103 Version string `json:"version"` 104 } 105 // githubRelease represents some of the JSON returned by the GitHub release API 106 // endpoint. Only the fields relevant to updating are included. 107 githubRelease struct { 108 TagName string `json:"tag_name"` 109 Assets []struct { 110 Name string `json:"name"` 111 DownloadURL string `json:"browser_download_url"` 112 } `json:"assets"` 113 } 114 ) 115 116 const ( 117 // The developer key is used to sign updates and other important Sia- 118 // related information. 119 developerKey = `-----BEGIN PUBLIC KEY----- 120 MIIEIjANBgkqhkiG9w0BAQEFAAOCBA8AMIIECgKCBAEAsoQHOEU6s/EqMDtw5HvA 121 YPTUaBgnviMFbG3bMsRqSCD8ug4XJYh+Ik6WP0xgq+OPDehPiaXK8ghAtBiW1EJK 122 mBRwlABXAzREZg8wRfG4l8Zj6ckAPJOgLn0jobXy6/SCQ+jZSWh4Y8DYr+LA3Mn3 123 EOga7Jvhpc3fTZ232GBGJ1BobuNfRfYmwxSphv+T4vzIA3JUjVfa8pYZGIjh5XbJ 124 5M8Lef0Xa9eqr6lYm5kQoOIXeOW56ImqI2BKg/I9NGw9phSPbwaFfy1V2kfHp5Xy 125 DtKnyj/O9zDi+qUKjoIivnEoV+3DkioHUWv7Fpf7yx/9cPyckwvaBsTd9Cfp4uBx 126 qJ5Qyv69VZQiD6DikNwgzjGbIjiLwfTObhInKZUoYl48yzgkR80ja5TW0SoidNvO 127 4WTbWcLolOl522VarTs7wlgbq0Ad7yrNVnHzo447v2iT20ILH2oeAcZqvpcvRmTl 128 U6uKoaVmBH3D3Y19dPluOjK53BrqfQ5L8RFli2wEJktPsi5fUTd4UI9BgnUieuDz 129 S7h/VH9bv9ZVvyjpu/uVjdvaikT3zbIy9J6wS6uE5qPLPhI4B9HgbrQ03muDGpql 130 gZrMiL3GdYrBiqpIbaWHfM0eMWEK3ZScUdtCgUXMMrkvaUJ4g9wEgbONFVVOMIV+ 131 YubIuzBFqug6WyxN/EAM/6Fss832AwVPcYM0NDTVGVdVplLMdN8YNjrYuaPngBCG 132 e8QaTWtHzLujyBIkVdAHqfkRS65jp7JLLMx7jUA74/E/v+0cNew3Y1p2gt3iQH8t 133 w93xn9IPUfQympc4h3KerP/Yn6P/qAh68jQkOiMMS+VbCq/BOn8Q3GbR+8rQ8dmk 134 qVoGA7XrPQ6bymKBTghk2Ek+ZjxrpAoj0xYoYyzWf0kuxeOT8kAjlLLmfQ8pm75S 135 QHLqH49FyfeETIU02rkw2oMOX/EYdJzZukHuouwbpKSElpRx+xTnaSemMJo+U7oX 136 xVjma3Zynh9w12abnFWkZKtrxwXv7FCSzb0UZmMWUqWzCS03Rrlur21jp4q2Wl71 137 Vt92xe5YbC/jbh386F1e/qGq6p+D1AmBynIpp/HE6fPsc9LWgJDDkREZcp7hthGW 138 IdYPeP3CesFHnsZMueZRib0i7lNUkBSRneO1y/C9poNv1vOeTCNEE0jvhp/XOJuc 139 yCQtrUSNALsvm7F+bnwP2F7K34k7MOlOgnTGqCqW+9WwBcjR44B0HI+YERCcRmJ8 140 krBuVo9OBMV0cYBWpjo3UI9j3lHESCYhLnCz7SPap7C1yORc2ydJh+qjKqdLBHom 141 t+JydcdJLbIG+kb3jB9QIIu5A4TlSGlHV6ewtxIWLS1473jEkITiVTt0Y5k+VLfW 142 bwIDAQAB 143 -----END PUBLIC KEY-----` 144 ) 145 146 // version returns the version number of a non-LTS release. This assumes that 147 // tag names will always be of the form "vX.Y.Z". 148 func (r *githubRelease) version() string { 149 return strings.TrimPrefix(r.TagName, "v") 150 } 151 152 // byVersion sorts non-LTS releases by their version string, placing the highest 153 // version number first. 154 type byVersion []githubRelease 155 156 func (rs byVersion) Len() int { return len(rs) } 157 func (rs byVersion) Swap(i, j int) { rs[i], rs[j] = rs[j], rs[i] } 158 func (rs byVersion) Less(i, j int) bool { 159 // we want the higher version number to reported as "less" so that it is 160 // placed first in the slice 161 return build.VersionCmp(rs[i].version(), rs[j].version()) >= 0 162 } 163 164 // latestRelease returns the latest non-LTS release, given a set of arbitrary 165 // releases. 166 func latestRelease(releases []githubRelease) (githubRelease, error) { 167 // filter the releases to exclude LTS releases 168 nonLTS := releases[:0] 169 for _, r := range releases { 170 if !strings.Contains(r.TagName, "lts") && build.IsVersion(r.version()) { 171 nonLTS = append(nonLTS, r) 172 } 173 } 174 175 // sort by version 176 sort.Sort(byVersion(nonLTS)) 177 178 // return the latest release 179 if len(nonLTS) == 0 { 180 return githubRelease{}, errEmptyUpdateResponse 181 } 182 return nonLTS[0], nil 183 } 184 185 // fetchLatestRelease returns metadata about the most recent non-LTS GitHub 186 // release. 187 func fetchLatestRelease() (githubRelease, error) { 188 req, err := http.NewRequest("GET", "https://api.github.com/repos/NebulousLabs/Sia/releases", nil) 189 if err != nil { 190 return githubRelease{}, err 191 } 192 req.Header.Set("Accept", "application/vnd.github.v3+json") 193 resp, err := http.DefaultClient.Do(req) 194 if err != nil { 195 return githubRelease{}, err 196 } 197 defer resp.Body.Close() 198 var releases []githubRelease 199 err = json.NewDecoder(resp.Body).Decode(&releases) 200 if err != nil { 201 return githubRelease{}, err 202 } 203 return latestRelease(releases) 204 } 205 206 // updateToRelease updates siad and siac to the release specified. siac is 207 // assumed to be in the same folder as siad. 208 func updateToRelease(release githubRelease) error { 209 updateOpts := update.Options{ 210 Verifier: update.NewRSAVerifier(), 211 } 212 err := updateOpts.SetPublicKeyPEM([]byte(developerKey)) 213 if err != nil { 214 // should never happen 215 return err 216 } 217 218 binaryFolder, err := osext.ExecutableFolder() 219 if err != nil { 220 return err 221 } 222 223 // construct release filename 224 releaseName := fmt.Sprintf("Sia-%s-%s-%s.zip", release.TagName, runtime.GOOS, runtime.GOARCH) 225 226 // find release 227 var downloadURL string 228 for _, asset := range release.Assets { 229 if asset.Name == releaseName { 230 downloadURL = asset.DownloadURL 231 break 232 } 233 } 234 if downloadURL == "" { 235 return errors.New("couldn't find download URL for " + releaseName) 236 } 237 238 // download release archive 239 resp, err := http.Get(downloadURL) 240 if err != nil { 241 return err 242 } 243 // release should be small enough to store in memory (<10 MiB); use 244 // LimitReader to ensure we don't download more than 32 MiB 245 content, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<25)) 246 resp.Body.Close() 247 if err != nil { 248 return err 249 } 250 r := bytes.NewReader(content) 251 z, err := zip.NewReader(r, r.Size()) 252 if err != nil { 253 return err 254 } 255 256 // process zip, finding siad/siac binaries and signatures 257 for _, binary := range []string{"siad", "siac"} { 258 var binData io.ReadCloser 259 var signature []byte 260 var binaryName string // needed for TargetPath below 261 for _, zf := range z.File { 262 switch base := path.Base(zf.Name); base { 263 case binary, binary + ".exe": 264 binaryName = base 265 binData, err = zf.Open() 266 if err != nil { 267 return err 268 } 269 defer binData.Close() 270 case binary + ".sig", binary + ".exe.sig": 271 sigFile, err := zf.Open() 272 if err != nil { 273 return err 274 } 275 defer sigFile.Close() 276 signature, err = ioutil.ReadAll(sigFile) 277 if err != nil { 278 return err 279 } 280 } 281 } 282 if binData == nil { 283 return errors.New("could not find " + binary + " binary") 284 } else if signature == nil { 285 return errors.New("could not find " + binary + " signature") 286 } 287 288 // apply update 289 updateOpts.Signature = signature 290 updateOpts.TargetMode = 0775 // executable 291 updateOpts.TargetPath = filepath.Join(binaryFolder, binaryName) 292 err = update.Apply(binData, updateOpts) 293 if err != nil { 294 return err 295 } 296 } 297 298 return nil 299 } 300 301 // daemonUpdateHandlerGET handles the API call that checks for an update. 302 func (srv *Server) daemonUpdateHandlerGET(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) { 303 release, err := fetchLatestRelease() 304 if err != nil { 305 api.WriteError(w, api.Error{Message: "Failed to fetch latest release: " + err.Error()}, http.StatusInternalServerError) 306 return 307 } 308 latestVersion := release.TagName[1:] // delete leading 'v' 309 api.WriteJSON(w, UpdateInfo{ 310 Available: build.VersionCmp(latestVersion, build.Version) > 0, 311 Version: latestVersion, 312 }) 313 } 314 315 // daemonUpdateHandlerPOST handles the API call that updates siad and siac. 316 // There is no safeguard to prevent "updating" to the same release, so callers 317 // should always check the latest version via daemonUpdateHandlerGET first. 318 // TODO: add support for specifying version to update to. 319 func (srv *Server) daemonUpdateHandlerPOST(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) { 320 release, err := fetchLatestRelease() 321 if err != nil { 322 api.WriteError(w, api.Error{Message: "Failed to fetch latest release: " + err.Error()}, http.StatusInternalServerError) 323 return 324 } 325 err = updateToRelease(release) 326 if err != nil { 327 if rerr := update.RollbackError(err); rerr != nil { 328 api.WriteError(w, api.Error{Message: "Serious error: Failed to rollback from bad update: " + rerr.Error()}, http.StatusInternalServerError) 329 } else { 330 api.WriteError(w, api.Error{Message: "Failed to apply update: " + err.Error()}, http.StatusInternalServerError) 331 } 332 return 333 } 334 api.WriteSuccess(w) 335 } 336 337 // debugConstantsHandler prints a json file containing all of the constants. 338 func (srv *Server) daemonConstantsHandler(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) { 339 sc := SiaConstants{ 340 BlockFrequency: types.BlockFrequency, 341 BlockSizeLimit: types.BlockSizeLimit, 342 ExtremeFutureThreshold: types.ExtremeFutureThreshold, 343 FutureThreshold: types.FutureThreshold, 344 GenesisTimestamp: types.GenesisTimestamp, 345 MaturityDelay: types.MaturityDelay, 346 MedianTimestampWindow: types.MedianTimestampWindow, 347 SiafundCount: types.SiafundCount, 348 SiafundPortion: types.SiafundPortion, 349 TargetWindow: types.TargetWindow, 350 351 InitialCoinbase: types.InitialCoinbase, 352 MinimumCoinbase: types.MinimumCoinbase, 353 354 RootTarget: types.RootTarget, 355 RootDepth: types.RootDepth, 356 357 // DEPRECATED: same values as MaxTargetAdjustmentUp and 358 // MaxTargetAdjustmentDown. 359 MaxAdjustmentUp: types.MaxTargetAdjustmentUp, 360 MaxAdjustmentDown: types.MaxTargetAdjustmentDown, 361 362 MaxTargetAdjustmentUp: types.MaxTargetAdjustmentUp, 363 MaxTargetAdjustmentDown: types.MaxTargetAdjustmentDown, 364 365 SiacoinPrecision: types.SiacoinPrecision, 366 } 367 368 api.WriteJSON(w, sc) 369 } 370 371 // daemonVersionHandler handles the API call that requests the daemon's version. 372 func (srv *Server) daemonVersionHandler(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) { 373 api.WriteJSON(w, DaemonVersion{Version: build.Version, GitRevision: build.GitRevision, BuildTime: build.BuildTime}) 374 } 375 376 // daemonStopHandler handles the API call to stop the daemon cleanly. 377 func (srv *Server) daemonStopHandler(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) { 378 // can't write after we stop the server, so lie a bit. 379 api.WriteSuccess(w) 380 381 // need to flush the response before shutting down the server 382 f, ok := w.(http.Flusher) 383 if !ok { 384 panic("Server does not support flushing") 385 } 386 f.Flush() 387 388 if err := srv.Close(); err != nil { 389 build.Critical(err) 390 } 391 } 392 393 func (srv *Server) daemonHandler(password string) http.Handler { 394 router := httprouter.New() 395 396 router.GET("/daemon/constants", srv.daemonConstantsHandler) 397 router.GET("/daemon/version", srv.daemonVersionHandler) 398 router.GET("/daemon/update", srv.daemonUpdateHandlerGET) 399 router.POST("/daemon/update", srv.daemonUpdateHandlerPOST) 400 router.GET("/daemon/stop", api.RequirePassword(srv.daemonStopHandler, password)) 401 402 return router 403 } 404 405 // apiHandler handles all calls to the API. If the ready flag is not set, this 406 // will return an error. Otherwise it will serve the api. 407 func (srv *Server) apiHandler(w http.ResponseWriter, r *http.Request) { 408 srv.mu.Lock() 409 isReady := srv.api != nil 410 srv.mu.Unlock() 411 if !isReady { 412 api.WriteError(w, api.Error{Message: "siad is not ready. please wait for siad to finish loading."}, http.StatusServiceUnavailable) 413 return 414 } 415 srv.api.ServeHTTP(w, r) 416 } 417 418 // NewServer creates a new net.http server listening on bindAddr. Only the 419 // /daemon/ routes are registered by this func, additional routes can be 420 // registered later by calling serv.mux.Handle. 421 func NewServer(config Config) (*Server, error) { 422 // Process the config variables after they are parsed by cobra. 423 config, err := processConfig(config) 424 if err != nil { 425 return nil, err 426 } 427 // Create the listener for the server 428 l, err := net.Listen("tcp", config.Siad.APIaddr) 429 if err != nil { 430 if isAddrInUseErr(err) { 431 return nil, fmt.Errorf("%v; are you running another instance of siad?", err.Error()) 432 } 433 434 return nil, err 435 } 436 437 // Create the Server 438 mux := http.NewServeMux() 439 srv := &Server{ 440 listener: l, 441 httpServer: &http.Server{ 442 Handler: mux, 443 444 // set reasonable timeout windows for requests, to prevent the Sia API 445 // server from leaking file descriptors due to slow, disappearing, or 446 // unreliable API clients. 447 448 // ReadTimeout defines the maximum amount of time allowed to fully read 449 // the request body. This timeout is applied to every handler in the 450 // server. 451 ReadTimeout: time.Minute * 5, 452 453 // ReadHeaderTimeout defines the amount of time allowed to fully read the 454 // request headers. 455 ReadHeaderTimeout: time.Minute * 2, 456 457 // IdleTimeout defines the maximum duration a HTTP Keep-Alive connection 458 // the API is kept open with no activity before closing. 459 IdleTimeout: time.Minute * 5, 460 }, 461 config: config, 462 } 463 464 // Register siad routes 465 mux.Handle("/daemon/", api.RequireUserAgent(srv.daemonHandler(config.APIPassword), config.Siad.RequiredUserAgent)) 466 mux.HandleFunc("/", srv.apiHandler) 467 468 return srv, nil 469 } 470 471 // isAddrInUseErr checks if the error corresponds to syscall.EADDRINUSE 472 func isAddrInUseErr(err error) bool { 473 if opErr, ok := err.(*net.OpError); ok { 474 if syscallErr, ok := opErr.Err.(*os.SyscallError); ok { 475 return syscallErr.Err == syscall.EADDRINUSE 476 } 477 } 478 return false 479 } 480 481 // loadModules loads the modules defined by the server's config and makes their 482 // API routes available. 483 func (srv *Server) loadModules() error { 484 // Create the server and start serving daemon routes immediately. 485 fmt.Printf("(0/%d) Loading siad...\n", len(srv.config.Siad.Modules)) 486 487 // Initialize the Sia modules 488 i := 0 489 var err error 490 var g modules.Gateway 491 if strings.Contains(srv.config.Siad.Modules, "g") { 492 i++ 493 fmt.Printf("(%d/%d) Loading gateway...\n", i, len(srv.config.Siad.Modules)) 494 g, err = gateway.New(srv.config.Siad.RPCaddr, !srv.config.Siad.NoBootstrap, filepath.Join(srv.config.Siad.SiaDir, modules.GatewayDir)) 495 if err != nil { 496 return err 497 } 498 srv.moduleClosers = append(srv.moduleClosers, moduleCloser{name: "gateway", Closer: g}) 499 } 500 var cs modules.ConsensusSet 501 if strings.Contains(srv.config.Siad.Modules, "c") { 502 i++ 503 fmt.Printf("(%d/%d) Loading consensus...\n", i, len(srv.config.Siad.Modules)) 504 cs, err = consensus.New(g, !srv.config.Siad.NoBootstrap, filepath.Join(srv.config.Siad.SiaDir, modules.ConsensusDir)) 505 if err != nil { 506 return err 507 } 508 srv.moduleClosers = append(srv.moduleClosers, moduleCloser{name: "consensus", Closer: cs}) 509 } 510 var e modules.Explorer 511 if strings.Contains(srv.config.Siad.Modules, "e") { 512 i++ 513 fmt.Printf("(%d/%d) Loading explorer...\n", i, len(srv.config.Siad.Modules)) 514 e, err = explorer.New(cs, filepath.Join(srv.config.Siad.SiaDir, modules.ExplorerDir)) 515 if err != nil { 516 return err 517 } 518 srv.moduleClosers = append(srv.moduleClosers, moduleCloser{name: "explorer", Closer: e}) 519 } 520 var tpool modules.TransactionPool 521 if strings.Contains(srv.config.Siad.Modules, "t") { 522 i++ 523 fmt.Printf("(%d/%d) Loading transaction pool...\n", i, len(srv.config.Siad.Modules)) 524 tpool, err = transactionpool.New(cs, g, filepath.Join(srv.config.Siad.SiaDir, modules.TransactionPoolDir)) 525 if err != nil { 526 return err 527 } 528 srv.moduleClosers = append(srv.moduleClosers, moduleCloser{name: "transaction pool", Closer: tpool}) 529 } 530 var w modules.Wallet 531 if strings.Contains(srv.config.Siad.Modules, "w") { 532 i++ 533 fmt.Printf("(%d/%d) Loading wallet...\n", i, len(srv.config.Siad.Modules)) 534 w, err = wallet.New(cs, tpool, filepath.Join(srv.config.Siad.SiaDir, modules.WalletDir)) 535 if err != nil { 536 return err 537 } 538 srv.moduleClosers = append(srv.moduleClosers, moduleCloser{name: "wallet", Closer: w}) 539 } 540 var m modules.Miner 541 if strings.Contains(srv.config.Siad.Modules, "m") { 542 i++ 543 fmt.Printf("(%d/%d) Loading miner...\n", i, len(srv.config.Siad.Modules)) 544 m, err = miner.New(cs, tpool, w, filepath.Join(srv.config.Siad.SiaDir, modules.MinerDir)) 545 if err != nil { 546 return err 547 } 548 srv.moduleClosers = append(srv.moduleClosers, moduleCloser{name: "miner", Closer: m}) 549 } 550 var h modules.Host 551 if strings.Contains(srv.config.Siad.Modules, "h") { 552 i++ 553 fmt.Printf("(%d/%d) Loading host...\n", i, len(srv.config.Siad.Modules)) 554 h, err = host.New(cs, tpool, w, srv.config.Siad.HostAddr, filepath.Join(srv.config.Siad.SiaDir, modules.HostDir)) 555 if err != nil { 556 return err 557 } 558 srv.moduleClosers = append(srv.moduleClosers, moduleCloser{name: "host", Closer: h}) 559 } 560 var r modules.Renter 561 if strings.Contains(srv.config.Siad.Modules, "r") { 562 i++ 563 fmt.Printf("(%d/%d) Loading renter...\n", i, len(srv.config.Siad.Modules)) 564 r, err = renter.New(g, cs, w, tpool, filepath.Join(srv.config.Siad.SiaDir, modules.RenterDir)) 565 if err != nil { 566 return err 567 } 568 srv.moduleClosers = append(srv.moduleClosers, moduleCloser{name: "renter", Closer: r}) 569 } 570 571 // Create the Sia API 572 a := api.New( 573 srv.config.Siad.RequiredUserAgent, 574 srv.config.APIPassword, 575 cs, 576 e, 577 g, 578 h, 579 m, 580 r, 581 tpool, 582 w, 583 ) 584 585 // connect the API to the server 586 srv.mu.Lock() 587 srv.api = a 588 srv.mu.Unlock() 589 590 // Attempt to auto-unlock the wallet using the SIA_WALLET_PASSWORD env variable 591 if password := os.Getenv("SIA_WALLET_PASSWORD"); password != "" { 592 fmt.Println("Sia Wallet Password found, attempting to auto-unlock wallet") 593 if err := unlockWallet(w, password); err != nil { 594 fmt.Println("Auto-unlock failed.") 595 } else { 596 fmt.Println("Auto-unlock successful.") 597 } 598 } 599 600 return nil 601 } 602 603 // Serve starts the HTTP server 604 func (srv *Server) Serve() error { 605 // The server will run until an error is encountered or the listener is 606 // closed, via either the Close method or the signal handling above. 607 // Closing the listener will result in the benign error handled below. 608 err := srv.httpServer.Serve(srv.listener) 609 if err != nil && !strings.HasSuffix(err.Error(), "use of closed network connection") { 610 return err 611 } 612 return nil 613 } 614 615 // Close closes the Server's listener, causing the HTTP server to shut down. 616 func (srv *Server) Close() error { 617 var errs []error 618 // Close the listener, which will cause Server.Serve() to return. 619 if err := srv.listener.Close(); err != nil { 620 errs = append(errs, err) 621 } 622 // Close all of the modules in reverse order 623 for i := len(srv.moduleClosers) - 1; i >= 0; i-- { 624 m := srv.moduleClosers[i] 625 fmt.Printf("Closing %v...\n", m.name) 626 if err := m.Close(); err != nil { 627 errs = append(errs, err) 628 } 629 } 630 631 return build.JoinErrors(errs, "\n") 632 }