daemon.go 12.3 KB
Newer Older
1
package main
2 3

import (
4
	_ "expvar"
5
	"fmt"
6 7
	"net/http"
	_ "net/http/pprof"
8
	"os"
9
	"strings"
10
	"sync"
11

12
	_ "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/codahale/metrics/runtime"
13
	ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
14 15
	"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"

16 17 18 19 20 21 22 23
	cmds "github.com/ipfs/go-ipfs/commands"
	"github.com/ipfs/go-ipfs/core"
	commands "github.com/ipfs/go-ipfs/core/commands"
	corehttp "github.com/ipfs/go-ipfs/core/corehttp"
	"github.com/ipfs/go-ipfs/core/corerouting"
	peer "github.com/ipfs/go-ipfs/p2p/peer"
	fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
	util "github.com/ipfs/go-ipfs/util"
24 25 26
)

const (
27 28 29 30 31 32 33
	initOptionKwd             = "init"
	routingOptionKwd          = "routing"
	routingOptionSupernodeKwd = "supernode"
	mountKwd                  = "mount"
	writableKwd               = "writable"
	ipfsMountKwd              = "mount-ipfs"
	ipnsMountKwd              = "mount-ipns"
34
	unrestrictedApiAccess     = "unrestricted-api"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
35 36
	// apiAddrKwd    = "address-api"
	// swarmAddrKwd  = "address-swarm"
37 38
)

39
var daemonCmd = &cmds.Command{
40
	Helptext: cmds.HelpText{
Matt Bell's avatar
Matt Bell committed
41 42 43 44 45 46 47
		Tagline: "Run a network-connected IPFS node",
		ShortDescription: `
'ipfs daemon' runs a persistent IPFS daemon that can serve commands
over the network. Most applications that use IPFS will do so by
communicating with a daemon over the HTTP API. While the daemon is
running, calls to 'ipfs' commands will be sent over the network to
the daemon.
anarcat's avatar
anarcat committed
48 49 50 51 52 53 54

The daemon will start listening on ports on the network, which are
documented in (and can be modified through) 'ipfs config Addresses'.
For example, to change the 'Gateway' port:

    ipfs config Addresses.Gateway /ip4/127.0.0.1/tcp/8082

anarcat's avatar
anarcat committed
55 56 57 58 59 60 61 62 63 64 65
The API address can be changed the same way:

   ipfs config Addresses.API /ip4/127.0.0.1/tcp/5002

Make sure to restart the daemon after changing addresses.

By default, the gateway is only accessible locally. To expose it to other computers
in the network, use 0.0.0.0 as the ip address:

   ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8080

Harlan T Wood's avatar
Harlan T Wood committed
66
Be careful if you expose the API. It is a security risk, as anyone could control
anarcat's avatar
anarcat committed
67 68
your node remotely. If you need to control the node remotely, make sure to protect
the port as you would other services or database (firewall, authenticated proxy, etc).`,
69 70
	},

71
	Options: []cmds.Option{
72
		cmds.BoolOption(initOptionKwd, "Initialize IPFS with default settings if not already initialized"),
73
		cmds.StringOption(routingOptionKwd, "Overrides the routing option (dht, supernode)"),
74
		cmds.BoolOption(mountKwd, "Mounts IPFS to the filesystem"),
75
		cmds.BoolOption(writableKwd, "Enable writing objects (with POST, PUT and DELETE)"),
76 77
		cmds.StringOption(ipfsMountKwd, "Path to the mountpoint for IPFS (if using --mount)"),
		cmds.StringOption(ipnsMountKwd, "Path to the mountpoint for IPNS (if using --mount)"),
78
		cmds.BoolOption(unrestrictedApiAccess, "Allow API access to unlisted hashes"),
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
79 80 81 82

		// TODO: add way to override addresses. tricky part: updating the config if also --init.
		// cmds.StringOption(apiAddrKwd, "Address for the daemon rpc API (overrides config)"),
		// cmds.StringOption(swarmAddrKwd, "Address for the swarm socket (overrides config)"),
83
	},
84 85 86 87
	Subcommands: map[string]*cmds.Command{},
	Run:         daemonFunc,
}

