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

import (
4
	"errors"
5
	_ "expvar"
6
	"fmt"
7
	"net"
8 9
	"net/http"
	_ "net/http/pprof"
10
	"os"
Overbool's avatar
Overbool committed
11
	"runtime"
12
	"sort"
13
	"sync"
14

Overbool's avatar
Overbool committed
15
	version "github.com/ipfs/go-ipfs"
16 17
	config "github.com/ipfs/go-ipfs-config"
	cserial "github.com/ipfs/go-ipfs-config/serialize"
18
	sockets "github.com/ipfs/go-ipfs/cmd/ipfs/sockets"
19
	utilmain "github.com/ipfs/go-ipfs/cmd/ipfs/util"
keks's avatar
fmt  
keks committed
20
	oldcmds "github.com/ipfs/go-ipfs/commands"
21
	"github.com/ipfs/go-ipfs/core"
keks's avatar
fmt  
keks committed
22
	commands "github.com/ipfs/go-ipfs/core/commands"
23
	coreapi "github.com/ipfs/go-ipfs/core/coreapi"
24
	corehttp "github.com/ipfs/go-ipfs/core/corehttp"
rht's avatar
rht committed
25
	corerepo "github.com/ipfs/go-ipfs/core/corerepo"
26
	libp2p "github.com/ipfs/go-ipfs/core/node/libp2p"
27
	nodeMount "github.com/ipfs/go-ipfs/fuse/node"
28
	fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
Jeromy's avatar
Jeromy committed
29
	migrate "github.com/ipfs/go-ipfs/repo/fsrepo/migrations"
Jakub Sztandera's avatar
Jakub Sztandera committed
30

31
	"github.com/hashicorp/go-multierror"
32
	cmds "github.com/ipfs/go-ipfs-cmds"
Jakub Sztandera's avatar
Jakub Sztandera committed
33 34 35
	mprome "github.com/ipfs/go-metrics-prometheus"
	goprocess "github.com/jbenet/goprocess"
	ma "github.com/multiformats/go-multiaddr"
36
	manet "github.com/multiformats/go-multiaddr-net"
Jakub Sztandera's avatar
Jakub Sztandera committed
37
	"github.com/prometheus/client_golang/prometheus"
38 39 40
)

const (
Jeromy's avatar
Jeromy committed
41 42
	adjustFDLimitKwd          = "manage-fdlimit"
	enableGCKwd               = "enable-gc"
43
	initOptionKwd             = "init"
44
	initConfigOptionKwd       = "init-config"
45
	initProfileOptionKwd      = "init-profile"
46 47
	ipfsMountKwd              = "mount-ipfs"
	ipnsMountKwd              = "mount-ipns"
Jeromy's avatar
Jeromy committed
48 49
	migrateKwd                = "migrate"
	mountKwd                  = "mount"
50
	offlineKwd                = "offline" // global option
Jeromy's avatar
Jeromy committed
51 52
	routingOptionKwd          = "routing"
	routingOptionSupernodeKwd = "supernode"
Jeromy's avatar
Jeromy committed
53
	routingOptionDHTClientKwd = "dhtclient"
54
	routingOptionDHTKwd       = "dht"
Jeromy's avatar
Jeromy committed
55
	routingOptionNoneKwd      = "none"
56
	routingOptionDefaultKwd   = "default"
Jeromy's avatar
Jeromy committed
57 58 59
	unencryptTransportKwd     = "disable-transport-encryption"
	unrestrictedApiAccessKwd  = "unrestricted-api"
	writableKwd               = "writable"
Steven Allen's avatar
Steven Allen committed
60
	enablePubSubKwd           = "enable-pubsub-experiment"
vyzo's avatar
vyzo committed
61
	enableIPNSPubSubKwd       = "enable-namesys-pubsub"
62
	enableMultiplexKwd        = "enable-mplex-experiment"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
63 64
	// apiAddrKwd    = "address-api"
	// swarmAddrKwd  = "address-swarm"
65 66
)

