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