88 89 90 91 92 93 94 95 96 97 98
// defaultMux tells mux to serve path using the default muxer. This is
// mostly useful to hook up things that register in the default muxer,
// and don't provide a convenient http.Handler entry point, such as
// expvar and http/pprof.
func defaultMux(path string) func(node *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) {
	return func(node *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) {
		mux.Handle(path, http.DefaultServeMux)
		return mux, nil
	}
}

99
func daemonFunc(req cmds.Request, res cmds.Response) {
100 101
	// let the user know we're going.
	fmt.Printf("Initializing daemon...\n")
102

103 104 105 106 107 108 109 110 111
	ctx := req.Context()

	go func() {
		select {
		case <-ctx.Context.Done():
			fmt.Println("Received interrupt signal, shutting down...")
		}
	}()

112 113
	// first, whether user has provided the initialization flag. we may be
	// running in an uninitialized state.
114
	initialize, _, err := req.Option(initOptionKwd).Bool()
115
	if err != nil {
116 117
		res.SetError(err, cmds.ErrNormal)
		return
118
	}
119

120
	if initialize {
121 122 123 124 125 126

		// now, FileExists is our best method of detecting whether IPFS is
		// configured. Consider moving this into a config helper method
		// `IsInitialized` where the quality of the signal can be improved over
		// time, and many call-sites can benefit.
		if !util.FileExists(req.Context().ConfigRoot) {
127
			err := initWithDefaults(os.Stdout, req.Context().ConfigRoot)
128
			if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
129
				res.SetError(err, cmds.ErrNormal)
130
				return
131 132 133 134
			}
		}
	}

135 136 137
	// acquire the repo lock _before_ constructing a node. we need to make
	// sure we are permitted to access the resources (datastore, etc.)
	repo, err := fsrepo.Open(req.Context().ConfigRoot)
138
	if err != nil {
139 140
		res.SetError(err, cmds.ErrNormal)
		return
141 142
	}

143
	cfg, err := ctx.GetConfig()
144
	if err != nil {
145
		res.SetError(err, cmds.ErrNormal)
146
		return
147 148
	}

Jeromy's avatar
Jeromy committed
149 150 151 152
	// Start assembling corebuilder
	nb := core.NewNodeBuilder().Online()
	nb.SetRepo(repo)

153
	routingOption, _, err := req.Option(routingOptionKwd).String()
154 155 156 157
	if err != nil {
		res.SetError(err, cmds.ErrNormal)
		return
	}
158
	if routingOption == routingOptionSupernodeKwd {
159
		servers, err := repo.Config().SupernodeRouting.ServerIPFSAddrs()
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			repo.Close() // because ownership hasn't been transferred to the node
			return
		}
		var infos []peer.PeerInfo
		for _, addr := range servers {
			infos = append(infos, peer.PeerInfo{
				ID:    addr.ID(),
				Addrs: []ma.Multiaddr{addr.Transport()},
			})
		}
		nb.SetRouting(corerouting.SupernodeClient(infos...))
	}

Jeromy's avatar
Jeromy committed
175
	node, err := nb.Build(ctx.Context)
176
	if err != nil {
177
		log.Error("error from node construction: ", err)
178 179
		res.SetError(err, cmds.ErrNormal)
		return
180
	}
181 182 183 184 185 186 187 188 189 190 191 192 193

	defer func() {
		// We wait for the node to close first, as the node has children
		// that it will wait for before closing, such as the API server.
		node.Close()

		select {
		case <-ctx.Context.Done():
			log.Info("Gracefully shut down daemon")
		default:
		}
	}()

194 195 196
	req.Context().ConstructNode = func() (*core.IpfsNode, error) {
		return node, nil
	}
197

198
	// construct api endpoint - every time
Henry's avatar
Henry committed
199
	err, apiErrc := serveHTTPApi(req)
200
	if err != nil {
201 202
		res.SetError(err, cmds.ErrNormal)
		return
203 204
	}

205 206
	// construct http gateway - if it is set in the config
	var gwErrc <-chan error
207
	if len(cfg.Addresses.Gateway) > 0 {
208
		var err error
Henry's avatar
Henry committed
209
		err, gwErrc = serveHTTPGateway(req)
210 211 212
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
213
		}