67
var daemonCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
68
	Helptext: cmds.HelpText{
69
		Tagline: "Run a network-connected IPFS node.",
Matt Bell's avatar
Matt Bell committed
70
		ShortDescription: `
71
'ipfs daemon' runs a persistent ipfs daemon that can serve commands
Matt Bell's avatar
Matt Bell committed
72 73 74 75
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.
76 77
`,
		LongDescription: `
anarcat's avatar
anarcat committed
78 79 80 81
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:

82
  ipfs config Addresses.Gateway /ip4/127.0.0.1/tcp/8082
anarcat's avatar
anarcat committed
83

anarcat's avatar
anarcat committed
84 85
The API address can be changed the same way:

86
  ipfs config Addresses.API /ip4/127.0.0.1/tcp/5002
anarcat's avatar
anarcat committed
87 88 89

Make sure to restart the daemon after changing addresses.

90 91
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:
anarcat's avatar
anarcat committed
92

93
  ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8080
anarcat's avatar
anarcat committed
94

95 96 97 98
Be careful if you expose the API. It is a security risk, as anyone could
control 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).
Jason Carver's avatar
Jason Carver committed
99

100
HTTP Headers
Jason Carver's avatar
Jason Carver committed
101

102
ipfs supports passing arbitrary headers to the API and Gateway. You can
103 104 105
do this by setting headers on the API.HTTPHeaders and Gateway.HTTPHeaders
keys:

Oli Evans's avatar
Oli Evans committed
106 107
  ipfs config --json API.HTTPHeaders.X-Special-Header "[\"so special :)\"]"
  ipfs config --json Gateway.HTTPHeaders.X-Special-Header "[\"so special :)\"]"
108 109 110 111 112 113 114 115 116

Note that the value of the keys is an _array_ of strings. This is because
headers can have more than one value, and it is convenient to pass through
to other libraries.

CORS Headers (for API)

You can setup CORS headers the same way:

Oli Evans's avatar
Oli Evans committed
117 118 119
  ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin "[\"example.com\"]"
  ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods "[\"PUT\", \"GET\", \"POST\"]"
  ipfs config --json API.HTTPHeaders.Access-Control-Allow-Credentials "[\"true\"]"
120

121 122
Shutdown

123 124 125 126
To shutdown the daemon, send a SIGINT signal to it (e.g. by pressing 'Ctrl-C')
or send a SIGTERM signal to it (e.g. with 'kill'). It may take a while for the
daemon to shutdown gracefully, but it can be killed forcibly by sending a
second signal.
127

128 129
IPFS_PATH environment variable

130 131 132
ipfs uses a repository in the local file system. By default, the repo is
located at ~/.ipfs. To change the repo location, set the $IPFS_PATH
environment variable:
133

134
  export IPFS_PATH=/path/to/ipfsrepo
135

136 137 138
Routing

IPFS by default will use a DHT for content routing. There is a highly
139 140
experimental alternative that operates the DHT in a 'client only' mode that
can be enabled by running the daemon as:
141

142
  ipfs daemon --routing=dhtclient
143 144 145 146

This will later be transitioned into a config option once it gets out of the
'experimental' stage.

147 148
DEPRECATION NOTICE

149
Previously, ipfs used an environment variable as seen below:
150

151
  export API_ORIGIN="http://localhost:8888/"
152

153 154 155
This is deprecated. It is still honored in this version, but will be removed
in a future version, along with this notice. Please move to setting the HTTP
Headers.
156
`,
157 158
	},

