daemon.go 9.13 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"
31
	unrestrictedApiAccess     = "unrestricted-api"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
32 33
	// apiAddrKwd    = "address-api"
	// swarmAddrKwd  = "address-swarm"
34 35
)

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

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
52 53 54 55 56 57 58 59 60 61 62
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
63
Be careful if you expose the API. It is a security risk, as anyone could control
anarcat's avatar
anarcat committed
64 65
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).`,
66 67
	},

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

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

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

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

100 101 102 103 104 105 106 107 108
	ctx := req.Context()

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

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

117
	if initialize {
118 119 120 121 122 123

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

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

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

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

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

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

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

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

202 203 204 205 206 207 208 209
	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)
		}
210 211
	}

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

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

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

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

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

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

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

309 310 311
	if rootRedirect != nil {
		opts = append(opts, rootRedirect)
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
312
	fmt.Printf("API server listening on %s\n", apiMaddr)
313
	if err := corehttp.ListenAndServe(node, apiMaddr.String(), opts...); err != nil {
314 315 316
		res.SetError(err, cmds.ErrNormal)
		return
	}
317
}