diff --git a/cmd/ipfs/bootstrap.go b/cmd/ipfs/bootstrap.go new file mode 100644 index 000000000..68a1ad633 --- /dev/null +++ b/cmd/ipfs/bootstrap.go @@ -0,0 +1,251 @@ +package main + +import ( + "errors" + "strings" + + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/gonuts/flag" + "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/commander" + ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr" + mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash" + + config "github.com/jbenet/go-ipfs/config" + peer "github.com/jbenet/go-ipfs/peer" + u "github.com/jbenet/go-ipfs/util" +) + +var cmdIpfsBootstrap = &commander.Command{ + UsageLine: "bootstrap", + Short: "Show a list of bootstrapped addresses.", + Long: `ipfs bootstrap - show, or manipulate bootstrap node addresses + +Running 'ipfs bootstrap' with no arguments will run 'ipfs bootstrap list'. + +Commands: + + list Show the boostrap list. + add <address> Add a node's address to the bootstrap list. + remove <address> Remove an address from the bootstrap list. + +` + bootstrapSecurityWarning, + Run: bootstrapListCmd, + Subcommands: []*commander.Command{ + cmdIpfsBootstrapRemove, + cmdIpfsBootstrapAdd, + cmdIpfsBootstrapList, + }, + Flag: *flag.NewFlagSet("ipfs-bootstrap", flag.ExitOnError), +} + +var cmdIpfsBootstrapRemove = &commander.Command{ + UsageLine: "remove <address | peerid>", + Short: "Remove addresses from the bootstrap list.", + Long: `ipfs bootstrap remove - remove addresses from the bootstrap list +` + bootstrapSecurityWarning, + Run: bootstrapRemoveCmd, + Flag: *flag.NewFlagSet("ipfs-bootstrap-remove", flag.ExitOnError), +} + +var cmdIpfsBootstrapAdd = &commander.Command{ + UsageLine: "add <address | peerid>", + Short: "Add addresses to the bootstrap list.", + Long: `ipfs bootstrap add - add addresses to the bootstrap list +` + bootstrapSecurityWarning, + Run: bootstrapAddCmd, + Flag: *flag.NewFlagSet("ipfs-bootstrap-add", flag.ExitOnError), +} + +var cmdIpfsBootstrapList = &commander.Command{ + UsageLine: "list", + Short: "Show addresses in the bootstrap list.", + Run: bootstrapListCmd, + Flag: *flag.NewFlagSet("ipfs-bootstrap-list", flag.ExitOnError), +} + +func bootstrapRemoveCmd(c *commander.Command, inp []string) error { + + if len(inp) == 0 { + return errors.New("remove: no address or peerid specified") + } + + toRemove, err := bootstrapInputToPeers(inp) + if err != nil { + return err + } + + cfg, err := getConfig(c) + if err != nil { + return err + } + + keep := []*config.BootstrapPeer{} + remove := []*config.BootstrapPeer{} + + // function to filer what to keep + shouldKeep := func(bp *config.BootstrapPeer) bool { + for _, skipBP := range toRemove { + + // IDs must match to skip. + if bp.PeerID != skipBP.PeerID { + continue + } + + // if Addresses match, or skipBP has no addr (wildcard) + if skipBP.Address == bp.Address || skipBP.Address == "" { + return false + } + } + return true + } + + // filter all the existing peers + for _, currBP := range cfg.Bootstrap { + if shouldKeep(currBP) { + keep = append(keep, currBP) + } else { + remove = append(remove, currBP) + } + } + + // if didn't remove anyone, bail. + if len(keep) == len(cfg.Bootstrap) { + return errors.New("remove: peer given did not match any in list") + } + + // write new config + cfg.Bootstrap = keep + if err := writeConfig(c, cfg); err != nil { + return err + } + + for _, bp := range remove { + u.POut("removed %s\n", bp) + } + return nil +} + +func bootstrapAddCmd(c *commander.Command, inp []string) error { + + if len(inp) == 0 { + return errors.New("add: no address specified") + } + + toAdd, err := bootstrapInputToPeers(inp) + if err != nil { + return err + } + + cfg, err := getConfig(c) + if err != nil { + return err + } + + // function to check whether a peer is already in the list. + combine := func(lists ...[]*config.BootstrapPeer) []*config.BootstrapPeer { + + set := map[string]struct{}{} + final := []*config.BootstrapPeer{} + + for _, list := range lists { + for _, peer := range list { + // if already in the set, continue + _, found := set[peer.String()] + if found { + continue + } + + set[peer.String()] = struct{}{} + final = append(final, peer) + } + } + return final + } + + // combine both lists, removing dups. + cfg.Bootstrap = combine(cfg.Bootstrap, toAdd) + if err := writeConfig(c, cfg); err != nil { + return err + } + + for _, bp := range toAdd { + u.POut("added %s\n", bp) + } + return nil +} + +func bootstrapListCmd(c *commander.Command, inp []string) error { + + cfg, err := getConfig(c) + if err != nil { + return err + } + + for _, bp := range cfg.Bootstrap { + u.POut("%s\n", bp) + } + + return nil +} + +func writeConfig(c *commander.Command, cfg *config.Config) error { + + confdir, err := getConfigDir(c) + if err != nil { + return err + } + + filename, err := config.Filename(confdir) + if err != nil { + return err + } + + return config.WriteConfigFile(filename, cfg) +} + +func bootstrapInputToPeers(input []string) ([]*config.BootstrapPeer, error) { + split := func(addr string) (string, string) { + idx := strings.LastIndex(addr, "/") + if idx == -1 { + return "", addr + } + return addr[:idx], addr[idx+1:] + } + + peers := []*config.BootstrapPeer{} + for _, addr := range input { + addrS, peeridS := split(addr) + + // make sure addrS parses as a multiaddr. + if len(addrS) > 0 { + maddr, err := ma.NewMultiaddr(addrS) + if err != nil { + return nil, err + } + + addrS = maddr.String() + } + + // make sure idS parses as a peer.ID + peerid, err := mh.FromB58String(peeridS) + if err != nil { + return nil, err + } + + // construct config entry + peers = append(peers, &config.BootstrapPeer{ + Address: addrS, + PeerID: peer.ID(peerid).Pretty(), + }) + } + return peers, nil +} + +const bootstrapSecurityWarning = ` +SECURITY WARNING: + +The bootstrap command manipulates the "bootstrap list", which contains +the addresses of bootstrap nodes. These are the *trusted peers* from +which to learn about other peers in the network. Only edit this list +if you understand the risks of adding or removing nodes from this list. + +` diff --git a/cmd/ipfs/ipfs.go b/cmd/ipfs/ipfs.go index 8031f2754..e3d362913 100644 --- a/cmd/ipfs/ipfs.go +++ b/cmd/ipfs/ipfs.go @@ -56,6 +56,7 @@ Use "ipfs help <command>" for more information about a command. cmdIpfsServe, cmdIpfsRun, cmdIpfsName, + cmdIpfsBootstrap, }, Flag: *flag.NewFlagSet("ipfs", flag.ExitOnError), } @@ -122,23 +123,32 @@ func localNode(confdir string, online bool) (*core.IpfsNode, error) { // Gets the config "-c" flag from the command, or returns // the default configuration root directory func getConfigDir(c *commander.Command) (string, error) { - root := c - for root.Parent != nil { - root = root.Parent - } - conf := root.Flag.Lookup("c").Value.Get() - if conf == nil { - return config.PathRoot() - } - confStr, ok := conf.(string) - if !ok { - return "", errors.New("failed to retrieve config flag value") - } - if len(confStr) == 0 { - return config.PathRoot() + + // use the root cmd (that's where config is specified) + for ; c.Parent != nil; c = c.Parent { } - return u.TildeExpansion(confStr) + // flag should be defined on root. + param := c.Flag.Lookup("c").Value.Get().(string) + if param != "" { + return u.TildeExpansion(param) + } + + return config.PathRoot() +} + +func getConfig(c *commander.Command) (*config.Config, error) { + confdir, err := getConfigDir(c) + if err != nil { + return nil, err + } + + filename, err := config.Filename(confdir) + if err != nil { + return nil, err + } + + return config.Load(filename) } // cmdContext is a wrapper structure that keeps a node, a daemonlistener, and diff --git a/config/config.go b/config/config.go index 5a25118bd..5acc23c4d 100644 --- a/config/config.go +++ b/config/config.go @@ -41,6 +41,10 @@ type BootstrapPeer struct { PeerID string // until multiaddr supports ipfs, use another field. } +func (bp *BootstrapPeer) String() string { + return bp.Address + "/" + bp.PeerID +} + // Config is used to load IPFS config files. type Config struct { Identity Identity // local node's peer identity