mirror of
https://github.com/ipfs/kubo.git
synced 2025-06-05 07:29:02 +08:00
674 lines
20 KiB
Go
674 lines
20 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
_ "expvar"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
_ "net/http/pprof"
|
|
"os"
|
|
"sort"
|
|
"sync"
|
|
|
|
cmds "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"
|
|
"github.com/ipfs/go-ipfs/core/corerouting"
|
|
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"
|
|
|
|
mprome "gx/ipfs/QmSk46nSD78YiuNojYMS8NW6hSCjH95JajqqzzoychZgef/go-metrics-prometheus"
|
|
"gx/ipfs/QmX3QZ5jHEPidwUrymXV1iSCSUhdGxj15sm2gP4jKMef7B/client_golang/prometheus"
|
|
pstore "gx/ipfs/QmXZSd1qR5BxZkPyuwfT5jpqQFScZccoZvDneXsKzCNHWX/go-libp2p-peerstore"
|
|
iconn "gx/ipfs/QmcXRdAP5bCCm51X7XfDUrQ8Q9PsrKbU75pyvB18iuKob5/go-libp2p-interface-conn"
|
|
ma "gx/ipfs/QmcyqRMCAXVtYPS4DiBrA7sezL9rRGfW8Ctx7cywL4TXJj/go-multiaddr"
|
|
"gx/ipfs/Qmf1Gq7N45Rpuw7ev47uWgH6dLPtdnvcMRNPkVBwqjLJg2/go-multiaddr-net"
|
|
)
|
|
|
|
const (
|
|
adjustFDLimitKwd = "manage-fdlimit"
|
|
enableGCKwd = "enable-gc"
|
|
initOptionKwd = "init"
|
|
ipfsMountKwd = "mount-ipfs"
|
|
ipnsMountKwd = "mount-ipns"
|
|
migrateKwd = "migrate"
|
|
mountKwd = "mount"
|
|
offlineKwd = "offline"
|
|
routingOptionKwd = "routing"
|
|
routingOptionSupernodeKwd = "supernode"
|
|
routingOptionDHTClientKwd = "dhtclient"
|
|
routingOptionDHTKwd = "dht"
|
|
routingOptionNoneKwd = "none"
|
|
unencryptTransportKwd = "disable-transport-encryption"
|
|
unrestrictedApiAccessKwd = "unrestricted-api"
|
|
writableKwd = "writable"
|
|
enableFloodSubKwd = "enable-pubsub-experiment"
|
|
enableMultiplexKwd = "enable-mplex-experiment"
|
|
// apiAddrKwd = "address-api"
|
|
// swarmAddrKwd = "address-swarm"
|
|
)
|
|
|
|
var daemonCmd = &cmds.Command{
|
|
Helptext: cmds.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: []cmds.Option{
|
|
cmds.BoolOption(initOptionKwd, "Initialize ipfs with default settings if not already initialized").Default(false),
|
|
cmds.StringOption(routingOptionKwd, "Overrides the routing option").Default("dht"),
|
|
cmds.BoolOption(mountKwd, "Mounts IPFS to the filesystem").Default(false),
|
|
cmds.BoolOption(writableKwd, "Enable writing objects (with POST, PUT and DELETE)").Default(false),
|
|
cmds.StringOption(ipfsMountKwd, "Path to the mountpoint for IPFS (if using --mount). Defaults to config setting."),
|
|
cmds.StringOption(ipnsMountKwd, "Path to the mountpoint for IPNS (if using --mount). Defaults to config setting."),
|
|
cmds.BoolOption(unrestrictedApiAccessKwd, "Allow API access to unlisted hashes").Default(false),
|
|
cmds.BoolOption(unencryptTransportKwd, "Disable transport encryption (for debugging protocols)").Default(false),
|
|
cmds.BoolOption(enableGCKwd, "Enable automatic periodic repo garbage collection").Default(false),
|
|
cmds.BoolOption(adjustFDLimitKwd, "Check and raise file descriptor limits if needed").Default(true),
|
|
cmds.BoolOption(offlineKwd, "Run offline. Do not connect to the rest of the network but provide local API.").Default(false),
|
|
cmds.BoolOption(migrateKwd, "If true, assume yes at the migrate prompt. If false, assume no."),
|
|
cmds.BoolOption(enableFloodSubKwd, "Instantiate the ipfs daemon with the experimental pubsub feature enabled."),
|
|
cmds.BoolOption(enableMultiplexKwd, "Add the experimental 'go-multiplex' stream muxer to libp2p on construction.").Default(true),
|
|
// 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)"),
|
|
},
|
|
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
|
|
}
|
|
}
|
|
|
|
var fileDescriptorCheck = func() error { return nil }
|
|
|
|
func daemonFunc(req cmds.Request, res cmds.Response) {
|
|
// 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")
|
|
|
|
managefd, _, _ := req.Option(adjustFDLimitKwd).Bool()
|
|
if managefd {
|
|
if err := fileDescriptorCheck(); err != nil {
|
|
log.Errorf("setting file descriptor limit: %s", err)
|
|
}
|
|
}
|
|
|
|
ctx := req.InvocContext()
|
|
|
|
go func() {
|
|
select {
|
|
case <-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.Option(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)
|
|
iconn.EncryptConnections = false
|
|
}
|
|
|
|
// first, whether user has provided the initialization flag. we may be
|
|
// running in an uninitialized state.
|
|
initialize, _, err := req.Option(initOptionKwd).Bool()
|
|
if err != nil {
|
|
res.SetError(err, cmds.ErrNormal)
|
|
return
|
|
}
|
|
|
|
if initialize {
|
|
|
|
cfg := ctx.ConfigRoot
|
|
if !fsrepo.IsInitialized(cfg) {
|
|
err := initWithDefaults(os.Stdout, cfg)
|
|
if err != nil {
|
|
res.SetError(err, cmds.ErrNormal)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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(ctx.ConfigRoot)
|
|
switch err {
|
|
default:
|
|
res.SetError(err, cmds.ErrNormal)
|
|
return
|
|
case fsrepo.ErrNeedMigration:
|
|
domigrate, found, _ := req.Option(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")
|
|
res.SetError(fmt.Errorf("fs-repo requires migration"), cmds.ErrNormal)
|
|
return
|
|
}
|
|
|
|
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")
|
|
res.SetError(err, cmds.ErrNormal)
|
|
return
|
|
}
|
|
|
|
repo, err = fsrepo.Open(ctx.ConfigRoot)
|
|
if err != nil {
|
|
res.SetError(err, cmds.ErrNormal)
|
|
return
|
|
}
|
|
case nil:
|
|
break
|
|
}
|
|
|
|
cfg, err := ctx.GetConfig()
|
|
if err != nil {
|
|
res.SetError(err, cmds.ErrNormal)
|
|
return
|
|
}
|
|
|
|
offline, _, _ := req.Option(offlineKwd).Bool()
|
|
pubsub, _, _ := req.Option(enableFloodSubKwd).Bool()
|
|
mplex, _, _ := req.Option(enableMultiplexKwd).Bool()
|
|
|
|
// Start assembling node config
|
|
ncfg := &core.BuildCfg{
|
|
Repo: repo,
|
|
Permament: true, // It is temporary way to signify that node is permament
|
|
Online: !offline,
|
|
ExtraOpts: map[string]bool{
|
|
"pubsub": pubsub,
|
|
"mplex": mplex,
|
|
},
|
|
//TODO(Kubuxu): refactor Online vs Offline by adding Permanent vs Ephemeral
|
|
}
|
|
|
|
routingOption, _, err := req.Option(routingOptionKwd).String()
|
|
if err != nil {
|
|
res.SetError(err, cmds.ErrNormal)
|
|
return
|
|
}
|
|
switch routingOption {
|
|
case routingOptionSupernodeKwd:
|
|
servers, err := cfg.SupernodeRouting.ServerIPFSAddrs()
|
|
if err != nil {
|
|
res.SetError(err, cmds.ErrNormal)
|
|
repo.Close() // because ownership hasn't been transferred to the node
|
|
return
|
|
}
|
|
var infos []pstore.PeerInfo
|
|
for _, addr := range servers {
|
|
infos = append(infos, pstore.PeerInfo{
|
|
ID: addr.ID(),
|
|
Addrs: []ma.Multiaddr{addr.Transport()},
|
|
})
|
|
}
|
|
|
|
ncfg.Routing = corerouting.SupernodeClient(infos...)
|
|
case routingOptionDHTClientKwd:
|
|
ncfg.Routing = core.DHTClientOption
|
|
case routingOptionDHTKwd:
|
|
ncfg.Routing = core.DHTOption
|
|
case routingOptionNoneKwd:
|
|
ncfg.Routing = core.NilRouterOption
|
|
default:
|
|
res.SetError(fmt.Errorf("unrecognized routing option: %s", routingOption), cmds.ErrNormal)
|
|
return
|
|
}
|
|
|
|
node, err := core.NewNode(req.Context(), ncfg)
|
|
if err != nil {
|
|
log.Error("error from node construction: ", err)
|
|
res.SetError(err, cmds.ErrNormal)
|
|
return
|
|
}
|
|
node.SetLocal(false)
|
|
|
|
if node.PNetFingerpint != nil {
|
|
fmt.Println("Swarm is limited to private network of peers with the swarm key")
|
|
fmt.Printf("Swarm key fingerprint: %x\n", node.PNetFingerpint)
|
|
}
|
|
|
|
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:
|
|
}
|
|
}()
|
|
|
|
ctx.ConstructNode = func() (*core.IpfsNode, error) {
|
|
return node, nil
|
|
}
|
|
|
|
// construct api endpoint - every time
|
|
err, apiErrc := serveHTTPApi(req)
|
|
if err != nil {
|
|
res.SetError(err, cmds.ErrNormal)
|
|
return
|
|
}
|
|
|
|
// construct fuse mountpoints - if the user provided the --mount flag
|
|
mount, _, err := req.Option(mountKwd).Bool()
|
|
if err != nil {
|
|
res.SetError(err, cmds.ErrNormal)
|
|
return
|
|
}
|
|
if mount && offline {
|
|
res.SetError(errors.New("mount is not currently supported in offline mode"),
|
|
cmds.ErrClient)
|
|
return
|
|
}
|
|
if mount {
|
|
if err := mountFuse(req); err != nil {
|
|
res.SetError(err, cmds.ErrNormal)
|
|
return
|
|
}
|
|
}
|
|
|
|
// repo blockstore GC - if --enable-gc flag is present
|
|
err, gcErrc := maybeRunGC(req, node)
|
|
if err != nil {
|
|
res.SetError(err, cmds.ErrNormal)
|
|
return
|
|
}
|
|
|
|
// construct http gateway - if it is set in the config
|
|
var gwErrc <-chan error
|
|
if len(cfg.Addresses.Gateway) > 0 {
|
|
var err error
|
|
err, gwErrc = serveHTTPGateway(req)
|
|
if err != nil {
|
|
res.SetError(err, cmds.ErrNormal)
|
|
return
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
log.Error(err)
|
|
res.SetError(err, cmds.ErrNormal)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// serveHTTPApi collects options, creates listener, prints status message and starts serving requests
|
|
func serveHTTPApi(req cmds.Request) (error, <-chan error) {
|
|
cfg, err := req.InvocContext().GetConfig()
|
|
if err != nil {
|
|
return fmt.Errorf("serveHTTPApi: GetConfig() failed: %s", err), nil
|
|
}
|
|
|
|
apiAddr, _, err := req.Option(commands.ApiOption).String()
|
|
if err != nil {
|
|
return fmt.Errorf("serveHTTPApi: %s", err), nil
|
|
}
|
|
if apiAddr == "" {
|
|
apiAddr = cfg.Addresses.API
|
|
}
|
|
apiMaddr, err := ma.NewMultiaddr(apiAddr)
|
|
if err != nil {
|
|
return fmt.Errorf("serveHTTPApi: invalid API address: %q (err: %s)", apiAddr, err), nil
|
|
}
|
|
|
|
apiLis, err := manet.Listen(apiMaddr)
|
|
if err != nil {
|
|
return fmt.Errorf("serveHTTPApi: manet.Listen(%s) failed: %s", apiMaddr, err), nil
|
|
}
|
|
// 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)
|
|
|
|
// 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, _, err := req.Option(unrestrictedApiAccessKwd).Bool()
|
|
if err != nil {
|
|
return fmt.Errorf("serveHTTPApi: Option(%s) failed: %s", unrestrictedApiAccessKwd, err), nil
|
|
}
|
|
gatewayOpt := corehttp.GatewayOption(false, corehttp.WebUIPaths...)
|
|
if unrestricted {
|
|
gatewayOpt = corehttp.GatewayOption(true, "/ipfs", "/ipns")
|
|
}
|
|
|
|
var opts = []corehttp.ServeOption{
|
|
corehttp.MetricsCollectionOption("api"),
|
|
corehttp.CommandsOption(*req.InvocContext()),
|
|
corehttp.WebUIOption,
|
|
gatewayOpt,
|
|
corehttp.VersionOption(),
|
|
defaultMux("/debug/vars"),
|
|
defaultMux("/debug/pprof/"),
|
|
corehttp.MetricsScrapingOption("/debug/metrics/prometheus"),
|
|
corehttp.LogOption(),
|
|
}
|
|
|
|
if len(cfg.Gateway.RootRedirect) > 0 {
|
|
opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect))
|
|
}
|
|
|
|
node, err := req.InvocContext().ConstructNode()
|
|
if err != nil {
|
|
return fmt.Errorf("serveHTTPApi: ConstructNode() failed: %s", err), nil
|
|
}
|
|
|
|
if err := node.Repo.SetAPIAddr(apiMaddr); err != nil {
|
|
return fmt.Errorf("serveHTTPApi: SetAPIAddr() failed: %s", err), nil
|
|
}
|
|
|
|
errc := make(chan error)
|
|
go func() {
|
|
errc <- corehttp.Serve(node, apiLis.NetListener(), opts...)
|
|
close(errc)
|
|
}()
|
|
return nil, errc
|
|
}
|
|
|
|
// 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 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 listening on %s\n", addr)
|
|
}
|
|
}
|
|
|
|
// serveHTTPGateway collects options, creates listener, prints status message and starts serving requests
|
|
func serveHTTPGateway(req cmds.Request) (error, <-chan error) {
|
|
cfg, err := req.InvocContext().GetConfig()
|
|
if err != nil {
|
|
return fmt.Errorf("serveHTTPGateway: GetConfig() failed: %s", err), nil
|
|
}
|
|
|
|
gatewayMaddr, err := ma.NewMultiaddr(cfg.Addresses.Gateway)
|
|
if err != nil {
|
|
return fmt.Errorf("serveHTTPGateway: invalid gateway address: %q (err: %s)", cfg.Addresses.Gateway, err), nil
|
|
}
|
|
|
|
writable, writableOptionFound, err := req.Option(writableKwd).Bool()
|
|
if err != nil {
|
|
return fmt.Errorf("serveHTTPGateway: req.Option(%s) failed: %s", writableKwd, err), nil
|
|
}
|
|
if !writableOptionFound {
|
|
writable = cfg.Gateway.Writable
|
|
}
|
|
|
|
gwLis, err := manet.Listen(gatewayMaddr)
|
|
if err != nil {
|
|
return fmt.Errorf("serveHTTPGateway: manet.Listen(%s) failed: %s", gatewayMaddr, err), nil
|
|
}
|
|
// 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)
|
|
}
|
|
|
|
var opts = []corehttp.ServeOption{
|
|
corehttp.MetricsCollectionOption("gateway"),
|
|
corehttp.CommandsROOption(*req.InvocContext()),
|
|
corehttp.VersionOption(),
|
|
corehttp.IPNSHostnameOption(),
|
|
corehttp.GatewayOption(writable, "/ipfs", "/ipns"),
|
|
}
|
|
|
|
if len(cfg.Gateway.RootRedirect) > 0 {
|
|
opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect))
|
|
}
|
|
|
|
node, err := req.InvocContext().ConstructNode()
|
|
if err != nil {
|
|
return fmt.Errorf("serveHTTPGateway: ConstructNode() failed: %s", err), nil
|
|
}
|
|
|
|
errc := make(chan error)
|
|
go func() {
|
|
errc <- corehttp.Serve(node, gwLis.NetListener(), opts...)
|
|
close(errc)
|
|
}()
|
|
return nil, errc
|
|
}
|
|
|
|
//collects options and opens the fuse mountpoint
|
|
func mountFuse(req cmds.Request) error {
|
|
cfg, err := req.InvocContext().GetConfig()
|
|
if err != nil {
|
|
return fmt.Errorf("mountFuse: GetConfig() failed: %s", err)
|
|
}
|
|
|
|
fsdir, found, err := req.Option(ipfsMountKwd).String()
|
|
if err != nil {
|
|
return fmt.Errorf("mountFuse: req.Option(%s) failed: %s", ipfsMountKwd, err)
|
|
}
|
|
if !found {
|
|
fsdir = cfg.Mounts.IPFS
|
|
}
|
|
|
|
nsdir, found, err := req.Option(ipnsMountKwd).String()
|
|
if err != nil {
|
|
return fmt.Errorf("mountFuse: req.Option(%s) failed: %s", ipnsMountKwd, err)
|
|
}
|
|
if !found {
|
|
nsdir = cfg.Mounts.IPNS
|
|
}
|
|
|
|
node, err := req.InvocContext().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) (error, <-chan error) {
|
|
enableGC, _, err := req.Option(enableGCKwd).Bool()
|
|
if err != nil {
|
|
return err, nil
|
|
}
|
|
if !enableGC {
|
|
return nil, nil
|
|
}
|
|
|
|
errc := make(chan error)
|
|
go func() {
|
|
errc <- corerepo.PeriodicGC(req.Context(), node)
|
|
close(errc)
|
|
}()
|
|
return nil, errc
|
|
}
|
|
|
|
// 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
|
|
}
|