From c97c44e8a41734dbbc6890a34f892a29458e0f5d Mon Sep 17 00:00:00 2001 From: achingbrain Date: Thu, 9 Aug 2018 12:09:05 +0100 Subject: [PATCH 01/13] Allow mfs files.write command to create parent directories Adds support for a `-p/--parents` flag to the `files.write` command similar to the one supported by the `files.mkdir` command. If this is true and the directory for the file is not `"/"`, try to create the containing directory before writing to the file. License: MIT Signed-off-by: Alex Potsides --- core/commands/files.go | 23 +++++++++++++++++++++++ test/sharness/t0250-files-api.sh | 22 +++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/core/commands/files.go b/core/commands/files.go index 6e0a4d4cd..586592267 100644 --- a/core/commands/files.go +++ b/core/commands/files.go @@ -721,6 +721,7 @@ stat' on the file or any of its ancestors. Options: []cmdkit.Option{ cmdkit.IntOption("offset", "o", "Byte offset to begin writing at."), cmdkit.BoolOption("create", "e", "Create the file if it does not exist."), + cmdkit.BoolOption("parents", "p", "Make parent directories as needed."), cmdkit.BoolOption("truncate", "t", "Truncate the file to size zero before writing."), cmdkit.IntOption("count", "n", "Maximum number of bytes to read."), cmdkit.BoolOption("raw-leaves", "Use raw blocks for newly created leaf nodes. (experimental)"), @@ -735,6 +736,7 @@ stat' on the file or any of its ancestors. } create, _ := req.Options["create"].(bool) + mkParents, _ := req.Options["parents"].(bool) trunc, _ := req.Options["truncate"].(bool) flush, _ := req.Options["flush"].(bool) rawLeaves, rawLeavesDef := req.Options["raw-leaves"].(bool) @@ -757,6 +759,14 @@ stat' on the file or any of its ancestors. return } + if mkParents { + err := ensureContainingDirectoryExists(nd.FilesRoot, path, prefix) + if err != nil { + re.SetError(err, cmdkit.ErrNormal) + return + } + } + fi, err := getFileHandle(nd.FilesRoot, path, create, prefix) if err != nil { re.SetError(err, cmdkit.ErrNormal) @@ -1146,6 +1156,19 @@ func getPrefix(req oldcmds.Request) (cid.Builder, error) { return &prefix, nil } +func ensureContainingDirectoryExists(r *mfs.Root, path string, builder cid.Builder) error { + dirtomake := gopath.Dir(path) + + if dirtomake == "/" { + return nil + } + + return mfs.Mkdir(r, dirtomake, mfs.MkdirOpts{ + Mkparents: true, + CidBuilder: builder, + }) +} + func getFileHandle(r *mfs.Root, path string, create bool, builder cid.Builder) (*mfs.File, error) { target, err := mfs.Lookup(r, path) switch err { diff --git a/test/sharness/t0250-files-api.sh b/test/sharness/t0250-files-api.sh index 007bad12d..7b97f31de 100755 --- a/test/sharness/t0250-files-api.sh +++ b/test/sharness/t0250-files-api.sh @@ -597,9 +597,29 @@ test_files_api() { ipfs files ls /adir | grep foobar ' + test_expect_success "should fail to write file and create intermediate directories with no --parents flag set $EXTRA" ' + echo "ipfs rocks" | test_must_fail ipfs files write --create /parents/foo/ipfs.txt + ' + + test_expect_success "can write file and create intermediate directories $EXTRA" ' + echo "ipfs rocks" | ipfs files write --create --parents /parents/foo/bar/baz/ipfs.txt && + ipfs files stat "/parents/foo/bar/baz/ipfs.txt" | grep -q "^Type: file" + ' + + test_expect_success "can write file and create intermediate directories with short flags $EXTRA" ' + echo "ipfs rocks" | ipfs files write -e -p /parents/foo/bar/baz/qux/quux/garply/ipfs.txt && + ipfs files stat "/parents/foo/bar/baz/qux/quux/garply/ipfs.txt" | grep -q "^Type: file" + ' + + test_expect_success "can write another file in the same directory with -e -p $EXTRA" ' + echo "ipfs rocks" | ipfs files write -e -p /parents/foo/bar/baz/qux/quux/garply/ipfs2.txt && + ipfs files stat "/parents/foo/bar/baz/qux/quux/garply/ipfs2.txt" | grep -q "^Type: file" + ' + test_expect_success "clean up $EXTRA" ' ipfs files rm -r /foobar && - ipfs files rm -r /adir + ipfs files rm -r /adir && + ipfs files rm -r /parents ' test_expect_success "root mfs entry is empty $EXTRA" ' From 1af6717eff4d121ac877f18441c334d608e85824 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 20 Aug 2018 15:53:10 -0700 Subject: [PATCH 02/13] add some basic gateway documentation License: MIT Signed-off-by: Steven Allen --- docs/gateway.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 docs/gateway.md diff --git a/docs/gateway.md b/docs/gateway.md new file mode 100644 index 000000000..7f9326e5a --- /dev/null +++ b/docs/gateway.md @@ -0,0 +1,48 @@ +# Gateway + +An IPFS Gateway acts as a bridge between traditional web browsers and IPFS. +Through the gateway, users can browse files and websites stored in IPFS as if +they were stored in a traditional web server. + +By default, go-ipfs nodes run a gateway at `http://127.0.0.1:5001/`. + +We also provide a public gateway at `https://ipfs.io`. If you've ever seen a +link in the form `https://ipfs.io/ipfs/Qm...`, that's being served from *our* +gateway. + +## Configuration + +The gateway's configuration options are (briefly) described in the +[config](https://github.com/ipfs/go-ipfs/blob/master/docs/config.md#gateway) +documentation. + +## Directories + +For convenience, the gateway (mostly) acts like a normal web-server when serving +a directory: + +1. If the directory contains an `index.html` file: + 1. If the path does not end in a `/`, append a `/` and redirect. This helps + avoid serving duplicate content from different paths. + 2. Otherwise, serve the `index.html` file. +2. Dynamically build and serve a listing of the contents of the directory. + +This redirect is skipped if the query string contains a +`go-get=1` parameter. See [PR#3964](https://github.com/ipfs/go-ipfs/pull/3963) +for details + +## Filenames + +When downloading files, browsers will usually guess a file's filename by looking +at the last component of the path. Unfortunately, when linking *directly* to a +file (with no containing directory), the final component is just a CID +(`Qm...`). This isn't exactly user-friendly. + +To work around this issue, you can add a `filename=some_filename` parameter to +your query string to explicitly specify the filename. For example: + +> https://ipfs.io/ipfs/QmfM2r8seH2GiRaC4esTjeraXEachRt8ZsSeGaWTPLyMoG?filename=hello_world.txt + +## MIME-Types + +TODO From b0c480acf5fd89a7d30d3cdac3d8427a16563d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 21 Aug 2018 19:03:30 -0700 Subject: [PATCH 03/13] name cmd: move subcommands to subdirectory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit License: MIT Signed-off-by: Łukasz Magiera --- core/commands/add.go | 3 +- core/commands/bitswap.go | 13 ++-- core/commands/block.go | 7 ++- core/commands/cat.go | 3 +- core/commands/{ => cmdenv}/env.go | 2 +- core/commands/dht.go | 10 ++-- core/commands/dns.go | 7 ++- core/commands/files.go | 5 +- core/commands/filestore.go | 3 +- core/commands/get.go | 9 +-- core/commands/keystore.go | 9 +-- core/commands/mount_unix.go | 2 +- core/commands/{ => name}/ipns.go | 52 ++++++++-------- core/commands/{ => name}/ipnsps.go | 92 +++++++++++++++++------------ core/commands/{ => name}/name.go | 5 +- core/commands/{ => name}/publish.go | 44 ++++++-------- core/commands/object/patch.go | 15 +---- core/commands/p2p.go | 2 +- core/commands/ping.go | 2 +- core/commands/pubsub.go | 17 +++--- core/commands/repo.go | 3 +- core/commands/resolve.go | 13 ++-- core/commands/root.go | 14 +++-- core/commands/shutdown.go | 4 +- core/commands/stat.go | 6 +- core/commands/swarm.go | 18 +++--- core/commands/urlstore.go | 9 +-- 27 files changed, 195 insertions(+), 174 deletions(-) rename core/commands/{ => cmdenv}/env.go (98%) rename core/commands/{ => name}/ipns.go (76%) rename core/commands/{ => name}/ipnsps.go (59%) rename core/commands/{ => name}/name.go (95%) rename core/commands/{ => name}/publish.go (86%) diff --git a/core/commands/add.go b/core/commands/add.go index a0433c043..e3db80864 100644 --- a/core/commands/add.go +++ b/core/commands/add.go @@ -8,6 +8,7 @@ import ( "strings" core "github.com/ipfs/go-ipfs/core" + cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv" "github.com/ipfs/go-ipfs/core/coreunix" filestore "github.com/ipfs/go-ipfs/filestore" ft "gx/ipfs/QmQjEpRiwVvtowhq69dAtB4jhioPVFXiCcWZm9Sfgn7eqc/go-unixfs" @@ -141,7 +142,7 @@ You can now check what blocks have been created by: return nil }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { - n, err := GetNode(env) + n, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return diff --git a/core/commands/bitswap.go b/core/commands/bitswap.go index 8066939f3..f6686f595 100644 --- a/core/commands/bitswap.go +++ b/core/commands/bitswap.go @@ -7,6 +7,7 @@ import ( oldcmds "github.com/ipfs/go-ipfs/commands" lgc "github.com/ipfs/go-ipfs/commands/legacy" + cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv" e "github.com/ipfs/go-ipfs/core/commands/e" bitswap "gx/ipfs/QmTtmrK4iiM3MxWNA3pvbM9ekQiGZAiFyo57GP8B9FFgtz/go-bitswap" decision "gx/ipfs/QmTtmrK4iiM3MxWNA3pvbM9ekQiGZAiFyo57GP8B9FFgtz/go-bitswap/decision" @@ -48,7 +49,7 @@ var unwantCmd = &oldcmds.Command{ } if !nd.OnlineMode() { - res.SetError(errNotOnline, cmdkit.ErrClient) + res.SetError(ErrNotOnline, cmdkit.ErrClient) return } @@ -97,7 +98,7 @@ Print out all blocks currently on the bitswap wantlist for the local peer.`, } if !nd.OnlineMode() { - res.SetError(errNotOnline, cmdkit.ErrClient) + res.SetError(ErrNotOnline, cmdkit.ErrClient) return } @@ -140,14 +141,14 @@ var bitswapStatCmd = &cmds.Command{ }, Type: bitswap.Stat{}, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { - nd, err := GetNode(env) + nd, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } if !nd.OnlineMode() { - res.SetError(errNotOnline, cmdkit.ErrClient) + res.SetError(ErrNotOnline, cmdkit.ErrClient) return } @@ -215,7 +216,7 @@ prints the ledger associated with a given peer. } if !nd.OnlineMode() { - res.SetError(errNotOnline, cmdkit.ErrClient) + res.SetError(ErrNotOnline, cmdkit.ErrClient) return } @@ -272,7 +273,7 @@ Trigger reprovider to announce our data to network. } if !nd.OnlineMode() { - res.SetError(errNotOnline, cmdkit.ErrClient) + res.SetError(ErrNotOnline, cmdkit.ErrClient) return } diff --git a/core/commands/block.go b/core/commands/block.go index bc9acb8db..2e65fef8c 100644 --- a/core/commands/block.go +++ b/core/commands/block.go @@ -10,6 +10,7 @@ import ( "os" util "github.com/ipfs/go-ipfs/blocks/blockstoreutil" + cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv" e "github.com/ipfs/go-ipfs/core/commands/e" "gx/ipfs/QmPTfgFTo9PFr1PvPKyKoeMgBvYPh6cX3aDP7DHKVbnCbi/go-ipfs-cmds" @@ -137,7 +138,7 @@ than 'sha2-256' or format to anything other than 'v0' will result in CIDv1. cmdkit.IntOption("mhlen", "multihash hash length").WithDefault(-1), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { - n, err := GetNode(env) + n, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -248,7 +249,7 @@ func getBlockForKey(ctx context.Context, env cmds.Environment, skey string) (blo return nil, fmt.Errorf("zero length cid invalid") } - n, err := GetNode(env) + n, err := cmdenv.GetNode(env) if err != nil { return nil, err } @@ -282,7 +283,7 @@ It takes a list of base58 encoded multihashes to remove. cmdkit.BoolOption("quiet", "q", "Write minimal output."), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { - n, err := GetNode(env) + n, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return diff --git a/core/commands/cat.go b/core/commands/cat.go index f54f05ea5..559107828 100644 --- a/core/commands/cat.go +++ b/core/commands/cat.go @@ -7,6 +7,7 @@ import ( "os" core "github.com/ipfs/go-ipfs/core" + cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv" coreunix "github.com/ipfs/go-ipfs/core/coreunix" cmds "gx/ipfs/QmPTfgFTo9PFr1PvPKyKoeMgBvYPh6cX3aDP7DHKVbnCbi/go-ipfs-cmds" @@ -29,7 +30,7 @@ var CatCmd = &cmds.Command{ cmdkit.IntOption("length", "l", "Maximum number of bytes to read."), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { - node, err := GetNode(env) + node, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return diff --git a/core/commands/env.go b/core/commands/cmdenv/env.go similarity index 98% rename from core/commands/env.go rename to core/commands/cmdenv/env.go index ce6f8ad48..35ac97453 100644 --- a/core/commands/env.go +++ b/core/commands/cmdenv/env.go @@ -1,4 +1,4 @@ -package commands +package cmdenv import ( "fmt" diff --git a/core/commands/dht.go b/core/commands/dht.go index bcba721b0..85d38b48f 100644 --- a/core/commands/dht.go +++ b/core/commands/dht.go @@ -160,7 +160,7 @@ var findProvidersDhtCmd = &cmds.Command{ } if n.Routing == nil { - res.SetError(errNotOnline, cmdkit.ErrNormal) + res.SetError(ErrNotOnline, cmdkit.ErrNormal) return } @@ -272,7 +272,7 @@ var provideRefDhtCmd = &cmds.Command{ } if n.Routing == nil { - res.SetError(errNotOnline, cmdkit.ErrNormal) + res.SetError(ErrNotOnline, cmdkit.ErrNormal) return } @@ -424,7 +424,7 @@ var findPeerDhtCmd = &cmds.Command{ } if n.Routing == nil { - res.SetError(errNotOnline, cmdkit.ErrNormal) + res.SetError(ErrNotOnline, cmdkit.ErrNormal) return } @@ -529,7 +529,7 @@ Different key types can specify other 'best' rules. } if n.Routing == nil { - res.SetError(errNotOnline, cmdkit.ErrNormal) + res.SetError(ErrNotOnline, cmdkit.ErrNormal) return } @@ -643,7 +643,7 @@ NOTE: A value may not exceed 2048 bytes. } if n.Routing == nil { - res.SetError(errNotOnline, cmdkit.ErrNormal) + res.SetError(ErrNotOnline, cmdkit.ErrNormal) return } diff --git a/core/commands/dns.go b/core/commands/dns.go index 9fb48c430..2353ee7ec 100644 --- a/core/commands/dns.go +++ b/core/commands/dns.go @@ -6,6 +6,7 @@ import ( cmds "github.com/ipfs/go-ipfs/commands" e "github.com/ipfs/go-ipfs/core/commands/e" + ncmd "github.com/ipfs/go-ipfs/core/commands/name" namesys "github.com/ipfs/go-ipfs/namesys" nsopts "github.com/ipfs/go-ipfs/namesys/opts" @@ -72,7 +73,7 @@ The resolver can recursively resolve: res.SetError(err, cmdkit.ErrNormal) return } - res.SetOutput(&ResolvedPath{output}) + res.SetOutput(&ncmd.ResolvedPath{Path: output}) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { @@ -81,12 +82,12 @@ The resolver can recursively resolve: return nil, err } - output, ok := v.(*ResolvedPath) + output, ok := v.(*ncmd.ResolvedPath) if !ok { return nil, e.TypeErr(output, v) } return strings.NewReader(output.Path.String() + "\n"), nil }, }, - Type: ResolvedPath{}, + Type: ncmd.ResolvedPath{}, } diff --git a/core/commands/files.go b/core/commands/files.go index ca96dc8f6..a7745eac7 100644 --- a/core/commands/files.go +++ b/core/commands/files.go @@ -14,6 +14,7 @@ import ( oldcmds "github.com/ipfs/go-ipfs/commands" lgc "github.com/ipfs/go-ipfs/commands/legacy" core "github.com/ipfs/go-ipfs/core" + cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv" e "github.com/ipfs/go-ipfs/core/commands/e" ft "gx/ipfs/QmQjEpRiwVvtowhq69dAtB4jhioPVFXiCcWZm9Sfgn7eqc/go-unixfs" uio "gx/ipfs/QmQjEpRiwVvtowhq69dAtB4jhioPVFXiCcWZm9Sfgn7eqc/go-unixfs/io" @@ -114,7 +115,7 @@ var filesStatCmd = &cmds.Command{ res.SetError(err, cmdkit.ErrClient) } - node, err := GetNode(env) + node, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -745,7 +746,7 @@ stat' on the file or any of its ancestors. return } - nd, err := GetNode(env) + nd, err := cmdenv.GetNode(env) if err != nil { re.SetError(err, cmdkit.ErrNormal) return diff --git a/core/commands/filestore.go b/core/commands/filestore.go index 539505bfc..4eded09c9 100644 --- a/core/commands/filestore.go +++ b/core/commands/filestore.go @@ -9,6 +9,7 @@ import ( oldCmds "github.com/ipfs/go-ipfs/commands" lgc "github.com/ipfs/go-ipfs/commands/legacy" "github.com/ipfs/go-ipfs/core" + cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv" e "github.com/ipfs/go-ipfs/core/commands/e" "github.com/ipfs/go-ipfs/filestore" @@ -231,7 +232,7 @@ var dupsFileStore = &oldCmds.Command{ } func getFilestore(env interface{}) (*core.IpfsNode, *filestore.Filestore, error) { - n, err := GetNode(env) + n, err := cmdenv.GetNode(env) if err != nil { return nil, nil, err } diff --git a/core/commands/get.go b/core/commands/get.go index d59c38e67..f018be2ad 100644 --- a/core/commands/get.go +++ b/core/commands/get.go @@ -10,15 +10,16 @@ import ( "strings" core "github.com/ipfs/go-ipfs/core" + cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv" e "github.com/ipfs/go-ipfs/core/commands/e" - uarchive "gx/ipfs/QmQjEpRiwVvtowhq69dAtB4jhioPVFXiCcWZm9Sfgn7eqc/go-unixfs/archive" - dag "gx/ipfs/QmRiQCJZ91B7VNmLvA6sxzDuBJGSojS3uXHHVuNr3iueNZ/go-merkledag" - path "gx/ipfs/QmdMPBephdLYNESkruDX2hcDTgFYhoCt4LimWhgnomSdV2/go-path" "gx/ipfs/QmPTfgFTo9PFr1PvPKyKoeMgBvYPh6cX3aDP7DHKVbnCbi/go-ipfs-cmds" "gx/ipfs/QmPtj12fdwuAqj9sBSTNUxBNu8kCGNp8b3o8yUzMm5GHpq/pb" tar "gx/ipfs/QmQine7gvHncNevKtG9QXxf3nXcwSj6aDDmMm52mHofEEp/tar-utils" + uarchive "gx/ipfs/QmQjEpRiwVvtowhq69dAtB4jhioPVFXiCcWZm9Sfgn7eqc/go-unixfs/archive" + dag "gx/ipfs/QmRiQCJZ91B7VNmLvA6sxzDuBJGSojS3uXHHVuNr3iueNZ/go-merkledag" "gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit" + path "gx/ipfs/QmdMPBephdLYNESkruDX2hcDTgFYhoCt4LimWhgnomSdV2/go-path" ) var ErrInvalidCompressionLevel = errors.New("compression level must be between 1 and 9") @@ -59,7 +60,7 @@ may also specify the level of compression by specifying '-l=<1-9>'. return } - node, err := GetNode(env) + node, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return diff --git a/core/commands/keystore.go b/core/commands/keystore.go index 9b10450ea..ab00a31ff 100644 --- a/core/commands/keystore.go +++ b/core/commands/keystore.go @@ -5,6 +5,7 @@ import ( "io" "text/tabwriter" + cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv" "github.com/ipfs/go-ipfs/core/commands/e" "github.com/ipfs/go-ipfs/core/coreapi/interface/options" @@ -66,7 +67,7 @@ var keyGenCmd = &cmds.Command{ cmdkit.StringArg("name", true, false, "name of key to create"), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { - api, err := GetApi(env) + api, err := cmdenv.GetApi(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -125,7 +126,7 @@ var keyListCmd = &cmds.Command{ cmdkit.BoolOption("l", "Show extra information about keys."), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { - api, err := GetApi(env) + api, err := cmdenv.GetApi(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -163,7 +164,7 @@ var keyRenameCmd = &cmds.Command{ cmdkit.BoolOption("force", "f", "Allow to overwrite an existing key."), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { - api, err := GetApi(env) + api, err := cmdenv.GetApi(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -215,7 +216,7 @@ var keyRmCmd = &cmds.Command{ cmdkit.BoolOption("l", "Show extra information about keys."), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { - api, err := GetApi(env) + api, err := cmdenv.GetApi(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return diff --git a/core/commands/mount_unix.go b/core/commands/mount_unix.go index b7e427a3f..97120e1e9 100644 --- a/core/commands/mount_unix.go +++ b/core/commands/mount_unix.go @@ -91,7 +91,7 @@ baz // error if we aren't running node in online mode if node.LocalMode() { - res.SetError(errNotOnline, cmdkit.ErrClient) + res.SetError(ErrNotOnline, cmdkit.ErrClient) return } diff --git a/core/commands/ipns.go b/core/commands/name/ipns.go similarity index 76% rename from core/commands/ipns.go rename to core/commands/name/ipns.go index cc1a248b3..36ab6067b 100644 --- a/core/commands/ipns.go +++ b/core/commands/name/ipns.go @@ -1,4 +1,4 @@ -package commands +package name import ( "errors" @@ -6,15 +6,25 @@ import ( "strings" "time" - cmds "github.com/ipfs/go-ipfs/commands" + cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv" e "github.com/ipfs/go-ipfs/core/commands/e" namesys "github.com/ipfs/go-ipfs/namesys" nsopts "github.com/ipfs/go-ipfs/namesys/opts" + cmds "gx/ipfs/QmPTfgFTo9PFr1PvPKyKoeMgBvYPh6cX3aDP7DHKVbnCbi/go-ipfs-cmds" + logging "gx/ipfs/QmRREK2CAZ5Re2Bd9zZFG6FeYDppUWt5cMgsoUEp3ktgSr/go-log" "gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit" offline "gx/ipfs/Qmd45r5jHr1PKMNQqifnbZy1ZQwHdtXUDJFamUEvUJE544/go-ipfs-routing/offline" + path "gx/ipfs/QmdMPBephdLYNESkruDX2hcDTgFYhoCt4LimWhgnomSdV2/go-path" + "fmt" ) +var log = logging.Logger("core/commands/ipns") + +type ResolvedPath struct { + Path path.Path +} + var IpnsCmd = &cmds.Command{ Helptext: cmdkit.HelpText{ Tagline: "Resolve IPNS names.", @@ -62,8 +72,8 @@ Resolve the value of a dnslink: cmdkit.UintOption("dht-record-count", "dhtrc", "Number of records to request for DHT resolution."), cmdkit.StringOption("dht-timeout", "dhtt", "Max time to collect values during DHT resolution eg \"30s\". Pass 0 for no timeout."), }, - Run: func(req cmds.Request, res cmds.Response) { - n, err := req.InvocContext().GetNode() + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { + n, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -77,8 +87,8 @@ Resolve the value of a dnslink: } } - nocache, _, _ := req.Option("nocache").Bool() - local, _, _ := req.Option("local").Bool() + nocache, _ := req.Options["nocache"].(bool) + local, _ := req.Options["local"].(bool) // default to nodes namesys resolver var resolver namesys.Resolver = n.Namesys @@ -98,7 +108,7 @@ Resolve the value of a dnslink: } var name string - if len(req.Arguments()) == 0 { + if len(req.Arguments) == 0 { if n.Identity == "" { res.SetError(errors.New("identity not loaded"), cmdkit.ErrNormal) return @@ -106,12 +116,12 @@ Resolve the value of a dnslink: name = n.Identity.Pretty() } else { - name = req.Arguments()[0] + name = req.Arguments[0] } - recursive, _, _ := req.Option("recursive").Bool() - rc, rcok, _ := req.Option("dht-record-count").Int() - dhtt, dhttok, _ := req.Option("dht-timeout").String() + recursive, _ := req.Options["recursive"].(bool) + rc, rcok := req.Options["dht-record-count"].(int) + dhtt, dhttok := req.Options["dht-timeout"].(string) var ropts []nsopts.ResolveOpt if !recursive { ropts = append(ropts, nsopts.Depth(1)) @@ -136,7 +146,7 @@ Resolve the value of a dnslink: name = "/ipns/" + name } - output, err := resolver.Resolve(req.Context(), name, ropts...) + output, err := resolver.Resolve(req.Context, name, ropts...) if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -144,21 +154,17 @@ Resolve the value of a dnslink: // TODO: better errors (in the case of not finding the name, we get "failed to find any peer in table") - res.SetOutput(&ResolvedPath{output}) + cmds.EmitOnce(res, &ResolvedPath{output}) }, - Marshalers: cmds.MarshalerMap{ - cmds.Text: func(res cmds.Response) (io.Reader, error) { - v, err := unwrapOutput(res.Output()) - if err != nil { - return nil, err - } - + Encoders: cmds.EncoderMap{ + cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, v interface{}) error { output, ok := v.(*ResolvedPath) if !ok { - return nil, e.TypeErr(output, v) + return e.TypeErr(output, v) } - return strings.NewReader(output.Path.String() + "\n"), nil - }, + _, err := fmt.Fprintln(w, []byte(output.Path.String())) + return err + }), }, Type: ResolvedPath{}, } diff --git a/core/commands/ipnsps.go b/core/commands/name/ipnsps.go similarity index 59% rename from core/commands/ipnsps.go rename to core/commands/name/ipnsps.go index ac0e34479..88871ca23 100644 --- a/core/commands/ipnsps.go +++ b/core/commands/name/ipnsps.go @@ -1,16 +1,18 @@ -package commands +package name import ( "errors" "io" "strings" - cmds "github.com/ipfs/go-ipfs/commands" - e "github.com/ipfs/go-ipfs/core/commands/e" + "github.com/ipfs/go-ipfs/core/commands/cmdenv" + "github.com/ipfs/go-ipfs/core/commands/e" - peer "gx/ipfs/QmQsErDt8Qgw1XrsXf2BpEzDgGWtB1YLsTAARBup5b6B9W/go-libp2p-peer" - cmdkit "gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit" - record "gx/ipfs/QmdHb9aBELnQKTVhvvA3hsQbRgUAwsWUzBP2vZ6Y5FBYvE/go-libp2p-record" + "gx/ipfs/QmPTfgFTo9PFr1PvPKyKoeMgBvYPh6cX3aDP7DHKVbnCbi/go-ipfs-cmds" + "gx/ipfs/QmQsErDt8Qgw1XrsXf2BpEzDgGWtB1YLsTAARBup5b6B9W/go-libp2p-peer" + "gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit" + "gx/ipfs/QmdHb9aBELnQKTVhvvA3hsQbRgUAwsWUzBP2vZ6Y5FBYvE/go-libp2p-record" + "fmt" ) type ipnsPubsubState struct { @@ -21,6 +23,10 @@ type ipnsPubsubCancel struct { Canceled bool } +type stringList struct { + Strings []string +} + // IpnsPubsubCmd is the subcommand that allows us to manage the IPNS pubsub system var IpnsPubsubCmd = &cmds.Command{ Helptext: cmdkit.HelpText{ @@ -42,26 +48,21 @@ var ipnspsStateCmd = &cmds.Command{ Helptext: cmdkit.HelpText{ Tagline: "Query the state of IPNS pubsub", }, - Run: func(req cmds.Request, res cmds.Response) { - n, err := req.InvocContext().GetNode() + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { + n, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return } - res.SetOutput(&ipnsPubsubState{n.PSRouter != nil}) + cmds.EmitOnce(res, &ipnsPubsubState{n.PSRouter != nil}) }, Type: ipnsPubsubState{}, - Marshalers: cmds.MarshalerMap{ - cmds.Text: func(res cmds.Response) (io.Reader, error) { - v, err := unwrapOutput(res.Output()) - if err != nil { - return nil, err - } - + Encoders: cmds.EncoderMap{ + cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, v interface{}) error { output, ok := v.(*ipnsPubsubState) if !ok { - return nil, e.TypeErr(output, v) + return e.TypeErr(output, v) } var state string @@ -71,8 +72,9 @@ var ipnspsStateCmd = &cmds.Command{ state = "disabled" } - return strings.NewReader(state + "\n"), nil - }, + _, err := fmt.Fprintln(w, []byte(state)) + return err + }), }, } @@ -80,8 +82,8 @@ var ipnspsSubsCmd = &cmds.Command{ Helptext: cmdkit.HelpText{ Tagline: "Show current name subscriptions", }, - Run: func(req cmds.Request, res cmds.Response) { - n, err := req.InvocContext().GetNode() + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { + n, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -106,11 +108,11 @@ var ipnspsSubsCmd = &cmds.Command{ paths = append(paths, "/ipns/"+peer.IDB58Encode(pid)) } - res.SetOutput(&stringList{paths}) + cmds.EmitOnce(res, &stringList{paths}) }, Type: stringList{}, - Marshalers: cmds.MarshalerMap{ - cmds.Text: stringListMarshaler, + Encoders: cmds.EncoderMap{ + cmds.Text: stringListMarshaler(), }, } @@ -118,8 +120,8 @@ var ipnspsCancelCmd = &cmds.Command{ Helptext: cmdkit.HelpText{ Tagline: "Cancel a name subscription", }, - Run: func(req cmds.Request, res cmds.Response) { - n, err := req.InvocContext().GetNode() + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { + n, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -130,7 +132,7 @@ var ipnspsCancelCmd = &cmds.Command{ return } - name := req.Arguments()[0] + name := req.Arguments[0] name = strings.TrimPrefix(name, "/ipns/") pid, err := peer.IDB58Decode(name) if err != nil { @@ -139,22 +141,17 @@ var ipnspsCancelCmd = &cmds.Command{ } ok := n.PSRouter.Cancel("/ipns/" + string(pid)) - res.SetOutput(&ipnsPubsubCancel{ok}) + cmds.EmitOnce(res, &ipnsPubsubCancel{ok}) }, Arguments: []cmdkit.Argument{ cmdkit.StringArg("name", true, false, "Name to cancel the subscription for."), }, Type: ipnsPubsubCancel{}, - Marshalers: cmds.MarshalerMap{ - cmds.Text: func(res cmds.Response) (io.Reader, error) { - v, err := unwrapOutput(res.Output()) - if err != nil { - return nil, err - } - + Encoders: cmds.EncoderMap{ + cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, v interface{}) error { output, ok := v.(*ipnsPubsubCancel) if !ok { - return nil, e.TypeErr(output, v) + return e.TypeErr(output, v) } var state string @@ -164,7 +161,26 @@ var ipnspsCancelCmd = &cmds.Command{ state = "no subscription" } - return strings.NewReader(state + "\n"), nil - }, + _, err := fmt.Fprintln(w, []byte(state)) + return err + }), }, } + +func stringListMarshaler() cmds.EncoderFunc { + return cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, v interface{}) error { + list, ok := v.(*stringList) + if !ok { + return e.TypeErr(list, v) + } + + for _, s := range list.Strings { + _, err := fmt.Fprintln(w, []byte(s)) + if err != nil { + return err + } + } + + return nil + }) +} diff --git a/core/commands/name.go b/core/commands/name/name.go similarity index 95% rename from core/commands/name.go rename to core/commands/name/name.go index 3efd49a87..aed92249a 100644 --- a/core/commands/name.go +++ b/core/commands/name/name.go @@ -1,8 +1,7 @@ -package commands +package name import ( - cmds "github.com/ipfs/go-ipfs/commands" - + "gx/ipfs/QmPTfgFTo9PFr1PvPKyKoeMgBvYPh6cX3aDP7DHKVbnCbi/go-ipfs-cmds" "gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit" ) diff --git a/core/commands/publish.go b/core/commands/name/publish.go similarity index 86% rename from core/commands/publish.go rename to core/commands/name/publish.go index d29604055..1814f2591 100644 --- a/core/commands/publish.go +++ b/core/commands/name/publish.go @@ -1,26 +1,24 @@ -package commands +package name import ( "context" "errors" "fmt" "io" - "strings" "time" - cmds "github.com/ipfs/go-ipfs/commands" core "github.com/ipfs/go-ipfs/core" + cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv" e "github.com/ipfs/go-ipfs/core/commands/e" keystore "github.com/ipfs/go-ipfs/keystore" - path "gx/ipfs/QmdMPBephdLYNESkruDX2hcDTgFYhoCt4LimWhgnomSdV2/go-path" + "gx/ipfs/QmPTfgFTo9PFr1PvPKyKoeMgBvYPh6cX3aDP7DHKVbnCbi/go-ipfs-cmds" crypto "gx/ipfs/QmPvyPwuCgJ7pDmrKDxRtsScJgBaM5h4EpRL2qQJsmXf4n/go-libp2p-crypto" peer "gx/ipfs/QmQsErDt8Qgw1XrsXf2BpEzDgGWtB1YLsTAARBup5b6B9W/go-libp2p-peer" "gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit" + path "gx/ipfs/QmdMPBephdLYNESkruDX2hcDTgFYhoCt4LimWhgnomSdV2/go-path" ) -var errNotOnline = errors.New("this command must be run in online mode. Try running 'ipfs daemon' first") - var PublishCmd = &cmds.Command{ Helptext: cmdkit.HelpText{ Tagline: "Publish IPNS names.", @@ -73,8 +71,8 @@ Alternatively, publish an using a valid PeerID (as listed by cmdkit.StringOption("ttl", "Time duration this record should be cached for (caution: experimental)."), cmdkit.StringOption("key", "k", "Name of the key to be used or a valid PeerID, as listed by 'ipfs key list -l'. Default: <>.").WithDefault("self"), }, - Run: func(req cmds.Request, res cmds.Response) { - n, err := req.InvocContext().GetNode() + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { + n, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -93,7 +91,7 @@ Alternatively, publish an using a valid PeerID (as listed by return } - pstr := req.Arguments()[0] + pstr := req.Arguments[0] if n.Identity == "" { res.SetError(errors.New("identity not loaded"), cmdkit.ErrNormal) @@ -102,9 +100,9 @@ Alternatively, publish an using a valid PeerID (as listed by popts := new(publishOpts) - popts.verifyExists, _, _ = req.Option("resolve").Bool() + popts.verifyExists, _ = req.Options["resolve"].(bool) - validtime, _, _ := req.Option("lifetime").String() + validtime, _ := req.Options["lifetime"].(string) d, err := time.ParseDuration(validtime) if err != nil { res.SetError(fmt.Errorf("error parsing lifetime option: %s", err), cmdkit.ErrNormal) @@ -113,8 +111,8 @@ Alternatively, publish an using a valid PeerID (as listed by popts.pubValidTime = d - ctx := req.Context() - if ttl, found, _ := req.Option("ttl").String(); found { + ctx := req.Context + if ttl, found := req.Options["ttl"].(string); found { d, err := time.ParseDuration(ttl) if err != nil { res.SetError(err, cmdkit.ErrNormal) @@ -124,7 +122,7 @@ Alternatively, publish an using a valid PeerID (as listed by ctx = context.WithValue(ctx, "ipns-publish-ttl", d) } - kname, _, _ := req.Option("key").String() + kname, _ := req.Options["key"].(string) k, err := keylookup(n, kname) if err != nil { res.SetError(err, cmdkit.ErrNormal) @@ -142,22 +140,18 @@ Alternatively, publish an using a valid PeerID (as listed by res.SetError(err, cmdkit.ErrNormal) return } - res.SetOutput(output) + cmds.EmitOnce(res, output) }, - Marshalers: cmds.MarshalerMap{ - cmds.Text: func(res cmds.Response) (io.Reader, error) { - v, err := unwrapOutput(res.Output()) - if err != nil { - return nil, err - } + Encoders: cmds.EncoderMap{ + cmds.Text: cmds.MakeEncoder(func(req *cmds.Request, w io.Writer, v interface{}) error { entry, ok := v.(*IpnsEntry) if !ok { - return nil, e.TypeErr(entry, v) + return e.TypeErr(entry, v) } - s := fmt.Sprintf("Published to %s: %s\n", entry.Name, entry.Value) - return strings.NewReader(s), nil - }, + _, err := fmt.Fprintf(w, "Published to %s: %s\n", entry.Name, entry.Value) + return err + }), }, Type: IpnsEntry{}, } diff --git a/core/commands/object/patch.go b/core/commands/object/patch.go index dcffc8f05..024ab0aec 100644 --- a/core/commands/object/patch.go +++ b/core/commands/object/patch.go @@ -7,6 +7,7 @@ import ( oldcmds "github.com/ipfs/go-ipfs/commands" lgc "github.com/ipfs/go-ipfs/commands/legacy" + cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv" e "github.com/ipfs/go-ipfs/core/commands/e" coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" "github.com/ipfs/go-ipfs/core/coreapi/interface/options" @@ -67,7 +68,7 @@ the limit will not be respected by the network. cmdkit.FileArg("data", true, false, "Data to append.").EnableStdin(), }, Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) { - api, err := GetApi(env) + api, err := cmdenv.GetApi(env) if err != nil { re.SetError(err, cmdkit.ErrNormal) return @@ -250,15 +251,3 @@ to a file containing 'bar', and returns the hash of the new object. oldcmds.Text: objectMarshaler, }, } - -// TODO: fix import loop with core/commands so we don't need that -// COPIED FROM ONE LEVEL UP -// GetApi extracts CoreAPI instance from the environment. -func GetApi(env cmds.Environment) (coreiface.CoreAPI, error) { - ctx, ok := env.(*oldcmds.Context) - if !ok { - return nil, fmt.Errorf("expected env to be of type %T, got %T", ctx, env) - } - - return ctx.GetApi() -} diff --git a/core/commands/p2p.go b/core/commands/p2p.go index 2979a1cc4..ca852fafc 100644 --- a/core/commands/p2p.go +++ b/core/commands/p2p.go @@ -405,7 +405,7 @@ func getNode(req cmds.Request) (*core.IpfsNode, error) { } if !n.OnlineMode() { - return nil, errNotOnline + return nil, ErrNotOnline } return n, nil diff --git a/core/commands/ping.go b/core/commands/ping.go index 52dfca55d..2f4d078fd 100644 --- a/core/commands/ping.go +++ b/core/commands/ping.go @@ -78,7 +78,7 @@ trip latency information. // Must be online! if !n.OnlineMode() { - res.SetError(errNotOnline, cmdkit.ErrClient) + res.SetError(ErrNotOnline, cmdkit.ErrClient) return } diff --git a/core/commands/pubsub.go b/core/commands/pubsub.go index 846549eae..83882b4e2 100644 --- a/core/commands/pubsub.go +++ b/core/commands/pubsub.go @@ -11,6 +11,7 @@ import ( "time" core "github.com/ipfs/go-ipfs/core" + cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv" e "github.com/ipfs/go-ipfs/core/commands/e" cmds "gx/ipfs/QmPTfgFTo9PFr1PvPKyKoeMgBvYPh6cX3aDP7DHKVbnCbi/go-ipfs-cmds" @@ -73,7 +74,7 @@ This command outputs data in the following encodings: cmdkit.BoolOption("discover", "try to discover other peers subscribed to the same topic"), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { - n, err := GetNode(env) + n, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -81,7 +82,7 @@ This command outputs data in the following encodings: // Must be online! if !n.OnlineMode() { - res.SetError(errNotOnline, cmdkit.ErrClient) + res.SetError(ErrNotOnline, cmdkit.ErrClient) return } @@ -206,7 +207,7 @@ To use, the daemon must be run with '--enable-pubsub-experiment'. cmdkit.StringArg("data", true, true, "Payload of message to publish.").EnableStdin(), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { - n, err := GetNode(env) + n, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -214,7 +215,7 @@ To use, the daemon must be run with '--enable-pubsub-experiment'. // Must be online! if !n.OnlineMode() { - res.SetError(errNotOnline, cmdkit.ErrClient) + res.SetError(ErrNotOnline, cmdkit.ErrClient) return } @@ -253,7 +254,7 @@ To use, the daemon must be run with '--enable-pubsub-experiment'. `, }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { - n, err := GetNode(env) + n, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -261,7 +262,7 @@ To use, the daemon must be run with '--enable-pubsub-experiment'. // Must be online! if !n.OnlineMode() { - res.SetError(errNotOnline, cmdkit.ErrClient) + res.SetError(ErrNotOnline, cmdkit.ErrClient) return } @@ -310,7 +311,7 @@ To use, the daemon must be run with '--enable-pubsub-experiment'. cmdkit.StringArg("topic", false, false, "topic to list connected peers of"), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { - n, err := GetNode(env) + n, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -318,7 +319,7 @@ To use, the daemon must be run with '--enable-pubsub-experiment'. // Must be online! if !n.OnlineMode() { - res.SetError(errNotOnline, cmdkit.ErrClient) + res.SetError(ErrNotOnline, cmdkit.ErrClient) return } diff --git a/core/commands/repo.go b/core/commands/repo.go index 18b960f2d..844973fcd 100644 --- a/core/commands/repo.go +++ b/core/commands/repo.go @@ -11,6 +11,7 @@ import ( oldcmds "github.com/ipfs/go-ipfs/commands" lgc "github.com/ipfs/go-ipfs/commands/legacy" + cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv" e "github.com/ipfs/go-ipfs/core/commands/e" corerepo "github.com/ipfs/go-ipfs/core/corerepo" fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo" @@ -165,7 +166,7 @@ Version string The repo version. cmdkit.BoolOption("human", "Output sizes in MiB."), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { - n, err := GetNode(env) + n, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return diff --git a/core/commands/resolve.go b/core/commands/resolve.go index 32cafe5d8..a1fd265df 100644 --- a/core/commands/resolve.go +++ b/core/commands/resolve.go @@ -9,6 +9,7 @@ import ( cmds "github.com/ipfs/go-ipfs/commands" "github.com/ipfs/go-ipfs/core" e "github.com/ipfs/go-ipfs/core/commands/e" + ncmd "github.com/ipfs/go-ipfs/core/commands/name" ns "github.com/ipfs/go-ipfs/namesys" nsopts "github.com/ipfs/go-ipfs/namesys/opts" path "gx/ipfs/QmdMPBephdLYNESkruDX2hcDTgFYhoCt4LimWhgnomSdV2/go-path" @@ -16,10 +17,6 @@ import ( "gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit" ) -type ResolvedPath struct { - Path path.Path -} - var ResolveCmd = &cmds.Command{ Helptext: cmdkit.HelpText{ Tagline: "Resolve the value of names to IPFS.", @@ -113,7 +110,7 @@ Resolve the value of an IPFS DAG path: res.SetError(err, cmdkit.ErrNormal) return } - res.SetOutput(&ResolvedPath{p}) + res.SetOutput(&ncmd.ResolvedPath{Path: p}) return } @@ -132,7 +129,7 @@ Resolve the value of an IPFS DAG path: c := node.Cid() - res.SetOutput(&ResolvedPath{path.FromCid(c)}) + res.SetOutput(&ncmd.ResolvedPath{Path: path.FromCid(c)}) }, Marshalers: cmds.MarshalerMap{ cmds.Text: func(res cmds.Response) (io.Reader, error) { @@ -141,12 +138,12 @@ Resolve the value of an IPFS DAG path: return nil, err } - output, ok := v.(*ResolvedPath) + output, ok := v.(*ncmd.ResolvedPath) if !ok { return nil, e.TypeErr(output, v) } return strings.NewReader(output.Path.String() + "\n"), nil }, }, - Type: ResolvedPath{}, + Type: ncmd.ResolvedPath{}, } diff --git a/core/commands/root.go b/core/commands/root.go index 6be434198..9ed34a7f5 100644 --- a/core/commands/root.go +++ b/core/commands/root.go @@ -1,6 +1,7 @@ package commands import ( + "errors" "io" "strings" @@ -8,6 +9,7 @@ import ( lgc "github.com/ipfs/go-ipfs/commands/legacy" dag "github.com/ipfs/go-ipfs/core/commands/dag" e "github.com/ipfs/go-ipfs/core/commands/e" + name "github.com/ipfs/go-ipfs/core/commands/name" ocmd "github.com/ipfs/go-ipfs/core/commands/object" unixfs "github.com/ipfs/go-ipfs/core/commands/unixfs" @@ -18,6 +20,8 @@ import ( var log = logging.Logger("core/commands") +var ErrNotOnline = errors.New("this command must be run in online mode. Try running 'ipfs daemon' first") + const ( ApiOption = "api" ) @@ -125,7 +129,7 @@ var rootSubcommands = map[string]*cmds.Command{ "log": lgc.NewCommand(LogCmd), "ls": lgc.NewCommand(LsCmd), "mount": lgc.NewCommand(MountCmd), - "name": lgc.NewCommand(NameCmd), + "name": name.NameCmd, "object": ocmd.ObjectCmd, "pin": lgc.NewCommand(PinCmd), "ping": lgc.NewCommand(PingCmd), @@ -160,11 +164,11 @@ var rootROSubcommands = map[string]*cmds.Command{ "get": GetCmd, "dns": lgc.NewCommand(DNSCmd), "ls": lgc.NewCommand(LsCmd), - "name": lgc.NewCommand(&oldcmds.Command{ - Subcommands: map[string]*oldcmds.Command{ - "resolve": IpnsCmd, + "name": &cmds.Command{ + Subcommands: map[string]*cmds.Command{ + "resolve": name.IpnsCmd, }, - }), + }, "object": lgc.NewCommand(&oldcmds.Command{ Subcommands: map[string]*oldcmds.Command{ "data": ocmd.ObjectDataCmd, diff --git a/core/commands/shutdown.go b/core/commands/shutdown.go index c7e84584e..404f24b96 100644 --- a/core/commands/shutdown.go +++ b/core/commands/shutdown.go @@ -3,6 +3,8 @@ package commands import ( "fmt" + cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv" + cmds "gx/ipfs/QmPTfgFTo9PFr1PvPKyKoeMgBvYPh6cX3aDP7DHKVbnCbi/go-ipfs-cmds" "gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit" ) @@ -12,7 +14,7 @@ var daemonShutdownCmd = &cmds.Command{ Tagline: "Shut down the ipfs daemon", }, Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) { - nd, err := GetNode(env) + nd, err := cmdenv.GetNode(env) if err != nil { re.SetError(err, cmdkit.ErrNormal) return diff --git a/core/commands/stat.go b/core/commands/stat.go index efbd19428..5e5d3a6f0 100644 --- a/core/commands/stat.go +++ b/core/commands/stat.go @@ -7,6 +7,8 @@ import ( "os" "time" + cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv" + humanize "gx/ipfs/QmPSBJL4momYnE7DcUyk2DVhD6rH488ZmHBGLbxNdhU44K/go-humanize" cmds "gx/ipfs/QmPTfgFTo9PFr1PvPKyKoeMgBvYPh6cX3aDP7DHKVbnCbi/go-ipfs-cmds" peer "gx/ipfs/QmQsErDt8Qgw1XrsXf2BpEzDgGWtB1YLsTAARBup5b6B9W/go-libp2p-peer" @@ -80,7 +82,7 @@ Example: }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { - nd, err := GetNode(env) + nd, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return @@ -88,7 +90,7 @@ Example: // Must be online! if !nd.OnlineMode() { - res.SetError(errNotOnline, cmdkit.ErrClient) + res.SetError(ErrNotOnline, cmdkit.ErrClient) return } diff --git a/core/commands/swarm.go b/core/commands/swarm.go index b7cb9e568..d69c94b4e 100644 --- a/core/commands/swarm.go +++ b/core/commands/swarm.go @@ -72,7 +72,7 @@ var swarmPeersCmd = &cmds.Command{ } if n.PeerHost == nil { - res.SetError(errNotOnline, cmdkit.ErrClient) + res.SetError(ErrNotOnline, cmdkit.ErrClient) return } @@ -223,7 +223,7 @@ var swarmAddrsCmd = &cmds.Command{ } if n.PeerHost == nil { - res.SetError(errNotOnline, cmdkit.ErrClient) + res.SetError(ErrNotOnline, cmdkit.ErrClient) return } @@ -291,7 +291,7 @@ var swarmAddrsLocalCmd = &cmds.Command{ } if n.PeerHost == nil { - res.SetError(errNotOnline, cmdkit.ErrClient) + res.SetError(ErrNotOnline, cmdkit.ErrClient) return } @@ -331,7 +331,7 @@ var swarmAddrsListenCmd = &cmds.Command{ } if n.PeerHost == nil { - res.SetError(errNotOnline, cmdkit.ErrClient) + res.SetError(ErrNotOnline, cmdkit.ErrClient) return } @@ -381,7 +381,7 @@ ipfs swarm connect /ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3 addrs := req.Arguments() if n.PeerHost == nil { - res.SetError(errNotOnline, cmdkit.ErrClient) + res.SetError(ErrNotOnline, cmdkit.ErrClient) return } @@ -446,7 +446,7 @@ it will reconnect. addrs := req.Arguments() if n.PeerHost == nil { - res.SetError(errNotOnline, cmdkit.ErrClient) + res.SetError(ErrNotOnline, cmdkit.ErrClient) return } @@ -593,7 +593,7 @@ Filters default to those specified under the "Swarm.AddrFilters" config key. } if n.PeerHost == nil { - res.SetError(errNotOnline, cmdkit.ErrNormal) + res.SetError(ErrNotOnline, cmdkit.ErrNormal) return } @@ -641,7 +641,7 @@ add your filters to the ipfs config file. } if n.PeerHost == nil { - res.SetError(errNotOnline, cmdkit.ErrNormal) + res.SetError(ErrNotOnline, cmdkit.ErrNormal) return } @@ -714,7 +714,7 @@ remove your filters from the ipfs config file. } if n.PeerHost == nil { - res.SetError(errNotOnline, cmdkit.ErrNormal) + res.SetError(ErrNotOnline, cmdkit.ErrNormal) return } diff --git a/core/commands/urlstore.go b/core/commands/urlstore.go index f5e9ed290..4b3dd9f3d 100644 --- a/core/commands/urlstore.go +++ b/core/commands/urlstore.go @@ -5,13 +5,14 @@ import ( "io" "net/http" + cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv" filestore "github.com/ipfs/go-ipfs/filestore" - balanced "gx/ipfs/QmQjEpRiwVvtowhq69dAtB4jhioPVFXiCcWZm9Sfgn7eqc/go-unixfs/importer/balanced" - ihelper "gx/ipfs/QmQjEpRiwVvtowhq69dAtB4jhioPVFXiCcWZm9Sfgn7eqc/go-unixfs/importer/helpers" - trickle "gx/ipfs/QmQjEpRiwVvtowhq69dAtB4jhioPVFXiCcWZm9Sfgn7eqc/go-unixfs/importer/trickle" cmds "gx/ipfs/QmPTfgFTo9PFr1PvPKyKoeMgBvYPh6cX3aDP7DHKVbnCbi/go-ipfs-cmds" mh "gx/ipfs/QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8/go-multihash" + balanced "gx/ipfs/QmQjEpRiwVvtowhq69dAtB4jhioPVFXiCcWZm9Sfgn7eqc/go-unixfs/importer/balanced" + ihelper "gx/ipfs/QmQjEpRiwVvtowhq69dAtB4jhioPVFXiCcWZm9Sfgn7eqc/go-unixfs/importer/helpers" + trickle "gx/ipfs/QmQjEpRiwVvtowhq69dAtB4jhioPVFXiCcWZm9Sfgn7eqc/go-unixfs/importer/trickle" cmdkit "gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit" chunk "gx/ipfs/QmXzBbJo2sLf3uwjNTeoWYiJV7CjAhkiA4twtLvwJSSNdK/go-ipfs-chunker" cid "gx/ipfs/QmZFbDTY9jfSBms2MchvYM9oYRbAF19K7Pby47yDBfpPrb/go-cid" @@ -53,7 +54,7 @@ time. Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { url := req.Arguments[0] - n, err := GetNode(env) + n, err := cmdenv.GetNode(env) if err != nil { res.SetError(err, cmdkit.ErrNormal) return From faf7aac940251f4538fe1cc71ab0db10ed43cef5 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 22 Aug 2018 09:27:26 -0700 Subject: [PATCH 04/13] cmds: go fmt and minor cleanup License: MIT Signed-off-by: Steven Allen --- core/commands/name/ipns.go | 4 ++-- core/commands/name/ipnsps.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/commands/name/ipns.go b/core/commands/name/ipns.go index 36ab6067b..3dc9fd884 100644 --- a/core/commands/name/ipns.go +++ b/core/commands/name/ipns.go @@ -2,6 +2,7 @@ package name import ( "errors" + "fmt" "io" "strings" "time" @@ -16,7 +17,6 @@ import ( "gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit" offline "gx/ipfs/Qmd45r5jHr1PKMNQqifnbZy1ZQwHdtXUDJFamUEvUJE544/go-ipfs-routing/offline" path "gx/ipfs/QmdMPBephdLYNESkruDX2hcDTgFYhoCt4LimWhgnomSdV2/go-path" - "fmt" ) var log = logging.Logger("core/commands/ipns") @@ -162,7 +162,7 @@ Resolve the value of a dnslink: if !ok { return e.TypeErr(output, v) } - _, err := fmt.Fprintln(w, []byte(output.Path.String())) + _, err := fmt.Fprintln(w, output.Path) return err }), }, diff --git a/core/commands/name/ipnsps.go b/core/commands/name/ipnsps.go index 88871ca23..92ccce979 100644 --- a/core/commands/name/ipnsps.go +++ b/core/commands/name/ipnsps.go @@ -2,6 +2,7 @@ package name import ( "errors" + "fmt" "io" "strings" @@ -12,7 +13,6 @@ import ( "gx/ipfs/QmQsErDt8Qgw1XrsXf2BpEzDgGWtB1YLsTAARBup5b6B9W/go-libp2p-peer" "gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit" "gx/ipfs/QmdHb9aBELnQKTVhvvA3hsQbRgUAwsWUzBP2vZ6Y5FBYvE/go-libp2p-record" - "fmt" ) type ipnsPubsubState struct { @@ -72,7 +72,7 @@ var ipnspsStateCmd = &cmds.Command{ state = "disabled" } - _, err := fmt.Fprintln(w, []byte(state)) + _, err := fmt.Fprintln(w, state) return err }), }, @@ -161,7 +161,7 @@ var ipnspsCancelCmd = &cmds.Command{ state = "no subscription" } - _, err := fmt.Fprintln(w, []byte(state)) + _, err := fmt.Fprintln(w, state) return err }), }, @@ -175,7 +175,7 @@ func stringListMarshaler() cmds.EncoderFunc { } for _, s := range list.Strings { - _, err := fmt.Fprintln(w, []byte(s)) + _, err := fmt.Fprintln(w, s) if err != nil { return err } From dae6189bd8db7e9bb9db6efe0efc0379c6cc9fc8 Mon Sep 17 00:00:00 2001 From: David Dias Date: Wed, 22 Aug 2018 20:03:13 +0200 Subject: [PATCH 05/13] docs: README refresh, add cli-http-api-core diagram License: MIT Signed-off-by: diasdavid --- README.md | 132 ++++++++++++++++++----------- dev.md | 62 -------------- docs/cli-http-api-core-diagram.png | Bin 0 -> 55031 bytes 3 files changed, 82 insertions(+), 112 deletions(-) delete mode 100644 dev.md create mode 100644 docs/cli-http-api-core-diagram.png diff --git a/README.md b/README.md index 4f8ffe690..8404145a8 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,6 @@ [![GoDoc](https://godoc.org/github.com/ipfs/go-ipfs?status.svg)](https://godoc.org/github.com/ipfs/go-ipfs) [![Build Status](https://travis-ci.org/ipfs/go-ipfs.svg?branch=master)](https://travis-ci.org/ipfs/go-ipfs) -> IPFS implementation in Go - ## Project Status [![Throughput Graph](https://graphs.waffle.io/ipfs/go-ipfs/throughput.svg)](https://waffle.io/ipfs/go-ipfs/metrics/throughput) @@ -39,15 +37,14 @@ Please put all issues regarding: - [Download and Compile IPFS](#download-and-compile-ipfs) - [Troubleshooting](#troubleshooting) - [Development Dependencies](#development-dependencies) - - [Updating](#updating) + - [Updating](#updating-go-ipfs) - [Usage](#usage) - [Getting Started](#getting-started) - [Some things to try](#some-things-to-try) - [Docker usage](#docker-usage) - [Troubleshooting](#troubleshooting-1) +- [Development](#development) - [Contributing](#contributing) - - [Want to hack on IPFS?](#want-to-hack-on-ipfs) - - [Want to read our code?](#want-to-read-our-code) - [License](#license) ## Security Issues @@ -88,13 +85,15 @@ You can also download go-ipfs from this project's GitHub releases page if you ar In Arch Linux go-ipfs is available as [go-ipfs](https://www.archlinux.org/packages/community/x86_64/go-ipfs/) package. - $ sudo pacman -S go-ipfs +``` +$ sudo pacman -S go-ipfs +``` Development version of go-ipfs is also on AUR under [go-ipfs-git](https://aur.archlinux.org/packages/go-ipfs-git/). You can install it using your favourite AUR Helper or manually from AUR. -### Nix +#### Nix For Linux and MacOSX you can use the purely functional package manager [Nix](https://nixos.org/nix/): @@ -107,7 +106,9 @@ You can also install the Package by using it's attribute name, which is also `ip With snap, in any of the [supported Linux distributions](https://snapcraft.io/docs/core/install): - $ sudo snap install ipfs +``` +$ sudo snap install ipfs +``` ### Build from Source @@ -115,7 +116,6 @@ With snap, in any of the [supported Linux distributions](https://snapcraft.io/do The build process for ipfs requires Go 1.10 or higher. If you don't have it: [Download Go 1.10+](https://golang.org/dl/). - You'll need to add Go's bin directories to your `$PATH` environment variable e.g., by adding these lines to your `/etc/profile` (for a system-wide installation) or `$HOME/.profile`: ``` @@ -153,43 +153,44 @@ mismatched APIs. #### Troubleshooting -* Separate [instructions are available for building on Windows](docs/windows.md). -* Also, [instructions for OpenBSD](docs/openbsd.md). -* `git` is required in order for `go get` to fetch all dependencies. -* Package managers often contain out-of-date `golang` packages. +- Separate [instructions are available for building on Windows](docs/windows.md). +- Also, [instructions for OpenBSD](docs/openbsd.md). +- `git` is required in order for `go get` to fetch all dependencies. +- Package managers often contain out-of-date `golang` packages. Ensure that `go version` reports at least 1.10. See above for how to install go. -* If you are interested in development, please install the development +- If you are interested in development, please install the development dependencies as well. -* *WARNING: Older versions of OSX FUSE (for Mac OS X) can cause kernel panics when mounting!* +- _WARNING_: Older versions of OSX FUSE (for Mac OS X) can cause kernel panics when mounting!- We strongly recommend you use the [latest version of OSX FUSE](http://osxfuse.github.io/). (See https://github.com/ipfs/go-ipfs/issues/177) -* For more details on setting up FUSE (so that you can mount the filesystem), see the docs folder. -* Shell command completion is available in `misc/completion/ipfs-completion.bash`. Read [docs/command-completion.md](docs/command-completion.md) to learn how to install it. -* See the [init examples](https://github.com/ipfs/website/tree/master/static/docs/examples/init) for how to connect IPFS to systemd or whatever init system your distro uses. +- For more details on setting up FUSE (so that you can mount the filesystem), see the docs folder. +- Shell command completion is available in `misc/completion/ipfs-completion.bash`. Read [docs/command-completion.md](docs/command-completion.md) to learn how to install it. +- See the [init examples](https://github.com/ipfs/website/tree/master/static/docs/examples/init) for how to connect IPFS to systemd or whatever init system your distro uses. -### Development Dependencies +### Updating go-ipfs -If you make changes to the protocol buffers, you will need to install the [protoc compiler](https://github.com/google/protobuf). +#### Using ipfs-update -### Updating - -#### Updating using ipfs-update IPFS has an updating tool that can be accessed through `ipfs update`. The tool is not installed alongside IPFS in order to keep that logic independent of the main codebase. To install `ipfs update`, [download it here](https://ipfs.io/ipns/dist.ipfs.io/#ipfs-update). #### Downloading IPFS builds using IPFS + List the available versions of go-ipfs: + ``` $ ipfs cat /ipns/dist.ipfs.io/go-ipfs/versions ``` Then, to view available builds for a version from the previous command ($VERSION): + ``` $ ipfs ls /ipns/dist.ipfs.io/go-ipfs/$VERSION ``` To download a given build of a version: + ``` $ ipfs get /ipns/dist.ipfs.io/go-ipfs/$VERSION/go-ipfs_$VERSION_darwin-386.tar.gz # darwin 32-bit build $ ipfs get /ipns/dist.ipfs.io/go-ipfs/$VERSION/go-ipfs_$VERSION_darwin-amd64.tar.gz # darwin 64-bit build @@ -317,41 +318,72 @@ Stop the running container: ### Troubleshooting -If you have previously installed IPFS before and you are running into -problems getting a newer version to work, try deleting (or backing up somewhere -else) your IPFS config directory (~/.ipfs by default) and rerunning `ipfs init`. -This will reinitialize the config file to its defaults and clear out the local -datastore of any bad entries. +If you have previously installed IPFS before and you are running into problems getting a newer version to work, try deleting (or backing up somewhere else) your IPFS config directory (~/.ipfs by default) and rerunning `ipfs init`. This will reinitialize the config file to its defaults and clear out the local datastore of any bad entries. -Please direct general questions and help requests to our -[forum](https://discuss.ipfs.io) or our IRC channel (freenode #ipfs). +Please direct general questions and help requests to our [forum](https://discuss.ipfs.io) or our IRC channel (freenode #ipfs). -If you believe you've found a bug, check the [issues list](https://github.com/ipfs/go-ipfs/issues) -and, if you don't see your problem there, either come talk to us on IRC (freenode #ipfs) or -file an issue of your own! +If you believe you've found a bug, check the [issues list](https://github.com/ipfs/go-ipfs/issues) and, if you don't see your problem there, either come talk to us on IRC (freenode #ipfs) or file an issue of your own! + +## Development + +This is a simple description of where the codebase stands. There are multiple subpackages: + +- `bitswap` - the block exchange +- `blocks` - handles dealing with individual blocks and sharding files +- `blockservice` - handles getting and storing blocks +- `cmd/ipfs` - cli ipfs tool - the main **entrypoint** atm +- `config` - load/edit configuration +- `core` - the core node, joins all the pieces +- `fuse/readonly` - mount `/ipfs` as a readonly fuse fs +- `importer` - import files into ipfs +- `merkledag` - merkle dag data structure +- `path` - path resolution over merkledag data structure +- `peer` - identity + addresses of local and remote peers +- `routing` - the routing system +- `routing/dht` - the DHT default routing system implementation +- `swarm` - connection multiplexing, many peers and many transports +- `util` - various utilities + +Some places to get you started on the codebase: + +- Main file: [cmd/ipfs/main.go](https://github.com/ipfs/go-ipfs/blob/master/cmd/ipfs/main.go) +- CLI Commands: [core/commands/](https://github.com/ipfs/go-ipfs/tree/master/core/commands) +- Bitswap (the data trading engine): [exchange/bitswap/](https://github.com/ipfs/go-ipfs/tree/master/exchange/bitswap) +- libp2p + - libp2p: https://github.com/libp2p/go-libp2p + - DHT: https://github.com/libp2p/go-libp2p-kad-dht + - PubSub: https://github.com/libp2p/go-floodsub + +### CLI, HTTP-API, Architecture Diagram + +![](./docs/cli-http-api-core-diagram.png) + +> [Origin](https://github.com/ipfs/pm/pull/678#discussion_r210410924) + +Description: Dotted means "likely going away". The "Legacy" parts are thin wrappers around some commands to translate between the new system and the old system. The grayed-out parts on the "daemon" diagram are there to show that the code is all the same, it's just that we turn some pieces on and some pieces off depending on whether we're running on the client or the server. + +### Testing + +``` +make test +``` + +### Development Dependencies + +If you make changes to the protocol buffers, you will need to install the [protoc compiler](https://github.com/google/protobuf). + +### Developer Notes + +Find more documentation for developers on [docs](./docs) ## Contributing +[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md) + We ❤️ all [our contributors](docs/AUTHORS); this project wouldn’t be what it is without you! If you want to help out, please see [Contribute.md](contribute.md). This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). -### Want to hack on IPFS? - -[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md) - -### Want to read our code? - -Some places to get you started. (WIP) - -Main file: [cmd/ipfs/main.go](https://github.com/ipfs/go-ipfs/blob/master/cmd/ipfs/main.go)
-CLI Commands: [core/commands/](https://github.com/ipfs/go-ipfs/tree/master/core/commands)
-Bitswap (the data trading engine): [exchange/bitswap/](https://github.com/ipfs/go-ipfs/tree/master/exchange/bitswap) - -DHT: https://github.com/libp2p/go-libp2p-kad-dht
-PubSub: https://github.com/libp2p/go-floodsub
-libp2p: https://github.com/libp2p/go-libp2p - ## License -MIT +[MIT](./LICENSE) diff --git a/dev.md b/dev.md deleted file mode 100644 index eb3da1255..000000000 --- a/dev.md +++ /dev/null @@ -1,62 +0,0 @@ -# go-ipfs development - -This is a simple description of where the codebase stands. - -There are multiple subpackages: - -- `bitswap` - the block exchange -- `blocks` - handles dealing with individual blocks and sharding files -- `blockservice` - handles getting and storing blocks -- `cmd/ipfs` - cli ipfs tool - the main **entrypoint** atm -- `config` - load/edit configuration -- `core` - the core node, joins all the pieces -- `fuse/readonly` - mount `/ipfs` as a readonly fuse fs -- `importer` - import files into ipfs -- `merkledag` - merkle dag data structure -- `path` - path resolution over merkledag data structure -- `peer` - identity + addresses of local and remote peers -- `routing` - the routing system -- `routing/dht` - the DHT default routing system implementation -- `swarm` - connection multiplexing, many peers and many transports -- `util` - various utilities - - -### What's done: - -- merkle dag data structure -- path resolution over merkle dag -- local storage of blocks -- basic file import/export (`ipfs add`, `ipfs cat`) -- mounting `/ipfs` (try `{cat, ls} /ipfs/`) -- multiplexing connections (tcp atm) -- peer addressing -- dht - impl basic kademlia routing -- bitswap - impl basic block exchange functionality -- crypto - building trust between peers in the network -- block splitting on import - Rabin fingerprints, etc - -### What's in progress: - -- ipns - impl `/ipns` obj publishing + path resolution -- expose objects to the web at `http://ipfs.io/` - - -### What's next: - -- version control - `commit` like data structure -- more... - -## Cool demos - -A list of cool demos to work towards - -- boot a VM from an image in ipfs -- boot a VM from a filesystem tree in ipfs -- publish static websites directly from ipfs -- expose objects to the web at `http://ipfs.io/` -- mounted auto-committing versioned personal dropbox -- mounted encrypted personal/group dropbox -- mounted {npm, apt, other pkg manager} registry -- open a video on ipfs, stream it in -- watch a video with a topology of 1 seed N leechers (N ~100) -- more in section 3.8 in the [paper](https://github.com/ipfs/ipfs/blob/master/papers/ipfs-cap2pfs/ipfs-p2p-file-system.pdf) diff --git a/docs/cli-http-api-core-diagram.png b/docs/cli-http-api-core-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..b44bf936cc64361c852604c7c5d4d0e0997ee753 GIT binary patch literal 55031 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 Is`d7N0Jr4b)c^nh literal 0 HcmV?d00001 From 9cd3fca50f116eb7914784b09ddd573dfcb19126 Mon Sep 17 00:00:00 2001 From: David Dias Date: Wed, 22 Aug 2018 21:27:04 +0200 Subject: [PATCH 06/13] docs: apply CR License: MIT Signed-off-by: diasdavid --- README.md | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/README.md b/README.md index 8404145a8..715d84a3d 100644 --- a/README.md +++ b/README.md @@ -326,29 +326,11 @@ If you believe you've found a bug, check the [issues list](https://github.com/ip ## Development -This is a simple description of where the codebase stands. There are multiple subpackages: - -- `bitswap` - the block exchange -- `blocks` - handles dealing with individual blocks and sharding files -- `blockservice` - handles getting and storing blocks -- `cmd/ipfs` - cli ipfs tool - the main **entrypoint** atm -- `config` - load/edit configuration -- `core` - the core node, joins all the pieces -- `fuse/readonly` - mount `/ipfs` as a readonly fuse fs -- `importer` - import files into ipfs -- `merkledag` - merkle dag data structure -- `path` - path resolution over merkledag data structure -- `peer` - identity + addresses of local and remote peers -- `routing` - the routing system -- `routing/dht` - the DHT default routing system implementation -- `swarm` - connection multiplexing, many peers and many transports -- `util` - various utilities - Some places to get you started on the codebase: - Main file: [cmd/ipfs/main.go](https://github.com/ipfs/go-ipfs/blob/master/cmd/ipfs/main.go) - CLI Commands: [core/commands/](https://github.com/ipfs/go-ipfs/tree/master/core/commands) -- Bitswap (the data trading engine): [exchange/bitswap/](https://github.com/ipfs/go-ipfs/tree/master/exchange/bitswap) +- Bitswap (the data trading engine): [exchange/bitswap/](hhttps://github.com/ipfs/go-bitswap) - libp2p - libp2p: https://github.com/libp2p/go-libp2p - DHT: https://github.com/libp2p/go-libp2p-kad-dht From 991ae7cda438d10c14c869548e4f0f16c7fb6e19 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 22 Aug 2018 23:08:38 +0000 Subject: [PATCH 07/13] README: fix bitswap link And clarify that it's a separate package. License: MIT Signed-off-by: Steven Allen --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 715d84a3d..f791c05b3 100644 --- a/README.md +++ b/README.md @@ -328,9 +328,9 @@ If you believe you've found a bug, check the [issues list](https://github.com/ip Some places to get you started on the codebase: -- Main file: [cmd/ipfs/main.go](https://github.com/ipfs/go-ipfs/blob/master/cmd/ipfs/main.go) -- CLI Commands: [core/commands/](https://github.com/ipfs/go-ipfs/tree/master/core/commands) -- Bitswap (the data trading engine): [exchange/bitswap/](hhttps://github.com/ipfs/go-bitswap) +- Main file: [./cmd/ipfs/main.go](https://github.com/ipfs/go-ipfs/blob/master/cmd/ipfs/main.go) +- CLI Commands: [./core/commands/](https://github.com/ipfs/go-ipfs/tree/master/core/commands) +- Bitswap (the data trading engine): [go-bitswap](https://github.com/ipfs/go-bitswap) - libp2p - libp2p: https://github.com/libp2p/go-libp2p - DHT: https://github.com/libp2p/go-libp2p-kad-dht From 43f4147d2510d9ed458daf2cd6c548e9b64617bb Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 22 Aug 2018 17:14:56 -0700 Subject: [PATCH 08/13] fix the urlstore tests 1. GC the intentionally broken files so we don't reuse their blocks. 2. Make sure we actually check the *new* output file instead of an old one. License: MIT Signed-off-by: Steven Allen --- test/sharness/t0272-urlstore.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/sharness/t0272-urlstore.sh b/test/sharness/t0272-urlstore.sh index bc33efbd7..e646569d5 100755 --- a/test/sharness/t0272-urlstore.sh +++ b/test/sharness/t0272-urlstore.sh @@ -57,6 +57,7 @@ test_expect_success "make sure hashes are different" ' ' test_expect_success "get files via urlstore" ' + rm -f file1.actual file2.actual && ipfs get $HASH1 -o file1.actual && test_cmp file1 file1.actual && ipfs get $HASH2 -o file2.actual && @@ -111,6 +112,11 @@ test_expect_success "files can not be retrieved via the urlstore" ' test_must_fail ipfs cat $HASH2 > /dev/null ' +test_expect_success "remove broken files" ' + ipfs pin rm $HASH1 $HASH2 && + ipfs repo gc > /dev/null +' + test_expect_success "add large file using gateway address via url store" ' HASH3=$(ipfs urlstore add http://127.0.0.1:$GWAY_PORT/ipfs/$HASH3a) ' @@ -120,6 +126,7 @@ test_expect_success "make sure hashes are different" ' ' test_expect_success "get large file via urlstore" ' + rm -f file3.actual && ipfs get $HASH3 -o file3.actual && test_cmp file3 file3.actual ' From 501abdd1303a10c42ca38f7619e03c44938fe282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rau=CC=81l=20Kripalani?= Date: Sat, 11 Aug 2018 13:29:57 +0100 Subject: [PATCH 09/13] WIP DNS resolution for API endpoint on ipfs bin (#5249) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit License: MIT Signed-off-by: Raúl Kripalani --- cmd/ipfs/main.go | 33 +++++++++++++++++++++++++++------ package.json | 6 ++++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/cmd/ipfs/main.go b/cmd/ipfs/main.go index c39e94041..1f8b82637 100644 --- a/cmd/ipfs/main.go +++ b/cmd/ipfs/main.go @@ -34,6 +34,7 @@ import ( osh "gx/ipfs/QmXuBJ7DR6k3rmUEKtvVMhwjmXDuJgXXPUt4LQXKBMsU93/go-os-helper" ma "gx/ipfs/QmYmsdtJ3HsodkePE3eU3TsCaP2YvPZJ4LoXnNkDE5Tpt7/go-multiaddr" loggables "gx/ipfs/QmZ4zF1mBrt8C2mSCM4ZYE4aAnv78f7GvrzufJC4G5tecK/go-libp2p-loggables" + mdns "gx/ipfs/QmfXU2MhWoegxHoeMd3A2ytL2P6CY4FfqGWc23LTNWBwZt/go-multiaddr-dns" ) // log is the command logger @@ -235,7 +236,7 @@ func commandShouldRunOnDaemon(details cmdDetails, req *cmds.Request, cctx *oldcm // did user specify an api to use for this command? apiAddrStr, _ := req.Options[corecmds.ApiOption].(string) - client, err := getApiClient(cctx.ConfigRoot, apiAddrStr) + client, err := getApiClient(req.Context, cctx.ConfigRoot, apiAddrStr) if err == repo.ErrApiNotRunning { if apiAddrStr != "" && req.Command != daemonCmd { // if user SPECIFIED an api, and this cmd is not daemon @@ -406,7 +407,7 @@ var checkIPFSWinFmt = "Otherwise check:\n\ttasklist | findstr ipfs" // getApiClient checks the repo, and the given options, checking for // a running API service. if there is one, it returns a client. // otherwise, it returns errApiNotRunning, or another error. -func getApiClient(repoPath, apiAddrStr string) (http.Client, error) { +func getApiClient(ctx context.Context, repoPath, apiAddrStr string) (http.Client, error) { var apiErrorFmt string switch { case osh.IsUnix(): @@ -440,14 +441,34 @@ func getApiClient(repoPath, apiAddrStr string) (http.Client, error) { if len(addr.Protocols()) == 0 { return nil, fmt.Errorf(apiErrorFmt, repoPath, "multiaddr doesn't provide any protocols") } - return apiClientForAddr(addr) + return apiClientForAddr(ctx, addr) } -func apiClientForAddr(addr ma.Multiaddr) (http.Client, error) { - _, host, err := manet.DialArgs(addr) +func apiClientForAddr(ctx context.Context, addr ma.Multiaddr) (http.Client, error) { + addrs, err := mdns.Resolve(ctx, addr) if err != nil { return nil, err } - return http.NewClient(host, http.ClientWithAPIPrefix(corehttp.APIPath)), nil + dialer := &manet.Dialer{} + for _, addr := range addrs { + ctx, cancelFunc := context.WithTimeout(ctx, 5*time.Second) + defer cancelFunc() + + conn, err := dialer.DialContext(ctx, addr) + if err != nil { + log.Errorf("connection to %s failed, error: %s", addr, err) + continue + } + conn.Close() + + _, host, err := manet.DialArgs(addr) + if err != nil { + continue + } + + return http.NewClient(host, http.ClientWithAPIPrefix(corehttp.APIPath)), nil + } + + return nil, errors.New("non-resolvable API endpoint") } diff --git a/package.json b/package.json index 3968c405d..2d227c726 100644 --- a/package.json +++ b/package.json @@ -539,6 +539,12 @@ "hash": "QmPyxJ2QS7L5FhGkNYkNcXHGjDhvGHueJ4auqAstFHYxy5", "name": "go-cidutil", "version": "0.0.2" + }, + { + "author": "lgierth", + "hash": "QmfXU2MhWoegxHoeMd3A2ytL2P6CY4FfqGWc23LTNWBwZt", + "name": "go-multiaddr-dns", + "version": "0.2.4" } ], "gxVersion": "0.10.0", From 0ddaf58336af04c9fe822262b2e2e000fecfd4ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rau=CC=81l=20Kripalani?= Date: Sat, 11 Aug 2018 23:26:16 +0100 Subject: [PATCH 10/13] main: make --api option resolve hostnames via dns (#5249) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves #5249. Calls multiaddr-dns, and picks the first result. Uses a fixed timeout of 10 seconds. Adds test cases for one, multiple, and no DNS results. License: MIT Signed-off-by: Raúl Kripalani --- cmd/ipfs/dnsresolve_test.go | 72 +++++++++++++++++++++++++++++++++++++ cmd/ipfs/main.go | 46 +++++++++++++----------- 2 files changed, 97 insertions(+), 21 deletions(-) create mode 100644 cmd/ipfs/dnsresolve_test.go diff --git a/cmd/ipfs/dnsresolve_test.go b/cmd/ipfs/dnsresolve_test.go new file mode 100644 index 000000000..9f43be12d --- /dev/null +++ b/cmd/ipfs/dnsresolve_test.go @@ -0,0 +1,72 @@ +package main + +import ( + "context" + "fmt" + "net" + "strings" + "testing" + + ma "gx/ipfs/QmYmsdtJ3HsodkePE3eU3TsCaP2YvPZJ4LoXnNkDE5Tpt7/go-multiaddr" + madns "gx/ipfs/QmfXU2MhWoegxHoeMd3A2ytL2P6CY4FfqGWc23LTNWBwZt/go-multiaddr-dns" +) + +var ( + ctx = context.Background() + testAddr, _ = ma.NewMultiaddr("/dns4/example.com/tcp/5001") +) + +func makeResolver(n uint8) *madns.Resolver { + results := make([]net.IPAddr, n) + for i := uint8(0); i < n; i++ { + results[i] = net.IPAddr{IP: net.ParseIP(fmt.Sprintf("192.0.2.%d", i))} + } + + backend := &madns.MockBackend{ + IP: map[string][]net.IPAddr{ + "example.com": results, + }} + + return &madns.Resolver{ + Backend: backend, + } +} + +func TestApiEndpointResolveDNSOneResult(t *testing.T) { + dnsResolver = makeResolver(1) + + addr, err := resolveAddr(ctx, testAddr) + if err != nil { + t.Error(err) + } + + if ref, _ := ma.NewMultiaddr("/ip4/192.0.2.0/tcp/5001"); !addr.Equal(ref) { + t.Errorf("resolved address was different than expected") + } +} + +func TestApiEndpointResolveDNSMultipleResults(t *testing.T) { + dnsResolver = makeResolver(4) + + addr, err := resolveAddr(ctx, testAddr) + if err != nil { + t.Error(err) + } + + if ref, _ := ma.NewMultiaddr("/ip4/192.0.2.0/tcp/5001"); !addr.Equal(ref) { + t.Errorf("resolved address was different than expected") + } +} + +func TestApiEndpointResolveDNSNoResults(t *testing.T) { + dnsResolver = makeResolver(0) + + addr, err := resolveAddr(ctx, testAddr) + if addr != nil || err == nil { + t.Error("expected test address not to resolve, and to throw an error") + } + + if !strings.HasPrefix(err.Error(), "non-resolvable API endpoint") { + t.Errorf("expected error not thrown; actual: %v", err) + } +} diff --git a/cmd/ipfs/main.go b/cmd/ipfs/main.go index 1f8b82637..b02889310 100644 --- a/cmd/ipfs/main.go +++ b/cmd/ipfs/main.go @@ -34,7 +34,7 @@ import ( osh "gx/ipfs/QmXuBJ7DR6k3rmUEKtvVMhwjmXDuJgXXPUt4LQXKBMsU93/go-os-helper" ma "gx/ipfs/QmYmsdtJ3HsodkePE3eU3TsCaP2YvPZJ4LoXnNkDE5Tpt7/go-multiaddr" loggables "gx/ipfs/QmZ4zF1mBrt8C2mSCM4ZYE4aAnv78f7GvrzufJC4G5tecK/go-libp2p-loggables" - mdns "gx/ipfs/QmfXU2MhWoegxHoeMd3A2ytL2P6CY4FfqGWc23LTNWBwZt/go-multiaddr-dns" + madns "gx/ipfs/QmfXU2MhWoegxHoeMd3A2ytL2P6CY4FfqGWc23LTNWBwZt/go-multiaddr-dns" ) // log is the command logger @@ -42,6 +42,9 @@ var log = logging.Logger("cmd/ipfs") var errRequestCanceled = errors.New("request canceled") +// declared as a var for testing purposes +var dnsResolver = madns.DefaultResolver + const ( EnvEnableProfiling = "IPFS_PROF" cpuProfile = "ipfs.cpuprof" @@ -445,30 +448,31 @@ func getApiClient(ctx context.Context, repoPath, apiAddrStr string) (http.Client } func apiClientForAddr(ctx context.Context, addr ma.Multiaddr) (http.Client, error) { - addrs, err := mdns.Resolve(ctx, addr) + addr, err := resolveAddr(ctx, addr) if err != nil { return nil, err } - dialer := &manet.Dialer{} - for _, addr := range addrs { - ctx, cancelFunc := context.WithTimeout(ctx, 5*time.Second) - defer cancelFunc() - - conn, err := dialer.DialContext(ctx, addr) - if err != nil { - log.Errorf("connection to %s failed, error: %s", addr, err) - continue - } - conn.Close() - - _, host, err := manet.DialArgs(addr) - if err != nil { - continue - } - - return http.NewClient(host, http.ClientWithAPIPrefix(corehttp.APIPath)), nil + _, host, err := manet.DialArgs(addr) + if err != nil { + return nil, err } - return nil, errors.New("non-resolvable API endpoint") + return http.NewClient(host, http.ClientWithAPIPrefix(corehttp.APIPath)), nil +} + +func resolveAddr(ctx context.Context, addr ma.Multiaddr) (ma.Multiaddr, error) { + ctx, cancelFunc := context.WithTimeout(ctx, 10*time.Second) + defer cancelFunc() + + addrs, err := dnsResolver.Resolve(ctx, addr) + if err != nil { + return nil, err + } + + if len(addrs) == 0 { + return nil, errors.New("non-resolvable API endpoint") + } + + return addrs[0], nil } From 5fe05e9a16a7ccf81317bacc95a0c7c6f102bb95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rau=CC=81l=20Kripalani?= Date: Sat, 11 Aug 2018 23:49:01 +0100 Subject: [PATCH 11/13] fix linting issue (getApiClient => getAPIClient). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit License: MIT Signed-off-by: Raúl Kripalani --- cmd/ipfs/main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/ipfs/main.go b/cmd/ipfs/main.go index b02889310..7ddfab45b 100644 --- a/cmd/ipfs/main.go +++ b/cmd/ipfs/main.go @@ -239,7 +239,7 @@ func commandShouldRunOnDaemon(details cmdDetails, req *cmds.Request, cctx *oldcm // did user specify an api to use for this command? apiAddrStr, _ := req.Options[corecmds.ApiOption].(string) - client, err := getApiClient(req.Context, cctx.ConfigRoot, apiAddrStr) + client, err := getAPIClient(req.Context, cctx.ConfigRoot, apiAddrStr) if err == repo.ErrApiNotRunning { if apiAddrStr != "" && req.Command != daemonCmd { // if user SPECIFIED an api, and this cmd is not daemon @@ -407,10 +407,10 @@ If you're sure go-ipfs isn't running, you can just delete it. var checkIPFSUnixFmt = "Otherwise check:\n\tps aux | grep ipfs" var checkIPFSWinFmt = "Otherwise check:\n\ttasklist | findstr ipfs" -// getApiClient checks the repo, and the given options, checking for +// getAPIClient checks the repo, and the given options, checking for // a running API service. if there is one, it returns a client. // otherwise, it returns errApiNotRunning, or another error. -func getApiClient(ctx context.Context, repoPath, apiAddrStr string) (http.Client, error) { +func getAPIClient(ctx context.Context, repoPath, apiAddrStr string) (http.Client, error) { var apiErrorFmt string switch { case osh.IsUnix(): From 9a579499b2c2a96893eb4354f9140ee0b0de080b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rau=CC=81l=20Kripalani?= Date: Fri, 17 Aug 2018 18:30:29 +0100 Subject: [PATCH 12/13] add sharness test for DNS resolution on API CLI flag. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit License: MIT Signed-off-by: Raúl Kripalani --- test/sharness/t0236-cli-api-dns-resolve.sh | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100755 test/sharness/t0236-cli-api-dns-resolve.sh diff --git a/test/sharness/t0236-cli-api-dns-resolve.sh b/test/sharness/t0236-cli-api-dns-resolve.sh new file mode 100755 index 000000000..ef217d127 --- /dev/null +++ b/test/sharness/t0236-cli-api-dns-resolve.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2015 Jeromy Johnson +# MIT Licensed; see the LICENSE file in this repository. +# + +test_description="test dns resolution of api endpoint by cli" + +. lib/test-lib.sh + +test_init_ipfs + +# this test uses the localtest.me domain which resolves to 127.0.0.1 +# see http://readme.localtest.me/ +# in case if failure, check A record of that domain +test_expect_success "can make http request against dns resolved nc server" ' + nc -ld 5005 > nc_out & + NCPID=$! + go-sleep 0.5s && kill "$NCPID" & + ipfs cat /ipfs/Qmabcdef --api /dns4/localtest.me/tcp/5005 || true +' + +test_expect_success "request was received by local nc server" ' + grep "POST /api/v0/cat" nc_out +' + +test_done From c89102a01c6cc0c8fef0317062770e5075a5fc7e Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 23 Aug 2018 01:13:41 +0000 Subject: [PATCH 13/13] dns test: switch to localhost License: MIT Signed-off-by: Steven Allen --- test/sharness/t0236-cli-api-dns-resolve.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/sharness/t0236-cli-api-dns-resolve.sh b/test/sharness/t0236-cli-api-dns-resolve.sh index ef217d127..d53ce6a01 100755 --- a/test/sharness/t0236-cli-api-dns-resolve.sh +++ b/test/sharness/t0236-cli-api-dns-resolve.sh @@ -10,14 +10,11 @@ test_description="test dns resolution of api endpoint by cli" test_init_ipfs -# this test uses the localtest.me domain which resolves to 127.0.0.1 -# see http://readme.localtest.me/ -# in case if failure, check A record of that domain test_expect_success "can make http request against dns resolved nc server" ' nc -ld 5005 > nc_out & NCPID=$! go-sleep 0.5s && kill "$NCPID" & - ipfs cat /ipfs/Qmabcdef --api /dns4/localtest.me/tcp/5005 || true + ipfs cat /ipfs/Qmabcdef --api /dns4/localhost/tcp/5005 || true ' test_expect_success "request was received by local nc server" '