214 215
	}

216
	// construct fuse mountpoints - if the user provided the --mount flag
217
	mount, _, err := req.Option(mountKwd).Bool()
218
	if err != nil {
219 220
		res.SetError(err, cmds.ErrNormal)
		return
221
	}
222
	if mount {
223
		if err := mountFuse(req); err != nil {
224 225
			res.SetError(err, cmds.ErrNormal)
			return
226
		}
227
	}
228

229 230 231
	// collect long-running errors and block for shutdown
	// TODO(cryptix): our fuse currently doesnt follow this pattern for graceful shutdown
	for err := range merge(apiErrc, gwErrc) {
232
		if err != nil {
233 234
			res.SetError(err, cmds.ErrNormal)
			return
235 236
		}
	}
237
}
238

Henry's avatar
Henry committed
239 240
// serveHTTPApi collects options, creates listener, prints status message and starts serving requests
func serveHTTPApi(req cmds.Request) (error, <-chan error) {
241 242
	cfg, err := req.Context().GetConfig()
	if err != nil {
Henry's avatar
Henry committed
243
		return fmt.Errorf("serveHTTPApi: GetConfig() failed: %s", err), nil
244 245
	}

246
	apiMaddr, err := ma.NewMultiaddr(cfg.Addresses.API)
247
	if err != nil {
Henry's avatar
Henry committed
248
		return fmt.Errorf("serveHTTPApi: invalid API address: %q (err: %s)", cfg.Addresses.API, err), nil
249
	}
250 251 252

	apiLis, err := manet.Listen(apiMaddr)
	if err != nil {
Henry's avatar
Henry committed
253
		return fmt.Errorf("serveHTTPApi: manet.Listen(%s) failed: %s", apiMaddr, err), nil
254
	}
255 256 257
	// we might have listened to /tcp/0 - lets see what we are listing on
	apiMaddr = apiLis.Multiaddr()
	fmt.Printf("API server listening on %s\n", apiMaddr)
258

259 260
	unrestricted, _, err := req.Option(unrestrictedApiAccess).Bool()
	if err != nil {
Henry's avatar
Henry committed
261
		return fmt.Errorf("serveHTTPApi: Option(%s) failed: %s", unrestrictedApiAccess, err), nil
262 263
	}

264
	apiGw := corehttp.NewGateway(corehttp.GatewayConfig{
265 266 267
		Writable: true,
		BlockList: &corehttp.BlockList{
			Decider: func(s string) bool {
268 269 270
				if unrestricted {
					return true
				}
271 272 273 274 275 276 277 278 279
				// for now, only allow paths in the WebUI path
				for _, webuipath := range corehttp.WebUIPaths {
					if strings.HasPrefix(s, webuipath) {
						return true
					}
				}
				return false
			},
		},
280
	})
281 282 283
	var opts = []corehttp.ServeOption{
		corehttp.CommandsOption(*req.Context()),
		corehttp.WebUIOption,
284
		apiGw.ServeOption(),
285
		corehttp.VersionOption(),
286 287
		defaultMux("/debug/vars"),
		defaultMux("/debug/pprof/"),
288
	}
289

290 291
	if len(cfg.Gateway.RootRedirect) > 0 {
		opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect))
292
	}
293 294 295

	node, err := req.Context().ConstructNode()
	if err != nil {
Henry's avatar
Henry committed
296
		return fmt.Errorf("serveHTTPGateway: ConstructNode() failed: %s", err), nil
297 298 299 300 301 302 303 304 305
	}

	errc := make(chan error)
	go func() {
		errc <- corehttp.Serve(node, apiLis.NetListener(), opts...)
	}()
	return nil, errc
}

