github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/cmd/ipfs/daemon.go (about)

     1  package main
     2  
     3  import (
     4  	_ "expvar"
     5  	"fmt"
     6  	"net"
     7  	"net/http"
     8  	_ "net/http/pprof"
     9  	"os"
    10  	"sort"
    11  	"strings"
    12  	"sync"
    13  
    14  	_ "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/codahale/metrics/runtime"
    15  	ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
    16  	"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"
    17  
    18  	cmds "github.com/ipfs/go-ipfs/commands"
    19  	"github.com/ipfs/go-ipfs/core"
    20  	commands "github.com/ipfs/go-ipfs/core/commands"
    21  	corehttp "github.com/ipfs/go-ipfs/core/corehttp"
    22  	"github.com/ipfs/go-ipfs/core/corerouting"
    23  	conn "github.com/ipfs/go-ipfs/p2p/net/conn"
    24  	peer "github.com/ipfs/go-ipfs/p2p/peer"
    25  	fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
    26  	util "github.com/ipfs/go-ipfs/util"
    27  )
    28  
    29  const (
    30  	initOptionKwd             = "init"
    31  	routingOptionKwd          = "routing"
    32  	routingOptionSupernodeKwd = "supernode"
    33  	mountKwd                  = "mount"
    34  	writableKwd               = "writable"
    35  	ipfsMountKwd              = "mount-ipfs"
    36  	ipnsMountKwd              = "mount-ipns"
    37  	unrestrictedApiAccessKwd  = "unrestricted-api"
    38  	unencryptTransportKwd     = "disable-transport-encryption"
    39  	// apiAddrKwd    = "address-api"
    40  	// swarmAddrKwd  = "address-swarm"
    41  )
    42  
    43  var daemonCmd = &cmds.Command{
    44  	Helptext: cmds.HelpText{
    45  		Tagline: "Run a network-connected IPFS node",
    46  		ShortDescription: `
    47  'ipfs daemon' runs a persistent IPFS daemon that can serve commands
    48  over the network. Most applications that use IPFS will do so by
    49  communicating with a daemon over the HTTP API. While the daemon is
    50  running, calls to 'ipfs' commands will be sent over the network to
    51  the daemon.
    52  
    53  The daemon will start listening on ports on the network, which are
    54  documented in (and can be modified through) 'ipfs config Addresses'.
    55  For example, to change the 'Gateway' port:
    56  
    57      ipfs config Addresses.Gateway /ip4/127.0.0.1/tcp/8082
    58  
    59  The API address can be changed the same way:
    60  
    61     ipfs config Addresses.API /ip4/127.0.0.1/tcp/5002
    62  
    63  Make sure to restart the daemon after changing addresses.
    64  
    65  By default, the gateway is only accessible locally. To expose it to
    66  other computers in the network, use 0.0.0.0 as the ip address:
    67  
    68     ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8080
    69  
    70  Be careful if you expose the API. It is a security risk, as anyone could
    71  control your node remotely. If you need to control the node remotely,
    72  make sure to protect the port as you would other services or database
    73  (firewall, authenticated proxy, etc).
    74  
    75  HTTP Headers
    76  
    77  IPFS supports passing arbitrary headers to the API and Gateway. You can
    78  do this by setting headers on the API.HTTPHeaders and Gateway.HTTPHeaders
    79  keys:
    80  
    81  	ipfs config --json API.HTTPHeaders.X-Special-Header '["so special :)"]'
    82  	ipfs config --json Gateway.HTTPHeaders.X-Special-Header '["so special :)"]'
    83  
    84  Note that the value of the keys is an _array_ of strings. This is because
    85  headers can have more than one value, and it is convenient to pass through
    86  to other libraries.
    87  
    88  CORS Headers (for API)
    89  
    90  You can setup CORS headers the same way:
    91  
    92  	ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["*"]'
    93  	ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '["PUT", "GET", "POST"]'
    94  	ipfs config --json API.HTTPHeaders.Access-Control-Allow-Credentials '["true"]'
    95  
    96  
    97  DEPRECATION NOTICE
    98  
    99  Previously, IPFS used an environment variable as seen below:
   100  
   101     export API_ORIGIN="http://localhost:8888/"
   102  
   103  This is deprecated. It is still honored in this version, but will be removed in a
   104  future version, along with this notice. Please move to setting the HTTP Headers.
   105  `,
   106  	},
   107  
   108  	Options: []cmds.Option{
   109  		cmds.BoolOption(initOptionKwd, "Initialize IPFS with default settings if not already initialized"),
   110  		cmds.StringOption(routingOptionKwd, "Overrides the routing option (dht, supernode)"),
   111  		cmds.BoolOption(mountKwd, "Mounts IPFS to the filesystem"),
   112  		cmds.BoolOption(writableKwd, "Enable writing objects (with POST, PUT and DELETE)"),
   113  		cmds.StringOption(ipfsMountKwd, "Path to the mountpoint for IPFS (if using --mount)"),
   114  		cmds.StringOption(ipnsMountKwd, "Path to the mountpoint for IPNS (if using --mount)"),
   115  		cmds.BoolOption(unrestrictedApiAccessKwd, "Allow API access to unlisted hashes"),
   116  		cmds.BoolOption(unencryptTransportKwd, "Disable transport encryption (for debugging protocols)"),
   117  
   118  		// TODO: add way to override addresses. tricky part: updating the config if also --init.
   119  		// cmds.StringOption(apiAddrKwd, "Address for the daemon rpc API (overrides config)"),
   120  		// cmds.StringOption(swarmAddrKwd, "Address for the swarm socket (overrides config)"),
   121  	},
   122  	Subcommands: map[string]*cmds.Command{},
   123  	Run:         daemonFunc,
   124  }
   125  
   126  // defaultMux tells mux to serve path using the default muxer. This is
   127  // mostly useful to hook up things that register in the default muxer,
   128  // and don't provide a convenient http.Handler entry point, such as
   129  // expvar and http/pprof.
   130  func defaultMux(path string) corehttp.ServeOption {
   131  	return func(node *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
   132  		mux.Handle(path, http.DefaultServeMux)
   133  		return mux, nil
   134  	}
   135  }
   136  
   137  func daemonFunc(req cmds.Request, res cmds.Response) {
   138  	// let the user know we're going.
   139  	fmt.Printf("Initializing daemon...\n")
   140  
   141  	ctx := req.InvocContext()
   142  
   143  	go func() {
   144  		select {
   145  		case <-req.Context().Done():
   146  			fmt.Println("Received interrupt signal, shutting down...")
   147  		}
   148  	}()
   149  
   150  	// check transport encryption flag.
   151  	unencrypted, _, _ := req.Option(unencryptTransportKwd).Bool()
   152  	if unencrypted {
   153  		log.Warningf(`Running with --%s: All connections are UNENCRYPTED.
   154  		You will not be able to connect to regular encrypted networks.`, unencryptTransportKwd)
   155  		conn.EncryptConnections = false
   156  	}
   157  
   158  	// first, whether user has provided the initialization flag. we may be
   159  	// running in an uninitialized state.
   160  	initialize, _, err := req.Option(initOptionKwd).Bool()
   161  	if err != nil {
   162  		res.SetError(err, cmds.ErrNormal)
   163  		return
   164  	}
   165  
   166  	if initialize {
   167  
   168  		// now, FileExists is our best method of detecting whether IPFS is
   169  		// configured. Consider moving this into a config helper method
   170  		// `IsInitialized` where the quality of the signal can be improved over
   171  		// time, and many call-sites can benefit.
   172  		if !util.FileExists(req.InvocContext().ConfigRoot) {
   173  			err := initWithDefaults(os.Stdout, req.InvocContext().ConfigRoot)
   174  			if err != nil {
   175  				res.SetError(err, cmds.ErrNormal)
   176  				return
   177  			}
   178  		}
   179  	}
   180  
   181  	// acquire the repo lock _before_ constructing a node. we need to make
   182  	// sure we are permitted to access the resources (datastore, etc.)
   183  	repo, err := fsrepo.Open(req.InvocContext().ConfigRoot)
   184  	if err != nil {
   185  		res.SetError(err, cmds.ErrNormal)
   186  		return
   187  	}
   188  
   189  	cfg, err := ctx.GetConfig()
   190  	if err != nil {
   191  		res.SetError(err, cmds.ErrNormal)
   192  		return
   193  	}
   194  
   195  	// Start assembling node config
   196  	ncfg := &core.BuildCfg{
   197  		Online: true,
   198  		Repo:   repo,
   199  	}
   200  
   201  	routingOption, _, err := req.Option(routingOptionKwd).String()
   202  	if err != nil {
   203  		res.SetError(err, cmds.ErrNormal)
   204  		return
   205  	}
   206  	if routingOption == routingOptionSupernodeKwd {
   207  		servers, err := cfg.SupernodeRouting.ServerIPFSAddrs()
   208  		if err != nil {
   209  			res.SetError(err, cmds.ErrNormal)
   210  			repo.Close() // because ownership hasn't been transferred to the node
   211  			return
   212  		}
   213  		var infos []peer.PeerInfo
   214  		for _, addr := range servers {
   215  			infos = append(infos, peer.PeerInfo{
   216  				ID:    addr.ID(),
   217  				Addrs: []ma.Multiaddr{addr.Transport()},
   218  			})
   219  		}
   220  
   221  		ncfg.Routing = corerouting.SupernodeClient(infos...)
   222  	}
   223  
   224  	node, err := core.NewNode(req.Context(), ncfg)
   225  	if err != nil {
   226  		log.Error("error from node construction: ", err)
   227  		res.SetError(err, cmds.ErrNormal)
   228  		return
   229  	}
   230  
   231  	printSwarmAddrs(node)
   232  
   233  	defer func() {
   234  		// We wait for the node to close first, as the node has children
   235  		// that it will wait for before closing, such as the API server.
   236  		node.Close()
   237  
   238  		select {
   239  		case <-req.Context().Done():
   240  			log.Info("Gracefully shut down daemon")
   241  		default:
   242  		}
   243  	}()
   244  
   245  	req.InvocContext().ConstructNode = func() (*core.IpfsNode, error) {
   246  		return node, nil
   247  	}
   248  
   249  	// construct api endpoint - every time
   250  	err, apiErrc := serveHTTPApi(req)
   251  	if err != nil {
   252  		res.SetError(err, cmds.ErrNormal)
   253  		return
   254  	}
   255  
   256  	// construct http gateway - if it is set in the config
   257  	var gwErrc <-chan error
   258  	if len(cfg.Addresses.Gateway) > 0 {
   259  		var err error
   260  		err, gwErrc = serveHTTPGateway(req)
   261  		if err != nil {
   262  			res.SetError(err, cmds.ErrNormal)
   263  			return
   264  		}
   265  	}
   266  
   267  	// construct fuse mountpoints - if the user provided the --mount flag
   268  	mount, _, err := req.Option(mountKwd).Bool()
   269  	if err != nil {
   270  		res.SetError(err, cmds.ErrNormal)
   271  		return
   272  	}
   273  	if mount {
   274  		if err := mountFuse(req); err != nil {
   275  			res.SetError(err, cmds.ErrNormal)
   276  			return
   277  		}
   278  	}
   279  
   280  	fmt.Printf("Daemon is ready\n")
   281  	// collect long-running errors and block for shutdown
   282  	// TODO(cryptix): our fuse currently doesnt follow this pattern for graceful shutdown
   283  	for err := range merge(apiErrc, gwErrc) {
   284  		if err != nil {
   285  			res.SetError(err, cmds.ErrNormal)
   286  			return
   287  		}
   288  	}
   289  }
   290  
   291  // serveHTTPApi collects options, creates listener, prints status message and starts serving requests
   292  func serveHTTPApi(req cmds.Request) (error, <-chan error) {
   293  	cfg, err := req.InvocContext().GetConfig()
   294  	if err != nil {
   295  		return fmt.Errorf("serveHTTPApi: GetConfig() failed: %s", err), nil
   296  	}
   297  
   298  	apiAddr, _, err := req.Option(commands.ApiOption).String()
   299  	if err != nil {
   300  		return fmt.Errorf("serveHTTPApi: %s", err), nil
   301  	}
   302  	if apiAddr == "" {
   303  		apiAddr = cfg.Addresses.API
   304  	}
   305  	apiMaddr, err := ma.NewMultiaddr(apiAddr)
   306  	if err != nil {
   307  		return fmt.Errorf("serveHTTPApi: invalid API address: %q (err: %s)", apiAddr, err), nil
   308  	}
   309  
   310  	apiLis, err := manet.Listen(apiMaddr)
   311  	if err != nil {
   312  		return fmt.Errorf("serveHTTPApi: manet.Listen(%s) failed: %s", apiMaddr, err), nil
   313  	}
   314  	// we might have listened to /tcp/0 - lets see what we are listing on
   315  	apiMaddr = apiLis.Multiaddr()
   316  	fmt.Printf("API server listening on %s\n", apiMaddr)
   317  
   318  	unrestricted, _, err := req.Option(unrestrictedApiAccessKwd).Bool()
   319  	if err != nil {
   320  		return fmt.Errorf("serveHTTPApi: Option(%s) failed: %s", unrestrictedApiAccessKwd, err), nil
   321  	}
   322  
   323  	apiGw := corehttp.NewGateway(corehttp.GatewayConfig{
   324  		Writable: true,
   325  		BlockList: &corehttp.BlockList{
   326  			Decider: func(s string) bool {
   327  				if unrestricted {
   328  					return true
   329  				}
   330  				// for now, only allow paths in the WebUI path
   331  				for _, webuipath := range corehttp.WebUIPaths {
   332  					if strings.HasPrefix(s, webuipath) {
   333  						return true
   334  					}
   335  				}
   336  				return false
   337  			},
   338  		},
   339  	})
   340  	var opts = []corehttp.ServeOption{
   341  		corehttp.CommandsOption(*req.InvocContext()),
   342  		corehttp.WebUIOption,
   343  		apiGw.ServeOption(),
   344  		corehttp.VersionOption(),
   345  		defaultMux("/debug/vars"),
   346  		defaultMux("/debug/pprof/"),
   347  		corehttp.LogOption(),
   348  		corehttp.PrometheusOption("/debug/metrics/prometheus"),
   349  	}
   350  
   351  	if len(cfg.Gateway.RootRedirect) > 0 {
   352  		opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect))
   353  	}
   354  
   355  	node, err := req.InvocContext().ConstructNode()
   356  	if err != nil {
   357  		return fmt.Errorf("serveHTTPApi: ConstructNode() failed: %s", err), nil
   358  	}
   359  
   360  	if err := node.Repo.SetAPIAddr(apiAddr); err != nil {
   361  		return fmt.Errorf("serveHTTPApi: SetAPIAddr() failed: %s", err), nil
   362  	}
   363  
   364  	errc := make(chan error)
   365  	go func() {
   366  		errc <- corehttp.Serve(node, apiLis.NetListener(), opts...)
   367  		close(errc)
   368  	}()
   369  	return nil, errc
   370  }
   371  
   372  // printSwarmAddrs prints the addresses of the host
   373  func printSwarmAddrs(node *core.IpfsNode) {
   374  	var addrs []string
   375  	for _, addr := range node.PeerHost.Addrs() {
   376  		addrs = append(addrs, addr.String())
   377  	}
   378  	sort.Sort(sort.StringSlice(addrs))
   379  
   380  	for _, addr := range addrs {
   381  		fmt.Printf("Swarm listening on %s\n", addr)
   382  	}
   383  }
   384  
   385  // serveHTTPGateway collects options, creates listener, prints status message and starts serving requests
   386  func serveHTTPGateway(req cmds.Request) (error, <-chan error) {
   387  	cfg, err := req.InvocContext().GetConfig()
   388  	if err != nil {
   389  		return fmt.Errorf("serveHTTPGateway: GetConfig() failed: %s", err), nil
   390  	}
   391  
   392  	gatewayMaddr, err := ma.NewMultiaddr(cfg.Addresses.Gateway)
   393  	if err != nil {
   394  		return fmt.Errorf("serveHTTPGateway: invalid gateway address: %q (err: %s)", cfg.Addresses.Gateway, err), nil
   395  	}
   396  
   397  	writable, writableOptionFound, err := req.Option(writableKwd).Bool()
   398  	if err != nil {
   399  		return fmt.Errorf("serveHTTPGateway: req.Option(%s) failed: %s", writableKwd, err), nil
   400  	}
   401  	if !writableOptionFound {
   402  		writable = cfg.Gateway.Writable
   403  	}
   404  
   405  	gwLis, err := manet.Listen(gatewayMaddr)
   406  	if err != nil {
   407  		return fmt.Errorf("serveHTTPGateway: manet.Listen(%s) failed: %s", gatewayMaddr, err), nil
   408  	}
   409  	// we might have listened to /tcp/0 - lets see what we are listing on
   410  	gatewayMaddr = gwLis.Multiaddr()
   411  
   412  	if writable {
   413  		fmt.Printf("Gateway (writable) server listening on %s\n", gatewayMaddr)
   414  	} else {
   415  		fmt.Printf("Gateway (readonly) server listening on %s\n", gatewayMaddr)
   416  	}
   417  
   418  	var opts = []corehttp.ServeOption{
   419  		corehttp.CommandsROOption(*req.InvocContext()),
   420  		corehttp.VersionOption(),
   421  		corehttp.IPNSHostnameOption(),
   422  		corehttp.GatewayOption(writable),
   423  	}
   424  
   425  	if len(cfg.Gateway.RootRedirect) > 0 {
   426  		opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect))
   427  	}
   428  
   429  	node, err := req.InvocContext().ConstructNode()
   430  	if err != nil {
   431  		return fmt.Errorf("serveHTTPGateway: ConstructNode() failed: %s", err), nil
   432  	}
   433  
   434  	errc := make(chan error)
   435  	go func() {
   436  		errc <- corehttp.Serve(node, gwLis.NetListener(), opts...)
   437  		close(errc)
   438  	}()
   439  	return nil, errc
   440  }
   441  
   442  //collects options and opens the fuse mountpoint
   443  func mountFuse(req cmds.Request) error {
   444  	cfg, err := req.InvocContext().GetConfig()
   445  	if err != nil {
   446  		return fmt.Errorf("mountFuse: GetConfig() failed: %s", err)
   447  	}
   448  
   449  	fsdir, found, err := req.Option(ipfsMountKwd).String()
   450  	if err != nil {
   451  		return fmt.Errorf("mountFuse: req.Option(%s) failed: %s", ipfsMountKwd, err)
   452  	}
   453  	if !found {
   454  		fsdir = cfg.Mounts.IPFS
   455  	}
   456  
   457  	nsdir, found, err := req.Option(ipnsMountKwd).String()
   458  	if err != nil {
   459  		return fmt.Errorf("mountFuse: req.Option(%s) failed: %s", ipnsMountKwd, err)
   460  	}
   461  	if !found {
   462  		nsdir = cfg.Mounts.IPNS
   463  	}
   464  
   465  	node, err := req.InvocContext().ConstructNode()
   466  	if err != nil {
   467  		return fmt.Errorf("mountFuse: ConstructNode() failed: %s", err)
   468  	}
   469  
   470  	err = commands.Mount(node, fsdir, nsdir)
   471  	if err != nil {
   472  		return err
   473  	}
   474  	fmt.Printf("IPFS mounted at: %s\n", fsdir)
   475  	fmt.Printf("IPNS mounted at: %s\n", nsdir)
   476  	return nil
   477  }
   478  
   479  // merge does fan-in of multiple read-only error channels
   480  // taken from http://blog.golang.org/pipelines
   481  func merge(cs ...<-chan error) <-chan error {
   482  	var wg sync.WaitGroup
   483  	out := make(chan error)
   484  
   485  	// Start an output goroutine for each input channel in cs.  output
   486  	// copies values from c to out until c is closed, then calls wg.Done.
   487  	output := func(c <-chan error) {
   488  		for n := range c {
   489  			out <- n
   490  		}
   491  		wg.Done()
   492  	}
   493  	for _, c := range cs {
   494  		if c != nil {
   495  			wg.Add(1)
   496  			go output(c)
   497  		}
   498  	}
   499  
   500  	// Start a goroutine to close out once all the output goroutines are
   501  	// done.  This must start after the wg.Add call.
   502  	go func() {
   503  		wg.Wait()
   504  		close(out)
   505  	}()
   506  	return out
   507  }