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

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

8 9 10 11 12 13 14 15 16
	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"
17 18 19
)

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

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

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

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

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

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

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

91
	if initialize {
92 93 94 95 96 97

		// 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) {
98
			err := initWithDefaults(os.Stdout, req.Context().ConfigRoot)
99
			if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
100
				res.SetError(err, cmds.ErrNormal)
101
				return
102 103 104 105
			}
		}
	}

106 107 108 109 110 111 112 113
	// 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 {
114 115
		res.SetError(err, cmds.ErrNormal)
		return
116 117
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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