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

import (
4
	"fmt"
5
	"net/http"
6
	"os"
7

8
	manners "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/braintree/manners"
9
	ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
10
	manet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"
11 12
	cmds "github.com/jbenet/go-ipfs/commands"
	cmdsHttp "github.com/jbenet/go-ipfs/commands/http"
Brian Tiger Chow's avatar
Brian Tiger Chow committed
13
	core "github.com/jbenet/go-ipfs/core"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
14 15
	commands "github.com/jbenet/go-ipfs/core/commands"
	daemon "github.com/jbenet/go-ipfs/core/daemon"
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
	initOptionKwd = "init"
22 23 24
	mountKwd      = "mount"
	ipfsMountKwd  = "mount-ipfs"
	ipnsMountKwd  = "mount-ipns"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
25 26
	// apiAddrKwd    = "address-api"
	// swarmAddrKwd  = "address-swarm"
27

28
	originEnvKey = "API_ORIGIN"
29 30

	webuiPath = "/ipfs/QmTWvqK9dYvqjAMAcCeUun8b45Fwu7wPhEN9B9TsGbkXfJ"
31 32
)

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

45
	Options: []cmds.Option{
46
		cmds.BoolOption(initOptionKwd, "Initialize IPFS with default settings if not already initialized"),
47 48 49
		cmds.BoolOption(mountKwd, "Mounts IPFS to the filesystem"),
		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
50 51 52 53

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

59
func daemonFunc(req cmds.Request) (interface{}, error) {
60

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

		// 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) {
			err := initWithDefaults(req.Context().ConfigRoot)
			if err != nil {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
76
				return nil, debugerror.Wrap(err)
77 78 79 80
			}
		}
	}

81 82 83 84 85 86 87 88 89 90 91 92 93
	// 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 {
		return nil, err
	}

	// acquire the daemon lock _before_ constructing a node. we need to make
	// sure we are permitted to access the resources (datastore, etc.)
94
	lock, err := daemon.Lock(req.Context().ConfigRoot)
95
	if err != nil {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
96
		return nil, debugerror.Errorf("Couldn't obtain lock. Is another daemon already running?")
97
	}
98
	defer lock.Close()
99

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
100 101
	// OK!!! Now we're ready to construct the node.
	// make sure we construct an online node.
102 103 104 105 106 107
	ctx.Online = true
	node, err := ctx.GetNode()
	if err != nil {
		return nil, err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
108 109
	// verify api address is valid multiaddr
	apiMaddr, err := ma.NewMultiaddr(cfg.Addresses.API)
110
	if err != nil {
111
		return nil, err
112 113
	}

114 115 116 117 118 119 120 121
	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)
		}
122 123
	}

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

		nsdir, found, err := req.Option(ipnsMountKwd).String()
		if err != nil {
			return nil, err
		}
		if !found {
			nsdir = cfg.Mounts.IPNS
		}

		err = commands.Mount(node, fsdir, nsdir)
		if err != nil {
			return nil, err
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
150 151
		fmt.Printf("IPFS mounted at: %s\n", fsdir)
		fmt.Printf("IPNS mounted at: %s\n", nsdir)
152
	}
153

154
	if gatewayMaddr != nil {
155 156 157 158 159 160
		go func() {
			err := listenAndServeGateway(node, gatewayMaddr)
			if err != nil {
				log.Error(err)
			}
		}()
161 162
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
163 164 165 166
	return nil, listenAndServeAPI(node, req, apiMaddr)
}

func listenAndServeAPI(node *core.IpfsNode, req cmds.Request, addr ma.Multiaddr) error {
167 168 169
	origin := os.Getenv(originEnvKey)
	cmdHandler := cmdsHttp.NewHandler(*req.Context(), commands.Root, origin)
	gateway, err := NewGatewayHandler(node)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
170 171 172 173
	if err != nil {
		return err
	}

174 175
	mux := http.NewServeMux()
	mux.Handle(cmdsHttp.ApiPath+"/", cmdHandler)
176 177 178 179
	mux.Handle("/ipfs/", gateway)
	mux.Handle("/webui/", &redirectHandler{webuiPath})
	return listenAndServe("API", node, addr, mux)
}
180

181 182
// the gateway also listens on its own address:port in addition to the API listener
func listenAndServeGateway(node *core.IpfsNode, addr ma.Multiaddr) error {
183
	gateway, err := NewGatewayHandler(node)
184 185 186
	if err != nil {
		return err
	}
187

188
	mux := http.NewServeMux()
189
	mux.Handle("/ipfs/", gateway)
190 191 192 193 194 195 196 197 198 199
	return listenAndServe("gateway", node, addr, mux)
}

func listenAndServe(name string, node *core.IpfsNode, addr ma.Multiaddr, mux *http.ServeMux) error {
	_, host, err := manet.DialArgs(addr)
	if err != nil {
		return err
	}

	server := manners.NewServer()
200

201 202 203
	// if the server exits beforehand
	var serverError error
	serverExited := make(chan struct{})
204

205
	go func() {
206
		fmt.Printf("%s server listening on %s\n", name, addr)
207 208
		serverError = server.ListenAndServe(host, mux)
		close(serverExited)
209 210
	}()

211 212 213 214 215 216
	// wait for server to exit.
	select {
	case <-serverExited:

	// if node being closed before server exits, close server
	case <-node.Closing():
217
		log.Infof("server at %s terminating...", addr)
218
		server.Shutdown <- true
219
		<-serverExited // now, DO wait until server exit
220
	}
221

222
	log.Infof("server at %s terminated", addr)
223
	return serverError
224
}
225

226 227 228 229 230 231 232
type redirectHandler struct {
	path string
}

func (i *redirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, i.path, 302)
}