daemon.go 7.28 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 41
		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.
`,
42 43
	},

44
	Options: []cmds.Option{
45
		cmds.BoolOption(initOptionKwd, "Initialize IPFS with default settings if not already initialized"),
46
		cmds.StringOption(routingOptionKwd, "Overrides the routing option (dht, supernode)"),
47
		cmds.BoolOption(mountKwd, "Mounts IPFS to the filesystem"),
48
		cmds.BoolOption(writableKwd, "Enable writing objects (with POST, PUT and DELETE)"),
49 50
		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
51 52 53 54

		// 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)"),
55
	},
56 57 58 59
	Subcommands: map[string]*cmds.Command{},
	Run:         daemonFunc,
}

60
func daemonFunc(req cmds.Request, res cmds.Response) {
61 62
	// let the user know we're going.
	fmt.Printf("Initializing daemon...\n")
63

64 65
	// first, whether user has provided the initialization flag. we may be
	// running in an uninitialized state.
66
	initialize, _, err := req.Option(initOptionKwd).Bool()
67
	if err != nil {
68 69
		res.SetError(err, cmds.ErrNormal)
		return
70
	}
71

72
	if initialize {
73 74 75 76 77 78

		// 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) {
79
			err := initWithDefaults(os.Stdout, req.Context().ConfigRoot)
80
			if err != nil {
81 82
				res.SetError(debugerror.Wrap(err), cmds.ErrNormal)
				return
83 84 85 86
			}
		}
	}

87 88 89 90 91 92 93 94
	// 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 {
95 96
		res.SetError(err, cmds.ErrNormal)
		return
97 98
	}

99
	// acquire the repo lock _before_ constructing a node. we need to make
100
	// sure we are permitted to access the resources (datastore, etc.)
101 102
	repo := fsrepo.At(req.Context().ConfigRoot)
	if err := repo.Open(); err != nil {
103 104
		res.SetError(debugerror.Errorf("Couldn't obtain lock. Is another daemon already running?"), cmds.ErrNormal)
		return
105 106
	}

Jeromy's avatar
Jeromy committed
107 108 109 110
	// Start assembling corebuilder
	nb := core.NewNodeBuilder().Online()
	nb.SetRepo(repo)

111
	routingOption, _, err := req.Option(routingOptionKwd).String()
112 113 114 115
	if err != nil {
		res.SetError(err, cmds.ErrNormal)
		return
	}
116
	if routingOption == routingOptionSupernodeKwd {
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
		servers, err := repo.Config().GCR.ServerIPFSAddrs()
		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
133
	node, err := nb.Build(ctx.Context)
134
	if err != nil {
135 136
		res.SetError(err, cmds.ErrNormal)
		return
137
	}
138 139 140 141
	defer node.Close()
	req.Context().ConstructNode = func() (*core.IpfsNode, error) {
		return node, nil
	}
142

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
143 144
	// verify api address is valid multiaddr
	apiMaddr, err := ma.NewMultiaddr(cfg.Addresses.API)
145
	if err != nil {
146 147
		res.SetError(err, cmds.ErrNormal)
		return
148 149
	}

150 151 152 153 154 155 156 157
	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)
		}
158 159
	}

160 161
	// mount if the user provided the --mount flag
	mount, _, err := req.Option(mountKwd).Bool()
162
	if err != nil {
163 164
		res.SetError(err, cmds.ErrNormal)
		return
165
	}
166 167 168
	if mount {
		fsdir, found, err := req.Option(ipfsMountKwd).String()
		if err != nil {
169 170
			res.SetError(err, cmds.ErrNormal)
			return
171 172 173 174 175 176 177
		}
		if !found {
			fsdir = cfg.Mounts.IPFS
		}

		nsdir, found, err := req.Option(ipnsMountKwd).String()
		if err != nil {
178 179
			res.SetError(err, cmds.ErrNormal)
			return
180 181 182 183 184 185 186
		}
		if !found {
			nsdir = cfg.Mounts.IPNS
		}

		err = commands.Mount(node, fsdir, nsdir)
		if err != nil {
187 188
			res.SetError(err, cmds.ErrNormal)
			return
189
		}
190 191
		fmt.Printf("IPFS mounted at: %s\n", fsdir)
		fmt.Printf("IPNS mounted at: %s\n", nsdir)
192
	}
193

194 195 196 197 198
	var rootRedirect corehttp.ServeOption
	if len(cfg.Gateway.RootRedirect) > 0 {
		rootRedirect = corehttp.RedirectOption("", cfg.Gateway.RootRedirect)
	}

199 200 201 202 203 204 205 206 207
	writable, writableOptionFound, err := req.Option(writableKwd).Bool()
	if err != nil {
		res.SetError(err, cmds.ErrNormal)
		return
	}
	if !writableOptionFound {
		writable = cfg.Gateway.Writable
	}

208
	if gatewayMaddr != nil {
209
		go func() {
210 211 212 213
			var opts = []corehttp.ServeOption{
				corehttp.IPNSHostnameOption(),
				corehttp.GatewayOption(writable),
			}
214 215 216
			if rootRedirect != nil {
				opts = append(opts, rootRedirect)
			}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
217
			fmt.Printf("Gateway server listening on %s\n", gatewayMaddr)
218 219 220
			if writable {
				fmt.Printf("Gateway server is writable\n")
			}
221
			err := corehttp.ListenAndServe(node, gatewayMaddr.String(), opts...)
222 223 224 225
			if err != nil {
				log.Error(err)
			}
		}()
226 227
	}

228
	gateway := corehttp.NewGateway(corehttp.GatewayConfig{
229 230 231 232 233 234 235 236 237 238 239 240
		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
			},
		},
241
	})
242 243 244
	var opts = []corehttp.ServeOption{
		corehttp.CommandsOption(*req.Context()),
		corehttp.WebUIOption,
245
		gateway.ServeOption(),
246
	}
247 248 249
	if rootRedirect != nil {
		opts = append(opts, rootRedirect)
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
250
	fmt.Printf("API server listening on %s\n", apiMaddr)
251
	if err := corehttp.ListenAndServe(node, apiMaddr.String(), opts...); err != nil {
252 253 254
		res.SetError(err, cmds.ErrNormal)
		return
	}
255
}