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

import (
4
	_ "expvar"
5
	"fmt"
6
	_ "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/codahale/metrics/runtime"
7 8
	"net/http"
	_ "net/http/pprof"
9
	"os"
10
	"strings"
11

12 13 14 15 16 17 18 19 20
	ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
	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"
21 22 23
)

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

35
var daemonCmd = &cmds.Command{
36
	Helptext: cmds.HelpText{
Matt Bell's avatar
Matt Bell committed
37 38 39 40 41 42 43
		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
44 45 46 47 48 49 50

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
51 52 53 54 55 56 57 58 59 60 61
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

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
62
Be careful if you expose the API. It is a security risk, as anyone could use control
anarcat's avatar
anarcat committed
63 64
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).`,
65 66
	},

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

		// 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)"),
78
	},
79 80 81 82
	Subcommands: map[string]*cmds.Command{},
	Run:         daemonFunc,
}

83 84 85 86 87 88 89 90 91 92 93
// 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
	}
}

94
func daemonFunc(req cmds.Request, res cmds.Response) {
95 96
	// let the user know we're going.
	fmt.Printf("Initializing daemon...\n")
97

98 99 100 101 102 103 104 105 106
	ctx := req.Context()

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

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

115
	if initialize {
116 117 118 119 120 121

		// 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) {
122
			err := initWithDefaults(os.Stdout, req.Context().ConfigRoot)
123
			if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
124
				res.SetError(err, cmds.ErrNormal)
125
				return
126 127 128 129
			}
		}
	}

130 131 132
	// 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)
133
	if err != nil {
134 135
		res.SetError(err, cmds.ErrNormal)
		return
136 137
	}

138
	cfg, err := ctx.GetConfig()
139
	if err != nil {
140
		res.SetError(err, cmds.ErrNormal)
141
		return
142 143
	}

Jeromy's avatar
Jeromy committed
144 145 146 147
	// Start assembling corebuilder
	nb := core.NewNodeBuilder().Online()
	nb.SetRepo(repo)

148
	routingOption, _, err := req.Option(routingOptionKwd).String()
149 150 151 152
	if err != nil {
		res.SetError(err, cmds.ErrNormal)
		return
	}
153
	if routingOption == routingOptionSupernodeKwd {
154
		servers, err := repo.Config().SupernodeRouting.ServerIPFSAddrs()
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
		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
170
	node, err := nb.Build(ctx.Context)
171
	if err != nil {
172
		log.Error("error from node construction: ", err)
173 174
		res.SetError(err, cmds.ErrNormal)
		return
175
	}
176 177 178 179 180 181 182 183 184 185 186 187 188

	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:
		}
	}()

189 190 191
	req.Context().ConstructNode = func() (*core.IpfsNode, error) {
		return node, nil
	}
192

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
193 194
	// verify api address is valid multiaddr
	apiMaddr, err := ma.NewMultiaddr(cfg.Addresses.API)
195
	if err != nil {
196 197
		res.SetError(err, cmds.ErrNormal)
		return
198 199
	}

200 201 202 203 204 205 206 207
	var gatewayMaddr ma.Multiaddr
	if len(cfg.Addresses.Gateway) > 0 {
		// ignore error for gateway address
		// if there is an error (invalid address), then don't run the gateway
		gatewayMaddr, _ = ma.NewMultiaddr(cfg.Addresses.Gateway)
		if gatewayMaddr == nil {
			log.Errorf("Invalid gateway address: %s", cfg.Addresses.Gateway)
		}
208 209
	}

210 211
	// mount if the user provided the --mount flag
	mount, _, err := req.Option(mountKwd).Bool()
212
	if err != nil {
213 214
		res.SetError(err, cmds.ErrNormal)
		return
215
	}
216 217 218
	if mount {
		fsdir, found, err := req.Option(ipfsMountKwd).String()
		if err != nil {
219 220
			res.SetError(err, cmds.ErrNormal)
			return
221 222 223 224 225 226 227
		}
		if !found {
			fsdir = cfg.Mounts.IPFS
		}

		nsdir, found, err := req.Option(ipnsMountKwd).String()
		if err != nil {
228 229
			res.SetError(err, cmds.ErrNormal)
			return
230 231 232 233 234 235 236
		}
		if !found {
			nsdir = cfg.Mounts.IPNS
		}

		err = commands.Mount(node, fsdir, nsdir)
		if err != nil {
237 238
			res.SetError(err, cmds.ErrNormal)
			return
239
		}
240 241
		fmt.Printf("IPFS mounted at: %s\n", fsdir)
		fmt.Printf("IPNS mounted at: %s\n", nsdir)
242
	}
243

244 245 246 247 248
	var rootRedirect corehttp.ServeOption
	if len(cfg.Gateway.RootRedirect) > 0 {
		rootRedirect = corehttp.RedirectOption("", cfg.Gateway.RootRedirect)
	}

249 250 251 252 253 254 255 256 257
	writable, writableOptionFound, err := req.Option(writableKwd).Bool()
	if err != nil {
		res.SetError(err, cmds.ErrNormal)
		return
	}
	if !writableOptionFound {
		writable = cfg.Gateway.Writable
	}

258
	if gatewayMaddr != nil {
259
		go func() {
260
			var opts = []corehttp.ServeOption{
Jeromy's avatar
Jeromy committed
261
				corehttp.VersionOption(),
262 263 264
				corehttp.IPNSHostnameOption(),
				corehttp.GatewayOption(writable),
			}
265 266 267
			if rootRedirect != nil {
				opts = append(opts, rootRedirect)
			}
268
			if writable {
269 270 271
				fmt.Printf("Gateway (writable) server listening on %s\n", gatewayMaddr)
			} else {
				fmt.Printf("Gateway (readonly) server listening on %s\n", gatewayMaddr)
272
			}
273
			err := corehttp.ListenAndServe(node, gatewayMaddr.String(), opts...)
274 275 276 277
			if err != nil {
				log.Error(err)
			}
		}()
278 279
	}

280
	gateway := corehttp.NewGateway(corehttp.GatewayConfig{
281 282 283 284 285 286 287 288 289 290 291 292
		Writable: true,
		BlockList: &corehttp.BlockList{
			Decider: func(s string) bool {
				// for now, only allow paths in the WebUI path
				for _, webuipath := range corehttp.WebUIPaths {
					if strings.HasPrefix(s, webuipath) {
						return true
					}
				}
				return false
			},
		},
293
	})
294 295 296
	var opts = []corehttp.ServeOption{
		corehttp.CommandsOption(*req.Context()),
		corehttp.WebUIOption,
297
		gateway.ServeOption(),
298
		corehttp.VersionOption(),
299 300
		defaultMux("/debug/vars"),
		defaultMux("/debug/pprof/"),
301
	}
302

303 304 305
	if rootRedirect != nil {
		opts = append(opts, rootRedirect)
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
306
	fmt.Printf("API server listening on %s\n", apiMaddr)
307
	if err := corehttp.ListenAndServe(node, apiMaddr.String(), opts...); err != nil {
308 309 310
		res.SetError(err, cmds.ErrNormal)
		return
	}
311
}