Steven Allen's avatar
Steven Allen committed
159 160
	Options: []cmds.Option{
		cmds.BoolOption(initOptionKwd, "Initialize ipfs with default settings if not already initialized"),
161
		cmds.StringOption(initConfigOptionKwd, "Path to existing configuration file to be loaded during --init"),
Steven Allen's avatar
Steven Allen committed
162 163 164 165 166 167 168 169 170 171 172 173 174 175
		cmds.StringOption(initProfileOptionKwd, "Configuration profiles to apply for --init. See ipfs init --help for more"),
		cmds.StringOption(routingOptionKwd, "Overrides the routing option").WithDefault(routingOptionDefaultKwd),
		cmds.BoolOption(mountKwd, "Mounts IPFS to the filesystem"),
		cmds.BoolOption(writableKwd, "Enable writing objects (with POST, PUT and DELETE)"),
		cmds.StringOption(ipfsMountKwd, "Path to the mountpoint for IPFS (if using --mount). Defaults to config setting."),
		cmds.StringOption(ipnsMountKwd, "Path to the mountpoint for IPNS (if using --mount). Defaults to config setting."),
		cmds.BoolOption(unrestrictedApiAccessKwd, "Allow API access to unlisted hashes"),
		cmds.BoolOption(unencryptTransportKwd, "Disable transport encryption (for debugging protocols)"),
		cmds.BoolOption(enableGCKwd, "Enable automatic periodic repo garbage collection"),
		cmds.BoolOption(adjustFDLimitKwd, "Check and raise file descriptor limits if needed").WithDefault(true),
		cmds.BoolOption(migrateKwd, "If true, assume yes at the migrate prompt. If false, assume no."),
		cmds.BoolOption(enablePubSubKwd, "Instantiate the ipfs daemon with the experimental pubsub feature enabled."),
		cmds.BoolOption(enableIPNSPubSubKwd, "Enable IPNS record distribution through pubsub; enables pubsub."),
		cmds.BoolOption(enableMultiplexKwd, "Add the experimental 'go-multiplex' stream muxer to libp2p on construction.").WithDefault(true),
Jan Winkelmann's avatar
Jan Winkelmann committed
176

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
177
		// TODO: add way to override addresses. tricky part: updating the config if also --init.
Steven Allen's avatar
Steven Allen committed
178 179
		// cmds.StringOption(apiAddrKwd, "Address for the daemon rpc API (overrides config)"),
		// cmds.StringOption(swarmAddrKwd, "Address for the swarm socket (overrides config)"),
180
	},
181 182 183 184
	Subcommands: map[string]*cmds.Command{},
	Run:         daemonFunc,
}

185 186 187 188
// 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.
189 190
func defaultMux(path string) corehttp.ServeOption {
	return func(node *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
191 192 193 194 195
		mux.Handle(path, http.DefaultServeMux)
		return mux, nil
	}
}

