github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/cmd/ipfs/daemon.go (about) 1 package main 2 3 import ( 4 _ "expvar" 5 "fmt" 6 "net" 7 "net/http" 8 _ "net/http/pprof" 9 "os" 10 "sort" 11 "strings" 12 "sync" 13 14 _ "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/codahale/metrics/runtime" 15 ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" 16 "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net" 17 18 cmds "github.com/ipfs/go-ipfs/commands" 19 "github.com/ipfs/go-ipfs/core" 20 commands "github.com/ipfs/go-ipfs/core/commands" 21 corehttp "github.com/ipfs/go-ipfs/core/corehttp" 22 "github.com/ipfs/go-ipfs/core/corerouting" 23 conn "github.com/ipfs/go-ipfs/p2p/net/conn" 24 peer "github.com/ipfs/go-ipfs/p2p/peer" 25 fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo" 26 util "github.com/ipfs/go-ipfs/util" 27 ) 28 29 const ( 30 initOptionKwd = "init" 31 routingOptionKwd = "routing" 32 routingOptionSupernodeKwd = "supernode" 33 mountKwd = "mount" 34 writableKwd = "writable" 35 ipfsMountKwd = "mount-ipfs" 36 ipnsMountKwd = "mount-ipns" 37 unrestrictedApiAccessKwd = "unrestricted-api" 38 unencryptTransportKwd = "disable-transport-encryption" 39 // apiAddrKwd = "address-api" 40 // swarmAddrKwd = "address-swarm" 41 ) 42 43 var daemonCmd = &cmds.Command{ 44 Helptext: cmds.HelpText{ 45 Tagline: "Run a network-connected IPFS node", 46 ShortDescription: ` 47 'ipfs daemon' runs a persistent IPFS daemon that can serve commands 48 over the network. Most applications that use IPFS will do so by 49 communicating with a daemon over the HTTP API. While the daemon is 50 running, calls to 'ipfs' commands will be sent over the network to 51 the daemon. 52 53 The daemon will start listening on ports on the network, which are 54 documented in (and can be modified through) 'ipfs config Addresses'. 55 For example, to change the 'Gateway' port: 56 57 ipfs config Addresses.Gateway /ip4/127.0.0.1/tcp/8082 58 59 The API address can be changed the same way: 60 61 ipfs config Addresses.API /ip4/127.0.0.1/tcp/5002 62 63 Make sure to restart the daemon after changing addresses. 64 65 By default, the gateway is only accessible locally. To expose it to 66 other computers in the network, use 0.0.0.0 as the ip address: 67 68 ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8080 69 70 Be careful if you expose the API. It is a security risk, as anyone could 71 control your node remotely. If you need to control the node remotely, 72 make sure to protect the port as you would other services or database 73 (firewall, authenticated proxy, etc). 74 75 HTTP Headers 76 77 IPFS supports passing arbitrary headers to the API and Gateway. You can 78 do this by setting headers on the API.HTTPHeaders and Gateway.HTTPHeaders 79 keys: 80 81 ipfs config --json API.HTTPHeaders.X-Special-Header '["so special :)"]' 82 ipfs config --json Gateway.HTTPHeaders.X-Special-Header '["so special :)"]' 83 84 Note that the value of the keys is an _array_ of strings. This is because 85 headers can have more than one value, and it is convenient to pass through 86 to other libraries. 87 88 CORS Headers (for API) 89 90 You can setup CORS headers the same way: 91 92 ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["*"]' 93 ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '["PUT", "GET", "POST"]' 94 ipfs config --json API.HTTPHeaders.Access-Control-Allow-Credentials '["true"]' 95 96 97 DEPRECATION NOTICE 98 99 Previously, IPFS used an environment variable as seen below: 100 101 export API_ORIGIN="http://localhost:8888/" 102 103 This is deprecated. It is still honored in this version, but will be removed in a 104 future version, along with this notice. Please move to setting the HTTP Headers. 105 `, 106 }, 107 108 Options: []cmds.Option{ 109 cmds.BoolOption(initOptionKwd, "Initialize IPFS with default settings if not already initialized"), 110 cmds.StringOption(routingOptionKwd, "Overrides the routing option (dht, supernode)"), 111 cmds.BoolOption(mountKwd, "Mounts IPFS to the filesystem"), 112 cmds.BoolOption(writableKwd, "Enable writing objects (with POST, PUT and DELETE)"), 113 cmds.StringOption(ipfsMountKwd, "Path to the mountpoint for IPFS (if using --mount)"), 114 cmds.StringOption(ipnsMountKwd, "Path to the mountpoint for IPNS (if using --mount)"), 115 cmds.BoolOption(unrestrictedApiAccessKwd, "Allow API access to unlisted hashes"), 116 cmds.BoolOption(unencryptTransportKwd, "Disable transport encryption (for debugging protocols)"), 117 118 // TODO: add way to override addresses. tricky part: updating the config if also --init. 119 // cmds.StringOption(apiAddrKwd, "Address for the daemon rpc API (overrides config)"), 120 // cmds.StringOption(swarmAddrKwd, "Address for the swarm socket (overrides config)"), 121 }, 122 Subcommands: map[string]*cmds.Command{}, 123 Run: daemonFunc, 124 } 125 126 // defaultMux tells mux to serve path using the default muxer. This is 127 // mostly useful to hook up things that register in the default muxer, 128 // and don't provide a convenient http.Handler entry point, such as 129 // expvar and http/pprof. 130 func defaultMux(path string) corehttp.ServeOption { 131 return func(node *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { 132 mux.Handle(path, http.DefaultServeMux) 133 return mux, nil 134 } 135 } 136 137 func daemonFunc(req cmds.Request, res cmds.Response) { 138 // let the user know we're going. 139 fmt.Printf("Initializing daemon...\n") 140 141 ctx := req.InvocContext() 142 143 go func() { 144 select { 145 case <-req.Context().Done(): 146 fmt.Println("Received interrupt signal, shutting down...") 147 } 148 }() 149 150 // check transport encryption flag. 151 unencrypted, _, _ := req.Option(unencryptTransportKwd).Bool() 152 if unencrypted { 153 log.Warningf(`Running with --%s: All connections are UNENCRYPTED. 154 You will not be able to connect to regular encrypted networks.`, unencryptTransportKwd) 155 conn.EncryptConnections = false 156 } 157 158 // first, whether user has provided the initialization flag. we may be 159 // running in an uninitialized state. 160 initialize, _, err := req.Option(initOptionKwd).Bool() 161 if err != nil { 162 res.SetError(err, cmds.ErrNormal) 163 return 164 } 165 166 if initialize { 167 168 // now, FileExists is our best method of detecting whether IPFS is 169 // configured. Consider moving this into a config helper method 170 // `IsInitialized` where the quality of the signal can be improved over 171 // time, and many call-sites can benefit. 172 if !util.FileExists(req.InvocContext().ConfigRoot) { 173 err := initWithDefaults(os.Stdout, req.InvocContext().ConfigRoot) 174 if err != nil { 175 res.SetError(err, cmds.ErrNormal) 176 return 177 } 178 } 179 } 180 181 // acquire the repo lock _before_ constructing a node. we need to make 182 // sure we are permitted to access the resources (datastore, etc.) 183 repo, err := fsrepo.Open(req.InvocContext().ConfigRoot) 184 if err != nil { 185 res.SetError(err, cmds.ErrNormal) 186 return 187 } 188 189 cfg, err := ctx.GetConfig() 190 if err != nil { 191 res.SetError(err, cmds.ErrNormal) 192 return 193 } 194 195 // Start assembling node config 196 ncfg := &core.BuildCfg{ 197 Online: true, 198 Repo: repo, 199 } 200 201 routingOption, _, err := req.Option(routingOptionKwd).String() 202 if err != nil { 203 res.SetError(err, cmds.ErrNormal) 204 return 205 } 206 if routingOption == routingOptionSupernodeKwd { 207 servers, err := cfg.SupernodeRouting.ServerIPFSAddrs() 208 if err != nil { 209 res.SetError(err, cmds.ErrNormal) 210 repo.Close() // because ownership hasn't been transferred to the node 211 return 212 } 213 var infos []peer.PeerInfo 214 for _, addr := range servers { 215 infos = append(infos, peer.PeerInfo{ 216 ID: addr.ID(), 217 Addrs: []ma.Multiaddr{addr.Transport()}, 218 }) 219 } 220 221 ncfg.Routing = corerouting.SupernodeClient(infos...) 222 } 223 224 node, err := core.NewNode(req.Context(), ncfg) 225 if err != nil { 226 log.Error("error from node construction: ", err) 227 res.SetError(err, cmds.ErrNormal) 228 return 229 } 230 231 printSwarmAddrs(node) 232 233 defer func() { 234 // We wait for the node to close first, as the node has children 235 // that it will wait for before closing, such as the API server. 236 node.Close() 237 238 select { 239 case <-req.Context().Done(): 240 log.Info("Gracefully shut down daemon") 241 default: 242 } 243 }() 244 245 req.InvocContext().ConstructNode = func() (*core.IpfsNode, error) { 246 return node, nil 247 } 248 249 // construct api endpoint - every time 250 err, apiErrc := serveHTTPApi(req) 251 if err != nil { 252 res.SetError(err, cmds.ErrNormal) 253 return 254 } 255 256 // construct http gateway - if it is set in the config 257 var gwErrc <-chan error 258 if len(cfg.Addresses.Gateway) > 0 { 259 var err error 260 err, gwErrc = serveHTTPGateway(req) 261 if err != nil { 262 res.SetError(err, cmds.ErrNormal) 263 return 264 } 265 } 266 267 // construct fuse mountpoints - if the user provided the --mount flag 268 mount, _, err := req.Option(mountKwd).Bool() 269 if err != nil { 270 res.SetError(err, cmds.ErrNormal) 271 return 272 } 273 if mount { 274 if err := mountFuse(req); err != nil { 275 res.SetError(err, cmds.ErrNormal) 276 return 277 } 278 } 279 280 fmt.Printf("Daemon is ready\n") 281 // collect long-running errors and block for shutdown 282 // TODO(cryptix): our fuse currently doesnt follow this pattern for graceful shutdown 283 for err := range merge(apiErrc, gwErrc) { 284 if err != nil { 285 res.SetError(err, cmds.ErrNormal) 286 return 287 } 288 } 289 } 290 291 // serveHTTPApi collects options, creates listener, prints status message and starts serving requests 292 func serveHTTPApi(req cmds.Request) (error, <-chan error) { 293 cfg, err := req.InvocContext().GetConfig() 294 if err != nil { 295 return fmt.Errorf("serveHTTPApi: GetConfig() failed: %s", err), nil 296 } 297 298 apiAddr, _, err := req.Option(commands.ApiOption).String() 299 if err != nil { 300 return fmt.Errorf("serveHTTPApi: %s", err), nil 301 } 302 if apiAddr == "" { 303 apiAddr = cfg.Addresses.API 304 } 305 apiMaddr, err := ma.NewMultiaddr(apiAddr) 306 if err != nil { 307 return fmt.Errorf("serveHTTPApi: invalid API address: %q (err: %s)", apiAddr, err), nil 308 } 309 310 apiLis, err := manet.Listen(apiMaddr) 311 if err != nil { 312 return fmt.Errorf("serveHTTPApi: manet.Listen(%s) failed: %s", apiMaddr, err), nil 313 } 314 // we might have listened to /tcp/0 - lets see what we are listing on 315 apiMaddr = apiLis.Multiaddr() 316 fmt.Printf("API server listening on %s\n", apiMaddr) 317 318 unrestricted, _, err := req.Option(unrestrictedApiAccessKwd).Bool() 319 if err != nil { 320 return fmt.Errorf("serveHTTPApi: Option(%s) failed: %s", unrestrictedApiAccessKwd, err), nil 321 } 322 323 apiGw := corehttp.NewGateway(corehttp.GatewayConfig{ 324 Writable: true, 325 BlockList: &corehttp.BlockList{ 326 Decider: func(s string) bool { 327 if unrestricted { 328 return true 329 } 330 // for now, only allow paths in the WebUI path 331 for _, webuipath := range corehttp.WebUIPaths { 332 if strings.HasPrefix(s, webuipath) { 333 return true 334 } 335 } 336 return false 337 }, 338 }, 339 }) 340 var opts = []corehttp.ServeOption{ 341 corehttp.CommandsOption(*req.InvocContext()), 342 corehttp.WebUIOption, 343 apiGw.ServeOption(), 344 corehttp.VersionOption(), 345 defaultMux("/debug/vars"), 346 defaultMux("/debug/pprof/"), 347 corehttp.LogOption(), 348 corehttp.PrometheusOption("/debug/metrics/prometheus"), 349 } 350 351 if len(cfg.Gateway.RootRedirect) > 0 { 352 opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect)) 353 } 354 355 node, err := req.InvocContext().ConstructNode() 356 if err != nil { 357 return fmt.Errorf("serveHTTPApi: ConstructNode() failed: %s", err), nil 358 } 359 360 if err := node.Repo.SetAPIAddr(apiAddr); err != nil { 361 return fmt.Errorf("serveHTTPApi: SetAPIAddr() failed: %s", err), nil 362 } 363 364 errc := make(chan error) 365 go func() { 366 errc <- corehttp.Serve(node, apiLis.NetListener(), opts...) 367 close(errc) 368 }() 369 return nil, errc 370 } 371 372 // printSwarmAddrs prints the addresses of the host 373 func printSwarmAddrs(node *core.IpfsNode) { 374 var addrs []string 375 for _, addr := range node.PeerHost.Addrs() { 376 addrs = append(addrs, addr.String()) 377 } 378 sort.Sort(sort.StringSlice(addrs)) 379 380 for _, addr := range addrs { 381 fmt.Printf("Swarm listening on %s\n", addr) 382 } 383 } 384 385 // serveHTTPGateway collects options, creates listener, prints status message and starts serving requests 386 func serveHTTPGateway(req cmds.Request) (error, <-chan error) { 387 cfg, err := req.InvocContext().GetConfig() 388 if err != nil { 389 return fmt.Errorf("serveHTTPGateway: GetConfig() failed: %s", err), nil 390 } 391 392 gatewayMaddr, err := ma.NewMultiaddr(cfg.Addresses.Gateway) 393 if err != nil { 394 return fmt.Errorf("serveHTTPGateway: invalid gateway address: %q (err: %s)", cfg.Addresses.Gateway, err), nil 395 } 396 397 writable, writableOptionFound, err := req.Option(writableKwd).Bool() 398 if err != nil { 399 return fmt.Errorf("serveHTTPGateway: req.Option(%s) failed: %s", writableKwd, err), nil 400 } 401 if !writableOptionFound { 402 writable = cfg.Gateway.Writable 403 } 404 405 gwLis, err := manet.Listen(gatewayMaddr) 406 if err != nil { 407 return fmt.Errorf("serveHTTPGateway: manet.Listen(%s) failed: %s", gatewayMaddr, err), nil 408 } 409 // we might have listened to /tcp/0 - lets see what we are listing on 410 gatewayMaddr = gwLis.Multiaddr() 411 412 if writable { 413 fmt.Printf("Gateway (writable) server listening on %s\n", gatewayMaddr) 414 } else { 415 fmt.Printf("Gateway (readonly) server listening on %s\n", gatewayMaddr) 416 } 417 418 var opts = []corehttp.ServeOption{ 419 corehttp.CommandsROOption(*req.InvocContext()), 420 corehttp.VersionOption(), 421 corehttp.IPNSHostnameOption(), 422 corehttp.GatewayOption(writable), 423 } 424 425 if len(cfg.Gateway.RootRedirect) > 0 { 426 opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect)) 427 } 428 429 node, err := req.InvocContext().ConstructNode() 430 if err != nil { 431 return fmt.Errorf("serveHTTPGateway: ConstructNode() failed: %s", err), nil 432 } 433 434 errc := make(chan error) 435 go func() { 436 errc <- corehttp.Serve(node, gwLis.NetListener(), opts...) 437 close(errc) 438 }() 439 return nil, errc 440 } 441 442 //collects options and opens the fuse mountpoint 443 func mountFuse(req cmds.Request) error { 444 cfg, err := req.InvocContext().GetConfig() 445 if err != nil { 446 return fmt.Errorf("mountFuse: GetConfig() failed: %s", err) 447 } 448 449 fsdir, found, err := req.Option(ipfsMountKwd).String() 450 if err != nil { 451 return fmt.Errorf("mountFuse: req.Option(%s) failed: %s", ipfsMountKwd, err) 452 } 453 if !found { 454 fsdir = cfg.Mounts.IPFS 455 } 456 457 nsdir, found, err := req.Option(ipnsMountKwd).String() 458 if err != nil { 459 return fmt.Errorf("mountFuse: req.Option(%s) failed: %s", ipnsMountKwd, err) 460 } 461 if !found { 462 nsdir = cfg.Mounts.IPNS 463 } 464 465 node, err := req.InvocContext().ConstructNode() 466 if err != nil { 467 return fmt.Errorf("mountFuse: ConstructNode() failed: %s", err) 468 } 469 470 err = commands.Mount(node, fsdir, nsdir) 471 if err != nil { 472 return err 473 } 474 fmt.Printf("IPFS mounted at: %s\n", fsdir) 475 fmt.Printf("IPNS mounted at: %s\n", nsdir) 476 return nil 477 } 478 479 // merge does fan-in of multiple read-only error channels 480 // taken from http://blog.golang.org/pipelines 481 func merge(cs ...<-chan error) <-chan error { 482 var wg sync.WaitGroup 483 out := make(chan error) 484 485 // Start an output goroutine for each input channel in cs. output 486 // copies values from c to out until c is closed, then calls wg.Done. 487 output := func(c <-chan error) { 488 for n := range c { 489 out <- n 490 } 491 wg.Done() 492 } 493 for _, c := range cs { 494 if c != nil { 495 wg.Add(1) 496 go output(c) 497 } 498 } 499 500 // Start a goroutine to close out once all the output goroutines are 501 // done. This must start after the wg.Add call. 502 go func() { 503 wg.Wait() 504 close(out) 505 }() 506 return out 507 }