daemon.go 6.28 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
	originEnvKey = "API_ORIGIN"
28 29
)

30
var daemonCmd = &cmds.Command{
31
	Helptext: cmds.HelpText{
Matt Bell's avatar
Matt Bell committed
32 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.
`,
40 41
	},

42
	Options: []cmds.Option{
43
		cmds.BoolOption(initOptionKwd, "Initialize IPFS with default settings if not already initialized"),
44 45 46
		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
47 48 49 50

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

56
func daemonFunc(req cmds.Request) (interface{}, error) {
57

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

		// 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
73
				return nil, debugerror.Wrap(err)
74 75 76 77
			}
		}
	}

78 79 80 81 82 83 84 85 86 87 88 89 90
	// 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.)
91
	lock, err := daemon.Lock(req.Context().ConfigRoot)
92
	if err != nil {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
93
		return nil, debugerror.Errorf("Couldn't obtain lock. Is another daemon already running?")
94
	}
95
	defer lock.Close()
96

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

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

111 112 113 114 115 116 117
	// 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 {
		fmt.Println("Invalid gateway address, not running gateway")
	}

118 119
	// mount if the user provided the --mount flag
	mount, _, err := req.Option(mountKwd).Bool()
120
	if err != nil {
121
		return nil, err
122
	}
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
	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
144 145
		fmt.Printf("IPFS mounted at: %s\n", fsdir)
		fmt.Printf("IPNS mounted at: %s\n", nsdir)
146
	}
147

148 149 150 151
	if gatewayMaddr != nil {
		listenAndServeGateway(node, gatewayMaddr)
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
152 153 154 155 156 157 158 159 160
	return nil, listenAndServeAPI(node, req, apiMaddr)
}

func listenAndServeAPI(node *core.IpfsNode, req cmds.Request, addr ma.Multiaddr) error {
	_, host, err := manet.DialArgs(addr)
	if err != nil {
		return err
	}

161 162
	origin := os.Getenv(originEnvKey)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
163
	server := manners.NewServer()
164
	mux := http.NewServeMux()
165
	cmdHandler := cmdsHttp.NewHandler(*req.Context(), commands.Root, origin)
166
	mux.Handle(cmdsHttp.ApiPath+"/", cmdHandler)
167

168 169 170
	ifpsHandler := &ipfsHandler{node: node}
	ifpsHandler.LoadTemplate()

171 172
	mux.Handle("/ipfs/", ifpsHandler)

173 174 175
	// if the server exits beforehand
	var serverError error
	serverExited := make(chan struct{})
176

177
	go func() {
178
		fmt.Printf("API server listening on %s\n", addr)
179 180
		serverError = server.ListenAndServe(host, mux)
		close(serverExited)
181 182
	}()

183 184 185 186 187 188 189 190
	// wait for server to exit.
	select {
	case <-serverExited:

	// if node being closed before server exits, close server
	case <-node.Closing():
		log.Infof("daemon at %s terminating...", addr)
		server.Shutdown <- true
191
		<-serverExited // now, DO wait until server exit
192
	}
193

194 195
	log.Infof("daemon at %s terminated", addr)
	return serverError
196
}
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234

func listenAndServeGateway(node *core.IpfsNode, addr ma.Multiaddr) error {
	_, host, err := manet.DialArgs(addr)
	if err != nil {
		return err
	}

	server := manners.NewServer()
	mux := http.NewServeMux()
	ifpsHandler := &ipfsHandler{node}
	mux.Handle("/ipfs/", ifpsHandler)

	done := make(chan struct{}, 1)
	defer func() {
		done <- struct{}{}
	}()

	// go wait until the node dies
	go func() {
		select {
		case <-node.Closed():
		case <-done:
			return
		}

		log.Infof("terminating gateway at %s...", addr)
		server.Shutdown <- true
	}()

	fmt.Printf("Gateway listening on %s\n", addr)
	go func() {
		if err := server.ListenAndServe(host, mux); err != nil {
			log.Error(err)
		}
	}()

	return nil
}