196
func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) (_err error) {
197 198 199
	// Inject metrics before we do anything
	err := mprome.Inject()
	if err != nil {
keks's avatar
keks committed
200
		log.Errorf("Injecting prometheus handler for metrics failed with message: %s\n", err.Error())
201 202
	}

203 204
	// let the user know we're going.
	fmt.Printf("Initializing daemon...\n")
205

206 207 208 209 210 211 212 213 214
	defer func() {
		if _err != nil {
			// Print an extra line before any errors. This could go
			// in the commands lib but doesn't really make sense for
			// all commands.
			fmt.Println()
		}
	}()

Overbool's avatar
Overbool committed
215 216 217
	// print the ipfs version
	printVersion()

218 219
	managefd, _ := req.Options[adjustFDLimitKwd].(bool)
	if managefd {
220
		if _, _, err := utilmain.ManageFdLimit(); err != nil {
221
			log.Errorf("setting file descriptor limit: %s", err)
222
		}
Jeromy's avatar
Jeromy committed
223 224
	}

keks's avatar
cleanup  
keks committed
225
	cctx := env.(*oldcmds.Context)
226

227
	// check transport encryption flag.
228
	unencrypted, _ := req.Options[unencryptTransportKwd].(bool)
229 230 231 232 233
	if unencrypted {
		log.Warningf(`Running with --%s: All connections are UNENCRYPTED.
		You will not be able to connect to regular encrypted networks.`, unencryptTransportKwd)
	}

234 235
	// first, whether user has provided the initialization flag. we may be
	// running in an uninitialized state.
236
	initialize, _ := req.Options[initOptionKwd].(bool)
237 238 239 240
	if initialize && !fsrepo.IsInitialized(cctx.ConfigRoot) {
		cfgLocation, _ := req.Options[initConfigOptionKwd].(string)
		profiles, _ := req.Options[initProfileOptionKwd].(string)
		var conf *config.Config
241

242 243
		if cfgLocation != "" {
			if conf, err = cserial.Load(cfgLocation); err != nil {
keks's avatar
keks committed
244
				return err
245 246
			}
		}
247 248 249 250

		if err = doInit(os.Stdout, cctx.ConfigRoot, false, nBitsForKeypairDefault, profiles, conf); err != nil {
			return err
		}
251 252
	}

253 254
	// acquire the repo lock _before_ constructing a node. we need to make
	// sure we are permitted to access the resources (datastore, etc.)
keks's avatar
cleanup  
keks committed
255
	repo, err := fsrepo.Open(cctx.ConfigRoot)
Jeromy's avatar
Jeromy committed
256 257
	switch err {
	default:
keks's avatar
keks committed
258
		return err
Jeromy's avatar
Jeromy committed
259
	case fsrepo.ErrNeedMigration:
260
		domigrate, found := req.Options[migrateKwd].(bool)
Lars Gierth's avatar
Lars Gierth committed
261
		fmt.Println("Found outdated fs-repo, migrations need to be run.")
Jeromy's avatar
Jeromy committed
262 263

		if !found {
Lars Gierth's avatar
Lars Gierth committed
264
			domigrate = YesNoPrompt("Run migrations now? [y/N]")
Jeromy's avatar
Jeromy committed
265
		}
Jeromy's avatar
Jeromy committed
266 267

		if !domigrate {
Lars Gierth's avatar
Lars Gierth committed
268 269
			fmt.Println("Not running migrations of fs-repo now.")
			fmt.Println("Please get fs-repo-migrations from https://dist.ipfs.io")
keks's avatar
keks committed
270
			return fmt.Errorf("fs-repo requires migration")
Jeromy's avatar
Jeromy committed
271 272 273
		}

		err = migrate.RunMigration(fsrepo.RepoVersion)
Jeromy's avatar
Jeromy committed
274
		if err != nil {
Lars Gierth's avatar
Lars Gierth committed
275 276 277 278
			fmt.Println("The migrations of fs-repo failed:")
			fmt.Printf("  %s\n", err)
			fmt.Println("If you think this is a bug, please file an issue and include this whole log output.")
			fmt.Println("  https://github.com/ipfs/fs-repo-migrations")
keks's avatar
keks committed
279
			return err
Jeromy's avatar
Jeromy committed
280 281
		}

keks's avatar
cleanup  
keks committed
282
		repo, err = fsrepo.Open(cctx.ConfigRoot)
Jeromy's avatar
Jeromy committed
283
		if err != nil {
keks's avatar
keks committed
284
			return err
Jeromy's avatar
Jeromy committed
285 286 287
		}
	case nil:
		break
288 289
	}

290 291 292 293
	// The node will also close the repo but there are many places we could
	// fail before we get to that. It can't hurt to close it twice.
	defer repo.Close()

294
	offline, _ := req.Options[offlineKwd].(bool)
keks's avatar
fmt  
keks committed
295
	ipnsps, _ := req.Options[enableIPNSPubSubKwd].(bool)
Steven Allen's avatar
Steven Allen committed
296
	pubsub, _ := req.Options[enablePubSubKwd].(bool)
297
	mplex, _ := req.Options[enableMultiplexKwd].(bool)
Jeromy's avatar
Jeromy committed
298

299 300
	// Start assembling node config
	ncfg := &core.BuildCfg{
Steven Allen's avatar
Steven Allen committed
301 302 303
		Repo:                        repo,
		Permanent:                   true, // It is temporary way to signify that node is permanent
		Online:                      !offline,
Steven Allen's avatar
Steven Allen committed
304
		DisableEncryptedConnections: unencrypted,
Jeromy's avatar
Jeromy committed
305 306
		ExtraOpts: map[string]bool{
			"pubsub": pubsub,
vyzo's avatar
vyzo committed
307
			"ipnsps": ipnsps,
308
			"mplex":  mplex,
Jeromy's avatar
Jeromy committed
309 310
		},
		//TODO(Kubuxu): refactor Online vs Offline by adding Permanent vs Ephemeral
311
	}
Jeromy's avatar
Jeromy committed
312

313
	routingOption, _ := req.Options[routingOptionKwd].(string)
314 315 316
	if routingOption == routingOptionDefaultKwd {
		cfg, err := repo.Config()
		if err != nil {
keks's avatar
keks committed
317
			return err
318 319
		}

320
		routingOption = cfg.Routing.Type
321 322 323 324
		if routingOption == "" {
			routingOption = routingOptionDHTKwd
		}
	}
Jeromy's avatar
Jeromy committed
325 326
	switch routingOption {
	case routingOptionSupernodeKwd:
keks's avatar
keks committed
327
		return errors.New("supernode routing was never fully implemented and has been removed")
Jeromy's avatar
Jeromy committed
328
	case routingOptionDHTClientKwd:
329
		ncfg.Routing = libp2p.DHTClientOption
330
	case routingOptionDHTKwd:
331
		ncfg.Routing = libp2p.DHTOption
Jeromy's avatar
Jeromy committed
332
	case routingOptionNoneKwd:
333
		ncfg.Routing = libp2p.NilRouterOption
334
	default:
keks's avatar
keks committed
335
		return fmt.Errorf("unrecognized routing option: %s", routingOption)
336 337
	}

338
	node, err := core.NewNode(req.Context, ncfg)
339
	if err != nil {
340
		log.Error("error from node construction: ", err)
keks's avatar
keks committed
341
		return err
342
	}
343
	node.IsDaemon = true
344

345
	if node.PNetFingerprint != nil {
Jakub Sztandera's avatar
Jakub Sztandera committed
346
		fmt.Println("Swarm is limited to private network of peers with the swarm key")
347
		fmt.Printf("Swarm key fingerprint: %x\n", node.PNetFingerprint)
Jakub Sztandera's avatar
Jakub Sztandera committed
348 349
	}

350 351
	printSwarmAddrs(node)

352 353 354 355 356 357
	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 {
358
		case <-req.Context.Done():
359 360 361 362 363
			log.Info("Gracefully shut down daemon")
		default:
		}
	}()