Henry's avatar
Henry committed
306 307
// serveHTTPGateway collects options, creates listener, prints status message and starts serving requests
func serveHTTPGateway(req cmds.Request) (error, <-chan error) {
308 309
	cfg, err := req.Context().GetConfig()
	if err != nil {
Henry's avatar
Henry committed
310
		return fmt.Errorf("serveHTTPGateway: GetConfig() failed: %s", err), nil
311 312 313 314
	}

	gatewayMaddr, err := ma.NewMultiaddr(cfg.Addresses.Gateway)
	if err != nil {
Henry's avatar
Henry committed
315
		return fmt.Errorf("serveHTTPGateway: invalid gateway address: %q (err: %s)", cfg.Addresses.Gateway, err), nil
316 317 318 319
	}

	writable, writableOptionFound, err := req.Option(writableKwd).Bool()
	if err != nil {
Henry's avatar
Henry committed
320
		return fmt.Errorf("serveHTTPGateway: req.Option(%s) failed: %s", writableKwd, err), nil
321 322 323 324 325 326 327
	}
	if !writableOptionFound {
		writable = cfg.Gateway.Writable
	}

	gwLis, err := manet.Listen(gatewayMaddr)
	if err != nil {
Henry's avatar
Henry committed
328
		return fmt.Errorf("serveHTTPGateway: manet.Listen(%s) failed: %s", gatewayMaddr, err), nil
329 330 331 332 333 334 335 336 337 338 339 340 341 342
	}
	// we might have listened to /tcp/0 - lets see what we are listing on
	gatewayMaddr = gwLis.Multiaddr()

	if writable {
		fmt.Printf("Gateway (writable) server listening on %s\n", gatewayMaddr)
	} else {
		fmt.Printf("Gateway (readonly) server listening on %s\n", gatewayMaddr)
	}

	var opts = []corehttp.ServeOption{
		corehttp.VersionOption(),
		corehttp.IPNSHostnameOption(),
		corehttp.GatewayOption(writable),
343
	}
344 345 346 347 348 349 350

	if len(cfg.Gateway.RootRedirect) > 0 {
		opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect))
	}

	node, err := req.Context().ConstructNode()
	if err != nil {
Henry's avatar
Henry committed
351
		return fmt.Errorf("serveHTTPGateway: ConstructNode() failed: %s", err), nil
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413
	}

	errc := make(chan error)
	go func() {
		errc <- corehttp.Serve(node, gwLis.NetListener(), opts...)
	}()
	return nil, errc
}

//collects options and opens the fuse mountpoint
func mountFuse(req cmds.Request) error {
	cfg, err := req.Context().GetConfig()
	if err != nil {
		return fmt.Errorf("mountFuse: GetConfig() failed: %s", err)
	}

	fsdir, found, err := req.Option(ipfsMountKwd).String()
	if err != nil {
		return fmt.Errorf("mountFuse: req.Option(%s) failed: %s", ipfsMountKwd, err)
	}
	if !found {
		fsdir = cfg.Mounts.IPFS
	}

	nsdir, found, err := req.Option(ipnsMountKwd).String()
	if err != nil {
		return fmt.Errorf("mountFuse: req.Option(%s) failed: %s", ipnsMountKwd, err)
	}
	if !found {
		nsdir = cfg.Mounts.IPNS
	}

	node, err := req.Context().ConstructNode()
	if err != nil {
		return fmt.Errorf("mountFuse: ConstructNode() failed: %s", err)
	}

	err = commands.Mount(node, fsdir, nsdir)
	if err != nil {
		return err
	}
	fmt.Printf("IPFS mounted at: %s\n", fsdir)
	fmt.Printf("IPNS mounted at: %s\n", nsdir)
	return nil
}

// merge does fan-in of multiple read-only error channels
// taken from http://blog.golang.org/pipelines
func merge(cs ...<-chan error) <-chan error {
	var wg sync.WaitGroup
	out := make(chan error)

	// Start an output goroutine for each input channel in cs.  output
	// copies values from c to out until c is closed, then calls wg.Done.
	output := func(c <-chan error) {
		for n := range c {
			out <- n
		}
		wg.Done()
	}
	for _, c := range cs {
		if c != nil {
Henry's avatar
Henry committed
414
			wg.Add(1)
415 416 417 418 419 420 421 422 423 424 425
			go output(c)
		}
	}

	// Start a goroutine to close out once all the output goroutines are
	// done.  This must start after the wg.Add call.
	go func() {
		wg.Wait()
		close(out)
	}()
	return out
426
}