daemon.go 8.24 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 85 86 87 88 89 90 91
	ctx := req.Context()

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

92 93
	// first, whether user has provided the initialization flag. we may be
	// running in an uninitialized state.
94
	initialize, _, err := req.Option(initOptionKwd).Bool()
95
	if err != nil {
96 97
		res.SetError(err, cmds.ErrNormal)
		return
98
	}
99

100
	if initialize {
101 102 103 104 105 106

		// 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) {
107
			err := initWithDefaults(os.Stdout, req.Context().ConfigRoot)
108
			if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
109
				res.SetError(err, cmds.ErrNormal)
110
				return
111 112 113 114
			}
		}
	}

115 116 117
	// 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)
118
	if err != nil {
119 120
		res.SetError(err, cmds.ErrNormal)
		return
121 122
	}

123
	cfg, err := ctx.GetConfig()
124
	if err != nil {
125
		res.SetError(err, cmds.ErrNormal)
126
		return
127 128
	}

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

133
	routingOption, _, err := req.Option(routingOptionKwd).String()
134 135 136 137
	if err != nil {
		res.SetError(err, cmds.ErrNormal)
		return
	}
138
	if routingOption == routingOptionSupernodeKwd {
139
		servers, err := repo.Config().SupernodeRouting.ServerIPFSAddrs()
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
		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
155
	node, err := nb.Build(ctx.Context)
156
	if err != nil {
157
		log.Error("error from node construction: ", err)
158 159
		res.SetError(err, cmds.ErrNormal)
		return
160
	}
161 162 163 164 165 166 167 168 169 170 171 172 173

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

174 175 176
	req.Context().ConstructNode = func() (*core.IpfsNode, error) {
		return node, nil
	}
177

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
178 179
	// verify api address is valid multiaddr
	apiMaddr, err := ma.NewMultiaddr(cfg.Addresses.API)
180
	if err != nil {
181 182
		res.SetError(err, cmds.ErrNormal)
		return
183 184
	}

185 186 187 188 189 190 191 192
	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)
		}
193 194
	}

195 196
	// mount if the user provided the --mount flag
	mount, _, err := req.Option(mountKwd).Bool()
197
	if err != nil {
198 199
		res.SetError(err, cmds.ErrNormal)
		return
200
	}
201 202 203
	if mount {
		fsdir, found, err := req.Option(ipfsMountKwd).String()
		if err != nil {
204 205
			res.SetError(err, cmds.ErrNormal)
			return
206 207 208 209 210 211 212
		}
		if !found {
			fsdir = cfg.Mounts.IPFS
		}

		nsdir, found, err := req.Option(ipnsMountKwd).String()
		if err != nil {
213 214
			res.SetError(err, cmds.ErrNormal)
			return
215 216 217 218 219 220 221
		}
		if !found {
			nsdir = cfg.Mounts.IPNS
		}

		err = commands.Mount(node, fsdir, nsdir)
		if err != nil {
222 223
			res.SetError(err, cmds.ErrNormal)
			return
224
		}
225 226
		fmt.Printf("IPFS mounted at: %s\n", fsdir)
		fmt.Printf("IPNS mounted at: %s\n", nsdir)
227
	}
228

229 230 231 232 233
	var rootRedirect corehttp.ServeOption
	if len(cfg.Gateway.RootRedirect) > 0 {
		rootRedirect = corehttp.RedirectOption("", cfg.Gateway.RootRedirect)
	}

234 235 236 237 238 239 240 241 242
	writable, writableOptionFound, err := req.Option(writableKwd).Bool()
	if err != nil {
		res.SetError(err, cmds.ErrNormal)
		return
	}
	if !writableOptionFound {
		writable = cfg.Gateway.Writable
	}

243
	if gatewayMaddr != nil {
244
		go func() {
245
			var opts = []corehttp.ServeOption{
Jeromy's avatar
Jeromy committed
246
				corehttp.VersionOption(),
247 248 249
				corehttp.IPNSHostnameOption(),
				corehttp.GatewayOption(writable),
			}
250 251 252
			if rootRedirect != nil {
				opts = append(opts, rootRedirect)
			}
253
			if writable {
254 255 256
				fmt.Printf("Gateway (writable) server listening on %s\n", gatewayMaddr)
			} else {
				fmt.Printf("Gateway (readonly) server listening on %s\n", gatewayMaddr)
257
			}
258
			err := corehttp.ListenAndServe(node, gatewayMaddr.String(), opts...)
259 260 261 262
			if err != nil {
				log.Error(err)
			}
		}()
263 264
	}

265
	gateway := corehttp.NewGateway(corehttp.GatewayConfig{
266 267 268 269 270 271 272 273 274 275 276 277
		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
			},
		},
278
	})
279 280 281
	var opts = []corehttp.ServeOption{
		corehttp.CommandsOption(*req.Context()),
		corehttp.WebUIOption,
282
		gateway.ServeOption(),
283
		corehttp.VersionOption(),
284
	}
285

286 287 288
	if rootRedirect != nil {
		opts = append(opts, rootRedirect)
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
289
	fmt.Printf("API server listening on %s\n", apiMaddr)
290
	if err := corehttp.ListenAndServe(node, apiMaddr.String(), opts...); err != nil {
291 292 293
		res.SetError(err, cmds.ErrNormal)
		return
	}
294
}