keks's avatar
cleanup  
keks committed
364
	cctx.ConstructNode = func() (*core.IpfsNode, error) {
365 366
		return node, nil
	}
367

368 369 370 371 372 373 374 375 376 377
	// Start "core" plugins. We want to do this *before* starting the HTTP
	// API as the user may be relying on these plugins.
	api, err := coreapi.NewCoreAPI(node)
	if err != nil {
		return err
	}
	err = cctx.Plugins.Start(api)
	if err != nil {
		return err
	}
378
	node.Process.AddChild(goprocess.WithTeardown(cctx.Plugins.Close))
379

380
	// construct api endpoint - every time
Steven Allen's avatar
Steven Allen committed
381
	apiErrc, err := serveHTTPApi(req, cctx)
382
	if err != nil {
keks's avatar
keks committed
383
		return err
384 385
	}

386
	// construct fuse mountpoints - if the user provided the --mount flag
387
	mount, _ := req.Options[mountKwd].(bool)
388
	if mount && offline {
Steven Allen's avatar
Steven Allen committed
389
		return cmds.Errorf(cmds.ErrClient, "mount is not currently supported in offline mode")
390
	}
391
	if mount {
keks's avatar
cleanup  
keks committed
392
		if err := mountFuse(req, cctx); err != nil {
keks's avatar
keks committed
393
			return err
394
		}
395
	}
396

rht's avatar
rht committed
397
	// repo blockstore GC - if --enable-gc flag is present
Steven Allen's avatar
Steven Allen committed
398
	gcErrc, err := maybeRunGC(req, node)
rht's avatar
rht committed
399
	if err != nil {
keks's avatar
keks committed
400
		return err
rht's avatar
rht committed
401 402
	}

403 404 405 406
	// construct http gateway
	gwErrc, err := serveHTTPGateway(req, cctx)
	if err != nil {
		return err
407 408
	}

Lars Gierth's avatar
Lars Gierth committed
409
	// initialize metrics collector
Jakub Sztandera's avatar
Jakub Sztandera committed
410
	prometheus.MustRegister(&corehttp.IpfsNodeCollector{Node: node})
Lars Gierth's avatar
Lars Gierth committed
411

412
	// The daemon is *finally* ready.
rht's avatar
rht committed
413
	fmt.Printf("Daemon is ready\n")
