1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-05-20 16:36:46 +08:00
Files
kubo/cmd/ipfs/daemon.go
Steven Allen 08cc5da55f gx: update deps
Importantly:

* fixes a bunch of MFS bugs
* pulls in some bitswap improvements

License: MIT
Signed-off-by: Steven Allen <steven@stebalien.com>
2019-01-08 19:19:34 -08:00

706 lines
21 KiB
Go

package main
import (
"errors"
_ "expvar"
"fmt"
"net"
"net/http"
_ "net/http/pprof"
"os"
"runtime"
"sort"
"sync"
version "github.com/ipfs/go-ipfs"
utilmain "github.com/ipfs/go-ipfs/cmd/ipfs/util"
oldcmds "github.com/ipfs/go-ipfs/commands"
"github.com/ipfs/go-ipfs/core"
commands "github.com/ipfs/go-ipfs/core/commands"
corehttp "github.com/ipfs/go-ipfs/core/corehttp"
corerepo "github.com/ipfs/go-ipfs/core/corerepo"
nodeMount "github.com/ipfs/go-ipfs/fuse/node"
fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
migrate "github.com/ipfs/go-ipfs/repo/fsrepo/migrations"
ma "gx/ipfs/QmNTCey11oxhb1AxDnQBRHtdhap6Ctud872NjAYPYYXPuc/go-multiaddr"
"gx/ipfs/QmTQuFQWHAWy4wMH6ZyPfGiawA5u9T8rs79FENoV8yXaoS/client_golang/prometheus"
mprome "gx/ipfs/QmVMcMs6duiwLzvhF6xWM3yc4GgjpNoctKFhvtBch5tpgo/go-metrics-prometheus"
"gx/ipfs/QmZcLBXKaFe8ND5YHPkJRAwmhJGrVsi1JqDZNyJ4nRK5Mj/go-multiaddr-net"
cmds "gx/ipfs/QmaAP56JAwdjwisPTu4yx17whcjTr6y5JCSCF77Y1rahWV/go-ipfs-cmds"
"gx/ipfs/Qmde5VP1qUkyQXKCfmEUA7bP64V2HAptbJ7phuPp7jXWwg/go-ipfs-cmdkit"
)
const (
adjustFDLimitKwd = "manage-fdlimit"
enableGCKwd = "enable-gc"
initOptionKwd = "init"
initProfileOptionKwd = "init-profile"
ipfsMountKwd = "mount-ipfs"
ipnsMountKwd = "mount-ipns"
migrateKwd = "migrate"
mountKwd = "mount"
offlineKwd = "offline" // global option
routingOptionKwd = "routing"
routingOptionSupernodeKwd = "supernode"
routingOptionDHTClientKwd = "dhtclient"
routingOptionDHTKwd = "dht"
routingOptionNoneKwd = "none"
routingOptionDefaultKwd = "default"
unencryptTransportKwd = "disable-transport-encryption"
unrestrictedApiAccessKwd = "unrestricted-api"
writableKwd = "writable"
enablePubSubKwd = "enable-pubsub-experiment"
enableIPNSPubSubKwd = "enable-namesys-pubsub"
enableMultiplexKwd = "enable-mplex-experiment"
// apiAddrKwd = "address-api"
// swarmAddrKwd = "address-swarm"
)
var daemonCmd = &cmds.Command{
Helptext: cmdkit.HelpText{
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.
`,
LongDescription: `
The daemon will start listening on ports on the network, which are
documented in (and can be modified through) 'ipfs config Addresses'.
For example, to change the 'Gateway' port:
ipfs config Addresses.Gateway /ip4/127.0.0.1/tcp/8082
The API address can be changed the same way:
ipfs config Addresses.API /ip4/127.0.0.1/tcp/5002
Make sure to restart the daemon after changing addresses.
By default, the gateway is only accessible locally. To expose it to
other computers in the network, use 0.0.0.0 as the ip address:
ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8080
Be careful if you expose the API. It is a security risk, as anyone could
control your node remotely. If you need to control the node remotely,
make sure to protect the port as you would other services or database
(firewall, authenticated proxy, etc).
HTTP Headers
ipfs supports passing arbitrary headers to the API and Gateway. You can
do this by setting headers on the API.HTTPHeaders and Gateway.HTTPHeaders
keys:
ipfs config --json API.HTTPHeaders.X-Special-Header '["so special :)"]'
ipfs config --json Gateway.HTTPHeaders.X-Special-Header '["so special :)"]'
Note that the value of the keys is an _array_ of strings. This is because
headers can have more than one value, and it is convenient to pass through
to other libraries.
CORS Headers (for API)
You can setup CORS headers the same way:
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["example.com"]'
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '["PUT", "GET", "POST"]'
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Credentials '["true"]'
Shutdown
To shutdown the daemon, send a SIGINT signal to it (e.g. by pressing 'Ctrl-C')
or send a SIGTERM signal to it (e.g. with 'kill'). It may take a while for the
daemon to shutdown gracefully, but it can be killed forcibly by sending a
second signal.
IPFS_PATH environment variable
ipfs uses a repository in the local file system. By default, the repo is
located at ~/.ipfs. To change the repo location, set the $IPFS_PATH
environment variable:
export IPFS_PATH=/path/to/ipfsrepo
Routing
IPFS by default will use a DHT for content routing. There is a highly
experimental alternative that operates the DHT in a 'client only' mode that
can be enabled by running the daemon as:
ipfs daemon --routing=dhtclient
This will later be transitioned into a config option once it gets out of the
'experimental' stage.
DEPRECATION NOTICE
Previously, ipfs used an environment variable as seen below:
export API_ORIGIN="http://localhost:8888/"
This is deprecated. It is still honored in this version, but will be removed
in a future version, along with this notice. Please move to setting the HTTP
Headers.
`,
},
Options: []cmdkit.Option{
cmdkit.BoolOption(initOptionKwd, "Initialize ipfs with default settings if not already initialized"),
cmdkit.StringOption(initProfileOptionKwd, "Configuration profiles to apply for --init. See ipfs init --help for more"),
cmdkit.StringOption(routingOptionKwd, "Overrides the routing option").WithDefault(routingOptionDefaultKwd),
cmdkit.BoolOption(mountKwd, "Mounts IPFS to the filesystem"),
cmdkit.BoolOption(writableKwd, "Enable writing objects (with POST, PUT and DELETE)"),
cmdkit.StringOption(ipfsMountKwd, "Path to the mountpoint for IPFS (if using --mount). Defaults to config setting."),
cmdkit.StringOption(ipnsMountKwd, "Path to the mountpoint for IPNS (if using --mount). Defaults to config setting."),
cmdkit.BoolOption(unrestrictedApiAccessKwd, "Allow API access to unlisted hashes"),
cmdkit.BoolOption(unencryptTransportKwd, "Disable transport encryption (for debugging protocols)"),
cmdkit.BoolOption(enableGCKwd, "Enable automatic periodic repo garbage collection"),
cmdkit.BoolOption(adjustFDLimitKwd, "Check and raise file descriptor limits if needed").WithDefault(true),
cmdkit.BoolOption(migrateKwd, "If true, assume yes at the migrate prompt. If false, assume no."),
cmdkit.BoolOption(enablePubSubKwd, "Instantiate the ipfs daemon with the experimental pubsub feature enabled."),
cmdkit.BoolOption(enableIPNSPubSubKwd, "Enable IPNS record distribution through pubsub; enables pubsub."),
cmdkit.BoolOption(enableMultiplexKwd, "Add the experimental 'go-multiplex' stream muxer to libp2p on construction.").WithDefault(true),
// TODO: add way to override addresses. tricky part: updating the config if also --init.
// cmdkit.StringOption(apiAddrKwd, "Address for the daemon rpc API (overrides config)"),
// cmdkit.StringOption(swarmAddrKwd, "Address for the swarm socket (overrides config)"),
},
Subcommands: map[string]*cmds.Command{},
Run: daemonFunc,
}
// defaultMux tells mux to serve path using the default muxer. This is
// mostly useful to hook up things that register in the default muxer,
// and don't provide a convenient http.Handler entry point, such as
// expvar and http/pprof.
func defaultMux(path string) corehttp.ServeOption {
return func(node *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
mux.Handle(path, http.DefaultServeMux)
return mux, nil
}
}
func daemonFunc(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) error {
// Inject metrics before we do anything
err := mprome.Inject()
if err != nil {
log.Errorf("Injecting prometheus handler for metrics failed with message: %s\n", err.Error())
}
// let the user know we're going.
fmt.Printf("Initializing daemon...\n")
// print the ipfs version
printVersion()
managefd, _ := req.Options[adjustFDLimitKwd].(bool)
if managefd {
if changedFds, newFdsLimit, err := utilmain.ManageFdLimit(); err != nil {
log.Errorf("setting file descriptor limit: %s", err)
} else {
if changedFds {
fmt.Printf("Successfully raised file descriptor limit to %d.\n", newFdsLimit)
}
}
}
cctx := env.(*oldcmds.Context)
go func() {
<-req.Context.Done()
fmt.Println("Received interrupt signal, shutting down...")
fmt.Println("(Hit ctrl-c again to force-shutdown the daemon.)")
}()
// check transport encryption flag.
unencrypted, _ := req.Options[unencryptTransportKwd].(bool)
if unencrypted {
log.Warningf(`Running with --%s: All connections are UNENCRYPTED.
You will not be able to connect to regular encrypted networks.`, unencryptTransportKwd)
}
// first, whether user has provided the initialization flag. we may be
// running in an uninitialized state.
initialize, _ := req.Options[initOptionKwd].(bool)
if initialize {
cfg := cctx.ConfigRoot
if !fsrepo.IsInitialized(cfg) {
profiles, _ := req.Options[initProfileOptionKwd].(string)
err := initWithDefaults(os.Stdout, cfg, profiles)
if err != nil {
return err
}
}
}
// acquire the repo lock _before_ constructing a node. we need to make
// sure we are permitted to access the resources (datastore, etc.)
repo, err := fsrepo.Open(cctx.ConfigRoot)
switch err {
default:
return err
case fsrepo.ErrNeedMigration:
domigrate, found := req.Options[migrateKwd].(bool)
fmt.Println("Found outdated fs-repo, migrations need to be run.")
if !found {
domigrate = YesNoPrompt("Run migrations now? [y/N]")
}
if !domigrate {
fmt.Println("Not running migrations of fs-repo now.")
fmt.Println("Please get fs-repo-migrations from https://dist.ipfs.io")
return fmt.Errorf("fs-repo requires migration")
}
err = migrate.RunMigration(fsrepo.RepoVersion)
if err != nil {
fmt.Println("The migrations of fs-repo failed:")
fmt.Printf(" %s\n", err)
fmt.Println("If you think this is a bug, please file an issue and include this whole log output.")
fmt.Println(" https://github.com/ipfs/fs-repo-migrations")
return err
}
repo, err = fsrepo.Open(cctx.ConfigRoot)
if err != nil {
return err
}
case nil:
break
}
cfg, err := cctx.GetConfig()
if err != nil {
return err
}
offline, _ := req.Options[offlineKwd].(bool)
ipnsps, _ := req.Options[enableIPNSPubSubKwd].(bool)
pubsub, _ := req.Options[enablePubSubKwd].(bool)
mplex, _ := req.Options[enableMultiplexKwd].(bool)
// Start assembling node config
ncfg := &core.BuildCfg{
Repo: repo,
Permanent: true, // It is temporary way to signify that node is permanent
Online: !offline,
DisableEncryptedConnections: unencrypted,
ExtraOpts: map[string]bool{
"pubsub": pubsub,
"ipnsps": ipnsps,
"mplex": mplex,
},
//TODO(Kubuxu): refactor Online vs Offline by adding Permanent vs Ephemeral
}
routingOption, _ := req.Options[routingOptionKwd].(string)
if routingOption == routingOptionDefaultKwd {
cfg, err := repo.Config()
if err != nil {
return err
}
routingOption = cfg.Routing.Type
if routingOption == "" {
routingOption = routingOptionDHTKwd
}
}
switch routingOption {
case routingOptionSupernodeKwd:
return errors.New("supernode routing was never fully implemented and has been removed")
case routingOptionDHTClientKwd:
ncfg.Routing = core.DHTClientOption
case routingOptionDHTKwd:
ncfg.Routing = core.DHTOption
case routingOptionNoneKwd:
ncfg.Routing = core.NilRouterOption
default:
return fmt.Errorf("unrecognized routing option: %s", routingOption)
}
node, err := core.NewNode(req.Context, ncfg)
if err != nil {
log.Error("error from node construction: ", err)
return err
}
node.SetLocal(false)
if node.PNetFingerprint != nil {
fmt.Println("Swarm is limited to private network of peers with the swarm key")
fmt.Printf("Swarm key fingerprint: %x\n", node.PNetFingerprint)
}
printSwarmAddrs(node)
defer func() {
// We wait for the node to close first, as the node has children
// that it will wait for before closing, such as the API server.
node.Close()
select {
case <-req.Context.Done():
log.Info("Gracefully shut down daemon")
default:
}
}()
cctx.ConstructNode = func() (*core.IpfsNode, error) {
return node, nil
}
// construct api endpoint - every time
apiErrc, err := serveHTTPApi(req, cctx)
if err != nil {
return err
}
// construct fuse mountpoints - if the user provided the --mount flag
mount, _ := req.Options[mountKwd].(bool)
if mount && offline {
return cmdkit.Errorf(cmdkit.ErrClient, "mount is not currently supported in offline mode")
}
if mount {
if err := mountFuse(req, cctx); err != nil {
return err
}
}
// repo blockstore GC - if --enable-gc flag is present
gcErrc, err := maybeRunGC(req, node)
if err != nil {
return err
}
// construct http gateway - if it is set in the config
var gwErrc <-chan error
if len(cfg.Addresses.Gateway) > 0 {
var err error
gwErrc, err = serveHTTPGateway(req, cctx)
if err != nil {
return err
}
}
// initialize metrics collector
prometheus.MustRegister(&corehttp.IpfsNodeCollector{Node: node})
fmt.Printf("Daemon is ready\n")
// collect long-running errors and block for shutdown
// TODO(cryptix): our fuse currently doesnt follow this pattern for graceful shutdown
for err := range merge(apiErrc, gwErrc, gcErrc) {
if err != nil {
return err
}
}
return nil
}
// serveHTTPApi collects options, creates listener, prints status message and starts serving requests
func serveHTTPApi(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, error) {
cfg, err := cctx.GetConfig()
if err != nil {
return nil, fmt.Errorf("serveHTTPApi: GetConfig() failed: %s", err)
}
apiAddrs := make([]string, 0, 2)
apiAddr, _ := req.Options[commands.ApiOption].(string)
if apiAddr == "" {
apiAddrs = cfg.Addresses.API
} else {
apiAddrs = append(apiAddrs, apiAddr)
}
listeners := make([]manet.Listener, 0, len(apiAddrs))
for _, addr := range apiAddrs {
apiMaddr, err := ma.NewMultiaddr(addr)
if err != nil {
return nil, fmt.Errorf("serveHTTPApi: invalid API address: %q (err: %s)", apiAddr, err)
}
apiLis, err := manet.Listen(apiMaddr)
if err != nil {
return nil, fmt.Errorf("serveHTTPApi: manet.Listen(%s) failed: %s", apiMaddr, err)
}
// we might have listened to /tcp/0 - lets see what we are listing on
apiMaddr = apiLis.Multiaddr()
fmt.Printf("API server listening on %s\n", apiMaddr)
fmt.Printf("WebUI: http://%s/webui\n", apiLis.Addr())
listeners = append(listeners, apiLis)
}
// by default, we don't let you load arbitrary ipfs objects through the api,
// because this would open up the api to scripting vulnerabilities.
// only the webui objects are allowed.
// if you know what you're doing, go ahead and pass --unrestricted-api.
unrestricted, _ := req.Options[unrestrictedApiAccessKwd].(bool)
gatewayOpt := corehttp.GatewayOption(false, corehttp.WebUIPaths...)
if unrestricted {
gatewayOpt = corehttp.GatewayOption(true, "/ipfs", "/ipns")
}
var opts = []corehttp.ServeOption{
corehttp.MetricsCollectionOption("api"),
corehttp.CheckVersionOption(),
corehttp.CommandsOption(*cctx),
corehttp.WebUIOption,
gatewayOpt,
corehttp.VersionOption(),
defaultMux("/debug/vars"),
defaultMux("/debug/pprof/"),
corehttp.MutexFractionOption("/debug/pprof-mutex/"),
corehttp.MetricsScrapingOption("/debug/metrics/prometheus"),
corehttp.LogOption(),
}
if len(cfg.Gateway.RootRedirect) > 0 {
opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect))
}
node, err := cctx.ConstructNode()
if err != nil {
return nil, fmt.Errorf("serveHTTPApi: ConstructNode() failed: %s", err)
}
if err := node.Repo.SetAPIAddr(listeners[0].Multiaddr()); err != nil {
return nil, fmt.Errorf("serveHTTPApi: SetAPIAddr() failed: %s", err)
}
errc := make(chan error)
var wg sync.WaitGroup
for _, apiLis := range listeners {
wg.Add(1)
go func(lis manet.Listener) {
defer wg.Done()
errc <- corehttp.Serve(node, manet.NetListener(lis), opts...)
}(apiLis)
}
go func() {
wg.Wait()
close(errc)
}()
return errc, nil
}
// printSwarmAddrs prints the addresses of the host
func printSwarmAddrs(node *core.IpfsNode) {
if !node.OnlineMode() {
fmt.Println("Swarm not listening, running in offline mode.")
return
}
var lisAddrs []string
ifaceAddrs, err := node.PeerHost.Network().InterfaceListenAddresses()
if err != nil {
log.Errorf("failed to read listening addresses: %s", err)
}
for _, addr := range ifaceAddrs {
lisAddrs = append(lisAddrs, addr.String())
}
sort.Sort(sort.StringSlice(lisAddrs))
for _, addr := range lisAddrs {
fmt.Printf("Swarm listening on %s\n", addr)
}
var addrs []string
for _, addr := range node.PeerHost.Addrs() {
addrs = append(addrs, addr.String())
}
sort.Sort(sort.StringSlice(addrs))
for _, addr := range addrs {
fmt.Printf("Swarm announcing %s\n", addr)
}
}
// serveHTTPGateway collects options, creates listener, prints status message and starts serving requests
func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, error) {
cfg, err := cctx.GetConfig()
if err != nil {
return nil, fmt.Errorf("serveHTTPGateway: GetConfig() failed: %s", err)
}
writable, writableOptionFound := req.Options[writableKwd].(bool)
if !writableOptionFound {
writable = cfg.Gateway.Writable
}
gatewayAddrs := cfg.Addresses.Gateway
listeners := make([]manet.Listener, 0, len(gatewayAddrs))
for _, addr := range gatewayAddrs {
gatewayMaddr, err := ma.NewMultiaddr(addr)
if err != nil {
return nil, fmt.Errorf("serveHTTPGateway: invalid gateway address: %q (err: %s)", addr, err)
}
gwLis, err := manet.Listen(gatewayMaddr)
if err != nil {
return nil, fmt.Errorf("serveHTTPGateway: manet.Listen(%s) failed: %s", gatewayMaddr, err)
}
// we might have listened to /tcp/0 - lets see what we are listing on
gatewayMaddr = gwLis.Multiaddr()
if writable {
fmt.Printf("Gateway (writable) server listening on %s\n", gatewayMaddr)
} else {
fmt.Printf("Gateway (readonly) server listening on %s\n", gatewayMaddr)
}
listeners = append(listeners, gwLis)
}
cmdctx := *cctx
cmdctx.Gateway = true
var opts = []corehttp.ServeOption{
corehttp.MetricsCollectionOption("gateway"),
corehttp.IPNSHostnameOption(),
corehttp.GatewayOption(writable, "/ipfs", "/ipns"),
corehttp.VersionOption(),
corehttp.CheckVersionOption(),
corehttp.CommandsROOption(cmdctx),
}
if cfg.Experimental.P2pHttpProxy {
opts = append(opts, corehttp.ProxyOption())
}
if len(cfg.Gateway.RootRedirect) > 0 {
opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect))
}
node, err := cctx.ConstructNode()
if err != nil {
return nil, fmt.Errorf("serveHTTPGateway: ConstructNode() failed: %s", err)
}
errc := make(chan error)
var wg sync.WaitGroup
for _, lis := range listeners {
wg.Add(1)
go func(lis manet.Listener) {
defer wg.Done()
errc <- corehttp.Serve(node, manet.NetListener(lis), opts...)
}(lis)
}
go func() {
wg.Wait()
close(errc)
}()
return errc, nil
}
//collects options and opens the fuse mountpoint
func mountFuse(req *cmds.Request, cctx *oldcmds.Context) error {
cfg, err := cctx.GetConfig()
if err != nil {
return fmt.Errorf("mountFuse: GetConfig() failed: %s", err)
}
fsdir, found := req.Options[ipfsMountKwd].(string)
if !found {
fsdir = cfg.Mounts.IPFS
}
nsdir, found := req.Options[ipnsMountKwd].(string)
if !found {
nsdir = cfg.Mounts.IPNS
}
node, err := cctx.ConstructNode()
if err != nil {
return fmt.Errorf("mountFuse: ConstructNode() failed: %s", err)
}
err = nodeMount.Mount(node, fsdir, nsdir)
if err != nil {
return err
}
fmt.Printf("IPFS mounted at: %s\n", fsdir)
fmt.Printf("IPNS mounted at: %s\n", nsdir)
return nil
}
func maybeRunGC(req *cmds.Request, node *core.IpfsNode) (<-chan error, error) {
enableGC, _ := req.Options[enableGCKwd].(bool)
if !enableGC {
return nil, nil
}
errc := make(chan error)
go func() {
errc <- corerepo.PeriodicGC(req.Context, node)
close(errc)
}()
return errc, nil
}
// merge does fan-in of multiple read-only error channels
// taken from http://blog.golang.org/pipelines
func merge(cs ...<-chan error) <-chan error {
var wg sync.WaitGroup
out := make(chan error)
// Start an output goroutine for each input channel in cs. output
// copies values from c to out until c is closed, then calls wg.Done.
output := func(c <-chan error) {
for n := range c {
out <- n
}
wg.Done()
}
for _, c := range cs {
if c != nil {
wg.Add(1)
go output(c)
}
}
// Start a goroutine to close out once all the output goroutines are
// done. This must start after the wg.Add call.
go func() {
wg.Wait()
close(out)
}()
return out
}
func YesNoPrompt(prompt string) bool {
var s string
for i := 0; i < 3; i++ {
fmt.Printf("%s ", prompt)
fmt.Scanf("%s", &s)
switch s {
case "y", "Y":
return true
case "n", "N":
return false
case "":
return false
}
fmt.Println("Please press either 'y' or 'n'")
}
return false
}
func printVersion() {
fmt.Printf("go-ipfs version: %s-%s\n", version.CurrentVersionNumber, version.CurrentCommit)
fmt.Printf("Repo version: %d\n", fsrepo.RepoVersion)
fmt.Printf("System version: %s\n", runtime.GOARCH+"/"+runtime.GOOS)
fmt.Printf("Golang version: %s\n", runtime.Version())
}