daemon.go 6.38 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
	fsrepo "github.com/jbenet/go-ipfs/repo/fsrepo"
14
	util "github.com/jbenet/go-ipfs/util"
Brian Tiger Chow's avatar
Brian Tiger Chow committed
15
	"github.com/jbenet/go-ipfs/util/debugerror"
16 17 18
)

const (
19
	initOptionKwd = "init"
20
	mountKwd      = "mount"
21
	writableKwd   = "writable"
22 23
	ipfsMountKwd  = "mount-ipfs"
	ipnsMountKwd  = "mount-ipns"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
24 25
	// apiAddrKwd    = "address-api"
	// swarmAddrKwd  = "address-swarm"
26 27
)

28
var daemonCmd = &cmds.Command{
29
	Helptext: cmds.HelpText{
Matt Bell's avatar
Matt Bell committed
30 31 32 33 34 35 36 37
		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.
`,
38 39
	},

40
	Options: []cmds.Option{
41
		cmds.BoolOption(initOptionKwd, "Initialize IPFS with default settings if not already initialized"),
42
		cmds.BoolOption(mountKwd, "Mounts IPFS to the filesystem"),
43
		cmds.BoolOption(writableKwd, "Enable writing objects (with POST, PUT and DELETE)"),
44 45
		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
46 47 48 49

		// 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)"),
50
	},
51 52 53 54
	Subcommands: map[string]*cmds.Command{},
	Run:         daemonFunc,
}

55
func daemonFunc(req cmds.Request, res cmds.Response) {
56 57
	// let the user know we're going.
	fmt.Printf("Initializing daemon...\n")
58

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

67
	if initialize {
68 69 70 71 72 73

		// 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) {
74
			err := initWithDefaults(os.Stdout, req.Context().ConfigRoot)
75
			if err != nil {
76 77
				res.SetError(debugerror.Wrap(err), cmds.ErrNormal)
				return
78 79 80 81
			}
		}
	}

82 83 84 85 86 87 88 89
	// 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 {
90 91
		res.SetError(err, cmds.ErrNormal)
		return
92 93
	}

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

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
102 103
	// OK!!! Now we're ready to construct the node.
	// make sure we construct an online node.
104
	node, err := core.NewIPFSNode(ctx.Context, core.Online(repo))
105
	if err != nil {
106 107
		res.SetError(err, cmds.ErrNormal)
		return
108
	}
109 110 111 112
	defer node.Close()
	req.Context().ConstructNode = func() (*core.IpfsNode, error) {
		return node, nil
	}
113

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
114 115
	// verify api address is valid multiaddr
	apiMaddr, err := ma.NewMultiaddr(cfg.Addresses.API)
116
	if err != nil {
117 118
		res.SetError(err, cmds.ErrNormal)
		return
119 120
	}

121 122 123 124 125 126 127 128
	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)
		}
129 130
	}

131 132
	// mount if the user provided the --mount flag
	mount, _, err := req.Option(mountKwd).Bool()
133
	if err != nil {
134 135
		res.SetError(err, cmds.ErrNormal)
		return
136
	}
137 138 139
	if mount {
		fsdir, found, err := req.Option(ipfsMountKwd).String()
		if err != nil {
140 141
			res.SetError(err, cmds.ErrNormal)
			return
142 143 144 145 146 147 148
		}
		if !found {
			fsdir = cfg.Mounts.IPFS
		}

		nsdir, found, err := req.Option(ipnsMountKwd).String()
		if err != nil {
149 150
			res.SetError(err, cmds.ErrNormal)
			return
151 152 153 154 155 156 157
		}
		if !found {
			nsdir = cfg.Mounts.IPNS
		}

		err = commands.Mount(node, fsdir, nsdir)
		if err != nil {
158 159
			res.SetError(err, cmds.ErrNormal)
			return
160
		}
161 162
		fmt.Printf("IPFS mounted at: %s\n", fsdir)
		fmt.Printf("IPNS mounted at: %s\n", nsdir)
163
	}
164

165 166 167 168 169
	var rootRedirect corehttp.ServeOption
	if len(cfg.Gateway.RootRedirect) > 0 {
		rootRedirect = corehttp.RedirectOption("", cfg.Gateway.RootRedirect)
	}

170 171 172 173 174 175 176 177 178
	writable, writableOptionFound, err := req.Option(writableKwd).Bool()
	if err != nil {
		res.SetError(err, cmds.ErrNormal)
		return
	}
	if !writableOptionFound {
		writable = cfg.Gateway.Writable
	}

179
	if gatewayMaddr != nil {
180
		go func() {
181
			var opts = []corehttp.ServeOption{corehttp.GatewayOption(writable)}
182 183 184
			if rootRedirect != nil {
				opts = append(opts, rootRedirect)
			}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
185
			fmt.Printf("Gateway server listening on %s\n", gatewayMaddr)
186 187 188
			if writable {
				fmt.Printf("Gateway server is writable\n")
			}
189
			err := corehttp.ListenAndServe(node, gatewayMaddr.String(), opts...)
190 191 192 193
			if err != nil {
				log.Error(err)
			}
		}()
194 195
	}

196 197
	blocklist := &corehttp.BlockList{}
	blocklist.SetDecider(func(s string) bool {
198 199 200 201 202 203 204
		// for now, only allow paths in the WebUI path
		for _, webuipath := range corehttp.WebUIPaths {
			if strings.HasPrefix(s, webuipath) {
				return true
			}
		}
		return false
205
	})
206
	gateway := corehttp.NewGateway(corehttp.GatewayConfig{
207 208
		Writable:  true,
		BlockList: blocklist,
209
	})
210 211 212
	var opts = []corehttp.ServeOption{
		corehttp.CommandsOption(*req.Context()),
		corehttp.WebUIOption,
213
		gateway.ServeOption(),
214
	}
215 216 217
	if rootRedirect != nil {
		opts = append(opts, rootRedirect)
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
218
	fmt.Printf("API server listening on %s\n", apiMaddr)
219
	if err := corehttp.ListenAndServe(node, apiMaddr.String(), opts...); err != nil {
220 221 222
		res.SetError(err, cmds.ErrNormal)
		return
	}
223
}