414 415 416 417 418 419 420 421

	// Give the user some immediate feedback when they hit C-c
	go func() {
		<-req.Context.Done()
		fmt.Println("Received interrupt signal, shutting down...")
		fmt.Println("(Hit ctrl-c again to force-shutdown the daemon.)")
	}()

422 423
	// collect long-running errors and block for shutdown
	// TODO(cryptix): our fuse currently doesnt follow this pattern for graceful shutdown
424
	var errs error
rht's avatar
rht committed
425
	for err := range merge(apiErrc, gwErrc, gcErrc) {
426
		if err != nil {
427
			errs = multierror.Append(errs, err)
428 429
		}
	}
keks's avatar
keks committed
430

431
	return errs
432
}
433

Henry's avatar
Henry committed
434
// serveHTTPApi collects options, creates listener, prints status message and starts serving requests
Steven Allen's avatar
Steven Allen committed
435
func serveHTTPApi(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, error) {
436
	cfg, err := cctx.GetConfig()
437
	if err != nil {
Steven Allen's avatar
Steven Allen committed
438
		return nil, fmt.Errorf("serveHTTPApi: GetConfig() failed: %s", err)
439 440
	}

441 442
	listeners, err := sockets.TakeSockets("io.ipfs.api")
	if err != nil {
Steven Allen's avatar
Steven Allen committed
443
		return nil, fmt.Errorf("serveHTTPApi: socket activation failed: %s", err)
444
	}
445

446 447 448 449 450 451 452
	if len(listeners) == 0 {
		apiAddrs := make([]string, 0, 2)
		apiAddr, _ := req.Options[commands.ApiOption].(string)
		if apiAddr == "" {
			apiAddrs = cfg.Addresses.API
		} else {
			apiAddrs = append(apiAddrs, apiAddr)
453 454
		}

455 456 457 458 459 460 461 462 463 464 465 466 467
		listeners := make([]manet.Listener, 0, len(apiAddrs))
		for _, addr := range apiAddrs {
			apiMaddr, err := ma.NewMultiaddr(addr)
			if err != nil {
				return nil, fmt.Errorf("serveHTTPApi: invalid API address: %q (err: %s)", apiAddr, err)
			}

			apiLis, err := manet.Listen(apiMaddr)
			if err != nil {
				return nil, fmt.Errorf("serveHTTPApi: manet.Listen(%s) failed: %s", apiMaddr, err)
			}

			listeners = append(listeners, apiLis)
468
		}
469
	}
470

471
	for _, listener := range listeners {
472
		// we might have listened to /tcp/0 - lets see what we are listing on
473 474
		fmt.Printf("API server listening on %s\n", listener.Multiaddr())
		fmt.Printf("WebUI: http://%s/webui\n", listener.Addr())
475 476
	}

477 478 479 480
	// by default, we don't let you load arbitrary ipfs objects through the api,
	// because this would open up the api to scripting vulnerabilities.
	// only the webui objects are allowed.
	// if you know what you're doing, go ahead and pass --unrestricted-api.
481
	unrestricted, _ := req.Options[unrestrictedApiAccessKwd].(bool)
Lars Gierth's avatar
Lars Gierth committed
482
	gatewayOpt := corehttp.GatewayOption(false, corehttp.WebUIPaths...)
483
	if unrestricted {
Lars Gierth's avatar
Lars Gierth committed
484
		gatewayOpt = corehttp.GatewayOption(true, "/ipfs", "/ipns")
485 486
	}

487
	var opts = []corehttp.ServeOption{
Lars Gierth's avatar
Lars Gierth committed
488
		corehttp.MetricsCollectionOption("api"),
keks's avatar
keks committed
489
		corehttp.CheckVersionOption(),
490
		corehttp.CommandsOption(*cctx),
491
		corehttp.WebUIOption,
492
		gatewayOpt,
493
		corehttp.VersionOption(),
494 495
		defaultMux("/debug/vars"),
		defaultMux("/debug/pprof/"),
496
		corehttp.MutexFractionOption("/debug/pprof-mutex/"),
Lars Gierth's avatar
Lars Gierth committed
497
		corehttp.MetricsScrapingOption("/debug/metrics/prometheus"),
Jeromy's avatar
Jeromy committed
498
		corehttp.LogOption(),
499
	}
500

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

505
	node, err := cctx.ConstructNode()
506
	if err != nil {
Steven Allen's avatar
Steven Allen committed
507
		return nil, fmt.Errorf("serveHTTPApi: ConstructNode() failed: %s", err)
508 509
	}

510
	if err := node.Repo.SetAPIAddr(listeners[0].Multiaddr()); err != nil {
Steven Allen's avatar
Steven Allen committed
511
		return nil, fmt.Errorf("serveHTTPApi: SetAPIAddr() failed: %s", err)
512 513 514
	}

	errc := make(chan error)
515 516 517 518 519 520 521 522 523
	var wg sync.WaitGroup
	for _, apiLis := range listeners {
		wg.Add(1)
		go func(lis manet.Listener) {
			defer wg.Done()
			errc <- corehttp.Serve(node, manet.NetListener(lis), opts...)
		}(apiLis)
	}

524
	go func() {
525
		wg.Wait()
526
		close(errc)
527
	}()
528

Steven Allen's avatar
Steven Allen committed
529
	return errc, nil
530 531
}

