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

import (
4
	"fmt"
5
	"os"
6
	"strings"
7

8
	ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
9
	cmds "github.com/jbenet/go-ipfs/commands"
10
	"github.com/jbenet/go-ipfs/core"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
11
	commands "github.com/jbenet/go-ipfs/core/commands"
12
	corehttp "github.com/jbenet/go-ipfs/core/corehttp"
13 14
	"github.com/jbenet/go-ipfs/core/corerouting"
	peer "github.com/jbenet/go-ipfs/p2p/peer"
15
	fsrepo "github.com/jbenet/go-ipfs/repo/fsrepo"
16
	util "github.com/jbenet/go-ipfs/util"
Brian Tiger Chow's avatar
Brian Tiger Chow committed
17
	"github.com/jbenet/go-ipfs/util/debugerror"
18 19 20
)

const (
21 22 23 24 25 26 27
	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
28 29
	// apiAddrKwd    = "address-api"
	// swarmAddrKwd  = "address-swarm"
30 31
)

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

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
48 49 50 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

Be careful if you expose the API. It is a security risk, as anyone could use 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).`,
62 63
	},

64
	Options: []cmds.Option{
65
		cmds.BoolOption(initOptionKwd, "Initialize IPFS with default settings if not already initialized"),
66
		cmds.StringOption(routingOptionKwd, "Overrides the routing option (dht, supernode)"),
67
		cmds.BoolOption(mountKwd, "Mounts IPFS to the filesystem"),
68
		cmds.BoolOption(writableKwd, "Enable writing objects (with POST, PUT and DELETE)"),
69 70
		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
71 72 73 74

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

80
func daemonFunc(req cmds.Request, res cmds.Response) {
81 82
	// let the user know we're going.
	fmt.Printf("Initializing daemon...\n")
83

84 85
	// first, whether user has provided the initialization flag. we may be
	// running in an uninitialized state.
86
	initialize, _, err := req.Option(initOptionKwd).Bool()
87
	if err != nil {
88 89
		res.SetError(err, cmds.ErrNormal)
		return
90
	}
91

92
	if initialize {
93 94 95 96 97 98

		// 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) {
99
			err := initWithDefaults(os.Stdout, req.Context().ConfigRoot)
100
			if err != nil {
101 102
				res.SetError(debugerror.Wrap(err), cmds.ErrNormal)
				return
103 104 105 106
			}
		}
	}

107 108 109 110 111 112 113 114
	// To ensure that IPFS has been initialized, fetch the config. Do this
	// _before_ acquiring the daemon lock so the user gets an appropriate error
	// message.
	// NB: It's safe to read the config without the daemon lock, but not safe
	// to write.
	ctx := req.Context()
	cfg, err := ctx.GetConfig()
	if err != nil {
115 116
		res.SetError(err, cmds.ErrNormal)
		return
117 118
	}

119
	// acquire the repo lock _before_ constructing a node. we need to make
120
	// sure we are permitted to access the resources (datastore, etc.)
121 122
	repo, err := fsrepo.Open(req.Context().ConfigRoot)
	if err != nil {
123 124
		res.SetError(debugerror.Errorf("Couldn't obtain lock. Is another daemon already running?"), cmds.ErrNormal)
		return
125 126
	}

Jeromy's avatar
Jeromy committed
127 128 129 130
	// Start assembling corebuilder
	nb := core.NewNodeBuilder().Online()
	nb.SetRepo(repo)

131
	routingOption, _, err := req.Option(routingOptionKwd).String()
132 133 134 135
	if err != nil {
		res.SetError(err, cmds.ErrNormal)
		return
	}
136
	if routingOption == routingOptionSupernodeKwd {
137
		servers, err := repo.Config().SupernodeRouting.ServerIPFSAddrs()
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
		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
153
	node, err := nb.Build(ctx.Context)
154
	if err != nil {
155 156
		res.SetError(err, cmds.ErrNormal)
		return
157
	}
158 159 160 161
	defer node.Close()
	req.Context().ConstructNode = func() (*core.IpfsNode, error) {
		return node, nil
	}
162

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
163 164
	// verify api address is valid multiaddr
	apiMaddr, err := ma.NewMultiaddr(cfg.Addresses.API)
165
	if err != nil {
166 167
		res.SetError(err, cmds.ErrNormal)
		return
168 169
	}

170 171 172 173 174 175 176 177
	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)
		}
178 179
	}

180 181
	// mount if the user provided the --mount flag
	mount, _, err := req.Option(mountKwd).Bool()
182
	if err != nil {
183 184
		res.SetError(err, cmds.ErrNormal)
		return
185
	}
186 187 188
	if mount {
		fsdir, found, err := req.Option(ipfsMountKwd).String()
		if err != nil {
189 190
			res.SetError(err, cmds.ErrNormal)
			return
191 192 193 194 195 196 197
		}
		if !found {
			fsdir = cfg.Mounts.IPFS
		}

		nsdir, found, err := req.Option(ipnsMountKwd).String()
		if err != nil {
198 199
			res.SetError(err, cmds.ErrNormal)
			return
200 201 202 203 204 205 206
		}
		if !found {
			nsdir = cfg.Mounts.IPNS
		}

		err = commands.Mount(node, fsdir, nsdir)
		if err != nil {
207 208
			res.SetError(err, cmds.ErrNormal)
			return
209
		}
210 211
		fmt.Printf("IPFS mounted at: %s\n", fsdir)
		fmt.Printf("IPNS mounted at: %s\n", nsdir)
212
	}
213

214 215 216 217 218
	var rootRedirect corehttp.ServeOption
	if len(cfg.Gateway.RootRedirect) > 0 {
		rootRedirect = corehttp.RedirectOption("", cfg.Gateway.RootRedirect)
	}

219 220 221 222 223 224 225 226 227
	writable, writableOptionFound, err := req.Option(writableKwd).Bool()
	if err != nil {
		res.SetError(err, cmds.ErrNormal)
		return
	}
	if !writableOptionFound {
		writable = cfg.Gateway.Writable
	}

228
	if gatewayMaddr != nil {
229
		go func() {
230
			var opts = []corehttp.ServeOption{
Jeromy's avatar
Jeromy committed
231
				corehttp.VersionOption(),
232 233 234
				corehttp.IPNSHostnameOption(),
				corehttp.GatewayOption(writable),
			}
235 236 237
			if rootRedirect != nil {
				opts = append(opts, rootRedirect)
			}
238
			if writable {
239 240 241
				fmt.Printf("Gateway (writable) server listening on %s\n", gatewayMaddr)
			} else {
				fmt.Printf("Gateway (readonly) server listening on %s\n", gatewayMaddr)
242
			}
243
			err := corehttp.ListenAndServe(node, gatewayMaddr.String(), opts...)
244 245 246 247
			if err != nil {
				log.Error(err)
			}
		}()
248 249
	}

250
	gateway := corehttp.NewGateway(corehttp.GatewayConfig{
251 252 253 254 255 256 257 258 259 260 261 262
		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
			},
		},
263
	})
264 265 266
	var opts = []corehttp.ServeOption{
		corehttp.CommandsOption(*req.Context()),
		corehttp.WebUIOption,
267
		gateway.ServeOption(),
268
		corehttp.VersionOption(),
269
	}
270 271

	// our global interrupt handler can now try to stop the daemon
272
	close(req.Context().InitDone)
273

274 275 276
	if rootRedirect != nil {
		opts = append(opts, rootRedirect)
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
277
	fmt.Printf("API server listening on %s\n", apiMaddr)
278
	if err := corehttp.ListenAndServe(node, apiMaddr.String(), opts...); err != nil {
279 280 281
		res.SetError(err, cmds.ErrNormal)
		return
	}
282
}