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  }