532 533
// printSwarmAddrs prints the addresses of the host
func printSwarmAddrs(node *core.IpfsNode) {
534
	if !node.IsOnline {
Jakub Sztandera's avatar
Jakub Sztandera committed
535 536 537
		fmt.Println("Swarm not listening, running in offline mode.")
		return
	}
538 539 540 541 542 543 544 545 546

	var lisAddrs []string
	ifaceAddrs, err := node.PeerHost.Network().InterfaceListenAddresses()
	if err != nil {
		log.Errorf("failed to read listening addresses: %s", err)
	}
	for _, addr := range ifaceAddrs {
		lisAddrs = append(lisAddrs, addr.String())
	}
547
	sort.Strings(lisAddrs)
548 549 550 551
	for _, addr := range lisAddrs {
		fmt.Printf("Swarm listening on %s\n", addr)
	}

552 553 554 555
	var addrs []string
	for _, addr := range node.PeerHost.Addrs() {
		addrs = append(addrs, addr.String())
	}
556
	sort.Strings(addrs)
557
	for _, addr := range addrs {
558
		fmt.Printf("Swarm announcing %s\n", addr)
559
	}
560

561 562
}

Henry's avatar
Henry committed
563
// serveHTTPGateway collects options, creates listener, prints status message and starts serving requests
Steven Allen's avatar
Steven Allen committed
564
func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, error) {
565
	cfg, err := cctx.GetConfig()
566
	if err != nil {
Steven Allen's avatar
Steven Allen committed
567
		return nil, fmt.Errorf("serveHTTPGateway: GetConfig() failed: %s", err)
568 569
	}

570
	writable, writableOptionFound := req.Options[writableKwd].(bool)
Lars Gierth's avatar
Lars Gierth committed
571 572
	if !writableOptionFound {
		writable = cfg.Gateway.Writable
573 574
	}

575 576 577 578
	listeners, err := sockets.TakeSockets("io.ipfs.gateway")
	if err != nil {
		return nil, fmt.Errorf("serveHTTPGateway: socket activation failed: %s", err)
	}
579

580 581 582 583 584 585 586
	if len(listeners) == 0 {
		gatewayAddrs := cfg.Addresses.Gateway
		for _, addr := range gatewayAddrs {
			gatewayMaddr, err := ma.NewMultiaddr(addr)
			if err != nil {
				return nil, fmt.Errorf("serveHTTPGateway: invalid gateway address: %q (err: %s)", addr, err)
			}
587

588 589 590 591 592
			gwLis, err := manet.Listen(gatewayMaddr)
			if err != nil {
				return nil, fmt.Errorf("serveHTTPGateway: manet.Listen(%s) failed: %s", gatewayMaddr, err)
			}
			listeners = append(listeners, gwLis)
593
		}
594 595 596 597 598 599 600
	}

	// we might have listened to /tcp/0 - lets see what we are listing on
	gwType := "readonly"
	if writable {
		gwType = "writable"
	}
601

602 603
	for _, listener := range listeners {
		fmt.Printf("Gateway (%s) server listening on %s\n", gwType, listener.Multiaddr())
604 605
	}

606 607 608
	cmdctx := *cctx
	cmdctx.Gateway = true

609
	var opts = []corehttp.ServeOption{
Lars Gierth's avatar
Lars Gierth committed
610
		corehttp.MetricsCollectionOption("gateway"),
611
		corehttp.IPNSHostnameOption(),
Lars Gierth's avatar
Lars Gierth committed
612
		corehttp.GatewayOption(writable, "/ipfs", "/ipns"),
613
		corehttp.VersionOption(),
614
		corehttp.CheckVersionOption(),
615
		corehttp.CommandsROOption(cmdctx),
616
	}
617

618 619 620 621
	if cfg.Experimental.P2pHttpProxy {
		opts = append(opts, corehttp.ProxyOption())
	}

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

626
	node, err := cctx.ConstructNode()
627
	if err != nil {
Steven Allen's avatar
Steven Allen committed
628
		return nil, fmt.Errorf("serveHTTPGateway: ConstructNode() failed: %s", err)
629 630 631
	}

	errc := make(chan error)
632 633 634 635 636 637 638 639 640
	var wg sync.WaitGroup
	for _, lis := range listeners {
		wg.Add(1)
		go func(lis manet.Listener) {
			defer wg.Done()
			errc <- corehttp.Serve(node, manet.NetListener(lis), opts...)
		}(lis)
	}

641
	go func() {
642
		wg.Wait()
643
		close(errc)
644
	}()
645

Steven Allen's avatar
Steven Allen committed
646
	return errc, nil
647 648 649
}

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

656
	fsdir, found := req.Options[ipfsMountKwd].(string)
657 658 659 660
	if !found {
		fsdir = cfg.Mounts.IPFS
	}

661
	nsdir, found := req.Options[ipnsMountKwd].(string)
662 663 664 665
	if !found {
		nsdir = cfg.Mounts.IPNS
	}

666
	node, err := cctx.ConstructNode()
667 668 669 670
	if err != nil {
		return fmt.Errorf("mountFuse: ConstructNode() failed: %s", err)
	}

671
	err = nodeMount.Mount(node, fsdir, nsdir)
672 673 674 675 676 677 678 679
	if err != nil {
		return err
	}
	fmt.Printf("IPFS mounted at: %s\n", fsdir)
	fmt.Printf("IPNS mounted at: %s\n", nsdir)
	return nil
}

Steven Allen's avatar
Steven Allen committed
680
func maybeRunGC(req *cmds.Request, node *core.IpfsNode) (<-chan error, error) {
681
	enableGC, _ := req.Options[enableGCKwd].(bool)
rht's avatar
rht committed
682 683 684 685 686 687
	if !enableGC {
		return nil, nil
	}

	errc := make(chan error)
	go func() {
688
		errc <- corerepo.PeriodicGC(req.Context, node)
rht's avatar
rht committed
689 690
		close(errc)
	}()
Steven Allen's avatar
Steven Allen committed
691
	return errc, nil
rht's avatar
rht committed
692 693
}

694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709
// 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
710
			wg.Add(1)
711 712 713 714 715 716 717 718 719 720 721
			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
722
}
Jeromy's avatar
Jeromy committed
723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741

func YesNoPrompt(prompt string) bool {
	var s string
	for i := 0; i < 3; i++ {
		fmt.Printf("%s ", prompt)
		fmt.Scanf("%s", &s)
		switch s {
		case "y", "Y":
			return true
		case "n", "N":
			return false
		case "":
			return false
		}
		fmt.Println("Please press either 'y' or 'n'")
	}

	return false
}
Overbool's avatar
Overbool committed
742 743

func printVersion() {
744 745 746 747 748
	v := version.CurrentVersionNumber
	if version.CurrentCommit != "" {
		v += "-" + version.CurrentCommit
	}
	fmt.Printf("go-ipfs version: %s\n", v)
Overbool's avatar
Overbool committed
749 750 751 752
	fmt.Printf("Repo version: %d\n", fsrepo.RepoVersion)
	fmt.Printf("System version: %s\n", runtime.GOARCH+"/"+runtime.GOOS)
	fmt.Printf("Golang version: %s\n", runtime.Version())
}